Files
nearby/embedded/client/source/nearby_fp_client.c
T
Janusz Sobczak f643f7a466 Update embedded SDK
New features:
* Smart Audio Source Switching.
* Fast Pair for BLE-only devices.
* Two byte salt size for improved security.

Other changes:
* Added optional encryption modules based on mbedtls.
* Fixed build issues.
* More verbose and readable logs.
2022-12-20 10:50:13 -08:00

2017 lines
75 KiB
C

// Copyright 2022 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
//
// https://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 "nearby_fp_client.h"
#include <string.h>
#include "nearby.h"
#include "nearby_assert.h"
#include "nearby_event.h"
#include "nearby_fp_library.h"
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
#include "nearby_platform_battery.h"
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
#if NEARBY_FP_MESSAGE_STREAM
#include "nearby_message_stream.h"
#endif /* NEARBY_FP_MESSAGE_STREAM */
#include "nearby_assert.h"
#include "nearby_platform_audio.h"
#include "nearby_platform_ble.h"
#include "nearby_platform_bt.h"
#include "nearby_platform_os.h"
#include "nearby_platform_persistence.h"
#include "nearby_platform_se.h"
#include "nearby_trace.h"
#include "nearby_utils.h"
#define ENCRYPTED_REQUEST_LENGTH 16
#define PUBLIC_KEY_LENGTH 64
#define REQUEST_BT_ADDRESS_OFFSET 2
// block pairing attempts for 5 minutes after 10 failures
#define MAX_PAIRING_FAILURE_COUNT 10
#define REJECT_PAIRING_TIMEOUT_MS (5 * 60 * 1000)
#define PASSKEY_MAX_WAIT_TIME_MS 10000
#define WAIT_FOR_PAIRING_REQUEST_TIME_MS 10000
#define ACCOUNT_KEY_WRITE_TIME_MS 60000
#define RETRO_PAIRING_REQUEST_TIME_MS 60000
// Defines the timeout when waiting for pairing result when the Seeker writes
// the Account Key before/during pairing.
#define WAIT_FOR_PAIRING_RESULT_AFTER_ACCOUNT_KEY_TIME_MS 60000
#define KEY_BASED_PAIRING_REQUEST_FLAG 0x00
#define ACTION_REQUEST_FLAG 0x10
// KBPR - Key Based Pairing Request
#define KBPR_INITIATE_PAIRING_MASK (1 << 6)
#define KBPR_NOTIFY_EXISTING_NAME_MASK (1 << 5)
#define KBPR_RETROACTIVELY_WRITE_ACCOUNT_KEY_MASK (1 << 4)
#define KBPR_SEEKER_SUPPORTS_BLE_DEVICES_MASK (1 << 3)
#define KBPR_SEEKER_ADDRESS_OFFSET 8
#define ACTION_REQUEST_DEVICE_ACTION_MASK (1 << 7)
#define ACTION_REQUEST_WILL_WRITE_DATA_CHARACTERISTIC_MASK (1 << 6)
#define SEEKER_PASSKEY_MESSAGE_TYPE 0x02
#define PROVIDER_PASSKEY_MESSAGE_TYPE 0x03
#define ACCOUNT_KEY_WRITE_MESSAGE_TYPE 0x04
#define PERSONALIZED_NAME_DATA_ID 1
#define INVALID_BATTERY_LEVEL (-1)
#define INVALID_PEER_ADDRESS 0
// One byte per left, right and charging case
#define BATTERY_LEVELS_SIZE 3
// Size of battery remaining time (16 bits)
#define BATTERY_TIME_SIZE 2
// Size of 'Message Stream PSM' Gatt response.
#define PSM_MESSAGE_LENGTH 3
// The BLE address should be rotated on average every 1024 seconds
#define ADDRESS_ROTATION_PERIOD_MS 1024000
// The maximum size of device bitmap. We require one bit per paired device
#define MAX_DEVICE_BITMAP_SIZE 4
// The maximum payload size for 'Notify connection status' message
#define MAX_NOTIFY_CONNECTION_STATUS_MESSAGE_SIZE \
(3 + MAX_DEVICE_BITMAP_SIZE + SESSION_NONCE_SIZE)
// Possible values of 'Active device flag'
#define SEEKER_IS_NOT_ACTIVE_DEVICE 0
#define SEEKER_IS_ACTIVE_DEVICE 1
#define SEEKER_IS_NON_SASS 2
#define SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_NAME_OFFSET 2
#define SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_TARGET_THIS_DEVICE 1
#define SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_TARGET_ANOTHER_DEVICE 2
enum PairingState {
kPairingStateIdle,
kPairingStateWaitingForPairingRequest,
kPairingStateWaitingForPasskey,
kPairingStateWaitingForPairingResult,
kPairingStateWaitingForAccountKeyWrite,
kPairingStateWaitingForAdditionalData
} pairing_state;
static const nearby_fp_client_Callbacks* client_callbacks;
static unsigned int timeout_start_ms;
static uint8_t pairing_failure_count;
static unsigned int reject_pairing_time_start_ms;
static uint64_t peer_public_address;
static int advertisement_mode;
static nearby_platform_AccountKeyInfo account_key_info;
static nearby_platform_AccountKeyInfo pending_account_key_info;
static uint64_t gatt_peer_address;
static uint32_t address_rotation_timestamp;
static void* address_rotation_task = NULL;
#if NEARBY_FP_ENABLE_ADDITIONAL_DATA
static uint8_t additional_data_id;
#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */
#ifdef NEARBY_FP_ENABLE_SASS
static const uint16_t kSassVersion = 0x101;
static uint8_t sass_custom_data = 0;
#endif /* NEARBY_FP_ENABLE_SASS */
#define RETURN_IF_ERROR(X) \
do { \
nearby_platform_status status = X; \
if (kNearbyStatusOK != status) return status; \
} while (0)
#define BIT(b) (1 << b)
#define ISSET(v, b) (v & BIT(b))
#if NEARBY_FP_MESSAGE_STREAM
// Callback triggered when a complete message is received over message stream
static void OnMessageReceived(uint64_t peer_address,
nearby_message_stream_Message* message);
static void SendBleAddressUpdatedToAll();
typedef struct {
nearby_message_stream_State state;
// Random session nonce generated by the provider. It is used to authenticate
// the seeker and prevent replay attacks
uint8_t session_nonce[SESSION_NONCE_SIZE];
uint8_t in_use_account_key[ACCOUNT_KEY_SIZE_BYTES];
uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE +
sizeof(nearby_message_stream_Metadata)];
uint8_t capabilities;
uint8_t platform_type;
uint8_t platform_build;
} rfcomm_input;
static void InitRfcommInput(uint64_t peer_address, rfcomm_input* input) {
memset(input, 0, sizeof(*input));
input->state.peer_address = peer_address;
input->state.on_message_received = OnMessageReceived;
for (int i = 0; i < SESSION_NONCE_SIZE; i++) {
input->session_nonce[i] = nearby_platform_Rand();
}
input->state.length = sizeof(input->buffer);
input->state.buffer = input->buffer;
// defaults to: companion app installed, silence mode supported
input->capabilities = BIT(MESSAGE_CODE_CAPABILITIES_COMPANION_APP_INSTALLED) |
BIT(MESSAGE_CODE_CAPABILITIES_SILENCE_MODE_SUPPORTED);
nearby_message_stream_Init(&input->state);
}
static rfcomm_input rfcomm_inputs[NEARBY_MAX_RFCOMM_CONNECTIONS];
#ifdef NEARBY_FP_ENABLE_SASS
static void UpdateAndNotifySass(const rfcomm_input* seeker);
#endif /* NEARBY_FP_ENABLE_SASS */
#endif /* NEARBY_FP_MESSAGE_STREAM */
#if NEARBY_FP_RETROACTIVE_PAIRING
typedef struct {
uint64_t peer_public_address;
uint64_t peer_le_address;
unsigned int retroactive_pairing_time_start_ms;
} retroactive_pairing_peer;
static retroactive_pairing_peer
retroactive_pairing_list[NEARBY_MAX_RETROACTIVE_PAIRING];
static bool AddRetroactivePairingPeer(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) {
retroactive_pairing_peer* peer = &retroactive_pairing_list[i];
if (nearby_platform_GetCurrentTimeMs() -
peer->retroactive_pairing_time_start_ms >
RETRO_PAIRING_REQUEST_TIME_MS) {
peer->peer_public_address = INVALID_PEER_ADDRESS;
peer->peer_le_address = INVALID_PEER_ADDRESS;
}
if (peer->peer_public_address == peer_address) {
return false;
}
}
for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) {
retroactive_pairing_peer* peer = &retroactive_pairing_list[i];
if (peer->peer_public_address == INVALID_PEER_ADDRESS) {
NEARBY_TRACE(INFO, "timer set for retroactive pairing");
peer->peer_public_address = peer_address;
peer->retroactive_pairing_time_start_ms =
nearby_platform_GetCurrentTimeMs();
return true;
}
}
return false;
}
static bool SetRetroactivePairingPeerLe(uint64_t peer_public_address,
uint64_t peer_le_address) {
for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) {
retroactive_pairing_peer* peer = &retroactive_pairing_list[i];
if (peer->peer_public_address == peer_public_address) {
peer->peer_le_address = peer_le_address;
return true;
}
}
return false;
}
static bool RetroactivePairingPeerPending(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) {
retroactive_pairing_peer* peer = &retroactive_pairing_list[i];
if (peer->peer_public_address == peer_address ||
peer->peer_le_address == peer_address) {
return true;
}
}
return false;
}
static bool RetroactivePairingPeerTimeout(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) {
retroactive_pairing_peer* peer = &retroactive_pairing_list[i];
if ((peer->peer_public_address == peer_address ||
peer->peer_le_address == peer_address) &&
(nearby_platform_GetCurrentTimeMs() -
peer->retroactive_pairing_time_start_ms >
RETRO_PAIRING_REQUEST_TIME_MS)) {
return true;
}
}
return false;
}
static void RemoveRetroactivePairingPeer(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) {
retroactive_pairing_peer* peer = &retroactive_pairing_list[i];
if (peer->peer_public_address == peer_address ||
peer->peer_le_address == peer_address) {
peer->peer_public_address = INVALID_PEER_ADDRESS;
peer->peer_le_address = INVALID_PEER_ADDRESS;
}
}
}
#endif /* NEARBY_FP_RETROACTIVE_PAIRING */
static bool BtAddressMatch(uint8_t* a, uint8_t* b) {
return 0 == memcmp(a, b, BT_ADDRESS_LENGTH);
}
static bool ShowPairingIndicator() {
return advertisement_mode & NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR;
}
#ifdef NEARBY_FP_ENABLE_SASS
static bool IsSassSeeker(const rfcomm_input* input) {
return input->state.peer_address != INVALID_PEER_ADDRESS &&
input->in_use_account_key[0] == 4;
}
static bool IsDeviceSass(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
if (input->state.peer_address == peer_address) {
return IsSassSeeker(input);
}
}
return false;
}
static uint8_t* FindInUseKey() {
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
if (IsSassSeeker(input)) {
return input->in_use_account_key;
}
}
return NULL;
}
static uint8_t GetCustomData() { return sass_custom_data; }
static void SetCustomData(uint8_t data) { sass_custom_data = data; }
#endif /* NEARBY_FP_ENABLE_SASS */
static bool IncludeSass() {
#ifdef NEARBY_FP_ENABLE_SASS
return advertisement_mode & NEARBY_FP_ADVERTISEMENT_SASS;
#else
return false;
#endif /* NEARBY_FP_ENABLE_SASS */
}
static uint8_t* SelectInUseKey() {
#ifdef NEARBY_FP_ENABLE_SASS
return IncludeSass() ? FindInUseKey() : NULL;
#else
return NULL;
#endif /* NEARBY_FP_ENABLE_SASS */
}
static bool IsInPairingMode() {
return nearby_platform_IsInPairingMode() ||
(pairing_state != kPairingStateIdle &&
pairing_state != kPairingStateWaitingForAccountKeyWrite &&
pairing_state != kPairingStateWaitingForAdditionalData);
}
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
static bool ShowBatteryIndicator() {
return advertisement_mode & NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR;
}
static bool IncludeBatteryInfo() {
return advertisement_mode & NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO;
}
// Gets battery info. Returns the input BatteryInfo or NULL on error.
static nearby_platform_BatteryInfo* PrepareBatteryInfo(
nearby_platform_BatteryInfo* battery_info) {
nearby_platform_status status;
battery_info->is_charging = false;
battery_info->right_bud_battery_level = battery_info->left_bud_battery_level =
battery_info->charging_case_battery_level = INVALID_BATTERY_LEVEL;
battery_info->remaining_time_minutes = 0;
status = nearby_platform_GetBatteryInfo(battery_info);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "GetBatteryInfo() failed with error %d", status);
return NULL;
}
return battery_info;
}
#if NEARBY_FP_MESSAGE_STREAM
static nearby_platform_status SendBatteryInfoMessage(uint64_t peer_address) {
uint8_t levels[BATTERY_LEVELS_SIZE];
nearby_message_stream_Message message = {
.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT,
.message_code = MESSAGE_CODE_BATTERY_UPDATED,
.length = sizeof(levels),
.data = levels};
nearby_platform_BatteryInfo battery_info;
if (!PrepareBatteryInfo(&battery_info)) return kNearbyStatusOK;
SerializeBatteryInfo(message.data, &battery_info);
return nearby_message_stream_Send(peer_address, &message);
}
static nearby_platform_status SendBatteryTimeMessage(uint64_t peer_address) {
uint8_t time[BATTERY_TIME_SIZE];
nearby_message_stream_Message message = {
.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT,
.message_code = MESSAGE_CODE_REMAINING_BATTERY_TIME,
.length = 1,
.data = time};
nearby_platform_BatteryInfo battery_info;
if (!PrepareBatteryInfo(&battery_info)) return kNearbyStatusOK;
if (battery_info.remaining_time_minutes > 255) {
message.length = sizeof(int16_t);
message.data[0] = battery_info.remaining_time_minutes >> 8;
message.data[1] = battery_info.remaining_time_minutes & 0xff;
} else {
message.data[0] = battery_info.remaining_time_minutes;
}
return nearby_message_stream_Send(peer_address, &message);
}
#endif /* NEARBY_FP_MESSAGE_STREAM */
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
static void AccountKeyRejected() {
if (++pairing_failure_count == MAX_PAIRING_FAILURE_COUNT) {
reject_pairing_time_start_ms = nearby_platform_GetCurrentTimeMs();
}
}
static void DiscardAccountKey() {
memset(&account_key_info, 0, sizeof(account_key_info));
}
static bool ShouldTimeout(unsigned int timeout_ms) {
return nearby_platform_GetCurrentTimeMs() - timeout_start_ms > timeout_ms;
}
static bool HasPendingAccountKey() {
return pending_account_key_info.account_key[0] ==
ACCOUNT_KEY_WRITE_MESSAGE_TYPE;
}
static void DiscardPendingAccountKey() {
memset(&pending_account_key_info, 0, sizeof(pending_account_key_info));
}
static void RotateBleAddress() {
address_rotation_timestamp = nearby_platform_GetCurrentTimeMs();
nearby_platform_SetAdvertisement(NULL, 0, kDisabled);
#ifdef NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION
uint64_t address = nearby_platform_RotateBleAddress();
NEARBY_TRACE(INFO, "Rotated BLE address to: %s",
nearby_utils_MacToString(address));
#else
unsigned i;
uint64_t address = 0;
for (i = 0; i < BT_ADDRESS_LENGTH; i++) {
address = (address << 8) ^ nearby_platform_Rand();
}
address |= (uint64_t)1 << 46;
address &= ~((uint64_t)1 << 47);
NEARBY_TRACE(WARNING, "Rotate address to: %s",
nearby_utils_MacToString(address));
nearby_platform_SetBleAddress(address);
#if NEARBY_FP_MESSAGE_STREAM
SendBleAddressUpdatedToAll();
#endif
#endif /* NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION */
}
static nearby_platform_status SendKeyBasedPairingResponse(
uint64_t peer_address, bool extended_response) {
nearby_platform_status status;
uint8_t raw[AES_MESSAGE_SIZE_BYTES], encrypted[AES_MESSAGE_SIZE_BYTES];
nearby_fp_CreateRawKeybasedPairingResponse(raw, extended_response);
status = nearby_platform_Aes128Encrypt(raw, encrypted,
account_key_info.account_key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to encrypt key-based pairing response");
return status;
}
status = nearby_platform_GattNotify(peer_address, kKeyBasedPairing, encrypted,
sizeof(encrypted));
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to notify on key-based pairing response");
}
return status;
}
#if NEARBY_FP_ENABLE_ADDITIONAL_DATA
static nearby_platform_status NotifyPersonalizedName(uint64_t peer_address) {
NEARBY_TRACE(VERBOSE, "NotifyPersonalizedName");
uint8_t data[ADDITIONAL_DATA_HEADER_SIZE + PERSONALIZED_NAME_MAX_SIZE];
size_t length = PERSONALIZED_NAME_MAX_SIZE;
nearby_platform_status status;
status = nearby_platform_LoadValue(
kStoredKeyPersonalizedName, data + ADDITIONAL_DATA_HEADER_SIZE, &length);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(WARNING, "Failed to load personalized name, status: %d",
status);
return status;
}
if (!length) {
NEARBY_TRACE(WARNING, "Empty personalized name");
}
length += ADDITIONAL_DATA_HEADER_SIZE;
status = nearby_fp_EncodeAdditionalData(data, length,
account_key_info.account_key);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR, "Failed to encrypt additional data, status: %d",
status);
return status;
}
status =
nearby_platform_GattNotify(peer_address, kAdditionalData, data, length);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR, "Failed to notify on additional data, status: %d",
status);
NEARBY_TRACE(ERROR, "Additional data: %s",
nearby_utils_ArrayToString(data, length));
}
return kNearbyStatusOK;
}
static nearby_platform_status SaveAdditionalData(const uint8_t* data,
size_t length) {
if (additional_data_id == PERSONALIZED_NAME_DATA_ID) {
char name[PERSONALIZED_NAME_MAX_SIZE * sizeof(uint8_t) / sizeof(char) + 1];
memcpy(name, data, length);
name[length * sizeof(uint8_t) / sizeof(char)] = '\0';
NEARBY_TRACE(INFO, "Saving personalized name: %s", name);
nearby_platform_SetDeviceName(name);
return nearby_platform_SaveValue(kStoredKeyPersonalizedName, data, length);
}
NEARBY_TRACE(WARNING, "Unsupported data id 0x%02x", additional_data_id);
return kNearbyStatusUnsupported;
}
static nearby_platform_status OnAdditionalDataWrite(uint64_t peer_address,
const uint8_t* request,
size_t length) {
NEARBY_TRACE(VERBOSE, "OnAdditionalDataWrite");
if (pairing_state != kPairingStateWaitingForAdditionalData) {
NEARBY_TRACE(WARNING,
"Not expecting a write to Additional Data. Current state: %d",
pairing_state);
return kNearbyStatusError;
}
nearby_platform_status status = nearby_fp_DecodeAdditionalData(
(uint8_t*)request, length, account_key_info.account_key);
DiscardAccountKey();
if (kNearbyStatusOK == status) {
status = SaveAdditionalData(request + ADDITIONAL_DATA_HEADER_SIZE,
length - ADDITIONAL_DATA_HEADER_SIZE);
}
pairing_state = kPairingStateIdle;
return status;
}
#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */
static nearby_platform_status HandleKeyBasedPairingRequest(
uint64_t peer_address, uint8_t request[ENCRYPTED_REQUEST_LENGTH]) {
NEARBY_TRACE(VERBOSE, "HandleKeyBasedPairingRequest");
int flags;
flags = request[1];
#if NEARBY_FP_ENABLE_ADDITIONAL_DATA
if (flags & KBPR_NOTIFY_EXISTING_NAME_MASK) {
NotifyPersonalizedName(peer_address);
}
#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */
if (flags & KBPR_RETROACTIVELY_WRITE_ACCOUNT_KEY_MASK) {
if (flags & KBPR_INITIATE_PAIRING_MASK) {
NEARBY_TRACE(WARNING,
"received flag Initiate pairing in retroactive pairing");
}
#if NEARBY_FP_RETROACTIVE_PAIRING
uint64_t peer_public_address =
nearby_utils_GetBigEndian48(request + KBPR_SEEKER_ADDRESS_OFFSET);
if (RetroactivePairingPeerPending(peer_public_address) == false) {
NEARBY_TRACE(
ERROR, "Ignoring retroactive pairing request from unexpected client");
RemoveRetroactivePairingPeer(peer_public_address);
return kNearbyStatusError;
}
if (SetRetroactivePairingPeerLe(peer_public_address, peer_address) ==
false) {
NEARBY_TRACE(ERROR, "Cannot set le address for retroactive pairing");
RemoveRetroactivePairingPeer(peer_public_address);
return kNearbyStatusError;
}
return kNearbyStatusOK;
#else
return kNearbyStatusUnsupported;
#endif /* NEARBY_FP_RETROACTIVE_PAIRING */
}
nearby_platform_SetFastPairCapabilities();
if (flags & KBPR_INITIATE_PAIRING_MASK) {
peer_public_address =
nearby_utils_GetBigEndian48(request + KBPR_SEEKER_ADDRESS_OFFSET);
NEARBY_TRACE(INFO, "Send pairing request to %s",
nearby_utils_MacToString(peer_public_address));
nearby_platform_SendPairingRequest(peer_public_address);
pairing_state = kPairingStateWaitingForPasskey;
} else {
pairing_state = kPairingStateWaitingForPairingRequest;
timeout_start_ms = nearby_platform_GetCurrentTimeMs();
}
return kNearbyStatusOK;
}
static nearby_platform_status HandleActionRequest(
uint64_t peer_address, uint8_t request[ENCRYPTED_REQUEST_LENGTH]) {
NEARBY_TRACE(VERBOSE, "HandleActionRequest");
int flags;
flags = request[1];
if (flags & ACTION_REQUEST_DEVICE_ACTION_MASK) {
NEARBY_TRACE(VERBOSE, "Device action not implemented");
return kNearbyStatusUnimplemented;
}
#if NEARBY_FP_ENABLE_ADDITIONAL_DATA
if (flags & ACTION_REQUEST_WILL_WRITE_DATA_CHARACTERISTIC_MASK) {
pairing_state = kPairingStateWaitingForAdditionalData;
additional_data_id = request[10];
}
return kNearbyStatusOK;
#else
return kNearbyStatusUnsupported;
#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */
}
static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address,
const uint8_t* request,
size_t length) {
nearby_platform_status status;
uint8_t decrypted_request[ENCRYPTED_REQUEST_LENGTH];
uint8_t ble_address[BT_ADDRESS_LENGTH];
uint8_t public_address[BT_ADDRESS_LENGTH];
NEARBY_TRACE(VERBOSE, "OnWriteKeyBasedPairing");
if (pairing_failure_count >= MAX_PAIRING_FAILURE_COUNT) {
unsigned int current_time = nearby_platform_GetCurrentTimeMs();
if (current_time - reject_pairing_time_start_ms <
REJECT_PAIRING_TIMEOUT_MS) {
NEARBY_TRACE(INFO, "Too many failed pairing attempts");
return kNearbyStatusOK;
} else {
NEARBY_TRACE(INFO, "Timeout expired. Allow pairing attempts");
pairing_failure_count = 0;
}
}
nearby_utils_CopyBigEndian(ble_address, nearby_platform_GetBleAddress(),
BT_ADDRESS_LENGTH);
nearby_utils_CopyBigEndian(public_address, nearby_platform_GetPublicAddress(),
BT_ADDRESS_LENGTH);
// When the device is nondiscoverable, accept a saved account key.
// or accept a new account key for retroactive pairing
// When the device is discoverable, we can accept a new account key too.
if (length == ENCRYPTED_REQUEST_LENGTH + PUBLIC_KEY_LENGTH) {
uint8_t key[ACCOUNT_KEY_SIZE_BYTES];
uint8_t remote_public_key[PUBLIC_KEY_LENGTH];
memcpy(remote_public_key, request + ENCRYPTED_REQUEST_LENGTH,
PUBLIC_KEY_LENGTH);
status = nearby_fp_CreateSharedSecret(remote_public_key, key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to create shared key, error: %d", status);
NEARBY_TRACE(
ERROR, "Remote public key: %s",
nearby_utils_ArrayToString(remote_public_key, PUBLIC_KEY_LENGTH));
return status;
}
status = nearby_platform_Aes128Decrypt(request, decrypted_request, key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to decrypt request, error: %d", status);
return status;
}
if (!BtAddressMatch(ble_address,
decrypted_request + REQUEST_BT_ADDRESS_OFFSET) &&
!BtAddressMatch(public_address,
decrypted_request + REQUEST_BT_ADDRESS_OFFSET)) {
NEARBY_TRACE(INFO, "Invalid incoming BT address %s",
nearby_utils_ArrayToString(decrypted_request, 6));
AccountKeyRejected();
return kNearbyStatusOK;
}
memcpy(account_key_info.account_key, key, ACCOUNT_KEY_SIZE_BYTES);
#ifdef NEARBY_FP_ENABLE_SASS
account_key_info.peer_address = peer_address;
#endif /* NEARBY_FP_ENABLE_SASS */
} else if (length == ENCRYPTED_REQUEST_LENGTH) {
// try each key in the persisted Account Key List
int num_keys = nearby_fp_GetAccountKeyCount();
int i;
for (i = 0; i < num_keys; i++) {
const nearby_platform_AccountKeyInfo* key = nearby_fp_GetAccountKey(i);
status = nearby_platform_Aes128Decrypt(request, decrypted_request,
key->account_key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to decrypt request, error: %d", status);
return status;
}
if (BtAddressMatch(ble_address,
decrypted_request + REQUEST_BT_ADDRESS_OFFSET) ||
BtAddressMatch(public_address,
decrypted_request + REQUEST_BT_ADDRESS_OFFSET)) {
NEARBY_TRACE(VERBOSE, "Matched key number: %d", i);
nearby_fp_CopyAccountKey(&account_key_info, i);
#ifdef NEARBY_FP_ENABLE_SASS
// The existing account key could be for a different peer, so let's
// add it again with the current peer address
account_key_info.peer_address = peer_address;
#endif /* NEARBY_FP_ENABLE_SASS */
nearby_fp_AddAccountKey(&account_key_info);
nearby_fp_SaveAccountKeys();
break;
}
}
if (i == num_keys) {
NEARBY_TRACE(VERBOSE, "No key matched");
AccountKeyRejected();
return kNearbyStatusOK;
}
} else {
NEARBY_TRACE(WARNING,
"Unexpected key based pairing request length %d Request: %s",
length, nearby_utils_ArrayToString(request, length));
return kNearbyStatusError;
}
pairing_failure_count = 0;
gatt_peer_address = peer_address;
DiscardPendingAccountKey();
bool extended_response =
decrypted_request[0] == KEY_BASED_PAIRING_REQUEST_FLAG &&
decrypted_request[1] & KBPR_SEEKER_SUPPORTS_BLE_DEVICES_MASK;
status = SendKeyBasedPairingResponse(peer_address, extended_response);
if (status != kNearbyStatusOK) return status;
// TODO(jsobczak): Note that at the end of the packet there is a salt
// attached. When possible, these salts should be tracked, and if the
// Provider receives a request containing an already used salt, the request
// should be ignored to prevent replay attacks.
if (decrypted_request[0] == KEY_BASED_PAIRING_REQUEST_FLAG) {
return HandleKeyBasedPairingRequest(peer_address, decrypted_request);
} else if (decrypted_request[0] == ACTION_REQUEST_FLAG) {
return HandleActionRequest(peer_address, decrypted_request);
}
return kNearbyStatusOK;
}
static nearby_platform_status NotifyProviderPasskey(uint64_t peer_address) {
uint8_t raw_passkey_block[AES_MESSAGE_SIZE_BYTES];
uint8_t encrypted[AES_MESSAGE_SIZE_BYTES];
uint32_t provider_passkey;
nearby_platform_status status;
provider_passkey = nearby_platfrom_GetPairingPassKey();
raw_passkey_block[0] = PROVIDER_PASSKEY_MESSAGE_TYPE;
nearby_utils_CopyBigEndian(raw_passkey_block + 1, provider_passkey, 3);
int i;
for (i = 4; i < AES_MESSAGE_SIZE_BYTES; i++) {
raw_passkey_block[i] = nearby_platform_Rand();
}
status = nearby_platform_Aes128Encrypt(raw_passkey_block, encrypted,
account_key_info.account_key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to encrypt passkey block");
return status;
}
status = nearby_platform_GattNotify(peer_address, kPasskey, encrypted,
sizeof(encrypted));
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR,
"Failed to notify on passkey characteristic, "
"Peer address: %s Encrypted: %s",
nearby_utils_MacToString(peer_address),
nearby_utils_ArrayToString(encrypted, sizeof(encrypted)));
}
return status;
}
static nearby_platform_status OnPasskeyWrite(uint64_t peer_address,
const uint8_t* request,
size_t length) {
NEARBY_TRACE(VERBOSE, "OnPasskeyWrite");
uint8_t raw_passkey_block[AES_MESSAGE_SIZE_BYTES];
nearby_platform_status status;
uint32_t seeker_passkey;
if (pairing_state != kPairingStateWaitingForPasskey) {
NEARBY_TRACE(INFO, "Not expecting a passkey write. Current state: %d",
pairing_state);
return kNearbyStatusError;
}
if (gatt_peer_address != peer_address) {
NEARBY_TRACE(INFO,
"Ignoring passkey write from unexpected client, expected GATT "
"peer address: %s",
nearby_utils_MacToString(gatt_peer_address));
NEARBY_TRACE(INFO, "Peer address: %s",
nearby_utils_MacToString(peer_address));
return kNearbyStatusError;
}
if (ShouldTimeout(PASSKEY_MAX_WAIT_TIME_MS)) {
NEARBY_TRACE(INFO, "Not expecting a passkey write");
return kNearbyStatusTimeout;
}
if (length != AES_MESSAGE_SIZE_BYTES) {
NEARBY_TRACE(INFO, "Passkey: expected %d bytes, got %d request: %s",
AES_MESSAGE_SIZE_BYTES, length,
nearby_utils_ArrayToString(request, length));
return kNearbyStatusError;
}
status = nearby_platform_Aes128Decrypt(request, raw_passkey_block,
account_key_info.account_key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(WARNING, "Failed to decrypt passkey block");
DiscardAccountKey();
return status;
}
if (raw_passkey_block[0] != SEEKER_PASSKEY_MESSAGE_TYPE) {
NEARBY_TRACE(WARNING, "Unexpected passkey message type 0x%x",
raw_passkey_block[0]);
return kNearbyStatusError;
}
pairing_state = kPairingStateWaitingForPairingResult;
NotifyProviderPasskey(peer_address);
seeker_passkey = nearby_utils_GetBigEndian24(raw_passkey_block + 1);
nearby_platform_SetRemotePasskey(seeker_passkey);
return kNearbyStatusOK;
}
static nearby_platform_status SetNonDiscoverableAdvertisement() {
uint8_t advertisement[NON_DISCOVERABLE_ADV_SIZE_BYTES];
const uint8_t* in_use_key = SelectInUseKey();
size_t length;
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
nearby_platform_BatteryInfo battery_info;
length = nearby_fp_CreateNondiscoverableAdvertisementWithBattery(
advertisement, sizeof(advertisement), ShowPairingIndicator(),
ShowBatteryIndicator(),
IncludeBatteryInfo() ? PrepareBatteryInfo(&battery_info) : NULL);
#else
length = nearby_fp_CreateNondiscoverableAdvertisement(
advertisement, sizeof(advertisement), ShowPairingIndicator());
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
#ifdef NEARBY_FP_ENABLE_SASS
if (IncludeSass() && (nearby_fp_GetAccountKeyCount() > 0)) {
uint8_t device_bitmap[MAX_DEVICE_BITMAP_SIZE];
size_t device_bitmap_length = sizeof(device_bitmap);
// Use either the in-use key or the most recently used account key
const uint8_t* key =
in_use_key ? in_use_key : nearby_fp_GetAccountKey(0)->account_key;
NEARBY_ASSERT(key != NULL);
nearby_platform_GetConnectionBitmap(device_bitmap, &device_bitmap_length);
size_t sass_length = nearby_fp_GenerateSassAdvertisement(
advertisement + length + RRF_HEADER_SIZE,
sizeof(advertisement) - length - RRF_HEADER_SIZE,
nearby_fp_GetSassConnectionState(), GetCustomData(), device_bitmap,
device_bitmap_length);
RETURN_IF_ERROR(nearby_fp_EncryptRandomResolvableField(
advertisement + length, RRF_HEADER_SIZE + sass_length, key,
nearby_fp_FindLtv(advertisement, SALT_FIELD_TYPE)));
length += RRF_HEADER_SIZE + sass_length;
// Update FP advertisement length
advertisement[0] = length - 1;
}
#endif /* NEARBY_FP_ENABLE_SASS */
nearby_fp_SetBloomFilter(advertisement, IncludeSass(), in_use_key);
length += nearby_fp_AppendTxPower(advertisement + length,
sizeof(advertisement) - length,
nearby_platform_GetTxLevel());
return nearby_platform_SetAdvertisement(advertisement, length,
kNoLargerThan250ms);
}
// Steps executed after successful pairing with a seeker and receiving the
// account key.
static void RunPostPairingSteps(
uint64_t peer_address, const nearby_platform_AccountKeyInfo* account_key) {
nearby_fp_AddAccountKey(account_key);
nearby_fp_SaveAccountKeys();
DiscardPendingAccountKey();
#if NEARBY_FP_RETROACTIVE_PAIRING
if (RetroactivePairingPeerPending(peer_address)) {
RemoveRetroactivePairingPeer(peer_address);
if (RetroactivePairingPeerPending(peer_address)) {
NEARBY_TRACE(WARNING, "peer is still pending %s",
nearby_utils_MacToString(peer_address));
}
if (pairing_state != kPairingStateIdle) {
NEARBY_TRACE(WARNING,
"Another fp pairing process has launched, do not change "
"pairing state");
return;
}
}
#endif /* NEARBY_FP_RETROACTIVE_PAIRING */
pairing_state = kPairingStateIdle;
#if NEARBY_FP_ENABLE_ADDITIONAL_DATA
pairing_state = kPairingStateWaitingForAdditionalData;
additional_data_id = PERSONALIZED_NAME_DATA_ID;
#endif
if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE) {
NEARBY_TRACE(INFO, "Account key added, update advertisement");
nearby_platform_SetAdvertisement(NULL, 0, kDisabled);
SetNonDiscoverableAdvertisement();
}
}
static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address,
const uint8_t* request,
size_t length) {
NEARBY_TRACE(VERBOSE, "OnAccountKeyWrite");
nearby_platform_AccountKeyInfo key_info;
uint8_t* decrypted_request = key_info.account_key;
nearby_platform_status status;
bool wait_until_paired = false;
#if NEARBY_FP_RETROACTIVE_PAIRING
if (RetroactivePairingPeerPending(peer_address)) {
if (RetroactivePairingPeerTimeout(peer_address)) {
NEARBY_TRACE(ERROR,
"Not expecting an retroactive pairing request. Timeout. "
"Peer address: %s",
nearby_utils_MacToString(peer_address));
RemoveRetroactivePairingPeer(peer_address);
return kNearbyStatusTimeout;
}
} else {
#endif /* NEARBY_FP_RETROACTIVE_PAIRING */
if (pairing_state == kPairingStateWaitingForPairingRequest ||
pairing_state == kPairingStateWaitingForPairingResult ||
pairing_state == kPairingStateWaitingForPasskey) {
NEARBY_TRACE(VERBOSE, "Account key write before paired event");
wait_until_paired = true;
} else if (pairing_state != kPairingStateWaitingForAccountKeyWrite) {
NEARBY_TRACE(INFO,
"Not expecting an account key write. Current state: %d",
pairing_state);
return kNearbyStatusError;
}
if (gatt_peer_address != peer_address &&
peer_public_address != peer_address) {
NEARBY_TRACE(INFO, "Ignoring account key write from unexpected client");
NEARBY_TRACE(ERROR, "Peer address: %s",
nearby_utils_MacToString(peer_address));
NEARBY_TRACE(ERROR, "Expected BLE peer address: %s",
nearby_utils_MacToString(gatt_peer_address));
NEARBY_TRACE(ERROR, "Expected BT peer address: %s",
nearby_utils_MacToString(peer_public_address));
return kNearbyStatusError;
}
if (ShouldTimeout(ACCOUNT_KEY_WRITE_TIME_MS)) {
NEARBY_TRACE(INFO, "Not expecting an account key write. Timeout");
return kNearbyStatusTimeout;
}
#if NEARBY_FP_RETROACTIVE_PAIRING
}
#endif /* NEARBY_FP_RETROACTIVE_PAIRING */
if (length != AES_MESSAGE_SIZE_BYTES) {
NEARBY_TRACE(INFO, "Account key: expected %d bytes, got %d",
AES_MESSAGE_SIZE_BYTES, length);
return kNearbyStatusError;
}
status = nearby_platform_Aes128Decrypt(request, decrypted_request,
account_key_info.account_key);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(WARNING, "Failed to decrypt account key block");
return status;
}
if (decrypted_request[0] != ACCOUNT_KEY_WRITE_MESSAGE_TYPE) {
NEARBY_TRACE(WARNING, "Unexpected account key message type 0x%x",
decrypted_request[0]);
return kNearbyStatusError;
}
#ifdef NEARBY_FP_ENABLE_SASS
key_info.peer_address = peer_address;
#endif /* NEARBY_FP_ENABLE_SASS */
if (wait_until_paired) {
memcpy(pending_account_key_info.account_key, decrypted_request,
ACCOUNT_KEY_SIZE_BYTES);
nearby_platform_StartTimer(
DiscardPendingAccountKey,
WAIT_FOR_PAIRING_RESULT_AFTER_ACCOUNT_KEY_TIME_MS);
return kNearbyStatusOK;
}
RunPostPairingSteps(peer_address, &key_info);
return kNearbyStatusOK;
}
static nearby_platform_status OnGattWrite(
uint64_t peer_address, nearby_fp_Characteristic characteristic,
const uint8_t* request, size_t length) {
switch (characteristic) {
case kKeyBasedPairing:
return OnWriteKeyBasedPairing(peer_address, request, length);
case kPasskey:
return OnPasskeyWrite(peer_address, request, length);
case kAccountKey:
return OnAccountKeyWrite(peer_address, request, length);
case kAdditionalData:
#if NEARBY_FP_ENABLE_ADDITIONAL_DATA
return OnAdditionalDataWrite(peer_address, request, length);
#else
break;
#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */
case kMessageStreamPsm:
case kModelId:
case kFirmwareRevision:
break;
}
NEARBY_TRACE(ERROR,
"Characteristic unsupported: Peer address: %s Characteristic: "
"%d Request: %s",
nearby_utils_MacToString(peer_address), characteristic,
nearby_utils_ArrayToString(request, length));
return kNearbyStatusUnsupported;
}
static int32_t GetMessageStreamPsm() {
#if NEARBY_FP_PREFER_LE_TRANSPORT
return nearby_platform_GetMessageStreamPsm();
#else
return -1;
#endif /* NEARBY_FP_PREFER_LE_TRANSPORT */
}
static nearby_platform_status OnMesssageStreamPsmRead(uint8_t* output,
size_t* length) {
int32_t psm = GetMessageStreamPsm();
if (*length < PSM_MESSAGE_LENGTH) {
return kNearbyStatusError;
}
memset(output, 0, PSM_MESSAGE_LENGTH);
*length = PSM_MESSAGE_LENGTH;
if (psm >= 0 && psm <= 0xFFFF) {
output[0] = 1;
output[1] = psm >> 8;
output[2] = psm;
}
return kNearbyStatusOK;
}
static nearby_platform_status OnGattRead(
uint64_t peer_address, nearby_fp_Characteristic characteristic,
uint8_t* output, size_t* length) {
switch (characteristic) {
case kModelId:
return nearby_fp_GattReadModelId(output, length);
case kMessageStreamPsm:
return OnMesssageStreamPsmRead(output, length);
case kKeyBasedPairing:
case kPasskey:
case kAccountKey:
case kFirmwareRevision:
case kAdditionalData:
break;
}
return kNearbyStatusUnsupported;
}
static void OnPairingRequest(uint64_t peer_address) {
NEARBY_TRACE(VERBOSE, "Pairing request from %s",
nearby_utils_MacToString(peer_address));
if (pairing_state == kPairingStateWaitingForPairingRequest) {
if (ShouldTimeout(WAIT_FOR_PAIRING_REQUEST_TIME_MS)) {
pairing_state = kPairingStateIdle;
} else {
pairing_state = kPairingStateWaitingForPasskey;
peer_public_address = peer_address;
timeout_start_ms = nearby_platform_GetCurrentTimeMs();
}
}
}
static void OnPaired(uint64_t peer_address) {
NEARBY_TRACE(INFO, "Paired with %s", nearby_utils_MacToString(peer_address));
if (peer_public_address == peer_address && HasPendingAccountKey()) {
NEARBY_TRACE(INFO, "Saving pending account key");
RunPostPairingSteps(peer_address, &pending_account_key_info);
return;
}
peer_public_address = peer_address;
if (pairing_state == kPairingStateWaitingForPairingResult) {
pairing_state = kPairingStateWaitingForAccountKeyWrite;
timeout_start_ms = nearby_platform_GetCurrentTimeMs();
}
#if NEARBY_FP_RETROACTIVE_PAIRING
else if (AddRetroactivePairingPeer(peer_address) == false) {
NEARBY_TRACE(WARNING, "No more timer for retroactive pairing");
}
#endif /* NEARBY_FP_RETROACTIVE_AIRING */
}
static void OnPairingFailed(uint64_t peer_address) {
NEARBY_TRACE(ERROR, "Pairing failed with %s",
nearby_utils_MacToString(peer_address));
DiscardAccountKey();
DiscardPendingAccountKey();
}
#if NEARBY_FP_MESSAGE_STREAM
// Finds and initializes an unused |rfcomm_input|. Returns NULL if not found.
static rfcomm_input* FindAvailableRfcommInput(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
if (input->state.peer_address == INVALID_PEER_ADDRESS ||
input->state.peer_address == peer_address) {
InitRfcommInput(peer_address, input);
return input;
}
}
return NULL;
}
// Gets |rfcomm_input| for |peer_address|. Returns NULL if not found.
static rfcomm_input* GetRfcommInput(uint64_t peer_address) {
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
if (rfcomm_inputs[i].state.peer_address == peer_address) {
return &rfcomm_inputs[i];
}
}
return NULL;
}
nearby_platform_status nearby_fp_client_SetSilenceMode(uint64_t peer_address,
bool enable) {
rfcomm_input* input = GetRfcommInput(peer_address);
if (!input) {
return kNearbyStatusError;
}
nearby_message_stream_Message message = {
.message_group = MESSAGE_GROUP_BLUETOOTH,
.message_code = MESSAGE_CODE_DISABLE_SILENCE_MODE,
.length = 0};
if (enable) message.message_code = MESSAGE_CODE_ENABLE_SILENCE_MODE;
if (!input) {
return kNearbyStatusError;
}
if (!ISSET(input->capabilities,
MESSAGE_CODE_CAPABILITIES_SILENCE_MODE_SUPPORTED)) {
return kNearbyStatusUnsupported;
}
return nearby_message_stream_Send(peer_address, &message);
}
nearby_platform_status nearby_fp_client_SignalLogBufferFull(
uint64_t peer_address) {
rfcomm_input* input = GetRfcommInput(peer_address);
if (!input) {
return kNearbyStatusError;
}
if (!ISSET(input->capabilities,
MESSAGE_CODE_CAPABILITIES_COMPANION_APP_INSTALLED)) {
return kNearbyStatusOK;
}
nearby_message_stream_Message message = {
.message_group = MESSAGE_GROUP_COMPANION_APP_EVENT,
.message_code = MESSAGE_CODE_LOG_BUFFER_FULL,
.length = 0};
return nearby_message_stream_Send(peer_address, &message);
}
static nearby_platform_status SendBleAddressUpdated(uint64_t peer_address) {
uint8_t buffer[BT_ADDRESS_LENGTH];
nearby_message_stream_Message message = {
.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT,
.message_code = MESSAGE_CODE_BLE_ADDRESS_UPDATED,
.length = BT_ADDRESS_LENGTH,
.data = buffer};
nearby_utils_CopyBigEndian(message.data, nearby_platform_GetBleAddress(),
BT_ADDRESS_LENGTH);
return nearby_message_stream_Send(peer_address, &message);
}
static void SendBleAddressUpdatedToAll() {
uint8_t buffer[BT_ADDRESS_LENGTH];
nearby_message_stream_Message message = {
.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT,
.message_code = MESSAGE_CODE_BLE_ADDRESS_UPDATED,
.length = BT_ADDRESS_LENGTH,
.data = buffer};
nearby_utils_CopyBigEndian(message.data, nearby_platform_GetBleAddress(),
BT_ADDRESS_LENGTH);
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
if (input->state.peer_address != INVALID_PEER_ADDRESS) {
nearby_message_stream_Send(input->state.peer_address, &message);
}
}
}
static void OnMessageStreamConnected(uint64_t peer_address) {
nearby_platform_status status;
uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE];
nearby_message_stream_Message message;
size_t length = sizeof(buffer);
rfcomm_input* input;
NEARBY_TRACE(VERBOSE, "OnMessageStreamConnected %s",
nearby_utils_MacToString(peer_address));
input = FindAvailableRfcommInput(peer_address);
if (!input) {
NEARBY_TRACE(WARNING, "Too many concurrent RFCOMM connections");
return;
}
message.data = buffer;
nearby_fp_GattReadModelId(message.data, &length);
message.length = length;
message.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT;
message.message_code = MESSAGE_CODE_MODEL_ID;
status = nearby_message_stream_Send(peer_address, &message);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR, "Failed to send model id, status: %d Peer address: %s",
status, nearby_utils_MacToString(peer_address));
return;
}
status = SendBleAddressUpdated(peer_address);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR,
"Failed to send ble address, status: %d Peer address: %s",
status, nearby_utils_MacToString(peer_address));
return;
}
// Send session nonce
message.message_code = MESSAGE_CODE_SESSION_NONCE;
message.length = SESSION_NONCE_SIZE;
message.data = input->session_nonce;
status = nearby_message_stream_Send(peer_address, &message);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR, "Failed to send session nonce, status: %d", status);
return;
}
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
status = SendBatteryInfoMessage(peer_address);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR,
"Failed to send battery info, status: %d Peer address: %s",
status, nearby_utils_MacToString(peer_address));
return;
}
status = SendBatteryTimeMessage(peer_address);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(ERROR,
"Failed to send battery info, status: %d Peer address: %s",
status, nearby_utils_MacToString(peer_address));
return;
}
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
if (client_callbacks != NULL && client_callbacks->on_event != NULL) {
nearby_event_MessageStreamConnected payload = {.peer_address =
peer_address};
nearby_event_Event event = {
.event_type = kNearbyEventMessageStreamConnected,
.payload = (uint8_t*)&payload};
client_callbacks->on_event(&event);
}
}
static void OnMessageStreamDisconnected(uint64_t peer_address) {
rfcomm_input* input;
NEARBY_TRACE(VERBOSE, "OnMessageStreamDisconnected %s",
nearby_utils_MacToString(peer_address));
input = GetRfcommInput(peer_address);
if (!input) {
NEARBY_TRACE(WARNING, "Unexpected disconnection from %s",
nearby_utils_MacToString(peer_address));
return;
}
#ifdef NEARBY_FP_ENABLE_SASS
bool update_advert = IncludeSass() && IsSassSeeker(input);
#endif /* NEARBY_FP_ENABLE_SASS */
input->state.peer_address = INVALID_PEER_ADDRESS;
if (client_callbacks != NULL && client_callbacks->on_event != NULL) {
nearby_event_MessageStreamDisconnected payload = {.peer_address =
peer_address};
nearby_event_Event event = {
.event_type = kNearbyEventMessageStreamDisconnected,
.payload = (uint8_t*)&payload};
client_callbacks->on_event(&event);
}
#if NEARBY_FP_RETROACTIVE_PAIRING
RemoveRetroactivePairingPeer(peer_address);
#endif /*NEARBY_FP_RETROACTIVE_PAIRING */
#ifdef NEARBY_FP_ENABLE_SASS
if (update_advert) {
UpdateAndNotifySass(NULL);
}
#endif /* NEARBY_FP_ENABLE_SASS */
}
static void OnMessageStreamReceived(uint64_t peer_address,
const uint8_t* message, size_t length) {
rfcomm_input* input = GetRfcommInput(peer_address);
if (!input) {
NEARBY_TRACE(WARNING, "Unexpected RFCOMM message from %s",
nearby_utils_MacToString(peer_address));
return;
}
nearby_message_stream_Read(&input->state, message, length);
}
static nearby_platform_status VerifyMessageLength(
uint64_t peer_address, const nearby_message_stream_Message* message,
size_t expected_length) {
if (message->length != expected_length) {
NEARBY_TRACE(WARNING, "Invalid message(%d) length %d, expected %d",
message->message_code, message->length, expected_length);
nearby_message_stream_SendNack(peer_address, message,
/* fail reason= */ 0);
return kNearbyStatusInvalidInput;
}
return kNearbyStatusOK;
}
// Sends either ACK or NACK response depending on |status|
static nearby_platform_status SendResponse(
uint64_t peer_address, const nearby_message_stream_Message* message,
nearby_platform_status status) {
if (kNearbyStatusOK == status) {
return nearby_message_stream_SendAck(peer_address, message);
} else {
// TODO(jsobczak): What should be the default fail reason?
uint8_t fail_reason = status == kNearbyStatusRedundantAction
? FAIL_REASON_REDUNDANT_DEVICE_ACTION
: 0;
return nearby_message_stream_SendNack(peer_address, message, fail_reason);
}
}
#ifdef NEARBY_FP_ENABLE_SASS
static uint8_t GetActiveDeviceFlag(uint64_t peer_address) {
uint64_t active_address = nearby_platform_GetActiveAudioSource();
if (IsDeviceSass(active_address) == false && active_address != 0x0) {
return SEEKER_IS_NON_SASS;
} else if (peer_address == active_address) {
return SEEKER_IS_ACTIVE_DEVICE;
} else {
return SEEKER_IS_NOT_ACTIVE_DEVICE;
}
}
static nearby_platform_status PrepareNotifyConnectionStatus(
nearby_message_stream_Message* message, rfcomm_input* input) {
size_t device_bitmap_length = MAX_DEVICE_BITMAP_SIZE;
NEARBY_ASSERT(message->length >= MAX_NOTIFY_CONNECTION_STATUS_MESSAGE_SIZE);
uint8_t iv[AES_MESSAGE_SIZE_BYTES];
memcpy(iv, input->session_nonce, SESSION_NONCE_SIZE);
for (int i = SESSION_NONCE_SIZE; i < AES_MESSAGE_SIZE_BYTES; i++) {
iv[i] = nearby_platform_Rand();
}
size_t offset = 0;
message->message_code = MESSAGE_CODE_SASS_NOTIFY_CONNECTION_STATUS;
message->data[offset++] = GetActiveDeviceFlag(input->state.peer_address);
message->data[offset++] = nearby_fp_GetSassConnectionState();
message->data[offset++] = GetCustomData();
nearby_platform_GetConnectionBitmap(message->data + offset,
&device_bitmap_length);
offset += device_bitmap_length;
memcpy(message->data + offset, iv + SESSION_NONCE_SIZE, SESSION_NONCE_SIZE);
// The first byte of |data| is not encrypted.
RETURN_IF_ERROR(nearby_fp_AesEncryptIv(message->data + 1, offset - 1, iv,
input->in_use_account_key));
message->length = offset + SESSION_NONCE_SIZE;
return kNearbyStatusOK;
}
static bool IsSameAccountKey(const rfcomm_input* a, const rfcomm_input* b) {
return a == b || (a != NULL && b != NULL &&
memcmp(a->in_use_account_key, b->in_use_account_key,
ACCOUNT_KEY_SIZE_BYTES) == 0);
}
static bool UsesInUseAccountKey(const rfcomm_input* input,
const uint8_t* account_key) {
return input != NULL && account_key != NULL &&
memcmp(input->in_use_account_key, account_key,
ACCOUNT_KEY_SIZE_BYTES) == 0;
}
static nearby_platform_status NotifySassConnectionStatus(
const rfcomm_input* seeker) {
const uint8_t* in_use_key = seeker == NULL ? SelectInUseKey() : NULL;
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
// Notify all seekers that are using the same key of the seeker
// if seeker is NULL, notify all seekers that are using the selected in
// use key
if (IsSassSeeker(input) && (IsSameAccountKey(input, seeker) ||
UsesInUseAccountKey(input, in_use_key))) {
uint8_t buffer[MAX_NOTIFY_CONNECTION_STATUS_MESSAGE_SIZE];
nearby_message_stream_Message reply;
reply.message_group = MESSAGE_GROUP_SASS;
reply.data = buffer;
reply.length = sizeof(buffer);
RETURN_IF_ERROR(PrepareNotifyConnectionStatus(&reply, input));
nearby_message_stream_Send(input->state.peer_address, &reply);
}
}
return kNearbyStatusOK;
}
// |seeker| is the peer that has initiated the update.
static void UpdateAndNotifySass(const rfcomm_input* seeker) {
if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE &&
IncludeSass()) {
NEARBY_TRACE(INFO, "Audio state changed, update advertisement");
nearby_platform_SetAdvertisement(NULL, 0, kDisabled);
SetNonDiscoverableAdvertisement();
}
NotifySassConnectionStatus(seeker);
}
static void OnAudioStateChange() { UpdateAndNotifySass(NULL); }
static char HexDigit(int digit) {
if (digit < 10) return '0' + digit;
if (digit < 16) return 'A' + digit - 10;
return '?';
}
static void OnMultipointSwitchEvent(uint8_t reason, uint64_t peer_address,
const char* name) {
uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE];
nearby_message_stream_Message message;
message.message_group = MESSAGE_GROUP_SASS;
message.message_code = MESSAGE_CODE_SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT;
message.data = buffer;
message.data[0] = reason;
if (name != NULL) {
size_t name_length = strlen(name);
size_t max_name_length =
sizeof(buffer) - SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_NAME_OFFSET;
if (name_length > max_name_length) name_length = max_name_length;
message.length =
SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_NAME_OFFSET + name_length;
memcpy(message.data + SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_NAME_OFFSET, name,
name_length);
} else {
message.length = 6;
message.data[2] = HexDigit((peer_address >> 12) & 0xF);
message.data[3] = HexDigit((peer_address >> 8) & 0xF);
message.data[4] = HexDigit((peer_address >> 4) & 0xF);
message.data[5] = HexDigit(peer_address & 0xF);
}
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
if (IsSassSeeker(input)) {
message.data[1] =
input->state.peer_address == peer_address
? SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_TARGET_THIS_DEVICE
: SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT_TARGET_ANOTHER_DEVICE;
nearby_message_stream_Send(input->state.peer_address, &message);
}
}
}
static const nearby_platform_AudioCallbacks kAudioInterface = {
.on_state_change = OnAudioStateChange,
.on_multipoint_switch_event = OnMultipointSwitchEvent,
};
static void PrepareNotifySassCapabilityResponse(
nearby_message_stream_Message* message) {
NEARBY_ASSERT(message->length >= 2 * sizeof(uint16_t));
message->message_code = MESSAGE_CODE_SASS_NOTIFY_CAPABILITY;
message->length = 2 * sizeof(uint16_t);
nearby_utils_CopyBigEndian(message->data, kSassVersion, sizeof(uint16_t));
nearby_utils_CopyBigEndian(message->data + sizeof(uint16_t),
nearby_fp_GetSassCapabilityFlags(),
sizeof(uint16_t));
}
static void PrepareNotifySwitchingPreferenceResponse(
nearby_message_stream_Message* message) {
NEARBY_ASSERT(message->length >= 2);
message->message_code = MESSAGE_CODE_SASS_NOTIFY_SWITCHING_PREFERENCE;
message->length = 2;
message->data[0] = nearby_platform_GetSwitchingPreference();
message->data[1] = 0;
}
// Verifies MAC and sends a NACK if verification failed.
static nearby_platform_status VerifyMac(
uint64_t peer_address, const nearby_message_stream_Message* message,
rfcomm_input* input) {
nearby_platform_status status = nearby_fp_VerifyMessageAuthenticationCode(
message->data, message->length, input->in_use_account_key,
input->session_nonce);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(WARNING, "Invalid MAC on message %d", message->message_code);
nearby_message_stream_SendNack(peer_address, message,
FAIL_REASON_INVALID_MAC);
}
return status;
}
// Finds the account key which was used to encode |message| and marks it at
// the in-use key for this session
static nearby_platform_status IndicateInUseKey(
const nearby_message_stream_Message* message, rfcomm_input* input) {
int offset = 0;
while (true) {
offset = nearby_fp_GetNextUniqueAccountKeyIndex(offset);
if (offset == -1) {
return kNearbyStatusInvalidInput;
} else if (kNearbyStatusOK ==
nearby_fp_VerifyMessageAuthenticationCode(
message->data, message->length,
nearby_fp_GetAccountKey(offset)->account_key,
input->session_nonce)) {
memcpy(input->in_use_account_key,
nearby_fp_GetAccountKey(offset)->account_key,
ACCOUNT_KEY_SIZE_BYTES);
nearby_fp_MarkAccountKeyAsActive(offset);
UpdateAndNotifySass(NULL);
return kNearbyStatusOK;
}
offset++;
}
}
// Selects a next, preferred audio source. The request to change the audio
// source came from |input|, which presumably is the current audio source.
static uint64_t FindNextAudioSource(const rfcomm_input* input) {
int i;
// 1. Select another seeker that uses the same account key.
for (i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
if (rfcomm_inputs[i].state.peer_address != INVALID_PEER_ADDRESS &&
rfcomm_inputs[i].state.peer_address != input->state.peer_address &&
IsSameAccountKey(&rfcomm_inputs[i], input)) {
return rfcomm_inputs[i].state.peer_address;
}
}
// 2. Select any other seeker.
for (i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
if (rfcomm_inputs[i].state.peer_address != INVALID_PEER_ADDRESS &&
rfcomm_inputs[i].state.peer_address != input->state.peer_address) {
return rfcomm_inputs[i].state.peer_address;
}
}
// 3. There's only one connected seeker
return INVALID_PEER_ADDRESS;
}
static nearby_platform_status HandleSassMessage(
uint64_t peer_address, nearby_message_stream_Message* message) {
rfcomm_input* input = GetRfcommInput(peer_address);
NEARBY_ASSERT(input != NULL);
uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE];
nearby_message_stream_Message reply;
reply.message_group = MESSAGE_GROUP_SASS;
reply.data = buffer;
reply.length = sizeof(buffer);
switch (message->message_code) {
case MESSAGE_CODE_SASS_GET_CAPABILITY:
PrepareNotifySassCapabilityResponse(&reply);
return nearby_message_stream_Send(peer_address, &reply);
case MESSAGE_CODE_SASS_SET_MULTIPOINT_STATE: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 17));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
bool enable = message->data[0] == 1;
NEARBY_TRACE(INFO, "Set Multipoint enable: %d", enable);
return SendResponse(peer_address, message,
nearby_platform_SetMultipoint(peer_address, enable));
}
case MESSAGE_CODE_SASS_SET_SWITCHING_PREFERENCE: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 18));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
uint8_t flags = message->data[0];
NEARBY_TRACE(INFO, "Set switching preference: 0x%x", flags);
return SendResponse(peer_address, message,
nearby_platform_SetSwitchingPreference(flags));
}
case MESSAGE_CODE_SASS_GET_SWITCHING_PREFERENCE:
PrepareNotifySwitchingPreferenceResponse(&reply);
return nearby_message_stream_Send(peer_address, &reply);
case MESSAGE_CODE_SASS_SWITCH_ACTIVE_AUDIO_SOURCE: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 17));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
uint8_t flags = message->data[0];
NEARBY_TRACE(INFO, "Switch active audio source: 0x%x", flags);
uint64_t preferred_audio_source =
ISSET(flags, 7) ? peer_address : FindNextAudioSource(input);
return SendResponse(peer_address, message,
nearby_platform_SwitchActiveAudioSource(
peer_address, flags, preferred_audio_source));
}
case MESSAGE_CODE_SASS_SWITCH_BACK_AUDIO_SOURCE: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 17));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
uint8_t flags = message->data[0];
NEARBY_TRACE(INFO, "Switch back: 0x%x", flags);
return SendResponse(
peer_address, message,
nearby_platform_SwitchBackAudioSource(peer_address, flags));
}
case MESSAGE_CODE_SASS_GET_CONNECTION_STATUS:
PrepareNotifyConnectionStatus(&reply, input);
return nearby_message_stream_Send(peer_address, &reply);
case MESSAGE_CODE_SASS_NOTIFY_SASS_INITIATED_CONNECTION: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 17));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
uint8_t flags = message->data[0];
NEARBY_TRACE(INFO, "Notify SASS initiated connection: 0x%x", flags);
return SendResponse(
peer_address, message,
nearby_platform_NotifySassInitiatedConnection(peer_address, flags));
}
case MESSAGE_CODE_SASS_IN_USE_ACCOUNT_KEY: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 22));
NEARBY_TRACE(INFO, "Indicate in-use key");
return SendResponse(peer_address, message,
IndicateInUseKey(message, input));
}
case MESSAGE_CODE_SASS_SEND_CUSTOM_DATA: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 17));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
uint8_t data = message->data[0];
NEARBY_TRACE(INFO, "Send custom data: 0x%x", data);
if (GetCustomData() != data) {
SetCustomData(data);
UpdateAndNotifySass(input);
}
return SendResponse(peer_address, message, kNearbyStatusOK);
}
case MESSAGE_CODE_SASS_SET_DROP_CONNECTION_TARGET: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 17));
RETURN_IF_ERROR(VerifyMac(peer_address, message, input));
uint8_t flags = message->data[0];
NEARBY_TRACE(INFO, "Set drop connection target: 0x%x", flags);
return SendResponse(
peer_address, message,
nearby_platform_SetDropConnectionTarget(peer_address, flags));
}
}
return kNearbyStatusOK;
}
#endif /* NEARBY_FP_ENABLE_SASS */
static void PrepareActiveComponentResponse(
nearby_message_stream_Message* message) {
NEARBY_ASSERT(message->length >= 2 * sizeof(uint16_t));
message->message_code = MESSAGE_CODE_ACTIVE_COMPONENT_RESPONSE;
message->length = 1;
message->data[0] = nearby_platform_GetEarbudLeftStatus() << 1 |
nearby_platform_GetEarbudRightStatus();
}
static nearby_platform_status HandleGeneralMessage(
uint64_t peer_address, nearby_message_stream_Message* message) {
rfcomm_input* input = GetRfcommInput(peer_address);
NEARBY_ASSERT(input != NULL);
uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE];
nearby_message_stream_Message reply;
reply.data = buffer;
reply.length = sizeof(buffer);
switch (message->message_group) {
case MESSAGE_GROUP_DEVICE_INFORMATION_EVENT: {
reply.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT;
switch (message->message_code) {
case MESSAGE_CODE_ACTIVE_COMPONENT_REQUEST: {
PrepareActiveComponentResponse(&reply);
return nearby_message_stream_Send(peer_address, &reply);
}
case MESSAGE_CODE_CAPABILITIES: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 1));
uint8_t flags = message->data[0];
NEARBY_TRACE(INFO, "Set capabilities: 0x%x", flags);
input->capabilities = flags;
return kNearbyStatusOK;
}
case MESSAGE_CODE_PLATFORM_TYPE: {
RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 2));
uint8_t type = message->data[0];
uint8_t build = message->data[1];
NEARBY_TRACE(INFO, "Set platform type: 0x%x:0x%x", type, build);
input->platform_type = type;
input->platform_build = build;
return kNearbyStatusOK;
}
}
break;
}
case MESSAGE_GROUP_DEVICE_ACTION_EVENT: {
reply.message_group = MESSAGE_GROUP_DEVICE_ACTION_EVENT;
switch (message->message_code) {
case MESSAGE_CODE_RING: {
if (message->length < 1 || message->length > 2) {
NEARBY_TRACE(WARNING, "Invalid message(%d) length %d",
message->message_code, message->length);
nearby_message_stream_SendNack(peer_address, message,
/* fail reason= */ 0);
return kNearbyStatusInvalidInput;
}
uint8_t command = message->data[0];
uint16_t timeout = 0;
if (message->length > 1) timeout = message->data[1];
NEARBY_TRACE(INFO, "Set ring device: 0x%x %d", command, timeout);
return SendResponse(peer_address, message,
nearby_platform_Ring(command, timeout * 10));
}
}
break;
}
}
return kNearbyStatusOK;
}
static void OnMessageReceived(uint64_t peer_address,
nearby_message_stream_Message* message) {
nearby_platform_status status;
status = HandleGeneralMessage(peer_address, message);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(WARNING,
"Processing stream message failed with %d Peer address: %s",
status, nearby_utils_MacToString(peer_address));
return;
}
#ifdef NEARBY_FP_ENABLE_SASS
if (message->message_group == MESSAGE_GROUP_SASS) {
nearby_platform_status status = HandleSassMessage(peer_address, message);
if (kNearbyStatusOK != status) {
NEARBY_TRACE(WARNING, "Processing SASS message failed with %d", status);
return;
}
}
#endif /* NEARBY_FP_ENABLE_SASS */
if (client_callbacks != NULL && client_callbacks->on_event != NULL) {
nearby_event_MessageStreamReceived payload = {
.peer_address = peer_address,
.message_group = message->message_group,
.message_code = message->message_code,
.length = message->length,
.data = message->data};
nearby_event_Event event = {.event_type = kNearbyEventMessageStreamReceived,
.payload = (uint8_t*)&payload};
client_callbacks->on_event(&event);
}
}
nearby_platform_status nearby_fp_client_SendMessage(
uint64_t peer_address, const nearby_message_stream_Message* message) {
return nearby_message_stream_Send(peer_address, message);
}
nearby_platform_status nearby_fp_client_SendAck(
const nearby_event_MessageStreamReceived* message) {
nearby_message_stream_Message stream_message = {
.message_group = message->message_group,
.message_code = message->message_code,
};
return nearby_message_stream_SendAck(message->peer_address, &stream_message);
}
nearby_platform_status nearby_fp_client_SendNack(
const nearby_event_MessageStreamReceived* message, uint8_t fail_reason) {
nearby_message_stream_Message stream_message = {
.message_group = message->message_group,
.message_code = message->message_code,
};
return nearby_message_stream_SendNack(message->peer_address, &stream_message,
fail_reason);
}
#endif /* NEARBY_FP_MESSAGE_STREAM */
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
static void OnBatteryChanged(void) {
nearby_platform_status status;
if (pairing_state != kPairingStateIdle &&
pairing_state != kPairingStateWaitingForAccountKeyWrite &&
pairing_state != kPairingStateWaitingForAdditionalData) {
NEARBY_TRACE(ERROR, "%s: device is in pairing process", __func__);
return;
}
if (IncludeBatteryInfo() &&
(advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE)) {
status = nearby_platform_SetAdvertisement(NULL, 0, kDisabled);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to update battery change, status: %d",
status);
return;
}
status = SetNonDiscoverableAdvertisement();
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR, "Failed to update battery change, status: %d",
status);
return;
}
}
#if NEARBY_FP_MESSAGE_STREAM
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
rfcomm_input* input = &rfcomm_inputs[i];
if (input->state.peer_address != INVALID_PEER_ADDRESS) {
status = SendBatteryInfoMessage(input->state.peer_address);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR,
"Failed to send battery change, status: %d "
"Peer address: %s",
status,
nearby_utils_MacToString(input->state.peer_address));
}
status = SendBatteryTimeMessage(input->state.peer_address);
if (status != kNearbyStatusOK) {
NEARBY_TRACE(ERROR,
"Failed to send battery change, status: %d "
"Peer address: %s",
status,
nearby_utils_MacToString(input->state.peer_address));
}
}
}
#endif /* NEARBY_FP_MESSAGE_STREAM */
}
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
static const nearby_platform_BleInterface kBleInterface = {
.on_gatt_write = OnGattWrite,
.on_gatt_read = OnGattRead,
};
static const nearby_platform_BtInterface kBtInterface = {
.on_pairing_request = OnPairingRequest,
.on_paired = OnPaired,
.on_pairing_failed = OnPairingFailed,
#if NEARBY_FP_MESSAGE_STREAM
.on_message_stream_connected = OnMessageStreamConnected,
.on_message_stream_disconnected = OnMessageStreamDisconnected,
.on_message_stream_received = OnMessageStreamReceived,
#endif /* NEARBY_FP_MESSAGE_STREAM */
};
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
static nearby_platform_BatteryInterface kBatteryInterface = {
.on_battery_changed = OnBatteryChanged,
};
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
static nearby_platform_status EnterDisabledMode() {
nearby_platform_SetDefaultCapabilities();
return nearby_platform_SetAdvertisement(NULL, 0, kDisabled);
}
static nearby_platform_status EnterDiscoverableMode() {
size_t length;
uint8_t advertisement[DISCOVERABLE_ADV_SIZE_BYTES];
if (pairing_state != kPairingStateIdle &&
pairing_state != kPairingStateWaitingForAccountKeyWrite &&
pairing_state != kPairingStateWaitingForAdditionalData) {
NEARBY_TRACE(ERROR, "%s: device is in pairing process", __func__);
return kNearbyStatusError;
}
length = nearby_fp_CreateDiscoverableAdvertisement(advertisement,
sizeof(advertisement));
length += nearby_fp_AppendTxPower(advertisement + length,
sizeof(advertisement) - length,
nearby_platform_GetTxLevel());
return nearby_platform_SetAdvertisement(advertisement, length,
kNoLargerThan100ms);
}
static nearby_platform_status EnterNonDiscoverableMode() {
if (pairing_state != kPairingStateIdle &&
pairing_state != kPairingStateWaitingForAccountKeyWrite &&
pairing_state != kPairingStateWaitingForAdditionalData) {
NEARBY_TRACE(ERROR, "%s: device is in pairing process", __func__);
return kNearbyStatusError;
}
nearby_platform_SetDefaultCapabilities();
return SetNonDiscoverableAdvertisement();
}
static bool ShouldRotateBleAddress(int mode) {
if (IsInPairingMode()) {
return false;
}
// We should rotate if advertisement mode changes to discoverable to prevent
// replay attacks.
return (mode & NEARBY_FP_ADVERTISEMENT_DISCOVERABLE) &&
(!(advertisement_mode & NEARBY_FP_ADVERTISEMENT_DISCOVERABLE));
}
static uint32_t GetRotationDelayMs() {
uint32_t delay_ms = ADDRESS_ROTATION_PERIOD_MS;
// Rotation should happen every 1024 seconds on average. It is required that
// the precise point at which the beacon starts advertising the new
// identifier is randomized within the window. This logic should give us
// +/-200 seconds variability.
for (int i = 0; i < 5; i++) {
delay_ms += (50 << i) * (int8_t)nearby_platform_Rand();
}
return delay_ms;
}
static void MaybeRotateBleAddress();
static void CancelAddressRotationTimer() {
void* task = address_rotation_task;
address_rotation_task = NULL;
if (task != NULL) {
nearby_platform_CancelTimer(task);
}
}
static void ScheduleAddressRotation() {
CancelAddressRotationTimer();
address_rotation_task =
nearby_platform_StartTimer(MaybeRotateBleAddress, GetRotationDelayMs());
}
static nearby_platform_status UpdateAdvertisements() {
if (advertisement_mode == NEARBY_FP_ADVERTISEMENT_NONE) {
return EnterDisabledMode();
}
if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_DISCOVERABLE) {
return EnterDiscoverableMode();
}
if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE) {
return EnterNonDiscoverableMode();
}
return kNearbyStatusUnsupported;
}
static void MaybeRotateBleAddress() {
ScheduleAddressRotation();
if (IsInPairingMode()) {
return;
}
RotateBleAddress();
UpdateAdvertisements();
}
static bool NeedsPeriodicAddressRotation() {
// FP spec says we should rotate BLE adress every ~15 minutes when
// advertising
return (advertisement_mode & (NEARBY_FP_ADVERTISEMENT_DISCOVERABLE |
NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE)) != 0;
}
nearby_platform_status nearby_fp_client_SetAdvertisement(int mode) {
if (advertisement_mode == mode) {
return kNearbyStatusOK;
}
if (ShouldRotateBleAddress(mode)) {
CancelAddressRotationTimer();
RotateBleAddress();
}
advertisement_mode = mode;
if (NeedsPeriodicAddressRotation() && address_rotation_task == NULL) {
ScheduleAddressRotation();
}
return UpdateAdvertisements();
}
#if NEARBY_FP_MESSAGE_STREAM
nearby_platform_status nearby_fp_client_GetSeekerInfo(
nearby_fp_client_SeekerInfo* seeker_info, size_t* seeker_info_length) {
int sl = *seeker_info_length;
int inx = 0;
*seeker_info_length = inx;
for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) {
if (rfcomm_inputs[i].state.peer_address != INVALID_PEER_ADDRESS) {
if (inx >= sl) return kNearbyStatusInvalidInput;
seeker_info[inx].peer_address = rfcomm_inputs[i].state.peer_address;
seeker_info[inx].capabilities = rfcomm_inputs[i].capabilities;
seeker_info[inx].platform_type = rfcomm_inputs[i].platform_type;
seeker_info[inx].platform_build = rfcomm_inputs[i].platform_build;
inx++;
*seeker_info_length = inx;
}
}
return kNearbyStatusOK;
}
#endif /* NEARBY_FP_MESSAGE_STREAM */
nearby_platform_status nearby_fp_client_Init(
const nearby_fp_client_Callbacks* callbacks) {
nearby_platform_status status;
client_callbacks = callbacks;
pairing_state = kPairingStateIdle;
pairing_failure_count = 0;
#if NEARBY_FP_MESSAGE_STREAM
memset(rfcomm_inputs, 0, sizeof(rfcomm_inputs));
#endif /* NEARBY_FP_MESSAGE_STREAM */
advertisement_mode = NEARBY_FP_ADVERTISEMENT_NONE;
address_rotation_task = NULL;
peer_public_address = 0;
DiscardPendingAccountKey();
status = nearby_platform_OsInit();
if (status != kNearbyStatusOK) return status;
status = nearby_platform_SecureElementInit();
if (status != kNearbyStatusOK) return status;
status = nearby_platform_BtInit(&kBtInterface);
if (status != kNearbyStatusOK) return status;
status = nearby_platform_BleInit(&kBleInterface);
if (status != kNearbyStatusOK) return status;
status = nearby_platform_PersistenceInit();
if (status != kNearbyStatusOK) return status;
#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION
status = nearby_platform_BatteryInit(&kBatteryInterface);
if (status != kNearbyStatusOK) return status;
#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */
status = nearby_fp_LoadAccountKeys();
if (status != kNearbyStatusOK) return status;
#ifdef NEARBY_FP_ENABLE_SASS
SetCustomData(0);
status = nearby_platform_AudioInit(&kAudioInterface);
if (status != kNearbyStatusOK) return status;
#endif /* NEARBY_FP_ENABLE_SASS */
RotateBleAddress();
return status;
}