mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
[BLE Refactor] Tune BleAdvertisement, BleAdvertisementHeader, BlePeripheral, AdvertisementReadResult, BleUtils.
PiperOrigin-RevId: 439365106
This commit is contained in:
committed by
Copybara-Service
parent
7eded6c621
commit
0747c0a349
@@ -21,7 +21,6 @@ cc_library(
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_radio.cc",
|
||||
"mediums.cc",
|
||||
"uuid.cc",
|
||||
"webrtc_stub.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
@@ -30,9 +29,7 @@ cc_library(
|
||||
"ble_v2.h",
|
||||
"bluetooth_classic.h",
|
||||
"bluetooth_radio.h",
|
||||
"lost_entity_tracker.h",
|
||||
"mediums.h",
|
||||
"uuid.h",
|
||||
"webrtc_stub.h",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
@@ -64,10 +61,13 @@ cc_library(
|
||||
name = "utils",
|
||||
srcs = [
|
||||
"utils.cc",
|
||||
"uuid.cc",
|
||||
"webrtc_peer_id.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"lost_entity_tracker.h",
|
||||
"utils.h",
|
||||
"uuid.h",
|
||||
"webrtc_peer_id.h",
|
||||
"webrtc_socket_stub.h",
|
||||
],
|
||||
@@ -75,12 +75,14 @@ cc_library(
|
||||
visibility = [
|
||||
"//connections/implementation:__pkg__",
|
||||
"//connections/implementation/mediums:__pkg__",
|
||||
"//connections/implementation/mediums/ble_v2:__subpackages__",
|
||||
"//connections/implementation/mediums/webrtc:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//connections/implementation/proto:offline_wire_formats_cc_proto",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:types",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "connections/implementation/mediums/ble_v2/ble_advertisement.h"
|
||||
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
|
||||
#include "connections/implementation/mediums/ble_v2/ble_utils.h"
|
||||
#include "connections/implementation/mediums/ble_v2/bloom_filter.h"
|
||||
#include "connections/implementation/mediums/bluetooth_radio.h"
|
||||
#include "connections/implementation/mediums/utils.h"
|
||||
@@ -29,7 +30,6 @@
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/mutex_lock.h"
|
||||
#include "internal/platform/prng.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -43,33 +43,6 @@ using ::location::nearby::api::ble_v2::PowerMode;
|
||||
|
||||
constexpr int kMaxAdvertisementLength = 512;
|
||||
constexpr int kDummyServiceIdLength = 128;
|
||||
constexpr absl::string_view kCopresenceServiceUuid =
|
||||
"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
// These two values make up the base UUID we use when advertising a slot.
|
||||
// The base is an all zero Version-3 name-based UUID. To turn this into an
|
||||
// advertisement slot UUID, we simply OR the least significant bits with the
|
||||
// slot number.
|
||||
//
|
||||
// More info about the format can be found here:
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)
|
||||
constexpr std::int64_t kAdvertisementUuidMsb = 0x0000000000003000;
|
||||
constexpr std::int64_t kAdvertisementUuidLsb = 0x8000000000000000;
|
||||
|
||||
ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes) {
|
||||
return Utils::Sha256Hash(
|
||||
advertisement_bytes,
|
||||
mediums::BleAdvertisementHeader::kAdvertisementHashLength);
|
||||
}
|
||||
|
||||
ByteArray GenerateHash(const std::string& source, size_t size) {
|
||||
return Utils::Sha256Hash(source, size);
|
||||
}
|
||||
|
||||
ByteArray GenerateDeviceToken() {
|
||||
return Utils::Sha256Hash(std::to_string(Prng().NextUint32()),
|
||||
mediums::BleAdvertisement::kDeviceTokenLength);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -122,14 +95,14 @@ bool BleV2::StartAdvertising(
|
||||
|
||||
// Wrap the connections advertisement to the medium advertisement.
|
||||
const bool is_fast_advertisement = !fast_advertisement_service_uuid.empty();
|
||||
ByteArray service_id_hash{GenerateHash(
|
||||
service_id, mediums::BleAdvertisement::kServiceIdHashLength)};
|
||||
ByteArray service_id_hash = mediums::bleutils::GenerateHash(
|
||||
service_id, mediums::BleAdvertisement::kServiceIdHashLength);
|
||||
ByteArray medium_advertisement_bytes{mediums::BleAdvertisement{
|
||||
mediums::BleAdvertisement::Version::kV2,
|
||||
mediums::BleAdvertisement::SocketVersion::kV2,
|
||||
/* service_id_hash= */
|
||||
is_fast_advertisement ? ByteArray{} : service_id_hash,
|
||||
advertisement_bytes, GenerateDeviceToken()}};
|
||||
advertisement_bytes, mediums::bleutils::GenerateDeviceToken()}};
|
||||
if (medium_advertisement_bytes.Empty()) {
|
||||
NEARBY_LOGS(INFO) << "Failed to BLE advertise because we could not wrap a "
|
||||
"connection advertisement to medium advertisement.";
|
||||
@@ -202,9 +175,10 @@ bool BleV2::StartAdvertising(
|
||||
scan_response_data.tx_power_level =
|
||||
BleAdvertisementData::kUnspecifiedTxPowerLevel;
|
||||
scan_response_data.service_uuids.insert(
|
||||
std::string(kCopresenceServiceUuid));
|
||||
std::string(mediums::bleutils::kCopresenceServiceUuid));
|
||||
scan_response_data.service_data.insert(
|
||||
{std::string(kCopresenceServiceUuid), advertisement_header_bytes});
|
||||
{std::string(mediums::bleutils::kCopresenceServiceUuid),
|
||||
advertisement_header_bytes});
|
||||
}
|
||||
|
||||
if (!medium_.StartAdvertising(advertising_data, scan_response_data,
|
||||
@@ -243,10 +217,10 @@ bool BleV2::StopAdvertising(const std::string& service_id) {
|
||||
// TODO(b/213835576): Check the BLE Connections is off. We set the fake
|
||||
// value for the time being till connections is implemented.
|
||||
bool no_incoming_ble_sockets = true;
|
||||
// Restart the BLE advertisement if there is still an advertiser.
|
||||
if (!subscribed_gatt_characteristics_.empty()) {
|
||||
// Set the value of characteristic to empty if there is still an advertiser.
|
||||
if (!hosted_gatt_characteristics_.empty()) {
|
||||
ByteArray empty_value = {};
|
||||
for (const auto& characteristic : subscribed_gatt_characteristics_) {
|
||||
for (const auto& characteristic : hosted_gatt_characteristics_) {
|
||||
if (!gatt_server_->UpdateCharacteristic(characteristic, empty_value)) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "Failed to clear characteristic uuid=" << characteristic.uuid
|
||||
@@ -254,7 +228,7 @@ bool BleV2::StopAdvertising(const std::string& service_id) {
|
||||
<< service_id;
|
||||
}
|
||||
}
|
||||
subscribed_gatt_characteristics_.clear();
|
||||
hosted_gatt_characteristics_.clear();
|
||||
} else if (no_incoming_ble_sockets) {
|
||||
// Otherwise, if we aren't restarting the BLE advertisement, then shutdown
|
||||
// the gatt server if it's not in use.
|
||||
@@ -318,7 +292,8 @@ bool BleV2::StartScanning(const std::string& service_id, PowerLevel power_level,
|
||||
scanned_service_ids_.insert(service_id);
|
||||
// TODO(b/213835576): We should re-start scanning once the power level is
|
||||
// changed.
|
||||
std::vector<std::string> service_uuids{std::string(kCopresenceServiceUuid)};
|
||||
std::vector<std::string> service_uuids{
|
||||
std::string(mediums::bleutils::kCopresenceServiceUuid)};
|
||||
if (!medium_.StartScanning(
|
||||
service_uuids, PowerLevelToPowerMode(power_level),
|
||||
{
|
||||
@@ -393,20 +368,14 @@ bool BleV2::StartAdvertisementGattServerLocked(
|
||||
|
||||
std::unique_ptr<GattServer> gatt_server = medium_.StartGattServer({
|
||||
.characteristic_subscription_cb =
|
||||
[this](const ServerGattConnection& connection,
|
||||
const GattCharacteristic& characteristic) {
|
||||
MutexLock lock(&mutex_);
|
||||
subscribed_gatt_characteristics_.insert(characteristic);
|
||||
[](const ServerGattConnection& connection,
|
||||
const GattCharacteristic& characteristic) {
|
||||
// TODO(b/213835576): Impl or remove.
|
||||
},
|
||||
.characteristic_unsubscription_cb =
|
||||
[this](const ServerGattConnection& connection,
|
||||
const GattCharacteristic& characteristic) {
|
||||
MutexLock lock(&mutex_);
|
||||
const auto char_it =
|
||||
subscribed_gatt_characteristics_.find(characteristic);
|
||||
if (char_it != subscribed_gatt_characteristics_.end()) {
|
||||
subscribed_gatt_characteristics_.erase(char_it);
|
||||
}
|
||||
[](const ServerGattConnection& connection,
|
||||
const GattCharacteristic& characteristic) {
|
||||
// TODO(b/213835576): Impl or remove.
|
||||
},
|
||||
});
|
||||
if (!gatt_server || !gatt_server->IsValid()) {
|
||||
@@ -436,21 +405,21 @@ bool BleV2::GenerateAdvertisementCharacteristic(
|
||||
GattCharacteristic::Property::kRead};
|
||||
|
||||
absl::optional<GattCharacteristic> gatt_characteristic =
|
||||
gatt_server.CreateCharacteristic(std::string(kCopresenceServiceUuid),
|
||||
GenerateAdvertisementUuid(slot),
|
||||
permissions, properties);
|
||||
|
||||
gatt_server.CreateCharacteristic(
|
||||
std::string(mediums::bleutils::kCopresenceServiceUuid),
|
||||
mediums::bleutils::GenerateAdvertisementUuid(slot), permissions,
|
||||
properties);
|
||||
if (!gatt_characteristic.has_value()) {
|
||||
NEARBY_LOGS(INFO) << "Unable to create and add a characterstic to the gatt "
|
||||
"server for the advertisement.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gatt_server.UpdateCharacteristic(*gatt_characteristic,
|
||||
if (!gatt_server.UpdateCharacteristic(gatt_characteristic.value(),
|
||||
gatt_advertisement)) {
|
||||
NEARBY_LOGS(INFO) << "Unable to write a value to the GATT characteristic.";
|
||||
return false;
|
||||
}
|
||||
hosted_gatt_characteristics_.insert(gatt_characteristic.value());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -478,7 +447,7 @@ ByteArray BleV2::CreateAdvertisementHeader() {
|
||||
bloom_filter.Add(dummy_service_id);
|
||||
|
||||
ByteArray advertisement_hash =
|
||||
GenerateAdvertisementHash(dummy_service_id_bytes);
|
||||
mediums::bleutils::GenerateAdvertisementHash(dummy_service_id_bytes);
|
||||
for (const auto& item : gatt_advertisements_) {
|
||||
const std::string& service_id = item.second.first;
|
||||
const ByteArray& gatt_advertisement = item.second.second;
|
||||
@@ -489,8 +458,8 @@ ByteArray BleV2::CreateAdvertisementHeader() {
|
||||
std::string advertisement_bodies = absl::StrCat(
|
||||
advertisement_hash.AsStringView(), gatt_advertisement.AsStringView());
|
||||
|
||||
advertisement_hash =
|
||||
GenerateAdvertisementHash(ByteArray(std::move(advertisement_bodies)));
|
||||
advertisement_hash = mediums::bleutils::GenerateAdvertisementHash(
|
||||
ByteArray(std::move(advertisement_bodies)));
|
||||
}
|
||||
|
||||
return ByteArray(mediums::BleAdvertisementHeader(
|
||||
@@ -500,10 +469,6 @@ ByteArray BleV2::CreateAdvertisementHeader() {
|
||||
advertisement_hash, /*psm=*/0));
|
||||
}
|
||||
|
||||
std::string BleV2::GenerateAdvertisementUuid(int slot) {
|
||||
return std::string(Uuid(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot));
|
||||
}
|
||||
|
||||
PowerMode BleV2::PowerLevelToPowerMode(PowerLevel power_level) {
|
||||
switch (power_level) {
|
||||
case PowerLevel::kHighPower:
|
||||
|
||||
@@ -124,7 +124,7 @@ class BleV2 final {
|
||||
absl::flat_hash_map<int, std::pair<std::string, ByteArray>>
|
||||
gatt_advertisements_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::flat_hash_set<api::ble_v2::GattCharacteristic>
|
||||
subscribed_gatt_characteristics_ ABSL_GUARDED_BY(mutex_);
|
||||
hosted_gatt_characteristics_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::flat_hash_set<std::string> scanned_service_ids_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ cc_library(
|
||||
"ble_advertisement.cc",
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
"ble_utils.cc",
|
||||
"bloom_filter.cc",
|
||||
],
|
||||
hdrs = [
|
||||
@@ -28,6 +29,7 @@ cc_library(
|
||||
"ble_advertisement_header.h",
|
||||
"ble_packet.h",
|
||||
"ble_peripheral.h",
|
||||
"ble_utils.h",
|
||||
"bloom_filter.h",
|
||||
"discovered_peripheral_callback.h",
|
||||
],
|
||||
@@ -37,6 +39,7 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
"//connections:core_types",
|
||||
"//connections/implementation/mediums:utils",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:types",
|
||||
@@ -58,6 +61,7 @@ cc_test(
|
||||
"ble_advertisement_test.cc",
|
||||
"ble_packet_test.cc",
|
||||
"ble_peripheral_test.cc",
|
||||
"ble_utils_test.cc",
|
||||
"bloom_filter_test.cc",
|
||||
],
|
||||
deps = [
|
||||
@@ -66,6 +70,7 @@ cc_test(
|
||||
"//internal/platform:comm",
|
||||
"//internal/platform/implementation/g3", # buildcleaner: keep
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_absl//absl/hash:hash_testing",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
|
||||
@@ -32,36 +32,28 @@ const AdvertisementReadResult::Config AdvertisementReadResult::kDefaultConfig{
|
||||
.max_backoff_duration = absl::Minutes(5),
|
||||
};
|
||||
|
||||
// Adds a successfully read advertisement for the specified slot to this read
|
||||
// result. This is fundamentally different from RecordLastReadStatus() because
|
||||
// we can report a read failure, but still manage to read some advertisements.
|
||||
void AdvertisementReadResult::AddAdvertisement(std::int32_t slot,
|
||||
void AdvertisementReadResult::AddAdvertisement(int slot,
|
||||
const ByteArray& advertisement) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Blindly remove from the advertisements map to make sure any existing
|
||||
// key-value pair is destroyed.
|
||||
advertisements_.emplace(slot, advertisement);
|
||||
// Replace if existed.
|
||||
advertisements_.insert_or_assign(slot, advertisement);
|
||||
}
|
||||
|
||||
// Determines whether or not an advertisement was successfully read at the
|
||||
// specified slot.
|
||||
bool AdvertisementReadResult::HasAdvertisement(std::int32_t slot) const {
|
||||
bool AdvertisementReadResult::HasAdvertisement(int slot) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return advertisements_.contains(slot);
|
||||
}
|
||||
|
||||
// Retrieves all raw advertisements that were successfully read.
|
||||
std::vector<const ByteArray*> AdvertisementReadResult::GetAdvertisements()
|
||||
const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
std::vector<const ByteArray*> all_advertisements;
|
||||
all_advertisements.reserve(advertisements_.size());
|
||||
for (const auto& item : advertisements_) {
|
||||
all_advertisements.emplace_back(&item.second);
|
||||
}
|
||||
std::transform(advertisements_.begin(), advertisements_.end(),
|
||||
std::back_inserter(all_advertisements),
|
||||
[](auto& kv) { return &kv.second; });
|
||||
|
||||
return all_advertisements;
|
||||
}
|
||||
@@ -73,7 +65,7 @@ AdvertisementReadResult::EvaluateRetryStatus() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Check if we have already succeeded reading this advertisement.
|
||||
if (status_ == Status::kSuccess) {
|
||||
if (result_.has_value() && *result_ == Result::kSuccess) {
|
||||
return RetryStatus::kPreviouslySucceeded;
|
||||
}
|
||||
|
||||
@@ -85,9 +77,6 @@ AdvertisementReadResult::EvaluateRetryStatus() const {
|
||||
return RetryStatus::kRetry;
|
||||
}
|
||||
|
||||
// Records the status of the latest read, and updates the next backoff
|
||||
// duration for subsequent reads. Be sure to also call
|
||||
// AddAdvertisement() if any advertisements were read.
|
||||
void AdvertisementReadResult::RecordLastReadStatus(bool is_success) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
@@ -101,7 +90,7 @@ void AdvertisementReadResult::RecordLastReadStatus(bool is_success) {
|
||||
} else {
|
||||
// Determine whether or not we were already failing before. If we were, we
|
||||
// should increase the backoff duration.
|
||||
if (status_ == Status::kFailure) {
|
||||
if (result_.has_value() && *result_ == Result::kFailure) {
|
||||
// Use exponential backoff to determine the next backoff duration. This
|
||||
// simply involves multiplying our current backoff duration by some
|
||||
// multiplier.
|
||||
@@ -119,11 +108,9 @@ void AdvertisementReadResult::RecordLastReadStatus(bool is_success) {
|
||||
}
|
||||
|
||||
// Update the internal result.
|
||||
status_ = is_success ? Status::kSuccess : Status::kFailure;
|
||||
result_ = is_success ? Result::kSuccess : Result::kFailure;
|
||||
}
|
||||
|
||||
// Returns how much time has passed since we last tried reading from an
|
||||
// advertisement GATT server.
|
||||
absl::Duration AdvertisementReadResult::GetDurationSinceRead() const {
|
||||
MutexLock lock(&mutex_);
|
||||
return GetDurationSinceReadLocked();
|
||||
|
||||
@@ -30,17 +30,10 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Representation of a GATT advertisement read result. This object helps us
|
||||
// determine whether or not we need to retry GATT reads.
|
||||
// Representation of a GATT/L2CAP advertisement read result. This object helps
|
||||
// us determine whether or not we need to retry GATT reads.
|
||||
class AdvertisementReadResult {
|
||||
public:
|
||||
// We need a long enough duration such that we always trigger a read
|
||||
// retry AND we always connect to it without delay. The former case
|
||||
// helps us initialize an AdvertisementReadResult so that we
|
||||
// unconditionally try reading on the first sighting. And the latter
|
||||
// case helps us connect immediately when we initialize a dummy read
|
||||
// result for fast advertisements (which don't use the GATT server).
|
||||
|
||||
struct Config {
|
||||
// How much to multiply the backoff duration by with every failure to read
|
||||
// from the advertisement GATT server. This should never be below 1!
|
||||
@@ -54,10 +47,11 @@ class AdvertisementReadResult {
|
||||
};
|
||||
|
||||
static const Config kDefaultConfig;
|
||||
|
||||
explicit AdvertisementReadResult(const Config& config = kDefaultConfig)
|
||||
: config_(config) {}
|
||||
~AdvertisementReadResult() = default;
|
||||
|
||||
// Indicator for deciding if we should retry reading the advertisement.
|
||||
enum class RetryStatus {
|
||||
kUnknown = 0,
|
||||
kRetry = 1,
|
||||
@@ -65,20 +59,38 @@ class AdvertisementReadResult {
|
||||
kTooSoon = 3,
|
||||
};
|
||||
|
||||
void AddAdvertisement(std::int32_t slot, const ByteArray& advertisement)
|
||||
// Adds a successfully read advertisement for the specified slot to this read
|
||||
// result. This is fundamentally different from RecordLastReadStatus()
|
||||
// because we can report a read failure, but still manage to read some
|
||||
// advertisements.
|
||||
void AddAdvertisement(int slot, const ByteArray& advertisement)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
bool HasAdvertisement(std::int32_t slot) const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Determines whether or not an advertisement was successfully read at the
|
||||
// specified slot.
|
||||
bool HasAdvertisement(int slot) const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Retrieves all raw advertisements that were successfully read.
|
||||
std::vector<const ByteArray*> GetAdvertisements() const
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Determines what stage we're in for retrying a read from an advertisement
|
||||
// GATT/L2CAP server.
|
||||
RetryStatus EvaluateRetryStatus() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Records the status of the latest read, and updates the next backoff
|
||||
// duration for subsequent reads. Be sure to also call AddAdvertisement(...)
|
||||
// if any advertisements were read.
|
||||
void RecordLastReadStatus(bool is_success) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns how much time has passed since we last tried reading from an
|
||||
// advertisement GATT server.
|
||||
absl::Duration GetDurationSinceRead() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class Status {
|
||||
kUnknown = 0,
|
||||
enum class Result {
|
||||
kFailure = 0,
|
||||
kSuccess = 1,
|
||||
kFailure = 2,
|
||||
};
|
||||
|
||||
absl::Duration GetDurationSinceReadLocked() const
|
||||
@@ -87,13 +99,21 @@ class AdvertisementReadResult {
|
||||
mutable Mutex mutex_;
|
||||
|
||||
// Maps slot numbers to the GATT advertisement found in that slot.
|
||||
absl::flat_hash_map<std::int32_t, ByteArray> advertisements_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
absl::flat_hash_map<int, ByteArray> advertisements_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
Config config_;
|
||||
absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_);
|
||||
Status status_ ABSL_GUARDED_BY(mutex_) = Status::kUnknown;
|
||||
absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_) =
|
||||
config_.base_backoff_duration;
|
||||
|
||||
// We need a long enough duration such that we always trigger a read retry AND
|
||||
// we always connect to it without delay. The former case helps us initialize
|
||||
// an AdvertisementReadResult so that we unconditionally try reading on the
|
||||
// first sighting. And the latter case helps us connect immediately when we
|
||||
// initialize a dummy read result for fast advertisements (which don't use the
|
||||
// GATT server).
|
||||
absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_) =
|
||||
SystemClock::ElapsedRealtime() - config_.max_backoff_duration;
|
||||
absl::optional<Result> result_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -41,7 +41,7 @@ TEST(AdvertisementReadResultTest, AdvertisementExists) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
int slot = 6;
|
||||
advertisement_read_result.AddAdvertisement(slot,
|
||||
ByteArray(kAdvertisementBytes));
|
||||
|
||||
@@ -52,7 +52,7 @@ TEST(AdvertisementReadResultTest, AdvertisementNonExistent) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
int slot = 6;
|
||||
|
||||
EXPECT_FALSE(advertisement_read_result.HasAdvertisement(slot));
|
||||
}
|
||||
@@ -114,7 +114,7 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) {
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Record an absurd amount of failures so we hit the maximum backoff duration.
|
||||
for (std::int32_t i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,27 +192,12 @@ BleAdvertisement::operator ByteArray() const {
|
||||
bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const {
|
||||
return this->GetVersion() == rhs.GetVersion() &&
|
||||
this->GetSocketVersion() == rhs.GetSocketVersion() &&
|
||||
this->IsFastAdvertisement() == rhs.IsFastAdvertisement() &&
|
||||
this->GetServiceIdHash() == rhs.GetServiceIdHash() &&
|
||||
this->GetData() == rhs.GetData() &&
|
||||
this->GetDeviceToken() == rhs.GetDeviceToken();
|
||||
}
|
||||
|
||||
bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const {
|
||||
if (this->GetVersion() != rhs.GetVersion()) {
|
||||
return this->GetVersion() < rhs.GetVersion();
|
||||
}
|
||||
if (this->GetSocketVersion() != rhs.GetSocketVersion()) {
|
||||
return this->GetSocketVersion() < rhs.GetSocketVersion();
|
||||
}
|
||||
if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) {
|
||||
return this->GetServiceIdHash() < rhs.GetServiceIdHash();
|
||||
}
|
||||
if (this->GetDeviceToken() != rhs.GetDeviceToken()) {
|
||||
return this->GetDeviceToken() < rhs.GetDeviceToken();
|
||||
}
|
||||
return this->GetData() < rhs.GetData();
|
||||
}
|
||||
|
||||
bool BleAdvertisement::IsSupportedVersion(Version version) const {
|
||||
return version >= Version::kV1 && version <= Version::kV2;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,15 @@ class BleAdvertisement {
|
||||
static constexpr int kServiceIdHashLength = 3;
|
||||
static constexpr int kDeviceTokenLength = 2;
|
||||
|
||||
// Hashable
|
||||
bool operator==(const BleAdvertisement &rhs) const;
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const BleAdvertisement &b) {
|
||||
return H::combine(std::move(h), b.version_, b.socket_version_,
|
||||
b.fast_advertisement_, b.service_id_hash_, b.data_,
|
||||
b.device_token_);
|
||||
}
|
||||
|
||||
BleAdvertisement() = default;
|
||||
BleAdvertisement(Version version, SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash, const ByteArray &data,
|
||||
@@ -69,9 +78,6 @@ class BleAdvertisement {
|
||||
~BleAdvertisement() = default;
|
||||
|
||||
explicit operator ByteArray() const;
|
||||
// Operator overloads when comparing BleAdvertisement.
|
||||
bool operator==(const BleAdvertisement &rhs) const;
|
||||
bool operator<(const BleAdvertisement &rhs) const;
|
||||
|
||||
bool IsValid() const { return IsSupportedVersion(version_); }
|
||||
Version GetVersion() const { return version_; }
|
||||
|
||||
@@ -136,6 +136,16 @@ BleAdvertisementHeader::operator ByteArray() const {
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
bool BleAdvertisementHeader::operator==(
|
||||
const BleAdvertisementHeader &rhs) const {
|
||||
return GetVersion() == rhs.GetVersion() &&
|
||||
IsExtendedAdvertisement() == rhs.IsExtendedAdvertisement() &&
|
||||
GetNumSlots() == rhs.GetNumSlots() &&
|
||||
GetServiceIdBloomFilter() == rhs.GetServiceIdBloomFilter() &&
|
||||
GetAdvertisementHash() == rhs.GetAdvertisementHash() &&
|
||||
GetPsmValue() == rhs.GetPsmValue();
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -54,6 +54,15 @@ class BleAdvertisementHeader {
|
||||
static constexpr int kAdvertisementHashLength = 4;
|
||||
static constexpr int kServiceIdBloomFilterLength = 10;
|
||||
|
||||
// Hashable
|
||||
bool operator==(const BleAdvertisementHeader &rhs) const;
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const BleAdvertisementHeader &b) {
|
||||
return H::combine(std::move(h), b.version_, b.extended_advertisement_,
|
||||
b.num_slots_, b.service_id_bloom_filter_,
|
||||
b.advertisement_hash_, b.psm_);
|
||||
}
|
||||
|
||||
BleAdvertisementHeader() = default;
|
||||
BleAdvertisementHeader(Version version, bool extended_advertisement,
|
||||
int num_slots,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/hash/hash_testing.h"
|
||||
#include "internal/platform/base64_utils.h"
|
||||
|
||||
namespace location {
|
||||
@@ -214,6 +215,23 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) {
|
||||
EXPECT_FALSE(short_ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, Hash) {
|
||||
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
|
||||
BleAdvertisementHeader(),
|
||||
BleAdvertisementHeader(kVersion, false, 0,
|
||||
ByteArray(std::string(kServiceIDBloomFilter)),
|
||||
ByteArray(std::string(kAdvertisementHash)), 0),
|
||||
BleAdvertisementHeader(
|
||||
kVersion, true, 2,
|
||||
ByteArray("\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15"),
|
||||
ByteArray("\x0E\x0F\x0G\x0H"), 1),
|
||||
BleAdvertisementHeader(
|
||||
kVersion, false, 4,
|
||||
ByteArray("\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29"),
|
||||
ByteArray("\x0i\x0j\x0k\x0l"), 2),
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/hash/hash_testing.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -448,6 +449,21 @@ TEST(BleAdvertisementTest,
|
||||
EXPECT_FALSE(corrupted_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, Hash) {
|
||||
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
|
||||
BleAdvertisement(),
|
||||
BleAdvertisement(kVersion, kSocketVersion, ByteArray{},
|
||||
ByteArray(std::string(kFastData)),
|
||||
ByteArray(std::string(kDeviceToken))),
|
||||
BleAdvertisement(
|
||||
kVersion, kSocketVersion, ByteArray(std::string(kServiceIDHashBytes)),
|
||||
ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken))),
|
||||
BleAdvertisement(kVersion, BleAdvertisement::SocketVersion::kV1,
|
||||
ByteArray{}, ByteArray(std::string(kFastData)),
|
||||
ByteArray(std::string(kDeviceToken))),
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace mediums {
|
||||
class BlePeripheral {
|
||||
public:
|
||||
BlePeripheral() = default;
|
||||
explicit BlePeripheral(const ByteArray& id) : id_(id) {}
|
||||
explicit BlePeripheral(const ByteArray& id) : BlePeripheral(id, 0) {}
|
||||
BlePeripheral(const ByteArray& id, int psm) : id_(id), psm_(psm) {}
|
||||
BlePeripheral(const BlePeripheral&) = default;
|
||||
BlePeripheral& operator=(const BlePeripheral&) = default;
|
||||
BlePeripheral(BlePeripheral&&) = default;
|
||||
@@ -39,11 +40,17 @@ class BlePeripheral {
|
||||
|
||||
bool IsValid() const { return !id_.Empty(); }
|
||||
ByteArray GetId() const { return id_; }
|
||||
int GetPsm() const { return psm_; }
|
||||
|
||||
private:
|
||||
// A unique identifier for this peripheral. It is the BLE advertisement it
|
||||
// was found on.
|
||||
// was found on, or even simply the BLE MAC address.
|
||||
ByteArray id_;
|
||||
|
||||
// The psm (protocol service multiplexer) value is used for create data
|
||||
// connection on L2CAP socket. It only exists when remote device supports
|
||||
// L2CAP socket feature.
|
||||
int psm_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -23,14 +23,27 @@ namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr absl::string_view kId{"AB12"};
|
||||
constexpr int kDefaultPsm = 0;
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionWorks) {
|
||||
ByteArray id{std::string(kId)};
|
||||
|
||||
BlePeripheral ble_peripheral{id};
|
||||
BlePeripheral ble_peripheral(id);
|
||||
|
||||
EXPECT_TRUE(ble_peripheral.IsValid());
|
||||
EXPECT_EQ(id, ble_peripheral.GetId());
|
||||
EXPECT_EQ(kDefaultPsm, ble_peripheral.GetPsm());
|
||||
}
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionWorksWithPsm) {
|
||||
ByteArray id{std::string(kId)};
|
||||
int psm = 1;
|
||||
|
||||
BlePeripheral ble_peripheral(id, psm);
|
||||
|
||||
EXPECT_TRUE(ble_peripheral.IsValid());
|
||||
EXPECT_EQ(id, ble_peripheral.GetId());
|
||||
EXPECT_EQ(psm, ble_peripheral.GetPsm());
|
||||
}
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionEmptyFails) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 "connections/implementation/mediums/ble_v2/ble_utils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace bleutils {
|
||||
|
||||
namespace {
|
||||
|
||||
// These two values make up the base UUID we use when advertising a slot.
|
||||
// The base is an all zero Version-3 name-based UUID. To turn this into an
|
||||
// advertisement slot UUID, we simply OR the least significant bits with the
|
||||
// slot number.
|
||||
//
|
||||
// More info about the format can be found here:
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)
|
||||
constexpr std::int64_t kAdvertisementUuidMsb = 0x0000000000003000;
|
||||
constexpr std::int64_t kAdvertisementUuidLsb = 0x8000000000000000;
|
||||
|
||||
} // namespace
|
||||
|
||||
const absl::string_view kCopresenceServiceUuid =
|
||||
"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
ByteArray GenerateHash(const std::string& source, size_t size) {
|
||||
return Utils::Sha256Hash(source, size);
|
||||
}
|
||||
|
||||
ByteArray GenerateServiceIdHash(const std::string& service_id) {
|
||||
return Utils::Sha256Hash(service_id, BlePacket::kServiceIdHashLength);
|
||||
}
|
||||
|
||||
ByteArray GenerateDeviceToken() {
|
||||
return Utils::Sha256Hash(std::to_string(Prng().NextUint32()),
|
||||
mediums::BleAdvertisement::kDeviceTokenLength);
|
||||
}
|
||||
|
||||
ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes) {
|
||||
return Utils::Sha256Hash(advertisement_bytes,
|
||||
BleAdvertisementHeader::kAdvertisementHashLength);
|
||||
}
|
||||
|
||||
std::string GenerateAdvertisementUuid(int slot) {
|
||||
if (slot < 0) {
|
||||
return {};
|
||||
}
|
||||
return std::string(Uuid(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot));
|
||||
}
|
||||
|
||||
} // namespace bleutils
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 CORE_INTERNAL_MEDIUMS_BLE_V2_UTILS_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "connections/implementation/mediums/ble_v2//ble_advertisement.h"
|
||||
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
|
||||
#include "connections/implementation/mediums/ble_v2/ble_packet.h"
|
||||
#include "connections/implementation/mediums/utils.h"
|
||||
#include "connections/implementation/mediums/uuid.h"
|
||||
#include "internal/platform/prng.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace bleutils {
|
||||
|
||||
ABSL_CONST_INIT extern const absl::string_view kCopresenceServiceUuid;
|
||||
|
||||
ByteArray GenerateHash(const std::string& source, size_t size);
|
||||
ByteArray GenerateServiceIdHash(const std::string& service_id);
|
||||
ByteArray GenerateDeviceToken();
|
||||
ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes);
|
||||
std::string GenerateAdvertisementUuid(int slot);
|
||||
|
||||
} // namespace bleutils
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_UTILS_H_
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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 "connections/implementation/mediums/ble_v2/ble_utils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace bleutils {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(BleUtilsTest, CanGenerateHash) {
|
||||
std::string service_id = {"service_id"};
|
||||
|
||||
ByteArray generated_bytes = GenerateHash(service_id, 4);
|
||||
|
||||
// Can generate a non-empty byte array.
|
||||
EXPECT_FALSE(generated_bytes.Empty());
|
||||
|
||||
generated_bytes = GenerateHash(service_id, 0);
|
||||
|
||||
// Cannot generate a non-empty byte array for size 0.
|
||||
EXPECT_TRUE(generated_bytes.Empty());
|
||||
}
|
||||
|
||||
TEST(BleUtilsTest, CanGenerateServiceIdHash) {
|
||||
std::string service_id = {"service_id"};
|
||||
|
||||
ByteArray generated_bytes = GenerateServiceIdHash(service_id);
|
||||
|
||||
// Can generate a non-empty byte array.
|
||||
EXPECT_FALSE(generated_bytes.Empty());
|
||||
}
|
||||
|
||||
TEST(BleUtilsTest, CanGenerateDeviceToken) {
|
||||
ByteArray generated_bytes = GenerateDeviceToken();
|
||||
|
||||
// Can generate a non-empty byte array.
|
||||
EXPECT_FALSE(generated_bytes.Empty());
|
||||
}
|
||||
|
||||
TEST(BleUtilsTest, CanGenerateAdvertisementHash) {
|
||||
ByteArray empty_advertisement_bytes = {};
|
||||
ByteArray non_empty_advertisement_bytes("abcd");
|
||||
ByteArray generated_bytes_1 =
|
||||
GenerateAdvertisementHash(empty_advertisement_bytes);
|
||||
|
||||
ByteArray generated_bytes_2 =
|
||||
GenerateAdvertisementHash(non_empty_advertisement_bytes);
|
||||
|
||||
// Can generate a non-empty byte array.
|
||||
EXPECT_FALSE(generated_bytes_1.Empty());
|
||||
EXPECT_FALSE(generated_bytes_2.Empty());
|
||||
}
|
||||
|
||||
TEST(BleUtilsTest, CanGenerateAdvertisementUuid) {
|
||||
std::string generated_string = GenerateAdvertisementUuid(0);
|
||||
|
||||
EXPECT_EQ("00000000-0000-3000-8000-000000000000", generated_string);
|
||||
|
||||
generated_string = GenerateAdvertisementUuid(1);
|
||||
|
||||
EXPECT_EQ("00000000-0000-3000-8000-000000000001", generated_string);
|
||||
|
||||
generated_string = GenerateAdvertisementUuid(10);
|
||||
|
||||
EXPECT_EQ("00000000-0000-3000-8000-00000000000a", generated_string);
|
||||
|
||||
generated_string = GenerateAdvertisementUuid(-1);
|
||||
|
||||
// Can't generate an advertisement uuid for slot < 0. The result is empty.
|
||||
EXPECT_TRUE(generated_string.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace bleutils
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -197,6 +197,7 @@ cc_test(
|
||||
":test_util",
|
||||
"//internal/platform/implementation/g3", # build_cleaner: keep
|
||||
"@com_github_protobuf_matchers//protobuf-matchers",
|
||||
"@com_google_absl//absl/hash:hash_testing",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -99,6 +99,12 @@ class ByteArray {
|
||||
return absl::string_view(data(), size());
|
||||
}
|
||||
|
||||
// Hashable
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const ByteArray& m) {
|
||||
return H::combine(std::move(h), m.data_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/hash/hash_testing.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -95,4 +96,13 @@ TEST(ByteArrayTest, CreateFromAbslStringReturnsTheSame) {
|
||||
EXPECT_EQ(bytes.AsStringView(), kTestString);
|
||||
}
|
||||
|
||||
TEST(ByteArrayTest, Hash) {
|
||||
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
|
||||
ByteArray(),
|
||||
ByteArray("12345"),
|
||||
ByteArray("ABCDE"),
|
||||
ByteArray("A1B2Z"),
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user