From daac1e01c845e38b0c94d676f749cfc4c37aad65 Mon Sep 17 00:00:00 2001 From: Janusz Sobczak Date: Thu, 14 Apr 2022 11:57:25 -0700 Subject: [PATCH] Add embedded SDK The first release, v1.0.0-embedded, of Nearby SDK for embedded devices. --- embedded/Makefile | 181 ++ embedded/README.md | 31 + embedded/build.sh | 83 + embedded/client/source/nearby_fp_client.c | 1454 +++++++++++ embedded/client/source/nearby_fp_client.h | 109 + embedded/client/tests/gLinux/audio.cc | 22 + embedded/client/tests/gLinux/battery.cc | 62 + embedded/client/tests/gLinux/ble.cc | 127 + embedded/client/tests/gLinux/bt.cc | 145 ++ embedded/client/tests/gLinux/fakes.h | 134 + embedded/client/tests/gLinux/os.cc | 82 + embedded/client/tests/gLinux/persistence.cc | 69 + embedded/client/tests/gLinux/se.cc | 249 ++ embedded/client/tests/gLinux/trace.cc | 42 + embedded/client/tests/message_stream_test.cc | 359 +++ embedded/client/tests/smoke_test.cc | 2199 +++++++++++++++++ embedded/common/source/nearby_assert.h | 45 + embedded/common/source/nearby_config.h | 46 + embedded/common/source/nearby_event.h | 140 ++ embedded/common/source/nearby_fp_library.c | 452 ++++ embedded/common/source/nearby_fp_library.h | 187 ++ .../common/source/nearby_message_stream.c | 126 + .../common/source/nearby_message_stream.h | 100 + embedded/common/source/nearby_trace.h | 84 + embedded/common/source/nearby_utils.c | 96 + embedded/common/source/nearby_utils.cc | 14 + embedded/common/source/nearby_utils.h | 45 + embedded/common/target/gLinux/nearby_types.h | 31 + embedded/common/target/nearby.h | 44 + .../common/target/nearby_platform_audio.h | 21 + .../common/target/nearby_platform_battery.h | 56 + embedded/common/target/nearby_platform_ble.h | 94 + embedded/common/target/nearby_platform_bt.h | 110 + embedded/common/target/nearby_platform_os.h | 80 + .../target/nearby_platform_persistence.h | 55 + embedded/common/target/nearby_platform_se.h | 80 + .../common/target/nearby_platform_trace.h | 60 + embedded/release_notes.md | 14 + embedded/target/gLinux/config.mk | 28 + embedded/target/gLinux/rules.mk | 87 + 40 files changed, 7443 insertions(+) create mode 100644 embedded/Makefile create mode 100644 embedded/README.md create mode 100755 embedded/build.sh create mode 100644 embedded/client/source/nearby_fp_client.c create mode 100644 embedded/client/source/nearby_fp_client.h create mode 100644 embedded/client/tests/gLinux/audio.cc create mode 100644 embedded/client/tests/gLinux/battery.cc create mode 100644 embedded/client/tests/gLinux/ble.cc create mode 100644 embedded/client/tests/gLinux/bt.cc create mode 100644 embedded/client/tests/gLinux/fakes.h create mode 100644 embedded/client/tests/gLinux/os.cc create mode 100644 embedded/client/tests/gLinux/persistence.cc create mode 100644 embedded/client/tests/gLinux/se.cc create mode 100644 embedded/client/tests/gLinux/trace.cc create mode 100644 embedded/client/tests/message_stream_test.cc create mode 100644 embedded/client/tests/smoke_test.cc create mode 100644 embedded/common/source/nearby_assert.h create mode 100644 embedded/common/source/nearby_config.h create mode 100644 embedded/common/source/nearby_event.h create mode 100644 embedded/common/source/nearby_fp_library.c create mode 100644 embedded/common/source/nearby_fp_library.h create mode 100644 embedded/common/source/nearby_message_stream.c create mode 100644 embedded/common/source/nearby_message_stream.h create mode 100644 embedded/common/source/nearby_trace.h create mode 100644 embedded/common/source/nearby_utils.c create mode 100644 embedded/common/source/nearby_utils.cc create mode 100644 embedded/common/source/nearby_utils.h create mode 100644 embedded/common/target/gLinux/nearby_types.h create mode 100644 embedded/common/target/nearby.h create mode 100644 embedded/common/target/nearby_platform_audio.h create mode 100644 embedded/common/target/nearby_platform_battery.h create mode 100644 embedded/common/target/nearby_platform_ble.h create mode 100644 embedded/common/target/nearby_platform_bt.h create mode 100644 embedded/common/target/nearby_platform_os.h create mode 100644 embedded/common/target/nearby_platform_persistence.h create mode 100644 embedded/common/target/nearby_platform_se.h create mode 100644 embedded/common/target/nearby_platform_trace.h create mode 100644 embedded/release_notes.md create mode 100644 embedded/target/gLinux/config.mk create mode 100644 embedded/target/gLinux/rules.mk diff --git a/embedded/Makefile b/embedded/Makefile new file mode 100644 index 0000000000..7c33226859 --- /dev/null +++ b/embedded/Makefile @@ -0,0 +1,181 @@ +# Makefile arguments +# +# +# Delete the default suffix rules. +.SUFFIXES: +.SECONDARY: + +GTEST_DIR = ../third_party/gtest/googletest/ +GMOCK_DIR = ../third_party/gtest/googlemock/ + +ARCH ?= host +OUT_DIR_NAME ?= $(ARCH) +OUT_DIR = out/$(OUT_DIR_NAME) +DIST_DIR = dist/$(OUT_DIR_NAME) + +CFLAGS := -Wall +COMMON_INCLUDE_DIRS := +CLIENT_INCLUDES := +# For ARCH variants like gLinux_hw set to gLinux. +ARCH_COMMON_NAME := $(ARCH) +ARCH_COMMON_DIR = common/source/$(ARCH_COMMON_NAME) +CLIENT_DIR = client/source +CLIENT_SRCS := + +NEARBY_LIB_NAME = libnearby.a + +NAME = $(OUT_DIR)/$(NEARBY_LIB_NAME) + +all : $(NAME) + +COMMON_DIR = common/source +COMMON_C_FILES := + +# All output objects for the build system should be added to ALL_OBJS. +ALL_OBJS := +# Extra dependencies of the dist target. +DIST_LIST := + +# Name of the libnearby static library that we distribute. +DIST_NEARBY_LIB_NAME ?= $(DIST_DIR)/$(NEARBY_LIB_NAME) + +# Kokoro on Windows is wonky. We can't shell out to git because we are in +# cygwin, so this is pre-computed by continuous.bat +ifneq ($(FIRMWARE_VERSION_FILENAME),) +FIRMWARE_VERSION := $(shell cat $(FIRMWARE_VERSION_FILENAME)) +else +FIRMWARE_VERSION_FILENAME = $(OUT_DIR)/FIRMWARE_VERSION +PHONY_VERSION_FILENAME = $(OUT_DIR)/PHONY_FIRMWARE_VERSION +FIRMWARE_VERSION := $(shell git -c core.fileMode=false describe --always --dirty --exclude="*") +FIRMWARE_VERSION_FILENAME: $(PHONY_VERSION_FILENAME) + +ifneq ($(KOKORO_RELEASE_BUILD),1) +FIRMWARE_VERSION := $(FIRMWARE_VERSION)ENG +endif + +# Update the phony version on every build. +.PHONY: $(PHONY_VERSION_FILENAME) +$(PHONY_VERSION_FILENAME): + @mkdir -p $(dir $@) ;\ + echo $(FIRMWARE_VERSION) > $@ ;\ + +# Only update this version if necessary (if it's different from the phony). This +# tricks make into not updating dependencies of FIRMWARE_VERSION_FILENAME unless +# the file is updated. .PHONY is not transitive. +$(FIRMWARE_VERSION_FILENAME): $(PHONY_VERSION_FILENAME) + @cmp $@ $< 2>> /dev/null || cp -v $< $@ + +endif + +# Note $(notdir) below converts a path to just the base file name +define compile_c + mkdir -p $(dir $@) + $(CC) -c $(1) -o $@ -D__NEARBY_SHORT_FILE__='"$(notdir $<)"' $< +endef + +# Include all of the target specific variables and rules. +# The $(ARCH).mk files should only depend on the variables listed above. +include target/$(ARCH)/config.mk + +# Set trace verbosity from target specific variable +CFLAGS += -DNEARBY_TRACE_LEVEL=NEARBY_TRACE_LEVEL_$(NEARBY_TRACE_LEVEL) +# if directory doesn't exist, raise an error so we don't fail later with more cryptic warnings +ifeq ($(wildcard common/target/$(ARCH_COMMON_NAME)),) +$(error need to create directory common/target/$(ARCH_COMMON_NAME) ) +endif + +COMMON_INCLUDE_DIRS += \ + -I. \ + -I$(ARCH_COMMON_DIR) \ + -Icommon/target \ + -Icommon/target/$(ARCH_COMMON_NAME) \ + -I$(COMMON_DIR) + +CLIENT_INCLUDES += \ + -I$(CLIENT_DIR) \ + -Iclient/target + +COMMON_C_FILES += \ + $(wildcard $(COMMON_DIR)/*.c) + +CLIENT_SRCS += \ + $(wildcard $(CLIENT_DIR)/*.c) + +CFLAGS += $(COMMON_INCLUDE_DIRS) + +CROSS_COMPILE ?= arm-none-eabi- +CC ?= $(CROSS_COMPILE)gcc +AR ?= $(CROSS_COMPILE)ar +ALL_C_FILES = $(COMMON_C_FILES) +COMMON_OBJS = $(patsubst %.c,$(OUT_DIR)/%.o,$(ALL_C_FILES)) +CLIENT_OBJS = $(patsubst %.c,$(OUT_DIR)/%.o,$(CLIENT_SRCS)) +NEARBY_LIB_OBJS = $(COMMON_OBJS) $(CLIENT_OBJS) + +NEARBY_HEADERS += $(patsubst common/target/%.h,$(DIST_DIR)/%.h,$(wildcard common/target/*.h)) +NEARBY_HEADERS += $(patsubst common/target/$(ARCH_COMMON_NAME)/%.h,$(DIST_DIR)/%.h,$(wildcard common/target/$(ARCH_COMMON_NAME)/*.h)) +NEARBY_HEADERS += $(patsubst client/target/%,$(DIST_DIR)/%,$(wildcard client/target/*.h)) + +NEARBY_DIST_HEADERS := $(NEARBY_HEADERS) + +DIST_LIST += $(NEARBY_DIST_HEADERS) +DIST_LIST += $(DIST_NEARBY_LIB_NAME) + +$(DIST_DIR)/nearby.h: $(FIRMWARE_VERSION_FILENAME) +$(DIST_DIR)/nearby.h: common/target/nearby.h + @mkdir -p $(dir $@) + $(call copy_dist_header) + sed -i -e "s/\(define\s*NEARBY_BUILD_ID\).*/\1 \"$(FIRMWARE_VERSION)\"/" $@ + +define copy_dist_header + @mkdir -p $(dir $@) + @test "$$(head -n 2 $< | grep 'Copyright [0-9]\{4\} Google LLC.')" != "" || \ + (echo ; echo "=====ERROR=====" ; echo "Missing copyright in $<" ; exit 1) + unifdef -t -b -x2 $(UNIFDEF_ARG) -o - $< | cat - >$@ + @# (mkartoz) Should be able to specify $@ as the output directly from + @# unifdef but, if you do that, the file never shows up on kokoro. Piping to + @# cat is a workaround. +endef + +# Do not bypass the whole build process if the dist directory exists +.PHONY: dist +dist : all \ + $(DIST_LIST) + +$(DIST_DIR)/$(NEARBY_LIB_NAME) : $(NAME) + mkdir -p $(dir $@) + cp -uv $< $@ + +$(DIST_DIR)/%.h : common/target/%.h + $(call copy_dist_header) + +$(DIST_DIR)/%.h : common/target/$(ARCH_COMMON_NAME)/%.h + $(call copy_dist_header) + +$(NAME): $(NEARBY_LIB_OBJS) + mkdir -p $(dir $@) + $(AR) rcs $@ $(NEARBY_LIB_OBJS) + +# If the hash has changed then we need to rebuild nearby.o and nearby_client.o +$(filter %/nearby_client.o, $(NEARBY_LIB_OBJS)): $(FIRMWARE_VERSION_FILENAME) +$(filter %/nearby.o, $(NEARBY_LIB_OBJS)): $(FIRMWARE_VERSION_FILENAME) + +$(CLIENT_OBJS) : $(OUT_DIR)/%.o : %.c + $(call compile_c, $(CFLAGS) -Icommon/target $(CLIENT_INCLUDES)) + +$(COMMON_OBJS) : $(OUT_DIR)/%.o: %.c + $(call compile_c,$(CFLAGS)) + +ALL_OBJS += $(NEARBY_LIB_OBJS) $(CLIENT_OBJS) + +DEPFILES = $(ALL_OBJS:.o=.d) + +-include $(DEPFILES) + +.PHONY: clean +clean: + rm -f $(NAME) + rm -f $(ALL_OBJS) $(DEPFILES) + rm -f $(FIRMWARE_VERSION_FILENAME) $(PHONY_VERSION_FILENAME) + rm -f $(DIST_LIST) $(TEST_BINARIES) + +-include target/$(ARCH)/rules.mk diff --git a/embedded/README.md b/embedded/README.md new file mode 100644 index 0000000000..83ce5a9b63 --- /dev/null +++ b/embedded/README.md @@ -0,0 +1,31 @@ +# Nearby embedded SDK library + +This repository contains Nearby SDK library for embedded systems. Nearby SDK +implements the Fast Pair protocol and its future versions per +https://developers.google.com/nearby/fast-pair/spec + + +## Threading model + +Nearby SDK assumes a single-threading model. All calls to the SDK must be on the same thread, or protected with a mutex. That includes: +- client API, for example `nearby_fp_client_SetAdvertisement()` +- ble and other event callbacks +- timers + +## Porting guidelines + +1. Follow the steps at [Fast Pair Help](https://developers.google.com/nearby/fast-pair/help) to + register a Model Id for your device. +1. Review `nearby_config.h` and disable features, if any, that you don't + wish to support. +1. Implement the HAL defined in `nearby_platform_*.h` headers. +1. Set platform specific compile flags in `config.mk` - see + `target/gLinux/config.mk` for inspiration, and add your platform in + `build.sh`, or use your own build system. +1. Compile with `./build.sh ` +1. Start advertising in your application. For example + ``` + 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 diff --git a/embedded/build.sh b/embedded/build.sh new file mode 100755 index 0000000000..f23c12e08d --- /dev/null +++ b/embedded/build.sh @@ -0,0 +1,83 @@ +#!/bin/sh +# +# Parameters: +# ${1} architecture (default: gLinux) +# ${2} make target (e.g. "dist") +# (remaining params are also passed to make) +# +# Checks for environment variables: +# NEARBY_MAKEFILE_DEBUG: if set, passes -d argument to make +# many others, not fully documented here yet +# +# e.g. +# ./build.sh gLinux dist DEBUG=1 + +if [ "${1}" == "help" ] ; then + echo "Usage:" + echo " ./build.sh [dist] [args]" + echo "" + echo "Optional arguments:" + echo " DEBUG=1" + echo " -j72 # parallelizes build using 72 cores" + exit -1; +fi + +if [ "${1}" != "" ] ; then + ARCH="${1}" + shift 1 +else + ARCH=gLinux +fi + +CC=arm-none-eabi-gcc +AR=arm-none-eabi-ar + +UNAME_OUT="$(uname -s)" +case "${UNAME_OUT}" in + Linux*) MACHINE=linux;; + Darwin*) MACHINE=mac;; + CYGWIN*) MACHINE=cygwin;; + MINGW*) MACHINE=mingw;; + *) MACHINE=windows;; +esac + +if [ "${ARCH}" = "gLinux" ] ; then + if [ -x /usr/bin/clang ] ; then + CC=clang + else + CC=clang-3.8 + fi + AR=ar +else + echo "Invalid target specified on command line ${ARCH}" + exit 1 +fi + +export NEARBY=$(cd `dirname ${0}` && pwd) + +PLATFORM=$(uname -s | grep -i '\(mingw\|cygwin\)') +echo $PLATFORM + +make_args= +if [ ! -z "$NEARBY_MAKEFILE_DEBUG" ]; then + echo "NEARBY_MAKEFILE_DEBUG is set, passing -d to make" + make_args+=" -d" +fi + +make \ + $make_args \ + -C "${NEARBY}" \ + CC="${CC}" \ + AR="${AR}" \ + ARCH="${ARCH}" \ + $@ + +make_rc=$? + +# NOTE: if make fails, it is important that the bad return code is bubbled up +# to the caller of this script. +# We don't want automated processes to keep going if there was a build error. + +if [ $make_rc -ne 0 ]; then + exit $make_rc +fi diff --git a/embedded/client/source/nearby_fp_client.c b/embedded/client/source/nearby_fp_client.c new file mode 100644 index 0000000000..5965754c5b --- /dev/null +++ b/embedded/client/source/nearby_fp_client.c @@ -0,0 +1,1454 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nearby_fp_client.h" + +#include + +#include "nearby.h" +#include "nearby_fp_library.h" +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#include "nearby_platform_battery.h" +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ +#ifdef NEARBY_FP_MESSAGE_STREAM +#include "nearby_message_stream.h" +#endif /* NEARBY_FP_MESSAGE_STREAM */ +#include "nearby_assert.h" +#include "nearby_platform_audio.h" +#include "nearby_platform_ble.h" +#include "nearby_platform_bt.h" +#include "nearby_platform_os.h" +#include "nearby_platform_persistence.h" +#include "nearby_platform_se.h" +#include "nearby_trace.h" +#include "nearby_utils.h" + +#define ENCRYPTED_REQUEST_LENGTH 16 +#define PUBLIC_KEY_LENGTH 64 +#define REQUEST_BT_ADDRESS_OFFSET 2 + +// block pairing attempts for 5 minutes after 10 failures +#define MAX_PAIRING_FAILURE_COUNT 10 +#define REJECT_PAIRING_TIMEOUT_MS (5 * 60 * 1000) +#define PASSKEY_MAX_WAIT_TIME_MS 10000 +#define WAIT_FOR_PAIRING_REQUEST_TIME_MS 10000 +#define ACCOUNT_KEY_WRITE_TIME_MS 60000 +#define RETRO_PAIRING_REQUEST_TIME_MS 60000 +// Defines the timeout when waiting for pairing result when the Seeker writes +// the Account Key before/during pairing. +#define WAIT_FOR_PAIRING_RESULT_AFTER_ACCOUNT_KEY_TIME_MS 60000 +#define KEY_BASED_PAIRING_REQUEST_FLAG 0x00 +#define ACTION_REQUEST_FLAG 0x10 + +// KBPR - Key Based Pairing Request +#define KBPR_INITIATE_PAIRING_MASK (1 << 6) +#define KBPR_NOTIFY_EXISTING_NAME_MASK (1 << 5) +#define KBPR_RETROACTIVELY_WRITE_ACCOUNT_KEY_MASK (1 << 4) +#define KBPR_SEEKER_ADDRESS_OFFSET 8 + +#define ACTION_REQUEST_DEVICE_ACTION_MASK (1 << 7) +#define ACTION_REQUEST_WILL_WRITE_DATA_CHARACTERISTIC_MASK (1 << 6) + +#define SEEKER_PASSKEY_MESSAGE_TYPE 0x02 +#define PROVIDER_PASSKEY_MESSAGE_TYPE 0x03 +#define ACCOUNT_KEY_WRITE_MESSAGE_TYPE 0x04 + +#define PERSONALIZED_NAME_DATA_ID 1 + +#define INVALID_BATTERY_LEVEL (-1) + +#define INVALID_PEER_ADDRESS 0 + +// One byte per left, right and charging case +#define BATTERY_LEVELS_SIZE 3 + +// Size of battery remaining time (16 bits) +#define BATTERY_TIME_SIZE 2 + +// The BLE address should be rotated on average every 1024 seconds +#define ADDRESS_ROTATION_PERIOD_MS 1024000 + +enum PairingState { + kPairingStateIdle, + kPairingStateWaitingForPairingRequest, + kPairingStateWaitingForPasskey, + kPairingStateWaitingForPairingResult, + kPairingStateWaitingForAccountKeyWrite, + kPairingStateWaitingForAdditionalData +} pairing_state; +static const nearby_fp_client_Callbacks* client_callbacks; +static unsigned int timeout_start_ms; +static uint8_t pairing_failure_count; +static unsigned int reject_pairing_time_start_ms; +static uint64_t peer_public_address; +static int advertisement_mode; +static uint8_t account_key[ACCOUNT_KEY_SIZE_BYTES]; +static uint8_t pending_account_key[ACCOUNT_KEY_SIZE_BYTES]; +static uint64_t gatt_peer_address; +static uint32_t address_rotation_timestamp; +static void* address_rotation_task = NULL; +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +static uint8_t additional_data_id; +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + +#define RETURN_IF_ERROR(X) \ + do { \ + nearby_platform_status status = X; \ + if (kNearbyStatusOK != status) return status; \ + } while (0) + +#define BIT(b) (1 << b) +#define ISSET(v, b) (v & BIT(b)) + +#ifdef NEARBY_FP_MESSAGE_STREAM +// Callback triggered when a complete message is received over message stream +static void OnMessageReceived(uint64_t peer_address, + nearby_message_stream_Message* message); +static void SendBleAddressUpdatedToAll(); + +typedef struct { + nearby_message_stream_State state; + uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE + + sizeof(nearby_message_stream_Metadata)]; + uint8_t capabilities; + uint8_t platform_type; + uint8_t platform_build; +} rfcomm_input; + +static void InitRfcommInput(uint64_t peer_address, rfcomm_input* input) { + memset(input, 0, sizeof(*input)); + input->state.peer_address = peer_address; + input->state.on_message_received = OnMessageReceived; + input->state.length = sizeof(input->buffer); + input->state.buffer = input->buffer; + // defaults to: companion app installed, silence mode supported + input->capabilities = BIT(MESSAGE_CODE_CAPABILITIES_COMPANION_APP_INSTALLED) | + BIT(MESSAGE_CODE_CAPABILITIES_SILENCE_MODE_SUPPORTED); + nearby_message_stream_Init(&input->state); +} + +static rfcomm_input rfcomm_inputs[NEARBY_MAX_RFCOMM_CONNECTIONS]; + +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef NEARBY_FP_RETROACTIVE_PAIRING +typedef struct { + uint64_t peer_public_address; + uint64_t peer_le_address; + unsigned int retroactive_pairing_time_start_ms; +} retroactive_pairing_peer; + +static retroactive_pairing_peer + retroactive_pairing_list[NEARBY_MAX_RETROACTIVE_PAIRING]; + +static bool AddRetroactivePairingPeer(uint64_t peer_address) { + for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) { + retroactive_pairing_peer* peer = &retroactive_pairing_list[i]; + + if (nearby_platform_GetCurrentTimeMs() - + peer->retroactive_pairing_time_start_ms > + RETRO_PAIRING_REQUEST_TIME_MS) { + peer->peer_public_address = INVALID_PEER_ADDRESS; + peer->peer_le_address = INVALID_PEER_ADDRESS; + } + + if (peer->peer_public_address == peer_address) { + return false; + } + } + + for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) { + retroactive_pairing_peer* peer = &retroactive_pairing_list[i]; + if (peer->peer_public_address == INVALID_PEER_ADDRESS) { + NEARBY_TRACE(INFO, "timer set for retroactive pairing"); + peer->peer_public_address = peer_address; + peer->retroactive_pairing_time_start_ms = + nearby_platform_GetCurrentTimeMs(); + return true; + } + } + return false; +} + +static bool SetRetroactivePairingPeerLe(uint64_t peer_public_address, + uint64_t peer_le_address) { + for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) { + retroactive_pairing_peer* peer = &retroactive_pairing_list[i]; + if (peer->peer_public_address == peer_public_address) { + peer->peer_le_address = peer_le_address; + return true; + } + } + return false; +} + +static bool RetroactivePairingPeerPending(uint64_t peer_address) { + for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) { + retroactive_pairing_peer* peer = &retroactive_pairing_list[i]; + if (peer->peer_public_address == peer_address || + peer->peer_le_address == peer_address) { + return true; + } + } + return false; +} + +static bool RetroactivePairingPeerTimeout(uint64_t peer_address) { + for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) { + retroactive_pairing_peer* peer = &retroactive_pairing_list[i]; + if ((peer->peer_public_address == peer_address || + peer->peer_le_address == peer_address) && + (nearby_platform_GetCurrentTimeMs() - + peer->retroactive_pairing_time_start_ms > + RETRO_PAIRING_REQUEST_TIME_MS)) { + return true; + } + } + return false; +} + +static void RemoveRetroactivePairingPeer(uint64_t peer_address) { + for (int i = 0; i < NEARBY_MAX_RETROACTIVE_PAIRING; i++) { + retroactive_pairing_peer* peer = &retroactive_pairing_list[i]; + if (peer->peer_public_address == peer_address || + peer->peer_le_address == peer_address) { + peer->peer_public_address = INVALID_PEER_ADDRESS; + peer->peer_le_address = INVALID_PEER_ADDRESS; + } + } +} +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ + +static bool BtAddressMatch(uint8_t* a, uint8_t* b) { + return 0 == memcmp(a, b, BT_ADDRESS_LENGTH); +} + +static bool ShowPairingIndicator() { + return advertisement_mode & NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR; +} + +static bool ShowBatteryIndicator() { + return advertisement_mode & NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR; +} + +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +static bool IncludeBatteryInfo() { + return advertisement_mode & NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO; +} + +static bool IsInPairingMode() { + return nearby_platform_IsInPairingMode() || + (pairing_state != kPairingStateIdle && + pairing_state != kPairingStateWaitingForAccountKeyWrite && + pairing_state != kPairingStateWaitingForAdditionalData); +} + +// Gets battery info. Returns the input BatteryInfo or NULL on error. +static nearby_platform_BatteryInfo* PrepareBatteryInfo( + nearby_platform_BatteryInfo* battery_info) { + nearby_platform_status status; + + battery_info->is_charging = false; + battery_info->right_bud_battery_level = battery_info->left_bud_battery_level = + battery_info->charging_case_battery_level = INVALID_BATTERY_LEVEL; + battery_info->remaining_time_minutes = 0; + status = nearby_platform_GetBatteryInfo(battery_info); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "GetBatteryInfo() failed with error %d", status); + return NULL; + } + return battery_info; +} + +#ifdef NEARBY_FP_MESSAGE_STREAM +static nearby_platform_status SendBatteryInfoMessage(uint64_t peer_address) { + uint8_t levels[BATTERY_LEVELS_SIZE]; + nearby_message_stream_Message message = { + .message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT, + .message_code = MESSAGE_CODE_BATTERY_UPDATED, + .length = sizeof(levels), + .data = levels}; + nearby_platform_BatteryInfo battery_info; + if (!PrepareBatteryInfo(&battery_info)) return kNearbyStatusOK; + + SerializeBatteryInfo(message.data, &battery_info); + return nearby_message_stream_Send(peer_address, &message); +} + +static nearby_platform_status SendBatteryTimeMessage(uint64_t peer_address) { + uint8_t time[BATTERY_TIME_SIZE]; + nearby_message_stream_Message message = { + .message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT, + .message_code = MESSAGE_CODE_REMAINING_BATTERY_TIME, + .length = 1, + .data = time}; + nearby_platform_BatteryInfo battery_info; + if (!PrepareBatteryInfo(&battery_info)) return kNearbyStatusOK; + + if (battery_info.remaining_time_minutes > 255) { + message.length = sizeof(int16_t); + message.data[0] = battery_info.remaining_time_minutes >> 8; + message.data[1] = battery_info.remaining_time_minutes & 0xff; + } else { + message.data[0] = battery_info.remaining_time_minutes; + } + return nearby_message_stream_Send(peer_address, &message); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +static void AccountKeyRejected() { + if (++pairing_failure_count == MAX_PAIRING_FAILURE_COUNT) { + reject_pairing_time_start_ms = nearby_platform_GetCurrentTimeMs(); + } +} + +static void DiscardAccountKey() { memset(account_key, 0, sizeof(account_key)); } + +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; +} + +static void DiscardPendingAccountKey() { + memset(pending_account_key, 0, sizeof(pending_account_key)); +} + +static void RotateBleAddress() { + address_rotation_timestamp = nearby_platform_GetCurrentTimeMs(); + nearby_platform_SetAdvertisement(NULL, 0, kDisabled); +#ifdef NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION + uint64_t address = nearby_platform_RotateBleAddress(); + NEARBY_TRACE(INFO, "Rotated BLE address to: 0x%lx", address); +#else + unsigned i; + uint64_t address = 0; + for (i = 0; i < BT_ADDRESS_LENGTH; i++) { + address = (address << 8) ^ nearby_platform_Rand(); + } + address |= (uint64_t)1 << 46; + address &= ~((uint64_t)1 << 47); + NEARBY_TRACE(WARNING, "Rotate address to: 0x%lx", address); + nearby_platform_SetBleAddress(address); +#ifdef NEARBY_FP_MESSAGE_STREAM + SendBleAddressUpdatedToAll(); +#endif +#endif /* NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION */ +} + +static nearby_platform_status SendKeyBasedPairingResponse( + uint64_t peer_address) { + 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); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to encrypt key-based pairing response"); + return status; + } + status = nearby_platform_GattNotify(peer_address, kKeyBasedPairing, encrypted, + sizeof(encrypted)); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to notify on key-based pairing response"); + } + return status; +} + +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +static nearby_platform_status NotifyPersonalizedName(uint64_t peer_address) { + NEARBY_TRACE(VERBOSE, "NotifyPersonalizedName"); + uint8_t data[ADDITIONAL_DATA_HEADER_SIZE + PERSONALIZED_NAME_MAX_SIZE]; + size_t length = PERSONALIZED_NAME_MAX_SIZE; + nearby_platform_status status; + status = nearby_platform_LoadValue( + kStoredKeyPersonalizedName, data + ADDITIONAL_DATA_HEADER_SIZE, &length); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(WARNING, "Failed to load personalized name, status: %d", + status); + return status; + } + if (!length) NEARBY_TRACE(WARNING, "Empty personalized name"); + + length += ADDITIONAL_DATA_HEADER_SIZE; + status = nearby_fp_EncodeAdditionalData(data, length, account_key); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(ERROR, "Failed to encrypt additional data, status: %d", + status); + return status; + } + status = + nearby_platform_GattNotify(peer_address, kAdditionalData, data, length); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(ERROR, "Failed to notify on additional data, status: %d", + status); + } + return kNearbyStatusOK; +} + +static nearby_platform_status SaveAdditionalData(const uint8_t* data, + size_t length) { + if (additional_data_id == PERSONALIZED_NAME_DATA_ID) { + char name[PERSONALIZED_NAME_MAX_SIZE * sizeof(uint8_t) / sizeof(char) + 1]; + memcpy(name, data, length); + name[length * sizeof(uint8_t) / sizeof(char)] = '\0'; + NEARBY_TRACE(INFO, "Saving personalized name: %s", name); + nearby_platform_SetDeviceName(name); + return nearby_platform_SaveValue(kStoredKeyPersonalizedName, data, length); + } + NEARBY_TRACE(WARNING, "Unsupported data id 0x%02x", additional_data_id); + return kNearbyStatusUnsupported; +} + +static nearby_platform_status OnAdditionalDataWrite(uint64_t peer_address, + const uint8_t* request, + size_t length) { + NEARBY_TRACE(VERBOSE, "OnAdditionalDataWrite"); + 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); + DiscardAccountKey(); + if (kNearbyStatusOK == status) { + status = SaveAdditionalData(request + ADDITIONAL_DATA_HEADER_SIZE, + length - ADDITIONAL_DATA_HEADER_SIZE); + } + pairing_state = kPairingStateIdle; + return kNearbyStatusUnsupported; +} +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + +static nearby_platform_status HandleKeyBasedPairingRequest( + uint64_t peer_address, uint8_t request[ENCRYPTED_REQUEST_LENGTH]) { + NEARBY_TRACE(VERBOSE, "HandleKeyBasedPairingRequest"); + int flags; + flags = request[1]; +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA + if (flags & KBPR_NOTIFY_EXISTING_NAME_MASK) { + NotifyPersonalizedName(peer_address); + } +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + + if (flags & KBPR_RETROACTIVELY_WRITE_ACCOUNT_KEY_MASK) { + if (flags & KBPR_INITIATE_PAIRING_MASK) + NEARBY_TRACE(WARNING, + "received flag Initiate pairing in retroactive pairing"); + +#ifdef NEARBY_FP_RETROACTIVE_PAIRING + uint64_t peer_public_address = + nearby_utils_GetBigEndian48(request + KBPR_SEEKER_ADDRESS_OFFSET); + + if (RetroactivePairingPeerPending(peer_public_address) == false) { + NEARBY_TRACE( + ERROR, "Ignoring retroactive pairing request from unexpected client"); + RemoveRetroactivePairingPeer(peer_public_address); + return kNearbyStatusError; + } + + if (SetRetroactivePairingPeerLe(peer_public_address, peer_address) == + false) { + NEARBY_TRACE(ERROR, "Cannot set le address for retroactive pairing"); + RemoveRetroactivePairingPeer(peer_public_address); + return kNearbyStatusError; + } + + return kNearbyStatusOK; +#else + return kNearbyStatusUnsupported; +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ + } + + nearby_platform_SetFastPairCapabilities(); + if (flags & KBPR_INITIATE_PAIRING_MASK) { + peer_public_address = + nearby_utils_GetBigEndian48(request + KBPR_SEEKER_ADDRESS_OFFSET); + NEARBY_TRACE(INFO, "Send pairing request to 0x%llx", peer_public_address); + nearby_platform_SendPairingRequest(peer_public_address); + pairing_state = kPairingStateWaitingForPasskey; + } else { + pairing_state = kPairingStateWaitingForPairingRequest; + timeout_start_ms = nearby_platform_GetCurrentTimeMs(); + } + return kNearbyStatusOK; +} + +static nearby_platform_status HandleActionRequest( + uint64_t peer_address, uint8_t request[ENCRYPTED_REQUEST_LENGTH]) { + NEARBY_TRACE(VERBOSE, "HandleActionRequest"); + int flags; + flags = request[1]; + if (flags & ACTION_REQUEST_DEVICE_ACTION_MASK) { + NEARBY_TRACE(VERBOSE, "Device action not implemented"); + return kNearbyStatusUnimplemented; + } +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA + if (flags & ACTION_REQUEST_WILL_WRITE_DATA_CHARACTERISTIC_MASK) { + pairing_state = kPairingStateWaitingForAdditionalData; + additional_data_id = request[10]; + } + return kNearbyStatusOK; +#else + return kNearbyStatusUnsupported; +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ +} + +static nearby_platform_status OnWriteKeyBasedPairing(uint64_t peer_address, + const uint8_t* request, + size_t length) { + nearby_platform_status status; + uint8_t decrypted_request[ENCRYPTED_REQUEST_LENGTH]; + uint8_t ble_address[BT_ADDRESS_LENGTH]; + uint8_t public_address[BT_ADDRESS_LENGTH]; + + NEARBY_TRACE(VERBOSE, "OnWriteKeyBasedPairing"); + if (pairing_failure_count >= MAX_PAIRING_FAILURE_COUNT) { + unsigned int current_time = nearby_platform_GetCurrentTimeMs(); + if (current_time - reject_pairing_time_start_ms < + REJECT_PAIRING_TIMEOUT_MS) { + NEARBY_TRACE(INFO, "Too many failed pairing attempts"); + return kNearbyStatusOK; + } else { + NEARBY_TRACE(INFO, "Timeout expired. Allow pairing attempts"); + pairing_failure_count = 0; + } + } + nearby_utils_CopyBigEndian(ble_address, nearby_platform_GetBleAddress(), + BT_ADDRESS_LENGTH); + nearby_utils_CopyBigEndian(public_address, nearby_platform_GetPublicAddress(), + BT_ADDRESS_LENGTH); + // When the device is nondiscoverable, accept a saved account key. + // or accept a new account key for retroactive pairing + // When the device is discoverable, we can accept a new account key too. + if (length == ENCRYPTED_REQUEST_LENGTH + PUBLIC_KEY_LENGTH) { + uint8_t key[ACCOUNT_KEY_SIZE_BYTES]; + uint8_t remote_public_key[PUBLIC_KEY_LENGTH]; + memcpy(remote_public_key, request + ENCRYPTED_REQUEST_LENGTH, + PUBLIC_KEY_LENGTH); + status = nearby_fp_CreateSharedSecret(remote_public_key, key); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to create shared key, error: %d", status); + return status; + } + status = nearby_platform_Aes128Decrypt(request, decrypted_request, key); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to decrypt request, error: %d", status); + return status; + } + if (!BtAddressMatch(ble_address, + decrypted_request + REQUEST_BT_ADDRESS_OFFSET) && + !BtAddressMatch(public_address, + decrypted_request + REQUEST_BT_ADDRESS_OFFSET)) { + NEARBY_TRACE(INFO, "Invalid incoming BT address %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]); + AccountKeyRejected(); + return kNearbyStatusOK; + } + memcpy(account_key, key, ACCOUNT_KEY_SIZE_BYTES); + } 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); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to decrypt request, error: %d", status); + return status; + } + if (BtAddressMatch(ble_address, + decrypted_request + REQUEST_BT_ADDRESS_OFFSET) || + BtAddressMatch(public_address, + decrypted_request + REQUEST_BT_ADDRESS_OFFSET)) { + NEARBY_TRACE(VERBOSE, "Matched key number: %d", i); + nearby_fp_CopyAccountKey(account_key, i); + nearby_fp_MarkAccountKeyAsActive(i); + nearby_fp_SaveAccountKeys(); + break; + } + } + if (i == num_keys) { + NEARBY_TRACE(VERBOSE, "No key matched"); + AccountKeyRejected(); + return kNearbyStatusOK; + } + } else { + NEARBY_TRACE(WARNING, "Unexpected key based pairing request length %d", + length); + return kNearbyStatusError; + } + pairing_failure_count = 0; + gatt_peer_address = peer_address; + + DiscardPendingAccountKey(); + + status = SendKeyBasedPairingResponse(peer_address); + if (status != kNearbyStatusOK) return status; + + // TODO(jsobczak): Note that at the end of the packet there is a salt + // attached. When possible, these salts should be tracked, and if the Provider + // receives a request containing an already used salt, the request should be + // ignored to prevent replay attacks. + if (decrypted_request[0] == KEY_BASED_PAIRING_REQUEST_FLAG) { + return HandleKeyBasedPairingRequest(peer_address, decrypted_request); + } else if (decrypted_request[0] == ACTION_REQUEST_FLAG) { + return HandleActionRequest(peer_address, decrypted_request); + } + return kNearbyStatusOK; +} + +static nearby_platform_status NotifyProviderPasskey(uint64_t peer_address) { + uint8_t raw_passkey_block[AES_MESSAGE_SIZE_BYTES]; + uint8_t encrypted[AES_MESSAGE_SIZE_BYTES]; + uint32_t provider_passkey; + nearby_platform_status status; + provider_passkey = nearby_platfrom_GetPairingPassKey(); + + raw_passkey_block[0] = PROVIDER_PASSKEY_MESSAGE_TYPE; + nearby_utils_CopyBigEndian(raw_passkey_block + 1, provider_passkey, 3); + int i; + for (i = 4; i < AES_MESSAGE_SIZE_BYTES; i++) { + raw_passkey_block[i] = nearby_platform_Rand(); + } + status = + nearby_platform_Aes128Encrypt(raw_passkey_block, encrypted, account_key); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to encrypt passkey block"); + return status; + } + status = nearby_platform_GattNotify(peer_address, kPasskey, encrypted, + sizeof(encrypted)); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to notify on passkey characteristic"); + } + return status; +} + +static nearby_platform_status OnPasskeyWrite(uint64_t peer_address, + const uint8_t* request, + size_t length) { + NEARBY_TRACE(VERBOSE, "OnPasskeyWrite"); + uint8_t raw_passkey_block[AES_MESSAGE_SIZE_BYTES]; + nearby_platform_status status; + uint32_t seeker_passkey; + if (pairing_state != kPairingStateWaitingForPasskey) { + NEARBY_TRACE(INFO, "Not expecting a passkey write. Current state: %d", + pairing_state); + return kNearbyStatusError; + } + if (gatt_peer_address != peer_address) { + NEARBY_TRACE(INFO, "Ignoring passkey write from unexpected client"); + return kNearbyStatusError; + } + if (ShouldTimeout(PASSKEY_MAX_WAIT_TIME_MS)) { + NEARBY_TRACE(INFO, "Not expecting a passkey write"); + return kNearbyStatusTimeout; + } + if (length != AES_MESSAGE_SIZE_BYTES) { + NEARBY_TRACE(INFO, "Passkey: expected %d bytes, got %d", + AES_MESSAGE_SIZE_BYTES, length); + return kNearbyStatusError; + } + status = + nearby_platform_Aes128Decrypt(request, raw_passkey_block, account_key); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(WARNING, "Failed to decrypt passkey block"); + DiscardAccountKey(); + return status; + } + if (raw_passkey_block[0] != SEEKER_PASSKEY_MESSAGE_TYPE) { + NEARBY_TRACE(WARNING, "Unexpected passkey message type 0x%x", + raw_passkey_block[0]); + return kNearbyStatusError; + } + pairing_state = kPairingStateWaitingForPairingResult; + NotifyProviderPasskey(peer_address); + seeker_passkey = nearby_utils_GetBigEndian24(raw_passkey_block + 1); + nearby_platform_SetRemotePasskey(seeker_passkey); + return kNearbyStatusOK; +} + +static nearby_platform_status SetNonDiscoverableAdvertisement() { + uint8_t advertisement[NON_DISCOVERABLE_ADV_SIZE_BYTES]; + size_t length; +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + nearby_platform_BatteryInfo battery_info; + length = nearby_fp_CreateNondiscoverableAdvertisementWithBattery( + advertisement, sizeof(advertisement), ShowPairingIndicator(), + ShowBatteryIndicator(), + IncludeBatteryInfo() ? PrepareBatteryInfo(&battery_info) : NULL); +#else + length = nearby_fp_CreateNondiscoverableAdvertisement( + advertisement, sizeof(advertisement), ShowPairingIndicator()); +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + length += nearby_fp_AppendTxPower(advertisement + length, + sizeof(advertisement) - length, + nearby_platform_GetTxLevel()); + return nearby_platform_SetAdvertisement(advertisement, length, + kNoLargerThan250ms); +} + +// Steps executed after successful pairing with a seeker and receiving the +// account key. +static void RunPostPairingSteps(uint64_t peer_address, + const uint8_t* account_key) { + nearby_fp_AddAccountKey(account_key); + nearby_fp_SaveAccountKeys(); + DiscardPendingAccountKey(); +#ifdef 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); + } + if (pairing_state != kPairingStateIdle) { + NEARBY_TRACE(WARNING, + "Another fp pairing process has launched, do not change " + "pairing state"); + return; + } + } +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ + + pairing_state = kPairingStateIdle; +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA + pairing_state = kPairingStateWaitingForAdditionalData; + additional_data_id = PERSONALIZED_NAME_DATA_ID; +#endif + if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE) { + NEARBY_TRACE(INFO, "Account key added, update advertisement"); + nearby_platform_SetAdvertisement(NULL, 0, kDisabled); + SetNonDiscoverableAdvertisement(); + } +} +static nearby_platform_status OnAccountKeyWrite(uint64_t peer_address, + const uint8_t* request, + size_t length) { + NEARBY_TRACE(VERBOSE, "OnAccountKeyWrite"); + uint8_t decrypted_request[AES_MESSAGE_SIZE_BYTES]; + nearby_platform_status status; + bool wait_until_paired = false; + +#ifdef NEARBY_FP_RETROACTIVE_PAIRING + if (RetroactivePairingPeerPending(peer_address)) { + if (RetroactivePairingPeerTimeout(peer_address)) { + NEARBY_TRACE(ERROR, + "Not expecting an retroactive pairing request. Timeout"); + RemoveRetroactivePairingPeer(peer_address); + return kNearbyStatusTimeout; + } + } else { +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ + if (pairing_state == kPairingStateWaitingForPairingRequest || + pairing_state == kPairingStateWaitingForPairingResult || + pairing_state == kPairingStateWaitingForPasskey) { + NEARBY_TRACE(VERBOSE, "Account key write before paired event"); + wait_until_paired = true; + } else if (pairing_state != kPairingStateWaitingForAccountKeyWrite) { + NEARBY_TRACE(INFO, + "Not expecting an account key write. Current state: %d", + pairing_state); + return kNearbyStatusError; + } + if (gatt_peer_address != peer_address && + peer_public_address != peer_address) { + NEARBY_TRACE(INFO, "Ignoring account key write from unexpected client"); + return kNearbyStatusError; + } + + if (ShouldTimeout(ACCOUNT_KEY_WRITE_TIME_MS)) { + NEARBY_TRACE(INFO, "Not expecting an account key write. Timeout"); + return kNearbyStatusTimeout; + } +#ifdef NEARBY_FP_RETROACTIVE_PAIRING + } +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ + if (length != AES_MESSAGE_SIZE_BYTES) { + NEARBY_TRACE(INFO, "Account key: expected %d bytes, got %d", + AES_MESSAGE_SIZE_BYTES, length); + return kNearbyStatusError; + } + status = + nearby_platform_Aes128Decrypt(request, decrypted_request, account_key); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(WARNING, "Failed to decrypt account key block"); + return status; + } + if (decrypted_request[0] != ACCOUNT_KEY_WRITE_MESSAGE_TYPE) { + NEARBY_TRACE(WARNING, "Unexpected account key message type 0x%x", + decrypted_request[0]); + return kNearbyStatusError; + } + if (wait_until_paired) { + memcpy(pending_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); + return kNearbyStatusOK; +} + +static nearby_platform_status OnGattWrite( + uint64_t peer_address, nearby_fp_Characteristic characteristic, + const uint8_t* request, size_t length) { + switch (characteristic) { + case kKeyBasedPairing: + return OnWriteKeyBasedPairing(peer_address, request, length); + case kPasskey: + return OnPasskeyWrite(peer_address, request, length); + case kAccountKey: + return OnAccountKeyWrite(peer_address, request, length); + case kAdditionalData: +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA + return OnAdditionalDataWrite(peer_address, request, length); +#else + break; +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + case kModelId: + case kFirmwareRevision: + break; + } + return kNearbyStatusUnsupported; +} + +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 kKeyBasedPairing: + case kPasskey: + case kAccountKey: + case kFirmwareRevision: + case kAdditionalData: + break; + } + return kNearbyStatusUnsupported; +} + +static void OnPairingRequest(uint64_t peer_address) { + NEARBY_TRACE(VERBOSE, "Pairing request from 0x%lx", peer_address); + if (pairing_state == kPairingStateWaitingForPairingRequest) { + if (ShouldTimeout(WAIT_FOR_PAIRING_REQUEST_TIME_MS)) { + pairing_state = kPairingStateIdle; + } else { + pairing_state = kPairingStateWaitingForPasskey; + peer_public_address = peer_address; + timeout_start_ms = nearby_platform_GetCurrentTimeMs(); + } + } +} + +static void OnPaired(uint64_t peer_address) { + NEARBY_TRACE(INFO, "Paired with 0x%lx", peer_address); + if (peer_public_address == peer_address && HasPendingAccountKey()) { + NEARBY_TRACE(INFO, "Saving pending account key"); + RunPostPairingSteps(peer_address, pending_account_key); + return; + } + peer_public_address = peer_address; + if (pairing_state == kPairingStateWaitingForPairingResult) { + pairing_state = kPairingStateWaitingForAccountKeyWrite; + timeout_start_ms = nearby_platform_GetCurrentTimeMs(); + } +#ifdef NEARBY_FP_RETROACTIVE_PAIRING + else if (AddRetroactivePairingPeer(peer_address) == false) { + NEARBY_TRACE(WARNING, "No more timer for retroactive pairing"); + } +#endif /* NEARBY_FP_RETROACTIVE_AIRING */ +} + +static void OnPairingFailed(uint64_t peer_address) { + NEARBY_TRACE(ERROR, "Pairing failed with 0x%lx", peer_address); + DiscardAccountKey(); + DiscardPendingAccountKey(); +} + +#ifdef NEARBY_FP_MESSAGE_STREAM +// Finds and initializes an unused |rfcomm_input|. Returns NULL if not found. +static rfcomm_input* FindAvailableRfcommInput(uint64_t peer_address) { + for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) { + rfcomm_input* input = &rfcomm_inputs[i]; + if (input->state.peer_address == INVALID_PEER_ADDRESS || + input->state.peer_address == peer_address) { + InitRfcommInput(peer_address, input); + return input; + } + } + return NULL; +} + +// Gets |rfcomm_input| for |peer_address|. Returns NULL if not found. +static rfcomm_input* GetRfcommInput(uint64_t peer_address) { + for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) { + if (rfcomm_inputs[i].state.peer_address == peer_address) { + return &rfcomm_inputs[i]; + } + } + return NULL; +} + +nearby_platform_status nearby_fp_client_SetSilenceMode(uint64_t peer_address, + bool enable) { + rfcomm_input* input = GetRfcommInput(peer_address); + if (!input) { + return kNearbyStatusError; + } + nearby_message_stream_Message message = { + .message_group = MESSAGE_GROUP_BLUETOOTH, + .message_code = MESSAGE_CODE_DISABLE_SILENCE_MODE, + .length = 0}; + if (enable) message.message_code = MESSAGE_CODE_ENABLE_SILENCE_MODE; + if (!input) { + return kNearbyStatusError; + } + if (!ISSET(input->capabilities, + MESSAGE_CODE_CAPABILITIES_SILENCE_MODE_SUPPORTED)) { + return kNearbyStatusUnsupported; + } + return nearby_message_stream_Send(peer_address, &message); +} + +nearby_platform_status nearby_fp_client_SignalLogBufferFull( + uint64_t peer_address) { + rfcomm_input* input = GetRfcommInput(peer_address); + if (!input) { + return kNearbyStatusError; + } + if (!ISSET(input->capabilities, + MESSAGE_CODE_CAPABILITIES_COMPANION_APP_INSTALLED)) { + return kNearbyStatusOK; + } + nearby_message_stream_Message message = { + .message_group = MESSAGE_GROUP_COMPANION_APP_EVENT, + .message_code = MESSAGE_CODE_LOG_BUFFER_FULL, + .length = 0}; + return nearby_message_stream_Send(peer_address, &message); +} + +static nearby_platform_status SendBleAddressUpdated(uint64_t peer_address) { + uint8_t buffer[BT_ADDRESS_LENGTH]; + nearby_message_stream_Message message = { + .message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT, + .message_code = MESSAGE_CODE_BLE_ADDRESS_UPDATED, + .length = BT_ADDRESS_LENGTH, + .data = buffer}; + nearby_utils_CopyBigEndian(message.data, nearby_platform_GetBleAddress(), + BT_ADDRESS_LENGTH); + return nearby_message_stream_Send(peer_address, &message); +} + +static void SendBleAddressUpdatedToAll() { + uint8_t buffer[BT_ADDRESS_LENGTH]; + nearby_message_stream_Message message = { + .message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT, + .message_code = MESSAGE_CODE_BLE_ADDRESS_UPDATED, + .length = BT_ADDRESS_LENGTH, + .data = buffer}; + nearby_utils_CopyBigEndian(message.data, nearby_platform_GetBleAddress(), + BT_ADDRESS_LENGTH); + for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) { + rfcomm_input* input = &rfcomm_inputs[i]; + if (input->state.peer_address != INVALID_PEER_ADDRESS) { + nearby_message_stream_Send(input->state.peer_address, &message); + } + } +} + +static void OnMessageStreamConnected(uint64_t peer_address) { + nearby_platform_status status; + uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE]; + nearby_message_stream_Message message; + size_t length = sizeof(buffer); + rfcomm_input* input; + + NEARBY_TRACE(VERBOSE, "OnMessageStreamConnected 0x%lx", peer_address); + input = FindAvailableRfcommInput(peer_address); + if (!input) { + NEARBY_TRACE(WARNING, "Too many concurrent RFCOMM connections"); + return; + } + + message.data = buffer; + nearby_fp_GattReadModelId(message.data, &length); + message.length = length; + message.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT; + message.message_code = MESSAGE_CODE_MODEL_ID; + + status = nearby_message_stream_Send(peer_address, &message); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(ERROR, "Failed to send model id, status: %d", status); + return; + } + status = SendBleAddressUpdated(peer_address); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(ERROR, "Failed to send ble address, status: %d", status); + return; + } + +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + status = SendBatteryInfoMessage(peer_address); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(ERROR, "Failed to send battery info, status: %d", status); + return; + } + status = SendBatteryTimeMessage(peer_address); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(ERROR, "Failed to send battery info, status: %d", status); + return; + } +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + + if (client_callbacks != NULL && client_callbacks->on_event != NULL) { + nearby_event_MessageStreamConnected payload = {.peer_address = + peer_address}; + nearby_event_Event event = { + .event_type = kNearbyEventMessageStreamConnected, + .payload = (uint8_t*)&payload}; + client_callbacks->on_event(&event); + } +} + +static void OnMessageStreamDisconnected(uint64_t peer_address) { + rfcomm_input* input; + + NEARBY_TRACE(VERBOSE, "OnMessageStreamDisconnected 0x%lx", peer_address); + input = GetRfcommInput(peer_address); + if (!input) { + NEARBY_TRACE(WARNING, "Unexpected disconnection from 0x%lx", peer_address); + return; + } + input->state.peer_address = INVALID_PEER_ADDRESS; + + if (client_callbacks != NULL && client_callbacks->on_event != NULL) { + nearby_event_MessageStreamDisconnected payload = {.peer_address = + peer_address}; + nearby_event_Event event = { + .event_type = kNearbyEventMessageStreamDisconnected, + .payload = (uint8_t*)&payload}; + client_callbacks->on_event(&event); + } +#ifdef NEARBY_FP_RETROACTIVE_PAIRING + RemoveRetroactivePairingPeer(peer_address); +#endif /*NEARBY_FP_RETROACTIVE_PAIRING */ +} + +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); + return; + } + nearby_message_stream_Read(&input->state, message, length); +} + +static nearby_platform_status VerifyMessageLength( + uint64_t peer_address, const nearby_message_stream_Message* message, + size_t expected_length) { + if (message->length != expected_length) { + NEARBY_TRACE(WARNING, "Invalid message(%d) length %d, expected %d", + message->message_code, message->length, expected_length); + nearby_message_stream_SendNack(peer_address, message, /* fail reason= */ 0); + return kNearbyStatusInvalidInput; + } + return kNearbyStatusOK; +} + +// Sends either ACK or NACK response depending on |status| +static nearby_platform_status SendResponse( + uint64_t peer_address, const nearby_message_stream_Message* message, + nearby_platform_status status) { + if (kNearbyStatusOK == status) { + return nearby_message_stream_SendAck(peer_address, message); + } else { + // TODO(jsobczak): What should be the default fail reason? + uint8_t fail_reason = status == kNearbyStatusRedundantAction + ? FAIL_REASON_REDUNDANT_DEVICE_ACTION + : 0; + return nearby_message_stream_SendNack(peer_address, message, fail_reason); + } +} + +static void PrepareActiveComponentResponse( + nearby_message_stream_Message* message) { + NEARBY_ASSERT(message->length >= 2 * sizeof(uint16_t)); + message->message_code = MESSAGE_CODE_ACTIVE_COMPONENT_RESPONSE; + message->length = 1; + message->data[0] = nearby_platform_GetEarbudLeftStatus() << 1 | + nearby_platform_GetEarbudRightStatus(); +} + +static nearby_platform_status HandleGeneralMessage( + uint64_t peer_address, nearby_message_stream_Message* message) { + rfcomm_input* input = GetRfcommInput(peer_address); + NEARBY_ASSERT(input != NULL); + uint8_t buffer[MAX_MESSAGE_STREAM_PAYLOAD_SIZE]; + nearby_message_stream_Message reply; + reply.data = buffer; + reply.length = sizeof(buffer); + switch (message->message_group) { + case MESSAGE_GROUP_DEVICE_INFORMATION_EVENT: { + reply.message_group = MESSAGE_GROUP_DEVICE_INFORMATION_EVENT; + switch (message->message_code) { + case MESSAGE_CODE_ACTIVE_COMPONENT_REQUEST: { + PrepareActiveComponentResponse(&reply); + return nearby_message_stream_Send(peer_address, &reply); + } + case MESSAGE_CODE_CAPABILITIES: { + RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 1)); + uint8_t flags = message->data[0]; + NEARBY_TRACE(INFO, "Set capabilities: 0x%x", flags); + input->capabilities = flags; + return kNearbyStatusOK; + } + case MESSAGE_CODE_PLATFORM_TYPE: { + RETURN_IF_ERROR(VerifyMessageLength(peer_address, message, 2)); + uint8_t type = message->data[0]; + uint8_t build = message->data[1]; + NEARBY_TRACE(INFO, "Set platform type: 0x%x:0x%x", type, build); + input->platform_type = type; + input->platform_build = build; + return kNearbyStatusOK; + } + } + } + case MESSAGE_GROUP_DEVICE_ACTION_EVENT: { + reply.message_group = MESSAGE_GROUP_DEVICE_ACTION_EVENT; + switch (message->message_code) { + case MESSAGE_CODE_RING: { + if (message->length < 1 || message->length > 2) { + NEARBY_TRACE(WARNING, "Invalid message(%d) length %d", + message->message_code, message->length); + nearby_message_stream_SendNack(peer_address, message, + /* fail reason= */ 0); + return kNearbyStatusInvalidInput; + } + uint8_t command = message->data[0]; + uint16_t timeout = 0; + if (message->length > 1) timeout = message->data[1]; + NEARBY_TRACE(INFO, "Set ring device: 0x%x %d", command, timeout); + return SendResponse(peer_address, message, + nearby_platform_Ring(command, timeout * 10)); + } + } + } + } + return kNearbyStatusOK; +} + +static void OnMessageReceived(uint64_t peer_address, + nearby_message_stream_Message* message) { + nearby_platform_status status; + status = HandleGeneralMessage(peer_address, message); + if (kNearbyStatusOK != status) { + NEARBY_TRACE(WARNING, "Processing stream message failed with %d", status); + return; + } + if (client_callbacks != NULL && client_callbacks->on_event != NULL) { + nearby_event_MessageStreamReceived payload = { + .peer_address = peer_address, + .message_group = message->message_group, + .message_code = message->message_code, + .length = message->length, + .data = message->data}; + nearby_event_Event event = {.event_type = kNearbyEventMessageStreamReceived, + .payload = (uint8_t*)&payload}; + client_callbacks->on_event(&event); + } +} + +nearby_platform_status nearby_fp_client_SendMessage( + uint64_t peer_address, const nearby_message_stream_Message* message) { + return nearby_message_stream_Send(peer_address, message); +} + +nearby_platform_status nearby_fp_client_SendAck( + const nearby_event_MessageStreamReceived* message) { + nearby_message_stream_Message stream_message = { + .message_group = message->message_group, + .message_code = message->message_code, + }; + return nearby_message_stream_SendAck(message->peer_address, &stream_message); +} + +nearby_platform_status nearby_fp_client_SendNack( + const nearby_event_MessageStreamReceived* message, uint8_t fail_reason) { + nearby_message_stream_Message stream_message = { + .message_group = message->message_group, + .message_code = message->message_code, + }; + return nearby_message_stream_SendNack(message->peer_address, &stream_message, + fail_reason); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +static void OnBatteryChanged(void) { + nearby_platform_status status; + if (pairing_state != kPairingStateIdle && + pairing_state != kPairingStateWaitingForAccountKeyWrite && + pairing_state != kPairingStateWaitingForAdditionalData) { + NEARBY_TRACE(ERROR, "%s: device is in pairing process", __func__); + return; + } + + if (IncludeBatteryInfo() && + (advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE)) { + status = nearby_platform_SetAdvertisement(NULL, 0, kDisabled); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to update battery change, status: %d", + status); + return; + } + + status = SetNonDiscoverableAdvertisement(); + if (status != kNearbyStatusOK) { + NEARBY_TRACE(ERROR, "Failed to update battery change, status: %d", + status); + return; + } + } + +#ifdef 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); + status = SendBatteryTimeMessage(input->state.peer_address); + if (status != kNearbyStatusOK) + NEARBY_TRACE(ERROR, "Failed to send battery change, status: %d", + status); + } + } +#endif /* NEARBY_FP_MESSAGE_STREAM */ +} +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +static const nearby_platform_BleInterface kBleInterface = { + .on_gatt_write = OnGattWrite, + .on_gatt_read = OnGattRead, +}; + +static const nearby_platform_BtInterface kBtInterface = { + .on_pairing_request = OnPairingRequest, + .on_paired = OnPaired, + .on_pairing_failed = OnPairingFailed, +#ifdef 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 +static nearby_platform_BatteryInterface kBatteryInterface = { + .on_battery_changed = OnBatteryChanged, +}; +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +static nearby_platform_status EnterDisabledMode() { + nearby_platform_SetDefaultCapabilities(); + return nearby_platform_SetAdvertisement(NULL, 0, kDisabled); +} + +static nearby_platform_status EnterDiscoverableMode() { + size_t length; + uint8_t advertisement[DISCOVERABLE_ADV_SIZE_BYTES]; + if (pairing_state != kPairingStateIdle && + pairing_state != kPairingStateWaitingForAccountKeyWrite && + pairing_state != kPairingStateWaitingForAdditionalData) { + NEARBY_TRACE(ERROR, "%s: device is in pairing process", __func__); + return kNearbyStatusError; + } + length = nearby_fp_CreateDiscoverableAdvertisement(advertisement, + sizeof(advertisement)); + length += nearby_fp_AppendTxPower(advertisement + length, + sizeof(advertisement) - length, + nearby_platform_GetTxLevel()); + return nearby_platform_SetAdvertisement(advertisement, length, + kNoLargerThan100ms); +} + +static nearby_platform_status EnterNonDiscoverableMode() { + if (pairing_state != kPairingStateIdle && + pairing_state != kPairingStateWaitingForAccountKeyWrite && + pairing_state != kPairingStateWaitingForAdditionalData) { + NEARBY_TRACE(ERROR, "%s: device is in pairing process", __func__); + return kNearbyStatusError; + } + nearby_platform_SetDefaultCapabilities(); + return SetNonDiscoverableAdvertisement(); +} + +static bool ShouldRotateBleAddress(int mode) { + if (IsInPairingMode()) { + return false; + } + // We should rotate if advertisement mode changes to discoverable to prevent + // replay attacks. + return (mode & NEARBY_FP_ADVERTISEMENT_DISCOVERABLE) && + (!(advertisement_mode & NEARBY_FP_ADVERTISEMENT_DISCOVERABLE)); +} + +static uint32_t GetRotationDelayMs() { + uint32_t delay_ms = ADDRESS_ROTATION_PERIOD_MS; + // Rotation should happen every 1024 seconds on average. It is required that + // the precise point at which the beacon starts advertising the new identifier + // is randomized within the window. This logic should give us +/-200 seconds + // variability. + for (int i = 0; i < 5; i++) { + delay_ms += (50 << i) * (int8_t)nearby_platform_Rand(); + } + return delay_ms; +} + +static void MaybeRotateBleAddress(); + +static void CancelAddressRotationTimer() { + void* task = address_rotation_task; + address_rotation_task = NULL; + if (task != NULL) { + nearby_platform_CancelTimer(task); + } +} + +static void ScheduleAddressRotation() { + CancelAddressRotationTimer(); + address_rotation_task = + nearby_platform_StartTimer(MaybeRotateBleAddress, GetRotationDelayMs()); +} + +static nearby_platform_status UpdateAdvertisements() { + if (advertisement_mode == NEARBY_FP_ADVERTISEMENT_NONE) { + return EnterDisabledMode(); + } + if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_DISCOVERABLE) { + return EnterDiscoverableMode(); + } + if (advertisement_mode & NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE) { + return EnterNonDiscoverableMode(); + } + return kNearbyStatusUnsupported; +} + +static void MaybeRotateBleAddress() { + ScheduleAddressRotation(); + if (IsInPairingMode()) { + return; + } + RotateBleAddress(); + UpdateAdvertisements(); +} + +static bool NeedsPeriodicAddressRotation() { + // FP spec says we should rotate BLE adress every ~15 minutes when advertising + return (advertisement_mode & (NEARBY_FP_ADVERTISEMENT_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE)) != 0; +} + +nearby_platform_status nearby_fp_client_SetAdvertisement(int mode) { + if (advertisement_mode == mode) { + return kNearbyStatusOK; + } + if (ShouldRotateBleAddress(mode)) { + CancelAddressRotationTimer(); + RotateBleAddress(); + } + advertisement_mode = mode; + if (NeedsPeriodicAddressRotation() && address_rotation_task == NULL) { + ScheduleAddressRotation(); + } + return UpdateAdvertisements(); +} + +nearby_platform_status nearby_fp_client_GetSeekerInfo( + nearby_fp_client_SeekerInfo* seeker_info, size_t* seeker_info_length) { + int sl = *seeker_info_length; + int inx = 0; + *seeker_info_length = inx; + for (int i = 0; i < NEARBY_MAX_RFCOMM_CONNECTIONS; i++) { + if (rfcomm_inputs[i].state.peer_address != INVALID_PEER_ADDRESS) { + if (inx >= sl) return kNearbyStatusInvalidInput; + seeker_info[inx].peer_address = rfcomm_inputs[i].state.peer_address; + seeker_info[inx].capabilities = rfcomm_inputs[i].capabilities; + seeker_info[inx].platform_type = rfcomm_inputs[i].platform_type; + seeker_info[inx].platform_build = rfcomm_inputs[i].platform_build; + inx++; + *seeker_info_length = inx; + } + } + return kNearbyStatusOK; +} + +nearby_platform_status nearby_fp_client_Init( + const nearby_fp_client_Callbacks* callbacks) { + nearby_platform_status status; + + client_callbacks = callbacks; + pairing_state = kPairingStateIdle; + pairing_failure_count = 0; +#ifdef NEARBY_FP_MESSAGE_STREAM + memset(rfcomm_inputs, 0, sizeof(rfcomm_inputs)); +#endif + advertisement_mode = NEARBY_FP_ADVERTISEMENT_NONE; + address_rotation_task = NULL; + peer_public_address = 0; + DiscardPendingAccountKey(); + + status = nearby_platform_OsInit(); + if (status != kNearbyStatusOK) return status; + + status = nearby_platform_SecureElementInit(); + if (status != kNearbyStatusOK) return status; + + status = nearby_platform_BtInit(&kBtInterface); + if (status != kNearbyStatusOK) return status; + + status = nearby_platform_BleInit(&kBleInterface); + if (status != kNearbyStatusOK) return status; + + status = nearby_platform_PersistenceInit(); + if (status != kNearbyStatusOK) return status; + +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + status = nearby_platform_BatteryInit(&kBatteryInterface); + if (status != kNearbyStatusOK) return status; +#endif + + status = nearby_fp_LoadAccountKeys(); + if (status != kNearbyStatusOK) return status; + + RotateBleAddress(); + + return status; +} diff --git a/embedded/client/source/nearby_fp_client.h b/embedded/client/source/nearby_fp_client.h new file mode 100644 index 0000000000..65e17055b3 --- /dev/null +++ b/embedded/client/source/nearby_fp_client.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef NEARBY_FP_CLIENT_H +#define NEARBY_FP_CLIENT_H + +// clang-format off +#include "nearby_config.h" +// clang-format on + +#include "nearby.h" +#include "nearby_event.h" +#include "nearby_message_stream.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct { + // Optional event callback + void (*on_event)(nearby_event_Event* event); +} nearby_fp_client_Callbacks; + +// Seeker information structure +// Returns current information on a given seeker. +typedef struct { + uint64_t peer_address; // address of peer/seeker + uint8_t capabilities; // Capability bits + // Bit 0: Companion app is installed/not installed. + // Bit 1: Silence mode is supported/not supported. + uint8_t platform_type; // platform type code (Android = 0x01) + uint8_t platform_build; // Platform build number (Android Pie=0x1c) +} nearby_fp_client_SeekerInfo; + +// No advertisement, default state +#define NEARBY_FP_ADVERTISEMENT_NONE 0x00 +// The device is discoverable and can be paired with +#define NEARBY_FP_ADVERTISEMENT_DISCOVERABLE 0x01 +// The device is not discoverable +#define NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE 0x02 +// 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 +// 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 +// Ask the Seeker to show Battery UI indication. This flag can be combined with +// NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO +#define NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR 0x10 +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +// Sets Fast Pair advertisement type +nearby_platform_status nearby_fp_client_SetAdvertisement(int mode); + +// Initalizes Fast Pair provider. The |callbacks| are optional - can be NULL. +nearby_platform_status nearby_fp_client_Init( + const nearby_fp_client_Callbacks* callbacks); + +#ifdef 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); + +// Sends out an ACK message for a received |message|. Note that not all messages +// require an ACK +nearby_platform_status nearby_fp_client_SendAck( + const nearby_event_MessageStreamReceived* message); + +// Sends out a NACK message for a received |message| with |fail_reason| reason. +nearby_platform_status nearby_fp_client_SendNack( + const nearby_event_MessageStreamReceived* message, uint8_t fail_reason); + +// Sends out a enable or disable silence mode, which stops audio from being +// routed to the provider. +nearby_platform_status nearby_fp_client_SetSilenceMode(uint64_t peer_address, + bool enable); + +// Sends log buffer full message to seeker, which passes it on to the companion +// app to manage log collection. +nearby_platform_status nearby_fp_client_SignalLogBufferFull( + uint64_t peer_address); + +// Gets information on currently connected seekers. +// An array of `SeekerInfo` structures is passed in, along with the length. +// On output, `seeker_info_length` is the actual number of structures read. +// Returns an error if input array is too small to hold information about +// all connected seekers. +nearby_platform_status nearby_fp_client_GetSeekerInfo( + nearby_fp_client_SeekerInfo* seeker_info, size_t* seeker_info_length); + +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_FP_CLIENT_H */ diff --git a/embedded/client/tests/gLinux/audio.cc b/embedded/client/tests/gLinux/audio.cc new file mode 100644 index 0000000000..e77e71949b --- /dev/null +++ b/embedded/client/tests/gLinux/audio.cc @@ -0,0 +1,22 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fakes.h" +#include "nearby.h" +#include "nearby_fp_client.h" +#include "nearby_platform_audio.h" + +bool nearby_platform_GetEarbudRightStatus() { return false; } + +bool nearby_platform_GetEarbudLeftStatus() { return false; } diff --git a/embedded/client/tests/gLinux/battery.cc b/embedded/client/tests/gLinux/battery.cc new file mode 100644 index 0000000000..e0725a824b --- /dev/null +++ b/embedded/client/tests/gLinux/battery.cc @@ -0,0 +1,62 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fakes.h" +#include "nearby.h" +#include "nearby_platform_battery.h" + +static nearby_platform_BatteryInfo test_battery_info = { + .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_status get_battery_info_result = kNearbyStatusOK; + +static const nearby_platform_BatteryInterface* battery_interface; +nearby_platform_status nearby_platform_GetBatteryInfo( + nearby_platform_BatteryInfo* battery_info) { + (*battery_info) = test_battery_info; + return get_battery_info_result; +} + +void nearby_test_fakes_SetIsCharging(bool charging) { + test_battery_info.is_charging = charging; +} + +void nearby_test_fakes_SetRightBudBatteryLevel(unsigned battery_level) { + test_battery_info.right_bud_battery_level = battery_level; +} + +void nearby_test_fakes_SetLeftBudBatteryLevel(unsigned battery_level) { + test_battery_info.left_bud_battery_level = battery_level; +} + +void nearby_test_fakes_SetChargingCaseBatteryLevel(unsigned battery_level) { + test_battery_info.charging_case_battery_level = battery_level; +} + +void nearby_test_fakes_BatteryTime(uint16_t battery_time) { + test_battery_info.remaining_time_minutes = battery_time; +} + +void nearby_test_fakes_SetGetBatteryInfoResult(nearby_platform_status status) { + get_battery_info_result = status; +} + +nearby_platform_status nearby_platform_BatteryInit( + nearby_platform_BatteryInterface* callbacks) { + battery_interface = callbacks; + return kNearbyStatusOK; +} diff --git a/embedded/client/tests/gLinux/ble.cc b/embedded/client/tests/gLinux/ble.cc new file mode 100644 index 0000000000..4ed89d26c6 --- /dev/null +++ b/embedded/client/tests/gLinux/ble.cc @@ -0,0 +1,127 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +// clang-format off +#include "nearby_config.h" +// clang-format on + +#include "fakes.h" +#include "nearby_platform_ble.h" +#include "nearby_platform_se.h" + +constexpr uint64_t kDefaultBleAddress = 0xbabababa; +static uint64_t ble_address = kDefaultBleAddress; +static uint64_t peer_address = 0x345678ab; +static const nearby_platform_BleInterface* ble_interface; +static std::map> notifications; +static std::vector advertisement; +static nearby_fp_AvertisementInterval interval; + +std::map>& +nearby_test_fakes_GetGattNotifications() { + return notifications; +} + +// Gets BLE address. +uint64_t nearby_platform_GetBleAddress() { return ble_address; } + +// Sets BLE address. Returns address after change, which may be different than +// requested address. +uint64_t nearby_platform_SetBleAddress(uint64_t address) { + ble_address = address; + return ble_address; +} + +#ifdef NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION +// Rotates BLE address to a random resolvable private address (RPA). Returns +// address after change. +uint64_t nearby_platform_RotateBleAddress() { + unsigned i; + uint64_t address = 0; + for (i = 0; i < sizeof(address); i++) { + address = (address << 8) ^ nearby_platform_Rand(); + } + address |= (uint64_t)1 << 46; + address &= ~((uint64_t)1 << 47); + nearby_platform_SetBleAddress(address); + return address; +} +#endif /* NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION */ + +// Sends a notification to the connected GATT client. +nearby_platform_status nearby_platform_GattNotify( + uint64_t peer_address, nearby_fp_Characteristic characteristic, + const uint8_t* message, size_t length) { + notifications.emplace(characteristic, + std::vector(message, message + length)); + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SetAdvertisement( + const uint8_t* payload, size_t length, + nearby_fp_AvertisementInterval interval) { + if (payload == NULL || length == 0) { + advertisement.clear(); + } else { + advertisement.assign(payload, payload + length); + } + ::interval = interval; + return kNearbyStatusOK; +} + +// Initializes BLE +nearby_platform_status nearby_platform_BleInit( + const nearby_platform_BleInterface* callbacks) { + ble_interface = callbacks; + notifications.clear(); + advertisement.clear(); + interval = kDisabled; + ble_address = kDefaultBleAddress; + return kNearbyStatusOK; +} + +std::vector& nearby_test_fakes_GetAdvertisement() { + return advertisement; +} + +nearby_platform_status nearby_test_fakes_GattReadModelId(uint8_t* output, + size_t* length) { + return ble_interface->on_gatt_read(peer_address, kModelId, output, length); +} + +nearby_platform_status nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + const uint8_t* request, size_t length) { + return ble_interface->on_gatt_write(peer_address, kKeyBasedPairing, request, + length); +} + +nearby_platform_status nearby_fp_fakes_ReceivePasskey(const uint8_t* request, + size_t length) { + return ble_interface->on_gatt_write(peer_address, kPasskey, request, length); +} + +nearby_platform_status nearby_fp_fakes_ReceiveAccountKeyWrite( + const uint8_t* request, size_t length) { + return ble_interface->on_gatt_write(peer_address, kAccountKey, request, + length); +} + +nearby_platform_status nearby_fp_fakes_ReceiveAdditionalData( + const uint8_t* request, size_t length) { + return ble_interface->on_gatt_write(peer_address, kAdditionalData, request, + length); +} diff --git a/embedded/client/tests/gLinux/bt.cc b/embedded/client/tests/gLinux/bt.cc new file mode 100644 index 0000000000..43c5d594b4 --- /dev/null +++ b/embedded/client/tests/gLinux/bt.cc @@ -0,0 +1,145 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "nearby_platform_bt.h" + +static const uint32_t kFastPairId = 0x101112; +static const int8_t kTxLevel = 33; +static const uint64_t kPublicAddress = 0xA0A1A2A3A4A5; +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::vector device_name; +static bool pairing_mode = false; + +static const nearby_platform_BtInterface* bt_interface; +// Returns Fast Pair Model Id. +uint32_t nearby_platform_GetModelId() { return kFastPairId; } + +int8_t nearby_platform_GetTxLevel() { return kTxLevel; } + +// Returns public BR/EDR address +uint64_t nearby_platform_GetPublicAddress() { return kPublicAddress; } + +// Returns passkey used during pairing +uint32_t nearby_platfrom_GetPairingPassKey() { return kLocalPasskey; } + +void nearby_platform_SetRemotePasskey(uint32_t passkey) { + remote_passkey = passkey; + if (remote_passkey == kLocalPasskey && + remote_address != 0 & bt_interface != NULL) { + paired_peer_address = remote_address; + bt_interface->on_paired(remote_address); + } +} + +nearby_platform_status nearby_platform_SendPairingRequest( + uint64_t remote_party_br_edr_address) { + remote_address = remote_party_br_edr_address; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SetFastPairCapabilities() { + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SetDefaultCapabilities() { + remote_address = 0; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_BtInit( + const nearby_platform_BtInterface* callbacks) { + bt_interface = callbacks; + remote_address = 0; + remote_passkey = 0; + paired_peer_address = 0; + pairing_mode = false; + return kNearbyStatusOK; +} + +uint64_t nearby_test_fakes_GetPairingRequestAddress() { return remote_address; } + +uint32_t nearby_test_fakes_GetRemotePasskey() { return remote_passkey; } + +void nearby_test_fakes_SimulatePairing(uint64_t peer_address) { + remote_address = peer_address; + if (bt_interface != NULL) { + bt_interface->on_pairing_request(peer_address); + } +} + +uint64_t nearby_test_fakes_GetPairedDevice() { return paired_peer_address; } + +void nearby_test_fakes_DevicePaired(uint64_t peer_address) { + bt_interface->on_paired(peer_address); +} + +nearby_platform_status nearby_platform_SendMessageStream(uint64_t peer_address, + const uint8_t* message, + size_t length) { + for (int i = 0; i < length; i++) { + rfcomm_output.push_back(message[i]); + } + return kNearbyStatusOK; +} + +std::vector& nearby_test_fakes_GetRfcommOutput() { + return rfcomm_output; +} + +nearby_platform_status nearby_platform_SetDeviceName(const char* name) { + // + 1 for null terminator + device_name = std::vector(name, name + std::strlen(name) + 1); + return kNearbyStatusOK; +} + +// Gets null-terminated device name string in UTF-8 encoding +// pass buffer size in char, and get string length in char. +nearby_platform_status nearby_platform_GetDeviceName(char* name, + size_t* length) { + if (*length < device_name.size()) { + return kNearbyStatusResourceExhausted; + } + *length = device_name.size(); + std::memcpy(name, device_name.data(), device_name.size()); + return kNearbyStatusOK; +} + +bool nearby_platform_IsInPairingMode() { return pairing_mode; } + +void nearby_test_fakes_SetInPairingMode(bool in_pairing_mode) { + pairing_mode = in_pairing_mode; +} + +#ifdef NEARBY_FP_MESSAGE_STREAM +void nearby_test_fakes_MessageStreamConnected(uint64_t peer_address) { + bt_interface->on_message_stream_connected(peer_address); +} + +void nearby_test_fakes_MessageStreamDisconnected(uint64_t peer_address) { + bt_interface->on_message_stream_disconnected(peer_address); +} + +void nearby_test_fakes_MessageStreamReceived(uint64_t peer_address, + const uint8_t* message, + size_t length) { + bt_interface->on_message_stream_received(peer_address, message, length); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ diff --git a/embedded/client/tests/gLinux/fakes.h b/embedded/client/tests/gLinux/fakes.h new file mode 100644 index 0000000000..abbeeb26ac --- /dev/null +++ b/embedded/client/tests/gLinux/fakes.h @@ -0,0 +1,134 @@ +// 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. + +// Copyright 2021 Google LLC. +#ifndef NEARBY_TEST_FAKES_H +#define NEARBY_TEST_FAKES_H +#include +#include +#include + +#include "nearby.h" +#include "nearby_platform_ble.h" + +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); + +nearby_platform_status nearby_test_fakes_SetAntiSpoofingKey( + const uint8_t private_key[32], const uint8_t public_key[64]); + +nearby_platform_status nearby_test_fakes_GenSec256r1Secret( + const uint8_t remote_party_public_key[64], uint8_t secret[32]); + +nearby_platform_status nearby_test_fakes_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 nearby_test_fakes_Aes128Encrypt( + const uint8_t input[AES_MESSAGE_SIZE_BYTES], + uint8_t output[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]); + +std::vector& nearby_test_fakes_GetAdvertisement(); + +std::map>& +nearby_test_fakes_GetGattNotifications(); + +void nearby_test_fakes_SimulatePairing(uint64_t peer_address); +nearby_platform_status nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + const uint8_t* request, size_t length); +nearby_platform_status nearby_fp_fakes_ReceivePasskey(const uint8_t* request, + size_t length); +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); +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 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)); + } + } + + size_t size() { return keys_.size(); } + + std::vector>& GetKeys() { return keys_; } + + std::vector GetRawFormat() { + std::vector result; + result.push_back(size()); + for (auto& key : keys_) { + for (auto& v : key) { + result.push_back(v); + } + } + return result; + } + + private: + std::vector> keys_; +}; + +void nearby_test_fakes_SetAccountKeys(AccountKeyList& keys); + +AccountKeyList nearby_test_fakes_GetAccountKeys(); + +void nearby_test_fakes_SetIsCharging(bool charging); + +void nearby_test_fakes_SetRightBudBatteryLevel(unsigned battery_level); + +void nearby_test_fakes_SetLeftBudBatteryLevel(unsigned battery_level); + +void nearby_test_fakes_SetChargingCaseBatteryLevel(unsigned battery_level); + +void nearby_test_fakes_BatteryTime(uint16_t battery_time); + +void nearby_test_fakes_SetGetBatteryInfoResult(nearby_platform_status status); + +std::vector& nearby_test_fakes_GetRfcommOutput(); + +void nearby_test_fakes_MessageStreamConnected(uint64_t peer_address); + +void nearby_test_fakes_MessageStreamDisconnected(uint64_t peer_address); + +void nearby_test_fakes_MessageStreamReceived(uint64_t peer_address, + const uint8_t* message, + size_t length); + +uint32_t nearby_test_fakes_GetNextTimerMs(); +// Sets current time and triggers the timer callback if it expired +void nearby_test_fakes_SetCurrentTimeMs(uint32_t ms); + +void nearby_test_fakes_SetInPairingMode(bool in_pairing_mode); + +uint8_t nearby_test_fakes_GetRingCommand(void); + +uint16_t nearby_test_fakes_GetRingTimeout(void); + +#endif /* NEARBY_TEST_FAKES_H */ diff --git a/embedded/client/tests/gLinux/os.cc b/embedded/client/tests/gLinux/os.cc new file mode 100644 index 0000000000..40d2189983 --- /dev/null +++ b/embedded/client/tests/gLinux/os.cc @@ -0,0 +1,82 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "nearby_assert.h" +#include "nearby_platform_os.h" + +typedef void (*timer)(); +static timer timer_callback; +static uint32_t timer_trigger_time; +static bool timer_has_run; +static uint32_t current_time; +static constexpr nearby_platform_RingingInfo kDefaultRingingInfo = { + .ring_state = kRingStateStoppedReasonTimeout, + .num_components = 3, + .components = 0, + .timeout = 0, +}; + +static nearby_platform_RingingInfo ringing_info = kDefaultRingingInfo; + +// Gets current time in ms. +unsigned int nearby_platform_GetCurrentTimeMs() { return current_time; } + +// Starts a timer. Returns an opaque timer handle or null on error. +void* nearby_platform_StartTimer(void (*callback)(), unsigned int delay_ms) { + timer_callback = callback; + timer_trigger_time = nearby_platform_GetCurrentTimeMs() + delay_ms; + timer_has_run = false; + return (void*)timer_callback; +} + +// Cancels a timer +nearby_platform_status nearby_platform_CancelTimer(void* timer) { + NEARBY_ASSERT(timer == timer_callback); + timer_callback = NULL; + timer_trigger_time = 0; + return kNearbyStatusOK; +} + +uint32_t nearby_test_fakes_GetNextTimerMs() { return timer_trigger_time; } + +void nearby_test_fakes_SetCurrentTimeMs(uint32_t ms) { + current_time = ms; + if (!timer_has_run && timer_callback != NULL && timer_trigger_time <= ms) { + timer_has_run = true; + timer_callback(); + } +} + +nearby_platform_status nearby_platform_Ring(uint8_t command, uint16_t timeout) { + ringing_info.components = command; + ringing_info.timeout = timeout; + ringing_info.ring_state = kRingStateStarted; + return kNearbyStatusOK; +} + +uint8_t nearby_test_fakes_GetRingCommand(void) { + return ringing_info.components; +} + +uint16_t nearby_test_fakes_GetRingTimeout(void) { return ringing_info.timeout; } + +nearby_platform_status nearby_platform_OsInit() { + current_time = 0; + timer_callback = NULL; + timer_trigger_time = 0; + timer_has_run = false; + return kNearbyStatusOK; +} diff --git a/embedded/client/tests/gLinux/persistence.cc b/embedded/client/tests/gLinux/persistence.cc new file mode 100644 index 0000000000..dfb438ca8b --- /dev/null +++ b/embedded/client/tests/gLinux/persistence.cc @@ -0,0 +1,69 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "fakes.h" +#include "nearby_platform_persistence.h" + +static std::map> storage; +nearby_platform_status nearby_platform_LoadValue(nearby_fp_StoredKey key, + uint8_t* output, + size_t* length) { + auto search = storage.find(key); + if (search != storage.end()) { + size_t value_length = search->second.size(); + if (value_length > *length) { + *length = 0; + return kNearbyStatusResourceExhausted; + } + *length = value_length; + std::copy(search->second.begin(), search->second.end(), output); + } else { + // key not found. + *length = 0; + } + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_SaveValue(nearby_fp_StoredKey key, + const uint8_t* input, + size_t length) { + storage[key].assign(input, input + length); + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_PersistenceInit() { + storage.clear(); + return kNearbyStatusOK; +} + +void nearby_test_fakes_SetAccountKeys(const uint8_t* input, size_t length) { + storage[kStoredKeyAccountKeyList].assign(input, input + length); +} + +std::vector nearby_test_fakes_GetRawAccountKeys() { + return storage[kStoredKeyAccountKeyList]; +} + +void nearby_test_fakes_SetAccountKeys(AccountKeyList& keys) { + auto v = keys.GetRawFormat(); + nearby_test_fakes_SetAccountKeys(v.data(), v.size()); +} + +AccountKeyList nearby_test_fakes_GetAccountKeys() { + return AccountKeyList(nearby_test_fakes_GetRawAccountKeys()); +} diff --git a/embedded/client/tests/gLinux/se.cc b/embedded/client/tests/gLinux/se.cc new file mode 100644 index 0000000000..9039a06069 --- /dev/null +++ b/embedded/client/tests/gLinux/se.cc @@ -0,0 +1,249 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fakes.h" +#include "nearby_platform_se.h" + +static unsigned int random_value = 0; +static std::queue random_sequence; + +static std::unique_ptr anti_spoofing_key( + NULL, EVP_PKEY_free); + +static std::string ArrayToString(const uint8_t *data, size_t length) { + std::stringstream output; + output << "0x" << std::hex << std::setfill('0') << std::setw(2); + for (int i = 0; i < length; i++) { + output << (unsigned)data[i]; + } + return output.str(); +} + +// Generates a random number. +uint8_t nearby_platform_Rand() { + if (random_sequence.empty()) { + return random_value; + } else { + uint8_t v = random_sequence.front(); + random_sequence.pop(); + return v; + } +} + +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +static SHA256_CTX sha256_context; + +nearby_platform_status nearby_platform_Sha256Start() { + SHA256_Init(&sha256_context); + return kNearbyStatusOK; +} + +nearby_platform_status nearby_platform_Sha256Update(const void *data, + size_t length) { + SHA256_Update(&sha256_context, data, length); + return kNearbyStatusOK; +} + +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], + uint8_t output[16], + const uint8_t key[16]) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int input_length = 16; + int output_length = 16; + + EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, NULL); + + if (1 != + EVP_EncryptUpdate(ctx, output, &output_length, input, input_length)) { + return kNearbyStatusError; + } + + EVP_CIPHER_CTX_free(ctx); + return kNearbyStatusOK; +} + +// Encrypts a data block with AES128 in ECB mode. +nearby_platform_status nearby_platform_Aes128Decrypt(const uint8_t input[16], + uint8_t output[16], + const uint8_t key[16]) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int input_length = 16; + int output_length = 16; + + EVP_DecryptInit(ctx, EVP_aes_128_ecb(), key, NULL); + + if (1 != + EVP_DecryptUpdate(ctx, output, &output_length, input, input_length)) { + return kNearbyStatusError; + } + + EVP_CIPHER_CTX_free(ctx); + return kNearbyStatusOK; +} + +static EC_POINT *load_public_key(const uint8_t public_key[64]) { + BN_CTX *bn_ctx; + EC_KEY *key; + EC_POINT *point; + const EC_GROUP *group; + uint8_t oct_key[65]; + + oct_key[0] = 0x04; + memcpy(oct_key + 1, public_key, 64); + bn_ctx = BN_CTX_new(); + key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + group = EC_KEY_get0_group(key); + point = EC_POINT_new(group); + if (1 != EC_POINT_oct2point(group, point, oct_key, sizeof(oct_key), bn_ctx)) + return NULL; + BN_CTX_free(bn_ctx); + EC_KEY_free(key); + return point; +} + +// 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 secret[32]) { + EVP_PKEY_CTX *ctx; + EVP_PKEY *peerkey; + EC_POINT *peer_point; + EC_KEY *ec_peer_key; + size_t secret_len; + + /* Create the context for the shared secret derivation */ + if (NULL == (ctx = EVP_PKEY_CTX_new(anti_spoofing_key.get(), NULL))) + return kNearbyStatusError; + + /* Initialise */ + if (1 != EVP_PKEY_derive_init(ctx)) return kNearbyStatusError; + + if (NULL == (peer_point = load_public_key(remote_party_public_key))) + return kNearbyStatusError; + + ec_peer_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + + if (1 != EC_KEY_set_public_key(ec_peer_key, peer_point)) + return kNearbyStatusError; + + peerkey = EVP_PKEY_new(); + if (1 != EVP_PKEY_assign_EC_KEY(peerkey, ec_peer_key)) + return kNearbyStatusError; + + /* Provide the peer public key */ + if (1 != EVP_PKEY_derive_set_peer(ctx, peerkey)) return kNearbyStatusError; + + /* Determine buffer length for shared secret */ + if (1 != EVP_PKEY_derive(ctx, NULL, &secret_len)) return kNearbyStatusError; + + if (secret_len != 32) return kNearbyStatusError; + + /* Derive the shared secret */ + if (1 != (EVP_PKEY_derive(ctx, secret, &secret_len))) + return kNearbyStatusError; + + EVP_PKEY_CTX_free(ctx); + 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; +} + +void nearby_test_fakes_SetRandomNumber(unsigned int value) { + random_value = value; +} + +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; + BIGNUM *prv; + EC_POINT *pub; + + prv = load_private_key(private_key); + if (prv == NULL) return kNearbyStatusError; + pub = load_public_key(public_key); + if (pub == NULL) return kNearbyStatusError; + + key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + + if (1 != EC_KEY_set_private_key(key, prv)) return kNearbyStatusError; + if (1 != EC_KEY_set_public_key(key, pub)) return kNearbyStatusError; + + anti_spoofing_key.reset(EVP_PKEY_new()); + if (1 != EVP_PKEY_assign_EC_KEY(anti_spoofing_key.get(), key)) + return kNearbyStatusError; + BN_free(prv); + EC_POINT_free(pub); + return kNearbyStatusOK; +} + +nearby_platform_status nearby_test_fakes_GenSec256r1Secret( + const uint8_t remote_party_public_key[64], uint8_t secret[32]) { + return nearby_platform_GenSec256r1Secret(remote_party_public_key, secret); +} + +nearby_platform_status nearby_test_fakes_Aes128Decrypt( + const uint8_t input[AES_MESSAGE_SIZE_BYTES], + uint8_t output[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { + return nearby_platform_Aes128Decrypt(input, output, key); +} + +nearby_platform_status nearby_test_fakes_Aes128Encrypt( + const uint8_t input[AES_MESSAGE_SIZE_BYTES], + uint8_t output[AES_MESSAGE_SIZE_BYTES], + const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { + return nearby_platform_Aes128Encrypt(input, output, key); +} diff --git a/embedded/client/tests/gLinux/trace.cc b/embedded/client/tests/gLinux/trace.cc new file mode 100644 index 0000000000..2a56567f91 --- /dev/null +++ b/embedded/client/tests/gLinux/trace.cc @@ -0,0 +1,42 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include "nearby_platform_trace.h" + +void nearby_platform_Trace(nearby_platform_TraceLevel level, + const char *filename, int lineno, const char *fmt, + ...) { + char buff[1024]; + va_list args; + va_start(args, fmt); + + vsnprintf(buff, sizeof(buff), fmt, args); + std::cout << filename << ":" << lineno << " " << buff << std::endl; + va_end(args); +} + +void nearby_platfrom_CrashOnAssert(const char *filename, int lineno, + const char *reason) { + fprintf(stderr, "ASSERT %s, %s:%d\n", reason, filename, lineno); + abort(); +} + +void nearby_platform_TraceInit(void) {} diff --git a/embedded/client/tests/message_stream_test.cc b/embedded/client/tests/message_stream_test.cc new file mode 100644 index 0000000000..e4604e28cc --- /dev/null +++ b/embedded/client/tests/message_stream_test.cc @@ -0,0 +1,359 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "fakes.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "nearby.h" +#include "nearby_message_stream.h" + +#ifdef NEARBY_FP_MESSAGE_STREAM +constexpr uint64_t kPeerAddress = 0x101112; +constexpr size_t kBufferSize = 64; +constexpr size_t kMaxPayloadSize = + kBufferSize - sizeof(nearby_message_stream_Metadata); +constexpr size_t kHeaderSize = 4; + +using ::testing::ElementsAreArray; + +class StreamMessage { + public: + explicit StreamMessage(nearby_message_stream_Message* message) + : StreamMessage(message->message_group, message->message_code, + std::vector(message->data, + message->data + message->length)) {} + StreamMessage(uint8_t group, uint8_t code) : group_(group), code_(code) {} + StreamMessage(uint8_t group, uint8_t code, std::vector data) + : group_(group), code_(code), data_(data) {} + + bool operator==(const StreamMessage& b) const { + return this->group_ == b.group_ && this->code_ == b.code_ && + this->data_ == b.data_; + } + + private: + unsigned int group_; + unsigned int code_; + std::vector data_; + friend std::ostream& operator<<(std::ostream& os, + const StreamMessage& message); +}; + +static std::string VecToString(std::vector data) { + std::stringstream output; + output << "0x" << std::hex; + for (int i = 0; i < data.size(); i++) { + output << std::setfill('0') << std::setw(2) << (unsigned)data[i]; + } + return output.str(); +} + +std::ostream& operator<<(std::ostream& os, const StreamMessage& message) { + os << std::hex; + os << "group: 0x" << std::setfill('0') << std::setw(2) << message.group_; + os << " code: 0x" << std::setfill('0') << std::setw(2) << message.code_; + os << std::dec << " length: " << message.data_.size(); + if (message.data_.size() > 0) { + os << " data: " << VecToString(message.data_); + } + return os; +} + +uint8_t buffer[kBufferSize]; + +void OnMessageReceived(uint64_t peer_address, + nearby_message_stream_Message* message); + +class MessageStreamTest : public ::testing::Test { + public: + void Read(const uint8_t* data, size_t length) { + nearby_message_stream_Read(&stream_state_, data, length); + } + + void ReadByteByByte(const uint8_t* data, size_t length) { + for (int i = 0; i < length; i++) { + nearby_message_stream_Read(&stream_state_, data + i, 1); + } + } + + void Send(const nearby_message_stream_Message* message) { + nearby_message_stream_Send(kPeerAddress, message); + } + + void SendAck(const nearby_message_stream_Message* message) { + nearby_message_stream_SendAck(kPeerAddress, message); + } + + void SendNack(const nearby_message_stream_Message* message, + uint8_t fail_reason) { + nearby_message_stream_SendNack(kPeerAddress, message, fail_reason); + } + + protected: + void SetUp() override; + + std::deque received_messages_; + + private: + void AddMessage(nearby_message_stream_Message* message) { + received_messages_.emplace_back(StreamMessage(message)); + } + // To allow access to |AddMessage| + friend void OnMessageReceived(uint64_t peer_address, + nearby_message_stream_Message* message); + + nearby_message_stream_State stream_state_ = { + .on_message_received = OnMessageReceived, + .peer_address = kPeerAddress, + .length = sizeof(buffer), + .buffer = buffer}; +} * test_fixture; + +void MessageStreamTest::SetUp() { + test_fixture = this; + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_message_stream_Init(&stream_state_); +} + +void OnMessageReceived(uint64_t peer_address, + nearby_message_stream_Message* message) { + EXPECT_EQ(kPeerAddress, peer_address); + test_fixture->AddMessage(message); +} + +TEST_F(MessageStreamTest, ReadWholeMessageWithNoData) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t message[] = {group, code, 0, 0}; + + Read(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code), received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadMessageInChunksWithNoData) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t message[] = {group, code, 0, 0}; + + ReadByteByByte(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code), received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadWholeMessageSmallPayload) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t message[] = {group, code, 0, 1, 30}; + + Read(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code, + std::vector(message + kHeaderSize, + message + sizeof(message))), + received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadMessageInChunksSmallPayload) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t message[] = {group, code, 0, 1, 30}; + + ReadByteByByte(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code, + std::vector(message + kHeaderSize, + message + sizeof(message))), + received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadWholeMessageMaximumPayloadSize) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t message[kHeaderSize + kMaxPayloadSize]; + message[0] = group; + message[1] = code; + message[2] = kMaxPayloadSize >> 8; + message[3] = kMaxPayloadSize; + for (unsigned i = 0; i < kMaxPayloadSize; i++) { + message[kHeaderSize + i] = i; + } + + Read(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code, + std::vector(message + kHeaderSize, + message + sizeof(message))), + received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadWholeMessageTruncatedPayload) { + uint8_t group = 120; + uint8_t code = 130; + constexpr size_t kPayloadSize = 0x102; + uint8_t message[kHeaderSize + kPayloadSize]; + message[0] = group; + message[1] = code; + message[2] = 0x01; + message[3] = 0x02; + for (unsigned i = 0; i < kPayloadSize; i++) { + message[kHeaderSize + i] = i; + } + + Read(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage( + group, code, + std::vector(message + kHeaderSize, + message + kHeaderSize + kMaxPayloadSize)), + received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadMessageInChunksTruncatedPayload) { + uint8_t group = 120; + uint8_t code = 130; + constexpr size_t kPayloadSize = 0x102; + uint8_t message[kHeaderSize + kPayloadSize]; + message[0] = group; + message[1] = code; + message[2] = 0x01; + message[3] = 0x02; + for (unsigned i = 0; i < kPayloadSize; i++) { + message[kHeaderSize + i] = i; + } + + ReadByteByByte(message, sizeof(message)); + + ASSERT_EQ(1, received_messages_.size()); + ASSERT_EQ(StreamMessage( + group, code, + std::vector(message + kHeaderSize, + message + kHeaderSize + kMaxPayloadSize)), + received_messages_[0]); +} + +TEST_F(MessageStreamTest, ReadTwoMessages) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t group2 = 121; + uint8_t code2 = 131; + uint8_t message[] = {group, code, 0, 1, 30, group2, code2, 0, 2, 31, 32}; + + Read(message, sizeof(message)); + + ASSERT_EQ(2, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code, + std::vector(message + 4, message + 5)), + received_messages_[0]); + ASSERT_EQ(StreamMessage(group2, code2, + std::vector(message + 9, message + 11)), + received_messages_[1]); +} + +TEST_F(MessageStreamTest, ReadTwoMessagesByteByByte) { + uint8_t group = 120; + uint8_t code = 130; + uint8_t group2 = 121; + uint8_t code2 = 131; + uint8_t message[] = {group, code, 0, 1, 30, group2, code2, 0, 2, 31, 32}; + + ReadByteByByte(message, sizeof(message)); + + ASSERT_EQ(2, received_messages_.size()); + ASSERT_EQ(StreamMessage(group, code, + std::vector(message + 4, message + 5)), + received_messages_[0]); + ASSERT_EQ(StreamMessage(group2, code2, + std::vector(message + 9, message + 11)), + received_messages_[1]); +} + +TEST_F(MessageStreamTest, SendMessageNoPayload) { + nearby_message_stream_Message message{ + .message_group = 10, + .message_code = 20, + .length = 0, + .data = nullptr, + }; + constexpr uint8_t kExpectedOutput[] = {10, 20, 0, 0}; + + Send(&message); + + ASSERT_THAT(kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} + +TEST_F(MessageStreamTest, SendMessageWithPayload) { + uint8_t payload[] = {20, 21, 22, 23, 24}; + nearby_message_stream_Message message{ + .message_group = 10, + .message_code = 11, + .length = sizeof(payload), + .data = payload, + }; + constexpr uint8_t kExpectedOutput[] = {10, 11, 0, sizeof(payload), 20, 21, + 22, 23, 24}; + + Send(&message); + + ASSERT_THAT(kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} + +TEST_F(MessageStreamTest, SendAck) { + nearby_message_stream_Message message{ + .message_group = 10, + .message_code = 20, + .length = 0, + .data = nullptr, + }; + constexpr uint8_t kExpectedOutput[] = {0xFF, 1, 0, 2, 10, 20}; + + SendAck(&message); + + ASSERT_THAT(kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} + +TEST_F(MessageStreamTest, SendNack) { + nearby_message_stream_Message message{ + .message_group = 10, + .message_code = 20, + .length = 0, + .data = nullptr, + }; + constexpr uint8_t kFailReason = 0x88; + constexpr uint8_t kExpectedOutput[] = {0xFF, 2, 0, 3, kFailReason, 10, 20}; + + SendNack(&message, kFailReason); + + ASSERT_THAT(kExpectedOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/embedded/client/tests/smoke_test.cc b/embedded/client/tests/smoke_test.cc new file mode 100644 index 0000000000..9f2ebf9ca2 --- /dev/null +++ b/embedded/client/tests/smoke_test.cc @@ -0,0 +1,2199 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include "fakes.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "nearby.h" +#include "nearby_event.h" +#include "nearby_fp_client.h" +#include "nearby_fp_library.h" +#include "nearby_platform_ble.h" +#include "nearby_platform_persistence.h" +#include "nearby_utils.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-const-variable" + +constexpr uint8_t kBobPrivateKey[32] = { + 0x02, 0xB4, 0x37, 0xB0, 0xED, 0xD6, 0xBB, 0xD4, 0x29, 0x06, 0x4A, + 0x4E, 0x52, 0x9F, 0xCB, 0xF1, 0xC4, 0x8D, 0x0D, 0x62, 0x49, 0x24, + 0xD5, 0x92, 0x27, 0x4B, 0x7E, 0xD8, 0x11, 0x93, 0xD7, 0x63}; +constexpr uint8_t kBobPublicKey[64] = { + 0xF7, 0xD4, 0x96, 0xA6, 0x2E, 0xCA, 0x41, 0x63, 0x51, 0x54, 0x0A, + 0xA3, 0x43, 0xBC, 0x69, 0x0A, 0x61, 0x09, 0xF5, 0x51, 0x50, 0x06, + 0x66, 0xB8, 0x3B, 0x12, 0x51, 0xFB, 0x84, 0xFA, 0x28, 0x60, 0x79, + 0x5E, 0xBD, 0x63, 0xD3, 0xB8, 0x83, 0x6F, 0x44, 0xA9, 0xA3, 0xE2, + 0x8B, 0xB3, 0x40, 0x17, 0xE0, 0x15, 0xF5, 0x97, 0x93, 0x05, 0xD8, + 0x49, 0xFD, 0xF8, 0xDE, 0x10, 0x12, 0x3B, 0x61, 0xD2}; + +constexpr uint8_t kAlicePrivateKey[32] = { + 0xD7, 0x5E, 0x54, 0xC7, 0x7D, 0x76, 0x24, 0x89, 0xE5, 0x7C, 0xFA, + 0x92, 0x37, 0x43, 0xF1, 0x67, 0x77, 0xA4, 0x28, 0x3D, 0x99, 0x80, + 0x0B, 0xAC, 0x55, 0x58, 0x48, 0x38, 0x93, 0xE5, 0xB0, 0x6D}; +constexpr uint8_t kAlicePublicKey[64] = { + 0x36, 0xAC, 0x68, 0x2C, 0x50, 0x82, 0x15, 0x66, 0x8F, 0xBE, 0xFE, + 0x24, 0x7D, 0x01, 0xD5, 0xEB, 0x96, 0xE6, 0x31, 0x8E, 0x85, 0x5B, + 0x2D, 0x64, 0xB5, 0x19, 0x5D, 0x38, 0xEE, 0x7E, 0x37, 0xBE, 0x18, + 0x38, 0xC0, 0xB9, 0x48, 0xC3, 0xF7, 0x55, 0x20, 0xE0, 0x7E, 0x70, + 0xF0, 0x72, 0x91, 0x41, 0x9A, 0xCE, 0x2D, 0x28, 0x14, 0x3C, 0x5A, + 0xDB, 0x2D, 0xBD, 0x98, 0xEE, 0x3C, 0x8E, 0x4F, 0xBF}; + +constexpr uint8_t kExpectedSharedSecret[32] = { + 0x9D, 0xAD, 0xE4, 0xF8, 0x6A, 0xC3, 0x48, 0x8B, 0xBA, 0xC2, 0xAC, + 0x34, 0xB5, 0xFE, 0x68, 0xA0, 0xEE, 0x5A, 0x67, 0x06, 0xF5, 0x43, + 0xD9, 0x06, 0x1A, 0xD5, 0x78, 0x89, 0x49, 0x8A, 0xE6, 0xBA}; + +constexpr uint8_t kExpectedAesKey[16] = {0xB0, 0x7F, 0x1F, 0x17, 0xC2, 0x36, + 0xCB, 0xD3, 0x35, 0x23, 0xC5, 0x15, + 0xF3, 0x50, 0xAE, 0x57}; + +constexpr uint64_t kRemoteDevice = 0xB0B1B2B3B4B5; + +constexpr uint8_t kTxPower = 33; +constexpr uint8_t kDiscoverableAdvertisement[] = { + 6, 0x16, 0x2C, 0xFE, 0x10, 0x11, 0x12, 2, 0x0A, kTxPower}; + +constexpr uint8_t kSeekerAccountKey[16] = {0x04, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34}; +constexpr uint8_t kSeekerAccountKey2[16] = {0x04, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64}; + +using ::testing::ElementsAreArray; + +static std::string VecToString(std::vector data) { + std::stringstream output; + output << "0x" << std::hex; + for (int i = 0; i < data.size(); i++) { + output << std::setfill('0') << std::setw(2) << (unsigned)data[i]; + } + return output.str(); +} + +// static std::string VecToString(uint8_t* start, uint8_t* end) { +// return VecToString(std::vector(start, end)); +// } + +class Event { + public: + explicit Event(nearby_event_Type type) : type_(type) {} + explicit Event(const nearby_event_Event* event) : Event(event->event_type) {} + + nearby_event_Type GetType() const { return type_; } + + virtual bool operator==(const Event& b) const { return type_ == b.type_; } + + virtual ~Event() {} + + protected: + virtual std::string ToString() const { + std::stringstream output; + output << "Event type: " << type_; + return output.str(); + } + nearby_event_Type type_; + friend std::ostream& operator<<(std::ostream& os, const Event& event); +}; + +class MessageStreamConnectedEvent : public Event { + public: + explicit MessageStreamConnectedEvent(uint64_t peer_address) + : Event(kNearbyEventMessageStreamConnected), + peer_address_(peer_address) {} + explicit MessageStreamConnectedEvent( + const nearby_event_MessageStreamConnected* payload) + : Event(kNearbyEventMessageStreamConnected), + peer_address_(payload->peer_address) { + EXPECT_NE(nullptr, payload); + } + explicit MessageStreamConnectedEvent(const nearby_event_Event* event) + : MessageStreamConnectedEvent( + (const nearby_event_MessageStreamConnected*)event->payload) { + EXPECT_EQ(kNearbyEventMessageStreamConnected, type_); + } + + virtual bool operator==(const Event& b) const override { + if (type_ != b.GetType()) return false; + const MessageStreamConnectedEvent* event = + (const MessageStreamConnectedEvent*)&b; + return peer_address_ == event->peer_address_; + } + + std::string ToString() const override { + std::stringstream output; + output << "Event type: " << type_ << " peer_address: " << peer_address_; + return output.str(); + } + + private: + uint64_t peer_address_; +}; + +class MessageStreamDisconnectedEvent : public Event { + public: + explicit MessageStreamDisconnectedEvent(uint64_t peer_address) + : Event(kNearbyEventMessageStreamDisconnected), + peer_address_(peer_address) {} + explicit MessageStreamDisconnectedEvent( + const nearby_event_MessageStreamDisconnected* payload) + : Event(kNearbyEventMessageStreamDisconnected), + peer_address_(payload->peer_address) { + EXPECT_NE(nullptr, payload); + } + explicit MessageStreamDisconnectedEvent(const nearby_event_Event* event) + : MessageStreamDisconnectedEvent( + (const nearby_event_MessageStreamDisconnected*)event->payload) { + EXPECT_EQ(kNearbyEventMessageStreamDisconnected, type_); + } + + virtual bool operator==(const Event& b) const override { + if (type_ != b.GetType()) return false; + const MessageStreamDisconnectedEvent* event = + (const MessageStreamDisconnectedEvent*)&b; + return peer_address_ == event->peer_address_; + } + + std::string ToString() const override { + std::stringstream output; + output << "Event type: " << type_ << " peer_address: " << peer_address_; + return output.str(); + } + + private: + uint64_t peer_address_; +}; + +class MessageStreamReceivedEvent : public Event { + public: + explicit MessageStreamReceivedEvent( + const nearby_event_MessageStreamReceived* payload) + : Event(kNearbyEventMessageStreamReceived) { + EXPECT_NE(nullptr, payload); + + peer_address_ = payload->peer_address; + group_ = payload->message_group; + code_ = payload->message_code; + if (payload->length > 0) { + data_ = + std::vector(payload->data, payload->data + payload->length); + } + } + explicit MessageStreamReceivedEvent(const nearby_event_Event* event) + : MessageStreamReceivedEvent( + (const nearby_event_MessageStreamReceived*)event->payload) { + EXPECT_EQ(kNearbyEventMessageStreamReceived, event->event_type); + } + + virtual bool operator==(const Event& b) const override { + if (type_ != b.GetType()) return false; + const MessageStreamReceivedEvent* event = + (const MessageStreamReceivedEvent*)&b; + return peer_address_ == event->peer_address_ && group_ == event->group_ && + code_ == event->code_ && data_ == event->data_; + } + + std::string ToString() const override { + std::stringstream output; + output << "Event type: " << type_ << " peer_address: " << peer_address_ + << " group: " << (int)group_ << " code: " << (int)code_ + << " length: " << data_.size(); + if (data_.size() > 0) { + output << " data: " << VecToString(data_); + } + return output.str(); + } + + private: + uint64_t peer_address_; + uint8_t group_; + uint8_t code_; + std::vector data_; +}; + +std::ostream& operator<<(std::ostream& os, const Event& event) { + os << event.ToString(); + return os; +} + +static std::unique_ptr GetEvent(const nearby_event_Event* event) { + switch (event->event_type) { + case kNearbyEventMessageStreamConnected: + return std::make_unique(event); + case kNearbyEventMessageStreamDisconnected: + return std::make_unique(event); + case kNearbyEventMessageStreamReceived: + return std::make_unique(event); + } + return std::make_unique(event); +} + +std::vector> message_stream_events; +static void OnEventCallback(nearby_event_Event* event) { + message_stream_events.push_back(GetEvent(event)); +} + +constexpr nearby_fp_client_Callbacks kClientCallbacks = {.on_event = + OnEventCallback}; + +static void WriteToAccountKey() { + uint8_t encrypted_account_key_write_request[16]; + nearby_test_fakes_Aes128Encrypt( + kSeekerAccountKey, encrypted_account_key_write_request, kExpectedAesKey); + nearby_fp_fakes_ReceiveAccountKeyWrite( + encrypted_account_key_write_request, + sizeof(encrypted_account_key_write_request)); +} + +int GetCapability(uint64_t peer_address) { + nearby_fp_client_SeekerInfo seeker_infos[NEARBY_MAX_RFCOMM_CONNECTIONS]; + size_t sl = NEARBY_MAX_RFCOMM_CONNECTIONS; + nearby_fp_client_GetSeekerInfo(seeker_infos, &sl); + + for (int i = 0; i < sl; i++) { + if (seeker_infos[i].peer_address == peer_address) { + return seeker_infos[i].capabilities; + } + } + return -1; +} + +// |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_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = flags; + // 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))); + // BT negotatiates passkey 123456 (0x01E240) + uint8_t raw_passkey_block[16] = {0x02, 0x01, 0xE2, 0x40}; + uint8_t encrypted_passkey_block[16]; + nearby_test_fakes_Aes128Encrypt(raw_passkey_block, encrypted_passkey_block, + kExpectedAesKey); + // Seeker sends the passkey to provider + nearby_fp_fakes_ReceivePasskey(encrypted_passkey_block, + sizeof(encrypted_passkey_block)); + + // Seeker sends their account key + WriteToAccountKey(); +} + +TEST(NearbyFpClient, Init) { + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); +} + +TEST(NearbyFpClient, AccountKeyListIsEmpty) { + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(0, keys.size()); +} + +TEST(NearbyFpClient, CopyBigEndian_4bytes) { + uint8_t buffer[4]; + uint32_t value = 0x01020304; + + nearby_utils_CopyBigEndian(buffer, value, 4); + + ASSERT_EQ(1, buffer[0]); + ASSERT_EQ(2, buffer[1]); + ASSERT_EQ(3, buffer[2]); + ASSERT_EQ(4, buffer[3]); +} + +TEST(NearbyFpClient, CopyBigEndian_3bytes) { + uint8_t buffer[3]; + uint32_t value = 0x00010203; + + nearby_utils_CopyBigEndian(buffer, value, 3); + + ASSERT_EQ(1, buffer[0]); + ASSERT_EQ(2, buffer[1]); + ASSERT_EQ(3, buffer[2]); +} + +TEST(NearbyFpClient, AdvertisementDiscoverable) { + const int kBufferSize = DISCOVERABLE_ADV_SIZE_BYTES; + uint8_t buffer[kBufferSize]; + nearby_fp_client_Init(NULL); + + size_t written = + nearby_fp_CreateDiscoverableAdvertisement(buffer, kBufferSize); + written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, + kTxPower); + + ASSERT_EQ(kBufferSize, written); + ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), + ElementsAreArray(kDiscoverableAdvertisement)); +} + +TEST(NearbyFpClient, AdvertisementNondiscoverable_noKeys) { + const int kBufferSize = 9; + uint8_t buffer[kBufferSize]; + const uint8_t kExpectedResult[] = {5, 0x16, 0x2C, 0xFE, 0x00, + 0x00, 2, 0x0A, kTxPower}; + nearby_fp_client_Init(NULL); + + size_t written = + nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, + kTxPower); + + ASSERT_EQ(kBufferSize, written); + ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), + ElementsAreArray(kExpectedResult)); +} + +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}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + + size_t written = + nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, + kTxPower); + + ASSERT_EQ(kBufferSize, written); + ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), + ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, AdvertisementNondiscoverable_twoKeys) { + const int kBufferSize = 16; + uint8_t buffer[kBufferSize]; + 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}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + + size_t written = + nearby_fp_CreateNondiscoverableAdvertisement(buffer, kBufferSize, false); + written += nearby_fp_AppendTxPower(buffer + written, kBufferSize - written, + kTxPower); + + ASSERT_EQ(kBufferSize, written); + ASSERT_THAT(std::vector(buffer, buffer + kBufferSize), + ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, GattReadModelId) { + uint8_t buffer[3]; + size_t length = sizeof(buffer); + nearby_fp_client_Init(NULL); + + ASSERT_EQ(kNearbyStatusOK, + nearby_test_fakes_GattReadModelId(buffer, &length)); + + ASSERT_EQ(sizeof(buffer), length); + ASSERT_EQ(0x10, buffer[0]); + ASSERT_EQ(0x11, buffer[1]); + ASSERT_EQ(0x12, buffer[2]); +} + +TEST(NearbyFpClient, SetAntiSpoofingKey) { + nearby_fp_client_Init(NULL); + + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kBobPrivateKey, kBobPublicKey)); +} + +TEST(NearbyFpClient, GenSec256r1Secret_bobAlice) { + uint8_t secret[32]; + nearby_fp_client_Init(NULL); + + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kBobPrivateKey, kBobPublicKey)); + ASSERT_EQ(kNearbyStatusOK, + nearby_test_fakes_GenSec256r1Secret(kAlicePublicKey, secret)); + + for (int i = 0; i < sizeof(secret); i++) { + ASSERT_EQ(kExpectedSharedSecret[i], secret[i]) + << "Difference at position: " << i; + } +} + +TEST(NearbyFpClient, GenSec256r1Secret_aliceBob) { + uint8_t secret[32]; + nearby_fp_client_Init(NULL); + + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kAlicePrivateKey, kAlicePublicKey)); + ASSERT_EQ(kNearbyStatusOK, + nearby_test_fakes_GenSec256r1Secret(kBobPublicKey, secret)); + + for (int i = 0; i < sizeof(secret); i++) { + ASSERT_EQ(kExpectedSharedSecret[i], secret[i]) + << "Difference at position: " << i; + } +} + +TEST(NearbyFpClient, CreateSharedSecret_aliceBob) { + uint8_t secret[16]; + nearby_fp_client_Init(NULL); + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kAlicePrivateKey, kAlicePublicKey)); + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_CreateSharedSecret(kBobPublicKey, secret)); + + for (int i = 0; i < sizeof(secret); i++) { + ASSERT_EQ(kExpectedAesKey[i], secret[i]) << "Difference at position: " << i; + } +} + +TEST(NearbyFpClient, CreateSharedSecret_bobAlice) { + uint8_t secret[16]; + nearby_fp_client_Init(NULL); + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_SetAntiSpoofingKey( + kBobPrivateKey, kBobPublicKey)); + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_CreateSharedSecret(kAlicePublicKey, secret)); + + for (int i = 0; i < sizeof(secret); i++) { + ASSERT_EQ(kExpectedAesKey[i], secret[i]) << "Difference at position: " << i; + } +} + +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +TEST(NearbyFpClient, + SetAdvertisementWithBatteryNotification_AdvertisementWithPairingUI) { + 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[] = { + 0x10, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x03, 0x78, 0x95, 0x67, + 0x0c, 0xc3, 0x0a, 0xcc, 0x56, 0x11, salt, 0x02, 0x0a, kTxPower}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR)); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kExpectedResult)); +} + +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}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kExpectedResult)); +} + +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}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + nearby_test_fakes_SetIsCharging(true); + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kExpectedResult)); +} + +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}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + nearby_test_fakes_SetIsCharging(false); + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kExpectedResult)); +} + +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[] = { + 0x10, 0x16, 0x2c, 0xfe, 0x00, 0x90, 0x03, 0x78, 0x95, 0x67, + 0x0c, 0xc3, 0x0a, 0xcc, 0x56, 0x11, salt, 2, 0x0a, kTxPower}; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetAccountKeys(account_keys, sizeof(account_keys)); + nearby_test_fakes_SetRandomNumber(salt); + nearby_fp_LoadAccountKeys(); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusUnsupported); + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_BATTERY_UI_INDICATOR | + NEARBY_FP_ADVERTISEMENT_INCLUDE_BATTERY_INFO)); + + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kExpectedResult)); +} + +#ifdef 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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(100); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_SetIsCharging(true); + + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + ASSERT_EQ(1, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(100); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_fp_client_SetSilenceMode(kPeerAddress, true); + + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(100); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_fp_client_SetSilenceMode(kPeerAddress, false); + + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(0x100); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_fp_client_SetSilenceMode(kPeerAddress, false); + + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef 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(); + + ASSERT_EQ(kNearbyStatusError, + nearby_fp_client_SetSilenceMode(kPeerAddress, true)); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef 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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(100); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_fp_client_SignalLogBufferFull(kPeerAddress); + + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef NEARBY_FP_MESSAGE_STREAM +TEST(NearbyFpClient, + ReceiveActiveComponentsRequest_SendsActiveComponentResponse) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {// active components request + 3, 5, 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 = 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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_BatteryTime(100); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef NEARBY_FP_MESSAGE_STREAM +TEST(NearbyFpClient, ReceiveCapabilities) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kCapabilities = 0x11; + constexpr uint8_t kPeerMessage[] = {// Seeker capabilities request, + // Companion app, silence mode. + 3, 7, 0, 1, kCapabilities}; + 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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + ASSERT_EQ(kCapabilities, GetCapability(kPeerAddress)); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef NEARBY_FP_MESSAGE_STREAM +TEST(NearbyFpClient, ReceivePlatformType) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {// platform type request, + // Android, Pie SDK + 3, 8, 0, 2, 0x01, 0x1c}; + 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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +#ifdef NEARBY_FP_MESSAGE_STREAM +TEST(NearbyFpClient, ReceiveRingRequest) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kRingTimeSeconds = 100; + constexpr uint16_t kRingTimeDeciseconds = 10 * kRingTimeSeconds; + constexpr uint8_t kPeerMessage[] = { + // Ring request, both buds, + // 100 seconds. + 4, + 1, + 0, + 2, + MESSAGE_CODE_RING_LEFT | MESSAGE_CODE_RING_RIGHT, + kRingTimeSeconds}; + 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}; + nearby_fp_client_Init(&kClientCallbacks); + Pair(0x40); + message_stream_events.clear(); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[1]); + ASSERT_EQ(nearby_test_fakes_GetRingCommand(), + MESSAGE_CODE_RING_LEFT | MESSAGE_CODE_RING_RIGHT); + ASSERT_EQ(nearby_test_fakes_GetRingTimeout(), kRingTimeDeciseconds); +} +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +TEST(NearbyFpClient, Pairing_ProviderInitiated) { + 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); + + // Provider sets the advertisement + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kDiscoverableAdvertisement)); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 0x40; // bit 1 (msb) set + // 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; + uint8_t decrypted_response[16]; + uint8_t expected_decrypted_response[16] = {0x01, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, + 0xA5, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_decrypted_response[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + // Provider sends pairing request + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairingRequestAddress()); + + // BT negotatiates passkey 123456 (0x01E240) + uint8_t raw_passkey_block[16] = {0x02, 0x01, 0xE2, 0x40}; + uint8_t encrypted_passkey_block[16]; + nearby_test_fakes_Aes128Encrypt(raw_passkey_block, encrypted_passkey_block, + kExpectedAesKey); + // Seeker sends the passkey to provider + nearby_fp_fakes_ReceivePasskey(encrypted_passkey_block, + sizeof(encrypted_passkey_block)); + + // Provider sends the passkey to seeker + response = nearby_test_fakes_GetGattNotifications().at(kPasskey); + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + uint8_t expected_passkey_block[16] = {0x03, 0x01, 0xE2, 0x40, salt, salt, + salt, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_passkey_block[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + ASSERT_EQ(123456, nearby_test_fakes_GetRemotePasskey()); + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairedDevice()); + + // Seeker sends their account key + WriteToAccountKey(); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +// Pairing flow where the Seeker writes to the account key a little bit too +// early - while the BT bonding is still in progress +TEST(NearbyFpClient, Pairing_WriteKeyBeforePaired_PairingSuccessful) { + 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); + + // Provider sets the advertisement + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kDiscoverableAdvertisement)); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 0x40; // bit 1 (msb) set + // 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; + uint8_t decrypted_response[16]; + uint8_t expected_decrypted_response[16] = {0x01, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, + 0xA5, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_decrypted_response[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + // Provider sends pairing request + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairingRequestAddress()); + + // Seeker sends their account key + WriteToAccountKey(); + + // BT negotatiates passkey 123456 (0x01E240) + uint8_t raw_passkey_block[16] = {0x02, 0x01, 0xE2, 0x40}; + uint8_t encrypted_passkey_block[16]; + nearby_test_fakes_Aes128Encrypt(raw_passkey_block, encrypted_passkey_block, + kExpectedAesKey); + // Seeker sends the passkey to provider + nearby_fp_fakes_ReceivePasskey(encrypted_passkey_block, + sizeof(encrypted_passkey_block)); + + // Provider sends the passkey to seeker + response = nearby_test_fakes_GetGattNotifications().at(kPasskey); + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + uint8_t expected_passkey_block[16] = {0x03, 0x01, 0xE2, 0x40, salt, salt, + salt, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_passkey_block[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + ASSERT_EQ(123456, nearby_test_fakes_GetRemotePasskey()); + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairedDevice()); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +TEST(NearbyFpClient, Pairing_SeekerInitiated_PairingSuccessful) { + 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); + + // Provider sets the advertisement + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kDiscoverableAdvertisement)); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 0x00; // bit 1 (msb) cleared + // Provider's public address + request[2] = 0xA0; + request[3] = 0xA1; + request[4] = 0xA2; + request[5] = 0xA3; + request[6] = 0xA4; + request[7] = 0xA5; + // salt + for (int i = 8; i < 16; i++) { + request[i] = 0xCD + i; + } + 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; + uint8_t decrypted_response[16]; + uint8_t expected_decrypted_response[16] = {0x01, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, + 0xA5, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_decrypted_response[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + // Seeker sends pairing request + nearby_test_fakes_SimulatePairing(kRemoteDevice); + + // BT negotatiates passkey 123456 (0x01E240) + uint8_t raw_passkey_block[16] = {0x02, 0x01, 0xE2, 0x40}; + uint8_t encrypted_passkey_block[16]; + nearby_test_fakes_Aes128Encrypt(raw_passkey_block, encrypted_passkey_block, + kExpectedAesKey); + // Seeker sends the passkey to provider + nearby_fp_fakes_ReceivePasskey(encrypted_passkey_block, + sizeof(encrypted_passkey_block)); + + // Provider sends the passkey to seeker + response = nearby_test_fakes_GetGattNotifications().at(kPasskey); + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + uint8_t expected_passkey_block[16] = {0x03, 0x01, 0xE2, 0x40, salt, salt, + salt, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_passkey_block[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + ASSERT_EQ(123456, nearby_test_fakes_GetRemotePasskey()); + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairedDevice()); + + // Seeker sends their account key + WriteToAccountKey(); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +TEST(NearbyFpClient, Pairing_SeekerInitiatedWriteKeyEarly_PairingSuccessful) { + 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); + + // Provider sets the advertisement + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kDiscoverableAdvertisement)); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 0x00; // bit 1 (msb) cleared + // Provider's public address + request[2] = 0xA0; + request[3] = 0xA1; + request[4] = 0xA2; + request[5] = 0xA3; + request[6] = 0xA4; + request[7] = 0xA5; + // salt + for (int i = 8; i < 16; i++) { + request[i] = 0xCD + i; + } + 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; + uint8_t decrypted_response[16]; + uint8_t expected_decrypted_response[16] = {0x01, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, + 0xA5, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_decrypted_response[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + // Seeker sends pairing request + nearby_test_fakes_SimulatePairing(kRemoteDevice); + + // Seeker sends their account key + WriteToAccountKey(); + + // BT negotatiates passkey 123456 (0x01E240) + uint8_t raw_passkey_block[16] = {0x02, 0x01, 0xE2, 0x40}; + uint8_t encrypted_passkey_block[16]; + nearby_test_fakes_Aes128Encrypt(raw_passkey_block, encrypted_passkey_block, + kExpectedAesKey); + // Seeker sends the passkey to provider + nearby_fp_fakes_ReceivePasskey(encrypted_passkey_block, + sizeof(encrypted_passkey_block)); + + // Provider sends the passkey to seeker + response = nearby_test_fakes_GetGattNotifications().at(kPasskey); + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + uint8_t expected_passkey_block[16] = {0x03, 0x01, 0xE2, 0x40, salt, salt, + salt, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_passkey_block[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + ASSERT_EQ(123456, nearby_test_fakes_GetRemotePasskey()); + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairedDevice()); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +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)); + nearby_fp_LoadAccountKeys(); + + Pair(0x40); + + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(5, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +TEST(NearbyFpClient, ReadModelId) { + std::vector expected_model = {0x10, 0x11, 0x12}; + uint8_t model[3]; + size_t length = sizeof(model); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_client_Init(NULL)); + + ASSERT_EQ(kNearbyStatusOK, nearby_test_fakes_GattReadModelId(model, &length)); + + ASSERT_EQ(sizeof(model), length); + ASSERT_EQ(expected_model, std::vector(model, model + length)); +} + +TEST(NearbyFpClient, Aes128Encrypt) { + uint8_t input[16] = {0xF3, 0x0F, 0x4E, 0x78, 0x6C, 0x59, 0xA7, 0xBB, + 0xF3, 0x87, 0x3B, 0x5A, 0x49, 0xBA, 0x97, 0xEA}; + uint8_t key[16] = {0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6, + 0xCF, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D}; + uint8_t expected_output[16] = {0xAC, 0x9A, 0x16, 0xF0, 0x95, 0x3A, + 0x3F, 0x22, 0x3D, 0xD1, 0x0C, 0xF5, + 0x36, 0xE0, 0x9E, 0x9C}; + uint8_t output[16]; + + ASSERT_EQ(kNearbyStatusOK, + nearby_test_fakes_Aes128Encrypt(input, output, key)); + + for (int i = 0; i < sizeof(expected_output); i++) { + ASSERT_EQ(expected_output[i], output[i]) << "Difference at position: " << i; + } +} + +#ifdef NEARBY_FP_ENABLE_ADDITIONAL_DATA +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, + 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}; + uint8_t result[32]; + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_HmacSha256(result, kKey, sizeof(kKey), + kData, sizeof(kData))); + + ASSERT_THAT(result, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, AesCtr) { + uint8_t message[] = {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[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x53, 0x6F, 0x6D, 0x65, + 0x6F, 0x6E, 0x65, 0x27, 0x73, 0x20, 0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65, + 0x20, 0x48, 0x65, 0x61, 0x64, 0x70, 0x68, 0x6F, 0x6E, 0x65}; + + ASSERT_EQ(kNearbyStatusOK, nearby_fp_AesCtr(message, sizeof(message), kKey)); + + ASSERT_THAT(message, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, DecodeAdditionalData) { + uint8_t message[] = {0x55, 0xEC, 0x5E, 0x60, 0x55, 0xAF, 0x6E, 0x92, 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, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x53, 0x6F, 0x6D, 0x65, 0x6F, 0x6E, + 0x65, 0x27, 0x73, 0x20, 0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, + 0x48, 0x65, 0x61, 0x64, 0x70, 0x68, 0x6F, 0x6E, 0x65}; + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_DecodeAdditionalData(message, sizeof(message), kKey)); + + ASSERT_THAT(message, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, EncodeAdditionalData) { + uint8_t message[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x53, 0x6F, + 0x6D, 0x65, 0x6F, 0x6E, 0x65, 0x27, 0x73, 0x20, 0x47, + 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x48, 0x65, 0x61, + 0x64, 0x70, 0x68, 0x6F, 0x6E, 0x65}; + std::vector random_numbers = {0, 1, 2, 3, 4, 5, 6, 7}; + 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, 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}; + nearby_test_fakes_SetRandomNumberSequence(random_numbers); + + ASSERT_EQ(kNearbyStatusOK, + nearby_fp_EncodeAdditionalData(message, sizeof(message), kKey)); + + ASSERT_THAT(message, ElementsAreArray(kExpectedResult)); +} + +TEST(NearbyFpClient, PairAndGetPersonalizedName) { + uint8_t name[] = {0x53, 0x6F, 0x6D, 0x65, 0x6F, 0x6E, 0x65, 0x27, 0x73, + 0x20, 0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x48, + 0x65, 0x61, 0x64, 0x70, 0x68, 0x6F, 0x6E, 0x65}; + nearby_fp_client_Init(NULL); + ASSERT_EQ(kNearbyStatusOK, + nearby_platform_SaveValue(kStoredKeyPersonalizedName, name, + sizeof(name))); + + Pair(0x60); + + auto additional_data = + nearby_test_fakes_GetGattNotifications().at(kAdditionalData); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_DecodeAdditionalData( + additional_data.data(), additional_data.size(), + kExpectedAesKey)); + ASSERT_THAT(std::vector( + additional_data.begin() + ADDITIONAL_DATA_HEADER_SIZE, + additional_data.end()), + ElementsAreArray(name)); +} + +TEST(NearbyFpClient, PairAndSetPersonalizedName) { + uint8_t name[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x53, 0x6F, + 0x6D, 0x65, 0x6F, 0x6E, 0x65, 0x27, 0x73, 0x20, 0x47, + 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x48, 0x65, 0x61, + 0x64, 0x70, 0x68, 0x6F, 0x6E, 0x65}; + nearby_fp_EncodeAdditionalData(name, sizeof(name), kSeekerAccountKey); + uint8_t request[16]; + request[0] = 0x10; // action request + request[1] = 0x40; // additional data characteristic + // Provider's public address + request[2] = 0xA0; + request[3] = 0xA1; + request[4] = 0xA2; + request[5] = 0xA3; + request[6] = 0xA4; + request[7] = 0xA5; + request[8] = 0; // message group, ignored + request[9] = 0; // message code, ignored + request[10] = 1; // data ID, personalized name + // salt + request[11] = 0x67; + request[12] = 0x89; + request[13] = 0xAB; + request[14] = 0xCD; + request[15] = 0xEF; + uint8_t encrypted[16]; + nearby_test_fakes_Aes128Encrypt(request, encrypted, kSeekerAccountKey); + nearby_fp_client_Init(NULL); + Pair(0x40); + + // Seeker wants to set personalized name + ASSERT_EQ(kNearbyStatusOK, nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + encrypted, sizeof(encrypted))); + nearby_fp_fakes_ReceiveAdditionalData(name, sizeof(name)); + + uint8_t result[sizeof(name) - ADDITIONAL_DATA_HEADER_SIZE]; + size_t length = sizeof(result); + ASSERT_EQ(kNearbyStatusOK, nearby_platform_LoadValue( + kStoredKeyPersonalizedName, result, &length)); + ASSERT_EQ(length, sizeof(result)); + ASSERT_THAT(std::vector(name + ADDITIONAL_DATA_HEADER_SIZE, + name + sizeof(name)), + ElementsAreArray(result)); +} +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + +#ifdef 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}; + 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); + + ASSERT_EQ(1, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} + +#ifdef 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_SetGetBatteryInfoResult(kNearbyStatusOK); + + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + uint8_t salt = 0xAB; + + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetRandomNumber(salt); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 1 << 4; + // 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))); + + // Seeker sends their account key + uint8_t encrypted_account_key_write_request[16]; + nearby_test_fakes_Aes128Encrypt( + kSeekerAccountKey, encrypted_account_key_write_request, kExpectedAesKey); + nearby_fp_fakes_ReceiveAccountKeyWrite( + encrypted_account_key_write_request, + sizeof(encrypted_account_key_write_request)); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +TEST(NearbyFpClient, RetroactivePairAfterInitialPair) { + constexpr uint64_t kPeerAddress = 0xB0B1B2B3B4B5; + 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); + + // Provider sets the advertisement + ASSERT_THAT(nearby_test_fakes_GetAdvertisement(), + ElementsAreArray(kDiscoverableAdvertisement)); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 0x40; // bit 1 (msb) set + // 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; + uint8_t decrypted_response[16]; + uint8_t expected_decrypted_response[16] = {0x01, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, + 0xA5, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_decrypted_response[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + // Provider sends pairing request + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairingRequestAddress()); + + // BT negotatiates passkey 123456 (0x01E240) + uint8_t raw_passkey_block[16] = {0x02, 0x01, 0xE2, 0x40}; + uint8_t encrypted_passkey_block[16]; + nearby_test_fakes_Aes128Encrypt(raw_passkey_block, encrypted_passkey_block, + kExpectedAesKey); + // Seeker sends the passkey to provider + nearby_fp_fakes_ReceivePasskey(encrypted_passkey_block, + sizeof(encrypted_passkey_block)); + + // Provider sends the passkey to seeker + response = nearby_test_fakes_GetGattNotifications().at(kPasskey); + nearby_test_fakes_Aes128Decrypt(response.data(), decrypted_response, + kExpectedAesKey); + uint8_t expected_passkey_block[16] = {0x03, 0x01, 0xE2, 0x40, salt, salt, + salt, salt, salt, salt, salt, salt, + salt, salt, salt, salt}; + for (int i = 0; i < sizeof(expected_decrypted_response); i++) { + ASSERT_EQ(expected_passkey_block[i], decrypted_response[i]) + << "Difference at position: " << i; + } + + ASSERT_EQ(123456, nearby_test_fakes_GetRemotePasskey()); + ASSERT_EQ(kRemoteDevice, nearby_test_fakes_GetPairedDevice()); + + // Seeker sends their account key + WriteToAccountKey(); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); + + message_stream_events.clear(); + nearby_test_fakes_GetRfcommOutput().clear(); + nearby_test_fakes_SetGetBatteryInfoResult(kNearbyStatusOK); + + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + // Retroactive pairing + + request[0] = 0x00; // key-based pairing request + request[1] = 1 << 4; + // 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; + nearby_test_fakes_Aes128Encrypt(request, encrypted, kExpectedAesKey); + memcpy(encrypted + 16, kAlicePublicKey, 64); + + // Seeker responds + ASSERT_NE(kNearbyStatusOK, nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + encrypted, sizeof(encrypted))); + + // Seeker sends their account key + uint8_t encrypted_account_key_write_request[16]; + nearby_test_fakes_Aes128Encrypt( + kSeekerAccountKey2, encrypted_account_key_write_request, kExpectedAesKey); + ASSERT_NE(kNearbyStatusOK, nearby_fp_fakes_ReceiveAccountKeyWrite( + encrypted_account_key_write_request, + sizeof(encrypted_account_key_write_request))); + + keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +TEST(NearbyFpClient, RetroactivePairTwice) { + 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_SetGetBatteryInfoResult(kNearbyStatusOK); + + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + uint8_t salt = 0xAB; + + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetRandomNumber(salt); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 1 << 4; + // 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))); + + // Seeker sends their account key + uint8_t encrypted_account_key_write_request[16]; + nearby_test_fakes_Aes128Encrypt( + kSeekerAccountKey, encrypted_account_key_write_request, kExpectedAesKey); + ASSERT_EQ(kNearbyStatusOK, nearby_fp_fakes_ReceiveAccountKeyWrite( + encrypted_account_key_write_request, + sizeof(encrypted_account_key_write_request))); + + std::cout << "Account keys: " + << VecToString(nearby_test_fakes_GetRawAccountKeys()) << std::endl; + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); + + nearby_test_fakes_SetRandomNumber(salt); + request[0] = 0x00; // key-based pairing request + request[1] = 1 << 4; + // 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; + nearby_test_fakes_Aes128Encrypt(request, encrypted, kExpectedAesKey); + memcpy(encrypted + 16, kAlicePublicKey, 64); + + // Seeker responds + ASSERT_NE(kNearbyStatusOK, nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + encrypted, sizeof(encrypted))); + + // Seeker sends their account key + nearby_test_fakes_Aes128Encrypt( + kSeekerAccountKey2, encrypted_account_key_write_request, kExpectedAesKey); + ASSERT_NE(kNearbyStatusOK, nearby_fp_fakes_ReceiveAccountKeyWrite( + encrypted_account_key_write_request, + sizeof(encrypted_account_key_write_request))); + + keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(1, keys.size()); + ASSERT_EQ(std::vector(kSeekerAccountKey, + kSeekerAccountKey + sizeof(kExpectedAesKey)), + keys.GetKeys()[0]); +} + +TEST(NearbyFpClient, RetroactivePairWrongBtAddress) { + 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_SetGetBatteryInfoResult(kNearbyStatusOK); + + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + + uint8_t salt = 0xAB; + + nearby_test_fakes_SetAntiSpoofingKey(kBobPrivateKey, kBobPublicKey); + nearby_test_fakes_SetRandomNumber(salt); + + uint8_t request[16]; + request[0] = 0x00; // key-based pairing request + request[1] = 1 << 4; + // 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] = 0xC0; + request[9] = 0xC1; + request[10] = 0xC2; + request[11] = 0xC3; + request[12] = 0xC4; + request[13] = 0xC5; + // 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_NE(kNearbyStatusOK, nearby_fp_fakes_ReceiveKeyBasedPairingRequest( + encrypted, sizeof(encrypted))); + + // Seeker sends their account key + uint8_t encrypted_account_key_write_request[16]; + nearby_test_fakes_Aes128Encrypt( + kSeekerAccountKey, encrypted_account_key_write_request, kExpectedAesKey); + ASSERT_NE(kNearbyStatusOK, nearby_fp_fakes_ReceiveAccountKeyWrite( + encrypted_account_key_write_request, + sizeof(encrypted_account_key_write_request))); + + auto keys = nearby_test_fakes_GetAccountKeys(); + ASSERT_EQ(0, keys.size()); +} +#endif /* NEARBY_FP_RETROACTIVE_PAIRING */ + +TEST(NearbyFpClient, RfcommConnected_ClientDisconnects_EmitsDisconnectEvent) { + constexpr uint64_t kPeerAddress = 0x123456; + 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); + + nearby_test_fakes_MessageStreamDisconnected(kPeerAddress); + + ASSERT_EQ(2, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamDisconnectedEvent(kPeerAddress), + *message_stream_events[1]); +} + +TEST(NearbyFpClient, RfcommConnected_PeerSendsMessage_PassMessageToClientApp) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {101, 102, 0, 4, 81, 82, 83, 84}; + 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, + }; + 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); + + 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]); +} + +TEST(NearbyFpClient, + RfcommConnected_ConnectAndDisconnect_CanHandleManySessions) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kPeerMessage[] = {101, 102, 0, 4, 81, 82, 83, 84}; + 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, + }; + 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++) { + nearby_test_fakes_MessageStreamConnected(kPeerAddress + i); + nearby_test_fakes_MessageStreamDisconnected(kPeerAddress + i); + } + nearby_test_fakes_MessageStreamConnected(kPeerAddress); + nearby_test_fakes_MessageStreamReceived(kPeerAddress, kPeerMessage, + sizeof(kPeerMessage)); + + int events = message_stream_events.size(); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress), + *message_stream_events[events - 2]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage), + *message_stream_events[events - 1]); +} + +#if NEARBY_MAX_RFCOMM_CONNECTIONS > 1 +TEST(NearbyFpClient, RfcommConnected_TwoInterleavedConnections_ParsesMessages) { + constexpr uint64_t kPeerAddress1 = 0x123456; + constexpr uint8_t kPeerMessage1[] = {101, 102, 0, 4, 81, 82, 83, 84}; + const nearby_event_MessageStreamReceived kExpectedMessage1 = { + .peer_address = kPeerAddress1, + .message_group = kPeerMessage1[0], + .message_code = kPeerMessage1[1], + .length = kPeerMessage1[2] * 256 + kPeerMessage1[3], + .data = (uint8_t*)kPeerMessage1 + 4, + }; + constexpr uint64_t kPeerAddress2 = 0x7890ab; + constexpr uint8_t kPeerMessage2[] = {201, 202, 0, 5, 91, 92, 93, 94, 95}; + const nearby_event_MessageStreamReceived kExpectedMessage2 = { + .peer_address = kPeerAddress2, + .message_group = kPeerMessage2[0], + .message_code = kPeerMessage2[1], + .length = kPeerMessage2[2] * 256 + kPeerMessage2[3], + .data = (uint8_t*)kPeerMessage2 + 4, + }; + 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); + + // Send the messages byte by byte, interleaving bytes from both connections + // to verify that the parses handles the streams separately + for (int i = 0; i < std::max(sizeof(kPeerMessage1), sizeof(kPeerMessage2)); + i++) { + if (i < sizeof(kPeerMessage1)) { + nearby_test_fakes_MessageStreamReceived(kPeerAddress1, kPeerMessage1 + i, + 1); + } + if (i < sizeof(kPeerMessage2)) { + nearby_test_fakes_MessageStreamReceived(kPeerAddress2, kPeerMessage2 + i, + 1); + } + } + + ASSERT_EQ(4, message_stream_events.size()); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress1), + *message_stream_events[0]); + ASSERT_EQ(MessageStreamConnectedEvent(kPeerAddress2), + *message_stream_events[1]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage1), + *message_stream_events[2]); + ASSERT_EQ(MessageStreamReceivedEvent(&kExpectedMessage2), + *message_stream_events[3]); +} +#endif /* NEARBY_MAX_RFCOMM_CONNECTIONS > 1 */ + +TEST(NearbyFpClient, SendMessageStreamMessage) { + 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); + + ASSERT_THAT(kExpectedRfcommOutput, + ElementsAreArray(nearby_test_fakes_GetRfcommOutput())); +} + +TEST(NearbyFpClient, SendAck) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr nearby_event_MessageStreamReceived kMessage{ + .peer_address = kPeerAddress, + .message_group = 20, + .message_code = 10, + }; + 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())); +} + +TEST(NearbyFpClient, SendNack) { + constexpr uint64_t kPeerAddress = 0x123456; + constexpr uint8_t kFailReason = 30; + constexpr nearby_event_MessageStreamReceived kMessage{ + .peer_address = kPeerAddress, + .message_group = 20, + .message_code = 10, + }; + 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())); +} + +#endif /* NEARBY_FP_MESSAGE_STREAM */ + +TEST(NearbyFpClient, TimerTriggered_RotatesBleAddress) { + uint64_t firstAddress, secondAddress, thirdAddress; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetInPairingMode(false); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); + firstAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetRandomNumber(30); + nearby_test_fakes_SetCurrentTimeMs(nearby_test_fakes_GetNextTimerMs()); + secondAddress = nearby_platform_GetBleAddress(); + nearby_test_fakes_SetRandomNumber(31); + nearby_test_fakes_SetCurrentTimeMs(nearby_test_fakes_GetNextTimerMs()); + thirdAddress = nearby_platform_GetBleAddress(); + + ASSERT_NE(firstAddress, secondAddress); + ASSERT_NE(secondAddress, thirdAddress); +} + +TEST(NearbyFpClient, TimerTriggered_InPairingMode_DoesntRotateBleAddress) { + uint64_t firstAddress, secondAddress, thirdAddress; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetInPairingMode(false); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE); + firstAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetInPairingMode(true); + nearby_test_fakes_SetRandomNumber(30); + nearby_test_fakes_SetCurrentTimeMs(nearby_test_fakes_GetNextTimerMs()); + secondAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetInPairingMode(false); + nearby_test_fakes_SetRandomNumber(31); + nearby_test_fakes_SetCurrentTimeMs(nearby_test_fakes_GetNextTimerMs()); + thirdAddress = nearby_platform_GetBleAddress(); + + ASSERT_EQ(firstAddress, secondAddress); + ASSERT_NE(secondAddress, thirdAddress); +} + +TEST(NearbyFpClient, AdvertiseDisoverable_RotatesBleAddress) { + uint64_t firstAddress; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetInPairingMode(false); + firstAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetRandomNumber(32); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); + + ASSERT_NE(firstAddress, nearby_platform_GetBleAddress()); +} + +TEST(NearbyFpClient, + AdvertiseDisoverable_InPairingMode_DoesntRotatesBleAddress) { + uint64_t firstAddress; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetInPairingMode(true); + firstAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetRandomNumber(32); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); + + ASSERT_EQ(firstAddress, nearby_platform_GetBleAddress()); +} + +TEST(NearbyFpClient, + ChangeAdvertisementType_InPairingMode_DoesntRotateBleAddress) { + uint64_t firstAddress; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetInPairingMode(true); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE); + firstAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetRandomNumber(34); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_DISCOVERABLE); + + ASSERT_EQ(firstAddress, nearby_platform_GetBleAddress()); +} + +TEST(NearbyFpClient, ChangeAdvertisementFlags_DoesntRotateBleAddress) { + uint64_t firstAddress; + nearby_fp_client_Init(NULL); + nearby_test_fakes_SetInPairingMode(false); + nearby_fp_client_SetAdvertisement(NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE); + firstAddress = nearby_platform_GetBleAddress(); + + nearby_test_fakes_SetRandomNumber(36); + nearby_fp_client_SetAdvertisement( + NEARBY_FP_ADVERTISEMENT_NON_DISCOVERABLE | + NEARBY_FP_ADVERTISEMENT_PAIRING_UI_INDICATOR); + + ASSERT_EQ(firstAddress, nearby_platform_GetBleAddress()); +} + +#pragma GCC diagnostic pop + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/embedded/common/source/nearby_assert.h b/embedded/common/source/nearby_assert.h new file mode 100644 index 0000000000..2a2b01b201 --- /dev/null +++ b/embedded/common/source/nearby_assert.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef NEARBY_ASSERT_H +#define NEARBY_ASSERT_H + +#include "nearby.h" +#include "nearby_platform_trace.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef __NEARBY_SHORT_FILE__ +#define NEARBY_ASSERT(cond) \ + do { \ + if (!(cond)) { \ + nearby_platfrom_CrashOnAssert(__NEARBY_SHORT_FILE__, __LINE__, #cond); \ + } \ + } while (0) +#else +#define NEARBY_ASSERT(cond) \ + do { \ + if (!(cond)) { \ + nearby_platfrom_CrashOnAssert(__FILE__, __LINE__, #cond); \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_ASSERT_H */ diff --git a/embedded/common/source/nearby_config.h b/embedded/common/source/nearby_config.h new file mode 100644 index 0000000000..37f7e5e25b --- /dev/null +++ b/embedded/common/source/nearby_config.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef NEARBY_CONFIG_H +#define NEARBY_CONFIG_H + +// Support FP Battery Notification extension +#define NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + +// Support FP Additional Data extension +#define 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 + +// Does the platform have a native BLE address rotation routine? +// #define NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION + +// 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 + +// The maximum number of concurrent RFCOMM connections +#define NEARBY_MAX_RFCOMM_CONNECTIONS 2 + +// Support Retroactive pairing extension +#define NEARBY_FP_RETROACTIVE_PAIRING + +// The maximum number of concurrent retroactive pairing process +#define NEARBY_MAX_RETROACTIVE_PAIRING 2 + +#endif /* NEARBY_CONFIG_H */ diff --git a/embedded/common/source/nearby_event.h b/embedded/common/source/nearby_event.h new file mode 100644 index 0000000000..74d76905d8 --- /dev/null +++ b/embedded/common/source/nearby_event.h @@ -0,0 +1,140 @@ +// 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. + +#ifndef NEARBY_EVENT_H +#define NEARBY_EVENT_H + +#include "nearby.h" + +// Types of events that can be emitted by Nearby library +typedef enum { + // A client connected over RFCOMM to the message stream endpoint defined in + // Fast Pair specification + // Payload: |nearby_event_MessageStreamConnected| + kNearbyEventMessageStreamConnected = 0, + // An RFCOMM client disconnected + // Payload: |nearby_event_MessageStreamDisonnected| + kNearbyEventMessageStreamDisconnected, + // A client has sent a message over the message stream. Some incoming messages + // are consumed by the Nearby library, in which case the event is not emitted. + // Payload: |nearby_event_MessageStreamReceived| + kNearbyEventMessageStreamReceived +} nearby_event_Type; + +// An event emitted by Nearby library +typedef struct { + // The type of the event + nearby_event_Type event_type; + // Optional event payload. |payload| should be cast to one the structs defined + // below. + uint8_t *payload; +} nearby_event_Event; + +// Payload for |kNearbyEventMessageStreamConnected| event. +typedef struct { + // The client BT address + uint64_t peer_address; +} nearby_event_MessageStreamConnected; + +// Payload for |kNearbyEventMessageStreamDisconnected| event. +typedef struct { + // The client BT address + uint64_t peer_address; +} nearby_event_MessageStreamDisconnected; + +// Message group Bluetooth +#define MESSAGE_GROUP_BLUETOOTH 1 + +// If the device supports silence mode, the client app should send either +// ENABLE_SILENCE_MODE or DISABLE_SILENCE_MODE message to the seeker once RFCOMM +// is connected to ensure the correct state is propagated. +// Direction: Provider -> Seeker +// Handled by: Client app +#define MESSAGE_CODE_ENABLE_SILENCE_MODE 1 + +// Direction: Provider -> Seeker +// Handled by: Client app +#define MESSAGE_CODE_DISABLE_SILENCE_MODE 2 + +// Message group Companion App Event +#define MESSAGE_GROUP_COMPANION_APP_EVENT 2 + +// Direction: Provider -> Seeker +// Handled by: Client app +#define MESSAGE_CODE_LOG_BUFFER_FULL 1 + +// Message group Device Information Event +#define MESSAGE_GROUP_DEVICE_INFORMATION_EVENT 3 + +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +#define MESSAGE_CODE_MODEL_ID 1 + +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +#define MESSAGE_CODE_BLE_ADDRESS_UPDATED 2 + +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +#define MESSAGE_CODE_BATTERY_UPDATED 3 + +// Direction: Provider -> Seeker +// Handled by: Nearby Fast Pair library +#define MESSAGE_CODE_REMAINING_BATTERY_TIME 4 + +// Direction: Seeker -> Provider +// Handled by: Client app +#define MESSAGE_CODE_ACTIVE_COMPONENT_REQUEST 5 + +// Direction: Provider -> Seeker +// Handled by: Client app +#define MESSAGE_CODE_ACTIVE_COMPONENT_RESPONSE 6 + +// Direction: Provider -> Seeker +// Handled by: Client app +#define MESSAGE_CODE_CAPABILITIES 7 + +#define MESSAGE_CODE_CAPABILITIES_COMPANION_APP_INSTALLED 0 +#define MESSAGE_CODE_CAPABILITIES_SILENCE_MODE_SUPPORTED 1 + +// Direction: Seeker -> Provider +// Handled by: Client app +#define MESSAGE_CODE_PLATFORM_TYPE 8 + +// Message group Device Action Event +#define MESSAGE_GROUP_DEVICE_ACTION_EVENT 4 + +// Direction: Seeker -> Provider +// Handled by: Client app +#define MESSAGE_CODE_RING 1 + +#define MESSAGE_CODE_RING_LEFT 0 +#define MESSAGE_CODE_RING_RIGHT 1 + +// Payload for |kNearbyEventMessageStreamReceived| event. See +// https://developers.google.com/nearby/fast-pair/spec#MessageStream +typedef struct { + // The peer BT address + uint64_t peer_address; + // Message group + uint8_t message_group; + // Message code + uint8_t message_code; + // If |length| is 0, then there's no additional data in the message + size_t length; + // Optional. Addtional message data + uint8_t *data; +} nearby_event_MessageStreamReceived; + +#endif /* NEARBY_EVENT_H */ diff --git a/embedded/common/source/nearby_fp_library.c b/embedded/common/source/nearby_fp_library.c new file mode 100644 index 0000000000..adf656fb4d --- /dev/null +++ b/embedded/common/source/nearby_fp_library.c @@ -0,0 +1,452 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nearby_fp_library.h" + +#include + +#include "nearby.h" +#include "nearby_assert.h" +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#include "nearby_platform_battery.h" +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ +#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 +#define SHOW_PAIRING_INDICATION_BYTE 0 +#define DONT_SHOW_PAIRING_INDICATION_BYTE 2 +#define SHOW_BATTERY_INDICATION_BYTE 0x33 +#define DONT_SHOW_BATTERY_INDICATION_BYTE 0x34 +#define BATTERY_INFO_CHARGING (1 << 7) +#define BATTERY_INFO_NOT_CHARGING 0 +// 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 BATTERY_INFO_SIZE_BYTES 4 +#define KEY_BASED_PAIRING_RESPONSE_FLAG 0x01 +#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 + +static uint8_t account_key_list[ACCOUNT_KEY_LIST_SIZE_BYTES]; + +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()); +} + +size_t nearby_fp_GetAccountKeyCount() { return account_key_list[0]; } + +size_t nearby_fp_GetAccountKeyOffset(unsigned key_number) { + return 1 + key_number * ACCOUNT_KEY_SIZE_BYTES; +} + +const uint8_t* nearby_fp_GetAccountKey(unsigned key_number) { + NEARBY_ASSERT(key_number < nearby_fp_GetAccountKeyCount()); + return account_key_list + nearby_fp_GetAccountKeyOffset(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); +} + +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); +} + +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]) { + // 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)) { + 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]++; + } + 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); + + size_t i = 0; + + // data size + output[i++] = 1 + FP_SERVICE_UUID_SIZE + FP_MODEL_ID_SIZE; + + // data type service + output[i++] = GAP_DATA_TYPE_SERVICE_DATA_UUID; + + // service data uuid + nearby_utils_CopyLittleEndian(output + i, FP_SERVICE_UUID, + FP_SERVICE_UUID_SIZE); + i += FP_SERVICE_UUID_SIZE; + + // service data + uint32_t model = nearby_platform_GetModelId(); + nearby_utils_CopyBigEndian(output + i, model, FP_MODEL_ID_SIZE); + i += FP_MODEL_ID_SIZE; + + return i; +} + +#ifdef 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 + : BATTERY_INFO_NOT_CHARGING; + output[0] = + charging | (battery_info->left_bud_battery_level & BATTERY_LEVEL_MASK); + output[1] = + charging | (battery_info->right_bud_battery_level & BATTERY_LEVEL_MASK); + output[2] = charging | + (battery_info->charging_case_battery_level & BATTERY_LEVEL_MASK); +} +static void AddBatteryInfo(uint8_t* output, size_t length, + bool show_ui_indicator, + const nearby_platform_BatteryInfo* battery_info) { + NEARBY_ASSERT(length >= BATTERY_INFO_SIZE_BYTES); + output[0] = show_ui_indicator ? SHOW_BATTERY_INDICATION_BYTE + : DONT_SHOW_BATTERY_INDICATION_BYTE; + SerializeBatteryInfo(output + 1, battery_info); +} + +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +static size_t CreateNondiscoverableAdvertisement( + uint8_t* output, size_t length, bool show_pairing_indicator +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + , + bool show_battery_indicator, const nearby_platform_BatteryInfo* battery_info +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ +) { + NEARBY_TRACE(VERBOSE, "nearby_fp_CreateNondiscoverableAdvertisement"); + const unsigned kHeaderSize = FP_SERVICE_UUID_SIZE + 2; + NEARBY_ASSERT(length >= kHeaderSize); + + unsigned i = 1; + + // service data UUID + output[i++] = GAP_DATA_TYPE_SERVICE_DATA_UUID; + + // service uuid + nearby_utils_CopyLittleEndian(output + i, FP_SERVICE_UUID, + FP_SERVICE_UUID_SIZE); + i += FP_SERVICE_UUID_SIZE; + + // service data + uint8_t salt = nearby_platform_Rand(); + size_t n = nearby_fp_GetAccountKeyCount(); + if (n == 0) { + const unsigned kMessageSize = 2; + NEARBY_ASSERT(length >= i + kMessageSize); + output[i++] = 0x00; + output[i++] = 0x00; + } else { + const unsigned kFlagFilterAndSaltSize = 4; + 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 + // 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 + output[i++] = 0; + // filter length and type + output[i++] = combineNibbles(s, show_pairing_indicator + ? 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 + if (battery_info != NULL) { + i += BATTERY_INFO_SIZE_BYTES; + } +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + } + + // service data size + output[0] = i - 1; + + return i; +} + +size_t nearby_fp_CreateNondiscoverableAdvertisement( + uint8_t* output, size_t length, bool show_pairing_indicator) { +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION + return CreateNondiscoverableAdvertisement( + output, length, show_pairing_indicator, false, NULL); +#else + return CreateNondiscoverableAdvertisement(output, length, + show_pairing_indicator); +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ +} + +#ifdef 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( + output, length, show_pairing_indicator, show_battery_indicator, + battery_info); +} +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +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; + + return offset; +} + +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); +} + +nearby_platform_status nearby_fp_SaveAccountKeys() { + return nearby_platform_SaveValue(kStoredKeyAccountKeyList, account_key_list, + GetAccountKeyListUsedSize()); +} + +nearby_platform_status nearby_fp_GattReadModelId(uint8_t* output, + size_t* length) { + NEARBY_ASSERT(*length >= FP_MODEL_ID_SIZE); + uint32_t model = nearby_platform_GetModelId(); + nearby_utils_CopyBigEndian(output, model, FP_MODEL_ID_SIZE); + *length = FP_MODEL_ID_SIZE; + return kNearbyStatusOK; +} + +nearby_platform_status nearby_fp_CreateSharedSecret( + const uint8_t remote_public_key[64], + uint8_t output[ACCOUNT_KEY_SIZE_BYTES]) { + nearby_platform_status status; + uint8_t secret[32]; + uint8_t hash[32]; + status = nearby_platform_GenSec256r1Secret(remote_public_key, secret); + if (status != kNearbyStatusOK) return status; + + status = nearby_fp_Sha256(hash, secret, sizeof(secret)); + if (status != kNearbyStatusOK) return status; + + memcpy(output, hash, ACCOUNT_KEY_SIZE_BYTES); + return status; +} + +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(), + BT_ADDRESS_LENGTH); + int i; + for (i = 1 + BT_ADDRESS_LENGTH; 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; + 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], + 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_IF_ERROR(nearby_platform_Sha256Finish(out)); + return kNearbyStatusOK; +} + +nearby_platform_status nearby_fp_HmacSha256(uint8_t out[32], const uint8_t* key, + size_t key_length, + const uint8_t* data, + size_t data_length) { + uint8_t hmac_key[HMAC_SHA256_KEY_SIZE]; + // out = HASH(Key XOR ipad, data) + PadKey(hmac_key, key, key_length, IPAD); + 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); +} + +nearby_platform_status nearby_fp_AesCtr( + uint8_t* message, size_t message_length, + const uint8_t key[AES_MESSAGE_SIZE_BYTES]) { + uint8_t key_stream[AES_MESSAGE_SIZE_BYTES]; + uint8_t iv[AES_MESSAGE_SIZE_BYTES]; + size_t offset = NONCE_SIZE; + memset(iv, 0, AES_MESSAGE_SIZE_BYTES - NONCE_SIZE); + memcpy(iv + AES_MESSAGE_SIZE_BYTES - NONCE_SIZE, message, NONCE_SIZE); + + while (offset < message_length) { + int i; + int 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]; + } + iv[0]++; + } + return kNearbyStatusOK; +} + +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); + uint8_t sha[32]; + RETURN_IF_ERROR(nearby_fp_HmacSha256(sha, key, ACCOUNT_KEY_SIZE_BYTES, + data + ADDITIONAL_DATA_SHA_SIZE, + length - ADDITIONAL_DATA_SHA_SIZE)); + if (memcmp(sha, data, ADDITIONAL_DATA_SHA_SIZE) != 0) { + NEARBY_TRACE(WARNING, "Additional Data SHA check failed"); + return kNearbyStatusError; + } + return nearby_fp_AesCtr(data + ADDITIONAL_DATA_SHA_SIZE, + length - ADDITIONAL_DATA_SHA_SIZE, key); +} + +nearby_platform_status nearby_fp_EncodeAdditionalData( + uint8_t* data, size_t length, const uint8_t key[ACCOUNT_KEY_SIZE_BYTES]) { + NEARBY_ASSERT(length >= ADDITIONAL_DATA_SHA_SIZE + NONCE_SIZE); + uint8_t sha[32]; + int i; + for (i = 0; i < NONCE_SIZE; i++) { + data[ADDITIONAL_DATA_SHA_SIZE + i] = nearby_platform_Rand(); + } + RETURN_IF_ERROR(nearby_fp_AesCtr(data + ADDITIONAL_DATA_SHA_SIZE, + length - ADDITIONAL_DATA_SHA_SIZE, key)); + RETURN_IF_ERROR(nearby_fp_HmacSha256(sha, key, ACCOUNT_KEY_SIZE_BYTES, + data + ADDITIONAL_DATA_SHA_SIZE, + length - ADDITIONAL_DATA_SHA_SIZE)); + memcpy(data, sha, ADDITIONAL_DATA_SHA_SIZE); + return kNearbyStatusOK; +} +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + +// Computes sha256 sum. +nearby_platform_status nearby_fp_Sha256(uint8_t out[32], const void* data, + size_t length) { + nearby_platform_status status = nearby_platform_Sha256Start(); + if (status == kNearbyStatusOK) { + status = nearby_platform_Sha256Update(data, length); + if (status == kNearbyStatusOK) { + status = nearby_platform_Sha256Finish(out); + } + } + return status; +} diff --git a/embedded/common/source/nearby_fp_library.h b/embedded/common/source/nearby_fp_library.h new file mode 100644 index 0000000000..2e2762397c --- /dev/null +++ b/embedded/common/source/nearby_fp_library.h @@ -0,0 +1,187 @@ +// 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. + +#ifndef NEARBY_FP_LIBRARY_H +#define NEARBY_FP_LIBRARY_H + +// clang-format off +#include "nearby_config.h" +// clang-format on + +#include "nearby.h" +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +#include "nearby_platform_battery.h" +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#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 + +#define ADDITIONAL_DATA_HEADER_SIZE 16 + +// Creates advertisement with the Model Id. Returns the number of bytes written +// to |output|. +// +// output - Advertisement data output buffer. +// length - Length of output buffer. +size_t nearby_fp_CreateDiscoverableAdvertisement(uint8_t* output, + size_t length); + +// Creates advertisement with Account Key Data. Returns the number of bytes +// written to |output|. +// +// output - Advertisement data output buffer. +// length - Length of data output buffer. +// show_ui_indicator - Ask seeker to show UI indication. +size_t nearby_fp_CreateNondiscoverableAdvertisement( + uint8_t* output, size_t length, bool show_pairing_indicator); + +#ifdef NEARBY_FP_ENABLE_BATTERY_NOTIFICATION +void SerializeBatteryInfo(uint8_t* output, + const nearby_platform_BatteryInfo* battery_info); + +// Creates advertisement with Account Key Data and optionally battery info. +// |battery_info| can be NULL. Returns the number of bytes written to |output|. +// +// output - Advertisement data output buffer. +// length - Length of output buffer. +// show_ui_indicator - Ask seeker to show UI indication. +// battery_info - Battery status data structure. +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); +#endif /* NEARBY_FP_ENABLE_BATTERY_NOTIFICATION */ + +// Appends TX power stanza to the advertisement. |Advertisement| should point +// past the end of advertisement created with one of the 'Create*' functions. +// +// advertisement - Pointer to end of advertising data. +// length - Remaining length of advertising data. +// power - Power level. +size_t nearby_fp_AppendTxPower(uint8_t* advertisement, size_t length, + int8_t tx_power); + +// Loads account keys from NV storage. +nearby_platform_status nearby_fp_LoadAccountKeys(); +// Saves account keys to NV storage. +nearby_platform_status nearby_fp_SaveAccountKeys(); + +// Gets Specific key by number to buffer +// +// dest - Buffer for key fetched. +// key_number - Number of key to fetch. +void nearby_fp_CopyAccountKey(uint8_t* 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 pointer to given key by ordinal number. +// +// key_number - Ordinal number of key to return. +const uint8_t* 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]); +// Gets fast pair model ID +// +// output - Buffer to hold model ID. +// length - On input, contains the length of the buffer. +// On output, returns the length of the model ID returned. +nearby_platform_status nearby_fp_GattReadModelId(uint8_t* output, + size_t* length); +// Creates shared secret from remote public key. +// +// remote_public_key - 512 bit peer public key. +// output - Buffer that returns the shared secret. +nearby_platform_status nearby_fp_CreateSharedSecret( + const uint8_t remote_public_key[64], + uint8_t output[ACCOUNT_KEY_SIZE_BYTES]); + +// Creates raw key based paring response. +// +// output - Buffer returning the pairing response. +nearby_platform_status nearby_fp_CreateRawKeybasedPairingResponse( + uint8_t output[AES_MESSAGE_SIZE_BYTES]); + +#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. +nearby_platform_status nearby_fp_HmacSha256(uint8_t out[32], const uint8_t* key, + size_t key_length, + const uint8_t* data, + size_t data_length); + +// The first 8 bytes of the message are the nonce. The message is +// encrypted/decrypted in place. +// +// message - Message buffer in/out. +// message_length - Length of message. +// key - Key. +nearby_platform_status nearby_fp_AesCtr( + uint8_t* message, size_t message_length, + const uint8_t key[AES_MESSAGE_SIZE_BYTES]); + +// Decodes data package read from Additional Data characteristics. The decoding +// happens in-place. Returns an error if HMAC-SHA checksum is invalid. +// +// data - Data to decode. +// length - Length of data. +// key - Key. +nearby_platform_status nearby_fp_DecodeAdditionalData( + uint8_t* data, size_t length, const uint8_t key[ACCOUNT_KEY_SIZE_BYTES]); + +// Encodes data package for writing to Additional Data characteristics. The +// encoding happens in-place. The first 16 bytes of the data buffer are used for +// checksum and nonce, so the actual message must begin at offset 16. +// +// data - Data to encode. +// length - Length of data. +// key - Key. +nearby_platform_status nearby_fp_EncodeAdditionalData( + uint8_t* data, size_t length, const uint8_t key[ACCOUNT_KEY_SIZE_BYTES]); + +#endif /* NEARBY_FP_ENABLE_ADDITIONAL_DATA */ + +// Computes sha256 sum. +// +// out - Output 256 bit sum. +// data - Data to compute sha256 sum over. +// length - Length of data. +nearby_platform_status nearby_fp_Sha256(uint8_t out[32], const void* data, + size_t length); + +#ifdef __cplusplus +} +#endif + +#endif // NEARBY_FP_LIBRARY_H diff --git a/embedded/common/source/nearby_message_stream.c b/embedded/common/source/nearby_message_stream.c new file mode 100644 index 0000000000..19d64d693e --- /dev/null +++ b/embedded/common/source/nearby_message_stream.c @@ -0,0 +1,126 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nearby_message_stream.h" + +#include "nearby_platform_bt.h" + +#define HEADER_SIZE 4 +#define ACK_MESSAGE_SIZE 6 +#define NACK_MESSAGE_SIZE 7 +#define ACKNOWLEDGEMENT_GROUP 0xFF +#define ACK_CODE 1 +#define NACK_CODE 2 + +#ifdef 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; + memset(metadata, 0, sizeof(nearby_message_stream_Metadata)); + metadata->message.data = + state->buffer + sizeof(nearby_message_stream_Metadata); +} + +void nearby_message_stream_Read(const nearby_message_stream_State* state, + const uint8_t* data, size_t length) { + nearby_message_stream_Metadata* metadata = + (nearby_message_stream_Metadata*)state->buffer; + nearby_message_stream_Message* message = &metadata->message; + uint16_t available_space = + state->length - sizeof(nearby_message_stream_Metadata); + while (length > 0) { + switch (metadata->bytes_read) { + case 0: + message->message_group = *data; + break; + case 1: + message->message_code = *data; + break; + case 2: + message->length = ((uint16_t)*data) << 8; + break; + case 3: + message->length += *data; + break; + default: { + uint16_t offset = metadata->bytes_read - HEADER_SIZE; + if (offset < message->length && offset < available_space) { + message->data[offset] = *data; + } + } + } + data++; + length--; + metadata->bytes_read++; + if (metadata->bytes_read - HEADER_SIZE == message->length) { + if (message->length > available_space) { + // Message truncated + message->length = available_space; + } + state->on_message_received(state->peer_address, message); + message->length = 0; + metadata->bytes_read = 0; + } + } +} + +nearby_platform_status nearby_message_stream_Send( + uint64_t peer_address, const nearby_message_stream_Message* message) { + nearby_platform_status status; + uint8_t header[HEADER_SIZE]; + header[0] = message->message_group; + header[1] = message->message_code; + header[2] = message->length >> 8; + header[3] = message->length; + status = + nearby_platform_SendMessageStream(peer_address, header, sizeof(header)); + if (kNearbyStatusOK == status && message->length > 0) { + status = nearby_platform_SendMessageStream(peer_address, message->data, + message->length); + } + return status; +} + +nearby_platform_status nearby_message_stream_SendAck( + uint64_t peer_address, const nearby_message_stream_Message* message) { + uint8_t ack[ACK_MESSAGE_SIZE]; + ack[0] = ACKNOWLEDGEMENT_GROUP; + ack[1] = ACK_CODE; + ack[2] = 0; + ack[3] = ACK_MESSAGE_SIZE - HEADER_SIZE; + ack[4] = message->message_group; + ack[5] = message->message_code; + return nearby_platform_SendMessageStream(peer_address, ack, sizeof(ack)); +} + +nearby_platform_status nearby_message_stream_SendNack( + uint64_t peer_address, const nearby_message_stream_Message* message, + uint8_t fail_reason) { + uint8_t nack[NACK_MESSAGE_SIZE]; + nack[0] = ACKNOWLEDGEMENT_GROUP; + nack[1] = NACK_CODE; + nack[2] = 0; + nack[3] = NACK_MESSAGE_SIZE - HEADER_SIZE; + nack[4] = fail_reason; + nack[5] = message->message_group; + nack[6] = message->message_code; + return nearby_platform_SendMessageStream(peer_address, nack, sizeof(nack)); +} + +uint16_t nearby_message_stream_GetMaxPayloadSize( + const nearby_message_stream_State* state) { + return state->length - sizeof(nearby_message_stream_Metadata); +} + +#endif /* NEARBY_FP_MESSAGE_STREAM */ diff --git a/embedded/common/source/nearby_message_stream.h b/embedded/common/source/nearby_message_stream.h new file mode 100644 index 0000000000..58d4e1e107 --- /dev/null +++ b/embedded/common/source/nearby_message_stream.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef NEARBY_MESSAGE_STREAM_H +#define NEARBY_MESSAGE_STREAM_H + +// clang-format off +#include "nearby_config.h" +// clang-format on + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +// Message group and codes for ACK and NACK messages +#define ACKNOWLEDGEMENT_GROUP 0xFF +#define ACK_CODE 1 +#define NACK_CODE 2 + +#define FAIL_REASON_REDUNDANT_DEVICE_ACTION 4 + +// Message sent and received over the message stream +// See: https://developers.google.com/nearby/fast-pair/spec#MessageStream +typedef struct { + uint8_t message_group; + uint8_t message_code; + // |data| length in native encoding + uint16_t length; + // message payload in big endian encoding + uint8_t* data; +} nearby_message_stream_Message; + +// State and configuration of message stream parser +typedef struct { + // Callback triggered when a complete message is read from the input stream + void (*on_message_received)(uint64_t peer_address, + nearby_message_stream_Message* message); + // |peer_address| that is passed to |on_message_received| + uint64_t peer_address; + // Length of |buffer| + size_t length; + // The buffer used for storing an incoming message. The parser uses the buffer + // to store nearby_message_stream_Metadata, so not all of the space is + // available for message payload + uint8_t* buffer; +} nearby_message_stream_State; + +typedef struct { + nearby_message_stream_Message message; + uint16_t bytes_read; +} nearby_message_stream_Metadata; + +#ifdef NEARBY_FP_MESSAGE_STREAM +// Initializes the parser +void nearby_message_stream_Init(const nearby_message_stream_State* state); + +// Reads and deserializes data from an input stream. It is OK to pass in +// incomplete packets. When the parses reads a complete message, it calls +// |on_message_received|. If the message payload is too big to fit the buffer - +// bigger than GetMaxPayloadSize(), then the payload is truncated. +void nearby_message_stream_Read(const nearby_message_stream_State* state, + const uint8_t* data, size_t length); + +// Serializes and sends |message| out using nearby_platform_SendMessageStream() +nearby_platform_status nearby_message_stream_Send( + uint64_t peer_address, const nearby_message_stream_Message* message); + +// Sends out an ACK message for a received |message|. Note that not all messages +// require an ACK +nearby_platform_status nearby_message_stream_SendAck( + uint64_t peer_address, const nearby_message_stream_Message* message); + +// Sends out a NACK message for a received |message| with |fail_reason| reason. +nearby_platform_status nearby_message_stream_SendNack( + uint64_t peer_address, const nearby_message_stream_Message* message, + uint8_t fail_reason); + +// Returns the maximum message payload size +uint16_t nearby_message_stream_GetMaxPayloadSize( + const nearby_message_stream_State* state); + +#endif /* NEARBY_FP_MESSAGE_STREAM */ +#ifdef __cplusplus +} +#endif + +#endif // NEARBY_MESSAGE_STREAM_H diff --git a/embedded/common/source/nearby_trace.h b/embedded/common/source/nearby_trace.h new file mode 100644 index 0000000000..27f55da18c --- /dev/null +++ b/embedded/common/source/nearby_trace.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef NEARBY_TRACE_H +#define NEARBY_TRACE_H + +#include "nearby.h" +#include "nearby_platform_trace.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +// Must be kept in sync with TraceLevel in nearby_platform_trace.h +#define NEARBY_TRACE_LEVEL_VERBOSE (1) +#define NEARBY_TRACE_LEVEL_DEBUG (2) +#define NEARBY_TRACE_LEVEL_INFO (3) +#define NEARBY_TRACE_LEVEL_WARNING (4) +#define NEARBY_TRACE_LEVEL_ERROR (5) +#define NEARBY_TRACE_LEVEL_OFF (6) + +#if NEARBY_TRACE_LEVEL < NEARBY_TRACE_LEVEL_VERBOSE || \ + NEARBY_TRACE_LEVEL > NEARBY_TRACE_LEVEL_OFF +#error "Invalid definition of NEARBY_TRACE_LEVEL." +#endif + +#if NEARBY_TRACE_LEVEL > NEARBY_TRACE_LEVEL_VERBOSE +#define NEARBY_TRACE_VERBOSE(level, ...) +#else +#define NEARBY_TRACE_VERBOSE(level, ...) _NEARBY_TRACE(level, __VA_ARGS__) +#define NEARBY_ALL_ENUM_TO_STR_ENABLE 1 +#endif + +#if NEARBY_TRACE_LEVEL > NEARBY_TRACE_LEVEL_DEBUG +#define NEARBY_TRACE_DEBUG(level, ...) +#else +#define NEARBY_TRACE_DEBUG(level, ...) _NEARBY_TRACE(level, __VA_ARGS__) +#endif + +#if NEARBY_TRACE_LEVEL > NEARBY_TRACE_LEVEL_INFO +#define NEARBY_TRACE_INFO(level, ...) +#else +#define NEARBY_TRACE_INFO(level, ...) _NEARBY_TRACE(level, __VA_ARGS__) +#endif + +#if NEARBY_TRACE_LEVEL > NEARBY_TRACE_LEVEL_WARNING +#define NEARBY_TRACE_WARNING(level, ...) +#else +#define NEARBY_TRACE_WARNING(level, ...) _NEARBY_TRACE(level, __VA_ARGS__) +#endif + +#if NEARBY_TRACE_LEVEL > NEARBY_TRACE_LEVEL_ERROR +#define NEARBY_TRACE_ERROR(level, ...) +#else +#define NEARBY_TRACE_ERROR(level, ...) _NEARBY_TRACE(level, __VA_ARGS__) +#endif + +#ifndef __NEARBY_SHORT_FILE__ +#define __NEARBY_SHORT_FILE__ __FILE__ +#endif + +#define _NEARBY_TRACE(level, ...) \ + nearby_platform_Trace(level, __NEARBY_SHORT_FILE__, __LINE__, \ + "@g " __VA_ARGS__); + +#define NEARBY_TRACE(level, ...) \ + NEARBY_TRACE_##level((nearby_platform_TraceLevel)NEARBY_TRACE_LEVEL_##level, \ + __VA_ARGS__) +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_TRACE_H */ diff --git a/embedded/common/source/nearby_utils.c b/embedded/common/source/nearby_utils.c new file mode 100644 index 0000000000..5fa665c300 --- /dev/null +++ b/embedded/common/source/nearby_utils.c @@ -0,0 +1,96 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nearby_utils.h" + +uint8_t nearby_utils_GetByte(uint64_t source, int byteNumber) { + return source >> (8 * byteNumber); +} + +void nearby_utils_CopyBigEndian(uint8_t* dest, uint64_t source, int bytes) { + int i; + for (i = 0; i < bytes; i++) { + dest[bytes - i - 1] = nearby_utils_GetByte(source, i); + } +} + +void nearby_utils_CopyLittleEndian(uint8_t* dest, uint64_t source, int bytes) { + int i; + for (i = 0; i < bytes; i++) { + dest[i] = nearby_utils_GetByte(source, i); + } +} + +uint32_t nearby_utils_GetBigEndian24(uint8_t* buffer) { + return (256 * 256 * buffer[0]) + (256 * buffer[1]) + buffer[2]; +} + +uint32_t nearby_utils_GetBigEndian32(uint8_t* buffer) { + return (256 * 256 * 256 * buffer[0]) + (256 * 256 * buffer[1]) + + (256 * buffer[2]) + buffer[3]; +} + +uint64_t nearby_utils_GetBigEndian48(uint8_t* b) { + return (((uint64_t)b[0]) << 40) | (((uint64_t)b[1]) << 32) | + (((uint64_t)b[2]) << 24) | (((uint64_t)b[3]) << 16) | + (((uint64_t)b[4]) << 8) | (uint64_t)b[5]; +} + +static char NibbleToHex(uint8_t value) { + if (value < 10) return '0' + value; + value -= 10; + if (value < 6) return 'A' + value; + return '?'; +} + +const char* nearby_utils_ArrayToString(const uint8_t* data, size_t length) { + static char buffer[80]; + size_t buffer_size = sizeof(buffer); + size_t num_bytes; + if (2 * length < buffer_size - 1) { + num_bytes = 2 * length; + buffer[num_bytes] = 0; + } else { + // The buffer is too small to hold all of data. Let's truncate the data and + // append an ellipsis. + num_bytes = buffer_size - 4; + buffer[buffer_size - 1] = 0; + buffer[buffer_size - 2] = '.'; + buffer[buffer_size - 3] = '.'; + buffer[buffer_size - 4] = '.'; + num_bytes = buffer_size - 4; + } + const uint8_t* input = data; + char* output = buffer; + for (size_t i = 0; i < num_bytes; i += 2) { + uint8_t x = *input++; + *output++ = NibbleToHex(x >> 4); + *output++ = NibbleToHex(x & 0x0F); + } + return buffer; +} + +const char* nearby_utils_MacToString(uint64_t address) { + static char buffer[18]; + char* p = buffer + sizeof(buffer) - 1; + *p-- = 0; + while (p > buffer) { + *p-- = NibbleToHex(address & 0xF); + address >>= 4; + *p-- = NibbleToHex(address & 0xF); + address >>= 4; + if (p >= buffer) *p-- = ':'; + } + return buffer; +} diff --git a/embedded/common/source/nearby_utils.cc b/embedded/common/source/nearby_utils.cc new file mode 100644 index 0000000000..c643dbffe9 --- /dev/null +++ b/embedded/common/source/nearby_utils.cc @@ -0,0 +1,14 @@ +// 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. + diff --git a/embedded/common/source/nearby_utils.h b/embedded/common/source/nearby_utils.h new file mode 100644 index 0000000000..5e24fba8d5 --- /dev/null +++ b/embedded/common/source/nearby_utils.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef NEARBY_UTILS_H +#define NEARBY_UTILS_H + +#include "nearby_types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +uint8_t nearby_utils_GetByte(uint64_t source, int byteNumber); + +void nearby_utils_CopyBigEndian(uint8_t* dest, uint64_t source, int bytes); +void nearby_utils_CopyLittleEndian(uint8_t* dest, uint64_t source, int bytes); + +uint32_t nearby_utils_GetBigEndian24(uint8_t* buffer); +uint32_t nearby_utils_GetBigEndian32(uint8_t* buffer); +uint64_t nearby_utils_GetBigEndian48(uint8_t* buffer); + +// Converts the data to a hex string for debugging. Returns a pointer to a +// static buffer. Don't call it twice in the same log message! +const char* nearby_utils_ArrayToString(const uint8_t* data, size_t data_length); + +// Converts BT MAC address to string for debugging. Returns a pointer to a +// static buffer. Don't call it twice in the same log message! +const char* nearby_utils_MacToString(uint64_t address); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_UTILS_H */ diff --git a/embedded/common/target/gLinux/nearby_types.h b/embedded/common/target/gLinux/nearby_types.h new file mode 100644 index 0000000000..8abec31883 --- /dev/null +++ b/embedded/common/target/gLinux/nearby_types.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef NEARBY_TYPES_H +#define NEARBY_TYPES_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef __cplusplus +} +#endif + +#endif // NEARBY_TYPES_H diff --git a/embedded/common/target/nearby.h b/embedded/common/target/nearby.h new file mode 100644 index 0000000000..9293618229 --- /dev/null +++ b/embedded/common/target/nearby.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef NEARBY_H +#define NEARBY_H + +#include "nearby_types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define AES_MESSAGE_SIZE_BYTES 16 +#define FP_SERVICE_UUID_SIZE 2 +#define FP_MODEL_ID_SIZE 3 +#define ACCOUNT_KEY_SIZE_BYTES AES_MESSAGE_SIZE_BYTES + +typedef enum { + kNearbyStatusOK = 0, + kNearbyStatusError, + kNearbyStatusTimeout, + kNearbyStatusResourceExhausted, + kNearbyStatusUnimplemented, + kNearbyStatusUnsupported, + kNearbyStatusInvalidInput, + kNearbyStatusRedundantAction +} nearby_platform_status; + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_H */ diff --git a/embedded/common/target/nearby_platform_audio.h b/embedded/common/target/nearby_platform_audio.h new file mode 100644 index 0000000000..d7ebdbc278 --- /dev/null +++ b/embedded/common/target/nearby_platform_audio.h @@ -0,0 +1,21 @@ +// Copyright 2021 Google LLC. +#ifndef NEARBY_PLATFORM_AUDIO_H +#define NEARBY_PLATFORM_AUDIO_H + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +// Returns true if right earbud is active +bool nearby_platform_GetEarbudRightStatus(); + +// Returns true if left earbud is active +bool nearby_platform_GetEarbudLeftStatus(); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_AUDIO_H */ diff --git a/embedded/common/target/nearby_platform_battery.h b/embedded/common/target/nearby_platform_battery.h new file mode 100644 index 0000000000..f12f4d704e --- /dev/null +++ b/embedded/common/target/nearby_platform_battery.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef NEARBY_PLATFORM_BATTERY_H +#define NEARBY_PLATFORM_BATTERY_H + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct { + // Set to true if the device is charging. + bool is_charging; + // Battery level of the right bud. 0 - 100 range. + uint8_t right_bud_battery_level; + // Battery level of the left bud. 0 - 100 range. + uint8_t left_bud_battery_level; + // Battery level of the charging case . 0 - 100 range. + uint8_t charging_case_battery_level; + // Battery remaining time in minutes, 0-65335 range. + uint16_t remaining_time_minutes; +} nearby_platform_BatteryInfo; + +typedef struct { + void (*on_battery_changed)(void); +} nearby_platform_BatteryInterface; + +// Gets battery and charging info +// +// battery_info - Battery status structure. +nearby_platform_status nearby_platform_GetBatteryInfo( + nearby_platform_BatteryInfo* battery_info); + +// Initializes battery module +// +// battery_interface - Battery status callback events. +nearby_platform_status nearby_platform_BatteryInit( + nearby_platform_BatteryInterface* battery_interface); +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_BATTERY_H */ diff --git a/embedded/common/target/nearby_platform_ble.h b/embedded/common/target/nearby_platform_ble.h new file mode 100644 index 0000000000..50baaa5b92 --- /dev/null +++ b/embedded/common/target/nearby_platform_ble.h @@ -0,0 +1,94 @@ +// 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. + +#ifndef NEARBY_PLATFORM_BLE_H +#define NEARBY_PLATFORM_BLE_H + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + kModelId = 0, // FE2C1233-8366-4814-8EB0-01DE32100BEA (read) + kKeyBasedPairing, // FE2C1234-8366-4814-8EB0-01DE32100BEA (write, notify) + kPasskey, // FE2C1235-8366-4814-8EB0-01DE32100BEA (write, notify) + kAccountKey, // FE2C1236-8366-4814-8EB0-01DE32100BEA (write) + kFirmwareRevision, // 0x2A26 + kAdditionalData, // FE2C1237-8366-4814-8EB0-01DE32100BEA +} nearby_fp_Characteristic; + +typedef enum { + kDisabled, + kNoLargerThan100ms, + kNoLargerThan250ms +} nearby_fp_AvertisementInterval; + +typedef struct { + nearby_platform_status (*on_gatt_write)( + uint64_t peer_address, nearby_fp_Characteristic characteristic, + const uint8_t* request, size_t length); + nearby_platform_status (*on_gatt_read)( + uint64_t peer_address, nearby_fp_Characteristic characteristic, + uint8_t* output, size_t* length); + +} nearby_platform_BleInterface; + +// Gets BLE address. +uint64_t nearby_platform_GetBleAddress(); + +// Sets BLE address. Returns address after change, which may be different than +// requested address. +// +// address - BLE address to set. +uint64_t nearby_platform_SetBleAddress(uint64_t address); + +#ifdef NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION +// Rotates BLE address to a random resolvable private address (RPA). Returns +// address after change. +uint64_t nearby_platform_RotateBleAddress(); +#endif /* NEARBY_FP_HAVE_BLE_ADDRESS_ROTATION */ + +// Sends a notification to the connected GATT client. +// +// peer_address - Address of peer. +// characteristic - Characteristic UUID +// message - Message buffer to transmit. +// length - Length of message in buffer. +nearby_platform_status nearby_platform_GattNotify( + uint64_t peer_address, nearby_fp_Characteristic characteristic, + const uint8_t* message, size_t length); + +// Sets the Fast Pair advertisement payload and starts advertising at a given +// interval. +// +// payload - Advertising data. +// length - Length of data. +// interval - Advertising interval code. +nearby_platform_status nearby_platform_SetAdvertisement( + const uint8_t* payload, size_t length, + nearby_fp_AvertisementInterval interval); + +// Initializes BLE +// +// ble_interface - GATT read and write callbacks structure. +nearby_platform_status nearby_platform_BleInit( + const nearby_platform_BleInterface* ble_interface); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_BLE_H */ diff --git a/embedded/common/target/nearby_platform_bt.h b/embedded/common/target/nearby_platform_bt.h new file mode 100644 index 0000000000..13f6bc1d1b --- /dev/null +++ b/embedded/common/target/nearby_platform_bt.h @@ -0,0 +1,110 @@ +// 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. + +#ifndef NEARBY_PLATFORM_BT_H +#define NEARBY_PLATFORM_BT_H + +// clang-format off +#include "nearby_config.h" +// clang-format on + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +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 + 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, + const uint8_t* message, size_t length); +#endif /* NEARBY_FP_MESSAGE_STREAM */ +} nearby_platform_BtInterface; + +// Returns Fast Pair Model Id. +uint32_t nearby_platform_GetModelId(); + +// Returns tx power level. +int8_t nearby_platform_GetTxLevel(); + +// Returns public BR/EDR address +uint64_t nearby_platform_GetPublicAddress(); + +// Returns passkey used during pairing +uint32_t nearby_platfrom_GetPairingPassKey(); + +// Provides the passkey received from the remote party. +// The system should compare local and remote party and accept/decline pairing +// request accordingly. +// +// passkey - Passkey +void nearby_platform_SetRemotePasskey(uint32_t passkey); + +// Sends a pairing request to the Seeker +// +// remote_party_br_edr_address - BT address of peer. +nearby_platform_status nearby_platform_SendPairingRequest( + uint64_t remote_party_br_edr_address); + +// Switches the device capabilities field back to default so that new +// pairings continue as expected. +nearby_platform_status nearby_platform_SetDefaultCapabilities(); + +// Switches the device capabilities field to Fast Pair required configuration: +// DisplayYes/No so that `confirm passkey` pairing method will be used. +nearby_platform_status nearby_platform_SetFastPairCapabilities(); + +// Sets null-terminated device name string in UTF-8 encoding +// +// name - Zero terminated string name of device. +nearby_platform_status nearby_platform_SetDeviceName(const char* name); + +// Gets null-terminated device name string in UTF-8 encoding +// pass buffer size in char, and get string length in char. +// +// name - Buffer to return name string. +// length - On input, the size of the name buffer. +// On output, returns size of name in buffer. +nearby_platform_status nearby_platform_GetDeviceName(char* name, + size_t* length); + +// 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 +// +// peer_address - Peer address. +// message - Contents of message. +// length - Length of message +nearby_platform_status nearby_platform_SendMessageStream(uint64_t peer_address, + const uint8_t* message, + size_t length); +#endif /* NEARBY_FP_MESSAGE_STREAM */ +// Initializes BT module +// +// bt_interface - BT callbacks event structure. +nearby_platform_status nearby_platform_BtInit( + const nearby_platform_BtInterface* bt_interface); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_BT_H */ diff --git a/embedded/common/target/nearby_platform_os.h b/embedded/common/target/nearby_platform_os.h new file mode 100644 index 0000000000..0682addcb6 --- /dev/null +++ b/embedded/common/target/nearby_platform_os.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef NEARBY_PLATFORM_OS_H +#define NEARBY_PLATFORM_OS_H + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + kRingStateStarted = 0, + kRingStateFailedToStartOrStop, + kRingStateStoppedReasonTimeout, + kRingStateStoppedReasonUserAction, + kRingStateStoppedReasonNearbyRequest +} nearby_platform_RingState; + +typedef struct { + nearby_platform_RingState ring_state; + // The number of components capable of ringing + // 0 - the device is incapable of ringing + // 1 - only a single component is capable of ringing + // 2 - left and right bud can ring + // 3 - left and right bud, and the case can ring + uint8_t num_components; + // A bitmap indicating what (sub)components are ringing. See + // `nearby_platform_Ring` for details + uint8_t components; + // Remaining ringing timeout in deciseconds + uint16_t timeout; +} nearby_platform_RingingInfo; + +// Gets current time in ms. +unsigned int nearby_platform_GetCurrentTimeMs(); + +// Starts a timer. Returns an opaque timer handle or null on error. +// +// callback - Function to call when timer matures. +// delay_ms - Number of milliseconds to run the timer. +void* nearby_platform_StartTimer(void (*callback)(), unsigned int delay_ms); + +// Cancels a timer +// +// timer - Timer handle returned by StartTimer. +nearby_platform_status nearby_platform_CancelTimer(void* timer); + +// Initializes OS module +nearby_platform_status nearby_platform_OsInit(); + +// Starts ringing +// +// `command` - the requested ringing state as a bitmap: +// Bit 1 (0x01): ring right +// Bit 2 (0x02): ring left +// Bit 3 (0x04): ring case +// Alternatively, `command` hold a special value of 0xFF to ring all +// components that can ring. +// `timeout` - the timeout in deciseconds. The timeout overrides the one already +// in effect if any component of the device is already ringing. +nearby_platform_status nearby_platform_Ring(uint8_t command, uint16_t timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_OS_H */ diff --git a/embedded/common/target/nearby_platform_persistence.h b/embedded/common/target/nearby_platform_persistence.h new file mode 100644 index 0000000000..d18b8ed5c3 --- /dev/null +++ b/embedded/common/target/nearby_platform_persistence.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef NEARBY_PLATFORM_PERSISTENCE_H +#define NEARBY_PLATFORM_PERSISTENCE_H + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + kStoredKeyAccountKeyList, + kStoredKeyPersonalizedName +} nearby_fp_StoredKey; + +// Loads stored key +// +// key - Type of key to fetch. +// output - Buffer to contain retrieved key. +// length - On input, contains the size of the output buffer. +// On output, contains the Length of key. +nearby_platform_status nearby_platform_LoadValue(nearby_fp_StoredKey key, + uint8_t* output, + size_t* length); + +// Saves stored key +// +// key - Type of key to store. +// output - Buffer containing key to store. +// length - Length of key. +nearby_platform_status nearby_platform_SaveValue(nearby_fp_StoredKey key, + const uint8_t* input, + size_t length); + +// Initializes persistence module +nearby_platform_status nearby_platform_PersistenceInit(); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_PERSISTENCE_H */ diff --git a/embedded/common/target/nearby_platform_se.h b/embedded/common/target/nearby_platform_se.h new file mode 100644 index 0000000000..1ce6ba7448 --- /dev/null +++ b/embedded/common/target/nearby_platform_se.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef NEARBY_PLATFORM_SE_H +#define NEARBY_PLATFORM_SE_H +// clang-format off +#include "nearby_config.h" +// clang-format on +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +// Generates a random number. +uint8_t nearby_platform_Rand(); + +// Computes the sha256 incrementally. Sha256Start() is called first, then +// Sha256Update() one or more times, and finally Sha256Finish(). +nearby_platform_status nearby_platform_Sha256Start(); + +// Intermediate sha256 compute. +// +// data - Block of data to compute into sha256. +// length - Length of data to process. +nearby_platform_status nearby_platform_Sha256Update(const void* data, + size_t length); + +// Finishes sha256 compute. +// +// out - Contains the final 256 bit sha. +nearby_platform_status nearby_platform_Sha256Finish(uint8_t out[32]); + +// Encrypts a data block with AES128 in ECB mode. +// +// input - Input data block to be encrypted. +// output - Resulting encrypted block. +// key - 128 bit key to use for encryption. +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]); + +// Decrypts a data block with AES128 in ECB mode. +// +// input - Input data block to be decrypted. +// output - Resulting decrypted block. +// key - 128 bit key to use for decryption. +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]); + +// Generates a shared sec256p1 secret using remote party public key and this +// device's private key. +// +// remote_party_public_key - Remote key. +// secret - 256 bit shared secret. +nearby_platform_status nearby_platform_GenSec256r1Secret( + const uint8_t remote_party_public_key[64], uint8_t secret[32]); + +// Initializes secure element module +nearby_platform_status nearby_platform_SecureElementInit(); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_SE_H */ diff --git a/embedded/common/target/nearby_platform_trace.h b/embedded/common/target/nearby_platform_trace.h new file mode 100644 index 0000000000..d38233fa7f --- /dev/null +++ b/embedded/common/target/nearby_platform_trace.h @@ -0,0 +1,60 @@ +// 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. + +#ifndef NEARBY_PLATFORM_TRACE_H +#define NEARBY_PLATFORM_TRACE_H + +#include "nearby.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + kTraceLevelUnknown = 0, + kTraceLevelVerbose = 1, + kTraceLevelDebug = 2, + kTraceLevelInfo = 3, + kTraceLevelWarning = 4, + kTraceLevelError = 5, +} nearby_platform_TraceLevel; + +// Generates conditional trace line. This is usually wrapped in a macro to +// provide compiler parameters. +// +// level - Debug level, higher is less messages. +// filename - Name of file calling trace. +// lineno - Source line of trace call. +// fmt - printf() style format string (%). +// ... - A series of parameters indicated by the fmt string. +void nearby_platform_Trace(nearby_platform_TraceLevel level, + const char *filename, int lineno, const char *fmt, + ...); + +// Processes assert. Processes a failed assertion. +// +// filename - Name of file calling assert. +// lineno - Source line of assert call +// reason - String message indicating reason for assert. +void nearby_platfrom_CrashOnAssert(const char *filename, int lineno, + const char *reason); + +// Initializes trace module. +void nearby_platform_TraceInit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NEARBY_PLATFORM_TRACE_H */ diff --git a/embedded/release_notes.md b/embedded/release_notes.md new file mode 100644 index 0000000000..4570b42fc7 --- /dev/null +++ b/embedded/release_notes.md @@ -0,0 +1,14 @@ +### Nearby SDK for embedded systems release notes + +## v1.0.0-embedded +Initial release supporting GFPS 3.1 per [specification](https://developers.google.com/nearby/fast-pair/specifications/introduction). + +Supported features include: +* Initial pairing with discoverable advertisement +* Subsequent pairing with non-discoverable advertisement +* Retroactive Active Key for provisioning after manual pairing +* Battery Notification +* Personalized Name +* RFCOMM message stream +* Unit-tests on a simulated gLinux platform implementation + diff --git a/embedded/target/gLinux/config.mk b/embedded/target/gLinux/config.mk new file mode 100644 index 0000000000..81de763101 --- /dev/null +++ b/embedded/target/gLinux/config.mk @@ -0,0 +1,28 @@ +CFLAGS_EXTRA ?= +CFLAGS += -g \ + -MD \ + -Werror \ + -Wno-deprecated-declarations \ + -DARCH_GLINUX \ + -fsanitize=address \ + -fno-omit-frame-pointer \ + -DARCH_GLINUX \ + -DNEARBY_ALL_MODULE_DEBUG \ + -DNEARBY_UNIT_TEST_ENABLED \ + $(CFLAGS_EXTRA) + +NEARBY_TRACE_LEVEL = VERBOSE + +ifeq ($(OPTIMIZED_BUILD),1) +CFLAGS += -O2 +else +CFLAGS += -O0 +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 diff --git a/embedded/target/gLinux/rules.mk b/embedded/target/gLinux/rules.mk new file mode 100644 index 0000000000..9c464f14b2 --- /dev/null +++ b/embedded/target/gLinux/rules.mk @@ -0,0 +1,87 @@ +ifneq ($(EXCLUDE_COMMON_TEST_SRCS),1) +TEST_SRCS += $(wildcard $(COMMON_DIR)/tests/*.cc) +endif + +TEST_SRCS += $(wildcard client/tests/*.cc) + +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) +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)) +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) + +# The glinux target layer +$(GLINUX_TARGET_OBJS) : $(OUT_DIR)/%.o: %.cc + $(call compile_c,$(TEST_INCLUDES) -I. -std=c++14 -c $(CFLAGS)) + +$(EMPTY_TARGET_OBJS) : $(OUT_DIR)/%.o: %.c + $(info "compiling empty target object $<") + $(call compile_c,-I. $(EMPTY_TARGET_INC) -std=c99 -c $(CFLAGS)) + +$(TEST_OBJS) : $(FIRMWARE_VERSION_FILENAME) +$(TEST_OBJS) : $(OUT_DIR)/%.o: %.cc + $(call compile_c,$(TEST_INCLUDES) -I. -std=c++14 $(CFLAGS)) + +$(GTEST_OBJS) : $(OUT_DIR)/%.o : %.cc + $(call compile_c,$(TEST_INCLUDES) -std=c++14 $(CFLAGS)) + +TESTS_TO_RUN = $(patsubst %.cc,$(OUT_DIR)/%_run,$(TEST_SRCS)) +.PHONY: $(TESTS_TO_RUN) + +# Static pattern rule to execute the test binary. +$(TESTS_TO_RUN) : %_run : % + mkdir -p $(OUT_DIR)/test_logs/$(notdir $<)_logs/ + ./$< --gtest_output=xml:$(OUT_DIR)/test_logs/$(notdir $<)_logs/sponge_log.xml + +TARGET_OBJS ?= $(GLINUX_TARGET_OBJS) + +TARGET_OS_SRCS := $(wildcard client/tests/gLinux/*.cc) +TARGET_OS_OBJS ?= $(patsubst %.cc,$(OUT_DIR)/%.o,$(TARGET_OS_SRCS)) +$(TARGET_OS_OBJS) : $(OUT_DIR)/%.o: %.cc + $(call compile_c,$(TEST_INCLUDES) -I. -std=c++14 $(CFLAGS)) + +ALL_TEST_OBJS += $(TEST_OBJS) +ALL_TEST_OBJS += $(TARGET_OS_OBJS) + +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). +-include $(ALL_TEST_OBJS:.o=.d) + +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)) +$(TEST_BINARIES) : $(NAME) $(GTEST_OBJS) $(GLINUX_TARGET_OBJS) +$(TEST_BINARIES) : $(TARGET_OS_OBJS) + +$(TEST_BINARIES) : % : %.o + mkdir -p $(dir $@) + $(CC) -o $@ $< \ + $(CFLAGS) \ + $(TEST_INCLUDES) \ + $(GTEST_OBJS) \ + $(TARGET_OBJS) \ + $(TARGET_OS_OBJS) \ + -L /usr/local/lib \ + -std=c++14 \ + -L $(OUT_DIR) -lnearby -lcrypto -lssl -lpthread -lstdc++ + +run_tests: $(TESTS_TO_RUN) + +tests: $(TEST_BINARIES) + +run_tests : tests +.PHONY : tests run_tests