mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
[BLE Refactor] BleAdvertisementHeader by adding psm value and extended advertisement flag to align with Android's counterpart.
PiperOrigin-RevId: 421797950
This commit is contained in:
committed by
Copybara-Service
parent
5e845655bb
commit
cf937ed90f
@@ -20,6 +20,7 @@ cc_library(
|
||||
"ble_advertisement.cc",
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
"bloom_filter.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.h",
|
||||
@@ -27,6 +28,7 @@ cc_library(
|
||||
"ble_advertisement_header.h",
|
||||
"ble_packet.h",
|
||||
"ble_peripheral.h",
|
||||
"bloom_filter.h",
|
||||
"discovered_peripheral_callback.h",
|
||||
],
|
||||
compatible_with = ["//buildenv/target:non_prod"],
|
||||
@@ -37,6 +39,7 @@ cc_library(
|
||||
deps = [
|
||||
"//absl/container:flat_hash_map",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/numeric:int128",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//core:core_types",
|
||||
@@ -44,6 +47,7 @@ cc_library(
|
||||
"//platform/base:util",
|
||||
"//platform/public:logging",
|
||||
"//platform/public:types",
|
||||
"//smhasher:libmurmur3",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -55,6 +59,7 @@ cc_test(
|
||||
"ble_advertisement_test.cc",
|
||||
"ble_packet_test.cc",
|
||||
"ble_peripheral_test.cc",
|
||||
"bloom_filter_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":ble_v2",
|
||||
|
||||
@@ -27,8 +27,9 @@ namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
Version version, int num_slots, const ByteArray &service_id_bloom_filter,
|
||||
const ByteArray &advertisement_hash) {
|
||||
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) {
|
||||
@@ -36,16 +37,15 @@ BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
}
|
||||
|
||||
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 std::string &ble_advertisement_header_string) {
|
||||
ByteArray ble_advertisement_header_bytes =
|
||||
Base64Utils::Decode(ble_advertisement_header_string);
|
||||
|
||||
const ByteArray &ble_advertisement_header_bytes) {
|
||||
if (ble_advertisement_header_bytes.Empty()) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
@@ -62,12 +62,14 @@ BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
return;
|
||||
}
|
||||
|
||||
BaseInputStream base_input_stream{ble_advertisement_header_bytes};
|
||||
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_pcp_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
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_pcp_byte & kVersionBitmask) >> 5);
|
||||
static_cast<Version>((version_and_num_slots_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV2) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
@@ -75,8 +77,11 @@ BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The lower 5 bits are supposed to be the number of slots.
|
||||
num_slots_ = static_cast<int>(version_and_pcp_byte & kNumSlotsBitmask);
|
||||
// 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;
|
||||
@@ -88,41 +93,43 @@ BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
|
||||
// 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 std::string() const {
|
||||
BleAdvertisementHeader::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return "";
|
||||
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(advertisement_hash_),
|
||||
std::string(psm_byte));
|
||||
// clang-format on
|
||||
|
||||
return Base64Utils::Encode(ByteArray(std::move(out)));
|
||||
}
|
||||
|
||||
bool BleAdvertisementHeader::operator<(
|
||||
const BleAdvertisementHeader &rhs) const {
|
||||
if (this->GetVersion() != rhs.GetVersion()) {
|
||||
return this->GetVersion() < rhs.GetVersion();
|
||||
}
|
||||
if (this->GetNumSlots() != rhs.GetNumSlots()) {
|
||||
return this->GetNumSlots() < rhs.GetNumSlots();
|
||||
}
|
||||
if (this->GetServiceIdBloomFilter() != rhs.GetServiceIdBloomFilter()) {
|
||||
return this->GetServiceIdBloomFilter() < rhs.GetServiceIdBloomFilter();
|
||||
}
|
||||
return this->GetAdvertisementHash() < rhs.GetAdvertisementHash();
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mediums {
|
||||
// Represents the format of the Mediums BLE Advertisement Header used in
|
||||
// Advertising + Discovery.
|
||||
//
|
||||
// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH]
|
||||
// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH][L2_CAP_PSM]
|
||||
//
|
||||
// See go/nearby-ble-design for more information.
|
||||
//
|
||||
@@ -52,41 +52,45 @@ class BleAdvertisementHeader {
|
||||
};
|
||||
|
||||
BleAdvertisementHeader() = default;
|
||||
BleAdvertisementHeader(Version version, int num_slots,
|
||||
BleAdvertisementHeader(Version version, bool extended_advertisement,
|
||||
int num_slots,
|
||||
const ByteArray &service_id_bloom_filter,
|
||||
const ByteArray &advertisement_hash);
|
||||
const ByteArray &advertisement_hash, int psm);
|
||||
explicit BleAdvertisementHeader(
|
||||
const std::string &ble_advertisement_header_string);
|
||||
const ByteArray &ble_advertisement_header_bytes);
|
||||
BleAdvertisementHeader(const BleAdvertisementHeader &) = default;
|
||||
BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default;
|
||||
BleAdvertisementHeader(BleAdvertisementHeader &&) = default;
|
||||
BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default;
|
||||
~BleAdvertisementHeader() = default;
|
||||
|
||||
// Produces an encoded binary string which can be decoded by the explicit
|
||||
// constructor. The returned string is empty if BleAdvertisementHeader is not
|
||||
// valid - false on IsValid().
|
||||
explicit operator std::string() const;
|
||||
bool operator<(const BleAdvertisementHeader &rhs) const;
|
||||
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 kServiceIdBloomFilterLength = 10;
|
||||
static constexpr int kAdvertisementHashLength = 4;
|
||||
static constexpr int kMinAdvertisementHeaderLength =
|
||||
1 + kServiceIdBloomFilterLength + kAdvertisementHashLength;
|
||||
kVersionAndNumSlotsLength + kServiceIdBloomFilterLength +
|
||||
kAdvertisementHashLength;
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kNumSlotsBitmask = 0x01F;
|
||||
static constexpr int kExtendedAdvertismentBitMask = 0x010;
|
||||
static constexpr int kNumSlotsBitmask = 0x00F;
|
||||
|
||||
Version version_ = Version::kUndefined;
|
||||
int num_slots_;
|
||||
bool extended_advertisement_ = false;
|
||||
int num_slots_ = 0;
|
||||
ByteArray service_id_bloom_filter_;
|
||||
ByteArray advertisement_hash_;
|
||||
int psm_ = 0;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace {
|
||||
constexpr BleAdvertisementHeader::Version kVersion =
|
||||
BleAdvertisementHeader::Version::kV2;
|
||||
constexpr int kNumSlots = 2;
|
||||
constexpr std::int16_t kPsmValue = 1;
|
||||
constexpr absl::string_view kServiceIDBloomFilter{
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"};
|
||||
constexpr absl::string_view kAdvertisementHash{"\x0a\x0b\x0c\x0d"};
|
||||
@@ -35,15 +36,18 @@ TEST(BleAdvertisementHeaderTest, ConstructionWorks) {
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion());
|
||||
EXPECT_FALSE(ble_advertisement_header.IsExtendedAdvertisement());
|
||||
EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots());
|
||||
EXPECT_EQ(service_id_bloom_filter,
|
||||
ble_advertisement_header.GetServiceIdBloomFilter());
|
||||
EXPECT_EQ(advertisement_hash,
|
||||
ble_advertisement_header.GetAdvertisementHash());
|
||||
EXPECT_EQ(kPsmValue, ble_advertisement_header.GetPsmValue());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) {
|
||||
@@ -53,7 +57,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) {
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
bad_version, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
@@ -65,7 +70,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWitZeroNumSlot) {
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, num_slot, service_id_bloom_filter, advertisement_hash};
|
||||
kVersion, false, num_slot, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
@@ -78,8 +84,9 @@ TEST(BleAdvertisementHeaderTest,
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, short_service_id_bloom_filter_bytes,
|
||||
advertisement_hash};
|
||||
kVersion, false,
|
||||
kNumSlots, short_service_id_bloom_filter_bytes,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
@@ -93,7 +100,8 @@ TEST(BleAdvertisementHeaderTest,
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
@@ -105,7 +113,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) {
|
||||
ByteArray advertisement_hash{short_advertisement_hash};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
@@ -116,7 +125,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{long_advertisement_hash};
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
@@ -126,20 +136,22 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) {
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader org_ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
auto ble_advertisement_header_string =
|
||||
std::string(org_ble_advertisement_header);
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
auto ble_advertisement_header_bytes = ByteArray(org_ble_advertisement_header);
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
ble_advertisement_header_string};
|
||||
ble_advertisement_header_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion());
|
||||
EXPECT_FALSE(ble_advertisement_header.IsExtendedAdvertisement());
|
||||
EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots());
|
||||
EXPECT_EQ(service_id_bloom_filter,
|
||||
ble_advertisement_header.GetServiceIdBloomFilter());
|
||||
EXPECT_EQ(advertisement_hash,
|
||||
ble_advertisement_header.GetAdvertisementHash());
|
||||
EXPECT_EQ(kPsmValue, ble_advertisement_header.GetPsmValue());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) {
|
||||
@@ -147,28 +159,26 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) {
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
auto ble_advertisement_header_string = std::string(ble_advertisement_header);
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
auto ble_advertisement_header_bytes = ByteArray(ble_advertisement_header);
|
||||
|
||||
// Base64 decode the string, add a character, and then re-encode it.
|
||||
ByteArray ble_advertisement_header_bytes =
|
||||
Base64Utils::Decode(ble_advertisement_header_string);
|
||||
ByteArray long_ble_advertisement_header_bytes{
|
||||
ble_advertisement_header_bytes.size() + 1};
|
||||
long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes);
|
||||
std::string long_ble_advertisement_header_string{
|
||||
Base64Utils::Encode(long_ble_advertisement_header_bytes)};
|
||||
|
||||
BleAdvertisementHeader long_ble_advertisement_header{
|
||||
long_ble_advertisement_header_string};
|
||||
long_ble_advertisement_header_bytes};
|
||||
|
||||
EXPECT_TRUE(long_ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion());
|
||||
EXPECT_FALSE(ble_advertisement_header.IsExtendedAdvertisement());
|
||||
EXPECT_EQ(kNumSlots, long_ble_advertisement_header.GetNumSlots());
|
||||
EXPECT_EQ(service_id_bloom_filter,
|
||||
long_ble_advertisement_header.GetServiceIdBloomFilter());
|
||||
EXPECT_EQ(advertisement_hash,
|
||||
long_ble_advertisement_header.GetAdvertisementHash());
|
||||
EXPECT_EQ(kPsmValue, long_ble_advertisement_header.GetPsmValue());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) {
|
||||
@@ -176,21 +186,17 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) {
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
auto ble_advertisement_header_string = std::string(ble_advertisement_header);
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
auto ble_advertisement_header_bytes = ByteArray(ble_advertisement_header);
|
||||
|
||||
// Base64 decode the string, remove a character, and then re-encode it.
|
||||
ByteArray ble_advertisement_header_bytes =
|
||||
Base64Utils::Decode(ble_advertisement_header_string);
|
||||
ByteArray short_ble_advertisement_header_bytes{
|
||||
ble_advertisement_header_bytes.size() - 1};
|
||||
ble_advertisement_header_bytes.size() - 3};
|
||||
short_ble_advertisement_header_bytes.CopyAt(0,
|
||||
ble_advertisement_header_bytes);
|
||||
std::string short_ble_advertisement_header_string{
|
||||
Base64Utils::Encode(short_ble_advertisement_header_bytes)};
|
||||
|
||||
BleAdvertisementHeader short_ble_advertisement_header{
|
||||
short_ble_advertisement_header_string};
|
||||
short_ble_advertisement_header_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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 "core/internal/mediums/ble_v2/bloom_filter.h"
|
||||
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "smhasher/src/MurmurHash3.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BloomFilterBase::BloomFilterBase(const ByteArray& bytes, BitSet* bit_set)
|
||||
: bits_(bit_set) {
|
||||
const char* bytes_read_ptr = bytes.data();
|
||||
for (size_t byte_index = 0; byte_index < bytes.size(); byte_index++) {
|
||||
for (size_t bit_index = 0; bit_index < 8; bit_index++) {
|
||||
bits_->Set((byte_index * 8) + bit_index,
|
||||
(*bytes_read_ptr >> bit_index) & 0x01);
|
||||
}
|
||||
bytes_read_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
BloomFilterBase::operator ByteArray() const {
|
||||
// Gets a binary string representation of the bitset where the leftmost
|
||||
// character corresponds to bitset position (total size) - 1.
|
||||
//
|
||||
// If the bitset's internal representation is:
|
||||
// [position 0] 0 0 1 1 0 0 0 1 0 1 0 1 [position 11]
|
||||
// The string representation will be outputted like this:
|
||||
// "1 0 1 0 1 0 0 0 1 1 0 0"
|
||||
std::string bitset_binary_string = bits_->ToString();
|
||||
|
||||
ByteArray result_bytes(GetMinBytesForBits());
|
||||
char* result_bytes_write_ptr = result_bytes.data();
|
||||
// We go through the string backwards because the rightmost character
|
||||
// corresponds to position 0 in the bitset.
|
||||
for (size_t i = bits_->Size(); i > 0; i -= 8) {
|
||||
std::string byte_binary_string = bitset_binary_string.substr(i - 8, 8);
|
||||
std::uint32_t byte_value;
|
||||
absl::numbers_internal::safe_strtou32_base(byte_binary_string, &byte_value,
|
||||
/* base= */ 2);
|
||||
*result_bytes_write_ptr = static_cast<char>(byte_value & 0x000000FF);
|
||||
result_bytes_write_ptr++;
|
||||
}
|
||||
return result_bytes;
|
||||
}
|
||||
|
||||
void BloomFilterBase::Add(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = GetHashes(s);
|
||||
for (int32_t hash : hashes) {
|
||||
size_t position = static_cast<size_t>(hash) % bits_->Size();
|
||||
bits_->Set(position, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool BloomFilterBase::PossiblyContains(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = GetHashes(s);
|
||||
for (int32_t hash : hashes) {
|
||||
size_t position = static_cast<size_t>(hash) % bits_->Size();
|
||||
if (!bits_->Test(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::int32_t> BloomFilterBase::GetHashes(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes(kHasherNumberOfRepetitions, 0);
|
||||
|
||||
absl::uint128 hash128;
|
||||
MurmurHash3_x64_128(s.data(), s.size(), 0, &hash128);
|
||||
std::uint64_t hash64 =
|
||||
absl::Uint128Low64(hash128); // the lower 64 bits of the 128-bit hash
|
||||
std::int32_t hash1 = static_cast<std::int32_t>(
|
||||
hash64 & 0x00000000FFFFFFFF); // the lower 32 bits of the 64-bit hash
|
||||
std::int32_t hash2 = static_cast<std::int32_t>(
|
||||
(hash64 >> 32) & 0x0FFFFFFFF); // the upper 32 bits of the 64-bit hash
|
||||
for (size_t i = 1; i <= kHasherNumberOfRepetitions; i++) {
|
||||
std::int32_t combinedHash = static_cast<std::int32_t>(hash1 + (i * hash2));
|
||||
// Flip all the bits if it's negative (guaranteed positive number)
|
||||
if (combinedHash < 0) combinedHash = ~combinedHash;
|
||||
hashes[i - 1] = combinedHash;
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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_BLOOM_FILTER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLOOM_FILTER_H_
|
||||
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
/**
|
||||
* A bloom filter that gives access to the underlying BitSet. The implementation
|
||||
* is copied from our Java version of Bloom filter, which in turn copies from
|
||||
* Guava's BloomFilter.
|
||||
*
|
||||
* BloomFilter is templatized on the size of the byte array and not the size of
|
||||
* the bit set to ensure the bit set's length is a multiple of 8 (and can
|
||||
* neatly be returned as a ByteArray).
|
||||
*/
|
||||
class BloomFilterBase {
|
||||
public:
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
void Add(const std::string& s);
|
||||
bool PossiblyContains(const std::string& s);
|
||||
|
||||
protected:
|
||||
class BitSet {
|
||||
public:
|
||||
virtual ~BitSet() = default;
|
||||
virtual std::string ToString() const = 0;
|
||||
virtual void Set(size_t pos, bool value) = 0;
|
||||
virtual bool Test(size_t pos) const = 0;
|
||||
virtual size_t Size() const = 0;
|
||||
};
|
||||
|
||||
BloomFilterBase(const ByteArray& bytes, BitSet* bit_set);
|
||||
virtual ~BloomFilterBase() = default;
|
||||
|
||||
constexpr static int kHasherNumberOfRepetitions = 5;
|
||||
std::vector<std::int32_t> GetHashes(const std::string& s);
|
||||
|
||||
private:
|
||||
int GetMinBytesForBits() const { return (bits_->Size() + 7) >> 3; }
|
||||
|
||||
BitSet* bits_;
|
||||
};
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
class BloomFilter final : public BloomFilterBase {
|
||||
public:
|
||||
BloomFilter() : BloomFilterBase(ByteArray{}, &bits_) {}
|
||||
explicit BloomFilter(const ByteArray& bytes)
|
||||
: BloomFilterBase(bytes, &bits_) {}
|
||||
BloomFilter(const BloomFilter&) = default;
|
||||
BloomFilter& operator=(const BloomFilter&) = default;
|
||||
BloomFilter(BloomFilter&& other) : BloomFilterBase(ByteArray{}, &bits_) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
BloomFilter& operator=(BloomFilter&& other) {
|
||||
std::swap((*this).bits_, other.bits_);
|
||||
return *this;
|
||||
}
|
||||
~BloomFilter() override = default;
|
||||
|
||||
private:
|
||||
class BitSetImpl final : public BitSet {
|
||||
public:
|
||||
std::string ToString() const override { return bits_.to_string(); }
|
||||
void Set(size_t pos, bool value) override { bits_.set(pos, value); }
|
||||
bool Test(size_t pos) const override { return bits_.test(pos); }
|
||||
size_t Size() const override { return bits_.size(); }
|
||||
|
||||
private:
|
||||
std::bitset<CapacityInBytes * 8> bits_;
|
||||
} bits_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLOOM_FILTER_H_
|
||||
@@ -0,0 +1,207 @@
|
||||
// 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 "core/internal/mediums/ble_v2/bloom_filter.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kByteArrayLength = 100;
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
|
||||
EXPECT_EQ(empty_string, std::string(bloom_filter_bytes));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterNeverContains) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddOnlyGivenArg) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddMultipleArgs) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) {
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
bloom_filter.Add("ELEMENT_3");
|
||||
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
|
||||
EXPECT_NE(std::string(bloom_filter_bytes), empty_string);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, CopyConstructorAndAssignmentSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_copy_1{bloom_filter};
|
||||
BloomFilter<kByteArrayLength> bloom_filter_copy_2 = bloom_filter;
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter_copy_1.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter_copy_2.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, MoveConstructorSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_move{std::move(bloom_filter)};
|
||||
|
||||
EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, MoveAssignmentSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_move = std::move(bloom_filter);
|
||||
|
||||
EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test was added because of a bug where the BloomFilter doesn't utilize
|
||||
* all bits given. Functionally, the filter still works, but we just have a much
|
||||
* higher false positive rate. The bug was caused by confusing bit length and
|
||||
* byte length, which made our BloomFilter only set bits on the first byteLength
|
||||
* (bitLength / 8) bits rather than the whole bitLength bits.
|
||||
*
|
||||
* <p>Here, we're verifying that the bits set are somewhat scattered. So instead
|
||||
* of something like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting
|
||||
* something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0].
|
||||
*/
|
||||
TEST(BloomFilterTest, RandomnessNoEndBias) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
// Add one element to our BloomFilter.
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
std::int32_t non_zero_count = 0;
|
||||
std::int32_t longest_zero_streak = 0;
|
||||
std::int32_t current_zero_streak = 0;
|
||||
|
||||
// Record the amount of non-zero bytes and the longest streak of zero bytes in
|
||||
// the resulting BloomFilter. This is an approximation of reasonable
|
||||
// distribution since we're recording by bytes instead of bits.
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
const char* bloom_filter_bytes_read_ptr = bloom_filter_bytes.data();
|
||||
for (int i = 0; i < bloom_filter_bytes.size(); i++) {
|
||||
if (*bloom_filter_bytes_read_ptr == '\0') {
|
||||
current_zero_streak++;
|
||||
} else {
|
||||
// Increment the number of non-zero bytes we've seen, update the longest
|
||||
// zero streak, and then reset the current zero streak.
|
||||
non_zero_count++;
|
||||
longest_zero_streak = std::max(longest_zero_streak, current_zero_streak);
|
||||
current_zero_streak = 0;
|
||||
}
|
||||
bloom_filter_bytes_read_ptr++;
|
||||
}
|
||||
// Update the longest zero streak again for the tail case.
|
||||
longest_zero_streak = std::min(longest_zero_streak, current_zero_streak);
|
||||
|
||||
// Since randomness is hard to measure within one unit test, we instead do a
|
||||
// sanity check. All non-zero bytes should not be packed into one end of the
|
||||
// array.
|
||||
//
|
||||
// In this case, the size of one end is approximated to be:
|
||||
// kByteArrayLength / nonZeroCount.
|
||||
// Therefore, the longest zero streak should be less than:
|
||||
// kByteArrayLength - one end of the array.
|
||||
std::int32_t longest_acceptable_zero_streak =
|
||||
kByteArrayLength - (kByteArrayLength / non_zero_count);
|
||||
|
||||
EXPECT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, RandomnessFalsePositiveRate) {
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
// Add 5 distinct elements to the BloomFilter.
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
bloom_filter.Add("ELEMENT_3");
|
||||
bloom_filter.Add("ELEMENT_4");
|
||||
bloom_filter.Add("ELEMENT_5");
|
||||
|
||||
std::int32_t false_positives = 0;
|
||||
// Now test 100 other elements and record the number of false positives.
|
||||
for (int i = 5; i < 105; i++) {
|
||||
false_positives +=
|
||||
bloom_filter.PossiblyContains("ELEMENT_" + std::to_string(i)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// We expect the false positive rate to be 3% with 5 elements in a 10 byte
|
||||
// filter. Thus, we give a little leeway and verify that the false positive
|
||||
// rate is no more than 5%.
|
||||
EXPECT_LE(false_positives, 5);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
Reference in New Issue
Block a user