diff --git a/Package.swift b/Package.swift index 5cd7e4b4..8562ecf8 100644 --- a/Package.swift +++ b/Package.swift @@ -410,6 +410,7 @@ let package = Package( "internal/BUILD", "internal/crypto/BUILD", "internal/crypto/BUILD.gn", + "internal/weave/BUILD", "internal/platform/flags/BUILD", "internal/platform/implementation/shared/BUILD", "internal/platform/implementation/apple/Mediums/BUILD", @@ -543,6 +544,7 @@ let package = Package( "internal/test/fake_timer_test.cc", "internal/test/fake_device_info_test.cc", "internal/test/fake_task_runner_test.cc", + "internal/weave/packet_test.cc", // simulation "connections/implementation/offline_simulation_user.cc", "connections/implementation/simulation_user.cc", diff --git a/internal/platform/BUILD b/internal/platform/BUILD index 67093db8..8d44f2cc 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -50,6 +50,7 @@ cc_library( "//internal:__pkg__", "//internal/platform:__subpackages__", "//internal/platform/implementation:__subpackages__", + "//internal/weave:__pkg__", "//location/nearby/cpp:__subpackages__", "//presence:__subpackages__", ], diff --git a/internal/platform/implementation/g3/BUILD b/internal/platform/implementation/g3/BUILD index 63212d3d..1c4ca8eb 100644 --- a/internal/platform/implementation/g3/BUILD +++ b/internal/platform/implementation/g3/BUILD @@ -133,6 +133,7 @@ cc_library( "//internal/platform:__subpackages__", "//internal/proto/analytics:__subpackages__", "//internal/test:__subpackages__", + "//internal/weave:__subpackages__", "//location/nearby/cpp:__subpackages__", "//presence:__subpackages__", ], diff --git a/internal/weave/BUILD b/internal/weave/BUILD new file mode 100644 index 00000000..b58749a5 --- /dev/null +++ b/internal/weave/BUILD @@ -0,0 +1,31 @@ +cc_library( + name = "weave", + srcs = [ + "packet.cc", + ], + hdrs = [ + "packet.h", + ], + deps = [ + "//internal/platform:base", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + +cc_test( + name = "packet_test", + srcs = [ + "packet_test.cc", + ], + deps = [ + ":weave", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:str_format", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/internal/weave/packet.cc b/internal/weave/packet.cc new file mode 100644 index 00000000..fc472ded --- /dev/null +++ b/internal/weave/packet.cc @@ -0,0 +1,166 @@ +// Copyright 2023 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 "internal/weave/packet.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" + +namespace nearby { +namespace weave { + +namespace { +constexpr char kControlType = 0b10000000; +constexpr char kDataType = 0b00000000; +constexpr char kMaskPacketCounter = 0b01110000; +constexpr char kMaskControlCommandNumber = 0b00001111; +constexpr int kConnectionRequestExtraDataSize = 13; +constexpr int kConnectionConfirmExtraDataSize = 15; +constexpr int kConnectionConfirmPacketHeaderLength = 4; +constexpr int kConnectionRequestPacketHeaderLength = 6; +} // namespace + +Packet Packet::CreateDataPacket(bool is_first_packet, bool is_last_packet, + ByteArray payload) { + int next_four_bits = ((is_first_packet ? kFirstPacketBit : 0) | + (is_last_packet ? kLastPacketBit : 0)); + Packet packet = Packet(ByteArray(kPacketHeaderLength + payload.size())); + packet.SetHeader(/* is_control_packet = */ false, next_four_bits); + payload.AsStringView().copy(packet.bytes_.data() + kPacketHeaderLength, + payload.size()); + return packet; +} + +Packet Packet::CreateControlPacket(ControlPacketType command_number, + int payload_size) { + Packet packet = Packet(ByteArray(kPacketHeaderLength + payload_size)); + packet.SetHeader(/* is_control_packet = */ true, + static_cast(command_number)); + return packet; +} + +absl::StatusOr Packet::CreateConnectionRequestPacket( + int16_t min_protocol_version, int16_t max_protocol_version, + int16_t max_packet_size, absl::string_view extra_data) { + if (extra_data.size() > kConnectionRequestExtraDataSize) { + return absl::InvalidArgumentError( + "Connection request packet may contain at most 13 bytes of extra " + "data."); + } + Packet packet = CreateControlPacket( + ControlPacketType::kControlConnectionRequest, + kConnectionRequestPacketHeaderLength + extra_data.size()); + + // Create a string of length |kConnectionRequestPacketHeaderLength| filled + // with char 0. This string is used to store raw binary data. + std::string header_data(kConnectionRequestPacketHeaderLength, 0); + + // Put min_protocol_version into the header in big-endian order. + header_data[0] = (0xFF & (min_protocol_version >> 8)); + header_data[1] = (0xFF & min_protocol_version); + + // Put max_protocol_version into the header in big-endian order. + header_data[2] = (0xFF & (max_protocol_version >> 8)); + header_data[3] = (0xFF & max_protocol_version); + + // Put max_packet_size into the header in big-endian order. + header_data[4] = (0xFF & (max_packet_size >> 8)); + header_data[5] = (0xFF & max_packet_size); + + // Finally, move to packet. + header_data.copy(packet.bytes_.data() + kPacketHeaderLength, + header_data.size()); + + // Copy extra_data. + extra_data.copy(packet.bytes_.data() + kConnectionRequestPacketHeaderLength + + kPacketHeaderLength, + extra_data.size()); + return packet; +} + +absl::StatusOr Packet::CreateConnectionConfirmPacket( + int16_t selected_protocol_version, int16_t selected_packet_size, + absl::string_view extra_data) { + if (extra_data.size() > kConnectionConfirmExtraDataSize) { + return absl::InvalidArgumentError( + "Connection confirm packet may contain at most 15 bytes of extra " + "data."); + } + Packet packet = CreateControlPacket( + ControlPacketType::kControlConnectionConfirm, + kConnectionConfirmPacketHeaderLength + extra_data.size()); + // Create a string of length |kConnectionConfirmPacketHeaderLength| filled + // with char 0. This string is used to store raw binary data. + std::string header_data(kConnectionConfirmPacketHeaderLength, 0); + + // Put selected_protocol_version into the header in big-endian order. + header_data[0] = (0xFF & (selected_protocol_version >> 8)); + header_data[1] = (0xFF & selected_protocol_version); + + // Put selected_packet_size into the header in big-endian order. + header_data[2] = (0xFF & (selected_packet_size >> 8)); + header_data[3] = (0xFF & selected_packet_size); + + // Finally, move to packet. + header_data.copy(packet.bytes_.data() + kPacketHeaderLength, + header_data.size()); + + // Copy extra_data. + extra_data.copy(packet.bytes_.data() + kConnectionConfirmPacketHeaderLength + + kPacketHeaderLength, + extra_data.size()); + return packet; +} + +void Packet::SetHeader(bool is_control_packet, int last_four_bits) { + bytes_[0] = (is_control_packet ? kControlType : kDataType) | + (last_four_bits & 0b00001111); +} + +bool Packet::IsDataPacket() const { return !IsControlPacket(); } + +Packet::ControlPacketType Packet::GetControlCommandNumber() const { + return (ControlPacketType)(bytes_[0] & kMaskControlCommandNumber); +} + +int Packet::GetPacketCounter() const { + if (bytes_.empty()) return 0; + return (bytes_[0] & kMaskPacketCounter) >> 4; +} + +absl::Status Packet::SetPacketCounter(int packetCounter) { + if (packetCounter < 0 || packetCounter > kMaxPacketCounter) { + return absl::InvalidArgumentError( + absl::StrFormat("Packet counter %d out of range", packetCounter)); + } + bytes_[0] |= ((packetCounter << 4) & kMaskPacketCounter); + return absl::OkStatus(); +} + +std::string Packet::ToString() { + return absl::StrFormat("Packet[header: 0b%08d + payload: %d bytes]", + bytes_.front(), bytes_.size() - 1); +} + +} // namespace weave +} // namespace nearby diff --git a/internal/weave/packet.h b/internal/weave/packet.h new file mode 100644 index 00000000..b92d2e28 --- /dev/null +++ b/internal/weave/packet.h @@ -0,0 +1,153 @@ +// Copyright 2023 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 THIRD_PARTY_NEARBY_INTERNAL_WEAVE_PACKET_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_WEAVE_PACKET_H_ + +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "internal/platform/byte_array.h" + +namespace nearby { +namespace weave { + +/* +Spec: go/weave-ble-gatt-transport + +A weave packet has a 1 byte header indicating its packet counter, what type of +packet it is, and what type of control packet it may be. +Each control packet can also contain an optional payload, sized as follows: +Connection confirm packet: up to 15 bytes +Connection request packet: up to 13 bytes +Error packet: no payload + +A data packet by contrast, is sized to fit the connection as so: +min(server_packet_size, client_packet_size, connection_packet_size). + +The general format of the header of the Weave packet is as follows: +--------------------------- +| 7 | 6 5 4 | 3 | 2 | 1 0 | +--------------------------- +7) This bit is set to 0 if the packet is a data packet, and 1 if it is a control +packet. + +6, 5, 4) These bits represent the packet counter, which wraps after every 7 +packets. + +3) If set to 1, this bit indicates that the packet is the first packet of the +message. + +2) This bit indicates whether the packet is the last packet of the +message. + +1, 0) These bits indicate the type of control packet where 0b00 is a +connection request packet, 0b01 is a connection confirm packet, and 0b10 is an +error packet. 0b11 is not a type of control packet. + +If the packet is a control packet, and it is a connection request packet, the +first 6 bytes of the payload are as follows: +------------------------------------- +| 0x55 0x44 | 0x33 0x22 | 0x11 0x00 | +------------------------------------- +0x55, 0x44) The minimum protocol version the client supports. +0x33, 0x22) The maximum protocol version the client supports. +0x11, 0x00) The maximum packet size the client supports. + +If the packet is a connection confirm packet, the first 4 bytes of the payload +are as follows: +------------------------- +| 0x44 0x33 | 0x22 0x11 | +------------------------- +0x44, 0x33) The selected protocol version for this connection based on the +server support. + +0x22, 0x11) The selected packet size of this connection based on the server's +packet size. + +*/ +class Packet { + public: + static constexpr int kMaxPacketCounter = 0b111; + enum class ControlPacketType { + kControlConnectionRequest = 0, + kControlConnectionConfirm = 1, + kControlError = 2, + }; + + // NOTE: The below Packet builders will take ownership of the bytes passed + // into them. + static absl::StatusOr FromBytes(ByteArray bytes) { + if (bytes.Empty()) { + return absl::InvalidArgumentError( + "Need at least one byte in this packet"); + } + return Packet(std::move(bytes)); + } + static Packet CreateDataPacket(bool is_first_packet, bool is_last_packet, + ByteArray payload); + static absl::StatusOr CreateConnectionRequestPacket( + int16_t min_protocol_version, int16_t max_protocol_version, + int16_t max_packet_size, absl::string_view extra_data); + static absl::StatusOr CreateConnectionConfirmPacket( + int16_t selected_protocol_version, int16_t selected_packet_size, + absl::string_view extra_data); + static Packet CreateErrorPacket() { + return CreateControlPacket(ControlPacketType::kControlError, + /*payload_size=*/0); + } + + Packet(Packet&& other) = default; + Packet& operator=(Packet&& other) = default; + + bool IsFirstPacket() const { + return !bytes_.empty() && (bytes_.data()[0] & kFirstPacketBit) != 0; + } + bool IsLastPacket() const { + return !bytes_.empty() && (bytes_.data()[0] & kLastPacketBit) != 0; + } + bool IsControlPacket() const { + return !bytes_.empty() && (bytes_.data()[0] & kMaskType) != 0; + } + bool IsDataPacket() const; + int GetPacketCounter() const; + ControlPacketType GetControlCommandNumber() const; + std::string GetPayload() const { return bytes_.substr(kPacketHeaderLength); } + std::string GetBytes() const { return bytes_; } + absl::Status SetPacketCounter(int packetCounter); + std::string ToString(); + + private: + static constexpr char kFirstPacketBit = 0b00001000; + static constexpr char kLastPacketBit = 0b00000100; + static constexpr char kMaskType = 0b10000000; + static constexpr int kPacketHeaderLength = 1; + + static Packet CreateControlPacket(ControlPacketType command_number, + int payload_size); + + explicit Packet(ByteArray&& bytes) : bytes_(std::move(bytes)) {} + + void SetHeader(bool is_control_packet, int last_four_bits); + + // Raw binary packet data. + std::string bytes_; +}; + +} // namespace weave +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_WEAVE_PACKET_H_ diff --git a/internal/weave/packet_test.cc b/internal/weave/packet_test.cc new file mode 100644 index 00000000..8fc7385b --- /dev/null +++ b/internal/weave/packet_test.cc @@ -0,0 +1,153 @@ +// Copyright 2023 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 "internal/weave/packet.h" + +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/strings/str_format.h" + +namespace nearby { +namespace weave { + +namespace { + +constexpr absl::string_view kExtraData = "\x34\x56\x78"; +constexpr absl::string_view kConnectionRequestExtraData = "thirteen char"; +constexpr absl::string_view kConnectionConfirmExtraData = "fifteen char..."; + +TEST(PacketTest, PacketIsNotTriviallyConstructible) { + EXPECT_FALSE(std::is_trivially_constructible()); +} + +TEST(PacketTest, CreateErrorPacketTest) { + Packet packet = Packet::CreateErrorPacket(); + EXPECT_TRUE(packet.IsControlPacket()); + EXPECT_EQ(packet.GetControlCommandNumber(), + Packet::ControlPacketType::kControlError); + EXPECT_EQ(packet.GetPayload().size(), 0); +} + +TEST(PacketTest, CreateConnectionConfirmPacketGoodTest) { + Packet packet = + Packet::CreateConnectionConfirmPacket(1, 15, kConnectionConfirmExtraData) + .value(); + EXPECT_TRUE(packet.IsControlPacket()); + EXPECT_EQ(packet.GetControlCommandNumber(), + Packet::ControlPacketType::kControlConnectionConfirm); + ASSERT_EQ(packet.GetBytes().size(), 20); + auto expected = absl::StrFormat("%c%c%c%c%c%s", 0b10000001, 0x00, 0x01, 0x00, + 0x0F, kConnectionConfirmExtraData); + EXPECT_EQ(packet.GetBytes(), expected); +} + +TEST(PacketTest, CreateConnectionConfirmPacketBadTest) { + EXPECT_FALSE(Packet::CreateConnectionConfirmPacket( + /*selected_protocol_version=*/1, /*selected_packet_size=*/15, + "> than fifteen..") + .ok()); +} + +TEST(PacketTest, CreateConnectionRequestGoodPacketTest) { + Packet packet = Packet::CreateConnectionRequestPacket( + 0x0001, 0x0001, 0x000F, kConnectionRequestExtraData) + .value(); + EXPECT_TRUE(packet.IsControlPacket()); + EXPECT_EQ(packet.GetControlCommandNumber(), + Packet::ControlPacketType::kControlConnectionRequest); + ASSERT_EQ(packet.GetPayload().size(), 19); + ASSERT_EQ(packet.GetBytes().size(), 20); + auto expected = absl::StrCat( + absl::StrFormat("%c%c%c%c%c%c%c%s", 0b10000000, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x0F, kConnectionRequestExtraData)); + EXPECT_EQ(packet.GetBytes(), expected); +} + +TEST(PacketTest, CreateConnectionRequestBigProtocolVersionPacketTest) { + ByteArray extraData = ByteArray(std::string(kExtraData)); + Packet packet = + Packet::CreateConnectionRequestPacket(0x1001, 0x1001, 0x000F, kExtraData) + .value(); + EXPECT_TRUE(packet.IsControlPacket()); + EXPECT_EQ(packet.GetControlCommandNumber(), + Packet::ControlPacketType::kControlConnectionRequest); + ASSERT_EQ(packet.GetPayload().size(), 9); + ASSERT_EQ(packet.GetBytes().size(), 10); + auto expected = + absl::StrCat(absl::StrFormat("%c%c%c%c%c%c%c%s", 0b10000000, 0x10, 0x01, + 0x10, 0x01, 0x00, 0x0F, kExtraData)); + EXPECT_EQ(packet.GetBytes(), expected); +} + +TEST(PacketTest, CreateConnectionRequestBadPacketTest) { + EXPECT_FALSE( + Packet::CreateConnectionRequestPacket(1, 1, 15, "> than 13 chr.").ok()); +} + +TEST(PacketTest, CreateDataPacketTest) { + Packet packet = + Packet::CreateDataPacket(false, false, ByteArray("big payload")); + ASSERT_EQ(packet.GetBytes().size(), 12); + EXPECT_TRUE(packet.IsDataPacket()); + EXPECT_FALSE(packet.IsFirstPacket()); + EXPECT_FALSE(packet.IsLastPacket()); + EXPECT_EQ(packet.GetPayload(), "big payload"); + EXPECT_EQ(packet.GetBytes().substr(1), "big payload"); + EXPECT_EQ(packet.GetPacketCounter(), 0); +} + +TEST(PacketTest, SetPacketCounterTest) { + Packet packet = Packet::CreateDataPacket(false, false, ByteArray("sample")); + EXPECT_OK(packet.SetPacketCounter(1)); + EXPECT_EQ(packet.GetPacketCounter(), 1); + EXPECT_THAT(packet.SetPacketCounter(Packet::kMaxPacketCounter + 1), + testing::status::StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(packet.SetPacketCounter(-1), + testing::status::StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(packet.SetPacketCounter(8), + testing::status::StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(PacketTest, StringifyTest) { + Packet packet = Packet::CreateDataPacket(false, false, ByteArray("sample")); + EXPECT_EQ(packet.ToString(), + absl::StrFormat("Packet[header: 0b%08d + payload: %d bytes]", + 0b00000000, 6)); +} + +TEST(PacketTest, CreateFirstPacketTest) { + Packet packet = Packet::CreateDataPacket( + /*is_first_packet=*/true, /*is_last_packet=*/false, ByteArray("sample")); + EXPECT_FALSE(packet.IsControlPacket()); + EXPECT_TRUE(packet.IsFirstPacket()); + EXPECT_FALSE(packet.IsLastPacket()); + EXPECT_EQ(packet.GetPayload(), "sample"); +} + +TEST(PacketTest, CreateLastPacketTest) { + Packet packet = Packet::CreateDataPacket(false, true, ByteArray("sample")); + EXPECT_FALSE(packet.IsControlPacket()); + EXPECT_FALSE(packet.IsFirstPacket()); + EXPECT_TRUE(packet.IsLastPacket()); + EXPECT_EQ(packet.GetPayload(), "sample"); +} + +} // namespace + +} // namespace weave +} // namespace nearby