From 8d18c91e86654763fb2dd85aac346a1f00d35436 Mon Sep 17 00:00:00 2001 From: Edwin Wu Date: Thu, 13 Nov 2025 18:03:59 -0800 Subject: [PATCH] [BLEREFACTOR]:Add CreateFromStream API to BleL2capPacket PiperOrigin-RevId: 832082450 --- connections/implementation/mediums/ble/BUILD | 2 +- .../mediums/ble/ble_l2cap_packet.cc | 94 +++++++- .../mediums/ble/ble_l2cap_packet.h | 4 + .../mediums/ble/ble_l2cap_packet_test.cc | 206 ++++++++++++++++++ .../mediums/ble/ble_packet_test.cc | 31 +++ 5 files changed, 335 insertions(+), 2 deletions(-) diff --git a/connections/implementation/mediums/ble/BUILD b/connections/implementation/mediums/ble/BUILD index c822edc5..2e1bfe6e 100644 --- a/connections/implementation/mediums/ble/BUILD +++ b/connections/implementation/mediums/ble/BUILD @@ -184,12 +184,12 @@ cc_test( "//internal/platform/implementation:comm", "//internal/platform/implementation/g3", # buildcleaner: keep "//internal/test", - "//proto/mediums:ble_frames_cc_proto", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/hash:hash_testing", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/time", "@com_google_googletest//:gtest_main", diff --git a/connections/implementation/mediums/ble/ble_l2cap_packet.cc b/connections/implementation/mediums/ble/ble_l2cap_packet.cc index 4aa0f75d..323f6a29 100644 --- a/connections/implementation/mediums/ble/ble_l2cap_packet.cc +++ b/connections/implementation/mediums/ble/ble_l2cap_packet.cc @@ -14,6 +14,7 @@ #include "connections/implementation/mediums/ble/ble_l2cap_packet.h" +#include #include #include @@ -23,6 +24,8 @@ #include "connections/implementation/mediums/ble/ble_advertisement.h" #include "connections/implementation/mediums/utils.h" #include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/input_stream.h" #include "internal/platform/logging.h" #include "internal/platform/stream_reader.h" @@ -42,6 +45,95 @@ BleL2capPacket::BleL2capPacket(Command command, } } +absl::StatusOr BleL2capPacket::CreateFromStream( + InputStream& input_stream) { + // The first 1 byte is the command. + ExceptionOr command_byte = input_stream.Read(kCommandLength); + if (!command_byte.ok()) { + LOG(WARNING) << "Cannot read BleL2capPacket: command byte not available."; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: command byte not available."); + } + Command command_data = + static_cast((int)command_byte.result().data()[0]); + VLOG(1) << "command_data: " << static_cast(command_data); + + int data_length = 0; + if (command_data == Command::kRequestAdvertisement || + command_data == Command::kResponseAdvertisement) { + ExceptionOr length_byte = input_stream.Read(2); + if (!length_byte.ok()) { + LOG(WARNING) << "Cannot read BleL2capPacket: length byte not available."; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: length byte not available."); + } + data_length = (static_cast(length_byte.result().data()[0]) << 8) | + (static_cast(length_byte.result().data()[1])); + if (data_length == 0) { + LOG(WARNING) << "Cannot read BleL2capPacket: data length incorrect."; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: data length incorrect."); + } + } + + switch (command_data) { + case Command::kRequestAdvertisementFinish: + case Command::kRequestDataConnection: + case Command::kResponseServiceIdNotFound: + case Command::kResponseDataConnectionReady: + case Command::kResponseDataConnectionFailure: + return BleL2capPacket(command_data, nullptr, nullptr); + case Command::kRequestAdvertisement: { + if (data_length < BleAdvertisement::kServiceIdHashLength) { + LOG(WARNING) + << "Cannot read BleL2capPacket: service id hash length, got " + << data_length; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: service id hash length " + "incorrect."); + } + ExceptionOr service_id_hash_byte = + input_stream.Read(data_length); + if (!service_id_hash_byte.ok() || + service_id_hash_byte.result().size() != data_length) { + LOG(WARNING) << "Cannot read BleL2capPacket: service id hash byte " + "not available."; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: service id hash byte not " + "available."); + } + return BleL2capPacket(command_data, &service_id_hash_byte.result(), + nullptr); + } + case Command::kResponseAdvertisement: { + if (data_length > BleAdvertisement::kMaxAdvertisementLength) { + LOG(INFO) << "Cannot read BleL2capPacket: advertisement length, got " + << data_length; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: advertisement length incorrect."); + } + ExceptionOr advertisement_data = + input_stream.Read(data_length); + if (!advertisement_data.ok() || + advertisement_data.result().size() != data_length) { + LOG(WARNING) + << "Cannot read BleL2capPacket: advertisement not available."; + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: advertisement not available."); + } + return BleL2capPacket(command_data, nullptr, + &advertisement_data.result()); + } + default: + // fall through + break; + } + LOG(WARNING) << "Cannot read BleL2capPacket: unsupported command " + << static_cast(command_data); + return absl::InvalidArgumentError( + "Cannot read BleL2capPacket: unsupported command."); +} + absl::StatusOr BleL2capPacket::CreateFromBytes( const ByteArray& bytes) { if (bytes.size() < kCommandLength) { @@ -182,7 +274,7 @@ ByteArray BleL2capPacket::ByteArrayForCommand(BleL2capPacket::Command command, out = absl::StrCat(std::string(1, static_cast(command)), std::string(length_bytes), std::string(*data)); } else { - out = static_cast(command); + out = std::string(1, static_cast(command)); } return ByteArray{std::move(out)}; } diff --git a/connections/implementation/mediums/ble/ble_l2cap_packet.h b/connections/implementation/mediums/ble/ble_l2cap_packet.h index da6ee1e4..13343c53 100644 --- a/connections/implementation/mediums/ble/ble_l2cap_packet.h +++ b/connections/implementation/mediums/ble/ble_l2cap_packet.h @@ -20,6 +20,7 @@ #include "absl/status/statusor.h" #include "connections/implementation/mediums/ble/ble_advertisement.h" #include "internal/platform/byte_array.h" +#include "internal/platform/input_stream.h" namespace nearby { namespace connections { @@ -59,6 +60,9 @@ class BleL2capPacket { // Creates a BleL2capPacket from the raw bytes. static absl::StatusOr CreateFromBytes(const ByteArray& bytes); + // Creates a BleL2capPacket from the input stream. + static absl::StatusOr CreateFromStream( + InputStream& input_stream); // Creates a BleL2capPacket for requesting advertisement with service ID. static absl::StatusOr ByteArrayForRequestAdvertisement( const std::string& service_id); diff --git a/connections/implementation/mediums/ble/ble_l2cap_packet_test.cc b/connections/implementation/mediums/ble/ble_l2cap_packet_test.cc index 2084df2b..f5e6cc5b 100644 --- a/connections/implementation/mediums/ble/ble_l2cap_packet_test.cc +++ b/connections/implementation/mediums/ble/ble_l2cap_packet_test.cc @@ -15,10 +15,13 @@ #include "connections/implementation/mediums/ble/ble_l2cap_packet.h" #include +#include #include "gtest/gtest.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "internal/platform/byte_array.h" +#include "internal/platform/pipe.h" namespace nearby { namespace connections { @@ -197,6 +200,209 @@ TEST(BleL2capPacketTest, CreateFromBytesWithInvalidLengthAdvertisement) { ASSERT_FALSE(result.ok()); } +TEST(BleL2capPacketTest, CreateFromStreamFailsReadCommand) { + auto [input, output] = nearby::CreatePipe(); + + output->Write(ByteArray{}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + +TEST(BleL2capPacketTest, CreateFromStreamRequestAdvertisement) { + auto [input, output] = nearby::CreatePipe(); + auto byte_array = + BleL2capPacket::ByteArrayForRequestAdvertisement(std::string(kServiceID)); + ASSERT_TRUE(byte_array.ok()); + + output->Write(*byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsFetchAdvertisementRequest()); + EXPECT_EQ(ble_l2cap_packet->GetServiceIdHash(), + BleL2capPacket::GenerateServiceIdHash(std::string(kServiceID))); +} + +TEST(BleL2capPacketTest, CreateFromStreamZeroDataLength) { + auto [input, output] = nearby::CreatePipe(); + std::string out = absl::StrCat( + std::string( + 1, static_cast(BleL2capPacket::Command::kRequestAdvertisement)), + std::string("\x00\x00", 2)); + + output->Write(ByteArray{std::move(out)}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + +TEST(BleL2capPacketTest, + CreateFromStreamRequestAdvertisementBadServiceIdHashLength) { + auto [input, output] = nearby::CreatePipe(); + std::string out = absl::StrCat( + std::string( + 1, static_cast(BleL2capPacket::Command::kRequestAdvertisement)), + std::string("\x00\x01", 2)); + + output->Write(ByteArray{std::move(out)}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + +TEST(BleL2capPacketTest, CreateFromStreamFailsReadServiceIdHash) { + auto [input, output] = nearby::CreatePipe(); + std::string out = absl::StrCat( + std::string( + 1, static_cast(BleL2capPacket::Command::kRequestAdvertisement)), + std::string("\x00\x03", 2)); + + output->Write(ByteArray{std::move(out)}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + +TEST(BleL2capPacketTest, CreateFromStreamRequestAdvertisementFinish) { + auto [input, output] = nearby::CreatePipe(); + auto byte_array = BleL2capPacket::ByteArrayForRequestAdvertisementFinish(); + + output->Write(byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsFetchAdvertisementFinished()); +} + +TEST(BleL2capPacketTest, CreateFromStreamResponseAdvertisement) { + auto [input, output] = nearby::CreatePipe(); + ByteArray advertisement(kCorrectData.data(), kCorrectData.size()); + auto byte_array = + BleL2capPacket::ByteArrayForResponseAdvertisement(advertisement); + ASSERT_TRUE(byte_array.ok()); + + output->Write(*byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsAdvertisementResponse()); + EXPECT_EQ(ble_l2cap_packet->GetAdvertisement(), advertisement); +} + +TEST(BleL2capPacketTest, + CreateFromStreamResponseAdvertisementBadAdvertisementLength) { + auto [input, output] = nearby::CreatePipe(); + // 0xFFFF is too large for advertisement length. + std::string out = absl::StrCat( + std::string(1, static_cast( + BleL2capPacket::Command::kResponseAdvertisement)), + "\xFF\xFF"); + + output->Write(ByteArray{std::move(out)}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + +TEST(BleL2capPacketTest, CreateFromStreamFailsReadAdvertisement) { + auto [input, output] = nearby::CreatePipe(); + std::string out = absl::StrCat( + std::string(1, static_cast( + BleL2capPacket::Command::kResponseAdvertisement)), + std::string("\x00\x0a", 2)); + + output->Write(ByteArray{std::move(out)}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + +TEST(BleL2capPacketTest, CreateFromStreamResponseLargeAdvertisement) { + auto [input, output] = nearby::CreatePipe(); + ByteArray advertisement(300); + auto byte_array = + BleL2capPacket::ByteArrayForResponseAdvertisement(advertisement); + ASSERT_TRUE(byte_array.ok()); + + output->Write(*byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsAdvertisementResponse()); + EXPECT_EQ(ble_l2cap_packet->GetAdvertisement(), advertisement); + EXPECT_EQ(ble_l2cap_packet->GetAdvertisement().size(), 300); +} + +TEST(BleL2capPacketTest, CreateFromStreamServiceIdNotFound) { + auto [input, output] = nearby::CreatePipe(); + auto byte_array = BleL2capPacket::ByteArrayForServiceIdNotFound(); + + output->Write(byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsErrorServiceIdNotFound()); +} + +TEST(BleL2capPacketTest, CreateFromStreamRequestDataConnection) { + auto [input, output] = nearby::CreatePipe(); + auto byte_array = BleL2capPacket::ByteArrayForRequestDataConnection(); + + output->Write(byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsDataConnectionRequest()); +} + +TEST(BleL2capPacketTest, CreateFromStreamDataConnectionReady) { + auto [input, output] = nearby::CreatePipe(); + auto byte_array = BleL2capPacket::ByteArrayForDataConnectionReady(); + + output->Write(byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsDataConnectionReadyResponse()); +} + +TEST(BleL2capPacketTest, CreateFromStreamDataConnectionFailure) { + auto [input, output] = nearby::CreatePipe(); + auto byte_array = BleL2capPacket::ByteArrayForDataConnectionFailure(); + + output->Write(byte_array); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_TRUE(ble_l2cap_packet.ok()); + EXPECT_TRUE(ble_l2cap_packet->IsDataConnectionFailureResponse()); +} + +TEST(BleL2capPacketTest, CreateFromStreamUnsupportedCommand) { + auto [input, output] = nearby::CreatePipe(); + std::string out = absl::StrCat(std::string(1, static_cast(0XFF))); + + output->Write(ByteArray{std::move(out)}); + output->Close(); + + auto ble_l2cap_packet = BleL2capPacket::CreateFromStream(*input); + ASSERT_FALSE(ble_l2cap_packet.ok()); +} + } // namespace } // namespace mediums } // namespace connections diff --git a/connections/implementation/mediums/ble/ble_packet_test.cc b/connections/implementation/mediums/ble/ble_packet_test.cc index 8dd888d5..44148dbe 100644 --- a/connections/implementation/mediums/ble/ble_packet_test.cc +++ b/connections/implementation/mediums/ble/ble_packet_test.cc @@ -174,6 +174,37 @@ TEST(BlePacketTest, ConstructionFromSerializedShortLengthDataBytesFails) { EXPECT_FALSE(short_ble_packet.IsValid()); } +TEST(BlePacketTest, ConstructionFromEmptyBytesIsInvalid) { + BlePacket ble_packet((ByteArray())); + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromSerializedControlBytesWorks) { + ByteArray service_id_hash{std::string(kServiceIDHash)}; + absl::StatusOr org_ble_packet_status_or = + BlePacket::CreateControlIntroductionPacket(service_id_hash); + 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_TRUE(ble_packet.IsControlPacket()); + EXPECT_EQ(ble_packet.GetServiceIdHash(), service_id_hash); + EXPECT_EQ(ble_packet.GetControlFrameType(), + SocketControlFrame::INTRODUCTION); + ASSERT_OK_AND_ASSIGN( + SocketVersion version, + ble_packet.GetIntroductonSocketVersion()); + EXPECT_EQ(version, SocketVersion::V2); +} + +TEST(BlePacketTest, ConstructionFromInvalidControlPacketIsInvalid) { + ByteArray invalid_control_packet_bytes("\x00\x00\x00\x01\x02\x03", 6); + BlePacket ble_packet(invalid_control_packet_bytes); + EXPECT_FALSE(ble_packet.IsValid()); +} + TEST(BlePacketTest, IsControlPacketBytes) { EXPECT_TRUE(BlePacket::IsControlPacketBytes(ByteArray("\x00\x00\x00", 3))); EXPECT_FALSE(