From 2ea692213d7fbbf13020857219860d3ba9c503df Mon Sep 17 00:00:00 2001 From: Mingshiou Wu Date: Fri, 14 Mar 2025 00:32:55 -0700 Subject: [PATCH] Add BleL2capPacket PiperOrigin-RevId: 736771387 --- Package.swift | 1 + .../implementation/mediums/ble_v2/BUILD | 3 + .../mediums/ble_v2/ble_advertisement.h | 40 ++-- .../ble_v2/ble_advertisement_header.cc | 2 +- .../mediums/ble_v2/ble_l2cap_packet.cc | 192 +++++++++++++++++ .../mediums/ble_v2/ble_l2cap_packet.h | 135 ++++++++++++ .../mediums/ble_v2/ble_l2cap_packet_test.cc | 203 ++++++++++++++++++ 7 files changed, 554 insertions(+), 22 deletions(-) create mode 100644 connections/implementation/mediums/ble_v2/ble_l2cap_packet.cc create mode 100644 connections/implementation/mediums/ble_v2/ble_l2cap_packet.h create mode 100644 connections/implementation/mediums/ble_v2/ble_l2cap_packet_test.cc diff --git a/Package.swift b/Package.swift index 1103cc0b..4a597d5f 100644 --- a/Package.swift +++ b/Package.swift @@ -460,6 +460,7 @@ let package = Package( "connections/implementation/mediums/advertisements/advertisement_util_test.cc", "connections/implementation/mediums/ble_v2_test.cc", "connections/implementation/mediums/ble_v2/bloom_filter_test.cc", + "connections/implementation/mediums/ble_v2/ble_l2cap_packet_test.cc", "connections/implementation/mediums/ble_v2/ble_packet_test.cc", "connections/implementation/mediums/ble_v2/ble_advertisement_test.cc", "connections/implementation/mediums/ble_v2/advertisement_read_result_test.cc", diff --git a/connections/implementation/mediums/ble_v2/BUILD b/connections/implementation/mediums/ble_v2/BUILD index a6e5066b..77b1d020 100644 --- a/connections/implementation/mediums/ble_v2/BUILD +++ b/connections/implementation/mediums/ble_v2/BUILD @@ -54,6 +54,7 @@ cc_library( srcs = [ "advertisement_read_result.cc", "ble_advertisement.cc", + "ble_l2cap_packet.cc", "ble_packet.cc", "ble_utils.cc", "discovered_peripheral_tracker.cc", @@ -63,6 +64,7 @@ cc_library( hdrs = [ "advertisement_read_result.h", "ble_advertisement.h", + "ble_l2cap_packet.h", "ble_packet.h", "ble_utils.h", "discovered_peripheral_callback.h", @@ -111,6 +113,7 @@ cc_test( "advertisement_read_result_test.cc", "ble_advertisement_header_test.cc", "ble_advertisement_test.cc", + "ble_l2cap_packet_test.cc", "ble_packet_test.cc", "ble_utils_test.cc", "bloom_filter_test.cc", diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement.h b/connections/implementation/mediums/ble_v2/ble_advertisement.h index 232d4d08..d8e95848 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement.h +++ b/connections/implementation/mediums/ble_v2/ble_advertisement.h @@ -62,7 +62,25 @@ class BleAdvertisement { static constexpr int kServiceIdHashLength = 3; static constexpr int kDeviceTokenLength = 2; - + static constexpr int kVersionLength = 1; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kSocketVersionBitmask = 0x01C; + static constexpr int kFastAdvertisementFlagBitmask = 0x002; + static constexpr int kDataSizeLength = 4; // Length of one int. + static constexpr int kFastDataSizeLength = 1; // Length of one byte. + static constexpr int kMinAdvertisementLength = + kVersionLength + kServiceIdHashLength + kDataSizeLength; + // The maximum length for a Gatt characteristic value is 512 bytes, so make + // sure the entire advertisement is less than that. The data can take up + // whatever space is remaining after the bytes preceding it. + static constexpr int kMaxAdvertisementLength = 512; + static constexpr int kMinFastAdvertisementLegth = + kVersionLength + kFastDataSizeLength; + // The maximum length for the scan response is 31 bytes. However, with the + // required header that comes before the service data, this leaves the + // advertiser with 27 leftover bytes. + static constexpr int kMaxFastAdvertisementLength = 27; + static constexpr int kExtraFieldsMaskLength = 1; // Hashable bool operator==(const BleAdvertisement &rhs) const; template @@ -160,26 +178,6 @@ class BleAdvertisement { total_optional_length); } - static constexpr int kVersionLength = 1; - static constexpr int kVersionBitmask = 0x0E0; - static constexpr int kSocketVersionBitmask = 0x01C; - static constexpr int kFastAdvertisementFlagBitmask = 0x002; - static constexpr int kDataSizeLength = 4; // Length of one int. - static constexpr int kFastDataSizeLength = 1; // Length of one byte. - static constexpr int kMinAdvertisementLength = - kVersionLength + kServiceIdHashLength + kDataSizeLength; - // The maximum length for a Gatt characteristic value is 512 bytes, so make - // sure the entire advertisement is less than that. The data can take up - // whatever space is remaining after the bytes preceding it. - static constexpr int kMaxAdvertisementLength = 512; - static constexpr int kMinFastAdvertisementLegth = - kVersionLength + kFastDataSizeLength; - // The maximum length for the scan response is 31 bytes. However, with the - // required header that comes before the service data, this leaves the - // advertiser with 27 leftover bytes. - static constexpr int kMaxFastAdvertisementLength = 27; - static constexpr int kExtraFieldsMaskLength = 1; - Version version_{Version::kUndefined}; SocketVersion socket_version_{SocketVersion::kUndefined}; bool fast_advertisement_ = false; diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc b/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc index 717ee916..b8821635 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc @@ -146,7 +146,7 @@ BleAdvertisementHeader::operator ByteArray() const { // Convert psm_ value to 2-bytes. ByteArray psm_bytes{kPsmValueByteLength}; char *data = psm_bytes.data(); - data[0] = psm_ & 0xFF00; + data[0] = (psm_ & 0xFF00) >> 8; data[1] = psm_ & 0x00FF; // clang-format off diff --git a/connections/implementation/mediums/ble_v2/ble_l2cap_packet.cc b/connections/implementation/mediums/ble_v2/ble_l2cap_packet.cc new file mode 100644 index 00000000..016ec257 --- /dev/null +++ b/connections/implementation/mediums/ble_v2/ble_l2cap_packet.cc @@ -0,0 +1,192 @@ +// Copyright 2025 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_l2cap_packet.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "connections/implementation/mediums/ble_v2/ble_advertisement.h" +#include "connections/implementation/mediums/utils.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/logging.h" +#include "internal/platform/stream_reader.h" + +namespace nearby { +namespace connections { +namespace mediums { + +BleL2capPacket::BleL2capPacket(Command command, + const ByteArray* service_id_hash, + const ByteArray* data) + : command_(command) { + if (service_id_hash) { + service_id_hash_ = *service_id_hash; + } + if (data) { + advertisement_ = *data; + } +} + +absl::StatusOr BleL2capPacket::CreateFromBytes( + const ByteArray& bytes) { + if (bytes.size() < kCommandLength) { + LOG(INFO) << "Cannot deserialize BleL2capPacket: expecting min " + << kCommandLength << " bytes, got " << bytes.size(); + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: input bytes too short."); + } + + StreamReader stream_reader(bytes); + // The first 1 byte is the command. + auto command_byte = stream_reader.ReadUint8(); + if (!command_byte.has_value()) { + LOG(INFO) + << "Cannot deserialize BleL2capPacket: command byte not available."; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: command byte not available."); + } + Command command_data = static_cast(*command_byte); + + if (command_data == Command::kRequestAdvertisementFinish || + command_data == Command::kRequestDataConnection || + command_data == Command::kResponseServiceIdNotFound || + command_data == Command::kResponseDataConnectionReady || + command_data == Command::kResponseDataConnectionFailure) { + return BleL2capPacket(command_data, nullptr, nullptr); + } + + if (!stream_reader.IsAvailable(kDataLength)) { + LOG(INFO) << "Cannot deserialize BleL2capPacket: data length bytes not " + "available."; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: data length bytes not available."); + } + + int data_length = stream_reader.ReadUint16().value_or(0); + if (data_length == 0) { + LOG(INFO) << "Cannot deserialize BleL2capPacket: data length incorrect."; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: data length incorrect."); + } + + if (command_data == Command::kRequestAdvertisement) { + if (data_length < BleAdvertisement::kServiceIdHashLength) { + LOG(INFO) + << "Cannot deserialize BleL2capPacket: service id hash length, got " + << data_length; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: service id hash length " + "incorrect."); + } + auto service_id_hash_data = stream_reader.ReadBytes(data_length); + if (!service_id_hash_data.has_value()) { + LOG(INFO) << "Cannot deserialize BleL2capPacket: service id hash not " + "available."; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: service id hash not available."); + } + return BleL2capPacket(command_data, &service_id_hash_data.value(), nullptr); + } else if (command_data == Command::kResponseAdvertisement) { + if (data_length > BleAdvertisement::kMaxAdvertisementLength) { + LOG(INFO) << "Cannot deserialize BleL2capPacket: advertisement length, " + "got " + << data_length; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: advertisement length incorrect."); + } + auto advertisement_data = stream_reader.ReadBytes(data_length); + if (!advertisement_data.has_value()) { + LOG(INFO) + << "Cannot deserialize BleL2capPacket: advertisement not available."; + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: advertisement not available."); + } + return BleL2capPacket(command_data, nullptr, &advertisement_data.value()); + } else { + LOG(INFO) << "Cannot deserialize BleL2capPacket: unsupported command " + << static_cast(command_data); + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: unsupported command."); + } +} + +absl::StatusOr BleL2capPacket::ByteArrayForRequestAdvertisement( + const std::string& service_id) { + if (service_id.empty()) { + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: service id is empty."); + } + ByteArray service_id_hash = GenerateServiceIdHash(service_id); + return ByteArrayForCommand(BleL2capPacket::Command::kRequestAdvertisement, + &service_id_hash); +} + +absl::StatusOr BleL2capPacket::ByteArrayForResponseAdvertisement( + const ByteArray& advertisement) { + if (advertisement.size() < BleAdvertisement::kMinAdvertisementLength) { + return absl::InvalidArgumentError( + "Cannot deserialize BleL2capPacket: advertisement size is too small."); + } + return ByteArrayForCommand(BleL2capPacket::Command::kResponseAdvertisement, + &advertisement); +} + +ByteArray BleL2capPacket::ByteArrayForRequestAdvertisementFinish() { + return ByteArrayForCommand( + BleL2capPacket::Command::kRequestAdvertisementFinish, {}); +} + +ByteArray BleL2capPacket::ByteArrayForServiceIdNotFound() { + return ByteArrayForCommand(Command::kResponseServiceIdNotFound, {}); +} + +ByteArray BleL2capPacket::ByteArrayForRequestDataConnection() { + return ByteArrayForCommand(Command::kRequestDataConnection, {}); +} + +ByteArray BleL2capPacket::ByteArrayForDataConnectionReady() { + return ByteArrayForCommand(Command::kResponseDataConnectionReady, {}); +} + +ByteArray BleL2capPacket::ByteArrayForDataConnectionFailure() { + return ByteArrayForCommand(Command::kResponseDataConnectionFailure, {}); +} + +ByteArray BleL2capPacket::GenerateServiceIdHash(const std::string& service_id) { + return Utils::Sha256Hash(service_id, BleAdvertisement::kServiceIdHashLength); +} + +ByteArray BleL2capPacket::ByteArrayForCommand(BleL2capPacket::Command command, + const ByteArray* data) { + std::string out; + if (data) { + ByteArray length_bytes{kDataLength}; + char* length_bytes_data = length_bytes.data(); + length_bytes_data[0] = (data->size() & 0xFF00) >> 8; + length_bytes_data[1] = data->size() & 0x00FF; + out = absl::StrCat(std::string(1, static_cast(command)), + std::string(length_bytes), std::string(*data)); + } else { + out = static_cast(command); + } + return ByteArray{std::move(out)}; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby diff --git a/connections/implementation/mediums/ble_v2/ble_l2cap_packet.h b/connections/implementation/mediums/ble_v2/ble_l2cap_packet.h new file mode 100644 index 00000000..67f74885 --- /dev/null +++ b/connections/implementation/mediums/ble_v2/ble_l2cap_packet.h @@ -0,0 +1,135 @@ +// Copyright 2025 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_BLE_L2CAP_PACKET_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_L2CAP_PACKET_H_ + +#include + +#include "absl/status/statusor.h" +#include "connections/implementation/mediums/ble_v2/ble_advertisement.h" +#include "internal/platform/byte_array.h" + +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the BLE L2CAP packet used in L2CAP socket for +// requesting/responsing. +// +// [COMMAND] or [COMMAND][LENGTH][DATA] +// +class BleL2capPacket { + public: + enum class Command { + // Use to fetch advertisement from server with serviceId. + kRequestAdvertisement = 1, + // Use to notify the server have fetched all advertisements completely. + kRequestAdvertisementFinish = 2, + // Use to notify the server current L2CAP socket use for file transferring. + kRequestDataConnection = 3, + // 11-20 reserved + // Use to response the advertisement raw data to the client. + kResponseAdvertisement = 21, + // Use to response the queried service ID not exist in server side. + kResponseServiceIdNotFound = 22, + // Use to notify the data connection ready + kResponseDataConnectionReady = 23, + // Use to notify failure for requesting the data connection. + kResponseDataConnectionFailure = 24, + }; + + static constexpr int kCommandLength = 1; + static constexpr int kDataLength = 2; + // The maximum length of a BleL2capPacket is the length when carry the + // advertisement raw data. + static constexpr int kMaxLength = + kCommandLength + kDataLength + BleAdvertisement::kMaxAdvertisementLength; + + // Creates a BleL2capPacket from the raw bytes. + static absl::StatusOr CreateFromBytes(const ByteArray& bytes); + // Creates a BleL2capPacket for requesting advertisement with service ID. + static absl::StatusOr ByteArrayForRequestAdvertisement( + const std::string& service_id); + // Creates a BleL2capPacket for responding advertisement with raw data. + static absl::StatusOr ByteArrayForResponseAdvertisement( + const ByteArray& advertisement); + // Creates a BleL2capPacket for requesting advertisement finish. + static ByteArray ByteArrayForRequestAdvertisementFinish(); + // Creates a BleL2capPacket for responding service ID not found. + static ByteArray ByteArrayForServiceIdNotFound(); + // Creates a BleL2capPacket for requesting data connection. + static ByteArray ByteArrayForRequestDataConnection(); + // Creates a BleL2capPacket for responding data connection ready. + static ByteArray ByteArrayForDataConnectionReady(); + // Creates a BleL2capPacket for responding data connection failure. + static ByteArray ByteArrayForDataConnectionFailure(); + // Generates the service ID hash bytes from the service ID string. + static ByteArray GenerateServiceIdHash(const std::string& service_id); + // Checks if the length of the BleL2capPacket is valid. + static bool IsValidLength(int length) { + return length > 0 && length <= kMaxLength; + } + + BleL2capPacket(Command command, const ByteArray* service_id_hash, + const ByteArray* data); + BleL2capPacket(const BleL2capPacket&) = default; + BleL2capPacket& operator=(const BleL2capPacket&) = default; + BleL2capPacket(BleL2capPacket&&) = default; + BleL2capPacket& operator=(BleL2capPacket&&) = default; + ~BleL2capPacket() = default; + + explicit operator ByteArray() const; + + // The service ID hash bytes, only exist for command REQUEST_ADVERTISEMENT and + // Command.REQUEST_DATA_CONNECTION. + ByteArray GetServiceIdHash() const { return service_id_hash_; } + // The advertisement raw bytes, only exist for command RESPONSE_ADVERTISEMENT. + ByteArray GetAdvertisement() const { return advertisement_; } + bool IsErrorServiceIdNotFound() const { + return command_ == Command::kResponseServiceIdNotFound; + } + bool IsAdvertisementResponse() const { + return command_ == Command::kResponseAdvertisement; + } + bool IsFetchAdvertisementRequest() const { + return command_ == Command::kRequestAdvertisement; + } + bool IsFetchAdvertisementFinished() const { + return command_ == Command::kRequestAdvertisementFinish; + } + bool IsDataConnectionRequest() const { + return command_ == Command::kRequestDataConnection; + } + bool IsDataConnectionReadyResponse() const { + return command_ == Command::kResponseDataConnectionReady; + } + bool IsDataConnectionFailureResponse() const { + return command_ == Command::kResponseDataConnectionFailure; + } + + private: + BleL2capPacket() = default; + static ByteArray ByteArrayForCommand(Command command, const ByteArray* data); + + Command command_; + ByteArray service_id_hash_; + ByteArray advertisement_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby + +#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_L2CAP_PACKET_H_ diff --git a/connections/implementation/mediums/ble_v2/ble_l2cap_packet_test.cc b/connections/implementation/mediums/ble_v2/ble_l2cap_packet_test.cc new file mode 100644 index 00000000..6103bb59 --- /dev/null +++ b/connections/implementation/mediums/ble_v2/ble_l2cap_packet_test.cc @@ -0,0 +1,203 @@ +// 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_l2cap_packet.h" + +#include + +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" + +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +constexpr absl::string_view kInCorrectData = {"\x01\x02\x03\x04\x05"}; +constexpr absl::string_view kServiceID = "1test_service_id"; +constexpr absl::string_view kCorrectData = { + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00"}; + +TEST(BleL2capPacketTest, ByteArrayForRequestAdvertisementWithCorrectServiceID) { + auto byte_array = + BleL2capPacket::ByteArrayForRequestAdvertisement(std::string(kServiceID)); + + ASSERT_TRUE(byte_array.ok()); + + auto result = BleL2capPacket::CreateFromBytes(*byte_array); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsFetchAdvertisementRequest()); + ASSERT_EQ(result.value().GetServiceIdHash(), + BleL2capPacket::GenerateServiceIdHash(std::string(kServiceID))); +} + +TEST(BleL2capPacketTest, ByteArrayForRequestAdvertisementWithEmptyServiceID) { + auto byte_array = BleL2capPacket::ByteArrayForRequestAdvertisement(""); + + ASSERT_FALSE(byte_array.ok()); +} + +TEST(BleL2capPacketTest, + ByteArrayForResponseAdvertisementWithCorrectAdvertisement) { + ByteArray advertisement(kCorrectData.data(), kCorrectData.size()); + auto byte_array = + BleL2capPacket::ByteArrayForResponseAdvertisement(advertisement); + + ASSERT_TRUE(byte_array.ok()); + + auto result = BleL2capPacket::CreateFromBytes(*byte_array); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsAdvertisementResponse()); + ASSERT_EQ(result.value().GetAdvertisement(), advertisement); +} + +TEST(BleL2capPacketTest, + ByteArrayForResponseAdvertisementWithInCorrectAdvertisement) { + ByteArray advertisement(kInCorrectData.data(), kInCorrectData.size()); + auto byte_array = + BleL2capPacket::ByteArrayForResponseAdvertisement(advertisement); + + ASSERT_FALSE(byte_array.ok()); +} + +TEST(BleL2capPacketTest, + ByteArrayForResponseAdvertisementWithLargeAdvertisement) { + auto byte_array = + BleL2capPacket::ByteArrayForResponseAdvertisement(ByteArray(300)); + + ASSERT_TRUE(byte_array.ok()); + + auto result = BleL2capPacket::CreateFromBytes(byte_array.value()); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsAdvertisementResponse()); + ASSERT_EQ(result.value().GetAdvertisement().size(), 300); +} + +TEST(BleL2capPacketTest, ByteArrayForRequestAdvertisementFinish) { + ByteArray byte_array = + BleL2capPacket::ByteArrayForRequestAdvertisementFinish(); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsFetchAdvertisementFinished()); +} + +TEST(BleL2capPacketTest, ByteArrayForServiceIdNotFound) { + ByteArray byte_array = BleL2capPacket::ByteArrayForServiceIdNotFound(); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_TRUE(result.value().IsErrorServiceIdNotFound()); +} + +TEST(BleL2capPacketTest, ByteArrayForRequestDataConnection) { + ByteArray byte_array = BleL2capPacket::ByteArrayForRequestDataConnection(); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsDataConnectionRequest()); +} + +TEST(BleL2capPacketTest, ByteArrayForDataConnectionReady) { + ByteArray byte_array = BleL2capPacket::ByteArrayForDataConnectionReady(); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsDataConnectionReadyResponse()); +} + +TEST(BleL2capPacketTest, ByteArrayForDataConnectionFailure) { + ByteArray byte_array = BleL2capPacket::ByteArrayForDataConnectionFailure(); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value().IsDataConnectionFailureResponse()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithEmptyData) { + auto byte_array = BleL2capPacket::CreateFromBytes(ByteArray()); + + ASSERT_FALSE(byte_array.ok()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithInvalidCommand) { + ByteArray byte_array(nullptr, 1); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_FALSE(result.ok()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithoutDataLength) { + ByteArray byte_array{1}; + char *byte_array_data = byte_array.data(); + byte_array_data[0] = + static_cast(BleL2capPacket::Command::kRequestAdvertisement); + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_FALSE(result.ok()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithEmptyDataLength) { + ByteArray byte_array{3}; + char *byte_array_data = byte_array.data(); + byte_array_data[0] = + static_cast(BleL2capPacket::Command::kRequestAdvertisement); + byte_array_data[1] = 0x00; + byte_array_data[2] = 0x00; + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_FALSE(result.ok()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithInvalidServiceIdHashLength) { + ByteArray byte_array{3}; + char *byte_array_data = byte_array.data(); + byte_array_data[0] = + static_cast(BleL2capPacket::Command::kRequestAdvertisement); + byte_array_data[1] = 0x00; + byte_array_data[2] = 0x01; + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_FALSE(result.ok()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithTooLongAdvertisementLength) { + ByteArray byte_array{3}; + char *byte_array_data = byte_array.data(); + byte_array_data[0] = + static_cast(BleL2capPacket::Command::kResponseAdvertisement); + byte_array_data[1] = 0xFF; + byte_array_data[2] = 0xFF; + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_FALSE(result.ok()); +} + +TEST(BleL2capPacketTest, CreateFromBytesWithInvalidLengthAdvertisement) { + ByteArray byte_array{4}; + char *byte_array_data = byte_array.data(); + byte_array_data[0] = + static_cast(BleL2capPacket::Command::kResponseAdvertisement); + byte_array_data[1] = 0x00; + byte_array_data[2] = 0x03; + byte_array_data[3] = 0x01; + + auto result = BleL2capPacket::CreateFromBytes(byte_array); + ASSERT_FALSE(result.ok()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby