blob: dc6c2a577eea9bf1e34cef65a9d9874f40da9a90 [file]
// 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 "pw_allocator/testing.h"
#include "pw_assert/check.h"
#include "pw_bytes/array.h"
#include "pw_bytes/span.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();
auto buf = pw::Buf::Allocate(allocator, total_size);
auto packet = internal::OutboundPacket::Message(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::kMessage);
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);
auto buf = pw::Buf::Allocate(allocator, total_size);
auto encode_result =
internal::OutboundPacket::StreamEnd(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(), internal::PacketType::kStreamEnd);
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, pw::Status::FailedPrecondition())
.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::kError);
EXPECT_EQ(decoded.call_id(), 0x12345678u);
EXPECT_EQ(decoded.status(), pw::Status::FailedPrecondition());
}
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{0x06}, 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::StreamEnd(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, pw::Status::FailedPrecondition())
.Encode(std::move(buf));
ASSERT_EQ(encode_result.status(), pw::OkStatus());
EXPECT_EQ(encode_result->size(), sizeof(internal::ErrorWireFormat));
}
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::Message(1).Encode(pw::Buf()).status(),
pw::Status::FailedPrecondition());
}
static_assert(
internal::PacketSizeWithoutPayload(internal::PacketType::kRequest) == 13u);
static_assert(
internal::PacketSizeWithoutPayload(internal::PacketType::kMessage) == 5u);
static_assert(
internal::PacketSizeWithoutPayload(internal::PacketType::kResponse) == 5u);
static_assert(
internal::PacketSizeWithoutPayload(internal::PacketType::kStreamEnd) == 5u);
static_assert(
internal::PacketSizeWithoutPayload(internal::PacketType::kError) == 9u);
static_assert(internal::OutboundPacket::Request(1, 2, 3).payload_offset() ==
13u);
static_assert(internal::OutboundPacket::Message(1).payload_offset() == 5u);
static_assert(internal::OutboundPacket::Response(1).payload_offset() == 5u);
static_assert(internal::OutboundPacket::StreamEnd(1).payload_offset() == 5u);
static_assert(internal::OutboundPacket::Error(1, pw::Status::Internal())
.payload_offset() == 9u);
TEST(PacketTest, EncodedMessageHeaderLayout) {
std::array<std::byte, sizeof(internal::MessageWireFormat)> buffer = {};
auto result =
internal::OutboundPacket::Message(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.
constexpr auto expected = pw::bytes::Array<0x78, 0x56, 0x34, 0x12, 0x02>();
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,
0x01,
0x01,
0xef,
0xcd,
0xab,
0x89,
0x67,
0x45,
0x23>();
EXPECT_EQ(buffer, expected);
}
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::Message(1).EncodeHeader(buffer, 5).status(),
pw::Status::ResourceExhausted());
EXPECT_EQ(
internal::OutboundPacket::Message(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::Message(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::kMessage);
EXPECT_EQ(decoded.call_id(), 42u);
EXPECT_EQ(decoded.payload().size(), kTrailingBytes);
}
} // namespace