[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:
edwinwu
2022-01-14 05:08:02 -08:00
committed by Copybara-Service
parent 5e845655bb
commit cf937ed90f
9 changed files with 97 additions and 85 deletions
+5
View File
@@ -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