[Nearby Connections] Add control packet in BlePacket

PiperOrigin-RevId: 513573452
This commit is contained in:
Edwin Wu
2023-03-02 10:21:07 -08:00
committed by Copybara-Service
parent d2965024ca
commit 517ff0f52e
4 changed files with 326 additions and 57 deletions
@@ -47,10 +47,12 @@ cc_library(
"//internal/platform:types",
"//internal/platform:util",
"//internal/platform:uuid",
"//proto/mediums:ble_frames_cc_proto",
"@aappleby_smhasher//:libmurmur3",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/numeric:int128",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time",
@@ -75,8 +77,10 @@ cc_test(
"//internal/platform:comm",
"//internal/platform:types",
"//internal/platform/implementation/g3", # buildcleaner: keep
"//proto/mediums:ble_frames_cc_proto",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/hash:hash_testing",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/time",
"@com_google_googletest//:gtest_main",
],
@@ -14,21 +14,110 @@
#include "connections/implementation/mediums/ble_v2/ble_packet.h"
#include <limits>
#include <string>
#include <utility>
#include "absl/strings/str_cat.h"
#include "internal/platform/base_input_stream.h"
#include "internal/platform/logging.h"
#include "proto/mediums/ble_frames.pb.h"
namespace nearby {
namespace connections {
namespace mediums {
BlePacket::BlePacket(const ByteArray& service_id_hash, const ByteArray& data) {
if (service_id_hash.size() != kServiceIdHashLength ||
data.size() > kMaxDataSize) {
return;
using ::location::nearby::mediums::SocketControlFrame;
using ::location::nearby::mediums::SocketVersion;
// The 3 0x00 bytes are used in the control packet to identify the packet.
constexpr char kControlPacketServiceIdHash[] = "\x00\x00\x00";
constexpr std::uint32_t kMaxDataSize =
std::numeric_limits<int32_t>::max() - BlePacket::kServiceIdHashLength;
absl::StatusOr<BlePacket> BlePacket::CreateControlIntroductionPacket(
const ByteArray& service_id_hash) {
if (service_id_hash.size() != kServiceIdHashLength) {
return absl::InvalidArgumentError("service_id_hash is incorrect.");
}
service_id_hash_ = service_id_hash;
data_ = data;
SocketControlFrame frame;
frame.set_type(SocketControlFrame::INTRODUCTION);
auto* introduction_frame = frame.mutable_introduction();
introduction_frame->set_service_id_hash(service_id_hash.data());
introduction_frame->set_socket_version(SocketVersion::V2);
ByteArray frame_bytes(frame.ByteSizeLong());
frame.SerializeToArray(frame_bytes.data(), frame_bytes.size());
return CreateControlPacket(frame_bytes);
}
absl::StatusOr<BlePacket> BlePacket::CreateControlDisconnectionPacket(
const ByteArray& service_id_hash) {
if (service_id_hash.size() != kServiceIdHashLength) {
return absl::InvalidArgumentError("service_id_hash is incorrect.");
}
SocketControlFrame frame;
frame.set_type(SocketControlFrame::DISCONNECTION);
auto* disconnection_frame = frame.mutable_disconnection();
disconnection_frame->set_service_id_hash(service_id_hash.data());
ByteArray frame_bytes(frame.ByteSizeLong());
frame.SerializeToArray(frame_bytes.data(), frame_bytes.size());
return CreateControlPacket(frame_bytes);
}
absl::StatusOr<BlePacket> BlePacket::CreateControlPacketAcknowledgementPacket(
const ByteArray& service_id_hash, int received_size) {
if (service_id_hash.size() != kServiceIdHashLength) {
return absl::InvalidArgumentError("service_id_hash is incorrect.");
}
SocketControlFrame frame;
frame.set_type(SocketControlFrame::PACKET_ACKNOWLEDGEMENT);
auto* packet_acknowledgement_frame = frame.mutable_packet_acknowledgement();
packet_acknowledgement_frame->set_service_id_hash(service_id_hash.data());
packet_acknowledgement_frame->set_received_size(received_size);
ByteArray frame_bytes(frame.ByteSizeLong());
frame.SerializeToArray(frame_bytes.data(), frame_bytes.size());
return CreateControlPacket(frame_bytes);
}
absl::StatusOr<BlePacket> BlePacket::CreateControlPacket(
const ByteArray& data) {
if (data.size() > kMaxDataSize) {
return absl::InvalidArgumentError(
absl::StrCat("Packet size: ", data.size(), " > ", kMaxDataSize));
}
BlePacket ble_packet;
ble_packet.packet_type_ = BlePacketType::kControl;
ble_packet.service_id_hash_ =
ByteArray(kControlPacketServiceIdHash, kServiceIdHashLength);
ble_packet.data_ = data;
return ble_packet;
}
absl::StatusOr<BlePacket> BlePacket::CreateDataPacket(
const ByteArray& service_id_hash, const ByteArray& data) {
if (data.size() > kMaxDataSize) {
return absl::InvalidArgumentError(
absl::StrCat("Packet size: ", data.size(), " > ", kMaxDataSize));
}
if (service_id_hash.size() != kServiceIdHashLength ||
service_id_hash ==
ByteArray(kControlPacketServiceIdHash, kServiceIdHashLength)) {
return absl::InvalidArgumentError("service_id_hash is incorrect.");
}
BlePacket ble_packet;
ble_packet.packet_type_ = BlePacketType::kData;
ble_packet.service_id_hash_ = service_id_hash;
ble_packet.data_ = data;
return ble_packet;
}
BlePacket::BlePacket(const ByteArray& ble_packet_bytes) {
@@ -45,10 +134,16 @@ BlePacket::BlePacket(const ByteArray& ble_packet_bytes) {
return;
}
ByteArray packet_bytes{ble_packet_bytes};
ByteArray packet_bytes(ble_packet_bytes);
BaseInputStream base_input_stream{packet_bytes};
// The first 3 bytes are supposed to be the service_id_hash.
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
if (service_id_hash_ ==
ByteArray(kControlPacketServiceIdHash, kServiceIdHashLength)) {
packet_type_ = BlePacketType::kControl;
} else {
packet_type_ = BlePacketType::kData;
}
// The rest bytes are supposed to be the data.
data_ = base_input_stream.ReadBytes(ble_packet_bytes.size() -
@@ -66,6 +161,14 @@ BlePacket::operator ByteArray() const {
return ByteArray(std::move(out));
}
bool BlePacket::IsValid() const {
return packet_type_ != BlePacketType::kInvalid;
}
bool BlePacket::IsControlPacket() const {
return packet_type_ == BlePacketType::kControl;
}
} // namespace mediums
} // namespace connections
} // namespace nearby
@@ -15,8 +15,7 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_
#include <limits>
#include "absl/status/statusor.h"
#include "internal/platform/byte_array.h"
namespace nearby {
@@ -30,10 +29,49 @@ namespace mediums {
// See go/nearby-ble-design for more information.
class BlePacket {
public:
static const std::uint32_t kServiceIdHashLength = 3;
static constexpr int kServiceIdHashLength = 3;
// Creates an Introduction frame for Control packet with the given
// 'service_id_hash'. The size of 'service_id_hash' must equal to
// 'kServiceIdHashLength', otherwise returns an error.
//
// service_id_hash - hash of client service id.
static absl::StatusOr<BlePacket> CreateControlIntroductionPacket(
const ByteArray& service_id_hash);
// Creates an Disconnection frame for Control packet with the given
// 'service_id_hash'. The size of 'service_id_hash' must equal to
// 'kServiceIdHashLength', otherwise returns an error.
//
// service_id_hash - hash of client service id.
static absl::StatusOr<BlePacket> CreateControlDisconnectionPacket(
const ByteArray& service_id_hash);
// Creates an Packet Acknowledgement frame for Control packet with the given
// 'service_id_hash'. The size of 'service_id_hash' must equal to
// 'kServiceIdHashLength', otherwise returns an error.
//
// service_id_hash - hash of client service id.
// received_size - payload size received sucessfully.
static absl::StatusOr<BlePacket> CreateControlPacketAcknowledgementPacket(
const ByteArray& service_id_hash, int received_size);
// Creates a Control packet. Returns error when data size has exceeded maximum
// size.
//
// data - the raw bytes exported by |SocketControlFrame| that supports all the
// |ControlFrameType|.
static absl::StatusOr<BlePacket> CreateControlPacket(const ByteArray& data);
// Creates a Data packet. Returns error when the size of 'service_id_hash' is
// not equal to 'kServiceIdHashLength', or data size has exceeded maximum
// size.
//
// service_id_hash : hash of client service id. It should be other than
// 0x000000 that is reserved for Control packet.
static absl::StatusOr<BlePacket> CreateDataPacket(
const ByteArray& service_id_hash, const ByteArray& data);
BlePacket() = default;
BlePacket(const ByteArray& service_id_hash, const ByteArray& data);
explicit BlePacket(const ByteArray& ble_packet_byte);
BlePacket(const BlePacket&) = default;
BlePacket& operator=(const BlePacket&) = default;
@@ -43,14 +81,22 @@ class BlePacket {
explicit operator ByteArray() const;
bool IsValid() const { return !service_id_hash_.Empty(); }
bool IsValid() const;
ByteArray GetServiceIdHash() const { return service_id_hash_; }
ByteArray GetData() const { return data_; }
int GetPacketSize() const { return data_.size() + kServiceIdHashLength; }
bool IsControlPacket() const;
private:
static const std::uint32_t kMaxDataSize =
std::numeric_limits<int32_t>::max() - kServiceIdHashLength;
enum class BlePacketType {
kInvalid = 0,
kData,
kControl,
};
BlePacket() = default;
BlePacketType packet_type_ = BlePacketType::kInvalid;
ByteArray service_id_hash_;
ByteArray data_;
};
@@ -14,96 +14,212 @@
#include "connections/implementation/mediums/ble_v2/ble_packet.h"
#include <algorithm>
#include <string>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/status/statusor.h"
#include "proto/mediums/ble_frames.proto.h"
namespace nearby {
namespace connections {
namespace mediums {
namespace {
using ::location::nearby::mediums::SocketControlFrame;
using ::protobuf_matchers::EqualsProto;
constexpr absl::string_view kServiceIDHash{"\x0a\x0b\x0c"};
constexpr absl::string_view kData{"\x01\x02\x03\x04\x05"};
constexpr absl::string_view kServiceIDHash = {"\x01\x02\x03"};
constexpr absl::string_view kData = {"\x01\x02\x03\x04\x05"};
TEST(BlePacketTest, ConstructionWorks) {
ByteArray service_id_hash{std::string(kServiceIDHash)};
ByteArray data{std::string(kData)};
TEST(BlePacketTest, CreatingControlPacketWorks) {
ByteArray data((std::string(kData)));
BlePacket ble_packet{service_id_hash, data};
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateControlPacket(data);
EXPECT_TRUE(ble_packet.IsValid());
EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash());
EXPECT_EQ(data, ble_packet.GetData());
ASSERT_OK(ble_packet_status_or);
EXPECT_TRUE(ble_packet_status_or.value().IsValid());
EXPECT_TRUE(ble_packet_status_or.value().IsControlPacket());
EXPECT_EQ(data, ble_packet_status_or.value().GetData());
}
TEST(BlePacketTest, ConstructionWorksWithEmptyData) {
TEST(BlePacketTest, CreatingControlIntroductionFramePacketWorks) {
ByteArray service_id_hash((std::string(kServiceIDHash)));
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateControlIntroductionPacket(service_id_hash);
ASSERT_OK(ble_packet_status_or);
EXPECT_TRUE(ble_packet_status_or.value().IsValid());
EXPECT_TRUE(ble_packet_status_or.value().IsControlPacket());
constexpr absl::string_view kExpected =
R"pb(
type: INTRODUCTION
introduction: < service_id_hash: "\001\002\003" socket_version: V2 >)pb";
SocketControlFrame frame;
frame.ParseFromString(std::string(ble_packet_status_or.value().GetData()));
EXPECT_THAT(frame, EqualsProto(kExpected));
}
TEST(BlePacketTest, CreatingControlDisconnectionFramePacketWorks) {
ByteArray service_id_hash((std::string(kServiceIDHash)));
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateControlDisconnectionPacket(service_id_hash);
ASSERT_OK(ble_packet_status_or);
EXPECT_TRUE(ble_packet_status_or.value().IsValid());
EXPECT_TRUE(ble_packet_status_or.value().IsControlPacket());
constexpr absl::string_view kExpected =
R"pb(
type: DISCONNECTION
disconnection: < service_id_hash: "\001\002\003" >)pb";
SocketControlFrame frame;
frame.ParseFromString(std::string(ble_packet_status_or.value().GetData()));
EXPECT_THAT(frame, EqualsProto(kExpected));
}
TEST(BlePacketTest, CreatingControlPacketAcknowledgementFramePacketWorks) {
ByteArray service_id_hash((std::string(kServiceIDHash)));
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateControlPacketAcknowledgementPacket(service_id_hash, 100);
ASSERT_OK(ble_packet_status_or);
EXPECT_TRUE(ble_packet_status_or.value().IsValid());
EXPECT_TRUE(ble_packet_status_or.value().IsControlPacket());
constexpr absl::string_view kExpected =
R"pb(
type: PACKET_ACKNOWLEDGEMENT
packet_acknowledgement: <
service_id_hash: "\001\002\003"
received_size: 100
>)pb";
SocketControlFrame frame;
frame.ParseFromString(std::string(ble_packet_status_or.value().GetData()));
EXPECT_THAT(frame, EqualsProto(kExpected));
}
TEST(BlePacketTest, CreatingDataPacketWorks) {
ByteArray service_id_hash((std::string(kServiceIDHash)));
ByteArray data((std::string(kData)));
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateDataPacket(service_id_hash, data);
ASSERT_OK(ble_packet_status_or);
EXPECT_TRUE(ble_packet_status_or.value().IsValid());
EXPECT_EQ(service_id_hash, ble_packet_status_or.value().GetServiceIdHash());
EXPECT_EQ(data, ble_packet_status_or.value().GetData());
}
TEST(BlePacketTest, CreatingDataPacketWorksWithEmptyData) {
char empty_data[] = "";
ByteArray service_id_hash{std::string(kServiceIDHash)};
ByteArray data{empty_data};
ByteArray service_id_hash((std::string(kServiceIDHash)));
ByteArray data(empty_data);
BlePacket ble_packet{service_id_hash, data};
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateDataPacket(service_id_hash, data);
EXPECT_TRUE(ble_packet.IsValid());
EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash());
EXPECT_EQ(data, ble_packet.GetData());
ASSERT_OK(ble_packet_status_or);
EXPECT_TRUE(ble_packet_status_or.value().IsValid());
EXPECT_EQ(service_id_hash, ble_packet_status_or.value().GetServiceIdHash());
EXPECT_EQ(data, ble_packet_status_or.value().GetData());
}
TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) {
TEST(BlePacketTest, CreatingDataPacketFailsWithShortServiceIdHash) {
char short_service_id_hash[] = "\x0a\x0b";
ByteArray service_id_hash{short_service_id_hash};
ByteArray data{std::string(kData)};
ByteArray service_id_hash(short_service_id_hash);
ByteArray data((std::string(kData)));
BlePacket ble_packet(service_id_hash, data);
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateDataPacket(service_id_hash, data);
EXPECT_FALSE(ble_packet.IsValid());
EXPECT_FALSE(ble_packet_status_or.ok());
}
TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) {
TEST(BlePacketTest, CreatingDataPacketFailsWithLongServiceIdHash) {
char long_service_id_hash[] = "\x0a\x0b\x0c\x0d";
ByteArray service_id_hash{long_service_id_hash};
ByteArray data{std::string(kData)};
ByteArray service_id_hash(long_service_id_hash);
ByteArray data((std::string(kData)));
BlePacket ble_packet{service_id_hash, data};
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateDataPacket(service_id_hash, data);
EXPECT_FALSE(ble_packet.IsValid());
EXPECT_FALSE(ble_packet_status_or.ok());
}
TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) {
ByteArray service_id_hash{std::string(kServiceIDHash)};
ByteArray data{std::string(kData)};
ByteArray service_id_hash((std::string(kServiceIDHash)));
ByteArray data((std::string(kData)));
BlePacket org_ble_packet{service_id_hash, data};
ByteArray ble_packet_bytes{org_ble_packet};
absl::StatusOr<BlePacket> org_ble_packet_status_or =
BlePacket::CreateDataPacket(service_id_hash, data);
BlePacket ble_packet{ble_packet_bytes};
ASSERT_OK(org_ble_packet_status_or);
ByteArray ble_packet_bytes(org_ble_packet_status_or.value());
BlePacket ble_packet(ble_packet_bytes);
EXPECT_TRUE(ble_packet.IsValid());
EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash());
EXPECT_EQ(data, ble_packet.GetData());
}
TEST(BlePacketTest, ConstructionFromNullBytesFails) {
BlePacket ble_packet{ByteArray{}};
TEST(BlePacketTest, ConstructionFromSerializedShortLengthDataBytesFails) {
ByteArray service_id_hash((std::string(kServiceIDHash)));
ByteArray data((std::string(kData)));
EXPECT_FALSE(ble_packet.IsValid());
}
absl::StatusOr<BlePacket> org_ble_packet_status_or =
BlePacket::CreateDataPacket(service_id_hash, data);
TEST(BlePacketTest, ConstructionFromShortLengthDataFails) {
ByteArray service_id_hash{std::string(kServiceIDHash)};
ByteArray data{std::string(kData)};
ASSERT_OK(org_ble_packet_status_or);
BlePacket org_ble_packet{service_id_hash, data};
ByteArray org_ble_packet_bytes{org_ble_packet};
ByteArray org_ble_packet_bytes(org_ble_packet_status_or.value());
// Cut off the packet so that it's too short
ByteArray short_ble_packet_bytes{ByteArray{org_ble_packet_bytes.data(), 2}};
ByteArray short_ble_packet_bytes(ByteArray(org_ble_packet_bytes.data(), 2));
BlePacket short_ble_packet{short_ble_packet_bytes};
BlePacket short_ble_packet(short_ble_packet_bytes);
EXPECT_FALSE(short_ble_packet.IsValid());
}
TEST(BlePacketTest,
ConstructionFromSerializedBytesAsDataByteWithInvalidControlBytes) {
ByteArray data((std::string(kData)));
// Construct a control packet.
absl::StatusOr<BlePacket> ble_packet_status_or =
BlePacket::CreateControlPacket(data);
ByteArray ble_packet_bytes(ble_packet_status_or.value());
// Corrupt the first byte of ble_packet_bytes.
memset(ble_packet_bytes.data(), 0x01, 1);
BlePacket new_ble_packet(ble_packet_bytes);
EXPECT_TRUE(new_ble_packet.IsValid());
// It is not control packet eventually.
EXPECT_FALSE(new_ble_packet.IsControlPacket());
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby