mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Data Parse for subsequent pairing non-discoverable advertisement
PiperOrigin-RevId: 534929577
This commit is contained in:
committed by
Copybara-Service
parent
1d5abfcc0f
commit
6b08c4dda7
@@ -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",
|
||||
],
|
||||
|
||||
@@ -16,20 +16,38 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<uint8_t>& aes_key_bytes,
|
||||
const std::vector<uint8_t>& encrypted_bytes) {
|
||||
if (aes_key_bytes.size() != kAesBlockByteSize) {
|
||||
@@ -60,6 +78,19 @@ void ConvertVectorsToArrays(
|
||||
out_encrypted_bytes.begin());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CopyFieldBytesToVector(
|
||||
const std::vector<uint8_t>& service_data,
|
||||
const absl::flat_hash_map<size_t, std::pair<size_t, size_t>>& field_indices,
|
||||
size_t key) {
|
||||
std::vector<uint8_t> 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<uint8_t, kAesBlockByteSize> key;
|
||||
std::array<uint8_t, kEncryptedDataByteSize> 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<uint8_t> 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<size_t, std::pair<size_t, size_t>> 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<uint8_t> 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<uint8_t> 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<uint8_t, kAddressByteSize> address_bytes;
|
||||
device::ParseBluetoothAddress(
|
||||
address, absl::MakeSpan(address_bytes.data(), kAddressByteSize));
|
||||
salt_bytes =
|
||||
std::vector<uint8_t>(address_bytes.begin(), address_bytes.end());
|
||||
}
|
||||
|
||||
// Battery info bytes
|
||||
std::vector<uint8_t> 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
|
||||
|
||||
@@ -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<void(std::optional<DecryptedPasskey>)>;
|
||||
|
||||
using ParseNotDiscoverableAdvertisementCallback =
|
||||
absl::AnyInvocable<void(std::optional<NonDiscoverableAdvertisement>)>;
|
||||
|
||||
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<uint8_t>& aes_key_bytes,
|
||||
const std::vector<uint8_t>& 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
|
||||
|
||||
@@ -22,56 +22,66 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#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<uint8_t, kAesBlockByteSize> 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<uint8_t> 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<absl::string_view> model_id) {
|
||||
service_data, [&](std::optional<absl::string_view> 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<uint8_t> 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<absl::string_view> model_id) {
|
||||
service_data, [&](std::optional<absl::string_view> 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<uint8_t> encrypted_bytes(encrypted_bytes_array.begin(),
|
||||
encrypted_bytes_array.end());
|
||||
|
||||
absl::Notification notification;
|
||||
CountDownLatch latch(1);
|
||||
FastPairDataParser::ParseDecryptedResponse(
|
||||
kAesKeyBytes, encrypted_bytes,
|
||||
[¬ification](std::optional<DecryptedResponse> response) {
|
||||
[&](std::optional<DecryptedResponse> 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<uint8_t> 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<DecryptedResponse> response) {
|
||||
[&](std::optional<DecryptedResponse> 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<uint8_t> 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<DecryptedResponse> response) {
|
||||
EXPECT_TRUE(response.has_value());
|
||||
[&](std::optional<DecryptedResponse> 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<uint8_t> encrypted_bytes(encrypted_bytes_array.begin(),
|
||||
encrypted_bytes_array.end());
|
||||
|
||||
absl::Notification notification;
|
||||
CountDownLatch latch(1);
|
||||
FastPairDataParser::ParseDecryptedPasskey(
|
||||
kAesKeyBytes, encrypted_bytes,
|
||||
[¬ification](std::optional<DecryptedPasskey> decrypted_passkey) {
|
||||
[&](std::optional<DecryptedPasskey> 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<uint8_t> 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<DecryptedPasskey> decrypted_passkey) {
|
||||
[&](std::optional<DecryptedPasskey> 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<uint8_t> 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<DecryptedPasskey> decrypted_passkey) {
|
||||
EXPECT_TRUE(decrypted_passkey.has_value());
|
||||
[&](std::optional<DecryptedPasskey> 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<uint8_t> 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<DecryptedPasskey> decrypted_passkey) {
|
||||
EXPECT_TRUE(decrypted_passkey.has_value());
|
||||
[&](std::optional<DecryptedPasskey> 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<NonDiscoverableAdvertisement> advertisement) {
|
||||
EXPECT_FALSE(advertisement.has_value());
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
TEST(FastPairDataParserTest,
|
||||
ParseNotDiscoverableAdvertisementNoApplicibleData) {
|
||||
std::vector<uint8_t> bytes = FastPairServiceDataCreator::Builder()
|
||||
.SetHeader(kNotDiscoverableAdvHeader)
|
||||
.Build()
|
||||
->CreateServiceData();
|
||||
CountDownLatch latch(1);
|
||||
FastPairDataParser::ParseNotDiscoverableAdvertisement(
|
||||
std::string(bytes.begin(), bytes.end()), kDeviceAddress,
|
||||
[&](const std::optional<NonDiscoverableAdvertisement> advertisement) {
|
||||
EXPECT_FALSE(advertisement.has_value());
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
TEST(FastPairDataParserTest,
|
||||
ParseNotDiscoverableAdvertisementAccountKeyFilter) {
|
||||
const std::vector<uint8_t> kSaltBytes = {0x01};
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> 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<uint8_t> kSaltBytes = {0x01};
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> 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<uint8_t> bytes = FastPairServiceDataCreator::Builder()
|
||||
.SetHeader(/*InvalidHeader*/ 0b00100000)
|
||||
.Build()
|
||||
->CreateServiceData();
|
||||
CountDownLatch latch(1);
|
||||
|
||||
FastPairDataParser::ParseNotDiscoverableAdvertisement(
|
||||
std::string(bytes.begin(), bytes.end()), kDeviceAddress,
|
||||
[&](const std::optional<NonDiscoverableAdvertisement> advertisement) {
|
||||
EXPECT_FALSE(advertisement.has_value());
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
TEST(FastPairDataParserTest,
|
||||
ParseNotDiscoverableAdvertisementZeroLengthExtraField) {
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> advertisement) {
|
||||
EXPECT_FALSE(advertisement.has_value());
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementWrongType) {
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> advertisement) {
|
||||
EXPECT_FALSE(advertisement.has_value());
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementSaltTwoBytes) {
|
||||
const std::vector<uint8_t> kLargeSaltBytes = {0xC7, 0xC8};
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> 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<uint8_t> 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<NonDiscoverableAdvertisement> advertisement) {
|
||||
EXPECT_FALSE(advertisement.has_value());
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
TEST(FastPairDataParserTest, ParseNotDiscoverableAdvertisementWithBattery) {
|
||||
const std::vector<uint8_t> kSaltBytes = {0x01};
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> 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<uint8_t> kDeviceAddressBytes = {17, 18, 19, 20, 21, 22};
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> 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<uint8_t> kSaltBytes = {0x01};
|
||||
std::vector<uint8_t> 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<NonDiscoverableAdvertisement> 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
|
||||
|
||||
@@ -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<uint8_t>* service_data) {
|
||||
int FastPairDecoder::GetIdLength(const std::vector<uint8_t>* service_data) {
|
||||
return service_data->size() == kMinModelIdLength
|
||||
? kMinModelIdLength
|
||||
: ((*service_data)[kHeaderIndex] & kHeaderLengthBitmask) >>
|
||||
kHeaderLengthOffset;
|
||||
}
|
||||
|
||||
bool IsIdLengthValid(const std::vector<uint8_t>* service_data) {
|
||||
int id_length = GetIdLength(service_data);
|
||||
return kMinModelIdLength <= id_length && id_length <= kMaxModelIdLength &&
|
||||
id_length + kHeaderLength <= static_cast<int>(service_data->size());
|
||||
}
|
||||
|
||||
int GetVersion(const std::vector<uint8_t>* service_data) {
|
||||
int FastPairDecoder::GetVersion(const std::vector<uint8_t>* service_data) {
|
||||
return service_data->size() == kMinModelIdLength
|
||||
? 0
|
||||
: ((*service_data)[kHeaderIndex] & kHeaderVersionBitmask) >>
|
||||
kHeaderVersionOffset;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
bool IsIdLengthValid(const std::vector<uint8_t>* service_data) {
|
||||
int id_length = FastPairDecoder::GetIdLength(service_data);
|
||||
return kMinModelIdLength <= id_length && id_length <= kMaxModelIdLength &&
|
||||
id_length + kHeaderLength <= static_cast<int>(service_data->size());
|
||||
}
|
||||
|
||||
bool FastPairDecoder::HasModelId(const std::vector<uint8_t>* service_data) {
|
||||
return service_data != nullptr &&
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace fastpair {
|
||||
|
||||
class FastPairDecoder {
|
||||
public:
|
||||
static int GetVersion(const std::vector<uint8_t>* service_data);
|
||||
static int GetIdLength(const std::vector<uint8_t>* service_data);
|
||||
static bool HasModelId(const std::vector<uint8_t>* service_data);
|
||||
|
||||
static std::optional<std::string> GetHexModelIdFromServiceData(
|
||||
|
||||
@@ -43,7 +43,7 @@ class FakeBlePeripheral : public api::BlePeripheral {
|
||||
name_ = std::string(name);
|
||||
const std::vector<uint8_t> service_data =
|
||||
FastPairServiceDataCreator::Builder()
|
||||
.SetModelId(std::string(model_id))
|
||||
.SetModelId(model_id)
|
||||
.Build()
|
||||
->CreateServiceData();
|
||||
ByteArray advertisement_bytes(
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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<FastPairServiceDataCreator> Build();
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user