diff --git a/fastpair/dataparser/BUILD b/fastpair/dataparser/BUILD index 4b3eff0a..58a5f9b6 100644 --- a/fastpair/dataparser/BUILD +++ b/fastpair/dataparser/BUILD @@ -15,6 +15,7 @@ cc_library( ":decoder", "//fastpair/common", "//fastpair/crypto", + "//internal/base:bluetooth_address", "//internal/platform:base", "//internal/platform:logging", "@com_google_absl//absl/functional:any_invocable", @@ -50,12 +51,11 @@ cc_test( "//fastpair/common", "//fastpair/crypto", "//fastpair/testing", - "//internal/platform:logging", + "//internal/platform:types", "//internal/platform/implementation/g3", # build_cleaner: keep "@boringssl//:crypto", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/strings", - "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_googletest//:gtest_main", ], diff --git a/fastpair/dataparser/fast_pair_data_parser.cc b/fastpair/dataparser/fast_pair_data_parser.cc index 6275e762..b6e2c4f2 100644 --- a/fastpair/dataparser/fast_pair_data_parser.cc +++ b/fastpair/dataparser/fast_pair_data_parser.cc @@ -16,20 +16,38 @@ #include #include +#include #include +#include #include -#include +#include #include #include "third_party/nearby//fastpair/crypto/fast_pair_decryption.h" +#include "fastpair/common/battery_notification.h" #include "fastpair/common/constant.h" +#include "fastpair/common/non_discoverable_advertisement.h" #include "fastpair/dataparser/fast_pair_decoder.h" +#include "internal/base/bluetooth_address.h" #include "internal/platform/logging.h" namespace nearby { namespace fastpair { namespace { +constexpr int kHeaderIndex = 0; +constexpr int kFieldTypeBitmask = 0b00001111; +constexpr int kFieldLengthBitmask = 0b11110000; +constexpr int kHeaderLength = 1; +constexpr int kFieldLengthOffset = 4; +constexpr int kFieldTypeAccountKeyFilter = 0; +constexpr int kFieldTypeAccountKeyFilterSalt = 1; +constexpr int kFieldTypeAccountKeyFilterNoNotification = 2; +constexpr int kFieldTypeBattery = 3; +constexpr int kFieldTypeBatteryNoNotification = 4; +constexpr int kAddressByteSize = 6; +constexpr int kMaxLengthOfSaltBytes = 2; + bool ValidateInputSizes(const std::vector& aes_key_bytes, const std::vector& encrypted_bytes) { if (aes_key_bytes.size() != kAesBlockByteSize) { @@ -60,6 +78,19 @@ void ConvertVectorsToArrays( out_encrypted_bytes.begin()); } +std::vector CopyFieldBytesToVector( + const std::vector& service_data, + const absl::flat_hash_map>& field_indices, + size_t key) { + std::vector out; + DCHECK(field_indices.contains(key)); + + auto indices = field_indices.find(key)->second; + for (size_t i = indices.first; i < indices.second; i++) { + out.push_back(service_data[i]); + } + return out; +} } // namespace void FastPairDataParser::GetHexModelIdFromServiceData( @@ -82,7 +113,6 @@ void FastPairDataParser::ParseDecryptedResponse( std::array key; std::array bytes; ConvertVectorsToArrays(aes_key_bytes, encrypted_response_bytes, key, bytes); - callback(FastPairDecryption::ParseDecryptResponse(key, bytes)); } @@ -102,5 +132,100 @@ void FastPairDataParser::ParseDecryptedPasskey( callback(FastPairDecryption::ParseDecryptPasskey(key, bytes)); } +void FastPairDataParser::ParseNotDiscoverableAdvertisement( + absl::string_view fast_pair_service_data, absl::string_view address, + ParseNotDiscoverableAdvertisementCallback callback) { + std::vector service_data; + std::move(std::begin(fast_pair_service_data), + std::end(fast_pair_service_data), std::back_inserter(service_data)); + if (service_data.empty() || FastPairDecoder::GetVersion(&service_data) != 0) { + NEARBY_LOGS(WARNING) << " Invalid service data."; + callback(std::nullopt); + return; + } + + absl::flat_hash_map> field_indices; + size_t headerIndex = kHeaderIndex + kHeaderLength + + FastPairDecoder::GetIdLength(&service_data); + + while (headerIndex < service_data.size()) { + size_t type = service_data[headerIndex] & kFieldTypeBitmask; + size_t length = + (service_data[headerIndex] & kFieldLengthBitmask) >> kFieldLengthOffset; + size_t index = headerIndex + kHeaderLength; + size_t end = index + length; + + if (end <= service_data.size()) { + field_indices[type] = std::make_pair(index, end); + } + + headerIndex = end; + } + + // Account key filter bytes + std::vector account_key_filter_bytes; + NonDiscoverableAdvertisement::Type show_ui = + NonDiscoverableAdvertisement::Type::kNone; + if (field_indices.contains(kFieldTypeAccountKeyFilter)) { + account_key_filter_bytes = CopyFieldBytesToVector( + service_data, field_indices, kFieldTypeAccountKeyFilter); + show_ui = NonDiscoverableAdvertisement::Type::kShowUi; + } else if (field_indices.contains(kFieldTypeAccountKeyFilterNoNotification)) { + account_key_filter_bytes = CopyFieldBytesToVector( + service_data, field_indices, kFieldTypeAccountKeyFilterNoNotification); + show_ui = NonDiscoverableAdvertisement::Type::kHideUi; + } + + if (account_key_filter_bytes.empty()) { + NEARBY_LOGS(WARNING) << " Service data doesn't contain account key filter."; + callback(std::nullopt); + return; + } + + // Salt bytes + std::vector salt_bytes; + if (field_indices.contains(kFieldTypeAccountKeyFilterSalt)) { + salt_bytes = CopyFieldBytesToVector(service_data, field_indices, + kFieldTypeAccountKeyFilterSalt); + } + + // https://developers.devsite.corp.google.com/nearby/fast-pair/specifications/service/provider#AccountKeyFilter + if (salt_bytes.size() > kMaxLengthOfSaltBytes) { + NEARBY_LOGS(WARNING) << " Parsed a salt field larger than two bytes: " + << salt_bytes.size(); + callback(std::nullopt); + return; + } + + if (salt_bytes.empty()) { + NEARBY_LOGS(INFO) + << __func__ + << ": missing salt field from device. Using device address instead."; + std::array address_bytes; + device::ParseBluetoothAddress( + address, absl::MakeSpan(address_bytes.data(), kAddressByteSize)); + salt_bytes = + std::vector(address_bytes.begin(), address_bytes.end()); + } + + // Battery info bytes + std::vector battery_bytes; + BatteryNotification::Type show_ui_for_battery = + BatteryNotification::Type::kNone; + if (field_indices.contains(kFieldTypeBattery)) { + battery_bytes = + CopyFieldBytesToVector(service_data, field_indices, kFieldTypeBattery); + show_ui_for_battery = BatteryNotification::Type::kShowUi; + } else if (field_indices.contains(kFieldTypeBatteryNoNotification)) { + battery_bytes = CopyFieldBytesToVector(service_data, field_indices, + kFieldTypeBatteryNoNotification); + show_ui_for_battery = BatteryNotification::Type::kHideUi; + } + + callback(NonDiscoverableAdvertisement( + std::move(account_key_filter_bytes), show_ui, std::move(salt_bytes), + BatteryNotification::FromBytes(battery_bytes, show_ui_for_battery))); +} + } // namespace fastpair } // namespace nearby diff --git a/fastpair/dataparser/fast_pair_data_parser.h b/fastpair/dataparser/fast_pair_data_parser.h index 926d9ec6..95662e9e 100644 --- a/fastpair/dataparser/fast_pair_data_parser.h +++ b/fastpair/dataparser/fast_pair_data_parser.h @@ -26,6 +26,7 @@ #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" +#include "fastpair/common/non_discoverable_advertisement.h" #include "fastpair/crypto/decrypted_passkey.h" #include "fastpair/crypto/decrypted_response.h" @@ -45,6 +46,9 @@ class FastPairDataParser { using ParseDecryptPasskeyCallback = absl::AnyInvocable)>; + using ParseNotDiscoverableAdvertisementCallback = + absl::AnyInvocable)>; + public: // Gets the hex string representation of the device's model ID from the // service data. @@ -65,6 +69,13 @@ class FastPairDataParser { const std::vector& aes_key_bytes, const std::vector& encrypted_passkey_bytes, ParseDecryptPasskeyCallback callback); + + // Parses a 'Non Discoverable' advertisement from |service_data|. + // If the advertisement does not contain information about salt, use the + // |address| as salt instead. + static void ParseNotDiscoverableAdvertisement( + absl::string_view fast_pair_service_data, absl::string_view address, + ParseNotDiscoverableAdvertisementCallback callback); }; } // namespace fastpair diff --git a/fastpair/dataparser/fast_pair_data_parser_test.cc b/fastpair/dataparser/fast_pair_data_parser_test.cc index 9e661814..9ebbd34c 100644 --- a/fastpair/dataparser/fast_pair_data_parser_test.cc +++ b/fastpair/dataparser/fast_pair_data_parser_test.cc @@ -22,56 +22,66 @@ #include #include #include +#include #include #include "gtest/gtest.h" +#include "absl/strings/escaping.h" #include "absl/strings/string_view.h" -#include "absl/synchronization/notification.h" -#include "absl/time/time.h" #include "fastpair/common/constant.h" +#include "fastpair/common/non_discoverable_advertisement.h" #include "fastpair/crypto/fast_pair_encryption.h" #include "fastpair/testing/fast_pair_service_data_creator.h" +#include "internal/platform/count_down_latch.h" namespace nearby { namespace fastpair { namespace { +// Test data comes from: +// https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#test_cases -constexpr absl::Duration kWaitTimeout = absl::Milliseconds(200); constexpr absl::string_view kModelId("aabbcc"); constexpr absl::string_view kInvalidModelId("aabb"); +constexpr absl::string_view kDeviceAddress("11:12:13:14:15:16"); +constexpr absl::string_view kAccountKeyFilter("112233445566"); +constexpr absl::string_view kSalt("01"); +constexpr absl::string_view kBattery("01048F"); +constexpr int kBatteryHeader = 0b00110011; constexpr std::array kAeskeyarray = { 0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6, 0xCF, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D}; +constexpr int kNotDiscoverableAdvHeader = 0b00000110; +constexpr int kAccountKeyFilterHeader = 0b01100000; +constexpr int kSaltHeader = 0b00010001; TEST(FastPairDataParserTest, GetHexModelIdFromServiceDataUnsucessfully) { const std::vector service_data = FastPairServiceDataCreator::Builder() - .SetModelId(std::string(kInvalidModelId)) + .SetModelId(kInvalidModelId) .Build() ->CreateServiceData(); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::GetHexModelIdFromServiceData( - service_data, [¬ification](std::optional model_id) { + service_data, [&](std::optional model_id) { EXPECT_EQ(model_id, std::nullopt); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, GetHexModelIdFromServiceDataSuccessfully) { const std::vector service_data = FastPairServiceDataCreator::Builder() - .SetModelId(std::string(kModelId)) + .SetModelId(kModelId) .Build() ->CreateServiceData(); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::GetHexModelIdFromServiceData( - service_data, [¬ification](std::optional model_id) { + service_data, [&](std::optional model_id) { EXPECT_EQ(model_id, kModelId); - notification.Notify(); + latch.CountDown(); }); - - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptResponseUnsuccessfullyWithInvalidAesKey) { @@ -104,14 +114,14 @@ TEST(FastPairDataParserTest, DecryptResponseUnsuccessfullyWithInvalidAesKey) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end()); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedResponse( kAesKeyBytes, encrypted_bytes, - [¬ification](std::optional response) { + [&](std::optional response) { EXPECT_FALSE(response.has_value()); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptResponseUnsuccessfullyWithInvalidResponse) { @@ -144,14 +154,14 @@ TEST(FastPairDataParserTest, DecryptResponseUnsuccessfullyWithInvalidResponse) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end() - 1); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedResponse( kAesKeyBytes, encrypted_bytes, - [¬ification](std::optional response) { + [&](std::optional response) { EXPECT_FALSE(response.has_value()); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptResponseSuccessfully) { @@ -183,19 +193,18 @@ TEST(FastPairDataParserTest, DecryptResponseSuccessfully) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end()); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedResponse( kAesKeyBytes, encrypted_bytes, - [¬ification, &kAddressBytes, - &kSalt](std::optional response) { - EXPECT_TRUE(response.has_value()); + [&](std::optional response) { + ASSERT_TRUE(response.has_value()); EXPECT_EQ(response.value().message_type, FastPairMessageType::kKeyBasedPairingResponse); EXPECT_EQ(response.value().address_bytes, kAddressBytes); EXPECT_EQ(response.value().salt, kSalt); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptPasskeyUnsuccessfullyWithInvalidAesKey) { @@ -228,14 +237,14 @@ TEST(FastPairDataParserTest, DecryptPasskeyUnsuccessfullyWithInvalidAesKey) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end()); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedPasskey( kAesKeyBytes, encrypted_bytes, - [¬ification](std::optional decrypted_passkey) { + [&](std::optional decrypted_passkey) { EXPECT_FALSE(decrypted_passkey.has_value()); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptPasskeyUnsuccessfullyWithInvalidPasskey) { @@ -267,14 +276,14 @@ TEST(FastPairDataParserTest, DecryptPasskeyUnsuccessfullyWithInvalidPasskey) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end() - 1); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedPasskey( kAesKeyBytes, encrypted_bytes, - [¬ification](std::optional decrypted_passkey) { + [&](std::optional decrypted_passkey) { EXPECT_FALSE(decrypted_passkey.has_value()); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptSeekerPasskeySuccessfully) { @@ -305,19 +314,18 @@ TEST(FastPairDataParserTest, DecryptSeekerPasskeySuccessfully) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end()); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedPasskey( kAesKeyBytes, encrypted_bytes, - [¬ification, &kPasskey, - &kSalt](std::optional decrypted_passkey) { - EXPECT_TRUE(decrypted_passkey.has_value()); + [&](std::optional decrypted_passkey) { + ASSERT_TRUE(decrypted_passkey.has_value()); EXPECT_EQ(decrypted_passkey.value().message_type, FastPairMessageType::kSeekersPasskey); EXPECT_EQ(decrypted_passkey.value().passkey, kPasskey); EXPECT_EQ(decrypted_passkey.value().salt, kSalt); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); } TEST(FastPairDataParserTest, DecryptProviderPasskeySuccessfully) { @@ -348,19 +356,363 @@ TEST(FastPairDataParserTest, DecryptProviderPasskeySuccessfully) { std::vector encrypted_bytes(encrypted_bytes_array.begin(), encrypted_bytes_array.end()); - absl::Notification notification; + CountDownLatch latch(1); FastPairDataParser::ParseDecryptedPasskey( kAesKeyBytes, encrypted_bytes, - [¬ification, &kPasskey, - &kSalt](std::optional decrypted_passkey) { - EXPECT_TRUE(decrypted_passkey.has_value()); + [&](std::optional decrypted_passkey) { + ASSERT_TRUE(decrypted_passkey.has_value()); EXPECT_EQ(decrypted_passkey.value().message_type, FastPairMessageType::kProvidersPasskey); EXPECT_EQ(decrypted_passkey.value().passkey, kPasskey); EXPECT_EQ(decrypted_passkey.value().salt, kSalt); - notification.Notify(); + latch.CountDown(); }); - EXPECT_TRUE(notification.WaitForNotificationWithTimeout(kWaitTimeout)); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementEmpty) { + CountDownLatch latch(1); + FastPairDataParser::ParseNotDiscoverableAdvertisement( + "", kDeviceAddress, + [&](const std::optional advertisement) { + EXPECT_FALSE(advertisement.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, + ParseNotDiscoverableAdvertisementNoApplicibleData) { + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + EXPECT_FALSE(advertisement.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, + ParseNotDiscoverableAdvertisementAccountKeyFilter) { + const std::vector kSaltBytes = {0x01}; + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(kSaltHeader) + .AddExtraField(kSalt) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + ASSERT_TRUE(advertisement.has_value()); + EXPECT_EQ(absl::BytesToHexString( + std::string(advertisement->account_key_filter.begin(), + advertisement->account_key_filter.end())), + kAccountKeyFilter); + EXPECT_EQ(advertisement->salt, kSaltBytes); + EXPECT_EQ(advertisement->type, + NonDiscoverableAdvertisement::Type::kShowUi); + EXPECT_FALSE(advertisement->battery_notification.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, + ParseNotDiscoverableAdvertisementAccountKeyFilterNoNotification) { + const std::vector kSaltBytes = {0x01}; + std::vector bytes = + FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader( + /*accountKeyFilterNoNotificationHeader*/ 0b01100010) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(kSaltHeader) + .AddExtraField(kSalt) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + ASSERT_TRUE(advertisement.has_value()); + EXPECT_EQ(absl::BytesToHexString( + std::string(advertisement->account_key_filter.begin(), + advertisement->account_key_filter.end())), + kAccountKeyFilter); + EXPECT_EQ(advertisement->salt, kSaltBytes); + EXPECT_EQ(advertisement->type, + NonDiscoverableAdvertisement::Type::kHideUi); + EXPECT_FALSE(advertisement->battery_notification.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementWrongVersion) { + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(/*InvalidHeader*/ 0b00100000) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + EXPECT_FALSE(advertisement.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, + ParseNotDiscoverableAdvertisementZeroLengthExtraField) { + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField("") + .AddExtraFieldHeader(kSaltHeader) + .AddExtraField(kSalt) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + EXPECT_FALSE(advertisement.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementWrongType) { + std::vector bytes = + FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader /*InvalidType*/ (0b01100001) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(kSaltHeader) + .AddExtraField(kSalt) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + EXPECT_FALSE(advertisement.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementSaltTwoBytes) { + const std::vector kLargeSaltBytes = {0xC7, 0xC8}; + std::vector bytes = + FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(/*SaltTwoBytes*/ 0b00100001) + .AddExtraField(/*LargeSalt*/ "C7C8") + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + ASSERT_TRUE(advertisement.has_value()); + EXPECT_EQ(absl::BytesToHexString( + std::string(advertisement->account_key_filter.begin(), + advertisement->account_key_filter.end())), + kAccountKeyFilter); + EXPECT_EQ(advertisement->salt, kLargeSaltBytes); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementSaltTooLarge) { + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(0b00110001) + .AddExtraField(/*invalidSalt*/ "C7C8C9") + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + EXPECT_FALSE(advertisement.has_value()); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementWithBattery) { + const std::vector kSaltBytes = {0x01}; + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(kSaltHeader) + .AddExtraField(kSalt) + .AddExtraFieldHeader(kBatteryHeader) + .AddExtraField(kBattery) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + ASSERT_TRUE(advertisement.has_value()); + EXPECT_EQ(absl::BytesToHexString( + std::string(advertisement->account_key_filter.begin(), + advertisement->account_key_filter.end())), + kAccountKeyFilter); + EXPECT_EQ(advertisement->salt, kSaltBytes); + EXPECT_EQ(advertisement->type, + NonDiscoverableAdvertisement::Type::kShowUi); + ASSERT_TRUE(advertisement->battery_notification.has_value()); + EXPECT_EQ(advertisement->battery_notification->type, + BatteryNotification::Type::kShowUi); + EXPECT_FALSE(advertisement->battery_notification->battery_infos.at(0) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(0).percentage, + 1); + EXPECT_FALSE(advertisement->battery_notification->battery_infos.at(1) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(1).percentage, + 4); + ASSERT_TRUE(advertisement->battery_notification->battery_infos.at(2) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(2).percentage, + 15); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementMissingSalt) { + const std::vector kDeviceAddressBytes = {17, 18, 19, 20, 21, 22}; + std::vector bytes = FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(kBatteryHeader) + .AddExtraField(kBattery) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + ASSERT_TRUE(advertisement.has_value()); + EXPECT_EQ(absl::BytesToHexString( + std::string(advertisement->account_key_filter.begin(), + advertisement->account_key_filter.end())), + kAccountKeyFilter); + EXPECT_EQ(advertisement->salt, kDeviceAddressBytes); + EXPECT_EQ(advertisement->type, + NonDiscoverableAdvertisement::Type::kShowUi); + ASSERT_TRUE(advertisement->battery_notification.has_value()); + EXPECT_EQ(advertisement->battery_notification->type, + BatteryNotification::Type::kShowUi); + EXPECT_FALSE(advertisement->battery_notification->battery_infos.at(0) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(0).percentage, + 1); + EXPECT_FALSE(advertisement->battery_notification->battery_infos.at(1) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(1).percentage, + 4); + ASSERT_TRUE(advertisement->battery_notification->battery_infos.at(2) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(2).percentage, + 15); + latch.CountDown(); + }); + latch.Await(); +} + +TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementWithBatteryNoUi) { + const std::vector kSaltBytes = {0x01}; + std::vector bytes = + FastPairServiceDataCreator::Builder() + .SetHeader(kNotDiscoverableAdvHeader) + .SetModelId(kModelId) + .AddExtraFieldHeader(kAccountKeyFilterHeader) + .AddExtraField(kAccountKeyFilter) + .AddExtraFieldHeader(kSaltHeader) + .AddExtraField(kSalt) + .AddExtraFieldHeader(/*batterHeaderNoNotification*/ 0b00110100) + .AddExtraField(kBattery) + .Build() + ->CreateServiceData(); + CountDownLatch latch(1); + + FastPairDataParser::ParseNotDiscoverableAdvertisement( + std::string(bytes.begin(), bytes.end()), kDeviceAddress, + [&](const std::optional advertisement) { + ASSERT_TRUE(advertisement.has_value()); + EXPECT_EQ(absl::BytesToHexString( + std::string(advertisement->account_key_filter.begin(), + advertisement->account_key_filter.end())), + kAccountKeyFilter); + EXPECT_EQ(advertisement->salt, kSaltBytes); + EXPECT_EQ(advertisement->type, + NonDiscoverableAdvertisement::Type::kShowUi); + ASSERT_TRUE(advertisement->battery_notification.has_value()); + EXPECT_EQ(advertisement->battery_notification->type, + BatteryNotification::Type::kHideUi); + EXPECT_FALSE(advertisement->battery_notification->battery_infos.at(0) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(0).percentage, + 1); + EXPECT_FALSE(advertisement->battery_notification->battery_infos.at(1) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(1).percentage, + 4); + ASSERT_TRUE(advertisement->battery_notification->battery_infos.at(2) + .is_charging); + EXPECT_EQ( + advertisement->battery_notification->battery_infos.at(2).percentage, + 15); + latch.CountDown(); + }); + latch.Await(); } } // namespace diff --git a/fastpair/dataparser/fast_pair_decoder.cc b/fastpair/dataparser/fast_pair_decoder.cc index b7f783d6..1b12e64e 100644 --- a/fastpair/dataparser/fast_pair_decoder.cc +++ b/fastpair/dataparser/fast_pair_decoder.cc @@ -27,7 +27,6 @@ namespace nearby { namespace fastpair { namespace { - constexpr int kHeaderIndex = 0; constexpr int kHeaderLength = 1; constexpr int kHeaderLengthBitmask = 0b00011110; @@ -36,28 +35,27 @@ constexpr int kHeaderVersionBitmask = 0b11100000; constexpr int kHeaderVersionOffset = 5; constexpr int kMinModelIdLength = 3; constexpr int kMaxModelIdLength = 14; +} // namespace -int GetIdLength(const std::vector* service_data) { +int FastPairDecoder::GetIdLength(const std::vector* service_data) { return service_data->size() == kMinModelIdLength ? kMinModelIdLength : ((*service_data)[kHeaderIndex] & kHeaderLengthBitmask) >> kHeaderLengthOffset; } -bool IsIdLengthValid(const std::vector* service_data) { - int id_length = GetIdLength(service_data); - return kMinModelIdLength <= id_length && id_length <= kMaxModelIdLength && - id_length + kHeaderLength <= static_cast(service_data->size()); -} - -int GetVersion(const std::vector* service_data) { +int FastPairDecoder::GetVersion(const std::vector* service_data) { return service_data->size() == kMinModelIdLength ? 0 : ((*service_data)[kHeaderIndex] & kHeaderVersionBitmask) >> kHeaderVersionOffset; } -} // namespace +bool IsIdLengthValid(const std::vector* service_data) { + int id_length = FastPairDecoder::GetIdLength(service_data); + return kMinModelIdLength <= id_length && id_length <= kMaxModelIdLength && + id_length + kHeaderLength <= static_cast(service_data->size()); +} bool FastPairDecoder::HasModelId(const std::vector* service_data) { return service_data != nullptr && diff --git a/fastpair/dataparser/fast_pair_decoder.h b/fastpair/dataparser/fast_pair_decoder.h index ec412475..bbf98328 100644 --- a/fastpair/dataparser/fast_pair_decoder.h +++ b/fastpair/dataparser/fast_pair_decoder.h @@ -25,6 +25,8 @@ namespace fastpair { class FastPairDecoder { public: + static int GetVersion(const std::vector* service_data); + static int GetIdLength(const std::vector* service_data); static bool HasModelId(const std::vector* service_data); static std::optional GetHexModelIdFromServiceData( diff --git a/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc index 96a8a970..45ccb2bd 100644 --- a/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc +++ b/fastpair/scanning/fastpair/fast_pair_discoverable_scanner_impl_test.cc @@ -43,7 +43,7 @@ class FakeBlePeripheral : public api::BlePeripheral { name_ = std::string(name); const std::vector service_data = FastPairServiceDataCreator::Builder() - .SetModelId(std::string(model_id)) + .SetModelId(model_id) .Build() ->CreateServiceData(); ByteArray advertisement_bytes( diff --git a/fastpair/testing/fast_pair_service_data_creator.cc b/fastpair/testing/fast_pair_service_data_creator.cc index c626814f..efbb2749 100644 --- a/fastpair/testing/fast_pair_service_data_creator.cc +++ b/fastpair/testing/fast_pair_service_data_creator.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "absl/strings/escaping.h" @@ -38,8 +39,8 @@ FastPairServiceDataCreator::Builder::SetHeader(uint8_t byte) { } FastPairServiceDataCreator::Builder& -FastPairServiceDataCreator::Builder::SetModelId(std::string model_id) { - model_id_ = model_id; +FastPairServiceDataCreator::Builder::SetModelId(absl::string_view model_id) { + model_id_ = std::string(model_id); return *this; } @@ -50,8 +51,8 @@ FastPairServiceDataCreator::Builder::AddExtraFieldHeader(uint8_t header) { } FastPairServiceDataCreator::Builder& -FastPairServiceDataCreator::Builder::AddExtraField(std::string field) { - extra_fields_.push_back(field); +FastPairServiceDataCreator::Builder::AddExtraField(absl::string_view field) { + extra_fields_.push_back(std::string(field)); return *this; } diff --git a/fastpair/testing/fast_pair_service_data_creator.h b/fastpair/testing/fast_pair_service_data_creator.h index 90c50da8..64fcef88 100644 --- a/fastpair/testing/fast_pair_service_data_creator.h +++ b/fastpair/testing/fast_pair_service_data_creator.h @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace nearby { @@ -36,9 +37,9 @@ class FastPairServiceDataCreator { ~Builder(); Builder& SetHeader(uint8_t byte); - Builder& SetModelId(std::string model_id); + Builder& SetModelId(std::string_view model_id); Builder& AddExtraFieldHeader(uint8_t header); - Builder& AddExtraField(std::string field); + Builder& AddExtraField(std::string_view field); std::unique_ptr Build(); private: