[BLE Refactor] Implement CreateAdvertisementHeader in BleV2 medium.

PiperOrigin-RevId: 429550598
This commit is contained in:
edwinwu
2022-02-18 06:30:09 -08:00
committed by Copybara-Service
parent ca172feb55
commit 8326aeef38
12 changed files with 590 additions and 2 deletions
+2
View File
@@ -17,6 +17,7 @@ cc_library(
name = "mediums",
srcs = [
"ble.cc",
"ble_v2.cc",
"bluetooth_classic.cc",
"bluetooth_radio.cc",
"mediums.cc",
@@ -26,6 +27,7 @@ cc_library(
],
hdrs = [
"ble.h",
"ble_v2.h",
"bluetooth_classic.h",
"bluetooth_radio.h",
"lost_entity_tracker.h",
@@ -0,0 +1,84 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "connections/implementation/mediums/ble_v2.h"
#include <string>
#include <string_view>
#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/bloom_filter.h"
#include "connections/implementation/mediums/utils.h"
#include "internal/platform/prng.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
constexpr int kDummyServiceIdLength = 128;
ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes) {
return Utils::Sha256Hash(
advertisement_bytes,
mediums::BleAdvertisementHeader::kAdvertisementHashLength);
}
} // namespace
BleV2::BleV2(BluetoothRadio& radio)
: radio_(radio), adapter_(radio_.GetBluetoothAdapter()) {}
ByteArray BleV2::CreateAdvertisementHeader() {
// Create a randomized dummy service id to anonymize the header with.
ByteArray dummy_service_id_bytes =
Utils::GenerateRandomBytes(kDummyServiceIdLength);
std::string dummy_service_id{dummy_service_id_bytes};
mediums::BloomFilter<
mediums::BleAdvertisementHeader::kServiceIdBloomFilterLength>
bloom_filter;
bloom_filter.Add(dummy_service_id);
ByteArray advertisement_hash =
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;
bloom_filter.Add(service_id);
// Compute the next hash according to the algorithm in
// https://source.corp.google.com/piper///depot/google3/java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums/bluetooth/BluetoothLowEnergy.java;rcl=428397891;l=1043
ByteArray previous_advertisement_hash = advertisement_hash;
std::string advertisement_bodies =
absl::StrCat(previous_advertisement_hash.AsStringView(),
gatt_advertisement.AsStringView());
advertisement_hash =
GenerateAdvertisementHash(ByteArray(std::move(advertisement_bodies)));
}
return ByteArray(mediums::BleAdvertisementHeader(
mediums::BleAdvertisementHeader::Version::kV2,
/*extended_advertisement=*/false,
/*num_slots=*/gatt_advertisements_.size(), ByteArray(bloom_filter),
advertisement_hash, /*psm=*/0));
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,52 @@
// 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_H_
#define CORE_INTERNAL_MEDIUMS_BLE_V2_H_
#include <cstdint>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "connections/implementation/mediums/bluetooth_radio.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/mutex.h"
namespace location {
namespace nearby {
namespace connections {
// TODO(b/213691253): Add BleV2 medium tests after more functions are ready.
// Provides the operations that can be performed on the Bluetooth Low Energy
// (BLE) medium.
class BleV2 {
public:
explicit BleV2(BluetoothRadio& bluetooth_radio);
private:
ByteArray CreateAdvertisementHeader() ABSL_SHARED_LOCKS_REQUIRED(mutex_);
mutable Mutex mutex_;
BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_);
BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_);
absl::flat_hash_map<int, std::pair<std::string, ByteArray>>
gatt_advertisements_ ABSL_GUARDED_BY(mutex_);
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_H_
@@ -26,6 +26,10 @@ namespace nearby {
namespace connections {
namespace mediums {
// These definitions are necessary before C++17.
constexpr int BleAdvertisementHeader::kAdvertisementHashLength;
constexpr int BleAdvertisementHeader::kServiceIdBloomFilterLength;
BleAdvertisementHeader::BleAdvertisementHeader(
Version version, bool extended_advertisement, int num_slots,
const ByteArray &service_id_bloom_filter,
@@ -51,6 +51,9 @@ class BleAdvertisementHeader {
// characteristic so the two are not compatible.
};
static constexpr int kAdvertisementHashLength = 4;
static constexpr int kServiceIdBloomFilterLength = 10;
BleAdvertisementHeader() = default;
BleAdvertisementHeader(Version version, bool extended_advertisement,
int num_slots,
@@ -76,8 +79,6 @@ class BleAdvertisementHeader {
private:
static constexpr int kVersionAndNumSlotsLength = 1;
static constexpr int kServiceIdBloomFilterLength = 10;
static constexpr int kAdvertisementHashLength = 4;
static constexpr int kMinAdvertisementHeaderLength =
kVersionAndNumSlotsLength + kServiceIdBloomFilterLength +
kAdvertisementHashLength;
+14
View File
@@ -0,0 +1,14 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
+90
View File
@@ -0,0 +1,90 @@
// 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 "third_party/nearby/cpp/core/internal/mediums/ble_v2.h"
#include <string>
#include "absl/strings/str_cat.h"
#include "third_party/nearby/cpp/core/internal/mediums/ble_v2/ble_advertisement.h"
#include "third_party/nearby/cpp/core/internal/mediums/ble_v2/ble_advertisement_header.h"
#include "third_party/nearby/cpp/core/internal/mediums/ble_v2/bloom_filter.h"
#include "third_party/nearby/cpp/core/internal/mediums/utils.h"
#include "third_party/nearby/cpp/platform/base/prng.h"
namespace location {
namespace nearby {
namespace connections {
ByteArray BleV2::GenerateAdvertisementHash(
const ByteArray& advertisement_bytes) {
return Utils::Sha256Hash(
advertisement_bytes,
mediums::BleAdvertisementHeader::kAdvertisementHashLength);
}
ByteArray BleV2::GenerateHash(const std::string& source, size_t size) {
return Utils::Sha256Hash(source, size);
}
ByteArray BleV2::GenerateDeviceToken() {
return Utils::Sha256Hash(std::to_string(Prng().NextUint32()),
mediums::BleAdvertisement::kDeviceTokenLength);
}
BleV2::BleV2(BluetoothRadio& radio) : radio_(radio) {}
ByteArray BleV2::CreateAdvertisementHeader() {
// Create a randomized dummy service id to anonymize the header with.
ByteArray dummy_service_id_bytes =
Utils::GenerateRandomBytes(kDummyServiceIdLength);
std::string dummy_service_id{dummy_service_id_bytes};
// Create a bloom filter with the dummy service id.
mediums::BloomFilter<
mediums::BleAdvertisementHeader::kServiceIdBloomFilterLength>
bloom_filter;
bloom_filter.Add(dummy_service_id);
// Add the service id for each GATT advertisement into the bloom filter, while
// also creating a hash of dummyServiceIdBytes + all GATT advertisements.
ByteArray advertisement_hash =
GenerateAdvertisementHash(dummy_service_id_bytes);
int num_slots = 0;
for (const auto& item : gatt_advertisement_info.gatt_advertisements) {
std::string service_id = item.second.first;
ByteArray gatt_advertisment = item.second.second;
bloom_filter.Add(service_id);
// Compute the next hash by taking the hash of [previous hash] + [next
// advertisement data].
ByteArray previous_advertisement_hash = advertisement_hash;
std::string advertisement_bodies =
absl::StrCat(std::string(previous_advertisement_hash),
std::string(gatt_advertisment));
advertisement_hash =
GenerateAdvertisementHash(ByteArray{std::move(advertisement_bodies)});
num_slots++;
}
return ByteArray(mediums::BleAdvertisementHeader(
mediums::BleAdvertisementHeader::Version::kV2,
/*extended_advertisement=*/false, num_slots, ByteArray(bloom_filter),
advertisement_hash, /*psm=*/0));
}
} // namespace connections
} // namespace nearby
} // namespace location
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_H_
#define CORE_INTERNAL_MEDIUMS_BLE_V2_H_
#include <cstdint>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "third_party/nearby/cpp/core/internal/mediums/bluetooth_radio.h"
#include "third_party/nearby/cpp/platform/base/byte_array.h"
#include "third_party/nearby/cpp/platform/public/mutex.h"
namespace location {
namespace nearby {
namespace connections {
class BleV2 {
public:
explicit BleV2(BluetoothRadio& bluetooth_radio);
~BleV2() = default;
private:
// Container that stores info about a GATT advertisement.
struct GattAdvertisementInfo {
bool Empty() const { return gatt_advertisements.empty(); }
void Clear() { gatt_advertisements.clear(); }
void Add(int slot, std::pair<std::string, ByteArray> gatt_advertisement) {
gatt_advertisements.insert({slot, gatt_advertisement});
}
void Remove(int slot) { gatt_advertisements.erase(slot); }
bool Existed(int slot) const {
return gatt_advertisements.contains(slot);
}
std::pair<std::string, ByteArray> GetAdvertisement(int slot) {
const auto& it = gatt_advertisements.find(slot);
if (it == gatt_advertisements.end()) {
return {};
}
return it->second;
}
// A map of slot -> pair of {service_id, gatt_advertisement}
absl::flat_hash_map<int, std::pair<std::string, ByteArray>>
gatt_advertisements;
};
static constexpr int kMaxAdvertisementLength = 512;
static constexpr int kDummyServiceIdLength = 128;
static ByteArray GenerateAdvertisementHash(
const ByteArray& advertisement_bytes);
static ByteArray GenerateHash(const std::string& source, size_t size);
static ByteArray GenerateDeviceToken();
ByteArray CreateAdvertisementHeader() ABSL_SHARED_LOCKS_REQUIRED(mutex_);
mutable Mutex mutex_;
BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_);
BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){
radio_.GetBluetoothAdapter()};
GattAdvertisementInfo gatt_advertisement_info ABSL_GUARDED_BY(mutex_);
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_H_
@@ -0,0 +1,142 @@
// Copyright 2020 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 "third_party/nearby/cpp/core/internal/mediums/ble_v2/ble_advertisement_header.h"
#include <inttypes.h>
#include "absl/strings/str_cat.h"
#include "third_party/nearby/cpp/platform/base/base64_utils.h"
#include "third_party/nearby/cpp/platform/base/base_input_stream.h"
#include "third_party/nearby/cpp/platform/public/logging.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// These definitions are necessary before C++17.
constexpr int BleAdvertisementHeader::kAdvertisementHashLength;
constexpr int BleAdvertisementHeader::kServiceIdBloomFilterLength;
BleAdvertisementHeader::BleAdvertisementHeader(
Version version, bool extended_advertisement, int num_slots,
const ByteArray &service_id_bloom_filter,
const ByteArray &advertisement_hash, int psm) {
if (version != Version::kV2 || num_slots <= 0 ||
service_id_bloom_filter.size() != kServiceIdBloomFilterLength ||
advertisement_hash.size() != kAdvertisementHashLength) {
return;
}
version_ = version;
extended_advertisement_ = extended_advertisement;
num_slots_ = num_slots;
service_id_bloom_filter_ = service_id_bloom_filter;
advertisement_hash_ = advertisement_hash;
psm_ = psm;
}
BleAdvertisementHeader::BleAdvertisementHeader(
const ByteArray &ble_advertisement_header_bytes) {
if (ble_advertisement_header_bytes.Empty()) {
NEARBY_LOG(
ERROR,
"Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding");
return;
}
if (ble_advertisement_header_bytes.size() < kMinAdvertisementHeaderLength) {
NEARBY_LOG(ERROR,
"Cannot deserialize BleAdvertisementHeader: expecting min %u "
"raw bytes, got %" PRIu64 " instead",
kMinAdvertisementHeaderLength,
ble_advertisement_header_bytes.size());
return;
}
ByteArray advertisement_header_bytes{ble_advertisement_header_bytes};
BaseInputStream base_input_stream{advertisement_header_bytes};
// The first 1 byte is supposed to be the version and number of slots.
auto version_and_num_slots_byte =
static_cast<char>(base_input_stream.ReadUint8());
// The upper 3 bits are supposed to be the version.
version_ =
static_cast<Version>((version_and_num_slots_byte & kVersionBitmask) >> 5);
if (version_ != Version::kV2) {
NEARBY_LOG(
ERROR,
"Cannot deserialize BleAdvertisementHeader: unsupported Version %d",
version_);
return;
}
// The next 1 bit is supposed to be the extended advertisement flag.
extended_advertisement_ =
((version_and_num_slots_byte & kExtendedAdvertismentBitMask) >> 4) == 1;
// The lower 4 bits are supposed to be the number of slots.
num_slots_ = static_cast<int>(version_and_num_slots_byte & kNumSlotsBitmask);
if (num_slots_ <= 0) {
version_ = Version::kUndefined;
return;
}
// The next 10 bytes are supposed to be the service_id_bloom_filter.
service_id_bloom_filter_ =
base_input_stream.ReadBytes(kServiceIdBloomFilterLength);
// The next 4 bytes are supposed to be the advertisement_hash.
advertisement_hash_ = base_input_stream.ReadBytes(kAdvertisementHashLength);
// The next 2 bytes are PSM value.
if (base_input_stream.IsAvailable(sizeof(std::uint16_t))) {
psm_ = static_cast<int>(base_input_stream.ReadUint16());
}
}
BleAdvertisementHeader::operator ByteArray() const {
if (!IsValid()) {
return ByteArray();
}
// The first 3 bits are the Version.
char version_and_num_slots_byte =
(static_cast<char>(version_) << 5) & kVersionBitmask;
// The next 1 bit is extended advertisement flag.
version_and_num_slots_byte |=
(static_cast<char>(extended_advertisement_) << 4) &
kExtendedAdvertismentBitMask;
// The next 5 bits are the number of slots.
version_and_num_slots_byte |=
static_cast<char>(num_slots_) & kNumSlotsBitmask;
// Convert psm_ value to 2-bytes.
ByteArray psm_byte{sizeof(std::uint16_t)};
char *data = psm_byte.data();
data[0] = psm_ & 0xFF00;
data[1] = psm_ & 0x00FF;
// clang-format off
std::string out = absl::StrCat(std::string(1, version_and_num_slots_byte),
std::string(service_id_bloom_filter_),
std::string(advertisement_hash_),
std::string(psm_byte));
// clang-format on
return ByteArray(std::move(out));
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,102 @@
// Copyright 2020 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_BLE_ADVERTISEMENT_HEADER_H_
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_
#include <string>
#include "third_party/nearby/cpp/platform/base/byte_array.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Represents the format of the Mediums BLE Advertisement Header used in
// Advertising + Discovery.
//
// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH][L2_CAP_PSM]
//
// See go/nearby-ble-design for more information.
//
// Note. The object constructed by default constructor or the parameterized
// constructor with invalid value(s) is treated as invalid instance. Caller
// should be responsible to call IsValid() to check the instance is invalid in
// advance before continue on.
class BleAdvertisementHeader {
public:
// Versions of the BleAdvertisementHeader.
enum class Version {
kUndefined = 0,
kV1 = 1,
kV2 = 2,
// Version is only allocated 3 bits in the BleAdvertisementHeader, so this
// can never go beyond V7.
//
// V1 is not present because it's an old format used in Nearby Connections
// before this logic was pushed down into Nearby Mediums. V1 put
// everything in the service data, while V2 puts the data inside a GATT
// characteristic so the two are not compatible.
};
static constexpr int kAdvertisementHashLength = 4;
static constexpr int kServiceIdBloomFilterLength = 10;
BleAdvertisementHeader() = default;
BleAdvertisementHeader(Version version, bool extended_advertisement,
int num_slots,
const ByteArray &service_id_bloom_filter,
const ByteArray &advertisement_hash, int psm);
explicit BleAdvertisementHeader(
const ByteArray &ble_advertisement_header_bytes);
BleAdvertisementHeader(const BleAdvertisementHeader &) = default;
BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default;
BleAdvertisementHeader(BleAdvertisementHeader &&) = default;
BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default;
~BleAdvertisementHeader() = default;
explicit operator ByteArray() const;
bool IsValid() const { return version_ == Version::kV2; }
Version GetVersion() const { return version_; }
bool IsExtendedAdvertisement() const { return extended_advertisement_; }
int GetNumSlots() const { return num_slots_; }
ByteArray GetServiceIdBloomFilter() const { return service_id_bloom_filter_; }
ByteArray GetAdvertisementHash() const { return advertisement_hash_; }
int GetPsmValue() const { return psm_; }
private:
static constexpr int kVersionAndNumSlotsLength = 1;
static constexpr int kMinAdvertisementHeaderLength =
kVersionAndNumSlotsLength + kServiceIdBloomFilterLength +
kAdvertisementHashLength;
static constexpr int kVersionBitmask = 0x0E0;
static constexpr int kExtendedAdvertismentBitMask = 0x010;
static constexpr int kNumSlotsBitmask = 0x00F;
Version version_ = Version::kUndefined;
bool extended_advertisement_ = false;
int num_slots_ = 0;
ByteArray service_id_bloom_filter_;
ByteArray advertisement_hash_;
int psm_ = 0;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_
+7
View File
@@ -23,6 +23,8 @@
#include <type_traits>
#include <utility>
#include "absl/strings/str_cat.h"
namespace location {
namespace nearby {
@@ -92,6 +94,11 @@ class ByteArray {
// operation.
explicit operator std::string() && { return std::move(data_); }
// Returns the representation of the underlying data as a string view.
absl::string_view AsStringView() const {
return absl::string_view(data(), size());
}
private:
std::string data_;
};
+8
View File
@@ -87,4 +87,12 @@ TEST(ByteArrayTest, CreateFromNonNullTerminatedStdArray) {
EXPECT_EQ(std::string(bytes), std::string(data.data(), data.size()));
}
TEST(ByteArrayTest, CreateFromAbslStringReturnsTheSame) {
const absl::string_view kTestString = "Test String";
ByteArray bytes{std::string(kTestString)};
EXPECT_EQ(bytes.size(), kTestString.size());
EXPECT_EQ(bytes.AsStringView(), kTestString);
}
} // namespace