From f643f7a466c05cc6a07ddc0495200e66fd3e7526 Mon Sep 17 00:00:00 2001 From: Janusz Sobczak Date: Tue, 20 Dec 2022 10:50:13 -0800 Subject: [PATCH 1/2] 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. --- embedded/Makefile | 32 + embedded/README.md | 14 +- embedded/client/source/nearby_fp_client.c | 784 ++++++- embedded/client/source/nearby_fp_client.h | 9 +- embedded/client/tests/gLinux/audio.cc | 166 ++ embedded/client/tests/gLinux/battery.cc | 5 +- embedded/client/tests/gLinux/ble.cc | 13 + embedded/client/tests/gLinux/bt.cc | 28 +- embedded/client/tests/gLinux/fakes.h | 94 +- embedded/client/tests/gLinux/persistence.cc | 6 + embedded/client/tests/gLinux/se.cc | 51 +- embedded/client/tests/message_stream_test.cc | 26 +- embedded/client/tests/smoke_test.cc | 1983 +++++++++++++++-- embedded/common/source/mbedtls/gen_secret.c | 108 + embedded/common/source/mbedtls/mbedtls.c | 121 + embedded/common/source/nearby_config.h | 43 +- embedded/common/source/nearby_event.h | 112 + embedded/common/source/nearby_fp_library.c | 571 ++++- embedded/common/source/nearby_fp_library.h | 162 +- .../common/source/nearby_message_stream.c | 2 +- .../common/source/nearby_message_stream.h | 3 +- embedded/common/target/nearby.h | 16 + .../common/target/nearby_platform_audio.h | 175 ++ embedded/common/target/nearby_platform_ble.h | 14 + embedded/common/target/nearby_platform_bt.h | 19 +- embedded/common/target/nearby_platform_se.h | 6 + embedded/target/gLinux/config.mk | 21 +- embedded/target/gLinux/rules.mk | 41 +- 28 files changed, 4093 insertions(+), 532 deletions(-) create mode 100644 embedded/common/source/mbedtls/gen_secret.c create mode 100644 embedded/common/source/mbedtls/mbedtls.c diff --git a/embedded/Makefile b/embedded/Makefile index 7c33226859..4f25308def 100644 --- a/embedded/Makefile +++ b/embedded/Makefile @@ -7,6 +7,8 @@ GTEST_DIR = ../third_party/gtest/googletest/ GMOCK_DIR = ../third_party/gtest/googlemock/ +MBEDTLS_DIR = ../third_party/mbedtls/ + ARCH ?= host OUT_DIR_NAME ?= $(ARCH) @@ -84,12 +86,42 @@ ifeq ($(wildcard common/target/$(ARCH_COMMON_NAME)),) $(error need to create directory common/target/$(ARCH_COMMON_NAME) ) endif +ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +CFLAGS += -DNEARBY_FP_ENABLE_BATTERY_NOTIFICATION=$(NEARBY_FP_ENABLE_BATTERY_NOTIFICATION) +endif + +ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +CFLAGS += -DNEARBY_FP_ENABLE_ADDITIONAL_DATA=$(NEARBY_FP_ENABLE_ADDITIONAL_DATA) +endif + +ifdef NEARBY_FP_MESSAGE_STREAM +CFLAGS += -DNEARBY_FP_MESSAGE_STREAM=$(NEARBY_FP_MESSAGE_STREAM) +endif + +ifdef NEARBY_FP_RETROACTIVE_PAIRING +CFLAGS += -DNEARBY_FP_RETROACTIVE_PAIRING=$(NEARBY_FP_RETROACTIVE_PAIRING) +endif + +ifdef NEARBY_FP_BLE_ONLY +CFLAGS += -DNEARBY_FP_BLE_ONLY=$(NEARBY_FP_BLE_ONLY) +endif + +ifdef NEARBY_FP_PREFER_BLE_BONDING +CFLAGS += -DNEARBY_FP_PREFER_BLE_BONDING=$(NEARBY_FP_PREFER_BLE_BONDING) +endif + +ifdef NEARBY_FP_PREFER_LE_TRANSPORT +CFLAGS += -DNEARBY_FP_PREFER_LE_TRANSPORT=$(NEARBY_FP_PREFER_LE_TRANSPORT) +endif COMMON_INCLUDE_DIRS += \ -I. \ -I$(ARCH_COMMON_DIR) \ -Icommon/target \ -Icommon/target/$(ARCH_COMMON_NAME) \ -I$(COMMON_DIR) +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +COMMON_INCLUDE_DIRS += -I$(MBEDTLS_DIR)/include +endif CLIENT_INCLUDES += \ -I$(CLIENT_DIR) \ diff --git a/embedded/README.md b/embedded/README.md index 83ce5a9b63..55c7ec2f48 100644 --- a/embedded/README.md +++ b/embedded/README.md @@ -28,4 +28,16 @@ Nearby SDK assumes a single-threading model. All calls to the SDK must be on the nearby_fp_client_Init(NULL); nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); ``` -1. Use [Fast Pair Validator](https://play.google.com/store/apps/details?id=com.google.location.nearby.apps.fastpair.validator) to verify that your device is behaving correctly. \ No newline at end of file +1. Use [Fast Pair Validator](https://play.google.com/store/apps/details?id=com.google.location.nearby.apps.fastpair.validator) to verify that your device is behaving correctly. + +## Optional modules +The optional modules provide partial implementation of HAL interfaces using common libraries, mbedtls in particular. + +1. *mbedtls* located in `common/source/mbedtls/mbedtls.c` implements `nearby_platform_Sha256Start()`, `nearby_platform_Sha256Update()`, `nearby_platform_Sha256Finish()`, `nearby_platform_Aes128Encrypt()`, `nearby_platform_Aes128Decrypt()`. + +Nearby SDK can be configured to use the MBEDTLS package, commonly available on ARM +implementations, with the config.mk flag `NEARBY_PLATFORM_USE_MBEDTLS`. + +2. *gen_secret* located in `common/source/mbedtls/gen_secret.c` implements `nearby_platform_GenSec256r1Secret()`. + +*gen_secret* generates a shared secret based on a given private key on platforms that don't support hardware SE. *gen_secret* module is enabled `NEARBY_PLATFORM_USE_MBEDTLS` is set and `NEARBY_PLATFORM_HAS_SE` is *not* set. When `NEARBY_PLATFORM_HAS_SE` is set, the platform needs to provide their own `nearby_platform_GenSec256r1Secret()` routine. diff --git a/embedded/client/source/nearby_fp_client.c b/embedded/client/source/nearby_fp_client.c index 5965754c5b..0ee80dbb36 100644 --- a/embedded/client/source/nearby_fp_client.c +++ b/embedded/client/source/nearby_fp_client.c @@ -17,11 +17,13 @@ #include #include "nearby.h" +#include "nearby_assert.h" +#include "nearby_event.h" #include "nearby_fp_library.h" -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION #include "nearby_platform_battery.h" #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM #include "nearby_message_stream.h" #endif /* NEARBY_FP_MESSAGE_STREAM */ #include "nearby_assert.h" @@ -55,6 +57,7 @@ #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) @@ -76,9 +79,28 @@ // 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, @@ -93,14 +115,18 @@ 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 uint8_t account_key[ACCOUNT_KEY_SIZE_BYTES]; -static uint8_t pending_account_key[ACCOUNT_KEY_SIZE_BYTES]; +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; -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#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 { \ @@ -111,7 +137,7 @@ static uint8_t additional_data_id; #define BIT(b) (1 << b) #define ISSET(v, b) (v & BIT(b)) -#ifdef NEARBY_FP_MESSAGE_STREAM +#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); @@ -119,6 +145,10 @@ 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; @@ -130,6 +160,9 @@ 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 @@ -139,10 +172,13 @@ static void InitRfcommInput(uint64_t peer_address, rfcomm_input* input) { } 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 */ -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING typedef struct { uint64_t peer_public_address; uint64_t peer_le_address; @@ -238,13 +274,52 @@ static bool ShowPairingIndicator() { return advertisement_mode & NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR; } -static bool ShowBatteryIndicator() { - return advertisement_mode & NEARBY_FP_ADVERTISEMENT_BATTERY_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; } -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION -static bool IncludeBatteryInfo() { - return advertisement_mode & NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO; +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() { @@ -254,6 +329,15 @@ static bool IsInPairingMode() { 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) { @@ -271,7 +355,7 @@ static nearby_platform_BatteryInfo* PrepareBatteryInfo( return battery_info; } -#ifdef NEARBY_FP_MESSAGE_STREAM +#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 = { @@ -314,18 +398,21 @@ static void AccountKeyRejected() { } } -static void DiscardAccountKey() { memset(account_key, 0, sizeof(account_key)); } +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[0] == ACCOUNT_KEY_WRITE_MESSAGE_TYPE; + return pending_account_key_info.account_key[0] == + ACCOUNT_KEY_WRITE_MESSAGE_TYPE; } static void DiscardPendingAccountKey() { - memset(pending_account_key, 0, sizeof(pending_account_key)); + memset(&pending_account_key_info, 0, sizeof(pending_account_key_info)); } static void RotateBleAddress() { @@ -333,7 +420,8 @@ static void RotateBleAddress() { 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: 0x%lx", address); + NEARBY_TRACE(INFO, "Rotated BLE address to: %s", + nearby_utils_MacToString(address)); #else unsigned i; uint64_t address = 0; @@ -342,20 +430,22 @@ static void RotateBleAddress() { } address |= (uint64_t)1 << 46; address &= ~((uint64_t)1 << 47); - NEARBY_TRACE(WARNING, "Rotate address to: 0x%lx", address); + NEARBY_TRACE(WARNING, "Rotate address to: %s", + nearby_utils_MacToString(address)); nearby_platform_SetBleAddress(address); -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM SendBleAddressUpdatedToAll(); #endif #endif /* NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION */ } static nearby_platform_status SendKeyBasedPairingResponse( - uint64_t peer_address) { + 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); - status = nearby_platform_Aes128Encrypt(raw, encrypted, account_key); + 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; @@ -368,7 +458,7 @@ static nearby_platform_status SendKeyBasedPairingResponse( return status; } -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#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]; @@ -381,10 +471,13 @@ static nearby_platform_status NotifyPersonalizedName(uint64_t peer_address) { status); return status; } - if (!length) NEARBY_TRACE(WARNING, "Empty personalized name"); + if (!length) { + NEARBY_TRACE(WARNING, "Empty personalized name"); + } length += ADDITIONAL_DATA_HEADER_SIZE; - status = nearby_fp_EncodeAdditionalData(data, length, account_key); + 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); @@ -395,6 +488,8 @@ static nearby_platform_status NotifyPersonalizedName(uint64_t peer_address) { 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; } @@ -417,22 +512,21 @@ static nearby_platform_status OnAdditionalDataWrite(uint64_t peer_address, const uint8_t* request, size_t length) { NEARBY_TRACE(VERBOSE, "OnAdditionalDataWrite"); - nearby_platform_status status; if (pairing_state != kPairingStateWaitingForAdditionalData) { NEARBY_TRACE(WARNING, "Not expecting a write to Additional Data. Current state: %d", pairing_state); return kNearbyStatusError; } - status = - nearby_fp_DecodeAdditionalData((uint8_t*)request, length, account_key); + 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 kNearbyStatusUnsupported; + return status; } #endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ @@ -441,18 +535,19 @@ static nearby_platform_status HandleKeyBasedPairingRequest( NEARBY_TRACE(VERBOSE, "HandleKeyBasedPairingRequest"); int flags; flags = request[1]; -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#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) + if (flags & KBPR_INITIATE_PAIRING_MASK) { NEARBY_TRACE(WARNING, "received flag Initiate pairing in retroactive pairing"); + } -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING uint64_t peer_public_address = nearby_utils_GetBigEndian48(request + KBPR_SEEKER_ADDRESS_OFFSET); @@ -480,7 +575,8 @@ static nearby_platform_status HandleKeyBasedPairingRequest( if (flags & KBPR_INITIATE_PAIRING_MASK) { peer_public_address = nearby_utils_GetBigEndian48(request + KBPR_SEEKER_ADDRESS_OFFSET); - NEARBY_TRACE(INFO, "Send pairing request to 0x%llx", peer_public_address); + NEARBY_TRACE(INFO, "Send pairing request to %s", + nearby_utils_MacToString(peer_public_address)); nearby_platform_SendPairingRequest(peer_public_address); pairing_state = kPairingStateWaitingForPasskey; } else { @@ -499,7 +595,7 @@ static nearby_platform_status HandleActionRequest( NEARBY_TRACE(VERBOSE, "Device action not implemented"); return kNearbyStatusUnimplemented; } -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#if NEARBY_FP_ENABLE_ADDITIONAL_DATA if (flags & ACTION_REQUEST_WILL_WRITE_DATA_CHARACTERISTIC_MASK) { pairing_state = kPairingStateWaitingForAdditionalData; additional_data_id = request[10]; @@ -545,6 +641,9 @@ static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address, 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); @@ -556,24 +655,23 @@ static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address, decrypted_request + REQUEST_BT_ADDRESS_OFFSET) && !BtAddressMatch(public_address, decrypted_request + REQUEST_BT_ADDRESS_OFFSET)) { - NEARBY_TRACE(INFO, "Invalid incoming BT address %02x%02x%02x%02x%02x%02x", - decrypted_request[REQUEST_BT_ADDRESS_OFFSET], - decrypted_request[REQUEST_BT_ADDRESS_OFFSET + 1], - decrypted_request[REQUEST_BT_ADDRESS_OFFSET + 2], - decrypted_request[REQUEST_BT_ADDRESS_OFFSET + 3], - decrypted_request[REQUEST_BT_ADDRESS_OFFSET + 4], - decrypted_request[REQUEST_BT_ADDRESS_OFFSET + 5]); + NEARBY_TRACE(INFO, "Invalid incoming BT address %s", + nearby_utils_ArrayToString(decrypted_request, 6)); AccountKeyRejected(); return kNearbyStatusOK; } - memcpy(account_key, key, ACCOUNT_KEY_SIZE_BYTES); + 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 uint8_t* key = nearby_fp_GetAccountKey(i); - status = nearby_platform_Aes128Decrypt(request, decrypted_request, key); + 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; @@ -583,8 +681,13 @@ static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address, BtAddressMatch(public_address, decrypted_request + REQUEST_BT_ADDRESS_OFFSET)) { NEARBY_TRACE(VERBOSE, "Matched key number: %d", i); - nearby_fp_CopyAccountKey(account_key, i); - nearby_fp_MarkAccountKeyAsActive(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; } @@ -595,8 +698,9 @@ static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address, return kNearbyStatusOK; } } else { - NEARBY_TRACE(WARNING, "Unexpected key based pairing request length %d", - length); + NEARBY_TRACE(WARNING, + "Unexpected key based pairing request length %d Request: %s", + length, nearby_utils_ArrayToString(request, length)); return kNearbyStatusError; } pairing_failure_count = 0; @@ -604,13 +708,16 @@ static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address, DiscardPendingAccountKey(); - status = SendKeyBasedPairingResponse(peer_address); + 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. + // 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) { @@ -632,8 +739,8 @@ static nearby_platform_status NotifyProviderPasskey(uint64_t peer_address) { 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); + 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; @@ -641,7 +748,11 @@ static nearby_platform_status NotifyProviderPasskey(uint64_t peer_address) { status = nearby_platform_GattNotify(peer_address, kPasskey, encrypted, sizeof(encrypted)); if (status != kNearbyStatusOK) { - NEARBY_TRACE(ERROR, "Failed to notify on passkey characteristic"); + 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; } @@ -659,7 +770,12 @@ static nearby_platform_status OnPasskeyWrite(uint64_t peer_address, return kNearbyStatusError; } if (gatt_peer_address != peer_address) { - NEARBY_TRACE(INFO, "Ignoring passkey write from unexpected client"); + 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)) { @@ -667,12 +783,13 @@ static nearby_platform_status OnPasskeyWrite(uint64_t peer_address, return kNearbyStatusTimeout; } if (length != AES_MESSAGE_SIZE_BYTES) { - NEARBY_TRACE(INFO, "Passkey: expected %d bytes, got %d", - AES_MESSAGE_SIZE_BYTES, length); + 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); + 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(); @@ -692,8 +809,9 @@ static nearby_platform_status OnPasskeyWrite(uint64_t peer_address, static nearby_platform_status SetNonDiscoverableAdvertisement() { uint8_t advertisement[NON_DISCOVERABLE_ADV_SIZE_BYTES]; + const uint8_t* in_use_key = SelectInUseKey(); size_t length; -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION nearby_platform_BatteryInfo battery_info; length = nearby_fp_CreateNondiscoverableAdvertisementWithBattery( advertisement, sizeof(advertisement), ShowPairingIndicator(), @@ -703,6 +821,30 @@ static nearby_platform_status SetNonDiscoverableAdvertisement() { 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()); @@ -712,17 +854,18 @@ static nearby_platform_status SetNonDiscoverableAdvertisement() { // Steps executed after successful pairing with a seeker and receiving the // account key. -static void RunPostPairingSteps(uint64_t peer_address, - const uint8_t* account_key) { +static void RunPostPairingSteps( + uint64_t peer_address, const nearby_platform_AccountKeyInfo* account_key) { nearby_fp_AddAccountKey(account_key); nearby_fp_SaveAccountKeys(); DiscardPendingAccountKey(); -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING if (RetroactivePairingPeerPending(peer_address)) { RemoveRetroactivePairingPeer(peer_address); if (RetroactivePairingPeerPending(peer_address)) { - NEARBY_TRACE(WARNING, "peer is still pending 0x%llx", peer_address); + NEARBY_TRACE(WARNING, "peer is still pending %s", + nearby_utils_MacToString(peer_address)); } if (pairing_state != kPairingStateIdle) { NEARBY_TRACE(WARNING, @@ -734,7 +877,7 @@ static void RunPostPairingSteps(uint64_t peer_address, #endif /* NEARBY_FP_RETROACTIVE_PAIRING */ pairing_state = kPairingStateIdle; -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#if NEARBY_FP_ENABLE_ADDITIONAL_DATA pairing_state = kPairingStateWaitingForAdditionalData; additional_data_id = PERSONALIZED_NAME_DATA_ID; #endif @@ -748,15 +891,18 @@ static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address, const uint8_t* request, size_t length) { NEARBY_TRACE(VERBOSE, "OnAccountKeyWrite"); - uint8_t decrypted_request[AES_MESSAGE_SIZE_BYTES]; + nearby_platform_AccountKeyInfo key_info; + uint8_t* decrypted_request = key_info.account_key; nearby_platform_status status; bool wait_until_paired = false; -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING if (RetroactivePairingPeerPending(peer_address)) { if (RetroactivePairingPeerTimeout(peer_address)) { NEARBY_TRACE(ERROR, - "Not expecting an retroactive pairing request. Timeout"); + "Not expecting an retroactive pairing request. Timeout. " + "Peer address: %s", + nearby_utils_MacToString(peer_address)); RemoveRetroactivePairingPeer(peer_address); return kNearbyStatusTimeout; } @@ -776,6 +922,12 @@ static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address, 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; } @@ -783,7 +935,7 @@ static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address, NEARBY_TRACE(INFO, "Not expecting an account key write. Timeout"); return kNearbyStatusTimeout; } -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING } #endif /* NEARBY_FP_RETROACTIVE_PAIRING */ if (length != AES_MESSAGE_SIZE_BYTES) { @@ -791,8 +943,8 @@ static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address, AES_MESSAGE_SIZE_BYTES, length); return kNearbyStatusError; } - status = - nearby_platform_Aes128Decrypt(request, decrypted_request, account_key); + 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; @@ -802,14 +954,18 @@ static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address, 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, decrypted_request, ACCOUNT_KEY_SIZE_BYTES); + 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, decrypted_request); + RunPostPairingSteps(peer_address, &key_info); return kNearbyStatusOK; } @@ -824,24 +980,56 @@ static nearby_platform_status OnGattWrite( case kAccountKey: return OnAccountKeyWrite(peer_address, request, length); case kAdditionalData: -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#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: @@ -853,7 +1041,8 @@ static nearby_platform_status OnGattRead( } static void OnPairingRequest(uint64_t peer_address) { - NEARBY_TRACE(VERBOSE, "Pairing request from 0x%lx", 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; @@ -866,10 +1055,10 @@ static void OnPairingRequest(uint64_t peer_address) { } static void OnPaired(uint64_t peer_address) { - NEARBY_TRACE(INFO, "Paired with 0x%lx", 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); + RunPostPairingSteps(peer_address, &pending_account_key_info); return; } peer_public_address = peer_address; @@ -877,7 +1066,7 @@ static void OnPaired(uint64_t peer_address) { pairing_state = kPairingStateWaitingForAccountKeyWrite; timeout_start_ms = nearby_platform_GetCurrentTimeMs(); } -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING else if (AddRetroactivePairingPeer(peer_address) == false) { NEARBY_TRACE(WARNING, "No more timer for retroactive pairing"); } @@ -885,12 +1074,13 @@ static void OnPaired(uint64_t peer_address) { } static void OnPairingFailed(uint64_t peer_address) { - NEARBY_TRACE(ERROR, "Pairing failed with 0x%lx", peer_address); + NEARBY_TRACE(ERROR, "Pairing failed with %s", + nearby_utils_MacToString(peer_address)); DiscardAccountKey(); DiscardPendingAccountKey(); } -#ifdef NEARBY_FP_MESSAGE_STREAM +#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++) { @@ -988,7 +1178,8 @@ static void OnMessageStreamConnected(uint64_t peer_address) { size_t length = sizeof(buffer); rfcomm_input* input; - NEARBY_TRACE(VERBOSE, "OnMessageStreamConnected 0x%lx", peer_address); + NEARBY_TRACE(VERBOSE, "OnMessageStreamConnected %s", + nearby_utils_MacToString(peer_address)); input = FindAvailableRfcommInput(peer_address); if (!input) { NEARBY_TRACE(WARNING, "Too many concurrent RFCOMM connections"); @@ -1003,24 +1194,41 @@ static void OnMessageStreamConnected(uint64_t peer_address) { status = nearby_message_stream_Send(peer_address, &message); if (kNearbyStatusOK != status) { - NEARBY_TRACE(ERROR, "Failed to send model id, status: %d", 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", 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; } -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION status = SendBatteryInfoMessage(peer_address); if (kNearbyStatusOK != status) { - NEARBY_TRACE(ERROR, "Failed to send battery info, status: %d", 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", 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 */ @@ -1038,12 +1246,17 @@ static void OnMessageStreamConnected(uint64_t peer_address) { static void OnMessageStreamDisconnected(uint64_t peer_address) { rfcomm_input* input; - NEARBY_TRACE(VERBOSE, "OnMessageStreamDisconnected 0x%lx", peer_address); + NEARBY_TRACE(VERBOSE, "OnMessageStreamDisconnected %s", + nearby_utils_MacToString(peer_address)); input = GetRfcommInput(peer_address); if (!input) { - NEARBY_TRACE(WARNING, "Unexpected disconnection from 0x%lx", peer_address); + 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) { @@ -1054,16 +1267,22 @@ static void OnMessageStreamDisconnected(uint64_t peer_address) { .payload = (uint8_t*)&payload}; client_callbacks->on_event(&event); } -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#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 0x%lx", peer_address); + NEARBY_TRACE(WARNING, "Unexpected RFCOMM message from %s", + nearby_utils_MacToString(peer_address)); return; } nearby_message_stream_Read(&input->state, message, length); @@ -1075,7 +1294,8 @@ static nearby_platform_status VerifyMessageLength( 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); + nearby_message_stream_SendNack(peer_address, message, + /* fail reason= */ 0); return kNearbyStatusInvalidInput; } return kNearbyStatusOK; @@ -1096,6 +1316,317 @@ static nearby_platform_status SendResponse( } } +#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)); @@ -1138,6 +1669,7 @@ static nearby_platform_status HandleGeneralMessage( return kNearbyStatusOK; } } + break; } case MESSAGE_GROUP_DEVICE_ACTION_EVENT: { reply.message_group = MESSAGE_GROUP_DEVICE_ACTION_EVENT; @@ -1158,6 +1690,7 @@ static nearby_platform_status HandleGeneralMessage( nearby_platform_Ring(command, timeout * 10)); } } + break; } } return kNearbyStatusOK; @@ -1168,9 +1701,21 @@ static void OnMessageReceived(uint64_t peer_address, nearby_platform_status status; status = HandleGeneralMessage(peer_address, message); if (kNearbyStatusOK != status) { - NEARBY_TRACE(WARNING, "Processing stream message failed with %d", 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, @@ -1209,7 +1754,7 @@ nearby_platform_status nearby_fp_client_SendNack( } #endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION static void OnBatteryChanged(void) { nearby_platform_status status; if (pairing_state != kPairingStateIdle && @@ -1236,18 +1781,26 @@ static void OnBatteryChanged(void) { } } -#ifdef NEARBY_FP_MESSAGE_STREAM +#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", - status); + 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", - status); + 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 */ @@ -1263,14 +1816,14 @@ static const nearby_platform_BtInterface kBtInterface = { .on_pairing_request = OnPairingRequest, .on_paired = OnPaired, .on_pairing_failed = OnPairingFailed, -#ifdef NEARBY_FP_MESSAGE_STREAM +#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 */ }; -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION static nearby_platform_BatteryInterface kBatteryInterface = { .on_battery_changed = OnBatteryChanged, }; @@ -1323,9 +1876,9 @@ static bool ShouldRotateBleAddress(int mode) { 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. + // 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(); } @@ -1371,7 +1924,8 @@ static void MaybeRotateBleAddress() { } static bool NeedsPeriodicAddressRotation() { - // FP spec says we should rotate BLE adress every ~15 minutes when advertising + // 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; } @@ -1391,6 +1945,7 @@ nearby_platform_status nearby_fp_client_SetAdvertisement(int mode) { 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; @@ -1409,6 +1964,7 @@ nearby_platform_status nearby_fp_client_GetSeekerInfo( } return kNearbyStatusOK; } +#endif /* NEARBY_FP_MESSAGE_STREAM */ nearby_platform_status nearby_fp_client_Init( const nearby_fp_client_Callbacks* callbacks) { @@ -1417,9 +1973,9 @@ nearby_platform_status nearby_fp_client_Init( client_callbacks = callbacks; pairing_state = kPairingStateIdle; pairing_failure_count = 0; -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM memset(rfcomm_inputs, 0, sizeof(rfcomm_inputs)); -#endif +#endif /* NEARBY_FP_MESSAGE_STREAM */ advertisement_mode = NEARBY_FP_ADVERTISEMENT_NONE; address_rotation_task = NULL; peer_public_address = 0; @@ -1440,14 +1996,20 @@ nearby_platform_status nearby_fp_client_Init( status = nearby_platform_PersistenceInit(); if (status != kNearbyStatusOK) return status; -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION status = nearby_platform_BatteryInit(&kBatteryInterface); if (status != kNearbyStatusOK) return status; -#endif +#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; diff --git a/embedded/client/source/nearby_fp_client.h b/embedded/client/source/nearby_fp_client.h index 65e17055b3..a51fd7b2fa 100644 --- a/embedded/client/source/nearby_fp_client.h +++ b/embedded/client/source/nearby_fp_client.h @@ -52,7 +52,7 @@ typedef struct { // Ask the Seeker to show pairing UI indication. This flag can be combined with // NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE #define NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR 0x04 -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION // Include battery and charging info in the advertisement. This flag can be // combined with NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE #define NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO 0x08 @@ -60,6 +60,11 @@ typedef struct { // NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO #define NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR 0x10 #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ +#ifdef NEARBY_FP_ENABLE_SASS +// Include SASS advertisement. This flag can be +// combined with NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE +#define NEARBY_FP_ADVERTISEMENT_SASS 0x20 +#endif /* NEARBY_FP_ENABLE_SASS */ // Sets Fast Pair advertisement type nearby_platform_status nearby_fp_client_SetAdvertisement(int mode); @@ -68,7 +73,7 @@ nearby_platform_status nearby_fp_client_SetAdvertisement(int mode); nearby_platform_status nearby_fp_client_Init( const nearby_fp_client_Callbacks* callbacks); -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM // Serializes and sends |message| over Message Stream nearby_platform_status nearby_fp_client_SendMessage( uint64_t peer_address, const nearby_message_stream_Message* message); diff --git a/embedded/client/tests/gLinux/audio.cc b/embedded/client/tests/gLinux/audio.cc index e77e71949b..947d5a0890 100644 --- a/embedded/client/tests/gLinux/audio.cc +++ b/embedded/client/tests/gLinux/audio.cc @@ -17,6 +17,172 @@ #include "nearby_fp_client.h" #include "nearby_platform_audio.h" +static const nearby_platform_AudioCallbacks* callbacks; + +static bool multipoint = false; +static uint8_t switching_preference_flags = 0; +static uint64_t switch_active_peer_address = 0; +static uint8_t switch_active_flags = 0; +static uint64_t switch_active_preferred_audio_source = 0; +static uint64_t switch_back_peer_address = 0; +static uint8_t switch_back_flags = 0; +static uint64_t notify_peer_address = 0; +static uint8_t notify_flags = 0; +static uint64_t drop_peer_address = 0; +static uint8_t drop_flags = 0; +static uint64_t active_peer_address = 0; + bool nearby_platform_GetEarbudRightStatus() { return false; } bool nearby_platform_GetEarbudLeftStatus() { return false; } + +unsigned int nearby_platform_GetAudioConnectionState() { + return NEARBY_PLATFORM_CONNECTION_STATE_A2DP_WITH_AVRCP; +} + +bool nearby_platform_OnHead() { return true; } + +// Returns true if the device can accept another audio connection without +// dropping any of the existing connections. +bool nearby_platform_CanAcceptConnection() { return false; } + +// When the device is in focus mode, connection switching is not allowed +bool nearby_platform_InFocusMode() { return false; } + +// Returns true if the current connection is auto-recconnected, meaning it is +// not connected by the user. For multi-point connections, returns true if any +// of the existing connections is auto-reconnected. +bool nearby_platform_AutoReconnected() { return false; } + +// Sets a bit in the |bitmap| for every connected peer. The bit stays cleared +// for bonded but not connected peers. The order change is acceptable if it is +// unavoidable, e.g. when users factory reset the headset or when the bonded +// device count reaches the upper limit. +// |length| is the |bitmap| length on input in bytes and used space on output. +// For example, if there are 5 bonded devices, then |length| should be set to 1. +void nearby_platform_GetConnectionBitmap(uint8_t* bitmap, size_t* length) { + if (*length > 0) { + bitmap[0] = 0x09; + *length = 1; + } +} + +bool nearby_platform_IsSassOn() { return true; } + +bool nearby_platform_IsMultipointConfigurable() { return true; } + +bool nearby_platform_IsMultipointOn() { return multipoint; } + +bool nearby_platform_IsOnHeadDetectionSupported() { return true; } + +bool nearby_platform_IsOnHeadDetectionEnabled() { return false; } + +nearby_platform_status nearby_platform_SetMultipoint(uint64_t peer_address, + bool enable) { + multipoint = enable; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SetSwitchingPreference(uint8_t flags) { + switching_preference_flags = flags; + return kNearbyStatusOK; +} + +// Gets switching preference flags +uint8_t nearby_platform_GetSwitchingPreference() { + return switching_preference_flags; +} + +nearby_platform_status nearby_platform_SwitchActiveAudioSource( + uint64_t peer_address, uint8_t flags, uint64_t preferred_audio_source) { + switch_active_peer_address = peer_address; + switch_active_flags = flags; + switch_active_preferred_audio_source = preferred_audio_source; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SwitchBackAudioSource( + uint64_t peer_address, uint8_t flags) { + switch_back_peer_address = peer_address; + switch_back_flags = flags; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_NotifySassInitiatedConnection( + uint64_t peer_address, uint8_t flags) { + notify_peer_address = peer_address; + notify_flags = flags; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SetDropConnectionTarget( + uint64_t peer_address, uint8_t flags) { + drop_peer_address = peer_address; + drop_flags = flags; + return kNearbyStatusOK; +} + +uint64_t nearby_platform_GetActiveAudioSource() { return active_peer_address; } + +void nearby_test_fakes_SetActiveAudioSource(uint64_t peer_address) { + active_peer_address = peer_address; +} + +// Initializes Audio module +nearby_platform_status nearby_platform_AudioInit( + const nearby_platform_AudioCallbacks* audio_interface) { + multipoint = false; + switching_preference_flags = 0; + switch_active_peer_address = 0; + switch_active_flags = 0; + switch_active_preferred_audio_source = 0; + switch_back_peer_address = 0; + switch_back_flags = 0; + notify_peer_address = 0; + notify_flags = 0; + drop_peer_address = 0; + drop_flags = 0; + + callbacks = audio_interface; + return kNearbyStatusOK; +} + +uint64_t nearby_test_fakes_GetSassSwitchActiveSourcePeerAddress() { + return switch_active_peer_address; +} + +uint8_t nearby_test_fakes_GetSassSwitchActiveSourceFlags() { + return switch_active_flags; +} + +uint64_t nearby_test_fakes_GetSassSwitchActiveSourcePreferredAudioSource() { + return switch_active_preferred_audio_source; +} + +uint64_t nearby_test_fakes_GetSassSwitchBackPeerAddress() { + return switch_back_peer_address; +} + +uint8_t nearby_test_fakes_GetSassSwitchBackFlags() { return switch_back_flags; } + +uint64_t nearby_test_fakes_GetSassNotifyInitiatedConnectionPeerAddress() { + return notify_peer_address; +} + +uint8_t nearby_test_fakes_GetSassNotifyInitiatedConnectionFlags() { + return notify_flags; +} + +uint64_t nearby_test_fakes_GetSassDropConnectionTargetPeerAddress() { + return drop_peer_address; +} + +uint8_t nearby_test_fakes_GetSassDropConnectionTargetFlags() { + return drop_flags; +} + +void nearby_test_fakes_SassMultipointSwitch(uint8_t reason, + uint64_t peer_address, + const char* name) { + callbacks->on_multipoint_switch_event(reason, peer_address, name); +} diff --git a/embedded/client/tests/gLinux/battery.cc b/embedded/client/tests/gLinux/battery.cc index e0725a824b..685c8cd91a 100644 --- a/embedded/client/tests/gLinux/battery.cc +++ b/embedded/client/tests/gLinux/battery.cc @@ -16,12 +16,13 @@ #include "nearby.h" #include "nearby_platform_battery.h" -static nearby_platform_BatteryInfo test_battery_info = { +constexpr nearby_platform_BatteryInfo kDefaultBatteryInfo = { .is_charging = true, .right_bud_battery_level = 80, .left_bud_battery_level = 85, .charging_case_battery_level = 90, .remaining_time_minutes = 100}; +static nearby_platform_BatteryInfo test_battery_info = kDefaultBatteryInfo; static nearby_platform_status get_battery_info_result = kNearbyStatusOK; static const nearby_platform_BatteryInterface* battery_interface; @@ -58,5 +59,7 @@ void nearby_test_fakes_SetGetBatteryInfoResult(nearby_platform_status status) { nearby_platform_status nearby_platform_BatteryInit( nearby_platform_BatteryInterface* callbacks) { battery_interface = callbacks; + test_battery_info = kDefaultBatteryInfo; + get_battery_info_result = kNearbyStatusOK; return kNearbyStatusOK; } diff --git a/embedded/client/tests/gLinux/ble.cc b/embedded/client/tests/gLinux/ble.cc index 4ed89d26c6..68e0eef6ed 100644 --- a/embedded/client/tests/gLinux/ble.cc +++ b/embedded/client/tests/gLinux/ble.cc @@ -30,6 +30,8 @@ static const nearby_platform_BleInterface* ble_interface; static std::map> notifications; static std::vector advertisement; static nearby_fp_AvertisementInterval interval; +constexpr int32_t kDefaultPsm = -1; +static int32_t psm = kDefaultPsm; std::map>& nearby_test_fakes_GetGattNotifications() { @@ -83,6 +85,8 @@ nearby_platform_status nearby_platform_SetAdvertisement( return kNearbyStatusOK; } +int32_t nearby_platform_GetMessageStreamPsm() { return psm; } + // Initializes BLE nearby_platform_status nearby_platform_BleInit( const nearby_platform_BleInterface* callbacks) { @@ -91,9 +95,12 @@ nearby_platform_status nearby_platform_BleInit( advertisement.clear(); interval = kDisabled; ble_address = kDefaultBleAddress; + psm = kDefaultPsm; return kNearbyStatusOK; } +void nearby_test_fakes_SetPsm(int32_t value) { psm = value; } + std::vector& nearby_test_fakes_GetAdvertisement() { return advertisement; } @@ -125,3 +132,9 @@ nearby_platform_status nearby_fp_fakes_ReceiveAdditionalData( return ble_interface->on_gatt_write(peer_address, kAdditionalData, request, length); } + +nearby_platform_status nearby_fp_fakes_GattReadMessageStreamPsm( + uint8_t* output, size_t* length) { + return ble_interface->on_gatt_read(peer_address, kMessageStreamPsm, output, + length); +} diff --git a/embedded/client/tests/gLinux/bt.cc b/embedded/client/tests/gLinux/bt.cc index 43c5d594b4..96a14a7560 100644 --- a/embedded/client/tests/gLinux/bt.cc +++ b/embedded/client/tests/gLinux/bt.cc @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include "nearby_platform_bt.h" @@ -20,13 +21,16 @@ static const uint32_t kFastPairId = 0x101112; static const int8_t kTxLevel = 33; static const uint64_t kPublicAddress = 0xA0A1A2A3A4A5; +// No secondary identity address by default. +static const uint64_t kSecondaryPublicAddress = 0; static const uint32_t kLocalPasskey = 123456; static uint32_t remote_passkey; static uint64_t remote_address; static uint64_t paired_peer_address; -static std::vector rfcomm_output; +static std::map> rfcomm_outputs; static std::vector device_name; static bool pairing_mode = false; +static uint64_t secondary_public_address = kSecondaryPublicAddress; static const nearby_platform_BtInterface* bt_interface; // Returns Fast Pair Model Id. @@ -37,6 +41,10 @@ int8_t nearby_platform_GetTxLevel() { return kTxLevel; } // Returns public BR/EDR address uint64_t nearby_platform_GetPublicAddress() { return kPublicAddress; } +uint64_t nearby_platform_GetSecondaryPublicAddress() { + return secondary_public_address; +} + // Returns passkey used during pairing uint32_t nearby_platfrom_GetPairingPassKey() { return kLocalPasskey; } @@ -71,6 +79,8 @@ nearby_platform_status nearby_platform_BtInit( remote_passkey = 0; paired_peer_address = 0; pairing_mode = false; + secondary_public_address = kSecondaryPublicAddress; + rfcomm_outputs.clear(); return kNearbyStatusOK; } @@ -85,6 +95,10 @@ void nearby_test_fakes_SimulatePairing(uint64_t peer_address) { } } +void nearby_test_fakes_SetSecondaryPublicAddress(uint64_t address) { + secondary_public_address = address; +} + uint64_t nearby_test_fakes_GetPairedDevice() { return paired_peer_address; } void nearby_test_fakes_DevicePaired(uint64_t peer_address) { @@ -94,14 +108,20 @@ void nearby_test_fakes_DevicePaired(uint64_t peer_address) { nearby_platform_status nearby_platform_SendMessageStream(uint64_t peer_address, const uint8_t* message, size_t length) { + rfcomm_outputs.emplace(peer_address, std::vector()); + auto rfcomm_output = rfcomm_outputs.find(peer_address); for (int i = 0; i < length; i++) { - rfcomm_output.push_back(message[i]); + rfcomm_output->second.push_back(message[i]); } return kNearbyStatusOK; } +std::vector& nearby_test_fakes_GetRfcommOutput(uint64_t peer_address) { + return rfcomm_outputs[peer_address]; +} + std::vector& nearby_test_fakes_GetRfcommOutput() { - return rfcomm_output; + return nearby_test_fakes_GetRfcommOutput(paired_peer_address); } nearby_platform_status nearby_platform_SetDeviceName(const char* name) { @@ -128,7 +148,7 @@ void nearby_test_fakes_SetInPairingMode(bool in_pairing_mode) { pairing_mode = in_pairing_mode; } -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM void nearby_test_fakes_MessageStreamConnected(uint64_t peer_address) { bt_interface->on_message_stream_connected(peer_address); } diff --git a/embedded/client/tests/gLinux/fakes.h b/embedded/client/tests/gLinux/fakes.h index abbeeb26ac..3d1a15b007 100644 --- a/embedded/client/tests/gLinux/fakes.h +++ b/embedded/client/tests/gLinux/fakes.h @@ -25,14 +25,19 @@ void nearby_test_fakes_SetRandomNumber(unsigned int value); void nearby_test_fakes_SetRandomNumberSequence(std::vector& value); -void nearby_test_fakes_SetAccountKeys(const uint8_t* input, size_t length); std::vector nearby_test_fakes_GetRawAccountKeys(); nearby_platform_status nearby_test_fakes_GattReadModelId(uint8_t* output, size_t* length); +#ifdef MBEDTLS_FOR_SSL +extern "C" { +#endif nearby_platform_status nearby_test_fakes_SetAntiSpoofingKey( const uint8_t private_key[32], const uint8_t public_key[64]); +#ifdef MBEDTLS_FOR_SSL +} +#endif nearby_platform_status nearby_test_fakes_GenSec256r1Secret( const uint8_t remote_party_public_key[64], uint8_t secret[32]); @@ -60,41 +65,79 @@ nearby_platform_status nearby_fp_fakes_ReceiveAccountKeyWrite( const uint8_t* request, size_t length); nearby_platform_status nearby_fp_fakes_ReceiveAdditionalData( const uint8_t* request, size_t length); +nearby_platform_status nearby_fp_fakes_GattReadMessageStreamPsm(uint8_t* output, + size_t* length); uint64_t nearby_test_fakes_GetPairingRequestAddress(); uint32_t nearby_test_fakes_GetRemotePasskey(); uint64_t nearby_test_fakes_GetPairedDevice(); void nearby_test_fakes_DevicePaired(uint64_t peer_address); +class AccountKeyPair { + public: + AccountKeyPair(uint64_t bt_address, const std::vector& account_key) + : address_(bt_address), account_key_(account_key) {} + AccountKeyPair(uint64_t bt_address, const uint8_t* account_key) + : address_(bt_address), + account_key_(account_key, account_key + ACCOUNT_KEY_SIZE_BYTES) {} + + uint64_t address_; + std::vector account_key_; +}; + +struct RawAccountKeyList { + uint8_t num_keys; + nearby_platform_AccountKeyInfo key[NEARBY_MAX_ACCOUNT_KEYS]; +}; + class AccountKeyList { public: explicit AccountKeyList(const std::vector& raw_values) { - if (raw_values.size() == 0) return; - int key_count = raw_values[0]; - for (int i = 0; i < key_count; i++) { - const uint8_t* p = raw_values.data() + 1 + (i * ACCOUNT_KEY_SIZE_BYTES); - keys_.emplace_back(std::vector(p, p + ACCOUNT_KEY_SIZE_BYTES)); + const RawAccountKeyList* list = + reinterpret_cast(raw_values.data()); + if (!list) return; + + for (size_t i = 0; i < list->num_keys; i++) { + uint64_t address = 0; +#ifdef NEARBY_FP_ENABLE_SASS + address = list->key[i].peer_address; +#endif /* NEARBY_FP_ENABLE_SASS */ + key_pairs_.emplace_back( + address, std::vector( + list->key[i].account_key, + list->key[i].account_key + ACCOUNT_KEY_SIZE_BYTES)); } } + explicit AccountKeyList(const std::vector& key_pairs) + : key_pairs_(key_pairs) {} - size_t size() { return keys_.size(); } + size_t size() { return key_pairs_.size(); } - std::vector>& GetKeys() { return keys_; } + std::vector GetKey(int index) { + return key_pairs_[index].account_key_; + } std::vector GetRawFormat() { - std::vector result; - result.push_back(size()); - for (auto& key : keys_) { - for (auto& v : key) { - result.push_back(v); + RawAccountKeyList account_keys; + account_keys.num_keys = size(); + for (size_t i = 0; i < size(); i++) { +#ifdef NEARBY_FP_ENABLE_SASS + account_keys.key[i].peer_address = key_pairs_[i].address_; +#endif /* NEARBY_FP_ENABLE_SASS */ + for (size_t j = 0; j < key_pairs_[i].account_key_.size(); j++) { + account_keys.key[i].account_key[j] = key_pairs_[i].account_key_[j]; } } - return result; + uint8_t* p = reinterpret_cast(&account_keys); + return std::vector(p, p + sizeof(account_keys)); } private: - std::vector> keys_; + std::vector key_pairs_; }; +void nearby_test_fakes_SetAccountKeys(const uint8_t* input, size_t length); +void nearby_test_fakes_SetAccountKeys( + std::vector& account_key_pairs); void nearby_test_fakes_SetAccountKeys(AccountKeyList& keys); AccountKeyList nearby_test_fakes_GetAccountKeys(); @@ -112,6 +155,7 @@ void nearby_test_fakes_BatteryTime(uint16_t battery_time); void nearby_test_fakes_SetGetBatteryInfoResult(nearby_platform_status status); std::vector& nearby_test_fakes_GetRfcommOutput(); +std::vector& nearby_test_fakes_GetRfcommOutput(uint64_t peer_address); void nearby_test_fakes_MessageStreamConnected(uint64_t peer_address); @@ -130,5 +174,25 @@ void nearby_test_fakes_SetInPairingMode(bool in_pairing_mode); uint8_t nearby_test_fakes_GetRingCommand(void); uint16_t nearby_test_fakes_GetRingTimeout(void); +uint64_t nearby_test_fakes_GetSassSwitchActiveSourcePeerAddress(); +uint8_t nearby_test_fakes_GetSassSwitchActiveSourceFlags(); +uint64_t nearby_test_fakes_GetSassSwitchActiveSourcePreferredAudioSource(); +uint64_t nearby_test_fakes_GetSassSwitchBackPeerAddress(); +uint8_t nearby_test_fakes_GetSassSwitchBackFlags(); +uint64_t nearby_test_fakes_GetSassNotifyInitiatedConnectionPeerAddress(); +uint8_t nearby_test_fakes_GetSassNotifyInitiatedConnectionFlags(); +uint64_t nearby_test_fakes_GetSassDropConnectionTargetPeerAddress(); +uint8_t nearby_test_fakes_GetSassDropConnectionTargetFlags(); +void nearby_test_fakes_SassMultipointSwitch(uint8_t reason, + uint64_t peer_address, + const char* name); + +#ifdef NEARBY_FP_ENABLE_SASS +void nearby_test_fakes_SetActiveAudioSource(uint64_t peer_address); +#endif /* NEARBY_FP_ENABLE_SASS */ + +void nearby_test_fakes_SetPsm(int32_t value); + +void nearby_test_fakes_SetSecondaryPublicAddress(uint64_t address); #endif /* NEARBY_TEST_FAKES_H */ diff --git a/embedded/client/tests/gLinux/persistence.cc b/embedded/client/tests/gLinux/persistence.cc index dfb438ca8b..6dccc62cd8 100644 --- a/embedded/client/tests/gLinux/persistence.cc +++ b/embedded/client/tests/gLinux/persistence.cc @@ -51,6 +51,12 @@ nearby_platform_status nearby_platform_PersistenceInit() { return kNearbyStatusOK; } +void nearby_test_fakes_SetAccountKeys( + std::vector& account_key_pairs) { + AccountKeyList list(account_key_pairs); + nearby_test_fakes_SetAccountKeys(list); +} + void nearby_test_fakes_SetAccountKeys(const uint8_t* input, size_t length) { storage[kStoredKeyAccountKeyList].assign(input, input + length); } diff --git a/embedded/client/tests/gLinux/se.cc b/embedded/client/tests/gLinux/se.cc index 9039a06069..a10d66b0a9 100644 --- a/embedded/client/tests/gLinux/se.cc +++ b/embedded/client/tests/gLinux/se.cc @@ -28,9 +28,13 @@ #include "fakes.h" #include "nearby_platform_se.h" +#pragma GCC diagnostic ignored "-Wunused-function" + static unsigned int random_value = 0; static std::queue random_sequence; +static uint8_t private_key_store[32]; + static std::unique_ptr anti_spoofing_key( NULL, EVP_PKEY_free); @@ -54,7 +58,7 @@ uint8_t nearby_platform_Rand() { } } -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#ifndef NEARBY_PLATFORM_USE_MBEDTLS static SHA256_CTX sha256_context; nearby_platform_status nearby_platform_Sha256Start() { @@ -72,7 +76,6 @@ nearby_platform_status nearby_platform_Sha256Finish(uint8_t out[32]) { SHA256_Final(out, &sha256_context); return kNearbyStatusOK; } -#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ // Encrypts a data block with AES128 in ECB mode. nearby_platform_status nearby_platform_Aes128Encrypt(const uint8_t input[16], @@ -102,6 +105,7 @@ nearby_platform_status nearby_platform_Aes128Decrypt(const uint8_t input[16], int output_length = 16; EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, NULL); + EVP_CIPHER_CTX_set_padding(ctx, 0); if (1 != EVP_DecryptUpdate(ctx, output, &output_length, input, input_length)) { @@ -111,6 +115,7 @@ nearby_platform_status nearby_platform_Aes128Decrypt(const uint8_t input[16], EVP_CIPHER_CTX_free(ctx); return kNearbyStatusOK; } +#endif /* NEARBY_PLATFORM_USE_MBEDTLS */ static EC_POINT *load_public_key(const uint8_t public_key[64]) { BN_CTX *bn_ctx; @@ -132,6 +137,16 @@ static EC_POINT *load_public_key(const uint8_t public_key[64]) { return point; } +static BIGNUM *load_private_key(const uint8_t private_key[32]) { + uint8_t buffer[37]; + buffer[0] = buffer[1] = buffer[2] = 0; + buffer[3] = 33; + buffer[4] = 0; + memcpy(buffer + 5, private_key, 32); + return BN_mpi2bn(buffer, sizeof(buffer), NULL); +} + +#ifdef NEARBY_PLATFORM_HAS_SE // Generates a shared sec256p1 secret using remote party public key and this // device's private key. nearby_platform_status nearby_platform_GenSec256r1Secret( @@ -177,16 +192,9 @@ nearby_platform_status nearby_platform_GenSec256r1Secret( EC_POINT_free(peer_point); EVP_PKEY_free(peerkey); - std::cout << "Secret: " << ArrayToString(secret, 32) << std::endl; - return kNearbyStatusOK; -} - -// Initializes secure element module -nearby_platform_status nearby_platform_SecureElementInit() { - random_value = 0; - random_sequence = std::queue(); return kNearbyStatusOK; } +#endif /* NEARBY_PLATFORM_HAS_SE */ void nearby_test_fakes_SetRandomNumber(unsigned int value) { random_value = value; @@ -196,15 +204,6 @@ void nearby_test_fakes_SetRandomNumberSequence(std::vector &value) { for (auto &v : value) random_sequence.push(v); } -static BIGNUM *load_private_key(const uint8_t private_key[32]) { - uint8_t buffer[37]; - buffer[0] = buffer[1] = buffer[2] = 0; - buffer[3] = 33; - buffer[4] = 0; - memcpy(buffer + 5, private_key, 32); - return BN_mpi2bn(buffer, sizeof(buffer), NULL); -} - nearby_platform_status nearby_test_fakes_SetAntiSpoofingKey( const uint8_t private_key[32], const uint8_t public_key[64]) { EC_KEY *key; @@ -224,6 +223,9 @@ nearby_platform_status nearby_test_fakes_SetAntiSpoofingKey( anti_spoofing_key.reset(EVP_PKEY_new()); if (1 != EVP_PKEY_assign_EC_KEY(anti_spoofing_key.get(), key)) return kNearbyStatusError; + + memcpy(private_key_store, private_key, 32); + BN_free(prv); EC_POINT_free(pub); return kNearbyStatusOK; @@ -247,3 +249,14 @@ nearby_platform_status nearby_test_fakes_Aes128Encrypt( const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { return nearby_platform_Aes128Encrypt(input, output, key); } + +const uint8_t *nearby_platform_GetAntiSpoofingPrivateKey() { + return private_key_store; +} + +// Initializes secure element module +nearby_platform_status nearby_platform_SecureElementInit() { + random_value = 0; + random_sequence = std::queue(); + return kNearbyStatusOK; +} diff --git a/embedded/client/tests/message_stream_test.cc b/embedded/client/tests/message_stream_test.cc index e4604e28cc..2ad2691621 100644 --- a/embedded/client/tests/message_stream_test.cc +++ b/embedded/client/tests/message_stream_test.cc @@ -22,7 +22,7 @@ #include "nearby.h" #include "nearby_message_stream.h" -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM constexpr uint64_t kPeerAddress = 0x101112; constexpr size_t kBufferSize = 64; constexpr size_t kMaxPayloadSize = @@ -122,11 +122,11 @@ class MessageStreamTest : public ::testing::Test { .peer_address = kPeerAddress, .length = sizeof(buffer), .buffer = buffer}; -} * test_fixture; +}* test_fixture; void MessageStreamTest::SetUp() { test_fixture = this; - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_message_stream_Init(&stream_state_); } @@ -300,8 +300,9 @@ TEST_F(MessageStreamTest, SendMessageNoPayload) { Send(&message); - ASSERT_THAT(kExpectedOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } TEST_F(MessageStreamTest, SendMessageWithPayload) { @@ -317,8 +318,9 @@ TEST_F(MessageStreamTest, SendMessageWithPayload) { Send(&message); - ASSERT_THAT(kExpectedOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } TEST_F(MessageStreamTest, SendAck) { @@ -332,8 +334,9 @@ TEST_F(MessageStreamTest, SendAck) { SendAck(&message); - ASSERT_THAT(kExpectedOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } TEST_F(MessageStreamTest, SendNack) { @@ -348,8 +351,9 @@ TEST_F(MessageStreamTest, SendNack) { SendNack(&message, kFailReason); - ASSERT_THAT(kExpectedOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } #endif /* NEARBY_FP_MESSAGE_STREAM */ diff --git a/embedded/client/tests/smoke_test.cc b/embedded/client/tests/smoke_test.cc index 9f2ebf9ca2..7fccda7216 100644 --- a/embedded/client/tests/smoke_test.cc +++ b/embedded/client/tests/smoke_test.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include "fakes.h" #include "gmock/gmock.h" @@ -26,6 +27,7 @@ #include "nearby_event.h" #include "nearby_fp_client.h" #include "nearby_fp_library.h" +#include "nearby_platform_audio.h" #include "nearby_platform_ble.h" #include "nearby_platform_persistence.h" #include "nearby_utils.h" @@ -69,6 +71,7 @@ constexpr uint8_t kExpectedAesKey[16] = {0xB0, 0x7F, 0x1F, 0x17, 0xC2, 0x36, constexpr uint64_t kRemoteDevice = 0xB0B1B2B3B4B5; constexpr uint8_t kTxPower = 33; +constexpr uint8_t kSalt = 0xAB; constexpr uint8_t kDiscoverableAdvertisement[] = { 6, 0x16, 0x2C, 0xFE, 0x10, 0x11, 0x12, 2, 0x0A, kTxPower}; @@ -77,6 +80,9 @@ constexpr uint8_t kSeekerAccountKey[16] = {0x04, 20, 21, 22, 23, 24, 25, 26, constexpr uint8_t kSeekerAccountKey2[16] = {0x04, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64}; +constexpr bool kRegularBloomFormat = false; +constexpr bool kSassBloomFormat = true; +constexpr uint8_t* kNoInUseKey = NULL; using ::testing::ElementsAreArray; static std::string VecToString(std::vector data) { @@ -92,6 +98,26 @@ static std::string VecToString(std::vector data) { // return VecToString(std::vector(start, end)); // } +static void Set5AccountKeys() { + uint8_t account_key1[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t account_key2[] = {0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88}; + uint8_t account_key3[] = {0x03, 0x13, 0x23, 0x33, 0x43, 0x53, 0x63, 0x73, + 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3}; + uint8_t account_key4[] = {0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, + 0x84, 0x94, 0xA4, 0xB4, 0xC4, 0xD4, 0xE4, 0xF4}; + uint8_t account_key5[] = {0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, 0x75, + 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key1), + AccountKeyPair(kRemoteDevice, account_key2), + AccountKeyPair(kRemoteDevice, account_key3), + AccountKeyPair(kRemoteDevice, account_key4), + AccountKeyPair(kRemoteDevice, account_key5)}; + nearby_test_fakes_SetAccountKeys(account_keys); +} + class Event { public: explicit Event(nearby_event_Type type) : type_(type) {} @@ -262,6 +288,7 @@ static void WriteToAccountKey() { sizeof(encrypted_account_key_write_request)); } +#if NEARBY_FP_MESSAGE_STREAM int GetCapability(uint64_t peer_address) { nearby_fp_client_SeekerInfo seeker_infos[NEARBY_MAX_RFCOMM_CONNECTIONS]; size_t sl = NEARBY_MAX_RFCOMM_CONNECTIONS; @@ -274,13 +301,12 @@ int GetCapability(uint64_t peer_address) { } return -1; } +#endif /* NEARBY_FP_MESSAGE_STREAM */ // |flags| from Table 1.2.1: Raw Request (type 0x00) in FP specification static void Pair(uint8_t flags) { - uint8_t salt = 0xAB; - nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); - nearby_test_fakes_SetRandomNumber(salt); + nearby_test_fakes_SetRandomNumber(kSalt); nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); uint8_t request[16]; @@ -388,22 +414,61 @@ TEST(NearbyFpClient, AdvertisementNondiscoverable_noKeys) { } TEST(NearbyFpClient, AdvertisementNondiscoverable_oneKey) { - const int kBufferSize = 15; - uint8_t buffer[kBufferSize]; uint8_t salt = 0xC7; - uint8_t account_keys[] = {1, 0x11, 0x22, 0x33, 0x44, 0x55, - 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; - const uint8_t kExpectedResult[] = {11, 0x16, 0x2C, 0xFE, 0x00, - 0x42, 0x0A, 0x42, 0x88, 0x10, - 0x11, salt, 2, 0x0A, kTxPower}; + uint8_t account_key[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key)}; + // Expected result for salt size 1 + const uint8_t kExpectedResult1[] = {11, 0x16, 0x2C, 0xFE, 0x00, + 0x42, 0x0A, 0x42, 0x88, 0x10, + 0x11, salt, 2, 0x0A, kTxPower}; + // Expected result for salt size 2 + const uint8_t kExpectedResult2[] = {12, 0x16, 0x2C, 0xFE, 0x00, 0x42, + 0x08, 0x48, 0x0c, 0x11, 0x21, salt, + salt, 2, 0x0A, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); + uint8_t buffer[kBufferSize]; + nearby_fp_client_Init(NULL); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetAccountKeys(account_keys); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); size_t written = nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + nearby_fp_SetBloomFilter(buffer, kRegularBloomFormat, kNoInUseKey); + written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, + kTxPower); + + ASSERT_EQ(kBufferSize, written); + ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), + ElementsAreArray(kExpectedResult, kBufferSize)); +} + +#if (NEARBY_FP_SALT_SIZE == 2) +TEST(NearbyFpClient, AdvertisementNondiscoverable_oneKey_twoByteSalt) { + const int kBufferSize = 16; + uint8_t buffer[kBufferSize]; + std::vector salt = {0xC7, 0xC8}; + uint8_t account_key[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key)}; + const uint8_t kExpectedResult[] = { + 12, 0x16, 0x2C, 0xFE, 0x00, 0x42, 0x02, 0x0c, + 0x80, 0x2a, 0x21, salt[0], salt[1], 2, 0x0A, kTxPower}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys); + nearby_test_fakes_SetRandomNumberSequence(salt); + nearby_fp_LoadAccountKeys(); + + size_t written = + nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + nearby_fp_SetBloomFilter(buffer, kRegularBloomFormat, kNoInUseKey); written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, kTxPower); @@ -411,31 +476,86 @@ TEST(NearbyFpClient, AdvertisementNondiscoverable_oneKey) { ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), ElementsAreArray(kExpectedResult)); } +#endif TEST(NearbyFpClient, AdvertisementNondiscoverable_twoKeys) { - const int kBufferSize = 16; + uint8_t salt = 0xC7; + uint8_t account_key1[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t account_key2[] = {0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key1), + AccountKeyPair(kRemoteDevice, account_key2)}; + const uint8_t kExpectedResult1[] = {12, 0x16, 0x2C, 0xFE, 0x00, 0x52, + 0x2f, 0xba, 0x06, 0x42, 0x00, 0x11, + salt, 2, 0x0A, kTxPower}; + const uint8_t kExpectedResult2[] = {13, 0x16, 0x2C, 0xFE, 0x00, 0x52, + 0x4d, 0x08, 0x00, 0x5d, 0x1c, 0x21, + salt, salt, 2, 0x0A, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); uint8_t buffer[kBufferSize]; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + + size_t written = + nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + nearby_fp_SetBloomFilter(buffer, kRegularBloomFormat, kNoInUseKey); + written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, + kTxPower); + + ASSERT_EQ(kBufferSize, written); + ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), + ElementsAreArray(kExpectedResult, kBufferSize)); +} + +TEST(NearbyFpClient, + AdvertisementNondiscoverable_duplicateKeys_ignoreDuplicates) { uint8_t salt = 0xC7; - uint8_t account_keys[] = { - 2, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, - 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, - 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88}; - const uint8_t kExpectedResult[] = {12, 0x16, 0x2C, 0xFE, 0x00, 0x52, - 0x2F, 0xBA, 0x06, 0x42, 0x00, 0x11, - salt, 2, 0x0A, kTxPower}; + constexpr uint64_t kOtherAddress = 0x505050505050; + uint8_t account_key1[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t account_key2[] = {0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key1), + AccountKeyPair(kRemoteDevice, account_key2), + AccountKeyPair(kOtherAddress, account_key2), + AccountKeyPair(kOtherAddress, account_key1), + }; + const uint8_t kExpectedResult1[] = {12, 0x16, 0x2C, 0xFE, 0x00, 0x52, + 0x2F, 0xBA, 0x06, 0x42, 0x00, 0x11, + salt, 2, 0x0A, kTxPower}; + const uint8_t kExpectedResult2[] = {13, 0x16, 0x2C, 0xFE, 0x00, 0x52, + 0x4D, 0x08, 0x00, 0x5D, 0x1C, 0x21, + salt, salt, 2, 0x0A, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); + uint8_t buffer[kBufferSize]; nearby_fp_client_Init(NULL); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetAccountKeys(account_keys); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); size_t written = nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + nearby_fp_SetBloomFilter(buffer, kRegularBloomFormat, kNoInUseKey); written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, kTxPower); ASSERT_EQ(kBufferSize, written); + std::cout << "Buffer: " + << VecToString(std::vector(buffer, buffer + kBufferSize)) + << std::endl; ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), - ElementsAreArray(kExpectedResult)); + ElementsAreArray(kExpectedResult, kBufferSize)); } TEST(NearbyFpClient, GattReadModelId) { @@ -517,25 +637,26 @@ TEST(NearbyFpClient, CreateSharedSecret_bobAlice) { } } -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION TEST(NearbyFpClient, SetAdvertisementWithBatteryNotification_AdvertisementWithPairingUI) { + // When the salt size is 2, the salt byte is repeated twice in this test. uint8_t salt = 0xC7; - uint8_t account_keys[] = { - 5, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, - 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88, 0x03, 0x13, 0x23, - 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, - 0xF3, 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, 0x84, 0x94, 0xA4, - 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, - 0x75, 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, - }; - const uint8_t kExpectedResult[] = { + // Expected result with salt size 1 + const uint8_t kExpectedResult1[] = { 0x10, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x03, 0x78, 0x95, 0x67, 0x0c, 0xc3, 0x0a, 0xcc, 0x56, 0x11, salt, 0x02, 0x0a, kTxPower}; + // Expected result with salt size 2 + const uint8_t kExpectedResult2[] = { + 0x11, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x04, 0xac, 0x49, 0x24, 0x23, + 0x4b, 0xd1, 0x6d, 0x9f, 0x21, salt, salt, 0x02, 0x0a, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); nearby_fp_client_Init(NULL); nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + Set5AccountKeys(); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); @@ -544,29 +665,28 @@ TEST(NearbyFpClient, NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR)); ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), - ElementsAreArray(kExpectedResult)); + ElementsAreArray(kExpectedResult, kBufferSize)); } TEST( NearbyFpClient, SetAdvertisementWithBatteryNotification_AdvertisementNoPairingUIWithBatteryUI) { uint8_t salt = 0xC7; - uint8_t account_keys[] = { - 5, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, - 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88, 0x03, 0x13, 0x23, - 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, - 0xF3, 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, 0x84, 0x94, 0xA4, - 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, - 0x75, 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, - }; - const uint8_t kExpectedResult[] = {0x14, 0x16, 0x2c, 0xfe, 0x00, 0x92, - 0x30, 0xa6, 0x17, 0x10, 0x0c, 0x6c, - 0xa9, 0xea, 0xf7, 0x11, salt, 0x33, - 0xd5, 0xd0, 0xda, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult1[] = {0x14, 0x16, 0x2c, 0xfe, 0x00, 0x92, + 0x30, 0xa6, 0x17, 0x10, 0x0c, 0x6c, + 0xa9, 0xea, 0xf7, 0x11, salt, 0x33, + 0xd5, 0xd0, 0xda, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult2[] = { + 0x15, 0x16, 0x2c, 0xfe, 0x00, 0x92, 0x9c, 0x84, 0x20, + 0x0b, 0xb1, 0xd7, 0x37, 0x42, 0x93, 0x21, salt, salt, + 0x33, 0xd5, 0xd0, 0xda, 2, 0x0a, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); nearby_fp_client_Init(NULL); nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + Set5AccountKeys(); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); @@ -576,29 +696,28 @@ TEST( NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), - ElementsAreArray(kExpectedResult)); + ElementsAreArray(kExpectedResult, kBufferSize)); } TEST( NearbyFpClient, SetAdvertisementWithBatteryNotification_Charging_AdvertisementContainsBatteryInfo) { uint8_t salt = 0xC7; - uint8_t account_keys[] = { - 5, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, - 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88, 0x03, 0x13, 0x23, - 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, - 0xF3, 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, 0x84, 0x94, 0xA4, - 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, - 0x75, 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, - }; - const uint8_t kExpectedResult[] = {0x14, 0x16, 0x2c, 0xfe, 0x00, 0x90, - 0x30, 0xa6, 0x17, 0x10, 0x0c, 0x6c, - 0xa9, 0xea, 0xf7, 0x11, salt, 0x33, - 0xd5, 0xd0, 0xda, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult1[] = {0x14, 0x16, 0x2c, 0xfe, 0x00, 0x90, + 0x30, 0xa6, 0x17, 0x10, 0x0c, 0x6c, + 0xa9, 0xea, 0xf7, 0x11, salt, 0x33, + 0xd5, 0xd0, 0xda, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult2[] = { + 0x15, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x9c, 0x84, 0x20, + 0x0b, 0xb1, 0xd7, 0x37, 0x42, 0x93, 0x21, salt, salt, + 0x33, 0xd5, 0xd0, 0xda, 2, 0x0a, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); nearby_fp_client_Init(NULL); nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + Set5AccountKeys(); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); nearby_test_fakes_SetIsCharging(true); @@ -610,29 +729,28 @@ TEST( NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), - ElementsAreArray(kExpectedResult)); + ElementsAreArray(kExpectedResult, kBufferSize)); } TEST( NearbyFpClient, SetAdvertisementWithBatteryNotification_NotCharging_AdvertisementContainsBatteryInfo) { uint8_t salt = 0xC7; - uint8_t account_keys[] = { - 5, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, - 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88, 0x03, 0x13, 0x23, - 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, - 0xF3, 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, 0x84, 0x94, 0xA4, - 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, - 0x75, 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, - }; - const uint8_t kExpectedResult[] = {0x14, 0x16, 0x2c, 0xfe, 0x00, 0x90, - 0x46, 0x84, 0x1e, 0x84, 0x2e, 0x27, - 0x05, 0x92, 0xcc, 0x11, salt, 0x33, - 0x55, 0x50, 0x5a, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult1[] = {0x14, 0x16, 0x2c, 0xfe, 0x00, 0x90, + 0x46, 0x84, 0x1e, 0x84, 0x2e, 0x27, + 0x05, 0x92, 0xcc, 0x11, salt, 0x33, + 0x55, 0x50, 0x5a, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult2[] = { + 0x15, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x59, 0x0d, 0x74, + 0xb3, 0xa3, 0x54, 0xe9, 0x28, 0x00, 0x21, salt, salt, + 0x33, 0x55, 0x50, 0x5a, 2, 0x0a, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); nearby_fp_client_Init(NULL); nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + Set5AccountKeys(); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); nearby_test_fakes_SetIsCharging(false); @@ -644,28 +762,26 @@ TEST( NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), - ElementsAreArray(kExpectedResult)); + ElementsAreArray(kExpectedResult, kBufferSize)); } TEST( NearbyFpClient, SetAdvertisementWithBatteryNotification_GetBatteryInfoFails_AdvertismentIsValid) { uint8_t salt = 0xC7; - uint8_t account_keys[] = { - 5, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, - 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88, 0x03, 0x13, 0x23, - 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, - 0xF3, 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, 0x84, 0x94, 0xA4, - 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, - 0x75, 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, - }; - const uint8_t kExpectedResult[] = { + const uint8_t kExpectedResult1[] = { 0x10, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x03, 0x78, 0x95, 0x67, 0x0c, 0xc3, 0x0a, 0xcc, 0x56, 0x11, salt, 2, 0x0a, kTxPower}; + const uint8_t kExpectedResult2[] = { + 0x11, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x04, 0xac, 0x49, 0x24, 0x23, + 0x4b, 0xd1, 0x6d, 0x9f, 0x21, salt, salt, 2, 0x0a, kTxPower}; + const uint8_t* kExpectedResult = + (NEARBY_FP_SALT_SIZE == 1) ? kExpectedResult1 : kExpectedResult2; + const int kBufferSize = (NEARBY_FP_SALT_SIZE == 1) ? sizeof(kExpectedResult1) + : sizeof(kExpectedResult2); nearby_fp_client_Init(NULL); nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + Set5AccountKeys(); nearby_test_fakes_SetRandomNumber(salt); nearby_fp_LoadAccountKeys(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); @@ -677,27 +793,29 @@ TEST( NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), - ElementsAreArray(kExpectedResult)); + ElementsAreArray(kExpectedResult, kBufferSize)); } -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, RfcommConnected_HasBatteryInfo_SendsModelIdBleAddressAndBatteryInfo) { constexpr uint64_t kPeerAddress = 0x123456; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab, - // Battery level - 3, 3, 0, 3, 0xd5, 0xd0, 0xda, - // Battery remaining time - 3, 4, 0, 1, 100}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time + 3, 4, 0, 1, 100}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); nearby_test_fakes_BatteryTime(100); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_SetIsCharging(true); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); @@ -706,138 +824,144 @@ TEST(NearbyFpClient, ASSERT_EQ(1, message_stream_events.size()); ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), *message_stream_events[0]); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } -#endif /* NEARBY_FP_MESSAGE_STREAM */ -#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ -#ifdef NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, EnableSilenceMode_RfcommConnected) { constexpr uint64_t kPeerAddress = 0x123456; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab, - // Battery level - 3, 3, 0, 3, 0xd5, 0xd0, 0xda, - // Battery remaining time - 3, 4, 0, 1, 100, - // Enable silence mode - 1, 1, 0, 0}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time + 3, 4, 0, 1, 100, + // Enable silence mode + 1, 1, 0, 0}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); nearby_test_fakes_BatteryTime(100); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_fp_client_SetSilenceMode(kPeerAddress, true); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } -#endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, DisableSilenceMode_RfcommConnected) { constexpr uint64_t kPeerAddress = 0x123456; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab, - // Battery level - 3, 3, 0, 3, 0xd5, 0xd0, 0xda, - // Battery remaining time - 3, 4, 0, 1, 100, - // Disable silence mode - 1, 2, 0, 0}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time + 3, 4, 0, 1, 100, + // Disable silence mode + 1, 2, 0, 0}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); nearby_test_fakes_BatteryTime(100); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_fp_client_SetSilenceMode(kPeerAddress, false); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } -#endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, BatteryLevelLongForm_RfcommConnected) { constexpr uint64_t kPeerAddress = 0x123456; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab, - // Battery level - 3, 3, 0, 3, 0xd5, 0xd0, 0xda, - // Battery remaining time (256) - 3, 4, 0, 2, 1, 0, - // Disable silence mode - 1, 2, 0, 0}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time (256) + 3, 4, 0, 2, 1, 0, + // Disable silence mode + 1, 2, 0, 0}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); nearby_test_fakes_BatteryTime(0x100); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_fp_client_SetSilenceMode(kPeerAddress, false); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } #endif /* NEARBY_FP_MESSAGE_STREAM */ +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, EnableSilenceMode_NoRfcommConnection_ReturnsError) { constexpr uint64_t kPeerAddress = 0x123456; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); ASSERT_EQ(kNearbyStatusError, nearby_fp_client_SetSilenceMode(kPeerAddress, true)); } #endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, SignalLogBufferFull_RfcommConnected) { constexpr uint64_t kPeerAddress = 0x123456; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab, - // Battery level - 3, 3, 0, 3, 0xd5, 0xd0, 0xda, - // Battery remaining time - 3, 4, 0, 1, 100, - // Signal log buffer full - 2, 1, 0, 0}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time + 3, 4, 0, 1, 100, + // Signal log buffer full + 2, 1, 0, 0}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); nearby_test_fakes_BatteryTime(100); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_fp_client_SignalLogBufferFull(kPeerAddress); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } -#endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, ReceiveActiveComponentsRequest_SendsActiveComponentResponse) { constexpr uint64_t kPeerAddress = 0x123456; @@ -849,22 +973,24 @@ TEST(NearbyFpClient, .message_code = kPeerMessage[1], .length = kPeerMessage[2] * 256 + kPeerMessage[3], .data = NULL}; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab, - // Battery level - 3, 3, 0, 3, 0xd5, 0xd0, 0xda, - // Battery remaining time - 3, 4, 0, 1, 100, - // Active component response - 3, 6, 0, 1, 0x00}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time + 3, 4, 0, 1, 100, + // Active component response + 3, 6, 0, 1, 0x00}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); nearby_test_fakes_BatteryTime(100); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, @@ -873,12 +999,14 @@ TEST(NearbyFpClient, ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), *message_stream_events[1]); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } #endif /* NEARBY_FP_MESSAGE_STREAM */ +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, ReceiveCapabilities) { constexpr uint64_t kPeerAddress = 0x123456; constexpr uint8_t kCapabilities = 0x11; @@ -894,7 +1022,7 @@ TEST(NearbyFpClient, ReceiveCapabilities) { nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, @@ -906,7 +1034,7 @@ TEST(NearbyFpClient, ReceiveCapabilities) { } #endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, ReceivePlatformType) { constexpr uint64_t kPeerAddress = 0x123456; constexpr uint8_t kPeerMessage[] = {// platform type request, @@ -921,7 +1049,7 @@ TEST(NearbyFpClient, ReceivePlatformType) { nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, @@ -932,7 +1060,7 @@ TEST(NearbyFpClient, ReceivePlatformType) { } #endif /* NEARBY_FP_MESSAGE_STREAM */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, ReceiveRingRequest) { constexpr uint64_t kPeerAddress = 0x123456; constexpr uint8_t kRingTimeSeconds = 100; @@ -955,7 +1083,7 @@ TEST(NearbyFpClient, ReceiveRingRequest) { nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_MessageStreamConnected(kPeerAddress); nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, @@ -1059,7 +1187,7 @@ TEST(NearbyFpClient, Pairing_ProviderInitiated) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } // Pairing flow where the Seeker writes to the account key a little bit too @@ -1154,7 +1282,7 @@ TEST(NearbyFpClient, Pairing_WriteKeyBeforePaired_PairingSuccessful) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } TEST(NearbyFpClient, Pairing_SeekerInitiated_PairingSuccessful) { @@ -1241,7 +1369,7 @@ TEST(NearbyFpClient, Pairing_SeekerInitiated_PairingSuccessful) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } TEST(NearbyFpClient, Pairing_SeekerInitiatedWriteKeyEarly_PairingSuccessful) { @@ -1328,21 +1456,146 @@ TEST(NearbyFpClient, Pairing_SeekerInitiatedWriteKeyEarly_PairingSuccessful) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); +} + +static uint8_t GetType2KeyBasedPairingResponseFlags() { + // Not this is a dup of code under test. + uint8_t flags = 0; +#if NEARBY_FP_BLE_ONLY + flags |= 0x80; +#endif /*NEARBY_FP_BLE_ONLY*/ +#if NEARBY_FP_PREFER_BLE_BONDING + flags |= 0x40; +#endif /*NEARBY_FP_PREFER_BLE_BONDING*/ +#if NEARBY_FP_PREFER_LE_TRANSPORT + flags |= 0x20; +#endif /*NEARBY_FP_PREFER_LE_TRANSPORT*/ + return flags; +} + +TEST(NearbyFpClient, + SeekerSupportsBleDevicesSendExtendedKeybasedPairingResponseOneAddress) { + uint8_t salt = 0xAB; + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kBobPrivateKey, kBobPublicKey)); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_DISCOVERABLE)); + nearby_test_fakes_SetRandomNumber(salt); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = + 0x08; // Bit 4: 1 if the Seeker supports the BLE Device. 0 otherwise. + // Provider's public address + request[2] = 0xA0; + request[3] = 0xA1; + request[4] = 0xA2; + request[5] = 0xA3; + request[6] = 0xA4; + request[7] = 0xA5; + // Seeker's address + request[8] = 0xB0; + request[9] = 0xB1; + request[10] = 0xB2; + request[11] = 0xB3; + request[12] = 0xB4; + request[13] = 0xB5; + // salt + request[14] = 0xCD; + request[15] = 0xEF; + uint8_t encrypted[16 + 64]; + nearby_test_fakes_Aes128Encrypt(request, encrypted, kExpectedAesKey); + memcpy(encrypted + 16, kAlicePublicKey, 64); + // Seeker responds + ASSERT_EQ(kNearbyStatusOK, nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + encrypted, sizeof(encrypted))); + + auto response = nearby_test_fakes_GetGattNotifications().at(kKeyBasedPairing); + std::cout << VecToString(response) << std::endl; + std::vector decrypted_response(16); + std::vector expected_decrypted_response = { + // 0x02 = Key-based Pairing Response + 0x02, + // flags + GetType2KeyBasedPairingResponseFlags(), + // Number of identity addresses. + 1, + // Presence identity address + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, + // Random salt + salt, salt, salt, salt, salt, salt, salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response.data(), + kExpectedAesKey); + + ASSERT_EQ(decrypted_response, expected_decrypted_response); +} + +TEST(NearbyFpClient, + SeekerSupportsBleDevicesSendExtendedKeybasedPairingResponseTwoAddresses) { + uint8_t salt = 0xAB; + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); + nearby_test_fakes_SetSecondaryPublicAddress(0xC0C1C2C3C4C5); + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kBobPrivateKey, kBobPublicKey)); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_DISCOVERABLE)); + nearby_test_fakes_SetRandomNumber(salt); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = + 0x08; // Bit 4: 1 if the Seeker supports the BLE Device. 0 otherwise. + // Provider's public address + request[2] = 0xA0; + request[3] = 0xA1; + request[4] = 0xA2; + request[5] = 0xA3; + request[6] = 0xA4; + request[7] = 0xA5; + // Seeker's address + request[8] = 0xB0; + request[9] = 0xB1; + request[10] = 0xB2; + request[11] = 0xB3; + request[12] = 0xB4; + request[13] = 0xB5; + // salt + request[14] = 0xCD; + request[15] = 0xEF; + uint8_t encrypted[16 + 64]; + nearby_test_fakes_Aes128Encrypt(request, encrypted, kExpectedAesKey); + memcpy(encrypted + 16, kAlicePublicKey, 64); + // Seeker responds + ASSERT_EQ(kNearbyStatusOK, nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + encrypted, sizeof(encrypted))); + + auto response = nearby_test_fakes_GetGattNotifications().at(kKeyBasedPairing); + std::cout << VecToString(response) << std::endl; + std::vector decrypted_response(16); + std::vector expected_decrypted_response = { + // 0x02 = Key-based Pairing Response + 0x02, + // flags + GetType2KeyBasedPairingResponseFlags(), + // Number of identity addresses. + 2, + // Presence identity address. + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, + // Secondary identity address. + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + // Random salt + salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response.data(), + kExpectedAesKey); + + ASSERT_EQ(decrypted_response, expected_decrypted_response); } TEST(NearbyFpClient, Pair_AccountKeyStorageFull_AddsNewKey) { - uint8_t account_keys[] = { - 5, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, - 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, - 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88, 0x03, 0x13, 0x23, - 0x33, 0x43, 0x53, 0x63, 0x73, 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, - 0xF3, 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, 0x84, 0x94, 0xA4, - 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, - 0x75, 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, - }; nearby_fp_client_Init(NULL); - nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + Set5AccountKeys(); nearby_fp_LoadAccountKeys(); Pair(0x40); @@ -1351,7 +1604,7 @@ TEST(NearbyFpClient, Pair_AccountKeyStorageFull_AddsNewKey) { ASSERT_EQ(5, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } TEST(NearbyFpClient, ReadModelId) { @@ -1366,6 +1619,39 @@ TEST(NearbyFpClient, ReadModelId) { ASSERT_EQ(expected_model, std::vector(model, model + length)); } +TEST(NearbyFpClient, ReadReadyPsm) { + std::vector expected_psm = +#if NEARBY_FP_PREFER_LE_TRANSPORT + {1, 0xAB, 0xCD}; +#else + {0, 0, 0}; +#endif + uint8_t psm[3]; + size_t length = sizeof(psm); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); + nearby_test_fakes_SetPsm(0xABCD); + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_fakes_GattReadMessageStreamPsm(psm, &length)); + + ASSERT_EQ(sizeof(psm), length); + ASSERT_EQ(expected_psm, std::vector(psm, psm + length)); +} + +TEST(NearbyFpClient, ReadInvalidPsm) { + std::vector expected_psm = {0, 0, 0}; + uint8_t psm[3]; + size_t length = sizeof(psm); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); + nearby_test_fakes_SetPsm(-1); + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_fakes_GattReadMessageStreamPsm(psm, &length)); + + ASSERT_EQ(sizeof(psm), length); + ASSERT_EQ(expected_psm, std::vector(psm, psm + length)); +} + TEST(NearbyFpClient, Aes128Encrypt) { uint8_t input[16] = {0xF3, 0x0F, 0x4E, 0x78, 0x6C, 0x59, 0xA7, 0xBB, 0xF3, 0x87, 0x3B, 0x5A, 0x49, 0xBA, 0x97, 0xEA}; @@ -1384,7 +1670,132 @@ TEST(NearbyFpClient, Aes128Encrypt) { } } -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +TEST(NearbyFpClient, HmacSha256IncrementalOneChunk) { + const uint8_t kData[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xEE, + 0x4A, 0x24, 0x83, 0x73, 0x80, 0x52, 0xE4, 0x4E, 0x9B, + 0x2A, 0x14, 0x5E, 0x5D, 0xDF, 0xAA, 0x44, 0xB9, 0xE5, + 0x53, 0x6A, 0xF4, 0x38, 0xE1, 0xE5, 0xC6}; + const uint8_t kKey[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + const uint8_t kExpectedResult[] = { + 0x55, 0xEC, 0x5E, 0x60, 0x55, 0xAF, 0x6E, 0x92, 0x61, 0x8B, 0x7D, + 0x87, 0x10, 0xD4, 0x41, 0x37, 0x09, 0xAB, 0x5D, 0xA2, 0x7C, 0xA2, + 0x6A, 0x66, 0xF5, 0x2E, 0x5A, 0xD4, 0xE8, 0x20, 0x90, 0x52}; + nearby_fp_HmacSha256Context context; + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HmacSha256Start(&context, kKey, sizeof(kKey))); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_HmacSha256Update(kData, sizeof(kData))); + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HmacSha256Finish(&context, kKey, sizeof(kKey))); + + ASSERT_THAT(context.hash, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, HmacSha256IncrementalTwoChunks) { + const uint8_t kData1[] = {0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0xEE}; + const uint8_t kData2[] = {0x4A, 0x24, 0x83, 0x73, 0x80, 0x52, 0xE4, + 0x4E, 0x9B, 0x2A, 0x14, 0x5E, 0x5D, 0xDF, + 0xAA, 0x44, 0xB9, 0xE5, 0x53, 0x6A, 0xF4, + 0x38, 0xE1, 0xE5, 0xC6}; + const uint8_t kKey[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + const uint8_t kExpectedResult[] = { + 0x55, 0xEC, 0x5E, 0x60, 0x55, 0xAF, 0x6E, 0x92, 0x61, 0x8B, 0x7D, + 0x87, 0x10, 0xD4, 0x41, 0x37, 0x09, 0xAB, 0x5D, 0xA2, 0x7C, 0xA2, + 0x6A, 0x66, 0xF5, 0x2E, 0x5A, 0xD4, 0xE8, 0x20, 0x90, 0x52}; + nearby_fp_HmacSha256Context context; + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HmacSha256Start(&context, kKey, sizeof(kKey))); + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HmacSha256Update(kData1, sizeof(kData1))); + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HmacSha256Update(kData2, sizeof(kData2))); + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HmacSha256Finish(&context, kKey, sizeof(kKey))); + + ASSERT_THAT(context.hash, ElementsAreArray(kExpectedResult)); +} + +// Test cases from https://www.rfc-editor.org/rfc/rfc5869#appendix-A +TEST(NearbyFpClient, HkdfExtractSha256) { + const uint8_t kSalt[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}; + const uint8_t kIkm[] = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b}; + const uint8_t kExpectedPrk[] = { + 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f, + 0x0d, 0xc4, 0x7b, 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, + 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5}; + uint8_t prk[SHA256_KEY_SIZE]; + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HkdfExtractSha256(prk, kSalt, sizeof(kSalt), kIkm, + sizeof(kIkm))); + + ASSERT_THAT(prk, ElementsAreArray(kExpectedPrk)); +} + +TEST(NearbyFpClient, HkdfExpandSha256) { + const uint8_t kInfo[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9}; + const uint8_t kPrk[] = {0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, + 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63, + 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, + 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5}; + constexpr size_t kOkmSize = 42; + const uint8_t kExpectedOkm[kOkmSize] = { + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, + 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, + 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, + 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65}; + uint8_t okm[kOkmSize]; + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HkdfExpandSha256(okm, kOkmSize, kPrk, sizeof(kPrk), kInfo, + sizeof(kInfo))); + + ASSERT_THAT(okm, ElementsAreArray(kExpectedOkm)); +} + +TEST(NearbyFpClient, HkdfExtractSha256NoSalt) { + const uint8_t kIkm[] = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b}; + const uint8_t kExpectedPrk[] = { + 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, 0x7f, 0x33, 0xa9, + 0x1d, 0x6f, 0x64, 0x8b, 0xdf, 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, + 0x63, 0x77, 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04}; + uint8_t prk[SHA256_KEY_SIZE]; + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_HkdfExtractSha256(prk, NULL, 0, kIkm, sizeof(kIkm))); + + ASSERT_THAT(prk, ElementsAreArray(kExpectedPrk)); +} + +TEST(NearbyFpClient, HkdfExpandSha256NoInfo) { + const uint8_t kPrk[] = {0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, + 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf, + 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, + 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04}; + constexpr size_t kOkmSize = 42; + const uint8_t kExpectedOkm[kOkmSize] = { + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, + 0x2a, 0x06, 0x3c, 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, + 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, 0x9d, + 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8}; + uint8_t okm[kOkmSize]; + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_HkdfExpandSha256(okm, kOkmSize, kPrk, + sizeof(kPrk), NULL, 0)); + + ASSERT_THAT(okm, ElementsAreArray(kExpectedOkm)); +} + TEST(NearbyFpClient, HmacSha256) { const uint8_t kData[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xEE, 0x4A, 0x24, 0x83, 0x73, 0x80, 0x52, 0xE4, 0x4E, 0x9B, @@ -1421,6 +1832,7 @@ TEST(NearbyFpClient, AesCtr) { ASSERT_THAT(message, ElementsAreArray(kExpectedResult)); } +#if NEARBY_FP_ENABLE_ADDITIONAL_DATA TEST(NearbyFpClient, DecodeAdditionalData) { uint8_t message[] = {0x55, 0xEC, 0x5E, 0x60, 0x55, 0xAF, 0x6E, 0x92, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xEE, 0x4A, @@ -1532,18 +1944,19 @@ TEST(NearbyFpClient, PairAndSetPersonalizedName) { } #endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM TEST(NearbyFpClient, RfcommConnected_NoBatteryInfo_SendsModelIdAndBleAddress) { constexpr uint64_t kPeerAddress = 0x123456; - constexpr uint8_t kExpectedRfcommOutput[] = {// Model Id - 3, 1, 0, 3, 0x10, 0x11, 0x12, - // Ble Address - 3, 2, 0, 6, 0x6b, 0xab, 0xab, - 0xab, 0xab, 0xab}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt}; nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnimplemented); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1551,17 +1964,18 @@ TEST(NearbyFpClient, RfcommConnected_NoBatteryInfo_SendsModelIdAndBleAddress) { ASSERT_EQ(1, message_stream_events.size()); ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), *message_stream_events[0]); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } -#ifdef NEARBY_FP_RETROACTIVE_PAIRING +#if NEARBY_FP_RETROACTIVE_PAIRING TEST(NearbyFpClient, RetroactivePair) { nearby_fp_client_Init(NULL); constexpr uint64_t kPeerAddress = 0xB0B1B2B3B4B5; nearby_test_fakes_DevicePaired(kPeerAddress); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1613,7 +2027,7 @@ TEST(NearbyFpClient, RetroactivePair) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } TEST(NearbyFpClient, RetroactivePairAfterInitialPair) { @@ -1707,10 +2121,10 @@ TEST(NearbyFpClient, RetroactivePairAfterInitialPair) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1755,7 +2169,7 @@ TEST(NearbyFpClient, RetroactivePairAfterInitialPair) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } TEST(NearbyFpClient, RetroactivePairTwice) { @@ -1763,7 +2177,7 @@ TEST(NearbyFpClient, RetroactivePairTwice) { constexpr uint64_t kPeerAddress = 0xB0B1B2B3B4B5; nearby_test_fakes_DevicePaired(kPeerAddress); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1815,7 +2229,7 @@ TEST(NearbyFpClient, RetroactivePairTwice) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); nearby_test_fakes_SetRandomNumber(salt); request[0] = 0x00; // key-based pairing request @@ -1855,7 +2269,7 @@ TEST(NearbyFpClient, RetroactivePairTwice) { ASSERT_EQ(1, keys.size()); ASSERT_EQ(std::vector(kSeekerAccountKey, kSeekerAccountKey + sizeof(kExpectedAesKey)), - keys.GetKeys()[0]); + keys.GetKey(0)); } TEST(NearbyFpClient, RetroactivePairWrongBtAddress) { @@ -1863,7 +2277,7 @@ TEST(NearbyFpClient, RetroactivePairWrongBtAddress) { constexpr uint64_t kPeerAddress = 0xB0B1B2B3B4B5; nearby_test_fakes_DevicePaired(kPeerAddress); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1919,7 +2333,6 @@ TEST(NearbyFpClient, RfcommConnected_ClientDisconnects_EmitsDisconnectEvent) { nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnimplemented); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1945,7 +2358,6 @@ TEST(NearbyFpClient, RfcommConnected_PeerSendsMessage_PassMessageToClientApp) { nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnimplemented); nearby_test_fakes_MessageStreamConnected(kPeerAddress); @@ -1973,7 +2385,6 @@ TEST(NearbyFpClient, nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnimplemented); for (int i = 1; i < NEARBY_MAX_RFCOMM_CONNECTIONS + 10; i++) { @@ -2014,7 +2425,6 @@ TEST(NearbyFpClient, RfcommConnected_TwoInterleavedConnections_ParsesMessages) { nearby_fp_client_Init(&kClientCallbacks); Pair(0x40); message_stream_events.clear(); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnimplemented); nearby_test_fakes_MessageStreamConnected(kPeerAddress1); nearby_test_fakes_MessageStreamConnected(kPeerAddress2); @@ -2046,18 +2456,19 @@ TEST(NearbyFpClient, RfcommConnected_TwoInterleavedConnections_ParsesMessages) { #endif /* NEARBY_MAX_RFCOMM_CONNECTIONS > 1 */ TEST(NearbyFpClient, SendMessageStreamMessage) { + constexpr uint64_t kPeerAddress = 0x123456; constexpr nearby_message_stream_Message kMessage{ .message_group = 20, .message_code = 10, }; constexpr uint8_t kExpectedRfcommOutput[] = {20, 10, 0, 0}; nearby_fp_client_Init(&kClientCallbacks); - nearby_test_fakes_GetRfcommOutput().clear(); - nearby_fp_client_SendMessage(0x123456, &kMessage); + nearby_fp_client_SendMessage(kPeerAddress, &kMessage); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } TEST(NearbyFpClient, SendAck) { @@ -2069,12 +2480,12 @@ TEST(NearbyFpClient, SendAck) { }; constexpr uint8_t kExpectedRfcommOutput[] = {0xFF, 1, 0, 2, 20, 10}; nearby_fp_client_Init(&kClientCallbacks); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_fp_client_SendAck(&kMessage); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } TEST(NearbyFpClient, SendNack) { @@ -2088,12 +2499,12 @@ TEST(NearbyFpClient, SendNack) { constexpr uint8_t kExpectedRfcommOutput[] = {0xFF, 2, 0, 3, kFailReason, 20, 10}; nearby_fp_client_Init(&kClientCallbacks); - nearby_test_fakes_GetRfcommOutput().clear(); nearby_fp_client_SendNack(&kMessage, kFailReason); - ASSERT_THAT(kExpectedRfcommOutput, - ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); } #endif /* NEARBY_FP_MESSAGE_STREAM */ @@ -2118,6 +2529,8 @@ TEST(NearbyFpClient, TimerTriggered_RotatesBleAddress) { TEST(NearbyFpClient, TimerTriggered_InPairingMode_DoesntRotateBleAddress) { uint64_t firstAddress, secondAddress, thirdAddress; + + nearby_test_fakes_SetRandomNumber(0); nearby_fp_client_Init(NULL); nearby_test_fakes_SetInPairingMode(false); nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE); @@ -2191,6 +2604,1092 @@ TEST(NearbyFpClient, ChangeAdvertisementFlags_DoesntRotateBleAddress) { ASSERT_EQ(firstAddress, nearby_platform_GetBleAddress()); } +TEST(NearbyFpClient, GenerateSassAdvertisement) { + constexpr size_t kBufferSize = 4; + uint8_t buffer[kBufferSize]; + constexpr uint8_t devices_bitmap[] = {0x09}; + constexpr uint8_t kExpectedResult[] = {0x35, 0x85, 0x38, 0x09}; + size_t sass_length = nearby_fp_GenerateSassAdvertisement( + buffer, kBufferSize, nearby_fp_GetSassConnectionState(), 0x38, + devices_bitmap, sizeof(devices_bitmap)); + + ASSERT_EQ(sass_length, kBufferSize); + ASSERT_THAT(buffer, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, GenerateAccountFilterNoSass) { + std::vector random_numbers = {0xC7, 0xC8}; + constexpr uint8_t kExpectedResult[] = {13, 0x16, 0x2C, 0xFE, 0x00, + 0x52, 0x8E, 0x83, 0x84, 0x30, + 0x0C, 0x21, 0xC7, 0xC8}; + uint8_t advertisement[] = {13, 0x16, 0x2C, 0xFE, 0x00, 0x52, 0x8E, + 0x83, 0x84, 0x30, 0x0C, 0x21, 0xC7, 0xC8}; + uint8_t account_key1[] = {0x04, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t account_key2[] = {0x04, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, + 0x55, 0x55, 0x66, 0x66, 0x77, 0x77, 0x88, 0x88}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key1), + AccountKeyPair(kRemoteDevice, account_key2)}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys); + nearby_fp_LoadAccountKeys(); + + nearby_fp_SetBloomFilter(advertisement, + /* use_sass_format= */ false, NULL); + + ASSERT_THAT(advertisement, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, GenerateAccountFilterOneKeyNotInUse) { + std::vector random_numbers = {0xC7, 0xC8}; + constexpr uint8_t kExpectedResult[] = {17, 0x16, 0x2C, 0xFE, 0x10, 0x40, + 0x42, 0x80, 0x81, 0x01, 0x21, 0xC7, + 0xC8, 0x46, 0x6E, 0x39, 0xCB, 0x21}; + uint8_t advertisement[] = {17, 0x16, 0x2C, 0xFE, 0x00, 0x40, + 0, 0, 0, 0, 0x21, 0xC7, + 0xC8, 0x46, 0x6E, 0x39, 0xCB, 0x21}; + uint8_t account_key[] = {0x04, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key)}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys); + nearby_fp_LoadAccountKeys(); + + nearby_fp_SetBloomFilter(advertisement, + /* use_sass_format= */ true, NULL); + + ASSERT_THAT(advertisement, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, GenerateAccountFilterOneKeyInUse) { + std::vector random_numbers = {0xC7, 0xC8}; + constexpr uint8_t kExpectedResult[] = {17, 0x16, 0x2C, 0xFE, 0x10, 0x40, + 0x81, 0x28, 0xA8, 0x04, 0x21, 0xC7, + 0xC8, 0x46, 0x6E, 0x39, 0xCB, 0x21}; + uint8_t advertisement[] = {17, 0x16, 0x2C, 0xFE, 0, 0x40, + 0, 0, 0, 0, 0x21, 0xC7, + 0xC8, 0x46, 0x6E, 0x39, 0xCB, 0x21}; + uint8_t account_key[] = {0x04, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key)}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys); + nearby_fp_LoadAccountKeys(); + + nearby_fp_SetBloomFilter(advertisement, + /* use_sass_format= */ true, account_key); + + ASSERT_THAT(advertisement, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, GenerateAccountFilterSassTwoKeys) { + std::vector random_numbers = {0xC7, 0xC8}; + constexpr uint8_t kExpectedResult[] = { + 18, 0x16, 0x2C, 0xFE, 0x10, 0x50, 0x41, 0x90, 0x34, 0x80, + 0xA2, 0x21, 0xC7, 0xC8, 0x46, 0xE8, 0xF0, 0x50, 0x69}; + uint8_t advertisement[] = {18, 0x16, 0x2C, 0xFE, 0x10, 0x50, 0x41, + 0x90, 0x34, 0x80, 0xA2, 0x21, 0xC7, 0xC8, + 0x46, 0xE8, 0xF0, 0x50, 0x69}; + uint8_t account_key1[] = {0x04, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t account_key2[] = {0x04, 0x12, 0x13, 0x14, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + std::vector account_keys{ + AccountKeyPair(kRemoteDevice, account_key1), + AccountKeyPair(kRemoteDevice, account_key2)}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys); + nearby_fp_LoadAccountKeys(); + + nearby_fp_SetBloomFilter(advertisement, + /* use_sass_format= */ true, account_key2); + + ASSERT_THAT(advertisement, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, EncryptRrf) { + constexpr uint8_t kSassAdvertisement[] = {0x35, 0x00, 0x38, 0x09}; + constexpr uint8_t kExpectedResult[] = {0x46, 0x6E, 0x39, 0xCB, 0x21}; + constexpr size_t kBufferSize = RRF_HEADER_SIZE + sizeof(kSassAdvertisement); + uint8_t buffer[kBufferSize]; + constexpr uint8_t kAccountKey[] = {0x04, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB, + 0xCC, 0xDD, 0xEE, 0xFF}; + constexpr uint8_t kSaltField[] = {0x21, 0xC7, 0xC8}; + memcpy(buffer + RRF_HEADER_SIZE, kSassAdvertisement, + sizeof(kSassAdvertisement)); + + nearby_platform_status status = nearby_fp_EncryptRandomResolvableField( + buffer, kBufferSize, kAccountKey, kSaltField); + + ASSERT_EQ(status, kNearbyStatusOK); + ASSERT_THAT(buffer, ElementsAreArray(kExpectedResult)); +} + +#ifdef NEARBY_FP_ENABLE_SASS +TEST(NearbyFpClient, + SassConnected_ReceiveGetCapability_SendsCapabilityResponse) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {0x07, 0x10, 0, 0}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Battery level + 3, 3, 0, 3, 0xd5, 0xd0, 0xda, + // Battery remaining time + 3, 4, 0, 1, 100, + // Capability response + 7, 0x11, 0, 4, 1, 1, 208, 0}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(100); + nearby_test_fakes_GetRfcommOutput(kPeerAddress).clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(2, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); +} + +constexpr uint8_t kInUseMessage[] = {0x07, 0x41, 0, 22, 'i', 'n', '-', + 'u', 's', 'e', 0xC0, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, 0x70, 0x69, 0xd6, + 0xc5, 0x06, 0xa4, 0x94, 0x32}; + +TEST(NearbyFpClient, SassConnected_IndicateValidInUseKey_SendsAck) { + constexpr uint64_t kPeerAddress = 0x123456; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kInUseMessage[0], + .message_code = kInUseMessage[1], + .length = kInUseMessage[2] * 256 + kInUseMessage[3], + .data = (uint8_t*)kInUseMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x41}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + ASSERT_EQ(2, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); +} + +TEST(NearbyFpClient, SassConnected_IndicateInvalidInUseKey_SendsNack) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {0x07, 0x41, 0, 22, 'i', 'n', '-', + 'u', 's', 'e', 0xFF, 0xC1, 0xC2, 0xC3, + 0xC4, 0xC5, 0xC6, 0xC7, 0x70, 0x69, 0xd6, + 0xc5, 0x06, 0xa4, 0x94, 0x32}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // NACK + 0xFF, 2, 0, 3, 0, 0x07, 0x41}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(2, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); +} + +TEST(NearbyFpClient, SassConnected_SetMultipointState) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {0x07, 0x12, 0, 17, 1, 0xC0, 0xC1, + 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xb7, + 0x2f, 0x61, 0xb4, 0xe8, 0x92, 0xe1, 0x44}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x12}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_TRUE(nearby_platform_IsMultipointOn()); +} + +TEST(NearbyFpClient, SassConnected_SetSwitchingPreference) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kFlags = 0xAB; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x20, 0, 18, kFlags, 0, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0x8d, 0x03, 0x9d, 0xb0, 0x3d, 0x4b, 0x93, 0x7a}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x20}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kFlags, nearby_platform_GetSwitchingPreference()); +} + +TEST( + NearbyFpClient, + SassConnected_ReceiveGetSwitchingPreference_SendsSwitchingPreferenceResponse) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {0x07, 0x21, 0, 0}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Switching preference response + 7, 0x22, 0, 2, 0, 0}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(2, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); +} + +TEST(NearbyFpClient, SassConnected_SwitchActiveAudioSourceToThisPeer) { + constexpr uint64_t kPeerAddress = 0x123456; + // Bit 0 (MSB) is set. + constexpr uint8_t kFlags = 0xAB; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x30, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x30, 0x75, 0x28, 0xdd, 0x98, 0x27, 0x07, 0x95}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x30}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassSwitchActiveSourcePeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassSwitchActiveSourceFlags()); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassSwitchActiveSourcePreferredAudioSource()); +} + +TEST(NearbyFpClient, + SassConnected_SwitchActiveAudioSourceToAnotherSourceWithSameKey) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint64_t kPeerWithSameKey = 0x17890a; + // Bit 0 (MSB) is not set. + constexpr uint8_t kFlags = 0x0B; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x30, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x55, 0xf2, 0x2d, 0x76, 0x05, 0x83, 0x5f, 0x52}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x30}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + nearby_test_fakes_MessageStreamConnected(kPeerWithSameKey); + nearby_test_fakes_MessageStreamReceived(kPeerWithSameKey, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassSwitchActiveSourcePeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassSwitchActiveSourceFlags()); + ASSERT_EQ(kPeerWithSameKey, + nearby_test_fakes_GetSassSwitchActiveSourcePreferredAudioSource()); +} + +TEST(NearbyFpClient, + SassConnected_SwitchActiveAudioSourceToAnotherSourceWithDifferentKey) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint64_t kPeerWithDifferentKey = 0x17890a; + // Bit 0 (MSB) is not set. + constexpr uint8_t kFlags = 0x0B; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x30, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x55, 0xf2, 0x2d, 0x76, 0x05, 0x83, 0x5f, 0x52}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x30}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + nearby_test_fakes_MessageStreamConnected(kPeerWithDifferentKey); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassSwitchActiveSourcePeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassSwitchActiveSourceFlags()); + ASSERT_EQ(kPeerWithDifferentKey, + nearby_test_fakes_GetSassSwitchActiveSourcePreferredAudioSource()); +} + +TEST(NearbyFpClient, SassConnected_SwitchActiveAudioSourceToUnknownSource) { + constexpr uint64_t kPeerAddress = 0x123456; + // Bit 0 (MSB) is not set. + constexpr uint8_t kFlags = 0x0B; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x30, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x55, 0xf2, 0x2d, 0x76, 0x05, 0x83, 0x5f, 0x52}; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x30}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassSwitchActiveSourcePeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassSwitchActiveSourceFlags()); + ASSERT_EQ(0, + nearby_test_fakes_GetSassSwitchActiveSourcePreferredAudioSource()); +} + +TEST(NearbyFpClient, SassConnected_SwitchBackToDisconnectedDevice) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kFlags = 0xAB; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x31, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x30, 0x75, 0x28, 0xdd, 0x98, 0x27, 0x07, 0x95}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x31}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, nearby_test_fakes_GetSassSwitchBackPeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassSwitchBackFlags()); +} + +TEST(NearbyFpClient, + SassConnected_ReceiveGetConnectionStatus_SendsConnectionStatusResponse) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {0x07, 0x33, 0, 0}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // Connection status response + 7, 0x34, 0, 12, 0x00, 0xaf, 0x75, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); +} + +TEST( + NearbyFpClient, + SassConnected_ReceiveGetConnectionStatus_SendsConnectionStatusResponseNonSassActive) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint64_t kPeerNonSassAddress = 0x654321; + constexpr uint8_t kPeerMessage[] = {0x07, 0x33, 0, 0}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // Connection status response + 7, 0x34, 0, 12, 0x02, 0xaf, 0x75, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt}; + + nearby_fp_client_Init(&kClientCallbacks); + nearby_test_fakes_DevicePaired(kPeerNonSassAddress); + nearby_test_fakes_SetActiveAudioSource(kPeerNonSassAddress); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); +} + +TEST(NearbyFpClient, SassConnected_NotifySassInitiatedConnection) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kFlags = 0xAB; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x40, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x30, 0x75, 0x28, 0xdd, 0x98, 0x27, 0x07, 0x95}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x40}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassNotifyInitiatedConnectionPeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassNotifyInitiatedConnectionFlags()); +} + +TEST(NearbyFpClient, SassConnected_SendCustomData) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint64_t kSecondPeerAddress = 0x89ABCD; + constexpr uint8_t kCustomData = 0xAB; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x42, 0, 17, kCustomData, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x30, 0x75, 0x28, 0xdd, 0x98, 0x27, 0x07, 0x95}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Connection state + 7, 0x34, 0, 12, 1, 0xaf, 0x75, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // Connection state + 7, 0x34, 0, 12, 1, 0xaf, 0x75, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt, + // Connection state + 7, 0x34, 0, 12, 1, 0xaf, 0xde, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt, + // Send custom data ACK + 0xFF, 1, 0, 2, 0x07, 0x42}; + constexpr uint8_t kSecondPeerExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Connection state + 7, 0x34, 0, 12, 0, 0xaf, 0x75, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // Connection State + 7, 0x34, 0, 12, 0, 0xaf, 0xde, 0x14, kSalt, kSalt, kSalt, kSalt, kSalt, + kSalt, kSalt, kSalt}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetActiveAudioSource(kPeerAddress); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_SASS); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + nearby_test_fakes_MessageStreamConnected(kSecondPeerAddress); + nearby_test_fakes_MessageStreamReceived(kSecondPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(5, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[4]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_THAT( + kSecondPeerExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kSecondPeerAddress))); +} + +TEST(NearbyFpClient, SassConnected_SetDropConnectionTarget) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kFlags = 0xAB; + constexpr uint8_t kPeerMessage[] = { + 0x07, 0x43, 0, 17, kFlags, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0x30, 0x75, 0x28, 0xdd, 0x98, 0x27, 0x07, 0x95}; + const nearby_event_MessageStreamReceived kExpectedMessage = { + .peer_address = kPeerAddress, + .message_group = kPeerMessage[0], + .message_code = kPeerMessage[1], + .length = kPeerMessage[2] * 256 + kPeerMessage[3], + .data = (uint8_t*)kPeerMessage + 4, + }; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0xFF, 1, 0, 2, 0x07, 0x43}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(3, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[2]); + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_EQ(kPeerAddress, + nearby_test_fakes_GetSassDropConnectionTargetPeerAddress()); + ASSERT_EQ(kFlags, nearby_test_fakes_GetSassDropConnectionTargetFlags()); +} + +TEST(NearbyFpClient, CreateSassAdvertiment_SeekerConnected_UsesInUseKey) { + constexpr uint64_t kPeerAddress = 0x123456; + std::map> expected_results = { + { + 1, // Salt size + {0x10, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x08, 0x20, 0x44, 0x11, + 0x11, kSalt, 0x46, 0xdb, 0x29, 0x6a, 0xb3, 2, 0x0a, kTxPower}, + }, + {2, // Salt size + {0x11, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x01, 0x11, 0x90, 0x60, 0x21, + kSalt, kSalt, 0x46, 0xdf, 0xa3, 0x4b, 0xed, 2, 0x0a, kTxPower}}}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_SASS); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(expected_results[NEARBY_FP_SALT_SIZE])); +} + +TEST(NearbyFpClient, + CreateSassAdvertiment_AdvertisingThenSeekerConnects_UsesInUseKey) { + constexpr uint64_t kPeerAddress = 0x123456; + std::map> expected_results = { + { + 1, // Salt size + {0x10, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x08, 0x20, 0x44, 0x11, + 0x11, kSalt, 0x46, 0xdb, 0x29, 0x6a, 0xb3, 2, 0x0a, kTxPower}, + }, + {2, // Salt size + {0x11, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x01, 0x11, 0x90, 0x60, 0x21, + kSalt, kSalt, 0x46, 0xdf, 0xa3, 0x4b, 0xed, 2, 0x0a, kTxPower}}}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_SASS); + + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(expected_results[NEARBY_FP_SALT_SIZE])); +} + +TEST(NearbyFpClient, CreateSassAdvertiment_NoSeeker_UsesLastUsedKey) { + std::map> expected_results = { + { + 1, // Salt size + {0x10, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x01, 0x03, 0x04, 0xa0, + 0x11, kSalt, 0x46, 0xdb, 0x29, 0x6a, 0xb3, 2, 0x0a, kTxPower}, + }, + {2, // Salt size + {0x11, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x02, 0x20, 0x82, 0x34, 0x21, + kSalt, kSalt, 0x46, 0xdf, 0xa3, 0x4b, 0xed, 2, 0x0a, kTxPower}}}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_SASS); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(expected_results[NEARBY_FP_SALT_SIZE])); +} + +TEST(NearbyFpClient, CreateSassAdvertiment_SeekerDisconnects_UsesLastUsedKey) { + constexpr uint64_t kPeerAddress = 0x123456; + std::map> expected_results = { + { + 1, // Salt size + {0x10, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x01, 0x03, 0x04, 0xa0, + 0x11, kSalt, 0x46, 0xdb, 0x29, 0x6a, 0xb3, 2, 0x0a, kTxPower}, + }, + {2, // Salt size + {0x11, 0x16, 0x2c, 0xfe, 0x10, 0x42, 0x02, 0x20, 0x82, 0x34, 0x21, + kSalt, kSalt, 0x46, 0xdf, 0xa3, 0x4b, 0xed, 2, 0x0a, kTxPower}}}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_SASS); + + nearby_test_fakes_MessageStreamDisconnected(kPeerAddress); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(expected_results[NEARBY_FP_SALT_SIZE])); +} + +TEST(NearbyFpClient, MultipointSwitch_WithName_SendsNotificationToAllSeekers) { + constexpr uint8_t kReason = 2; + const char* kName = "SEEKER"; + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint64_t kSecondPeerAddress = 0x89ABCD; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // Notify multipoint switch event + 0x07, 0x32, 0x00, 0x08, 0x02, 0x02, 'S', 'E', 'E', 'K', 'E', 'R'}; + constexpr uint8_t kSecondPeerExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0x07, 0x32, 0x00, 0x08, 0x02, 0x01, 'S', 'E', 'E', 'K', 'E', 'R'}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + nearby_test_fakes_MessageStreamConnected(kSecondPeerAddress); + nearby_test_fakes_MessageStreamReceived(kSecondPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_SassMultipointSwitch(kReason, kSecondPeerAddress, kName); + + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_THAT( + kSecondPeerExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kSecondPeerAddress))); +} + +TEST(NearbyFpClient, + MultipointSwitch_WithoutName_SendsNotificationToAllSeekers) { + constexpr uint8_t kReason = 2; + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint64_t kSecondPeerAddress = 0x8909AF; + constexpr uint8_t kExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // Notify multipoint switch event + 0x07, 0x32, 0x00, 0x06, 0x02, 0x02, '0', '9', 'A', 'F'}; + constexpr uint8_t kSecondPeerExpectedRfcommOutput[] = { + // Model Id + 3, 1, 0, 3, 0x10, 0x11, 0x12, + // Ble Address + 3, 2, 0, 6, 0x6b, 0xab, 0xab, 0xab, 0xab, 0xab, + // Session nonce + 3, 10, 0, 8, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, kSalt, + // Indicate in-use key ACK + 0xFF, 1, 0, 2, 0x07, 0x41, + // ACK + 0x07, 0x32, 0x00, 0x06, 0x02, 0x01, '0', '9', 'A', 'F'}; + + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + message_stream_events.clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + nearby_test_fakes_MessageStreamConnected(kSecondPeerAddress); + nearby_test_fakes_MessageStreamReceived(kSecondPeerAddress, kInUseMessage, + sizeof(kInUseMessage)); + + nearby_test_fakes_SassMultipointSwitch(kReason, kSecondPeerAddress, NULL); + + ASSERT_THAT( + kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kPeerAddress))); + ASSERT_THAT( + kSecondPeerExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput(kSecondPeerAddress))); +} +#endif /* NEARBY_FP_ENABLE_SASS */ + #pragma GCC diagnostic pop int main(int argc, char** argv) { diff --git a/embedded/common/source/mbedtls/gen_secret.c b/embedded/common/source/mbedtls/gen_secret.c new file mode 100644 index 0000000000..2f90422516 --- /dev/null +++ b/embedded/common/source/mbedtls/gen_secret.c @@ -0,0 +1,108 @@ +// 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. + +// +// gen_secret module +// +// Implements the nearby_platform_GenSec256r1Secret() function, using the +// MBEDTLS package developed by ARM. +// +// To use the package, uncomment the NEARBY_PLATFORM_USE_MBEDTLS define in +// config.mk. +// +// This routine was separated from the mbedtls.c code because an implementation +// may need to implement the shared secret function using a hardware embedded +// key. In such an implementation, the actual private key is not directly +// accessable, but rather the shared secret is generated using the public key +// and the sequestered private key without ever revealing the private key in +// code. +// + +#define MBEDTLS_ALLOW_PRIVATE_ACCESS + +#include +#include +#include +#include +#include /* generic interface */ +#include +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) +#include +#endif + +#include +#include + +#ifndef NEARBY_PLATFORM_HAS_SE +static int crypto_rand(void* const seed, uint8_t* const out, + size_t const size) { + (void)seed; + for (size_t i = 0; i < size; i++) { + out[i] = rand() % UINT8_MAX; + } + return 0; +} + +/** + * Generates a shared sec256p1 secret using remote party public key and this + * device's private key. + */ +nearby_platform_status nearby_platform_GenSec256r1Secret( + const uint8_t remote_party_public_key[64], uint8_t shared_secret[32]) { + nearby_platform_status status = kNearbyStatusError; + mbedtls_ecp_group grp; + mbedtls_ecp_point pub; + mbedtls_mpi prv; + mbedtls_mpi secret; + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&pub); + mbedtls_mpi_init(&prv); + mbedtls_mpi_init(&secret); + + mbedtls_mpi_uint p = 1; + pub.Z.p = &p; + pub.Z.n = 1; + + const uint8_t* pkp; + + pkp = nearby_platform_GetAntiSpoofingPrivateKey(); + if (!pkp) { + goto exit; + } + if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) != 0) goto exit; + if (mbedtls_mpi_read_binary(&pub.X, remote_party_public_key, 32) != 0) + goto exit; + if (mbedtls_mpi_read_binary(&pub.Y, remote_party_public_key + 32, 32) != 0) + goto exit; + if (mbedtls_mpi_read_binary(&prv, pkp, 32) != 0) goto exit; + if ((mbedtls_ecdh_compute_shared(&grp, &secret, &pub, &prv, crypto_rand, + NULL)) != 0) + goto exit; + if (mbedtls_mpi_write_binary(&secret, shared_secret, 32) != 0) goto exit; + + status = kNearbyStatusOK; + +exit: + pub.Z.p = NULL; + pub.Z.n = 0; + + mbedtls_ecp_group_free(&grp); + mbedtls_ecp_point_free(&pub); + mbedtls_mpi_free(&prv); + mbedtls_mpi_free(&secret); + + return status; +} +#endif /* NEARBY_PLATFORM_HAS_SE */ diff --git a/embedded/common/source/mbedtls/mbedtls.c b/embedded/common/source/mbedtls/mbedtls.c new file mode 100644 index 0000000000..e72b28b94f --- /dev/null +++ b/embedded/common/source/mbedtls/mbedtls.c @@ -0,0 +1,121 @@ +// 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. + +// +// MBEDTLS module +// +// Purpose: Implements the cryptographic functions used by fp-provider using the +// MBEDTLS package developed by ARM. +// +// To use the package, uncomment the NEARBY_PLATFORM_USE_MBEDTLS define in +// config.mk. +// +// The following features are implemented here: +// +// nearby_platform_Sha256Start(), nearby_platform_Sha256Update(), +// nearby_platform_Sha256Finish() +// +// Implements the SHA function across and arbitrary length block. Partial +// blocks can be sent, and the resulting SHA will be over all blocks. +// +// nearby_platform_Aes128Encrypt(), nearby_platform_Aes128Decrypt() +// +// Encrypt and decrypt a block of data with a given key. +// +// Note that the required function nearby_platform_GenSec256r1Secret() is in a +// separate file, gen_secret.c. +// + +#include +#include +#include +#include /* generic interface */ +#include +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) +#include +#endif + +#include + +static mbedtls_sha256_context sha256_ctx; + +nearby_platform_status nearby_platform_Sha256Start() { + nearby_platform_status status = kNearbyStatusError; + mbedtls_sha256_init(&sha256_ctx); + if (mbedtls_sha256_starts_ret(&sha256_ctx, 0) == 0) { + status = kNearbyStatusOK; + } else { + mbedtls_sha256_free(&sha256_ctx); + } + return status; +} + +nearby_platform_status nearby_platform_Sha256Update(const void* data, + size_t length) { + nearby_platform_status status = kNearbyStatusError; + if (mbedtls_sha256_update_ret(&sha256_ctx, (const unsigned char*)data, + length) == 0) { + status = kNearbyStatusOK; + } else { + mbedtls_sha256_free(&sha256_ctx); + } + return status; +} + +nearby_platform_status nearby_platform_Sha256Finish(uint8_t out[32]) { + nearby_platform_status status = kNearbyStatusError; + if (mbedtls_sha256_finish_ret(&sha256_ctx, out) == 0) { + status = kNearbyStatusOK; + } + mbedtls_sha256_free(&sha256_ctx); + return status; +} + +/** + * Encrypts a data block with AES128 in ECB mode. + */ +nearby_platform_status nearby_platform_Aes128Encrypt( + const uint8_t input[AES_MESSAGE_SIZE_BYTES], + uint8_t output[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { + nearby_platform_status status = kNearbyStatusError; + mbedtls_aes_context ctx; + mbedtls_aes_init(&ctx); + if (mbedtls_aes_setkey_enc(&ctx, key, 128) != 0) goto exit; + if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, input, output) != 0) + goto exit; + status = kNearbyStatusOK; +exit: + mbedtls_aes_free(&ctx); + return status; +} + +/** + * Decrypts a data block with AES128 in ECB mode. + */ +nearby_platform_status nearby_platform_Aes128Decrypt( + const uint8_t input[AES_MESSAGE_SIZE_BYTES], + uint8_t output[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { + nearby_platform_status status = kNearbyStatusError; + mbedtls_aes_context ctx; + mbedtls_aes_init(&ctx); + if (mbedtls_aes_setkey_dec(&ctx, key, 128) != 0) goto exit; + if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_DECRYPT, input, output) != 0) + goto exit; + status = kNearbyStatusOK; +exit: + mbedtls_aes_free(&ctx); + return status; +} diff --git a/embedded/common/source/nearby_config.h b/embedded/common/source/nearby_config.h index 37f7e5e25b..c425fc22eb 100644 --- a/embedded/common/source/nearby_config.h +++ b/embedded/common/source/nearby_config.h @@ -16,31 +16,64 @@ #define NEARBY_CONFIG_H // Support FP Battery Notification extension -#define NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#ifndef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#define NEARBY_FP_ENABLE_BATTERY_NOTIFICATION 1 +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ // Support FP Additional Data extension -#define NEARBY_FP_ENABLE_ADDITIONAL_DATA +#ifndef NEARBY_FP_ENABLE_ADDITIONAL_DATA +#define NEARBY_FP_ENABLE_ADDITIONAL_DATA 1 +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ // Personalized name max size in bytes #define PERSONALIZED_NAME_MAX_SIZE 64 // Support FP Message Stream extension -#define NEARBY_FP_MESSAGE_STREAM +#ifndef NEARBY_FP_MESSAGE_STREAM +#define NEARBY_FP_MESSAGE_STREAM 1 +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +// Number of salt bytes to use in advertisements +#define NEARBY_FP_SALT_SIZE 2 // Does the platform have a native BLE address rotation routine? // #define NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION +// Support Smart Audio Source Switching +// #define NEARBY_FP_ENABLE_SASS + // The maximum size in bytes of additional data in a message in Message Stream. // Bigger payloads will be truncated. -#define MAX_MESSAGE_STREAM_PAYLOAD_SIZE 8 +#define MAX_MESSAGE_STREAM_PAYLOAD_SIZE 22 // The maximum number of concurrent RFCOMM connections #define NEARBY_MAX_RFCOMM_CONNECTIONS 2 // Support Retroactive pairing extension -#define NEARBY_FP_RETROACTIVE_PAIRING +#ifndef NEARBY_FP_RETROACTIVE_PAIRING +#define NEARBY_FP_RETROACTIVE_PAIRING 1 +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ // The maximum number of concurrent retroactive pairing process #define NEARBY_MAX_RETROACTIVE_PAIRING 2 +// Is this platform a BLE-only device? +#ifndef NEARBY_FP_BLE_ONLY +#define NEARBY_FP_BLE_ONLY 0 +#endif /* NEARBY_FP_BLE_ONLY */ + +// Does this device prefer BLE bonding? +#ifndef NEARBY_FP_PREFER_BLE_BONDING +#define NEARBY_FP_PREFER_BLE_BONDING 0 +#endif /* NEARBY_FP_PREFER_BLE_BONDING */ + +// Does this device prefer LE transport for FP Message Stream? +// When this feature is On, the Seeker will try to connect to the provider over +// an L2CAP channel. When this feature is Off, the Seeker connects over RFCOMM. +#ifndef NEARBY_FP_PREFER_LE_TRANSPORT +#define NEARBY_FP_PREFER_LE_TRANSPORT 0 +#endif /* NEARBY_FP_PREFER_LE_TRANSPORT */ + +// The maximum number of account keys that can be stored on the device. +#define NEARBY_MAX_ACCOUNT_KEYS 5 #endif /* NEARBY_CONFIG_H */ diff --git a/embedded/common/source/nearby_event.h b/embedded/common/source/nearby_event.h index 74d76905d8..921696ef58 100644 --- a/embedded/common/source/nearby_event.h +++ b/embedded/common/source/nearby_event.h @@ -112,6 +112,10 @@ typedef struct { // Handled by: Client app #define MESSAGE_CODE_PLATFORM_TYPE 8 +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +#define MESSAGE_CODE_SESSION_NONCE 0x0A + // Message group Device Action Event #define MESSAGE_GROUP_DEVICE_ACTION_EVENT 4 @@ -137,4 +141,112 @@ typedef struct { uint8_t *data; } nearby_event_MessageStreamReceived; +// Message group Smart Audio Source Switching +#define MESSAGE_GROUP_SASS 7 + +// Direction: Both +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: No +// ACK required: No +#define MESSAGE_CODE_SASS_GET_CAPABILITY 0x10 + +// Direction: Both +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_NOTIFY_CAPABILITY 0x11 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_SET_MULTIPOINT_STATE 0x12 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_SET_SWITCHING_PREFERENCE 0x20 + +// Direction: Both +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: No +// ACK required: No +#define MESSAGE_CODE_SASS_GET_SWITCHING_PREFERENCE 0x21 + +// Direction: Both +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_NOTIFY_SWITCHING_PREFERENCE 0x22 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_SWITCH_ACTIVE_AUDIO_SOURCE 0x30 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_SWITCH_BACK_AUDIO_SOURCE 0x31 + +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: No +// ACK required: No +#define MESSAGE_CODE_SASS_NOTIFY_MULTIPOINT_SWITCH_EVENT 0x32 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: No +// ACK required: No +#define MESSAGE_CODE_SASS_GET_CONNECTION_STATUS 0x33 + +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +// Encrypted: Yes +// Signed: No +// ACK required: No +#define MESSAGE_CODE_SASS_NOTIFY_CONNECTION_STATUS 0x34 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_NOTIFY_SASS_INITIATED_CONNECTION 0x40 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_IN_USE_ACCOUNT_KEY 0x41 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_SEND_CUSTOM_DATA 0x42 + +// Direction: Seeker -> Provider +// Handled by: Nearby Fast Pair library +// Encrypted: No +// Signed: Yes +// ACK required: Yes +#define MESSAGE_CODE_SASS_SET_DROP_CONNECTION_TARGET 0x43 + #endif /* NEARBY_EVENT_H */ diff --git a/embedded/common/source/nearby_fp_library.c b/embedded/common/source/nearby_fp_library.c index adf656fb4d..6c1a2a31c3 100644 --- a/embedded/common/source/nearby_fp_library.c +++ b/embedded/common/source/nearby_fp_library.c @@ -18,16 +18,34 @@ #include "nearby.h" #include "nearby_assert.h" -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#include "nearby_message_stream.h" +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION #include "nearby_platform_battery.h" #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +#include "nearby_platform_audio.h" #include "nearby_platform_bt.h" #include "nearby_platform_persistence.h" #include "nearby_platform_se.h" #include "nearby_trace.h" #include "nearby_utils.h" -#define ACCOUNT_KEY_LIST_SIZE_BYTES 81 +typedef struct { + uint8_t num_keys; + nearby_platform_AccountKeyInfo key[NEARBY_MAX_ACCOUNT_KEYS]; +} AccountKeyList; + +// Advertisement header with SASS format (version 1) +#define SASS_HEADER 0x10 + +// Offset to the advertisement header +#define HEADER_OFFSET 4 + +// Offset to account data in a non-discoverable advertisement +#define ACCOUNT_KEY_DATA_OFFSET 5 + +#define LTV_HEADER_SIZE 1 +#define ACCOUNT_KEY_LIST_SIZE_BYTES sizeof(AccountKeyList) #define SHOW_PAIRING_INDICATION_BYTE 0 #define DONT_SHOW_PAIRING_INDICATION_BYTE 2 #define SHOW_BATTERY_INDICATION_BYTE 0x33 @@ -37,88 +55,175 @@ // In the battery values, the highest bit indicates charging, the lower 7 are // the battery level #define BATTERY_LEVEL_MASK 0x7F -#define SALT_FIELD_LENGTH_AND_TYPE_BYTE 0x11 -#define SALT_SIZE_BYTES 1 +#define SALT_FIELD_LENGTH_AND_TYPE_BYTE ((NEARBY_FP_SALT_SIZE << 4) | 1) #define BATTERY_INFO_SIZE_BYTES 4 +// Response for seekers that don't support BLE-only devices. #define KEY_BASED_PAIRING_RESPONSE_FLAG 0x01 +// Response for seekers that support BLE-only devices. +#define KEY_BASED_PAIRING_EXTENDED_RESPONSE_FLAG 0x02 #define GAP_DATA_TYPE_SERVICE_DATA_UUID 0x16 #define FP_SERVICE_UUID 0xFE2C #define GAP_DATA_TYPE_TX_POWER_LEVEL_UUID 0x0A #define TX_POWER_DATA_SIZE 2 +#define SASS_HEADER_SIZE 1 + +// (type + length) + state + custom_data +#define MIN_SASS_ADERTISEMENT_SIZE 3 + +#define SASS_CONN_STATE_ON_HEAD_OFFSET 7 +#define SASS_CONN_STATE_AVAIL_OFFSET 6 +#define SASS_CONN_STATE_FOCUS_OFFSET 5 +#define SASS_CONN_STATE_AUTO_RECONNECTED_OFFSET 4 +#define SASS_CONN_STATE_MASK 0x0F + +#define MOST_RECENTLY_USED_ACCOUNT_KEY_BIT 0x01 +#define IN_USE_ACCOUNT_KEY_BIT 0x02 -static uint8_t account_key_list[ACCOUNT_KEY_LIST_SIZE_BYTES]; +// SASS Configuration Flags +#define SASS_CF_ON_OFFSET 15 +#define SASS_CF_MULTIPOINT_CONFIGURABLE 14 +#define SASS_CF_MULTIPOINT_ON 13 +#define SASS_CF_OHD_SUPPORTED 12 +#define SASS_CF_OHD_ENABLED 11 + +#define MESSAGE_AUTHENTICATION_CODE_SIZE 8 + +#define BOOL_TO_INT(x) ((x) ? 1 : 0) + +static const uint8_t kSassRrdKey[] = {'S', 'A', 'S', 'S', '-', 'R', + 'R', 'D', '-', 'K', 'E', 'Y'}; + +static AccountKeyList account_key_list; static uint8_t sha_buffer[32]; -static uint8_t key_and_salt[ACCOUNT_KEY_SIZE_BYTES + SALT_SIZE_BYTES + - BATTERY_INFO_SIZE_BYTES]; -static size_t GetAccountKeyListUsedSize() { - return nearby_fp_GetAccountKeyOffset(nearby_fp_GetAccountKeyCount()); +#define RETURN_IF_ERROR(X) \ + do { \ + nearby_platform_status status = X; \ + if (kNearbyStatusOK != status) return status; \ + } while (0) + +// Returns the Length part of Length|Type field. +static int GetLtLength(uint8_t value) { return value >> 4; } + +// Returns the Type part of Length|Type field. +static int GetLtType(uint8_t value) { return value & 0x0F; } + +const uint8_t* nearby_fp_FindLtv(const uint8_t* advertisement, int type) { + size_t offset = ACCOUNT_KEY_DATA_OFFSET; + size_t advertisement_length = advertisement[0]; + while (offset < advertisement_length) { + if (type == GetLtType(advertisement[offset])) { + return advertisement + offset; + } + offset += GetLtLength(advertisement[offset]) + LTV_HEADER_SIZE; + } + return NULL; } -size_t nearby_fp_GetAccountKeyCount() { return account_key_list[0]; } +static size_t GetAccountKeyListUsedSize() { return sizeof(account_key_list); } -size_t nearby_fp_GetAccountKeyOffset(unsigned key_number) { - return 1 + key_number * ACCOUNT_KEY_SIZE_BYTES; +// Returns true if |account_key| is present in [0..end_offset) range in acount +// key list +static bool IsAccountKeyInRange(const uint8_t* account_key, size_t end_offset) { + for (size_t i = 0; i < end_offset; i++) { + if (!memcmp(account_key, nearby_fp_GetAccountKey(i)->account_key, + ACCOUNT_KEY_SIZE_BYTES)) { + return true; + } + } + return false; } -const uint8_t* nearby_fp_GetAccountKey(unsigned key_number) { +size_t nearby_fp_GetAccountKeyCount() { return account_key_list.num_keys; } + +size_t nearby_fp_GetUniqueAccountKeyCount() { + size_t count = 0; + int offset = 0; + while (true) { + offset = nearby_fp_GetNextUniqueAccountKeyIndex(offset); + if (offset == -1) break; + count++; + offset++; + } + return count; +} + +int nearby_fp_GetNextUniqueAccountKeyIndex(int offset) { + while (offset < nearby_fp_GetAccountKeyCount()) { + const nearby_platform_AccountKeyInfo* account_key_info = + nearby_fp_GetAccountKey(offset); + if (IsAccountKeyInRange(account_key_info->account_key, offset)) { + offset++; + } else { + return offset; + } + } + return -1; +} + +const nearby_platform_AccountKeyInfo* nearby_fp_GetAccountKey( + unsigned key_number) { NEARBY_ASSERT(key_number < nearby_fp_GetAccountKeyCount()); - return account_key_list + nearby_fp_GetAccountKeyOffset(key_number); + return &account_key_list.key[key_number]; } void nearby_fp_MarkAccountKeyAsActive(unsigned key_number) { NEARBY_ASSERT(key_number < nearby_fp_GetAccountKeyCount()); - uint8_t tmp[ACCOUNT_KEY_SIZE_BYTES]; if (key_number == 0) return; // Move the key to the top of the list - nearby_fp_CopyAccountKey(tmp, key_number); - memmove(account_key_list + nearby_fp_GetAccountKeyOffset(1), - account_key_list + nearby_fp_GetAccountKeyOffset(0), - key_number * ACCOUNT_KEY_SIZE_BYTES); - memcpy(account_key_list + nearby_fp_GetAccountKeyOffset(0), tmp, - ACCOUNT_KEY_SIZE_BYTES); + nearby_platform_AccountKeyInfo tmp = account_key_list.key[key_number]; + for (unsigned i = key_number; i > 0; i--) { + account_key_list.key[i] = account_key_list.key[i - 1]; + } + account_key_list.key[0] = tmp; } -void nearby_fp_CopyAccountKey(uint8_t* dest, unsigned key_number) { - size_t offset = nearby_fp_GetAccountKeyOffset(key_number); - NEARBY_ASSERT(offset + ACCOUNT_KEY_SIZE_BYTES <= ACCOUNT_KEY_LIST_SIZE_BYTES); - memcpy(dest, account_key_list + offset, ACCOUNT_KEY_SIZE_BYTES); +void nearby_fp_CopyAccountKey(nearby_platform_AccountKeyInfo* dest, + unsigned key_number) { + NEARBY_ASSERT(key_number < nearby_fp_GetAccountKeyCount()); + *dest = account_key_list.key[key_number]; } static unsigned combineNibbles(unsigned high, unsigned low) { return ((high << 4) & 0xF0) | (low & 0x0F); } -void nearby_fp_AddAccountKey(const uint8_t key[ACCOUNT_KEY_SIZE_BYTES]) { +// Returns |a| == |b| +static bool AccountKeyInfoEquals(const nearby_platform_AccountKeyInfo* a, + const nearby_platform_AccountKeyInfo* b) { + return a == b || + ( +#ifdef NEARBY_FP_ENABLE_SASS + a->peer_address == b->peer_address && +#endif /* NEARBY_FP_ENABLE_SASS */ + !memcmp(a->account_key, b->account_key, ACCOUNT_KEY_SIZE_BYTES)); +} + +void nearby_fp_AddAccountKey(const nearby_platform_AccountKeyInfo* key) { // Find if the key is already on the list unsigned i; size_t key_count; - size_t length; - size_t max_bytes; key_count = nearby_fp_GetAccountKeyCount(); for (i = 0; i < key_count; i++) { - if (!memcmp(key, nearby_fp_GetAccountKey(i), ACCOUNT_KEY_SIZE_BYTES)) { + if (AccountKeyInfoEquals(key, nearby_fp_GetAccountKey(i))) { nearby_fp_MarkAccountKeyAsActive(i); return; } } - // Insert `key` at the list top - length = key_count * ACCOUNT_KEY_SIZE_BYTES; - max_bytes = ACCOUNT_KEY_LIST_SIZE_BYTES - nearby_fp_GetAccountKeyOffset(1); - if (length > max_bytes) { - // Buffer is full, the last key will fall off the edge - length = max_bytes; - } else { - // We have room for one more key - account_key_list[0]++; + // Insert `key` at the list top. If the list is full, the last key will fall + // off the edge + size_t keys_to_copy = key_count < NEARBY_MAX_ACCOUNT_KEYS + ? key_count + : NEARBY_MAX_ACCOUNT_KEYS - 1; + for (i = keys_to_copy; i > 0; i--) { + account_key_list.key[i] = account_key_list.key[i - 1]; + } + account_key_list.key[0] = *key; + if (key_count < NEARBY_MAX_ACCOUNT_KEYS) { + account_key_list.num_keys++; } - memmove(account_key_list + nearby_fp_GetAccountKeyOffset(1), - account_key_list + nearby_fp_GetAccountKeyOffset(0), length); - memcpy(account_key_list + nearby_fp_GetAccountKeyOffset(0), key, - ACCOUNT_KEY_SIZE_BYTES); } - size_t nearby_fp_CreateDiscoverableAdvertisement(uint8_t* output, size_t length) { NEARBY_ASSERT(length >= DISCOVERABLE_ADV_SIZE_BYTES); @@ -144,7 +249,7 @@ size_t nearby_fp_CreateDiscoverableAdvertisement(uint8_t* output, return i; } -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION void SerializeBatteryInfo(uint8_t* output, const nearby_platform_BatteryInfo* battery_info) { uint8_t charging = battery_info->is_charging ? BATTERY_INFO_CHARGING @@ -169,7 +274,7 @@ static void AddBatteryInfo(uint8_t* output, size_t length, static size_t CreateNondiscoverableAdvertisement( uint8_t* output, size_t length, bool show_pairing_indicator -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION , bool show_battery_indicator, const nearby_platform_BatteryInfo* battery_info #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ @@ -179,6 +284,7 @@ static size_t CreateNondiscoverableAdvertisement( NEARBY_ASSERT(length >= kHeaderSize); unsigned i = 1; + uint8_t salt[NEARBY_FP_SALT_SIZE]; // service data UUID output[i++] = GAP_DATA_TYPE_SERVICE_DATA_UUID; @@ -189,29 +295,26 @@ static size_t CreateNondiscoverableAdvertisement( i += FP_SERVICE_UUID_SIZE; // service data - uint8_t salt = nearby_platform_Rand(); - size_t n = nearby_fp_GetAccountKeyCount(); + for (int si = 0; si < NEARBY_FP_SALT_SIZE; si++) + salt[si] = nearby_platform_Rand(); + size_t n = nearby_fp_GetUniqueAccountKeyCount(); if (n == 0) { const unsigned kMessageSize = 2; NEARBY_ASSERT(length >= i + kMessageSize); output[i++] = 0x00; output[i++] = 0x00; } else { - const unsigned kFlagFilterAndSaltSize = 4; + const unsigned kFlagFilterAndSaltSize = 3 + NEARBY_FP_SALT_SIZE; const size_t s = (6 * n + 15) / 5; const size_t kAccountKeyDataSize = s + kFlagFilterAndSaltSize; NEARBY_ASSERT(length >= i + kAccountKeyDataSize); - unsigned used_key_and_salt_size = ACCOUNT_KEY_SIZE_BYTES + SALT_SIZE_BYTES; -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION // We need to add the battery info first because it's used as salt if (battery_info != NULL) { uint8_t battery_chunk_offset = i + kAccountKeyDataSize; uint8_t* battery_chunk = output + battery_chunk_offset; - used_key_and_salt_size += BATTERY_INFO_SIZE_BYTES; AddBatteryInfo(battery_chunk, length - battery_chunk_offset, show_battery_indicator, battery_info); - memcpy(key_and_salt + ACCOUNT_KEY_SIZE_BYTES + SALT_SIZE_BYTES, - battery_chunk, BATTERY_INFO_SIZE_BYTES); } #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ // flags @@ -221,21 +324,10 @@ static size_t CreateNondiscoverableAdvertisement( ? SHOW_PAIRING_INDICATION_BYTE : DONT_SHOW_PAIRING_INDICATION_BYTE); memset(output + i, 0, s); - key_and_salt[ACCOUNT_KEY_SIZE_BYTES] = salt; - unsigned k, j; - for (k = 0; k < n; k++) { - nearby_fp_CopyAccountKey(key_and_salt, k); - nearby_fp_Sha256(sha_buffer, key_and_salt, used_key_and_salt_size); - for (j = 0; j < 8; j++) { - uint32_t x = nearby_utils_GetBigEndian32(sha_buffer + 4 * j); - uint32_t m = x % (s * 8); - output[i + (m / 8)] |= (1 << (m % 8)); - } - } i += s; output[i++] = SALT_FIELD_LENGTH_AND_TYPE_BYTE; - output[i++] = salt; -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + for (int si = 0; si < NEARBY_FP_SALT_SIZE; si++) output[i++] = salt[si]; +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION if (battery_info != NULL) { i += BATTERY_INFO_SIZE_BYTES; } @@ -250,7 +342,7 @@ static size_t CreateNondiscoverableAdvertisement( size_t nearby_fp_CreateNondiscoverableAdvertisement( uint8_t* output, size_t length, bool show_pairing_indicator) { -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION return CreateNondiscoverableAdvertisement( output, length, show_pairing_indicator, false, NULL); #else @@ -259,10 +351,11 @@ size_t nearby_fp_CreateNondiscoverableAdvertisement( #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ } -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION // |battery_info| can be NULL size_t nearby_fp_CreateNondiscoverableAdvertisementWithBattery( uint8_t* output, size_t length, bool show_pairing_indicator, + bool show_battery_indicator, const nearby_platform_BatteryInfo* battery_info) { return CreateNondiscoverableAdvertisement( @@ -271,18 +364,94 @@ size_t nearby_fp_CreateNondiscoverableAdvertisementWithBattery( } #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ +static const uint8_t* FindBatteryInfoLt(const uint8_t* advertisement) { + const uint8_t* battery_info = nearby_fp_FindLtv( + advertisement, BATTERY_INFO_WITH_UI_INDICATION_FIELD_TYPE); + if (battery_info == NULL) { + battery_info = nearby_fp_FindLtv( + advertisement, BATTERY_INFO_WITHOUT_UI_INDICATION_FIELD_TYPE); + } + return battery_info; +} + +size_t nearby_fp_SetBloomFilter(uint8_t* advertisement, bool use_sass_format, + const uint8_t* in_use_key) { + unsigned key_offset = 0; + if (advertisement[ACCOUNT_KEY_DATA_OFFSET] == 0) { + NEARBY_TRACE(INFO, "Empty account key filter"); + return 0; + } + // Salt is mandatory and is included in the calculation without the LT header + const uint8_t* salt_field = nearby_fp_FindLtv(advertisement, SALT_FIELD_TYPE); + NEARBY_ASSERT(salt_field != NULL); + const uint8_t* salt = salt_field + LTV_HEADER_SIZE; + int salt_length = GetLtLength(*salt_field); + // Battery info is optional and is included in the calculation with the LT + // header + const uint8_t* battery_info_field = FindBatteryInfoLt(advertisement); + int battery_info_field_length = + battery_info_field != NULL + ? GetLtLength(*battery_info_field) + LTV_HEADER_SIZE + : 0; + // Random resolvable field is optional and is included in the calculation with + // the LT header + const uint8_t* random_resolvable_field = + nearby_fp_FindLtv(advertisement, RANDOM_RESOLVABLE_FIELD_TYPE); + int random_resolvable_field_length = + random_resolvable_field != NULL + ? GetLtLength(*random_resolvable_field) + LTV_HEADER_SIZE + : 0; + const size_t n = nearby_fp_GetUniqueAccountKeyCount(); + const size_t s = (6 * n + 15) / 5; + NEARBY_ASSERT(s == GetLtLength(advertisement[ACCOUNT_KEY_DATA_OFFSET])); + uint8_t* output = advertisement + ACCOUNT_KEY_DATA_OFFSET + LTV_HEADER_SIZE; + memset(output, 0, s); + for (size_t k = 0; k < n; k++) { + key_offset = nearby_fp_GetNextUniqueAccountKeyIndex(key_offset); + NEARBY_ASSERT(key_offset >= 0); + const uint8_t* key = nearby_fp_GetAccountKey(key_offset)->account_key; + uint8_t flags = key[0]; + if (use_sass_format) { + if (in_use_key != NULL) { + if (!memcmp(key, in_use_key, ACCOUNT_KEY_SIZE_BYTES)) { + flags |= IN_USE_ACCOUNT_KEY_BIT; + } + } else if (k == 0) { + // The first key is the most recently used one + flags |= MOST_RECENTLY_USED_ACCOUNT_KEY_BIT; + } + } + key_offset++; + nearby_platform_Sha256Start(); + nearby_platform_Sha256Update(&flags, sizeof(flags)); + nearby_platform_Sha256Update(key + sizeof(flags), + ACCOUNT_KEY_SIZE_BYTES - sizeof(flags)); + nearby_platform_Sha256Update(salt, salt_length); + nearby_platform_Sha256Update(battery_info_field, battery_info_field_length); + nearby_platform_Sha256Update(random_resolvable_field, + random_resolvable_field_length); + nearby_platform_Sha256Finish(sha_buffer); + for (unsigned j = 0; j < 8; j++) { + uint32_t x = nearby_utils_GetBigEndian32(sha_buffer + 4 * j); + uint32_t m = x % (s * 8); + output[m / 8] |= (1 << (m % 8)); + } + } + if (use_sass_format) { + advertisement[HEADER_OFFSET] = SASS_HEADER; + } + return s; +} + size_t nearby_fp_AppendTxPower(uint8_t* advertisement, size_t length, int8_t tx_power) { size_t offset = 0; NEARBY_ASSERT(length >= 1 + TX_POWER_DATA_SIZE); - // tx power level data size advertisement[offset++] = TX_POWER_DATA_SIZE; - // tx power level UUID advertisement[offset++] = GAP_DATA_TYPE_TX_POWER_LEVEL_UUID; - // tx power level advertisement[offset++] = tx_power; @@ -291,13 +460,14 @@ size_t nearby_fp_AppendTxPower(uint8_t* advertisement, size_t length, nearby_platform_status nearby_fp_LoadAccountKeys() { size_t length = sizeof(account_key_list); - memset(account_key_list, 0, length); - return nearby_platform_LoadValue(kStoredKeyAccountKeyList, account_key_list, - &length); + memset(&account_key_list, 0, length); + return nearby_platform_LoadValue(kStoredKeyAccountKeyList, + (uint8_t*)&account_key_list, &length); } nearby_platform_status nearby_fp_SaveAccountKeys() { - return nearby_platform_SaveValue(kStoredKeyAccountKeyList, account_key_list, + return nearby_platform_SaveValue(kStoredKeyAccountKeyList, + (uint8_t*)&account_key_list, GetAccountKeyListUsedSize()); } @@ -327,51 +497,107 @@ nearby_platform_status nearby_fp_CreateSharedSecret( } nearby_platform_status nearby_fp_CreateRawKeybasedPairingResponse( - uint8_t output[AES_MESSAGE_SIZE_BYTES]) { - output[0] = KEY_BASED_PAIRING_RESPONSE_FLAG; - nearby_utils_CopyBigEndian(output + 1, nearby_platform_GetPublicAddress(), + uint8_t output[AES_MESSAGE_SIZE_BYTES], bool extended_response) { + int i = 0; + uint64_t secondary_address = 0; + if (extended_response) { + secondary_address = nearby_platform_GetSecondaryPublicAddress(); + output[i++] = KEY_BASED_PAIRING_EXTENDED_RESPONSE_FLAG; + uint8_t flags = 0; +#if NEARBY_FP_BLE_ONLY + flags |= 0x80; +#endif /* NEARBY_FP_BLE_ONLY */ +#if NEARBY_FP_PREFER_BLE_BONDING + flags |= 0x40; +#endif /* NEARBY_FP_PREFER_BLE_BONDING */ +#if NEARBY_FP_PREFER_LE_TRANSPORT + flags |= 0x20; +#endif /* NEARBY_FP_PREFER_LE_TRANSPORT */ + output[i++] = flags; + // Number of the Provider’s addresses. The number is either 1 or 2. + output[i++] = secondary_address != 0 ? 2 : 1; + } else { + output[i++] = KEY_BASED_PAIRING_RESPONSE_FLAG; + } + nearby_utils_CopyBigEndian(&output[i], nearby_platform_GetPublicAddress(), BT_ADDRESS_LENGTH); - int i; - for (i = 1 + BT_ADDRESS_LENGTH; i < AES_MESSAGE_SIZE_BYTES; i++) { + i += BT_ADDRESS_LENGTH; + if (secondary_address != 0) { + nearby_utils_CopyBigEndian(&output[i], secondary_address, + BT_ADDRESS_LENGTH); + i += BT_ADDRESS_LENGTH; + } + for (; i < AES_MESSAGE_SIZE_BYTES; i++) { output[i] = nearby_platform_Rand(); } return kNearbyStatusOK; } -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA -#define HMAC_SHA256_KEY_SIZE 64 #define OPAD 0x5C #define IPAD 0x36 #define NONCE_SIZE 8 -#define ADDITIONAL_DATA_SHA_SIZE 8 static void PadKey(uint8_t output[HMAC_SHA256_KEY_SIZE], const uint8_t* key, size_t key_length, uint8_t pad) { - int i; + size_t i; for (i = 0; i < key_length; i++) { *output++ = *key++ ^ pad; } memset(output, pad, HMAC_SHA256_KEY_SIZE - key_length); } -#define RETURN_IF_ERROR(X) \ - do { \ - nearby_platform_status status = X; \ - if (kNearbyStatusOK != status) return status; \ - } while (0) - -static nearby_platform_status HmacSha256(uint8_t out[32], +static nearby_platform_status HmacSha256(uint8_t out[SHA256_KEY_SIZE], uint8_t hmac_key[HMAC_SHA256_KEY_SIZE], const uint8_t* data, size_t data_length) { RETURN_IF_ERROR(nearby_platform_Sha256Start()); RETURN_IF_ERROR(nearby_platform_Sha256Update(hmac_key, HMAC_SHA256_KEY_SIZE)); RETURN_IF_ERROR(nearby_platform_Sha256Update(data, data_length)); + return nearby_platform_Sha256Finish(out); +} + +static nearby_platform_status HmacSha256WithNonce( + uint8_t out[32], uint8_t hmac_key[HMAC_SHA256_KEY_SIZE], + const uint8_t session_nonce[SESSION_NONCE_SIZE], + const uint8_t message_nonce[SESSION_NONCE_SIZE], const uint8_t* data, + size_t data_length) { + RETURN_IF_ERROR(nearby_platform_Sha256Start()); + RETURN_IF_ERROR(nearby_platform_Sha256Update(hmac_key, HMAC_SHA256_KEY_SIZE)); + RETURN_IF_ERROR( + nearby_platform_Sha256Update(session_nonce, SESSION_NONCE_SIZE)); + RETURN_IF_ERROR( + nearby_platform_Sha256Update(message_nonce, SESSION_NONCE_SIZE)); + RETURN_IF_ERROR(nearby_platform_Sha256Update(data, data_length)); RETURN_IF_ERROR(nearby_platform_Sha256Finish(out)); return kNearbyStatusOK; } -nearby_platform_status nearby_fp_HmacSha256(uint8_t out[32], const uint8_t* key, +nearby_platform_status nearby_fp_HmacSha256Start( + nearby_fp_HmacSha256Context* context, const uint8_t* key, + size_t key_length) { + // out = HASH(Key XOR ipad, data) + PadKey(context->hmac_key, key, key_length, IPAD); + RETURN_IF_ERROR(nearby_platform_Sha256Start()); + return nearby_platform_Sha256Update(context->hmac_key, HMAC_SHA256_KEY_SIZE); +} + +nearby_platform_status nearby_fp_HmacSha256Update(const uint8_t* data, + size_t data_length) { + return nearby_platform_Sha256Update(data, data_length); +} + +nearby_platform_status nearby_fp_HmacSha256Finish( + nearby_fp_HmacSha256Context* context, const uint8_t* key, + size_t key_length) { + RETURN_IF_ERROR(nearby_platform_Sha256Finish(context->hash)); + // out = HASH(Key XOR opad, out) + PadKey(context->hmac_key, key, key_length, OPAD); + return HmacSha256(context->hash, context->hmac_key, context->hash, + SHA256_KEY_SIZE); +} + +nearby_platform_status nearby_fp_HmacSha256(uint8_t out[SHA256_KEY_SIZE], + const uint8_t* key, size_t key_length, const uint8_t* data, size_t data_length) { @@ -381,7 +607,52 @@ nearby_platform_status nearby_fp_HmacSha256(uint8_t out[32], const uint8_t* key, RETURN_IF_ERROR(HmacSha256(out, hmac_key, data, data_length)); // out = HASH(Key XOR opad, out) PadKey(hmac_key, key, key_length, OPAD); - return HmacSha256(out, hmac_key, out, 32); + return HmacSha256(out, hmac_key, out, SHA256_KEY_SIZE); +} + +nearby_platform_status nearby_fp_HkdfExtractSha256(uint8_t out[SHA256_KEY_SIZE], + const uint8_t* salt, + size_t salt_length, + const uint8_t* ikm, + size_t ikm_length) { + return nearby_fp_HmacSha256(out, salt, salt_length, ikm, ikm_length); +} + +nearby_platform_status nearby_fp_HkdfExpandSha256( + uint8_t* out, size_t out_length, const uint8_t* prk, size_t prk_length, + const uint8_t* info, size_t info_length) { + nearby_fp_HmacSha256Context context; + size_t offset = 0; + uint8_t chunk_number = 1; + while (offset < out_length) { + RETURN_IF_ERROR(nearby_fp_HmacSha256Start(&context, prk, prk_length)); + if (offset != 0) { + RETURN_IF_ERROR( + nearby_fp_HmacSha256Update(context.hash, SHA256_KEY_SIZE)); + } + RETURN_IF_ERROR(nearby_fp_HmacSha256Update(info, info_length)); + RETURN_IF_ERROR( + nearby_fp_HmacSha256Update(&chunk_number, sizeof(chunk_number))); + RETURN_IF_ERROR(nearby_fp_HmacSha256Finish(&context, prk, prk_length)); + size_t space_left = out_length - offset; + size_t bytes_to_copy = + space_left < SHA256_KEY_SIZE ? space_left : SHA256_KEY_SIZE; + memcpy(out + offset, context.hash, bytes_to_copy); + offset += bytes_to_copy; + ++chunk_number; + } + return kNearbyStatusOK; +} + +static nearby_platform_status GetRrdKey( + uint8_t out[AES_MESSAGE_SIZE_BYTES], + const uint8_t account_key[ACCOUNT_KEY_SIZE_BYTES]) { + uint8_t prk[SHA256_KEY_SIZE]; + RETURN_IF_ERROR(nearby_fp_HkdfExtractSha256(prk, NULL, 0, account_key, + ACCOUNT_KEY_SIZE_BYTES)); + return nearby_fp_HkdfExpandSha256(out, AES_MESSAGE_SIZE_BYTES, prk, + sizeof(prk), kSassRrdKey, + sizeof(kSassRrdKey)); } nearby_platform_status nearby_fp_AesCtr( @@ -394,8 +665,8 @@ nearby_platform_status nearby_fp_AesCtr( memcpy(iv + AES_MESSAGE_SIZE_BYTES - NONCE_SIZE, message, NONCE_SIZE); while (offset < message_length) { - int i; - int bytes_left = message_length - offset; + unsigned i; + unsigned bytes_left = message_length - offset; RETURN_IF_ERROR(nearby_platform_Aes128Encrypt(iv, key_stream, key)); for (i = 0; i < bytes_left && i < sizeof(key_stream); i++) { message[offset++] ^= key_stream[i]; @@ -405,6 +676,8 @@ nearby_platform_status nearby_fp_AesCtr( return kNearbyStatusOK; } +#if NEARBY_FP_ENABLE_ADDITIONAL_DATA +#define ADDITIONAL_DATA_SHA_SIZE 8 nearby_platform_status nearby_fp_DecodeAdditionalData( uint8_t* data, size_t length, const uint8_t key[ACCOUNT_KEY_SIZE_BYTES]) { NEARBY_ASSERT(length > ADDITIONAL_DATA_SHA_SIZE + NONCE_SIZE); @@ -414,6 +687,12 @@ nearby_platform_status nearby_fp_DecodeAdditionalData( length - ADDITIONAL_DATA_SHA_SIZE)); if (memcmp(sha, data, ADDITIONAL_DATA_SHA_SIZE) != 0) { NEARBY_TRACE(WARNING, "Additional Data SHA check failed"); + NEARBY_TRACE(WARNING, "SHA: %s", + nearby_utils_ArrayToString(sha, sizeof(sha))); + NEARBY_TRACE( + WARNING, "key: %s", + nearby_utils_ArrayToString(key, sizeof(ACCOUNT_KEY_SIZE_BYTES))); + NEARBY_TRACE(WARNING, "data: %s", nearby_utils_ArrayToString(data, length)); return kNearbyStatusError; } return nearby_fp_AesCtr(data + ADDITIONAL_DATA_SHA_SIZE, @@ -450,3 +729,105 @@ nearby_platform_status nearby_fp_Sha256(uint8_t out[32], const void* data, } return status; } + +uint8_t nearby_fp_GetSassConnectionState() { + return (BOOL_TO_INT(nearby_platform_OnHead()) + << SASS_CONN_STATE_ON_HEAD_OFFSET) | + (BOOL_TO_INT(nearby_platform_CanAcceptConnection()) + << SASS_CONN_STATE_AVAIL_OFFSET) | + (BOOL_TO_INT(nearby_platform_InFocusMode()) + << SASS_CONN_STATE_FOCUS_OFFSET) | + (BOOL_TO_INT(nearby_platform_AutoReconnected()) + << SASS_CONN_STATE_AUTO_RECONNECTED_OFFSET) | + (nearby_platform_GetAudioConnectionState() & SASS_CONN_STATE_MASK); +} + +size_t nearby_fp_GenerateSassAdvertisement( + uint8_t* advertisement, size_t length, uint8_t connection_state, + uint8_t custom_data, const uint8_t* devices_bitmap, size_t bitmap_length) { + size_t advert_size = MIN_SASS_ADERTISEMENT_SIZE + bitmap_length; + NEARBY_ASSERT(length >= advert_size); + size_t offset = 0; + advertisement[offset++] = combineNibbles(advert_size - SASS_HEADER_SIZE, + SASS_ADVERTISEMENT_FIELD_TYPE); + advertisement[offset++] = connection_state; + advertisement[offset++] = custom_data; + memcpy(advertisement + offset, devices_bitmap, bitmap_length); + return advert_size; +} + +nearby_platform_status nearby_fp_EncryptRandomResolvableField( + uint8_t* data, size_t length, const uint8_t key[AES_MESSAGE_SIZE_BYTES], + const uint8_t* salt_field) { + NEARBY_ASSERT(length <= AES_MESSAGE_SIZE_BYTES + RRF_HEADER_SIZE); + uint8_t iv[AES_MESSAGE_SIZE_BYTES]; + uint8_t rrd_key[AES_MESSAGE_SIZE_BYTES]; + memset(iv, 0, sizeof(iv)); + if (salt_field != NULL) { + int salt_length = GetLtLength(salt_field[0]); + memcpy(iv, salt_field + LTV_HEADER_SIZE, salt_length); + } + RETURN_IF_ERROR(GetRrdKey(rrd_key, key)); + RETURN_IF_ERROR(nearby_platform_Aes128Encrypt(iv, iv, rrd_key)); + data[0] = + combineNibbles(length - RRF_HEADER_SIZE, RANDOM_RESOLVABLE_FIELD_TYPE); + for (size_t i = RRF_HEADER_SIZE; i < length; i++) { + data[i] ^= iv[i - RRF_HEADER_SIZE]; + } + return kNearbyStatusOK; +} + +nearby_platform_status nearby_fp_AesEncryptIv( + uint8_t* data, size_t length, uint8_t iv[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { + NEARBY_ASSERT(length <= AES_MESSAGE_SIZE_BYTES); + RETURN_IF_ERROR(nearby_platform_Aes128Encrypt(iv, iv, key)); + for (size_t i = 0; i < length; i++) { + data[i] ^= iv[i]; + } + return kNearbyStatusOK; +} + +uint16_t nearby_fp_GetSassCapabilityFlags() { + return (BOOL_TO_INT(nearby_platform_IsSassOn()) << SASS_CF_ON_OFFSET) | + (BOOL_TO_INT(nearby_platform_IsMultipointConfigurable()) + << SASS_CF_MULTIPOINT_CONFIGURABLE) | + (BOOL_TO_INT(nearby_platform_IsMultipointOn()) + << SASS_CF_MULTIPOINT_ON) | + (BOOL_TO_INT(nearby_platform_IsOnHeadDetectionSupported()) + << SASS_CF_OHD_SUPPORTED) | + (BOOL_TO_INT(nearby_platform_IsOnHeadDetectionEnabled()) + << SASS_CF_OHD_ENABLED); +} + +nearby_platform_status nearby_fp_VerifyMessageAuthenticationCode( + const uint8_t* message, size_t length, + const uint8_t key[ACCOUNT_KEY_SIZE_BYTES], + const uint8_t session_nonce[SESSION_NONCE_SIZE]) { + if (length <= SESSION_NONCE_SIZE - MESSAGE_AUTHENTICATION_CODE_SIZE) { + return kNearbyStatusInvalidInput; + } + size_t data_length = + length - SESSION_NONCE_SIZE - MESSAGE_AUTHENTICATION_CODE_SIZE; + const uint8_t* message_nonce = message + data_length; + uint8_t hmac_key[HMAC_SHA256_KEY_SIZE]; + uint8_t out[32]; + // out = HASH(Key XOR ipad, session nonce + message nonce + data) + PadKey(hmac_key, key, ACCOUNT_KEY_SIZE_BYTES, IPAD); + RETURN_IF_ERROR(HmacSha256WithNonce(out, hmac_key, session_nonce, + message_nonce, message, data_length)); + // out = HASH(Key XOR opad, out) + PadKey(hmac_key, key, ACCOUNT_KEY_SIZE_BYTES, OPAD); + RETURN_IF_ERROR(HmacSha256(out, hmac_key, out, 32)); + const uint8_t* mac = message + data_length + SESSION_NONCE_SIZE; + if (memcmp(out, mac, MESSAGE_AUTHENTICATION_CODE_SIZE)) { + NEARBY_TRACE(INFO, + "Expected MAC " + "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x", + out[0], out[1], out[2], out[3], out[4], out[5], out[6], + out[7]); + return kNearbyStatusInvalidInput; + } else { + return kNearbyStatusOK; + } +} diff --git a/embedded/common/source/nearby_fp_library.h b/embedded/common/source/nearby_fp_library.h index 2e2762397c..6e44a930cf 100644 --- a/embedded/common/source/nearby_fp_library.h +++ b/embedded/common/source/nearby_fp_library.h @@ -20,7 +20,8 @@ // clang-format on #include "nearby.h" -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#include "nearby_message_stream.h" +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION #include "nearby_platform_battery.h" #endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ @@ -30,13 +31,30 @@ extern "C" { #define BT_ADDRESS_LENGTH 6 #define DISCOVERABLE_ADV_SIZE_BYTES 10 -// Sufficient for 5 account keys and battery notification -#define NON_DISCOVERABLE_ADV_SIZE_BYTES 24 +#ifdef NEARBY_FP_ENABLE_SASS +// Sufficent for 5 account keys, battery notification and SASS info +#define NON_DISCOVERABLE_ADV_SIZE_BYTES 32 +#else +// Sufficent for 5 account keys and battery notification +#define NON_DISCOVERABLE_ADV_SIZE_BYTES 25 +#endif /* NEARBY_FP_ENABLE_SASS */ #define ADDITIONAL_DATA_HEADER_SIZE 16 +#define HMAC_SHA256_KEY_SIZE 64 +#define SHA256_KEY_SIZE 32 -// Creates advertisement with the Model Id. Returns the number of bytes written -// to |output|. +// Random Resolvable Field header size +#define RRF_HEADER_SIZE 1 + +// Field types in the non-discoverable advertisement +#define SALT_FIELD_TYPE 1 +#define BATTERY_INFO_WITH_UI_INDICATION_FIELD_TYPE 3 +#define BATTERY_INFO_WITHOUT_UI_INDICATION_FIELD_TYPE 4 +#define SASS_ADVERTISEMENT_FIELD_TYPE 5 +#define RANDOM_RESOLVABLE_FIELD_TYPE 6 + +// Creates advertisement with the Model Id. Returns the number of bytes +// written to |output|. // // output - Advertisement data output buffer. // length - Length of output buffer. @@ -52,7 +70,7 @@ size_t nearby_fp_CreateDiscoverableAdvertisement(uint8_t* output, size_t nearby_fp_CreateNondiscoverableAdvertisement( uint8_t* output, size_t length, bool show_pairing_indicator); -#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#if NEARBY_FP_ENABLE_BATTERY_NOTIFICATION void SerializeBatteryInfo(uint8_t* output, const nearby_platform_BatteryInfo* battery_info); @@ -62,6 +80,7 @@ void SerializeBatteryInfo(uint8_t* output, // output - Advertisement data output buffer. // length - Length of output buffer. // show_ui_indicator - Ask seeker to show UI indication. +// show_battery_indicator - Ask seeker to show battery indicator // battery_info - Battery status data structure. size_t nearby_fp_CreateNondiscoverableAdvertisementWithBattery( uint8_t* output, size_t length, bool show_pairing_indicator, @@ -87,26 +106,49 @@ nearby_platform_status nearby_fp_SaveAccountKeys(); // // dest - Buffer for key fetched. // key_number - Number of key to fetch. -void nearby_fp_CopyAccountKey(uint8_t* dest, unsigned key_number); +void nearby_fp_CopyAccountKey(nearby_platform_AccountKeyInfo* dest, + unsigned key_number); // Gets number of active keys. size_t nearby_fp_GetAccountKeyCount(); -// Gets offset of key by key number. Returns the offset. -// -// key_number - ordinal number of key to return. -size_t nearby_fp_GetAccountKeyOffset(unsigned key_number); + +// Gets the number of unique account keys. Account keys are duplicated when two +// or more seekers share an account key +size_t nearby_fp_GetUniqueAccountKeyCount(); + +// Returns the index of the next unique account key in [offset..number of keys] +// range, that is a key that wasn't already seen in [0..offset -1] range. +// Returns -1 if not found. +int nearby_fp_GetNextUniqueAccountKeyIndex(int offset); + // Gets pointer to given key by ordinal number. // // key_number - Ordinal number of key to return. -const uint8_t* nearby_fp_GetAccountKey(unsigned key_number); +const nearby_platform_AccountKeyInfo* nearby_fp_GetAccountKey( + unsigned key_number); + // Marks account key as active by moving it to the top of the key list. // // key_number - Original number of key to mark active. void nearby_fp_MarkAccountKeyAsActive(unsigned key_number); + // Adds key to key list. Inserts key to top of list. // // key - Buffer containing key to insert. -void nearby_fp_AddAccountKey(const uint8_t key[ACCOUNT_KEY_SIZE_BYTES]); +void nearby_fp_AddAccountKey(const nearby_platform_AccountKeyInfo* key); + +// Computes the account bloom filter and stores it in the Account Key Filter +// field in the advertisement. Returns the bloom filter size. +// +// `advertisement` is a non-discoverable FP advertisement. It must contain an +// LTV field for Account Key Filter, where the LT header is already set but the +// contents may be unitialized. It must contain an LTV with salt. Battery Info +// field and Random Resolvable field are used in the bloom filter calculation if +// present in the advertisement, When `use_sass_format` is set, the bloom filter +// defined by SASS will be used. +size_t nearby_fp_SetBloomFilter(uint8_t* advertisement, bool use_sass_format, + const uint8_t* in_use_key); + // Gets fast pair model ID // // output - Buffer to hold model ID. @@ -126,16 +168,77 @@ nearby_platform_status nearby_fp_CreateSharedSecret( // // output - Buffer returning the pairing response. nearby_platform_status nearby_fp_CreateRawKeybasedPairingResponse( - uint8_t output[AES_MESSAGE_SIZE_BYTES]); + uint8_t output[AES_MESSAGE_SIZE_BYTES], bool extended_response); + +// Context for calculating HMAC-SHA256 hash +typedef struct { + // The resulting hash after calling `nearby_fp_HmacSha256Finish` + uint8_t hash[SHA256_KEY_SIZE]; + uint8_t hmac_key[HMAC_SHA256_KEY_SIZE]; +} nearby_fp_HmacSha256Context; + +// Starts calculating HMAC-SHA156 hash incrementally. Only one hash calculation +// can be running at a time. +// +// context - Uninitalized context. +// key - Input key to calculate hash. +// key_length - Length of key. +nearby_platform_status nearby_fp_HmacSha256Start( + nearby_fp_HmacSha256Context* context, const uint8_t* key, + size_t key_length); + +// Adds data to the hash. +// +// data - Data to calculate hash. +// data_length - Data length. +nearby_platform_status nearby_fp_HmacSha256Update(const uint8_t* data, + size_t data_length); +// Finishes hash calculation. The parameters must match those passed to +// `nearby_fp_HmacSha256Start`. +// +// context - Context initalized with `nearby_fp_HmacSha256Start`. +// key - Input key to calculate hash. +// key_length - Length of key. +nearby_platform_status nearby_fp_HmacSha256Finish( + nearby_fp_HmacSha256Context* context, const uint8_t* key, + size_t key_length); + +// Implements HKDF-Extract method with SHA256 hash per +// https://www.rfc-editor.org/rfc/rfc5869#section-2.2 +// +// out - output Pseudo Random Key material +// salt - optional salt +// salt_length - salt length in bytes. Set to 0 when `salt is NULL +// ikm - Input Key Material +// ikm_length - IKM length in bytes +nearby_platform_status nearby_fp_HkdfExtractSha256(uint8_t out[SHA256_KEY_SIZE], + const uint8_t* salt, + size_t salt_length, + const uint8_t* ikm, + size_t ikm_length); + +// Implements HKDF-Expand with SHA256 hash per +// https://www.rfc-editor.org/rfc/rfc5869#section-2.3 +// +// out - Output Key Material +// out_length - OKM length in bytes +// prk - Pseudo Random Key +// prk_length - PRK length in bytes +// info - optional context information +// info_length - info length in bytes. Set to 0 when `info` is NULL. +nearby_platform_status nearby_fp_HkdfExpandSha256( + uint8_t* out, size_t out_length, const uint8_t* prk, size_t prk_length, + const uint8_t* info, size_t info_length); + +const uint8_t* nearby_fp_FindLtv(const uint8_t* advertisement, int type); -#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA // Calculates HMAC SHA256 hash. // // out - Output buffer for hash. // key - Input key to calculate hash. // key_length - Length of key. // data - Data to calculate hash. -// data_lenth - Data length. +// data_length - Data length. nearby_platform_status nearby_fp_HmacSha256(uint8_t out[32], const uint8_t* key, size_t key_length, const uint8_t* data, @@ -151,6 +254,14 @@ nearby_platform_status nearby_fp_AesCtr( uint8_t* message, size_t message_length, const uint8_t key[AES_MESSAGE_SIZE_BYTES]); +// Encrypts |data| in place: data = data ^ AES(key, iv). The data must +// be shorter than AES_MESSAGE_SIZE_BYTES. +// Side effect: |iv| contents are destroyed. +nearby_platform_status nearby_fp_AesEncryptIv( + uint8_t* data, size_t length, uint8_t iv[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]); + +#if NEARBY_FP_ENABLE_ADDITIONAL_DATA // Decodes data package read from Additional Data characteristics. The decoding // happens in-place. Returns an error if HMAC-SHA checksum is invalid. // @@ -180,6 +291,25 @@ nearby_platform_status nearby_fp_EncodeAdditionalData( nearby_platform_status nearby_fp_Sha256(uint8_t out[32], const void* data, size_t length); +uint8_t nearby_fp_GetSassConnectionState(); + +size_t nearby_fp_GenerateSassAdvertisement( + uint8_t* advertisement, size_t length, uint8_t connection_state, + uint8_t custom_data, const uint8_t* devices_bitmap, size_t bitmap_length); + +uint16_t nearby_fp_GetSassCapabilityFlags(); + +nearby_platform_status nearby_fp_VerifyMessageAuthenticationCode( + const uint8_t* message, size_t length, + const uint8_t key[ACCOUNT_KEY_SIZE_BYTES], + const uint8_t session_nonce[SESSION_NONCE_SIZE]); + +// Encrypts Random Resolvable Field in place. The payload must start +// at RRF_HEADER_SIZE offset in the |data| buffer. |salt_field| is a +// part of non-discoverable advertisement +nearby_platform_status nearby_fp_EncryptRandomResolvableField( + uint8_t* data, size_t length, const uint8_t key[AES_MESSAGE_SIZE_BYTES], + const uint8_t* salt_field); #ifdef __cplusplus } #endif diff --git a/embedded/common/source/nearby_message_stream.c b/embedded/common/source/nearby_message_stream.c index 19d64d693e..62da1fc6ce 100644 --- a/embedded/common/source/nearby_message_stream.c +++ b/embedded/common/source/nearby_message_stream.c @@ -23,7 +23,7 @@ #define ACK_CODE 1 #define NACK_CODE 2 -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM void nearby_message_stream_Init(const nearby_message_stream_State* state) { nearby_message_stream_Metadata* metadata = (nearby_message_stream_Metadata*)state->buffer; diff --git a/embedded/common/source/nearby_message_stream.h b/embedded/common/source/nearby_message_stream.h index 58d4e1e107..fb595f6b57 100644 --- a/embedded/common/source/nearby_message_stream.h +++ b/embedded/common/source/nearby_message_stream.h @@ -30,6 +30,7 @@ extern "C" { #define ACK_CODE 1 #define NACK_CODE 2 +#define FAIL_REASON_INVALID_MAC 3 #define FAIL_REASON_REDUNDANT_DEVICE_ACTION 4 // Message sent and received over the message stream @@ -63,7 +64,7 @@ typedef struct { uint16_t bytes_read; } nearby_message_stream_Metadata; -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM // Initializes the parser void nearby_message_stream_Init(const nearby_message_stream_State* state); diff --git a/embedded/common/target/nearby.h b/embedded/common/target/nearby.h index 9293618229..79bbf15fd0 100644 --- a/embedded/common/target/nearby.h +++ b/embedded/common/target/nearby.h @@ -17,6 +17,10 @@ #include "nearby_types.h" +// clang-format off +#include "nearby_config.h" +// clang-format on + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -24,7 +28,19 @@ extern "C" { #define AES_MESSAGE_SIZE_BYTES 16 #define FP_SERVICE_UUID_SIZE 2 #define FP_MODEL_ID_SIZE 3 +// Nearby stores BT addresses as uint64_t values +#define FP_BT_ADDRESS_SIZE sizeof(uint64_t) #define ACCOUNT_KEY_SIZE_BYTES AES_MESSAGE_SIZE_BYTES +#define SESSION_NONCE_SIZE 8 + +typedef struct { + uint8_t account_key[ACCOUNT_KEY_SIZE_BYTES]; +#ifdef NEARBY_FP_ENABLE_SASS + uint64_t peer_address; +#endif /* NEARBY_FP_ENABLE_SASS */ +} nearby_platform_AccountKeyInfo; + +#define NEARBY_FP_ACCOUNT_KEY_INFO_SIZE sizeof(nearby_platform_AccountKeyInfo) typedef enum { kNearbyStatusOK = 0, diff --git a/embedded/common/target/nearby_platform_audio.h b/embedded/common/target/nearby_platform_audio.h index d7ebdbc278..5b26e7d0e2 100644 --- a/embedded/common/target/nearby_platform_audio.h +++ b/embedded/common/target/nearby_platform_audio.h @@ -8,12 +8,187 @@ extern "C" { #endif /* __cplusplus */ +#define NEARBY_PLATFORM_CONNECTION_STATE_NO_CONNECTION 0 +#define NEARBY_PLATFORM_CONNECTION_STATE_PAGING 1 +#define NEARBY_PLATFORM_CONNECTION_STATE_NO_DATA_TRANFER 2 +#define NEARBY_PLATFORM_CONNECTION_STATE_NON_AUDIO_DATA_TRANSFER 3 +#define NEARBY_PLATFORM_CONNECTION_STATE_A2DP 4 +#define NEARBY_PLATFORM_CONNECTION_STATE_A2DP_WITH_AVRCP 5 +#define NEARBY_PLATFORM_CONNECTION_STATE_HFP 6 +#define NEARBY_PLATFORM_CONNECTION_STATE_LE_MEDIA_WITHOUT_CONTROL 7 +#define NEARBY_PLATFORM_CONNECTION_STATE_LE_MEDIA_WITH_CONTROL 8 +#define NEARBY_PLATFORM_CONNECTION_STATE_LE_CALL 9 +#define NEARBY_PLATFORM_CONNECTION_STATE_LE_BIS 10 +#define NEARBY_PLATFORM_CONNECTION_STATE_DISABLED_CONNECTION_SWITCH 15 + +// Multipoint switching preference flag bits. Every bit represents "new profile +// request" vs "current profile request". 1 - preference for switching, 0 - +// preference for not switching +#define NEARBY_SASS_SWITCHING_PREFERENCE_A2DP_VS_A2DP_MASK (1 << 7) +#define NEARBY_SASS_SWITCHING_PREFERENCE_HFP_VS_HFP_MASK (1 << 6) +#define NEARBY_SASS_SWITCHING_PREFERENCE_A2DP_VS_HFP_MASK (1 << 5) +#define NEARBY_SASS_SWITCHING_PREFERENCE_HFP_VS_A2DP_MASK (1 << 4) + +// Flags for SwitchActiveAudioSource +// 1 switch to this device, 0 switch to second connected device +#define NEARBY_SASS_SWITCH_ACTIVE_AUDIO_SOURCE_THIS_DEVICE_MASK (1 << 7) +// Resume playing on switch to device after switching, 0 otherwise. Resuming +// playing means the Provider sends a PLAY notification to the Seeker through +// AVRCP profile. If the previous state (before switched away) was not PLAY, the +// Provider should ignore this flag. +#define NEARBY_SASS_SWITCH_ACTIVE_AUDIO_SOURCE_RESUME_MASK (1 << 6) +// 1 reject SCO on switched away device, 0 otherwise +#define NEARBY_SASS_SWITCH_ACTIVE_AUDIO_SOURCE_REJECT_SCO_MASK (1 << 5) +// 1 disconnect Bluetooth on switch away device, 0 otherwise. +#define NEARBY_SASS_SWITCH_ACTIVE_AUDIO_SOURCE_DISCONNECT_PREVIOUS_MASK (1 << 4) + +// Flags for SwitchBackAudioSource. Note that the flags below are not bitmasks. +#define NEARBY_SASS_SWITCH_BACK 0x01 +#define NEARBY_SASS_SWITCH_BACK_AND_RESUME 0x02 + +// Flags for NotifySassInitiatedConnection. Note that the flags below are not +// bitmasks. +#define NEARBY_SASS_CONNECTION_INITIATED_BY_SASS 0 +#define NEARBY_SASS_CONNECTION_NOT_INITIATED_BY_SASS 1 + +// Reasons for |on_multipoint_switch_event| +#define NEARBY_SASS_MULTIPOINT_SWITCH_REASON_UNKNOWN 0 +#define NEARBY_SASS_MULTIPOINT_SWITCH_REASON_A2DP 1 +#define NEARBY_SASS_MULTIPOINT_SWITCH_REASON_HFP 2 + // Returns true if right earbud is active bool nearby_platform_GetEarbudRightStatus(); // Returns true if left earbud is active bool nearby_platform_GetEarbudLeftStatus(); +typedef struct { + // The platform HAL should call this callback on audio state changes. That is, + // when any of the query methods would return a new value. + void (*on_state_change)(); + // This event should be called after a multipoint audio source switch. To make + // users aware of a multipoint-switch event taking place, SASS Seekers may + // show a notification to users. So the Provider should notify connected SASS + // Seekers about the switching event. + // |reason| is one of NEARBY_SASS_MULTIPOINT_SWITCH_REASON_* constants + // |peer_address| is the address the new audio source + // |name| is utf-8 encoded name of the new audio source or NULL if name is not + // known. + void (*on_multipoint_switch_event)(uint8_t reason, uint64_t peer_address, + const char* name); +} nearby_platform_AudioCallbacks; + +// Returns one of NEARBY_PLATFORM_CONNECTION_STATE_* values +// Call |nearby_platform_AudioCallbacks::on_state_change| when this state +// changes. +unsigned int nearby_platform_GetAudioConnectionState(); + +// Returns true if the device is on head (or in ear). +// Call |nearby_platform_AudioCallbacks::on_state_change| when on-head state +// changes. +bool nearby_platform_OnHead(); + +// Returns true if the device can accept another audio connection without +// dropping any of the existing connections. +// Call |nearby_platform_AudioCallbacks::on_state_change| when this state +// changes. +bool nearby_platform_CanAcceptConnection(); + +// When the device is in focus mode, connection switching is not allowed +// Call |nearby_platform_AudioCallbacks::on_state_change| when this state +// changes. +bool nearby_platform_InFocusMode(); + +// Returns true if the current connection is auto-recconnected, meaning it is +// not connected by the user. For multi-point connections, returns true if any +// of the existing connections is auto-reconnected. +// Call |nearby_platform_AudioCallbacks::on_state_change| when this state +// changes. +bool nearby_platform_AutoReconnected(); + +// Sets a bit in the |bitmap| for every connected peer. The bit stays cleared +// for bonded but not connected peers. The order change is acceptable if it is +// unavoidable, e.g. when users factory reset the headset or when the bonded +// device count reaches the upper limit. +// |length| is the |bitmap| length on input in bytes and used space on output. +// For example, if there are 5 bonded devices, then |length| should be set to 1. +// Call |nearby_platform_AudioCallbacks::on_state_change| when this state +// changes. +void nearby_platform_GetConnectionBitmap(uint8_t* bitmap, size_t* length); + +// Returns true is SASS state in On +bool nearby_platform_IsSassOn(); + +// Returns true if the device supports multipoint and it can be switched between +// on and off +bool nearby_platform_IsMultipointConfigurable(); + +// Returns true is multipoint in On +bool nearby_platform_IsMultipointOn(); + +// Returns true if the device supports OHD (even if it's turned off at the +// moment) +bool nearby_platform_IsOnHeadDetectionSupported(); + +// Returns true if OHD is supported and enabled +bool nearby_platform_IsOnHeadDetectionEnabled(); + +// Enables or disables multipoint +// When |enable| is false, the device should keep the connection with +// |peer_address| and disconnect other connections (if any) +nearby_platform_status nearby_platform_SetMultipoint(uint64_t peer_address, + bool enable); + +// Sets multipoint switching preference flags +nearby_platform_status nearby_platform_SetSwitchingPreference(uint8_t flags); + +// Gets switching preference flags +uint8_t nearby_platform_GetSwitchingPreference(); + +// Switches active audio source (to a connected device). If the flags indicate a +// switch to |peer_address| device but |peer_address| is already the active +// device, then return kNearbyStatusRedundantAction. +// If the flags indicate a switch to another device, then +// |preferred_audio_source| is the address of the next, preferred audio source. +// |preferred_audio_source| is 0 if Nearby SDK cannot figure out what the next +// audio source should be. It may happen if they are no other connected Seekers. +nearby_platform_status nearby_platform_SwitchActiveAudioSource( + uint64_t peer_address, uint8_t flags, uint64_t preferred_audio_source); + +// Switches back to a disconnected audio source. +// |peer_address| is the address of the seeker who sent this command, *not* the +// address of the audio source that the device should switch to. The device +// should connect to the previous, currently disconnected, audio source and +// disconnect from |peer_address|. Ideally, the device should disconnect from +// |peer_address| after returning from this function. This would give Nearby SDK +// a chance to send an ACK message to the seeker. +nearby_platform_status nearby_platform_SwitchBackAudioSource( + uint64_t peer_address, uint8_t flags); + +// Notifies the platform if the connection was initiated by SASS. SASS Providers +// may need to know if the connection switching is triggered by SASS to have +// different reactions, e.g. disable earcons for SASS events. The Seeker sends a +// message to notify the Provider that this connection was a SASS initiated +// connection. +nearby_platform_status nearby_platform_NotifySassInitiatedConnection( + uint64_t peer_address, uint8_t flags); + +// Sets drop connection target +// On multipoint headphones, if the preferred connection to be dropped is not +// the least recently used one, SASS Seekers can tell the Provider which device +// to be dropped by using the message below. +nearby_platform_status nearby_platform_SetDropConnectionTarget( + uint64_t peer_address, uint8_t flags); + +// Returns the BT address of the active audio source +// Call |nearby_platform_AudioCallbacks::on_state_change| when active audio +// source changes. +uint64_t nearby_platform_GetActiveAudioSource(); + +// Initializes Audio module +nearby_platform_status nearby_platform_AudioInit( + const nearby_platform_AudioCallbacks* audio_interface); + #ifdef __cplusplus } #endif diff --git a/embedded/common/target/nearby_platform_ble.h b/embedded/common/target/nearby_platform_ble.h index 50baaa5b92..db5e54e3f6 100644 --- a/embedded/common/target/nearby_platform_ble.h +++ b/embedded/common/target/nearby_platform_ble.h @@ -28,6 +28,11 @@ typedef enum { kAccountKey, // FE2C1236-8366-4814-8EB0-01DE32100BEA (write) kFirmwareRevision, // 0x2A26 kAdditionalData, // FE2C1237-8366-4814-8EB0-01DE32100BEA + // Message Stream PSM characteristic is required to establish an L2CAP + // communication channel between Seeker and Provider. Platforms that don't + // support L2CAP channel may choose not to implement this characteristic. + // By default, the Seeker connect to the Provider using RFCOMM. + kMessageStreamPsm, // FE2C1239-8366-4814-8EB0-01DE32100BEA (read, encrypted) } nearby_fp_Characteristic; typedef enum { @@ -61,6 +66,15 @@ uint64_t nearby_platform_SetBleAddress(uint64_t address); uint64_t nearby_platform_RotateBleAddress(); #endif /* NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION */ +// Gets the PSM - Protocol and Service Mulitplexor - assigned to Fast Pair's +// Message Stream. +// To support Message Stream for BLE devices, Fast Pair will build and maintain +// a BLE L2CAP channel for sending and receiving messages. The PSM can be +// dynamic or fixed. +// Returns a 16 bit PSM number or a negative value on error. When a valid PSM +// number is returned, the device must be ready to accept L2CAP connections. +int32_t nearby_platform_GetMessageStreamPsm(); + // Sends a notification to the connected GATT client. // // peer_address - Address of peer. diff --git a/embedded/common/target/nearby_platform_bt.h b/embedded/common/target/nearby_platform_bt.h index 13f6bc1d1b..5b143d64ac 100644 --- a/embedded/common/target/nearby_platform_bt.h +++ b/embedded/common/target/nearby_platform_bt.h @@ -29,7 +29,9 @@ typedef struct { void (*on_pairing_request)(uint64_t peer_address); void (*on_paired)(uint64_t peer_address); void (*on_pairing_failed)(uint64_t peer_address); -#ifdef NEARBY_FP_MESSAGE_STREAM +#if NEARBY_FP_MESSAGE_STREAM + // Message Stream messages are sent over RFCOMM on BT devices or L2CAP on BLE + // devices. void (*on_message_stream_connected)(uint64_t peer_address); void (*on_message_stream_disconnected)(uint64_t peer_address); void (*on_message_stream_received)(uint64_t peer_address, @@ -43,9 +45,17 @@ uint32_t nearby_platform_GetModelId(); // Returns tx power level. int8_t nearby_platform_GetTxLevel(); -// Returns public BR/EDR address +// Returns public BR/EDR address. +// On a BLE-only device, return the public identity address. uint64_t nearby_platform_GetPublicAddress(); +// Returns the secondary identity address. +// Some devices, such as ear-buds, can advertise two identity addresses. In this +// case, the Seeker pairs with each address separately but treats them as a +// single logical device set. +// Return 0 if this device does not have a secondary identity address. +uint64_t nearby_platform_GetSecondaryPublicAddress(); + // Returns passkey used during pairing uint32_t nearby_platfrom_GetPairingPassKey(); @@ -87,8 +97,9 @@ nearby_platform_status nearby_platform_GetDeviceName(char* name, // Returns true if the device is in pairing mode (either fast-pair or manual). bool nearby_platform_IsInPairingMode(); -#ifdef NEARBY_FP_MESSAGE_STREAM -// Sends message stream through RFCOMM channel initiated by Seeker +#if NEARBY_FP_MESSAGE_STREAM +// Sends message stream through RFCOMM or L2CAP channel initiated by Seeker. +// BT devices should use RFCOMM channel. BLE-only devices should use L2CAP. // // peer_address - Peer address. // message - Contents of message. diff --git a/embedded/common/target/nearby_platform_se.h b/embedded/common/target/nearby_platform_se.h index 1ce6ba7448..d1e4f4e18f 100644 --- a/embedded/common/target/nearby_platform_se.h +++ b/embedded/common/target/nearby_platform_se.h @@ -70,6 +70,12 @@ nearby_platform_status nearby_platform_Aes128Decrypt( nearby_platform_status nearby_platform_GenSec256r1Secret( const uint8_t remote_party_public_key[64], uint8_t secret[32]); +// Returns anti-spoofing 128 bit private key. +// Only used if the implementation also uses the +// nearby_platform_GenSec256r1Secret() routine defined in gen_secret.c. +// Return NULL if not implemented. +const uint8_t* nearby_platform_GetAntiSpoofingPrivateKey(); + // Initializes secure element module nearby_platform_status nearby_platform_SecureElementInit(); diff --git a/embedded/target/gLinux/config.mk b/embedded/target/gLinux/config.mk index 81de763101..f5aa00c54b 100644 --- a/embedded/target/gLinux/config.mk +++ b/embedded/target/gLinux/config.mk @@ -1,3 +1,10 @@ +# Use MBEDTLS for test SSL implementation instead of OPENSSL +# Compiles in mbedtls implementation of a number of functions in se.h +NEARBY_PLATFORM_USE_MBEDTLS ?= 0 +# Use the hardware SE to generate the secp256r1 secret. Alternatively, generate +# the secret in software. +NEARBY_PLATFORM_HAS_SE ?= 1 + CFLAGS_EXTRA ?= CFLAGS += -g \ -MD \ @@ -19,10 +26,22 @@ else CFLAGS += -O0 endif +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +CFLAGS += -DNEARBY_PLATFORM_USE_MBEDTLS +endif + +ifeq ($(NEARBY_PLATFORM_HAS_SE),1) +CFLAGS += -DNEARBY_PLATFORM_HAS_SE +endif + TEST_INCLUDES = \ -I$(GTEST_DIR)/include -I$(GTEST_DIR) \ -I$(GMOCK_DIR)/include -I$(GMOCK_DIR) \ $(CLIENT_INCLUDES) \ -Iclient/tests/ \ -Iclient/tests/$(ARCH)/ \ - -Iclient/tests/mocks/ \ No newline at end of file + -Iclient/tests/mocks/ + +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +TEST_INCLUDES += -I$(MBEDTLS_DIR)/include +endif \ No newline at end of file diff --git a/embedded/target/gLinux/rules.mk b/embedded/target/gLinux/rules.mk index 9c464f14b2..7724f20f00 100644 --- a/embedded/target/gLinux/rules.mk +++ b/embedded/target/gLinux/rules.mk @@ -8,22 +8,34 @@ TEST_SRCS := $(filter-out $(TEST_SRCS_EXCLUDE), $(TEST_SRCS)) TEST_OBJS += $(patsubst %.cc,$(OUT_DIR)/%.o,$(TEST_SRCS)) GLINUX_TARGET_SRCS := $(wildcard client/tests/glinux/*.cc) +MBEDTLS_TARGET_SRCS := $(wildcard common/source/mbedtls/*.c) GTEST_SRCS = $(GTEST_DIR)/src/gtest-all.cc $(GMOCK_DIR)/src/gmock-all.cc EMPTY_TARGET_SRCS := $(wildcard client/tests/empty_target/*.c) GTEST_OBJS := $(patsubst %.cc,$(OUT_DIR)/%.o,$(GTEST_SRCS)) GLINUX_TARGET_OBJS := $(patsubst %.cc,$(OUT_DIR)/%.o,$(GLINUX_TARGET_SRCS)) +MBEDTLS_TARGET_OBJS := $(patsubst %.c,$(OUT_DIR)/%.o,$(MBEDTLS_TARGET_SRCS)) EMPTY_TARGET_OBJS := $(patsubst %.c,$(OUT_DIR)/%.o,$(EMPTY_TARGET_SRCS)) ALL_TEST_OBJS += $(GLINUX_TARGET_OBJS) + ALL_TEST_OBJS += $(TEST_OBJS) ALL_TEST_OBJS += $(GTEST_OBJS) ALL_TEST_OBJS += $(EMPTY_TARGET_OBJS) +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +ALL_TEST_OBJS += $(MBEDTLS_TARGET_OBJS) +endif # The glinux target layer $(GLINUX_TARGET_OBJS) : $(OUT_DIR)/%.o: %.cc $(call compile_c,$(TEST_INCLUDES) -I. -std=c++14 -c $(CFLAGS)) + +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +# Mbed TLS interface files +$(MBEDTLS_TARGET_OBJS) : $(OUT_DIR)/%.o: %.c + $(call compile_c,$(TEST_INCLUDES) -I. -std=c99 -c $(CFLAGS)) +endif $(EMPTY_TARGET_OBJS) : $(OUT_DIR)/%.o: %.c $(info "compiling empty target object $<") @@ -45,16 +57,25 @@ $(TESTS_TO_RUN) : %_run : % ./$< --gtest_output=xml:$(OUT_DIR)/test_logs/$(notdir $<)_logs/sponge_log.xml TARGET_OBJS ?= $(GLINUX_TARGET_OBJS) +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +TARGET_OBJS += $(MBEDTLS_TARGET_OBJS) +endif TARGET_OS_SRCS := $(wildcard client/tests/gLinux/*.cc) +TARGET_MBEDTLS_SRCS += $(wildcard common/source/mbedtls/*.c) TARGET_OS_OBJS ?= $(patsubst %.cc,$(OUT_DIR)/%.o,$(TARGET_OS_SRCS)) +TARGET_MBEDTLS_OBJS ?= $(patsubst %.c,$(OUT_DIR)/%.o,$(TARGET_MBEDTLS_SRCS)) $(TARGET_OS_OBJS) : $(OUT_DIR)/%.o: %.cc $(call compile_c,$(TEST_INCLUDES) -I. -std=c++14 $(CFLAGS)) +$(TARGET_MBED_OBJS) : $(OUT_DIR)/%.o: %.c + $(call compile_c,$(TEST_INCLUDES) -I. -std=c++14 $(CFLAGS)) ALL_TEST_OBJS += $(TEST_OBJS) ALL_TEST_OBJS += $(TARGET_OS_OBJS) +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +ALL_TEST_OBJS += $(TARGET_MBEDTLS_OBJS) +endif -ALL_TEST_OBJS += $(TARGET_OS_OBJS) ALL_OBJS += $(ALL_TEST_OBJS) # Must include the .d files for test sources here since gLinux.rules is included # by makefile after after it includes $(DEPFILES). @@ -64,9 +85,23 @@ EMPTY_TARGET_TEST = $(OUT_DIR)/client/tests/empty_target_test $(EMPTY_TARGET_TEST) : TARGET_OBJS = $(EMPTY_TARGET_OBJS) $(EMPTY_TARGET_TEST) : $(EMPTY_TARGET_OBJS) TEST_BINARIES = $(patsubst %.cc,$(OUT_DIR)/%,$(TEST_SRCS)) +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +$(TEST_BINARIES) : $(NAME) $(GTEST_OBJS) $(GLINUX_TARGET_OBJS) $(MBEDTLS_TARGET_OBJS) +else $(TEST_BINARIES) : $(NAME) $(GTEST_OBJS) $(GLINUX_TARGET_OBJS) +endif $(TEST_BINARIES) : $(TARGET_OS_OBJS) +LIBS ?= -L $(OUT_DIR) -lnearby -lcrypto -lssl -lpthread -lstdc++ + +ifeq ($(NEARBY_PLATFORM_USE_MBEDTLS),1) +MBEDTLS_LIBS = $(MBEDTLS_DIR)/library/libmbedtls.a $(MBEDTLS_DIR)/library/libmbedcrypto.a +$(MBEDTLS_LIBS) &: + cd $(MBEDTLS_DIR) && $(MAKE) +$(TEST_BINARIES) : $(MBEDTLS_LIBS) +LIBS += -L $(MBEDTLS_DIR)/library -lmbedtls -lmbedcrypto +endif + $(TEST_BINARIES) : % : %.o mkdir -p $(dir $@) $(CC) -o $@ $< \ @@ -74,10 +109,10 @@ $(TEST_BINARIES) : % : %.o $(TEST_INCLUDES) \ $(GTEST_OBJS) \ $(TARGET_OBJS) \ - $(TARGET_OS_OBJS) \ + $(TARGET_OS_OBJS) \ -L /usr/local/lib \ -std=c++14 \ - -L $(OUT_DIR) -lnearby -lcrypto -lssl -lpthread -lstdc++ + $(LIBS) run_tests: $(TESTS_TO_RUN) From d207756bd002e4530a2fe40b40bdb8d88b770454 Mon Sep 17 00:00:00 2001 From: jgsobczak <87881753+jgsobczak@users.noreply.github.com> Date: Wed, 21 Dec 2022 18:00:54 -0800 Subject: [PATCH 2/2] Update release_notes.md --- embedded/release_notes.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/embedded/release_notes.md b/embedded/release_notes.md index 4570b42fc7..c19031e27f 100644 --- a/embedded/release_notes.md +++ b/embedded/release_notes.md @@ -1,5 +1,17 @@ ### Nearby SDK for embedded systems release notes +## v1.1.0-embedded-RC1 +Support GFPS 3.2 specification per [specification](https://developers.google.com/nearby/fast-pair/specifications/introduction). + +New features: +* Smart Audio Source Switching (SASS) +* Two byte salt for improved security + +Other changes: +* Added optional encryption modules based on mbedtls. +* Fixed build issues. +* More verbose and readable logs. + ## v1.0.0-embedded Initial release supporting GFPS 3.1 per [specification](https://developers.google.com/nearby/fast-pair/specifications/introduction). @@ -11,4 +23,3 @@ Supported features include: * Personalized Name * RFCOMM message stream * Unit-tests on a simulated gLinux platform implementation -