| // Copyright 2026 The Pigweed Authors |
| // |
| // 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 "pw_rpc2/internal/packet.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstddef> |
| #include <string_view> |
| #include <utility> |
| |
| #include "pw_allocator/testing.h" |
| #include "pw_assert/check.h" |
| #include "pw_bytes/array.h" |
| #include "pw_bytes/span.h" |
| #include "pw_enum/to_string.h" |
| #include "pw_enum/traits.h" |
| #include "pw_rpc2/internal/protocol_status.h" |
| #include "pw_status/status.h" |
| #include "pw_unit_test/framework.h" |
| |
| namespace { |
| |
| namespace internal = ::pw::rpc2::internal; |
| |
| std::string_view AsString(pw::ConstByteSpan bytes) { |
| return std::string_view(reinterpret_cast<const char*>(bytes.data()), |
| bytes.size()); |
| } |
| |
| TEST(PacketTest, EncodeDecodeRequest) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| constexpr std::string_view kPayload = "hello request"; |
| size_t total_size = sizeof(internal::RequestWireFormat) + kPayload.size(); |
| |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| |
| auto packet = |
| internal::OutboundPacket::Request(0x12345678, 0xabcdef01, 0x23456789); |
| pw::ConstByteSpan payload_bytes = pw::as_bytes(pw::span(kPayload)); |
| std::copy(payload_bytes.begin(), |
| payload_bytes.end(), |
| buf.data() + packet.payload_offset()); |
| |
| auto encode_result = packet.Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), internal::PacketType::kRequest); |
| EXPECT_EQ(decoded.payload_offset(), sizeof(internal::RequestWireFormat)); |
| EXPECT_EQ(decoded.call_id(), 0x12345678u); |
| EXPECT_EQ(decoded.service_id(), 0xabcdef01u); |
| EXPECT_EQ(decoded.method_id(), 0x23456789u); |
| EXPECT_EQ(decoded.payload().size(), kPayload.size()); |
| EXPECT_EQ(AsString(decoded.payload()), kPayload); |
| |
| pw::ConstBuf payload_buf = std::move(decoded).TakePayload(); |
| EXPECT_EQ(payload_buf.size(), kPayload.size()); |
| EXPECT_EQ(AsString(payload_buf), kPayload); |
| } |
| |
| TEST(PacketTest, EncodeDecodeMessage) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| constexpr std::string_view kPayload = "hello msg"; |
| size_t total_size = sizeof(internal::MessageWireFormat) + kPayload.size(); |
| |
| for (auto [sender, expected_type] : |
| {std::pair{internal::EndpointRole::kClient, |
| internal::PacketType::kClientMessage}, |
| std::pair{internal::EndpointRole::kServer, |
| internal::PacketType::kServerMessage}}) { |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| |
| auto packet = internal::OutboundPacket::Message(sender, 0x12345678); |
| pw::ConstByteSpan payload_bytes = pw::as_bytes(pw::span(kPayload)); |
| std::copy(payload_bytes.begin(), |
| payload_bytes.end(), |
| buf.data() + packet.payload_offset()); |
| |
| auto encode_result = packet.Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), expected_type); |
| EXPECT_EQ(decoded.payload_offset(), sizeof(internal::MessageWireFormat)); |
| EXPECT_EQ(decoded.call_id(), 0x12345678u); |
| EXPECT_EQ(decoded.payload().size(), kPayload.size()); |
| EXPECT_EQ(AsString(decoded.payload()), kPayload); |
| |
| pw::ConstBuf payload_buf = std::move(decoded).TakePayload(); |
| EXPECT_EQ(payload_buf.size(), kPayload.size()); |
| EXPECT_EQ(AsString(payload_buf), kPayload); |
| } |
| } |
| |
| TEST(PacketTest, EncodeDecodeStreamEnd) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| size_t total_size = sizeof(internal::StreamEndWireFormat); |
| |
| for (auto [sender, expected_type] : |
| {std::pair{internal::EndpointRole::kClient, |
| internal::PacketType::kClientStreamEnd}, |
| std::pair{internal::EndpointRole::kServer, |
| internal::PacketType::kServerStreamEnd}}) { |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| |
| auto encode_result = internal::OutboundPacket::StreamEnd(sender, 0x12345678) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), expected_type); |
| EXPECT_EQ(decoded.call_id(), 0x12345678u); |
| } |
| } |
| |
| TEST(PacketTest, EncodeDecodeError) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| size_t total_size = sizeof(internal::ErrorWireFormat); |
| |
| { |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| auto encode_result = internal::OutboundPacket::Error( |
| 0x12345678, internal::ClientError::kCancelled) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), internal::PacketType::kClientError); |
| EXPECT_TRUE(internal::IsError(decoded.type())); |
| EXPECT_EQ(decoded.call_id(), 0x12345678u); |
| EXPECT_EQ(decoded.client_error(), internal::ClientError::kCancelled); |
| } |
| |
| { |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| auto encode_result = internal::OutboundPacket::Error( |
| 0x12345678, internal::ServerError::kUnknownMethod) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), internal::PacketType::kServerError); |
| EXPECT_TRUE(internal::IsError(decoded.type())); |
| EXPECT_EQ(decoded.call_id(), 0x12345678u); |
| EXPECT_EQ(decoded.server_error(), internal::ServerError::kUnknownMethod); |
| } |
| } |
| |
| TEST(PacketTest, DecodeBufferTooShortForHeader) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| auto buf = pw::Buf::Allocate(allocator, sizeof(internal::PacketHeader) - 1); |
| EXPECT_EQ( |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(buf))).status(), |
| pw::Status::DataLoss()); |
| } |
| |
| TEST(PacketTest, DecodeBufferTooShortForType) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| // Long enough for the common header, but not for a request header. |
| auto buf = pw::Buf::Allocate(allocator, sizeof(internal::PacketHeader)); |
| ASSERT_EQ(internal::OutboundPacket::Request(1, 2, 3) |
| .EncodeHeader(pw::ByteSpan(buf)) |
| .status(), |
| pw::Status::ResourceExhausted()); |
| // Write just the type byte by hand, since the header does not fit. |
| buf[offsetof(internal::PacketHeader, type)] = |
| static_cast<std::byte>(internal::PacketType::kRequest); |
| |
| EXPECT_EQ( |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(buf))).status(), |
| pw::Status::DataLoss()); |
| } |
| |
| TEST(PacketTest, DecodeUnrecognizedType) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| for (std::byte invalid_type : {std::byte{0x00}, |
| std::byte{0x01}, |
| std::byte{0x0a}, |
| std::byte{0x7f}, |
| std::byte{0xff}}) { |
| auto buf = pw::Buf::Allocate(allocator, 32); |
| buf[offsetof(internal::PacketHeader, type)] = invalid_type; |
| |
| EXPECT_EQ( |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(buf))).status(), |
| pw::Status::InvalidArgument()); |
| } |
| } |
| |
| TEST(PacketTest, EncodeDecodeHandshakePacket) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| size_t total_size = internal::HandshakePacket::kWireSizeBytes; |
| EXPECT_EQ(total_size, 8u); |
| |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| |
| internal::HandshakePacket packet(internal::HandshakePacket::Type::kSyn); |
| |
| auto encode_result = packet.Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = internal::HandshakePacket::Decode(encoded_buf); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::HandshakePacket decoded = decode_result.value(); |
| EXPECT_EQ(decoded.version(), 1u); |
| EXPECT_EQ(decoded.type(), internal::HandshakePacket::Type::kSyn); |
| } |
| |
| TEST(PacketTest, DecodeZeroLengthPayload) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| size_t total_size = sizeof(internal::RequestWireFormat); |
| |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| |
| auto encode_result = |
| internal::OutboundPacket::Request(0x12345678, 0x100, 0x200) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), internal::PacketType::kRequest); |
| EXPECT_EQ(decoded.payload().size(), 0u); |
| |
| pw::ConstBuf payload_buf = std::move(decoded).TakePayload(); |
| EXPECT_TRUE(payload_buf.empty()); |
| } |
| |
| TEST(PacketTest, DecodeCorruptHandshakePacket) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| auto buf = pw::Buf::Allocate(allocator, 4); |
| EXPECT_EQ(internal::HandshakePacket::Decode(buf).status(), |
| pw::Status::DataLoss()); |
| } |
| |
| TEST(PacketTest, EncodeDecodeResponse) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| constexpr std::string_view kPayload = "hello response"; |
| size_t total_size = sizeof(internal::ResponseWireFormat) + kPayload.size(); |
| |
| auto buf = pw::Buf::Allocate(allocator, total_size); |
| |
| auto packet = internal::OutboundPacket::Response(0x12345678); |
| pw::ConstByteSpan payload_bytes = pw::as_bytes(pw::span(kPayload)); |
| std::copy(payload_bytes.begin(), |
| payload_bytes.end(), |
| buf.data() + packet.payload_offset()); |
| |
| auto encode_result = packet.Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| pw::Buf encoded_buf = std::move(encode_result.value()); |
| EXPECT_EQ(encoded_buf.size(), total_size); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(encoded_buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), internal::PacketType::kResponse); |
| EXPECT_EQ(decoded.payload_offset(), sizeof(internal::ResponseWireFormat)); |
| EXPECT_EQ(decoded.call_id(), 0x12345678u); |
| EXPECT_EQ(decoded.payload().size(), kPayload.size()); |
| EXPECT_EQ(AsString(decoded.payload()), kPayload); |
| |
| pw::ConstBuf payload_buf = std::move(decoded).TakePayload(); |
| EXPECT_EQ(payload_buf.size(), kPayload.size()); |
| EXPECT_EQ(AsString(payload_buf), kPayload); |
| } |
| |
| TEST(PacketTest, EncodeStreamEndTruncatesExtraBuffer) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| auto buf = pw::Buf::Allocate(allocator, 64); |
| auto encode_result = internal::OutboundPacket::ServerStreamEnd(0x12345678) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| EXPECT_EQ(encode_result->size(), sizeof(internal::StreamEndWireFormat)); |
| } |
| |
| TEST(PacketTest, EncodeErrorTruncatesExtraBuffer) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| auto buf = pw::Buf::Allocate(allocator, 64); |
| auto encode_result = internal::OutboundPacket::Error( |
| 0x12345678, internal::ServerError::kUnknownMethod) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| EXPECT_EQ(encode_result->size(), sizeof(internal::ErrorWireFormat)); |
| } |
| |
| TEST(PacketTest, EncodeHandshakeTruncatesExtraBuffer) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| auto buf = pw::Buf::Allocate(allocator, 64); |
| auto encode_result = |
| internal::HandshakePacket(internal::HandshakePacket::Type::kSyn) |
| .Encode(std::move(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| EXPECT_EQ(encode_result->size(), internal::HandshakePacket::kWireSizeBytes); |
| |
| auto decode_result = internal::HandshakePacket::Decode(*encode_result); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| EXPECT_EQ(decode_result->type(), internal::HandshakePacket::Type::kSyn); |
| } |
| |
| TEST(PacketTest, EncodeRejectsBufferSmallerThanHeader) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| auto buf = |
| pw::Buf::Allocate(allocator, sizeof(internal::RequestWireFormat) - 1); |
| EXPECT_EQ(internal::OutboundPacket::Request(1, 2, 3) |
| .Encode(std::move(buf)) |
| .status(), |
| pw::Status::ResourceExhausted()); |
| } |
| |
| TEST(PacketTest, EncodeRejectsEmptyBuffer) { |
| EXPECT_EQ( |
| internal::OutboundPacket::ClientMessage(1).Encode(pw::Buf()).status(), |
| pw::Status::FailedPrecondition()); |
| } |
| |
| TEST(PacketTest, ServerErrorToStatusAndToString) { |
| using internal::ServerError; |
| |
| EXPECT_EQ(ToStatus(ServerError::kUnknown), pw::Status::Unknown()); |
| EXPECT_EQ(ToStatus(ServerError::kInternal), pw::Status::Internal()); |
| EXPECT_EQ(ToStatus(ServerError::kCancelled), pw::Status::Cancelled()); |
| EXPECT_EQ(ToStatus(ServerError::kReceivedPacketForClient), |
| pw::Status::Unimplemented()); |
| EXPECT_EQ(ToStatus(ServerError::kDroppedWithoutResponse), |
| pw::Status::Cancelled()); |
| EXPECT_EQ(ToStatus(ServerError::kServiceUnregistered), |
| pw::Status::Cancelled()); |
| EXPECT_EQ(ToStatus(ServerError::kUnknownService), pw::Status::NotFound()); |
| EXPECT_EQ(ToStatus(ServerError::kUnknownMethod), pw::Status::NotFound()); |
| EXPECT_EQ(ToStatus(ServerError::kInvalidRequestPayload), |
| pw::Status::DataLoss()); |
| EXPECT_EQ(ToStatus(ServerError::kFailedToAllocateCall), |
| pw::Status::ResourceExhausted()); |
| EXPECT_EQ(ToStatus(ServerError::kFailedToAllocateCallResourcesWhileRunning), |
| pw::Status::ResourceExhausted()); |
| EXPECT_EQ(ToStatus(ServerError::kOk), pw::Status::Internal()); |
| EXPECT_EQ(ToStatus(static_cast<ServerError>(0xff)), pw::Status::Unknown()); |
| |
| EXPECT_STREQ(pw::EnumToString(ServerError::kOk), "OK"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kUnknown), "UNKNOWN"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kInternal), "INTERNAL"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kCancelled), "CANCELLED"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kReceivedPacketForClient), |
| "RECEIVED_PACKET_FOR_CLIENT"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kDroppedWithoutResponse), |
| "DROPPED_WITHOUT_RESPONSE"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kServiceUnregistered), |
| "SERVICE_UNREGISTERED"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kUnknownService), |
| "UNKNOWN_SERVICE"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kUnknownMethod), "UNKNOWN_METHOD"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kInvalidRequestPayload), |
| "INVALID_REQUEST_PAYLOAD"); |
| EXPECT_STREQ(pw::EnumToString(ServerError::kFailedToAllocateCall), |
| "FAILED_TO_ALLOCATE_CALL"); |
| EXPECT_STREQ( |
| pw::EnumToString(ServerError::kFailedToAllocateCallResourcesWhileRunning), |
| "FAILED_TO_ALLOCATE_CALL_RESOURCES_WHILE_RUNNING"); |
| } |
| |
| TEST(PacketTest, ClientErrorToStatusAndToString) { |
| using internal::ClientError; |
| |
| EXPECT_EQ(ToStatus(ClientError::kUnknown), pw::Status::Unknown()); |
| EXPECT_EQ(ToStatus(ClientError::kInternal), pw::Status::Internal()); |
| EXPECT_EQ(ToStatus(ClientError::kCancelled), pw::Status::Cancelled()); |
| EXPECT_EQ(ToStatus(ClientError::kReceivedPacketForServer), |
| pw::Status::Unimplemented()); |
| EXPECT_EQ(ToStatus(ClientError::kOk), pw::Status::Internal()); |
| EXPECT_EQ(ToStatus(static_cast<ClientError>(0xff)), pw::Status::Unknown()); |
| |
| EXPECT_STREQ(pw::EnumToString(ClientError::kOk), "OK"); |
| EXPECT_STREQ(pw::EnumToString(ClientError::kUnknown), "UNKNOWN"); |
| EXPECT_STREQ(pw::EnumToString(ClientError::kInternal), "INTERNAL"); |
| EXPECT_STREQ(pw::EnumToString(ClientError::kCancelled), "CANCELLED"); |
| EXPECT_STREQ(pw::EnumToString(ClientError::kReceivedPacketForServer), |
| "RECEIVED_PACKET_FOR_SERVER"); |
| } |
| |
| // Codes with equivalent meanings share the same value. |
| static_assert(static_cast<uint8_t>(internal::ServerError::kOk) == |
| static_cast<uint8_t>(internal::ClientError::kOk)); |
| static_assert(static_cast<uint8_t>(internal::ServerError::kUnknown) == |
| static_cast<uint8_t>(internal::ClientError::kUnknown)); |
| static_assert(static_cast<uint8_t>(internal::ServerError::kInternal) == |
| static_cast<uint8_t>(internal::ClientError::kInternal)); |
| static_assert(static_cast<uint8_t>(internal::ServerError::kCancelled) == |
| static_cast<uint8_t>(internal::ClientError::kCancelled)); |
| static_assert( |
| static_cast<uint8_t>(internal::ServerError::kReceivedPacketForClient) == |
| static_cast<uint8_t>(internal::ClientError::kReceivedPacketForServer)); |
| |
| static_assert( |
| internal::PacketSizeWithoutPayload(internal::PacketType::kRequest) == 13u); |
| static_assert( |
| internal::PacketSizeWithoutPayload(internal::PacketType::kResponse) == 5u); |
| static_assert(internal::PacketSizeWithoutPayload( |
| internal::PacketType::kClientMessage) == 5u); |
| static_assert(internal::PacketSizeWithoutPayload( |
| internal::PacketType::kServerMessage) == 5u); |
| static_assert(internal::PacketSizeWithoutPayload( |
| internal::PacketType::kClientStreamEnd) == 5u); |
| static_assert(internal::PacketSizeWithoutPayload( |
| internal::PacketType::kServerStreamEnd) == 5u); |
| static_assert(internal::PacketSizeWithoutPayload( |
| internal::PacketType::kClientError) == 7u); |
| static_assert(internal::PacketSizeWithoutPayload( |
| internal::PacketType::kServerError) == 7u); |
| |
| static_assert(internal::OutboundPacket::Request(1, 2, 3).payload_offset() == |
| 13u); |
| static_assert(internal::OutboundPacket::Response(1).payload_offset() == 5u); |
| static_assert(internal::OutboundPacket::ClientMessage(1).payload_offset() == |
| 5u); |
| static_assert(internal::OutboundPacket::ServerMessage(1).payload_offset() == |
| 5u); |
| static_assert(internal::OutboundPacket::ClientStreamEnd(1).payload_offset() == |
| 5u); |
| static_assert(internal::OutboundPacket::ServerStreamEnd(1).payload_offset() == |
| 5u); |
| static_assert(internal::OutboundPacket::Error(1, |
| internal::ClientError::kCancelled) |
| .payload_offset() == 7u); |
| static_assert(internal::OutboundPacket::Error(1, |
| internal::ServerError::kCancelled) |
| .payload_offset() == 7u); |
| |
| static_assert(internal::IsError(internal::PacketType::kClientError)); |
| static_assert(internal::IsError(internal::PacketType::kServerError)); |
| static_assert(!internal::IsError(internal::PacketType::kClientStreamEnd)); |
| static_assert(!internal::IsError(internal::PacketType::kServerStreamEnd)); |
| static_assert(!internal::IsError(static_cast<internal::PacketType>(0x0a))); |
| static_assert(!internal::IsError(static_cast<internal::PacketType>(0x0b))); |
| |
| // True if `type` is addressed to `to` and not to the other endpoint. |
| constexpr bool OnlyFor(internal::PacketType type, internal::EndpointRole to) { |
| const auto other = to == internal::EndpointRole::kServer |
| ? internal::EndpointRole::kClient |
| : internal::EndpointRole::kServer; |
| return internal::IsPacketFor(type, to) && !internal::IsPacketFor(type, other); |
| } |
| |
| // Every valid type is addressed to exactly one endpoint. |
| static_assert(OnlyFor(internal::PacketType::kRequest, |
| internal::EndpointRole::kServer)); |
| static_assert(OnlyFor(internal::PacketType::kResponse, |
| internal::EndpointRole::kClient)); |
| static_assert(OnlyFor(internal::PacketType::kClientMessage, |
| internal::EndpointRole::kServer)); |
| static_assert(OnlyFor(internal::PacketType::kServerMessage, |
| internal::EndpointRole::kClient)); |
| static_assert(OnlyFor(internal::PacketType::kClientStreamEnd, |
| internal::EndpointRole::kServer)); |
| static_assert(OnlyFor(internal::PacketType::kServerStreamEnd, |
| internal::EndpointRole::kClient)); |
| static_assert(OnlyFor(internal::PacketType::kClientError, |
| internal::EndpointRole::kServer)); |
| static_assert(OnlyFor(internal::PacketType::kServerError, |
| internal::EndpointRole::kClient)); |
| |
| // Unrecognized type bytes are rejected even when their direction bit matches |
| // the destination. |
| static_assert(!internal::IsPacketFor(static_cast<internal::PacketType>(0x00), |
| internal::EndpointRole::kServer)); |
| static_assert(!internal::IsPacketFor(static_cast<internal::PacketType>(0x01), |
| internal::EndpointRole::kClient)); |
| static_assert(!internal::IsPacketFor(static_cast<internal::PacketType>(0x0a), |
| internal::EndpointRole::kServer)); |
| static_assert(!internal::IsPacketFor(static_cast<internal::PacketType>(0x0b), |
| internal::EndpointRole::kClient)); |
| static_assert(!internal::IsPacketFor(static_cast<internal::PacketType>(0xff), |
| internal::EndpointRole::kClient)); |
| |
| TEST(PacketTest, EncodedMessageHeaderLayout) { |
| std::array<std::byte, sizeof(internal::MessageWireFormat)> buffer = {}; |
| |
| auto result = |
| internal::OutboundPacket::ClientMessage(0x12345678).EncodeHeader(buffer); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| EXPECT_EQ(*result, buffer.size()); |
| |
| // call_id is little endian and comes first, followed by the type byte (0x04). |
| constexpr auto expected = pw::bytes::Array<0x78, 0x56, 0x34, 0x12, 0x04>(); |
| EXPECT_EQ(buffer, expected); |
| } |
| |
| TEST(PacketTest, EncodedRequestHeaderLayout) { |
| std::array<std::byte, sizeof(internal::RequestWireFormat)> buffer = {}; |
| |
| auto result = |
| internal::OutboundPacket::Request(0x12345678, 0xabcdef01, 0x23456789) |
| .EncodeHeader(buffer); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| EXPECT_EQ(*result, buffer.size()); |
| |
| constexpr auto expected = pw::bytes::Array<0x78, |
| 0x56, |
| 0x34, |
| 0x12, |
| 0x02, |
| 0x01, |
| 0xef, |
| 0xcd, |
| 0xab, |
| 0x89, |
| 0x67, |
| 0x45, |
| 0x23>(); |
| EXPECT_EQ(buffer, expected); |
| } |
| |
| TEST(PacketTest, EncodedErrorHeaderLayout) { |
| std::array<std::byte, sizeof(internal::ErrorWireFormat)> buffer = {}; |
| |
| auto result = internal::OutboundPacket::Error( |
| 0x12345678, internal::ServerError::kUnknownMethod) |
| .EncodeHeader(buffer); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| EXPECT_EQ(*result, buffer.size()); |
| |
| // 4-byte call_id (LE), 1-byte type (0x09), 2-byte error (LE). |
| constexpr auto expected = |
| pw::bytes::Array<0x78, |
| 0x56, |
| 0x34, |
| 0x12, |
| 0x09, |
| static_cast<uint8_t>( |
| internal::ServerError::kUnknownMethod), |
| 0x00>(); |
| EXPECT_EQ(buffer, expected); |
| |
| auto decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->server_error(), internal::ServerError::kUnknownMethod); |
| } |
| |
| TEST(PacketTest, DecodeErrorZeroMapsToInternal) { |
| // A wire error of 0 (kOk) is never valid in an error packet. |
| auto buffer = pw::bytes::Array<0x78, 0x56, 0x34, 0x12, 0x09, 0x00, 0x00>(); |
| |
| auto decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->server_error(), internal::ServerError::kInternal); |
| EXPECT_EQ(internal::ToStatus(decoded->server_error()), |
| pw::Status::Internal()); |
| |
| buffer[4] = static_cast<std::byte>(internal::PacketType::kClientError); |
| decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->client_error(), internal::ClientError::kInternal); |
| EXPECT_EQ(internal::ToStatus(decoded->client_error()), |
| pw::Status::Internal()); |
| } |
| |
| TEST(PacketTest, DecodeErrorOutOfRangeMapsToUnknown) { |
| auto buffer = pw::bytes::Array<0x78, 0x56, 0x34, 0x12, 0x09, 0x00, 0x01>(); |
| |
| // 0x0100 is beyond the largest known code. |
| auto decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->server_error(), internal::ServerError::kUnknown); |
| EXPECT_EQ(internal::ToStatus(decoded->server_error()), pw::Status::Unknown()); |
| |
| buffer[4] = static_cast<std::byte>(internal::PacketType::kClientError); |
| decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->client_error(), internal::ClientError::kUnknown); |
| EXPECT_EQ(internal::ToStatus(decoded->client_error()), pw::Status::Unknown()); |
| |
| // One past the maximum. |
| buffer[4] = static_cast<std::byte>(internal::PacketType::kServerError); |
| buffer[5] = static_cast<std::byte>( |
| static_cast<uint8_t>(pw::EnumTraits<internal::ServerError>::kMax) + 1); |
| buffer[6] = std::byte{0x00}; |
| decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->server_error(), internal::ServerError::kUnknown); |
| |
| buffer[4] = static_cast<std::byte>(internal::PacketType::kClientError); |
| buffer[5] = static_cast<std::byte>( |
| static_cast<uint8_t>(pw::EnumTraits<internal::ClientError>::kMax) + 1); |
| decoded = internal::InboundPacket::Decode(pw::ConstBuf::Unowned(buffer)); |
| ASSERT_EQ(decoded.status(), pw::OkStatus()); |
| EXPECT_EQ(decoded->client_error(), internal::ClientError::kUnknown); |
| } |
| |
| TEST(PacketTest, EncodeHeaderReportsTotalSizeWithoutWritingPayloadLength) { |
| constexpr auto buffer_init = [](size_t i) { |
| return static_cast<std::byte>(i + 1); |
| }; |
| auto buffer = pw::bytes::Initialized<64>(buffer_init); |
| |
| auto result = internal::OutboundPacket::Response(1).EncodeHeader(buffer, 10); |
| ASSERT_EQ(result.status(), pw::OkStatus()); |
| EXPECT_EQ(*result, sizeof(internal::ResponseWireFormat) + 10u); |
| |
| // Nothing past the header was modified. |
| for (size_t i = sizeof(internal::ResponseWireFormat); i < buffer.size(); |
| ++i) { |
| EXPECT_EQ(buffer[i], buffer_init(i)); |
| } |
| } |
| |
| TEST(PacketTest, EncodeHeaderRejectsBufferSmallerThanHeaderPlusPayload) { |
| std::array<std::byte, sizeof(internal::MessageWireFormat) + 4> buffer = {}; |
| |
| EXPECT_EQ(internal::OutboundPacket::ClientMessage(1) |
| .EncodeHeader(buffer, 5) |
| .status(), |
| pw::Status::ResourceExhausted()); |
| EXPECT_EQ(internal::OutboundPacket::ClientMessage(1) |
| .EncodeHeader(buffer, 4) |
| .status(), |
| pw::OkStatus()); |
| } |
| |
| TEST(PacketTest, DecodedPayloadIsEverythingAfterTheHeader) { |
| pw::allocator::test::AllocatorForTest<256> allocator; |
| |
| // The payload length is not on the wire, so all trailing bytes belong to the |
| // payload regardless of what was passed to EncodeHeader. |
| constexpr size_t kTrailingBytes = 7; |
| auto buf = pw::Buf::Allocate( |
| allocator, sizeof(internal::MessageWireFormat) + kTrailingBytes); |
| |
| auto encode_result = internal::OutboundPacket::ClientMessage(42).EncodeHeader( |
| pw::ByteSpan(buf)); |
| ASSERT_EQ(encode_result.status(), pw::OkStatus()); |
| ASSERT_EQ(*encode_result, sizeof(internal::MessageWireFormat)); |
| |
| auto decode_result = |
| internal::InboundPacket::Decode(pw::ConstBuf(std::move(buf))); |
| ASSERT_EQ(decode_result.status(), pw::OkStatus()); |
| |
| internal::InboundPacket decoded = std::move(decode_result.value()); |
| EXPECT_EQ(decoded.type(), internal::PacketType::kClientMessage); |
| EXPECT_EQ(decoded.call_id(), 42u); |
| EXPECT_EQ(decoded.payload().size(), kTrailingBytes); |
| } |
| |
| } // namespace |