Files
nearby/connections/implementation/mediums/ble_v2/ble_advertisement.cc
T
2023-01-06 02:53:57 -08:00

314 lines
11 KiB
C++

// 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 "connections/implementation/mediums/ble_v2/ble_advertisement.h"
#include <inttypes.h>
#include <cstdint>
#include <string>
#include <utility>
#include "absl/strings/str_cat.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
#include "internal/platform/base_input_stream.h"
#include "internal/platform/logging.h"
namespace nearby {
namespace connections {
namespace mediums {
namespace {
constexpr uint8_t kPsmBitmask = 0x01;
bool HasField(uint8_t field_mask, uint8_t psm_bit) {
return (field_mask & psm_bit) > 0;
}
} // namespace
BleAdvertisement::BleAdvertisement(Version version,
SocketVersion socket_version,
const ByteArray &service_id_hash,
const ByteArray &data,
const ByteArray &device_token, int psm) {
DoInitialize(/*fast_advertisement=*/service_id_hash.Empty(), version,
socket_version, service_id_hash, data, device_token, psm);
}
void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version,
SocketVersion socket_version,
const ByteArray &service_id_hash,
const ByteArray &data,
const ByteArray &device_token, int psm) {
// Check that the given input is valid.
fast_advertisement_ = fast_advertisement;
if (!fast_advertisement_) {
if (service_id_hash.size() != kServiceIdHashLength) return;
}
if (!IsSupportedVersion(version) ||
!IsSupportedSocketVersion(socket_version) ||
(!device_token.Empty() && device_token.size() != kDeviceTokenLength)) {
return;
}
int advertisement_Length = ComputeAdvertisementLength(
data.size(), device_token.size(), fast_advertisement_);
int max_advertisement_length = fast_advertisement
? kMaxFastAdvertisementLength
: kMaxAdvertisementLength;
if (advertisement_Length > max_advertisement_length) {
return;
}
version_ = version;
socket_version_ = socket_version;
if (!fast_advertisement_) service_id_hash_ = service_id_hash;
data_ = data;
device_token_ = device_token;
psm_ = psm;
}
BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) {
if (ble_advertisement_bytes.Empty()) {
NEARBY_LOG(INFO,
"Cannot deserialize BleAdvertisement: null bytes passed in.");
return;
}
if (ble_advertisement_bytes.size() < kVersionLength) {
NEARBY_LOG(
INFO,
"Cannot deserialize BleAdvertisement: expecting min %d raw bytes to "
"parse the version, got %" PRIu64,
kVersionLength, ble_advertisement_bytes.size());
return;
}
ByteArray advertisement_bytes{ble_advertisement_bytes};
BaseInputStream base_input_stream{advertisement_bytes};
// The first 1 byte is supposed to be the version, socket version and the fast
// advertisement flag.
auto version_byte = static_cast<char>(base_input_stream.ReadUint8());
// Version.
version_ = static_cast<Version>((version_byte & kVersionBitmask) >> 5);
if (!IsSupportedVersion(version_)) {
NEARBY_LOG(INFO,
"Cannot deserialize BleAdvertisement: unsupported Version %u",
version_);
return;
}
// Socket version.
socket_version_ =
static_cast<SocketVersion>((version_byte & kSocketVersionBitmask) >> 2);
if (!IsSupportedSocketVersion(socket_version_)) {
NEARBY_LOG(
INFO,
"Cannot deserialize BleAdvertisement: unsupported SocketVersion %u",
socket_version_);
version_ = Version::kUndefined;
return;
}
// Fast advertisement flag.
fast_advertisement_ =
static_cast<bool>((version_byte & kFastAdvertisementFlagBitmask) >> 1);
// The next 3 bytes are supposed to be the service_id_hash if not fast
// advertisement.
if (!fast_advertisement_) {
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
}
// Data length.
int expected_data_size =
fast_advertisement_
? static_cast<int>(
base_input_stream.ReadBytes(kFastDataSizeLength).data()[0])
: static_cast<int>(base_input_stream.ReadUint32());
if (expected_data_size < 0) {
NEARBY_LOG(INFO,
"Cannot deserialize BleAdvertisement: negative data size %d",
expected_data_size);
version_ = Version::kUndefined;
return;
}
// Data.
// Check that the stated data size is the same as what we received.
data_ = base_input_stream.ReadBytes(expected_data_size);
if (data_.size() != expected_data_size) {
NEARBY_LOG(INFO,
"Cannot deserialize BleAdvertisement: expected data to be %u "
"bytes, got %" PRIu64 " bytes ",
expected_data_size, data_.size());
version_ = Version::kUndefined;
return;
}
// Device token. If the number of remaining bytes are valid for device token,
// then read it.
if (base_input_stream.IsAvailable(kDeviceTokenLength)) {
device_token_ = base_input_stream.ReadBytes(kDeviceTokenLength);
} else {
// No device token no more optional field.
return;
}
// Extra fields, for backward compatible reason, put this field in the end of
// this advertisement. That means it must support device token if there's any
// extra field. E.g. If iOS or other platform wants to use extra fields, need
// to put a random or empty device token in the advertisement.
int extra_fields_byte_number =
kExtraFieldsMaskLength + BleAdvertisementHeader::kPsmValueByteLength;
if (base_input_stream.IsAvailable(extra_fields_byte_number)) {
BleExtraFields extra_fields{
base_input_stream.ReadBytes(extra_fields_byte_number)};
psm_ = extra_fields.GetPsm();
}
}
BleAdvertisement::operator ByteArray() const {
if (!IsValid()) {
return ByteArray{};
}
// The first 3 bits are the Version.
char version_byte = (static_cast<char>(version_) << 5) & kVersionBitmask;
// The next 3 bits are the Socket version. 2 bits left are reserved.
version_byte |=
(static_cast<char>(socket_version_) << 2) & kSocketVersionBitmask;
// The next 1 bit is the fast advertisement flag. 1 bit left is reserved.
version_byte |= (static_cast<char>(fast_advertisement_ ? 1 : 0) << 1) &
kFastAdvertisementFlagBitmask;
// Serialize Data size bytes
ByteArray data_size_bytes{static_cast<size_t>(
fast_advertisement_ ? kFastDataSizeLength : kDataSizeLength)};
auto *data_size_bytes_write_ptr = data_size_bytes.data();
SerializeDataSize(fast_advertisement_, data_size_bytes_write_ptr,
data_.size());
// For Extra fields, there's no space for legacy fast advertisement, use
// ByteArrayWithExtraField() to get the new advertisement bytes for extended
// advertising.
// clang-format on
if (fast_advertisement_) {
std::string out =
absl::StrCat(std::string(1, version_byte), std::string(data_size_bytes),
std::string(data_), std::string(device_token_));
return ByteArray{std::move(out)};
} else {
std::string out = absl::StrCat(
std::string(1, version_byte), std::string(service_id_hash_),
std::string(data_size_bytes), std::string(data_),
std::string(device_token_));
return ByteArray{std::move(out)};
}
// clang-format on
}
ByteArray BleAdvertisement::ByteArrayWithExtraField() const {
ByteArray advertisement_bytes = ByteArray(*this);
ByteArray extra_fields_bytes = ByteArray(BleExtraFields(psm_));
std::string advertisement_with_extra_fields_bytes = absl::StrCat(
std::string(advertisement_bytes), std::string(extra_fields_bytes));
return ByteArray(std::move(advertisement_with_extra_fields_bytes));
}
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() &&
this->GetPsm() == rhs.GetPsm();
}
bool BleAdvertisement::IsSupportedVersion(Version version) const {
return version >= Version::kV1 && version <= Version::kV2;
}
bool BleAdvertisement::IsSupportedSocketVersion(
SocketVersion socket_version) const {
return socket_version >= SocketVersion::kV1 &&
socket_version <= SocketVersion::kV2;
}
void BleAdvertisement::SerializeDataSize(bool fast_advertisement,
char *data_size_bytes_write_ptr,
size_t data_size) const {
// Get a raw representation of the data size bytes in memory.
char *data_size_bytes = reinterpret_cast<char *>(&data_size);
const int data_size_length =
fast_advertisement ? kFastDataSizeLength : kDataSizeLength;
// Append these raw bytes to advertisement bytes, keeping in mind that we need
// to convert from Little Endian to Big Endian in the process.
for (int i = 0; i < data_size_length; ++i) {
data_size_bytes_write_ptr[i] = data_size_bytes[data_size_length - i - 1];
}
}
BleAdvertisement::BleExtraFields::BleExtraFields(int psm) : psm_(psm) {}
BleAdvertisement::BleExtraFields::BleExtraFields(
const ByteArray &ble_extra_fields_bytes) {
if (ble_extra_fields_bytes.Empty()) {
return;
}
ByteArray mutated_extra_fields_bytes = {ble_extra_fields_bytes};
BaseInputStream base_input_stream{mutated_extra_fields_bytes};
// The first 1 byte is field mask.
auto mask_byte = static_cast<uint8_t>(base_input_stream.ReadUint8());
if (!mask_byte) {
return;
}
// The next 2 bytes are supposed to be the psm value.
if (HasField(mask_byte, kPsmBitmask) &&
base_input_stream.IsAvailable(
BleAdvertisementHeader::kPsmValueByteLength)) {
psm_ = static_cast<int>(base_input_stream.ReadUint16());
}
}
BleAdvertisement::BleExtraFields::operator ByteArray() const {
if (psm_ == BleAdvertisementHeader::kDefaultPsmValue) {
return ByteArray{};
}
ByteArray psm_byte{BleAdvertisementHeader::kPsmValueByteLength};
char *data = psm_byte.data();
data[0] = psm_ & 0xFF00;
data[1] = psm_ & 0x00FF;
std::string out =
absl::StrCat(std::string(1, kPsmBitmask), std::string(psm_byte));
return ByteArray{std::move(out)};
}
} // namespace mediums
} // namespace connections
} // namespace nearby