mirror of
https://github.com/google/pebble
synced 2026-10-17 08:44:11 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "drivers/rng.h"
|
||||
#include "services/common/bluetooth/ble_root_keys.h"
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "system/hexdump.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include <bluetooth/sm_types.h>
|
||||
|
||||
static void prv_generate_root_keys(SM128BitKey *keys_out) {
|
||||
uint8_t *rand_buffer = (uint8_t *)keys_out;
|
||||
|
||||
int tries_left = 20;
|
||||
// rng_rand generates only 4 bytes of random data at a time. Loop to fill up the whole array:
|
||||
for (uint32_t i = 0; i < ((sizeof(SM128BitKey) * SMRootKeyTypeNum) / sizeof(uint32_t)); ++i) {
|
||||
while (tries_left) {
|
||||
const bool success = rng_rand((uint32_t *) &rand_buffer[i * sizeof(uint32_t)]);
|
||||
if (success) {
|
||||
break;
|
||||
}
|
||||
--tries_left;
|
||||
}
|
||||
}
|
||||
if (tries_left == 0) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "rng_rand() failed too many times, falling back to rand()");
|
||||
// Fall back to rand():
|
||||
for (uint32_t i = 0; i < (sizeof(SM128BitKey) * SMRootKeyTypeNum); ++i) {
|
||||
rand_buffer[i] = rand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ble_root_keys_get_and_generate_if_needed(SM128BitKey *keys_out) {
|
||||
SM128BitKey *enc_key = &keys_out[SMRootKeyTypeEncryption];
|
||||
SM128BitKey *id_key = &keys_out[SMRootKeyTypeIdentity];
|
||||
|
||||
bool is_existing = false;
|
||||
if (bt_persistent_storage_get_root_key(SMRootKeyTypeIdentity, id_key) &&
|
||||
bt_persistent_storage_get_root_key(SMRootKeyTypeEncryption, enc_key)) {
|
||||
is_existing = true;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
prv_generate_root_keys(keys_out);
|
||||
|
||||
finally:
|
||||
#ifndef RELEASE
|
||||
PBL_LOG(LOG_LEVEL_INFO, "BLE Root Keys (existing=%u):", is_existing);
|
||||
PBL_HEXDUMP(LOG_LEVEL_INFO, (const uint8_t *)keys_out, 2 * sizeof(SM128BitKey));
|
||||
#endif
|
||||
if (!is_existing) {
|
||||
bt_persistent_storage_set_root_keys(keys_out);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/sm_types.h>
|
||||
|
||||
//! @param[out] keys_out Array of at least SMRootKeyTypeNum elements in which the root keys should
|
||||
//! be copied.
|
||||
void ble_root_keys_get_and_generate_if_needed(SM128BitKey *keys_out);
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define FILE_LOG_COLOR LOG_COLOR_BLUE
|
||||
#include "system/logging.h"
|
||||
|
||||
#include "console/dbgserial.h"
|
||||
#include "comm/ble/gap_le.h"
|
||||
#include "comm/ble/gatt_client_subscriptions.h"
|
||||
#include "drivers/clocksource.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/util/stop.h"
|
||||
#include "pebble_errors.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/bluetooth/ble_root_keys.h"
|
||||
#include "services/common/bluetooth/bluetooth_ctl.h"
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "services/common/bluetooth/dis.h"
|
||||
#include "services/common/bluetooth/local_addr.h"
|
||||
#include "services/common/bluetooth/local_id.h"
|
||||
#include "services/common/bluetooth/pairability.h"
|
||||
#include "services/common/regular_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "services/normal/bluetooth/ble_hrm.h"
|
||||
#include "os/mutex.h"
|
||||
|
||||
#include <bluetooth/init.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static bool s_comm_initialized = false;
|
||||
static bool s_comm_airplane_mode_on = false;
|
||||
static bool s_comm_enabled = false;
|
||||
static bool s_comm_is_running = false;
|
||||
static bool s_comm_state_change_eval_is_scheduled;
|
||||
static BtCtlModeOverride s_comm_override = BtCtlModeOverrideNone;
|
||||
static PebbleMutex *s_comm_state_change_mutex;
|
||||
|
||||
bool bt_ctl_is_airplane_mode_on(void) {
|
||||
return s_comm_airplane_mode_on;
|
||||
}
|
||||
|
||||
bool bt_ctl_is_bluetooth_active(void) {
|
||||
if (s_comm_enabled) {
|
||||
if (s_comm_override == BtCtlModeOverrideRun) {
|
||||
return true;
|
||||
} else if (s_comm_override == BtCtlModeOverrideNone && !s_comm_airplane_mode_on) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bt_ctl_is_bluetooth_running(void) {
|
||||
return s_comm_is_running;
|
||||
}
|
||||
|
||||
static void prv_put_disconnection_event(void) {
|
||||
PebbleEvent event = (PebbleEvent) {
|
||||
.type = PEBBLE_BT_CONNECTION_EVENT,
|
||||
.bluetooth.connection = {
|
||||
.is_ble = true,
|
||||
.state = PebbleBluetoothConnectionEventStateDisconnected,
|
||||
}
|
||||
};
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "New BT Conn change event, We are now disconnected");
|
||||
event_put(&event);
|
||||
}
|
||||
|
||||
static void prv_comm_start(void) {
|
||||
if (s_comm_is_running) {
|
||||
return;
|
||||
}
|
||||
stop_mode_disable(InhibitorCommMode);
|
||||
// Heap allocated to reduce stack usage
|
||||
BTDriverConfig *config = kernel_zalloc_check(sizeof(BTDriverConfig));
|
||||
ble_root_keys_get_and_generate_if_needed(config->root_keys);
|
||||
dis_get_info(&config->dis_info);
|
||||
#if CAPABILITY_HAS_BUILTIN_HRM
|
||||
config->is_hrm_supported_and_enabled = ble_hrm_is_supported_and_enabled();
|
||||
PBL_LOG(LOG_LEVEL_INFO, "BLE HRM sharing prefs: is_enabled=%u",
|
||||
config->is_hrm_supported_and_enabled);
|
||||
#endif
|
||||
s_comm_is_running = bt_driver_start(config);
|
||||
kernel_free(config);
|
||||
|
||||
if (s_comm_is_running) {
|
||||
bt_local_addr_init();
|
||||
bt_persistent_storage_register_existing_ble_bondings();
|
||||
gap_le_init();
|
||||
bt_local_id_configure_driver();
|
||||
#if CAPABILITY_HAS_BUILTIN_HRM
|
||||
ble_hrm_init();
|
||||
#endif
|
||||
bt_pairability_init();
|
||||
analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_BT_OFF_TIME);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "BT driver failed to start!");
|
||||
// FIXME: PBL-36163 -- handle this better
|
||||
}
|
||||
|
||||
stop_mode_enable(InhibitorCommMode);
|
||||
}
|
||||
|
||||
static void prv_comm_stop(void) {
|
||||
if (!s_comm_is_running) {
|
||||
return;
|
||||
}
|
||||
stop_mode_disable(InhibitorCommMode);
|
||||
#if CAPABILITY_HAS_BUILTIN_HRM
|
||||
ble_hrm_deinit();
|
||||
#endif
|
||||
gap_le_deinit();
|
||||
|
||||
// Should be the last thing to happen that touches the Bluetooth controller directly
|
||||
bt_driver_stop();
|
||||
stop_mode_enable(InhibitorCommMode);
|
||||
analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_BT_OFF_TIME, AnalyticsClient_System);
|
||||
s_comm_is_running = false;
|
||||
|
||||
// This is a legacy event used to update the Settings app.
|
||||
prv_put_disconnection_event();
|
||||
}
|
||||
|
||||
static void prv_send_state_change_event(void) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "----> Sending a BT state event");
|
||||
PebbleEvent event = {
|
||||
.type = PEBBLE_BT_STATE_EVENT,
|
||||
.bluetooth = {
|
||||
.state = {
|
||||
.airplane = s_comm_airplane_mode_on,
|
||||
.enabled = s_comm_enabled,
|
||||
.override = s_comm_override,
|
||||
},
|
||||
},
|
||||
};
|
||||
event_put(&event);
|
||||
}
|
||||
|
||||
static void prv_comm_state_change(void *context) {
|
||||
static bool s_first_run = true;
|
||||
mutex_lock(s_comm_state_change_mutex);
|
||||
s_comm_state_change_eval_is_scheduled = false;
|
||||
bool is_active_mode = bt_ctl_is_bluetooth_active();
|
||||
if (is_active_mode != s_comm_is_running) {
|
||||
if (is_active_mode) {
|
||||
prv_comm_start();
|
||||
} else {
|
||||
prv_comm_stop();
|
||||
}
|
||||
// Only send event if state changed successfully:
|
||||
if (is_active_mode == s_comm_is_running) {
|
||||
prv_send_state_change_event();
|
||||
}
|
||||
} else if (!s_comm_is_running && s_first_run) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Shutting down the BT stack on boot");
|
||||
bt_driver_power_down_controller_on_boot();
|
||||
}
|
||||
|
||||
s_first_run = false;
|
||||
mutex_unlock(s_comm_state_change_mutex);
|
||||
}
|
||||
|
||||
void bt_ctl_set_enabled(bool enabled) {
|
||||
if (!s_comm_initialized) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Error: Bluetooth isn't initialized yet");
|
||||
return;
|
||||
}
|
||||
mutex_lock(s_comm_state_change_mutex);
|
||||
s_comm_enabled = enabled;
|
||||
mutex_unlock(s_comm_state_change_mutex);
|
||||
prv_comm_state_change(NULL);
|
||||
}
|
||||
|
||||
void bt_ctl_set_override_mode(BtCtlModeOverride override) {
|
||||
if (!s_comm_initialized) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Error: Bluetooth isn't initialized yet");
|
||||
return;
|
||||
}
|
||||
mutex_lock(s_comm_state_change_mutex);
|
||||
s_comm_override = override;
|
||||
mutex_unlock(s_comm_state_change_mutex);
|
||||
prv_comm_state_change(NULL);
|
||||
}
|
||||
|
||||
static void prv_track_quick_airplane_mode_toggles(bool is_airplane_mode_currently_on) {
|
||||
// Track when coming out of airplane mode and we've gone into airplane mode less than 30 secs ago:
|
||||
static RtcTicks s_airplane_mode_last_toggle_ticks;
|
||||
const RtcTicks now_ticks = rtc_get_ticks();
|
||||
const uint64_t max_interval_secs = 30;
|
||||
if (((now_ticks - s_airplane_mode_last_toggle_ticks) < (max_interval_secs * RTC_TICKS_HZ)) &&
|
||||
is_airplane_mode_currently_on) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Quick airplane mode toggle detected!");
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_BT_AIRPLANE_MODE_QUICK_TOGGLE_COUNT,
|
||||
AnalyticsClient_System);
|
||||
}
|
||||
s_airplane_mode_last_toggle_ticks = now_ticks;
|
||||
}
|
||||
|
||||
void bt_ctl_set_airplane_mode_async(bool enabled) {
|
||||
if (!s_comm_initialized) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Error: Bluetooth isn't initialized yet");
|
||||
return;
|
||||
}
|
||||
mutex_lock(s_comm_state_change_mutex);
|
||||
prv_track_quick_airplane_mode_toggles(!enabled);
|
||||
bt_persistent_storage_set_airplane_mode_enabled(enabled);
|
||||
s_comm_airplane_mode_on = enabled;
|
||||
bool should_schedule_eval = false;
|
||||
if (!s_comm_state_change_eval_is_scheduled) {
|
||||
should_schedule_eval = true;
|
||||
s_comm_state_change_eval_is_scheduled = true;
|
||||
}
|
||||
mutex_unlock(s_comm_state_change_mutex);
|
||||
if (should_schedule_eval) {
|
||||
system_task_add_callback(prv_comm_state_change, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_ctl_init(void) {
|
||||
s_comm_state_change_mutex = mutex_create();
|
||||
|
||||
s_comm_airplane_mode_on = bt_persistent_storage_get_airplane_mode_enabled();
|
||||
s_comm_initialized = true;
|
||||
analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_BT_OFF_TIME, AnalyticsClient_System);
|
||||
|
||||
gatt_client_subscription_boot();
|
||||
}
|
||||
|
||||
static void prv_bt_ctl_reset_bluetooth_callback(void *context) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Resetting Bluetooth");
|
||||
mutex_lock(s_comm_state_change_mutex);
|
||||
|
||||
bool was_already_running = s_comm_is_running;
|
||||
|
||||
prv_comm_stop();
|
||||
prv_comm_start();
|
||||
|
||||
// It's possible a reset was triggered because the stack failed to boot up
|
||||
// correctly in which case we have never generated an event about the stack
|
||||
// booting up. Don't bother sending events if we are just returning the stack
|
||||
// to the state it is already in
|
||||
if (!was_already_running && s_comm_is_running) {
|
||||
prv_send_state_change_event();
|
||||
}
|
||||
mutex_unlock(s_comm_state_change_mutex);
|
||||
}
|
||||
|
||||
void bt_ctl_reset_bluetooth(void) {
|
||||
if (bt_ctl_is_bluetooth_active()) {
|
||||
system_task_add_callback(prv_bt_ctl_reset_bluetooth_callback, NULL);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Bluetooth is disabled, reset aborted");
|
||||
}
|
||||
}
|
||||
|
||||
void command_bt_airplane_mode(const char* new_mode) {
|
||||
// as tests run using command_bt_airplane_mode, will retain nomenclature
|
||||
// but work as override mode change
|
||||
BtCtlModeOverride override = BtCtlModeOverrideStop;
|
||||
if (strcmp(new_mode, "exit") == 0) {
|
||||
override = BtCtlModeOverrideNone;
|
||||
}
|
||||
bt_ctl_set_override_mode(override);
|
||||
bool new_state = bt_ctl_is_bluetooth_active();
|
||||
if (!new_state) {
|
||||
dbgserial_putstr("Entered airplane mode");
|
||||
} else {
|
||||
dbgserial_putstr("Left airplane mode");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
BtCtlModeOverrideNone,
|
||||
BtCtlModeOverrideStop,
|
||||
BtCtlModeOverrideRun
|
||||
} BtCtlModeOverride;
|
||||
|
||||
void bt_ctl_init(void);
|
||||
|
||||
//! returns the airplane mode state
|
||||
bool bt_ctl_is_airplane_mode_on(void);
|
||||
|
||||
//! Returns whether the bluetooth stack is supposed to be up and running (but might not because it's
|
||||
//! still starting or in the middle of resetting).
|
||||
bool bt_ctl_is_bluetooth_active(void);
|
||||
|
||||
//! Returns whether the bluetooth stack is up and running or not.
|
||||
bool bt_ctl_is_bluetooth_running(void);
|
||||
|
||||
// The following three functions are used for setting the flags that define the state of
|
||||
// the bluetooth stack.
|
||||
|
||||
//! Sets the airplane mode flag. The flag is persisted across reboots
|
||||
void bt_ctl_set_airplane_mode_async(bool enabled);
|
||||
|
||||
//! Sets enable flag (used by the runlevel system).
|
||||
void bt_ctl_set_enabled(bool enabled);
|
||||
|
||||
//! Sets the override mode used to stop and start the bluetooth independent of the airplane mode.
|
||||
void bt_ctl_set_override_mode(BtCtlModeOverride override);
|
||||
|
||||
//! Reset bluetoosh using sequential calls to comm_stop() and comm_start()
|
||||
void bt_ctl_reset_bluetooth(void);
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "services/common/comm_session/session_remote_version.h"
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/id.h>
|
||||
#include <bluetooth/sm_types.h>
|
||||
|
||||
//! @file bluetooth_persistent_storage.h
|
||||
//! Future file-based database for Bluetooth related credentials, cached data, etc.
|
||||
//! The idea is to replace the deprecated, registry-based "remote_prefs.c" and "known_device_list.c"
|
||||
//! For now this module contains temporary implementations that use the legacy registry.
|
||||
|
||||
typedef enum {
|
||||
BtPersistBondingOpInvalid = -1,
|
||||
BtPersistBondingOpDidAdd,
|
||||
BtPersistBondingOpDidChange,
|
||||
BtPersistBondingOpWillDelete,
|
||||
} BtPersistBondingOp;
|
||||
|
||||
typedef enum {
|
||||
BtPersistBondingTypeBTClassic,
|
||||
BtPersistBondingTypeBLE,
|
||||
BtPersistBondingNumTypes
|
||||
} BtPersistBondingType;
|
||||
|
||||
|
||||
//! Signature of function that handles changes in the pairing database
|
||||
typedef void (*BtPersistBondingChangeHandler)(BTBondingID affected_bonding,
|
||||
BtPersistBondingOp operation);
|
||||
|
||||
typedef void (*BtPersistBondingDBEachBLE)(BTDeviceInternal *device, SMIdentityResolvingKey *irk,
|
||||
const char *name, BTBondingID *id, void *context);
|
||||
|
||||
typedef void (*BtPersistBondingDBEachBTClassic)(BTDeviceAddress *addr, SM128BitKey *link_key,
|
||||
const char *name, uint8_t *platform_bits, void *context);
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! BLE Pairing Info
|
||||
|
||||
bool bt_persistent_storage_has_pinned_ble_pairings(void);
|
||||
|
||||
bool bt_persistent_storage_set_ble_pinned_address(const BTDeviceAddress *address);
|
||||
|
||||
bool bt_persistent_storage_get_ble_pinned_address(BTDeviceAddress *address_out);
|
||||
|
||||
BTBondingID bt_persistent_storage_store_ble_pairing(const SMPairingInfo *pairing_info,
|
||||
bool is_gateway, const char *device_name,
|
||||
bool requires_address_pinning,
|
||||
uint8_t flags);
|
||||
|
||||
bool bt_persistent_storage_update_ble_device_name(BTBondingID bonding, const char *device_name);
|
||||
|
||||
void bt_persistent_storage_delete_ble_pairing_by_id(BTBondingID);
|
||||
|
||||
bool bt_persistent_storage_get_ble_pairing_by_id(BTBondingID bonding,
|
||||
SMIdentityResolvingKey *IRK_out,
|
||||
BTDeviceInternal *device_out,
|
||||
char name[BT_DEVICE_NAME_BUFFER_SIZE]);
|
||||
|
||||
bool bt_persistent_storage_get_ble_pairing_by_addr(const BTDeviceInternal *device,
|
||||
SMIdentityResolvingKey *IRK_out,
|
||||
char name_out[BT_DEVICE_NAME_BUFFER_SIZE]);
|
||||
|
||||
//! Returns the first ANCS supported bonding that is found
|
||||
//! The case of having multiple supported ANCS bondings isn't handled well yet.
|
||||
//! When this happens this could easily be changed to a for_each_ancs_supported_bonding(cb)
|
||||
BTBondingID bt_persistent_storage_get_ble_ancs_bonding(void);
|
||||
|
||||
//! Returns true if the bondings is BLE and supports ANCS
|
||||
bool bt_persistent_storage_is_ble_ancs_bonding(BTBondingID bonding);
|
||||
|
||||
//! Returns true if there exists a BLE bonding which supports ANCS
|
||||
bool bt_persistent_storage_has_ble_ancs_bonding(void);
|
||||
|
||||
//! Returns true if the active gateway uses BLE
|
||||
//! [PG]: This will currently always return false until PPoGATT is supported
|
||||
bool bt_persistent_storage_has_active_ble_gateway_bonding(void);
|
||||
|
||||
//! Runs the callback for each BLE pairing
|
||||
//! The callback is NOT allowed to aquire the bt_lock() (or we could deadlock).
|
||||
void bt_persistent_storage_for_each_ble_pairing(BtPersistBondingDBEachBLE cb, void *context);
|
||||
|
||||
//! Registers all the existing BLE bondings with the BT driver lib.
|
||||
void bt_persistent_storage_register_existing_ble_bondings(void);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! BT Classic Pairing Info
|
||||
|
||||
BTBondingID bt_persistent_storage_store_bt_classic_pairing(BTDeviceAddress *address,
|
||||
SM128BitKey *key, char *name,
|
||||
uint8_t *platform_bits);
|
||||
|
||||
void bt_persistent_storage_delete_bt_classic_pairing_by_id(BTBondingID bonding);
|
||||
|
||||
void bt_persistent_storage_delete_bt_classic_pairing_by_addr(const BTDeviceAddress *bd_addr);
|
||||
|
||||
bool bt_persistent_storage_get_bt_classic_pairing_by_id(BTBondingID bonding,
|
||||
BTDeviceAddress *address_out,
|
||||
SM128BitKey *link_key_out,
|
||||
char *name_out,
|
||||
uint8_t *platform_bits_out);
|
||||
|
||||
BTBondingID bt_persistent_storage_get_bt_classic_pairing_by_addr(BTDeviceAddress *addr_in,
|
||||
SM128BitKey *link_key_out,
|
||||
char *name_out,
|
||||
uint8_t *platform_bits_out);
|
||||
|
||||
//! Returns true if the active gateway uses BT Classic
|
||||
bool bt_persistent_storage_has_active_bt_classic_gateway_bonding(void);
|
||||
|
||||
//! Runs the callback for each BT Classic pairing
|
||||
//! The callback is NOT allowed to aquire the bt_lock() (or we could deadlock).
|
||||
void bt_persistent_storage_for_each_bt_classic_pairing(BtPersistBondingDBEachBTClassic cb,
|
||||
void *context);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Local Device Info
|
||||
|
||||
//! Updates the active gateway (the gateway which implements PP)
|
||||
//! This bonding is used for BT Classic reconnection as well
|
||||
//! @param bonding The desired active gateway
|
||||
void bt_persistent_storage_set_active_gateway(BTBondingID bonding);
|
||||
|
||||
//! Returns false if no active gateway exists, true if one does exist
|
||||
//! bonding_out and type_out are only valid when this function returns true;
|
||||
bool bt_persistent_storage_get_active_gateway(BTBondingID *bonding_out,
|
||||
BtPersistBondingType *type_out);
|
||||
|
||||
//! Returns true when the active gateway is changed until a sync happens
|
||||
bool bt_persistent_storage_is_unfaithful(void);
|
||||
|
||||
//! Marks the device as being unfaithful
|
||||
void bt_persistent_storage_set_unfaithful(bool is_unfaithful);
|
||||
|
||||
//! Copies the BLE Encryption Root (ER) or Identity Root (IR) keys out of storage
|
||||
//! @param key_out Storage into which ER or IR should be copied.
|
||||
//! @param key_type The type of key to copy
|
||||
//! @return true if ER and IR are copied, false if there are no keys have been found to copy.
|
||||
bool bt_persistent_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out);
|
||||
|
||||
//! Stores new BLE Encryption Root (ER) and Identity Root (IR) keys
|
||||
void bt_persistent_storage_set_root_keys(SM128BitKey *keys_in);
|
||||
|
||||
//! @param local_device_name_out Storage for the local device name.
|
||||
//! @param max_size Size of the local_device_name_out buffer
|
||||
//! @return true if there is a valid local device name stored, otherwise false (a zero-length string
|
||||
bool bt_persistent_storage_get_local_device_name(char *local_device_name_out, size_t max_size);
|
||||
|
||||
//! Stores the customized local device name
|
||||
//! @param local_device_name The device name to store
|
||||
//! @param size The size of the string
|
||||
void bt_persistent_storage_set_local_device_name(char *local_device_name, size_t max_size);
|
||||
|
||||
//! Retrieve the airplane mode setting
|
||||
//! @return the stored airplane mode flag
|
||||
bool bt_persistent_storage_get_airplane_mode_enabled(void);
|
||||
|
||||
//! Store the airplane mode setting
|
||||
//! @param the airplane mode state to be saved
|
||||
void bt_persistent_storage_set_airplane_mode_enabled(bool enable);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Remote Device Info
|
||||
|
||||
//! Retrieve the most recent system session capabilities
|
||||
//! @param capabilities_out Storage for system session capabilities
|
||||
//! @note It's preferable to use \ref comm_session_get_capabilities when possible
|
||||
void bt_persistent_storage_get_cached_system_capabilities(
|
||||
PebbleProtocolCapabilities *capabilities_out);
|
||||
|
||||
//! Store the most recent system session capabilities
|
||||
//! @param capabilities The capability flags to be saved (cache will be cleared if NULL)
|
||||
void bt_persistent_storage_set_cached_system_capabilities(
|
||||
const PebbleProtocolCapabilities *capabilities);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Common
|
||||
|
||||
void bt_persistent_storage_init(void);
|
||||
|
||||
//! This will not delete the local device info, only pairings
|
||||
void bt_persistent_storage_delete_all_pairings(void);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Unit testing
|
||||
int bt_persistent_storage_get_raw_data(const void *key, size_t key_len,
|
||||
void *data_out, size_t buf_len);
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "bluetooth_persistent_storage_debug.h"
|
||||
|
||||
#include "console/prompt.h"
|
||||
#include "services/common/shared_prf_storage/shared_prf_storage_debug.h"
|
||||
#include "system/hexdump.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/features.h>
|
||||
#include <bluetooth/sm_types.h>
|
||||
#include <btutil/bt_device.h>
|
||||
#include <btutil/sm_util.h>
|
||||
|
||||
//
|
||||
// Strictly for debug. Pretty-prints most of the pairing information saved
|
||||
// in the gap bonding db and shared PRF.
|
||||
//
|
||||
|
||||
void bluetooth_persistent_storage_debug_dump_ble_pairing_info(
|
||||
char *display_buf, const SMPairingInfo *info) {
|
||||
prompt_send_response(" Local Encryption Info: ");
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG,
|
||||
(uint8_t *)&info->local_encryption_info,
|
||||
sizeof(info->local_encryption_info));
|
||||
|
||||
prompt_send_response(" Remote Encryption Info: ");
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG,
|
||||
(uint8_t *)&info->remote_encryption_info,
|
||||
sizeof(info->remote_encryption_info));
|
||||
|
||||
prompt_send_response(" SMIdentityResolvingKey: ");
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG,
|
||||
(uint8_t *)&info->irk,
|
||||
sizeof(info->irk));
|
||||
|
||||
prompt_send_response(" BTDeviceInternal: ");
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG,
|
||||
(uint8_t *)&info->identity,
|
||||
sizeof(BTDeviceInternal));
|
||||
|
||||
prompt_send_response(" SMConnectionSignatureResolvingKey: ");
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG,
|
||||
(uint8_t *)&info->csrk,
|
||||
sizeof(SMConnectionSignatureResolvingKey));
|
||||
|
||||
prompt_send_response_fmt(display_buf, DISPLAY_BUF_LEN,
|
||||
" local encryption valid: %s\n"
|
||||
" remote encryption valid: %s\n"
|
||||
" remote identity valid: %s\n"
|
||||
" remote signature valid: %s\n",
|
||||
bool_to_str(info->is_local_encryption_info_valid),
|
||||
bool_to_str(info->is_remote_encryption_info_valid),
|
||||
bool_to_str(info->is_remote_encryption_info_valid),
|
||||
bool_to_str(info->is_remote_signing_info_valid));
|
||||
}
|
||||
|
||||
void bluetooth_persistent_storage_debug_dump_classic_pairing_info(
|
||||
char *display_buf, BTDeviceAddress *addr, char *device_name, SM128BitKey *link_key,
|
||||
uint8_t platform_bits) {
|
||||
prompt_send_response(" Link Key:");
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG, (uint8_t *)link_key, sizeof(SM128BitKey));
|
||||
prompt_send_response_fmt(display_buf, DISPLAY_BUF_LEN, " BT ADDR: " BD_ADDR_FMT,
|
||||
BT_DEVICE_ADDRESS_XPLODE(*addr));
|
||||
prompt_send_response_fmt(display_buf, DISPLAY_BUF_LEN, " Name: %s",
|
||||
device_name);
|
||||
prompt_send_response_fmt(display_buf, DISPLAY_BUF_LEN, " Platform Bits: 0x%x",
|
||||
(int)platform_bits);
|
||||
}
|
||||
|
||||
|
||||
void bluetooth_persistent_storage_debug_dump_root_keys(SM128BitKey *irk, SM128BitKey *erk) {
|
||||
prompt_send_response("Root Key hexdumps:");
|
||||
|
||||
prompt_send_response(" IRK:");
|
||||
if (irk) {
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG, (uint8_t *)irk, sizeof(SM128BitKey));
|
||||
} else {
|
||||
prompt_send_response(" None");
|
||||
};
|
||||
|
||||
prompt_send_response(" ERK:");
|
||||
if (erk) {
|
||||
PBL_HEXDUMP_D_PROMPT(LOG_LEVEL_DEBUG, (uint8_t *)erk, sizeof(SM128BitKey));
|
||||
} else {
|
||||
prompt_send_response(" None");
|
||||
};
|
||||
}
|
||||
|
||||
extern void bluetooth_persistent_storage_dump_contents(void);
|
||||
void command_gapdb_dump(void) {
|
||||
#if !RECOVERY_FW
|
||||
bluetooth_persistent_storage_dump_contents();
|
||||
#endif
|
||||
shared_prf_storage_dump_contents();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/sm_types.h>
|
||||
#include <btutil/sm_util.h>
|
||||
|
||||
#define DISPLAY_BUF_LEN 160
|
||||
|
||||
void bluetooth_persistent_storage_debug_dump_ble_pairing_info(
|
||||
char *display_buf, const SMPairingInfo *info);
|
||||
|
||||
void bluetooth_persistent_storage_debug_dump_classic_pairing_info(
|
||||
char *display_buf, BTDeviceAddress *addr, char *device_name, SM128BitKey *link_key,
|
||||
uint8_t platform_bits);
|
||||
|
||||
void bluetooth_persistent_storage_debug_dump_root_keys(SM128BitKey *irk, SM128BitKey *erk);
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/bt_lock.h"
|
||||
|
||||
#include "console/prompt.h"
|
||||
#include "services/common/bluetooth/bluetooth_ctl.h"
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "services/common/bluetooth/bt_compliance_tests.h"
|
||||
#include "services/common/bluetooth/local_id.h"
|
||||
#include "services/common/bluetooth/pairability.h"
|
||||
#include "services/common/shared_prf_storage/shared_prf_storage.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/bt_test.h>
|
||||
#include <bluetooth/classic_connect.h>
|
||||
#include <bluetooth/id.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void command_bt_print_mac(void) {
|
||||
char addr_hex_str[BT_ADDR_FMT_BUFFER_SIZE_BYTES];
|
||||
bt_local_id_copy_address_hex_string(addr_hex_str);
|
||||
prompt_send_response(addr_hex_str);
|
||||
}
|
||||
|
||||
//! Overrides the BD ADDR of the Bluetooth controller for test automation purposes
|
||||
//! @param bd_addr String of 12 hex characters (6 bytes) of the Bluetooth device address
|
||||
//! @note To undo the change, call this with all zeroes.
|
||||
//! @note The change will take effect when the Bluetooth is (re)enabled.
|
||||
void command_bt_set_addr(const char *bd_addr_str) {
|
||||
char buffer[32];
|
||||
BTDeviceAddress bd_addr;
|
||||
if (convert_bt_addr_hex_str_to_bd_addr(bd_addr_str, (uint8_t *) &bd_addr, sizeof(bd_addr))) {
|
||||
bt_driver_test_set_spoof_address(&bd_addr);
|
||||
prompt_send_response_fmt(buffer, 32, BT_DEVICE_ADDRESS_FMT, BT_DEVICE_ADDRESS_XPLODE(bd_addr));
|
||||
} else {
|
||||
prompt_send_response("?");
|
||||
}
|
||||
}
|
||||
|
||||
//! @param bt_name A custom Bluetooth device name.
|
||||
void command_bt_set_name(const char *bt_name) {
|
||||
bt_local_id_set_device_name(bt_name);
|
||||
}
|
||||
|
||||
// BT FCC tests
|
||||
void command_bt_test_start(void) {
|
||||
// take down the BT stack and put the OS in a mode where it will not
|
||||
// interfere with the BT testing.
|
||||
bt_test_start();
|
||||
}
|
||||
|
||||
void command_bt_test_stop(void) {
|
||||
// restore the watch to normal operation
|
||||
bt_test_stop();
|
||||
}
|
||||
|
||||
void command_bt_test_hci_passthrough(void) {
|
||||
bt_test_enter_hci_passthrough();
|
||||
}
|
||||
|
||||
void command_bt_test_bt_sig_rf_mode(void) {
|
||||
if (bt_test_bt_sig_rf_test_mode()) {
|
||||
prompt_send_response("BT SIG RF Test Mode Enabled");
|
||||
} else {
|
||||
prompt_send_response("Failed to enter BT SIG RF Test Mode");
|
||||
}
|
||||
}
|
||||
|
||||
void command_bt_prefs_wipe(void) {
|
||||
bt_driver_classic_disconnect(NULL);
|
||||
bt_persistent_storage_delete_all_pairings();
|
||||
}
|
||||
|
||||
void command_bt_sprf_nuke(void) {
|
||||
shared_prf_storage_wipe_all();
|
||||
#if RECOVERY_FW
|
||||
// Reset system to get caches (in s_intents, s_connections and controller-side caches) in sync.
|
||||
extern void factory_reset_set_reason_and_reset(void);
|
||||
factory_reset_set_reason_and_reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef RECOVERY_FW
|
||||
void command_bt_status(void) {
|
||||
char buffer[64];
|
||||
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "Alive: %s",
|
||||
bt_ctl_is_bluetooth_running() ? "yes" : "no");
|
||||
|
||||
const char *prefix = "BT Chip Info: ";
|
||||
size_t prefix_length = strlen(prefix);
|
||||
strncpy(buffer, prefix, sizeof(buffer));
|
||||
bt_driver_id_copy_chip_info_string(buffer + prefix_length,
|
||||
sizeof(buffer) - prefix_length);
|
||||
prompt_send_response(buffer);
|
||||
|
||||
char name[BT_DEVICE_NAME_BUFFER_SIZE];
|
||||
bt_lock();
|
||||
bool connected = bt_driver_classic_copy_connected_device_name(name);
|
||||
if (!connected) {
|
||||
// Try LE:
|
||||
GAPLEConnection *connection = gap_le_connection_any();
|
||||
if (connection) {
|
||||
const char *device_name = connection->device_name ?: "<Unknown>";
|
||||
strncpy(name, device_name, BT_DEVICE_NAME_BUFFER_SIZE);
|
||||
name[BT_DEVICE_NAME_BUFFER_SIZE - 1] = '\0';
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
bt_unlock();
|
||||
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "Connected: %s", connected ? "yes" : "no");
|
||||
if (connected) {
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "Device: %s", name);
|
||||
}
|
||||
}
|
||||
#endif // RECOVERY_FW
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/ble/gap_le_device_name.h"
|
||||
#include "comm/bt_lock.h"
|
||||
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "services/common/bluetooth/local_addr.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
#include <bluetooth/bonding_sync.h>
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/sm_types.h>
|
||||
|
||||
void bt_driver_cb_handle_create_bonding(const BleBonding *bonding,
|
||||
const BTDeviceAddress *addr) {
|
||||
#if !defined(PLATFORM_TINTIN)
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Creating new bonding for "BT_DEVICE_ADDRESS_FMT,
|
||||
BT_DEVICE_ADDRESS_XPLODE(bonding->pairing_info.identity.address));
|
||||
#endif
|
||||
const bool should_pin_address = bonding->should_pin_address;
|
||||
if (should_pin_address) {
|
||||
bt_local_addr_pin(&bonding->pinned_address);
|
||||
}
|
||||
const uint8_t flags = bonding->flags;
|
||||
if (flags) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "flags: 0x02%x", flags);
|
||||
}
|
||||
BTBondingID bonding_id = bt_persistent_storage_store_ble_pairing(&bonding->pairing_info,
|
||||
bonding->is_gateway, NULL,
|
||||
should_pin_address,
|
||||
flags);
|
||||
bt_lock();
|
||||
{
|
||||
GAPLEConnection *connection = gap_le_connection_by_addr(addr);
|
||||
if (connection) {
|
||||
// Associate the connection with the bonding:
|
||||
connection->bonding_id = bonding_id;
|
||||
connection->is_gateway = bonding->is_gateway;
|
||||
|
||||
if (!connection->is_gateway) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "New bonding is not gateway?");
|
||||
}
|
||||
|
||||
// Request device name. iOS returns an "anonymized" device name before encryption, like
|
||||
// "iPhone" and only returns the real name i.e. "Martijn's iPhone" after encryption is set up.
|
||||
gap_le_device_name_request(&connection->device);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Couldn't find connection for bonding!");
|
||||
}
|
||||
}
|
||||
bt_unlock();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "console/console_internal.h"
|
||||
#include "console/prompt.h"
|
||||
|
||||
#include "services/common/bluetooth/bluetooth_ctl.h"
|
||||
#include "services/common/bluetooth/bt_compliance_tests.h"
|
||||
|
||||
#include "kernel/util/stop.h"
|
||||
|
||||
#include <bluetooth/bt_test.h>
|
||||
|
||||
static bool s_test_mode_enabled = false;
|
||||
|
||||
void bt_test_start(void) {
|
||||
if (s_test_mode_enabled) {
|
||||
prompt_send_response("Invalid operation: Run 'bt test stop' first");
|
||||
return;
|
||||
}
|
||||
s_test_mode_enabled = true;
|
||||
bt_ctl_set_override_mode(BtCtlModeOverrideStop);
|
||||
stop_mode_disable(InhibitorBluetooth);
|
||||
|
||||
bt_driver_test_start();
|
||||
}
|
||||
|
||||
bool bt_test_bt_sig_rf_test_mode(void) {
|
||||
return bt_driver_test_enter_rf_test_mode();
|
||||
}
|
||||
|
||||
void bt_test_enter_hci_passthrough(void) {
|
||||
// redirect all communications to the BT module
|
||||
serial_console_set_state(SERIAL_CONSOLE_STATE_HCI_PASSTHROUGH);
|
||||
|
||||
bt_driver_test_enter_hci_passthrough();
|
||||
}
|
||||
|
||||
void bt_test_stop(void) {
|
||||
if (!s_test_mode_enabled) {
|
||||
prompt_send_response("Invalid operation: Run 'bt test start' first");
|
||||
return;
|
||||
}
|
||||
|
||||
bt_driver_test_stop();
|
||||
stop_mode_enable(InhibitorBluetooth);
|
||||
|
||||
// Bring the normal BT stack back up - airplane mode makes this simple
|
||||
bt_ctl_set_override_mode(BtCtlModeOverrideNone);
|
||||
s_test_mode_enabled = false;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void bt_test_start(void);
|
||||
void bt_test_stop(void);
|
||||
|
||||
bool bt_test_bt_sig_rf_test_mode(void);
|
||||
void bt_test_enter_hci_passthrough(void);
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <bluetooth/dis.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "process_management/pebble_process_info.h"
|
||||
#include "mfg/mfg_info.h"
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "system/version.h"
|
||||
|
||||
#define MANUFACTURER_STR "Pebble Technology"
|
||||
|
||||
_Static_assert(MODEL_NUMBER_LEN >= MFG_HW_VERSION_SIZE + 1, "Size mismatch");
|
||||
_Static_assert(MANUFACTURER_LEN >= sizeof(MANUFACTURER_STR), "Size mismatch");
|
||||
_Static_assert(SERIAL_NUMBER_LEN >= MFG_SERIAL_NUMBER_SIZE + 1, "Size mismatch");
|
||||
_Static_assert(FW_REVISION_LEN >= sizeof(TINTIN_METADATA.version_tag), "Size mismatch");
|
||||
|
||||
static void prv_set_model_number(DisInfo *info) {
|
||||
mfg_info_get_hw_version(info->model_number, MODEL_NUMBER_LEN);
|
||||
}
|
||||
|
||||
static void prv_set_manufacturer_name(DisInfo *info) {
|
||||
strncpy(info->manufacturer, MANUFACTURER_STR, MANUFACTURER_LEN);
|
||||
}
|
||||
|
||||
static void prv_set_serial_number(DisInfo *info) {
|
||||
mfg_info_get_serialnumber(info->serial_number, SERIAL_NUMBER_LEN);
|
||||
}
|
||||
|
||||
static void prv_set_firmware_revision(DisInfo *info) {
|
||||
strncpy(info->fw_revision, (char*)TINTIN_METADATA.version_tag, FW_REVISION_LEN);
|
||||
}
|
||||
|
||||
static void prv_set_software_revision(DisInfo *info) {
|
||||
// Fmt: xx.xx\0
|
||||
char sdk_version[SW_REVISION_LEN];
|
||||
sniprintf(sdk_version, SW_REVISION_LEN, "%2u.%02u",
|
||||
PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR,
|
||||
PROCESS_INFO_CURRENT_SDK_VERSION_MINOR);
|
||||
strncpy(info->sw_revision, sdk_version, SW_REVISION_LEN);
|
||||
}
|
||||
|
||||
void dis_get_info(DisInfo *info) {
|
||||
prv_set_model_number(info);
|
||||
prv_set_manufacturer_name(info);
|
||||
prv_set_serial_number(info);
|
||||
prv_set_firmware_revision(info);
|
||||
prv_set_software_revision(info);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct DisInfo DisInfo;
|
||||
|
||||
void dis_get_info(DisInfo *info);
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "local_addr.h"
|
||||
|
||||
#include "comm/bt_lock.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/id.h>
|
||||
#include <btutil/bt_device.h>
|
||||
|
||||
static uint32_t s_pra_cycling_pause_count;
|
||||
static BTDeviceAddress s_pinned_addr;
|
||||
static bool s_cycling_paused_due_to_dependent_bondings;
|
||||
|
||||
static void prv_allow_cycling(bool allow_cycling) {
|
||||
bt_driver_set_local_address(allow_cycling, allow_cycling ? NULL : &s_pinned_addr);
|
||||
}
|
||||
|
||||
void bt_local_addr_pause_cycling(void) {
|
||||
bt_lock();
|
||||
{
|
||||
if (s_pra_cycling_pause_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Pausing address cycling (pinned_addr="BT_DEVICE_ADDRESS_FMT")",
|
||||
BT_DEVICE_ADDRESS_XPLODE(s_pinned_addr));
|
||||
prv_allow_cycling(false);
|
||||
}
|
||||
++s_pra_cycling_pause_count;
|
||||
}
|
||||
bt_unlock();
|
||||
}
|
||||
|
||||
void bt_local_addr_resume_cycling(void) {
|
||||
bt_lock();
|
||||
{
|
||||
PBL_ASSERTN(s_pra_cycling_pause_count);
|
||||
--s_pra_cycling_pause_count;
|
||||
if (s_pra_cycling_pause_count == 0) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Resuming address cycling (pinned_addr="BT_DEVICE_ADDRESS_FMT")",
|
||||
BT_DEVICE_ADDRESS_XPLODE(s_pinned_addr));
|
||||
prv_allow_cycling(true);
|
||||
}
|
||||
}
|
||||
bt_unlock();
|
||||
}
|
||||
|
||||
void bt_local_addr_pin(const BTDeviceAddress *addr) {
|
||||
// In a previous version of the code, the main FW would not know yet what address would be used
|
||||
// for pinning until the BT driver would give the address to pin when a pairing was added.
|
||||
// A single, persistent pinned address is now generated up front in bt_local_addr_init().
|
||||
// Getting the address back in this call from the BT driver currently only serves as a
|
||||
// consistency check.
|
||||
// It is possible that the addresses do not match in the following scenario:
|
||||
// 1. No bondings that require pinning present. Cycling address 'C' is used.
|
||||
// 2. Device A is connected.
|
||||
// 3. Become discoverable: cycling is requested to be paused at address 'P' but can't be granted
|
||||
// yet because device A is still connected.
|
||||
// 4. Device B connects (using 'C' as connection address)
|
||||
// 5. Device B requests pin + pairs => the remote bonding is stored with 'C' as key instead of 'P'
|
||||
// 6. We'll print here there's a mismatch.
|
||||
// 7. Once Device A & B disconnect, device B won't be able to recognize us because 'P' is used...
|
||||
|
||||
bt_lock();
|
||||
bool addresses_match = bt_device_address_equal(addr, &s_pinned_addr);
|
||||
bt_unlock();
|
||||
|
||||
PBL_LOG(LOG_LEVEL_INFO,
|
||||
"Requested to pin address to "BT_DEVICE_ADDRESS_FMT " match=%u",
|
||||
BT_DEVICE_ADDRESS_XPLODE_PTR(addr), addresses_match);
|
||||
}
|
||||
|
||||
void bt_local_addr_handle_bonding_change(BTBondingID bonding, BtPersistBondingOp op) {
|
||||
bool has_pinned_ble_pairings = bt_persistent_storage_has_pinned_ble_pairings();
|
||||
if (has_pinned_ble_pairings != s_cycling_paused_due_to_dependent_bondings) {
|
||||
if (has_pinned_ble_pairings) {
|
||||
bt_local_addr_pause_cycling();
|
||||
} else {
|
||||
bt_local_addr_resume_cycling();
|
||||
}
|
||||
s_cycling_paused_due_to_dependent_bondings = has_pinned_ble_pairings;
|
||||
}
|
||||
}
|
||||
|
||||
void bt_local_addr_init(void) {
|
||||
s_pra_cycling_pause_count = 0;
|
||||
s_cycling_paused_due_to_dependent_bondings = false;
|
||||
|
||||
// Load pinned address from settings file or generate one if it hasn't happened before:
|
||||
if (!bt_persistent_storage_get_ble_pinned_address(&s_pinned_addr)) {
|
||||
if (bt_driver_id_generate_private_resolvable_address(&s_pinned_addr)) {
|
||||
bt_persistent_storage_set_ble_pinned_address(&s_pinned_addr);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Failed to generate PRA... :(");
|
||||
}
|
||||
}
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Pinned address: " BT_DEVICE_ADDRESS_FMT,
|
||||
BT_DEVICE_ADDRESS_XPLODE(s_pinned_addr));
|
||||
|
||||
if (bt_persistent_storage_has_pinned_ble_pairings()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Bonding that requires address pinning exists, applying pinned addr!");
|
||||
bt_local_addr_pause_cycling();
|
||||
s_cycling_paused_due_to_dependent_bondings = true;
|
||||
} else {
|
||||
#if RECOVERY_FW
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Pausing address cycling because PRF!");
|
||||
bt_local_addr_pause_cycling();
|
||||
#elif BT_CONTROLLER_CC2564X && !RELEASE
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Pausing address cycling because cc2564x and beta build!");
|
||||
bt_local_addr_pause_cycling();
|
||||
#else
|
||||
PBL_LOG(LOG_LEVEL_INFO, "No bondings found that require address pinning!");
|
||||
bt_driver_set_local_address(true /* allow_cycling */, NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
|
||||
typedef struct BTDeviceAddress BTDeviceAddress;
|
||||
|
||||
//! Pauses cycling of local Private Resolvable Address (ref counted).
|
||||
//! As long as the cycling is paused, the address that is used "on air" will be stable for the
|
||||
//! duration that the BT stack is up (so the address can be expected to have changed after rebooting
|
||||
//! or resetting the stack).
|
||||
//! In case the local address is currently pinned, this function will be a no-op.
|
||||
void bt_local_addr_pause_cycling(void);
|
||||
|
||||
//! Resumes cycling of local Private Resolvable Address (ref counted).
|
||||
//! In case the local address is currently pinned, this function will be a no-op.
|
||||
void bt_local_addr_resume_cycling(void);
|
||||
|
||||
//! Called by BT driver to indicate what the local address was that was used during the pairing
|
||||
//! and pinning was requested. See comment in the implementation for more details.
|
||||
void bt_local_addr_pin(const BTDeviceAddress *addr);
|
||||
|
||||
//! Handler for bonding changes (deletions primarily).
|
||||
void bt_local_addr_handle_bonding_change(BTBondingID bonding, BtPersistBondingOp op);
|
||||
|
||||
//! Called during the BT stack initialization.
|
||||
void bt_local_addr_init(void);
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "local_id.h"
|
||||
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/size.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#include <bluetooth/features.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// Caches of the local address and device name.
|
||||
// Some clients (i.e. Settings app) make a lot of calls to this module. By caching this info,
|
||||
// we avoid having to reach out to the BT driver every time.
|
||||
static BTDeviceAddress s_local_address;
|
||||
static char s_local_device_name[BT_DEVICE_NAME_BUFFER_SIZE];
|
||||
static char s_local_le_device_name[BT_DEVICE_NAME_BUFFER_SIZE];
|
||||
|
||||
static void prv_populate_name(char name[BT_DEVICE_NAME_BUFFER_SIZE], const char *name_fmt) {
|
||||
sprintf(name, name_fmt, s_local_address.octets[1], s_local_address.octets[0]);
|
||||
}
|
||||
|
||||
static void prv_set_default_device_name(void) {
|
||||
#if (PLATFORM_SNOWY || PLATFORM_SPALDING || PLATFORM_ROBERT)
|
||||
const char *s_local_default_device_name_format = "Pebble Time %02X%02X";
|
||||
const char *s_local_default_le_device_name_format = "Pebble Time LE %02X%02X";
|
||||
#else
|
||||
const char *s_local_default_device_name_format = "Pebble %02X%02X";
|
||||
const char *s_local_default_le_device_name_format = "Pebble-LE %02X%02X";
|
||||
#endif
|
||||
|
||||
// Pebble + hex last 2 bytes of the device address:
|
||||
prv_populate_name(s_local_device_name, s_local_default_device_name_format);
|
||||
prv_populate_name(s_local_le_device_name, s_local_default_le_device_name_format);
|
||||
}
|
||||
|
||||
static bool prv_has_device_name(void) {
|
||||
return (s_local_device_name[0] != '\0');
|
||||
}
|
||||
|
||||
static void prv_configure_device_name(void) {
|
||||
bt_driver_id_set_local_device_name(s_local_device_name);
|
||||
}
|
||||
|
||||
void bt_local_id_configure_driver(void) {
|
||||
// Request the local address from the BT driver and cache it:
|
||||
bt_driver_id_copy_local_identity_address(&s_local_address);
|
||||
|
||||
if (!prv_has_device_name()) {
|
||||
if (!bt_persistent_storage_get_local_device_name(s_local_device_name,
|
||||
sizeof(s_local_device_name))) {
|
||||
prv_set_default_device_name();
|
||||
}
|
||||
}
|
||||
|
||||
prv_configure_device_name();
|
||||
}
|
||||
|
||||
void bt_local_id_set_device_name(const char *device_name) {
|
||||
strncpy(s_local_device_name, device_name, sizeof(s_local_device_name));
|
||||
s_local_device_name[sizeof(s_local_device_name) - 1] = '\0';
|
||||
prv_configure_device_name();
|
||||
}
|
||||
|
||||
void bt_local_id_copy_device_name(char name_out[BT_DEVICE_NAME_BUFFER_SIZE], bool is_le) {
|
||||
char *name = (is_le && bt_driver_supports_bt_classic()) ? s_local_le_device_name :
|
||||
s_local_device_name;
|
||||
strncpy(name_out, name, BT_DEVICE_NAME_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void bt_local_id_copy_address(BTDeviceAddress *addr_out) {
|
||||
*addr_out = s_local_address;
|
||||
}
|
||||
|
||||
void bt_local_id_copy_address_hex_string(char addr_hex_str_out[BT_ADDR_FMT_BUFFER_SIZE_BYTES]) {
|
||||
static const BTDeviceAddress null_addr = {};
|
||||
if (0 != memcmp(&null_addr, &s_local_address, sizeof(s_local_address))) {
|
||||
sniprintf(addr_hex_str_out, BT_DEVICE_ADDRESS_FMT_BUFFER_SIZE,
|
||||
BD_ADDR_FMT, BT_DEVICE_ADDRESS_XPLODE(s_local_address));
|
||||
} else {
|
||||
sniprintf(addr_hex_str_out, BT_DEVICE_ADDRESS_FMT_BUFFER_SIZE, "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
void bt_local_id_copy_address_mac_string(char addr_mac_str_out[BT_DEVICE_ADDRESS_FMT_BUFFER_SIZE]) {
|
||||
sniprintf(addr_mac_str_out, BT_DEVICE_ADDRESS_FMT_BUFFER_SIZE,
|
||||
BT_DEVICE_ADDRESS_FMT, BT_DEVICE_ADDRESS_XPLODE(s_local_address));
|
||||
}
|
||||
|
||||
T_STATIC void prv_generate_address(BTDeviceAddress *addr_out) {
|
||||
const char *serial = mfg_get_serial_number();
|
||||
const uint32_t full_len = strlen(serial);
|
||||
const uint32_t half_len = (full_len / 2);
|
||||
|
||||
// Hash of the normal serial
|
||||
const uint32_t serial_hash = hash((uint8_t *)serial, full_len);
|
||||
|
||||
// Hash of the serial reversed
|
||||
char tmp[full_len + 1];
|
||||
strncpy(tmp, serial, sizeof(tmp));
|
||||
string_reverse(tmp);
|
||||
const uint32_t reverse_hash = hash((uint8_t *)tmp, full_len);
|
||||
|
||||
struct PACKED {
|
||||
union {
|
||||
BTDeviceAddress bt_addr;
|
||||
struct PACKED {
|
||||
uint16_t a;
|
||||
uint32_t b;
|
||||
};
|
||||
};
|
||||
} addr = {
|
||||
.a = (uint16_t) reverse_hash,
|
||||
.b = (serial_hash ^ reverse_hash),
|
||||
};
|
||||
|
||||
*addr_out = addr.bt_addr;
|
||||
}
|
||||
|
||||
void bt_local_id_generate_address_from_serial(BTDeviceAddress *addr_out) {
|
||||
prv_generate_address(addr_out);
|
||||
addr_out->octets[ARRAY_LENGTH(addr_out->octets) - 1] |= 0b11000000;
|
||||
|
||||
// Addresses with all 0's or 1's
|
||||
const BTDeviceAddress zero_addr = {.octets = {0x00, 0x00, 0x00, 0x00, 0x00, 0xC0}};
|
||||
const BTDeviceAddress one_addr = {.octets = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
||||
// NOTE: It already has the two most sig. bits set.
|
||||
const BTDeviceAddress fallback_addr = {.octets = {0x3c, 0x08, 0x55, 0xaf, 0xd3, 0xc4}};
|
||||
|
||||
// Compare (the first 5 bytes) the generated one with the invalid ones. If they are equal,
|
||||
// fall back to this address.
|
||||
if (!memcmp(addr_out, &zero_addr, sizeof(BTDeviceAddress))
|
||||
|| !memcmp(addr_out, &one_addr, sizeof(BTDeviceAddress))) {
|
||||
*addr_out = fallback_addr;
|
||||
}
|
||||
|
||||
memcpy(addr_out, (uint8_t *)addr_out, sizeof(*addr_out));
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
#include <bluetooth/id.h>
|
||||
|
||||
//! Called by bl_ctl right after the stack starts, to configure the local device name and address.
|
||||
void bt_local_id_configure_driver(void);
|
||||
|
||||
//! Sets a new device name, overriding the existing (default) one.
|
||||
//! The name will be truncated to BT_DEVICE_NAME_BUFFER_SIZE - 1 characters.
|
||||
void bt_local_id_set_device_name(const char *device_name);
|
||||
|
||||
//! Copies the name of the local device into the given buffer.
|
||||
//! @param is_le Only consumed if the device used is dual mode. If so,
|
||||
// this changes the name returned
|
||||
void bt_local_id_copy_device_name(char name_out[BT_DEVICE_NAME_BUFFER_SIZE], bool is_le);
|
||||
|
||||
//! Copies the address of the local device.
|
||||
void bt_local_id_copy_address(BTDeviceAddress *addr_out);
|
||||
|
||||
//! Copies a hex-formatted string representation ("0x000000000000") of the device address into the
|
||||
//! given buffer. The buffer should be at least BT_ADDR_FMT_BUFFER_SIZE_BYTES bytes in size to fit
|
||||
//! the address string.
|
||||
//! If there is no local address known, the string "Unknown" will be copied into the buffer.
|
||||
void bt_local_id_copy_address_hex_string(char addr_hex_str_out[BT_ADDR_FMT_BUFFER_SIZE_BYTES]);
|
||||
|
||||
//! Copies a MAC-formatted string representation ("00:00:00:00:00:00") of the device address into
|
||||
//! the given buffer. The buffer should be at least BT_ADDR_FMT_BUFFER_SIZE_BYTES bytes in size to
|
||||
//! fit the address string.
|
||||
void bt_local_id_copy_address_mac_string(char addr_mac_str_out[BT_DEVICE_ADDRESS_FMT_BUFFER_SIZE]);
|
||||
|
||||
//! Generates a BTDeviceAddress from the serial number of the watch
|
||||
void bt_local_id_generate_address_from_serial(BTDeviceAddress *addr_out);
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define FILE_LOG_COLOR LOG_COLOR_BLUE
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "comm/ble/gap_le_slave_discovery.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "services/common/bluetooth/bluetooth_ctl.h"
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "services/common/bluetooth/local_addr.h"
|
||||
#include "services/common/bluetooth/pairability.h"
|
||||
#include "services/common/regular_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
|
||||
#include <bluetooth/connectability.h>
|
||||
#include <bluetooth/features.h>
|
||||
#include <bluetooth/pairability.h>
|
||||
|
||||
static void prv_pairability_timer_cb(void *unused);
|
||||
|
||||
static int s_allow_bt_pairing_refcount = 0;
|
||||
static int s_allow_ble_pairing_refcount = 0;
|
||||
|
||||
static RegularTimerInfo s_pairability_timer_info = {
|
||||
.cb = prv_pairability_timer_cb,
|
||||
};
|
||||
|
||||
static void evaluate_pairing_refcount(void *data) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelBackground);
|
||||
|
||||
if (!bt_ctl_is_bluetooth_running()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Pairabilty state: LE=%u, Classic=%u",
|
||||
s_allow_ble_pairing_refcount, s_allow_bt_pairing_refcount);
|
||||
|
||||
bool is_ble_pairable_and_discoverable = (s_allow_ble_pairing_refcount > 0);
|
||||
|
||||
bt_driver_le_pairability_set_enabled(is_ble_pairable_and_discoverable);
|
||||
if (gap_le_slave_is_discoverable() != is_ble_pairable_and_discoverable) {
|
||||
if (is_ble_pairable_and_discoverable) {
|
||||
bt_local_addr_pause_cycling();
|
||||
} else {
|
||||
bt_local_addr_resume_cycling();
|
||||
}
|
||||
gap_le_slave_set_discoverable(is_ble_pairable_and_discoverable);
|
||||
}
|
||||
|
||||
if (bt_driver_supports_bt_classic()) {
|
||||
bt_driver_classic_pairability_set_enabled((s_allow_bt_pairing_refcount > 0));
|
||||
bt_driver_classic_update_connectability();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_schedule_evaluation(void) {
|
||||
// We used to sparingly schedule the evaluation and had a bug because of this:
|
||||
// https://pebbletechnology.atlassian.net/browse/PBL-22884
|
||||
// Because this pretty much only happens in response to user input, don't bother limiting this,
|
||||
// and always evaluate, even though the state might not have changed:
|
||||
system_task_add_callback(evaluate_pairing_refcount, NULL);
|
||||
}
|
||||
|
||||
void bt_pairability_use(void) {
|
||||
++s_allow_bt_pairing_refcount;
|
||||
++s_allow_ble_pairing_refcount;
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
|
||||
void bt_pairability_use_bt(void) {
|
||||
++s_allow_bt_pairing_refcount;
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
|
||||
void bt_pairability_use_ble(void) {
|
||||
++s_allow_ble_pairing_refcount;
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
|
||||
static void prv_pairability_timer_cb(void *unused) {
|
||||
regular_timer_remove_callback(&s_pairability_timer_info);
|
||||
bt_pairability_release_ble();
|
||||
}
|
||||
|
||||
void bt_pairability_use_ble_for_period(uint16_t duration_secs) {
|
||||
if (!regular_timer_is_scheduled(&s_pairability_timer_info)) {
|
||||
// If this function is called multiple times before the timer is unscheduled, limit to calling
|
||||
// "use" only once:
|
||||
bt_pairability_use_ble();
|
||||
}
|
||||
// Always reschedule, even if the duration is shorter than the one that might already be
|
||||
// scheduled:
|
||||
regular_timer_add_multisecond_callback(&s_pairability_timer_info, duration_secs);
|
||||
}
|
||||
|
||||
void bt_pairability_release(void) {
|
||||
PBL_ASSERT(s_allow_bt_pairing_refcount != 0 && s_allow_ble_pairing_refcount != 0, "");
|
||||
--s_allow_bt_pairing_refcount;
|
||||
--s_allow_ble_pairing_refcount;
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
|
||||
void bt_pairability_release_bt(void) {
|
||||
PBL_ASSERT(s_allow_bt_pairing_refcount != 0, "");
|
||||
--s_allow_bt_pairing_refcount;
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
|
||||
void bt_pairability_release_ble(void) {
|
||||
PBL_ASSERT(s_allow_ble_pairing_refcount != 0, "");
|
||||
--s_allow_ble_pairing_refcount;
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
|
||||
//! Call this whenever we modify the number of saved bondings we have.
|
||||
void bt_pairability_update_due_to_bonding_change(void) {
|
||||
static bool s_pairable_due_to_no_gateway_bondings = false;
|
||||
const bool has_classic_bonding =
|
||||
(bt_driver_supports_bt_classic() &&
|
||||
bt_persistent_storage_has_active_bt_classic_gateway_bonding());
|
||||
|
||||
if (!has_classic_bonding &&
|
||||
!bt_persistent_storage_has_active_ble_gateway_bonding() &&
|
||||
!bt_persistent_storage_has_ble_ancs_bonding()) {
|
||||
if (!s_pairable_due_to_no_gateway_bondings) {
|
||||
bt_pairability_use();
|
||||
s_pairable_due_to_no_gateway_bondings = true;
|
||||
}
|
||||
} else {
|
||||
if (s_pairable_due_to_no_gateway_bondings) {
|
||||
bt_pairability_release();
|
||||
s_pairable_due_to_no_gateway_bondings = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_pairability_init(void) {
|
||||
bt_pairability_update_due_to_bonding_change();
|
||||
prv_schedule_evaluation();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! Reference counted request to allow us to be discovered and paired with over BT Classic & LE.
|
||||
void bt_pairability_use(void);
|
||||
|
||||
//! Reference counted request to allow us to be discovered and paired with over BT Classic.
|
||||
void bt_pairability_use_bt(void);
|
||||
|
||||
//! Reference counted request to allow us to be discovered and paired with over BLE.
|
||||
void bt_pairability_use_ble(void);
|
||||
|
||||
//! Reference counted request to allow us to be discovered and paired with over BLE for a specific
|
||||
//! period, after which bt_pairability_release_ble will be called automatically.
|
||||
void bt_pairability_use_ble_for_period(uint16_t duration_secs);
|
||||
|
||||
//! Reference counted request to disallow us to be discovered and paired with over BT Classic & LE.
|
||||
void bt_pairability_release(void);
|
||||
|
||||
//! Reference counted request to disallow us to be discovered and paired with over BT Classic.
|
||||
void bt_pairability_release_bt(void);
|
||||
|
||||
//! Reference counted request to disallow us to be discovered and paired with over BLE.
|
||||
void bt_pairability_release_ble(void);
|
||||
|
||||
//! Evaluates whether there are any bondings to gateways. If there are none, make the system
|
||||
//! discoverable and pairable.
|
||||
void bt_pairability_update_due_to_bonding_change(void);
|
||||
|
||||
void bt_pairability_init(void);
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <bluetooth/pebble_pairing_service.h>
|
||||
|
||||
#include "comm/ble/gap_le_connect_params.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/ble/kernel_le_client/app_launch/app_launch.h"
|
||||
#include "comm/bt_conn_mgr.h"
|
||||
#include "comm/bt_lock.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
extern void gap_le_connect_params_re_evaluate(GAPLEConnection *connection);
|
||||
|
||||
static void prv_convert_pps_request_params(const PebblePairingServiceConnParamSet *pps_params_in,
|
||||
GAPLEConnectRequestParams *params_out) {
|
||||
const uint16_t min_1_25ms = pps_params_in->interval_min_1_25ms;
|
||||
params_out->connection_interval_min_1_25ms = min_1_25ms;
|
||||
params_out->connection_interval_max_1_25ms =
|
||||
min_1_25ms + pps_params_in->interval_max_delta_1_25ms;
|
||||
#if RECOVERY_FW || BT_CONTROLLER_DA14681
|
||||
if (pps_params_in->slave_latency_events != 0) {
|
||||
# if RECOVERY_FW
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Overriding requested slave latency with 0 because PRF");
|
||||
# else
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Overriding requested slave latency with 0 because Dialog");
|
||||
# endif
|
||||
}
|
||||
params_out->slave_latency_events = 0;
|
||||
#else
|
||||
params_out->slave_latency_events = pps_params_in->slave_latency_events;
|
||||
#endif
|
||||
params_out->supervision_timeout_10ms = pps_params_in->supervision_timeout_30ms * 3;
|
||||
}
|
||||
|
||||
static void prv_handle_set_remote_param_mgmt_settings(GAPLEConnection *connection,
|
||||
const PebblePairingServiceRemoteParamMgmtSettings *settings, size_t settings_length) {
|
||||
bool is_remote_device_managing_connection_parameters =
|
||||
settings->is_remote_device_managing_connection_parameters;
|
||||
connection->is_remote_device_managing_connection_parameters =
|
||||
is_remote_device_managing_connection_parameters;
|
||||
PBL_LOG(LOG_LEVEL_INFO, "PPS: is_remote_mgmt=%u",
|
||||
is_remote_device_managing_connection_parameters);
|
||||
|
||||
if (settings_length >= PEBBLE_PAIRING_SERVICE_REMOTE_PARAM_MGTM_SETTINGS_SIZE_WITH_PARAM_SETS) {
|
||||
if (!connection->connection_parameter_sets) {
|
||||
const size_t size = sizeof(GAPLEConnectRequestParams) * NumResponseTimeState;
|
||||
connection->connection_parameter_sets =
|
||||
(GAPLEConnectRequestParams *) kernel_zalloc_check(size);
|
||||
}
|
||||
for (ResponseTimeState s = ResponseTimeMax; s < NumResponseTimeState; ++s) {
|
||||
const PebblePairingServiceConnParamSet *pps_params =
|
||||
&settings->connection_parameter_sets[s];
|
||||
GAPLEConnectRequestParams *params = &connection->connection_parameter_sets[s];
|
||||
prv_convert_pps_request_params(pps_params, params);
|
||||
PBL_LOG(LOG_LEVEL_INFO,
|
||||
"PPS: Updated param set %u: %u-%u, slave lat: %u, supervision timeout: %u",
|
||||
s, params->connection_interval_min_1_25ms, params->connection_interval_max_1_25ms,
|
||||
params->slave_latency_events, params->supervision_timeout_10ms);
|
||||
}
|
||||
}
|
||||
|
||||
// Always just re-evaluate, should be idempotent:
|
||||
gap_le_connect_params_re_evaluate(connection);
|
||||
}
|
||||
|
||||
static void prv_handle_set_remote_desired_state(GAPLEConnection *connection,
|
||||
const PebblePairingServiceRemoteDesiredState *desired_state) {
|
||||
const ResponseTimeState remote_desired_state = (ResponseTimeState)desired_state->state;
|
||||
PBL_LOG(LOG_LEVEL_INFO, "PPS: desired_state=%u", remote_desired_state);
|
||||
|
||||
// "As a safety measure, the watch will reset it back to ResponseTimeMax after 5 minutes."
|
||||
const uint16_t max_period_secs = 5 * 60;
|
||||
conn_mgr_set_ble_conn_response_time(connection, BtConsumerPebblePairingServiceRemoteDevice,
|
||||
remote_desired_state, max_period_secs);
|
||||
}
|
||||
|
||||
void bt_driver_cb_pebble_pairing_service_handle_connection_parameter_write(
|
||||
const BTDeviceInternal *device,
|
||||
const PebblePairingServiceConnParamsWrite *conn_params,
|
||||
size_t conn_params_length) {
|
||||
bt_lock();
|
||||
{
|
||||
GAPLEConnection *connection = gap_le_connection_by_device(device);
|
||||
if (!connection) {
|
||||
goto unlock;
|
||||
}
|
||||
const size_t length = (conn_params_length - offsetof(PebblePairingServiceConnParamsWrite,
|
||||
remote_desired_state));
|
||||
switch (conn_params->cmd) {
|
||||
case PebblePairingServiceConnParamsWriteCmd_SetRemoteParamMgmtSettings:
|
||||
prv_handle_set_remote_param_mgmt_settings(connection,
|
||||
&conn_params->remote_param_mgmt_settings, length);
|
||||
break;
|
||||
|
||||
case PebblePairingServiceConnParamsWriteCmd_SetRemoteDesiredState:
|
||||
prv_handle_set_remote_desired_state(connection, &conn_params->remote_desired_state);
|
||||
break;
|
||||
case PebblePairingServiceConnParamsWriteCmd_EnablePacketLengthExtension:
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Enabling BLE Packet Length Extension");
|
||||
break;
|
||||
case PebblePairingServiceConnParamsWriteCmd_InhibitBLESleep:
|
||||
PBL_LOG(LOG_LEVEL_INFO, "BLE Sleep Mode inhibited!");
|
||||
break;
|
||||
default:
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Unknown write_cmd %d", conn_params->cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
unlock:
|
||||
bt_unlock();
|
||||
}
|
||||
|
||||
void bt_driver_cb_pebble_pairing_service_handle_ios_app_termination_detected(void) {
|
||||
app_launch_trigger();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define FILE_LOG_COLOR LOG_COLOR_BLUE
|
||||
|
||||
#include "pp_ble_control.h"
|
||||
#include "services/common/bluetooth/pairability.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
typedef enum {
|
||||
// Values 0 - 3 are deprecated, do not use.
|
||||
BLEControlCommandTypeSetDiscoverablePairable = 4,
|
||||
} BLEControlCommandType;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t opcode;
|
||||
bool discoverable_pairable;
|
||||
uint16_t duration;
|
||||
} BLEControlCommandSetDiscoverablePairable;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Handler for the "Set Discoverable & Pairable" command
|
||||
static void prv_handle_set_discoverable_pairable(
|
||||
BLEControlCommandSetDiscoverablePairable *cmd_data) {
|
||||
bt_pairability_use_ble_for_period(cmd_data->duration);
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Set Discoverable Pairable: %u, %u",
|
||||
cmd_data->discoverable_pairable, cmd_data->duration);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Pebble protocol handler for the BLE control endpoint
|
||||
void pp_ble_control_protocol_msg_callback(
|
||||
CommSession* session, const uint8_t *data, unsigned int length) {
|
||||
PBL_ASSERT_RUNNING_FROM_EXPECTED_TASK(PebbleTask_KernelBackground);
|
||||
|
||||
if (length < sizeof(BLEControlCommandSetDiscoverablePairable)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Invalid pp_ble_control_protocol_msg_callback message: %d", length);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t opcode = *(const uint8_t *) data;
|
||||
switch (opcode) {
|
||||
case 0 ... 3:
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Deprecated & unsupported opcode: %u", opcode);
|
||||
break;
|
||||
|
||||
case BLEControlCommandTypeSetDiscoverablePairable: {
|
||||
BLEControlCommandSetDiscoverablePairable *cmd_data =
|
||||
(BLEControlCommandSetDiscoverablePairable *)data;
|
||||
prv_handle_set_discoverable_pairable(cmd_data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Unknown opcode %u", opcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "services/common/comm_session/session.h"
|
||||
|
||||
void pp_ble_control_protocol_msg_callback(CommSession* session,
|
||||
const uint8_t *data,
|
||||
unsigned int length);
|
||||
Reference in New Issue
Block a user