aboutsummaryrefslogtreecommitdiff
path: root/pw_rpc_transport/simple_framing_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'pw_rpc_transport/simple_framing_test.cc')
-rw-r--r--pw_rpc_transport/simple_framing_test.cc381
1 files changed, 381 insertions, 0 deletions
diff --git a/pw_rpc_transport/simple_framing_test.cc b/pw_rpc_transport/simple_framing_test.cc
new file mode 100644
index 000000000..2ac75a5b4
--- /dev/null
+++ b/pw_rpc_transport/simple_framing_test.cc
@@ -0,0 +1,381 @@
+// Copyright 2023 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_rpc_transport/simple_framing.h"
+
+#include <algorithm>
+#include <array>
+#include <random>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/span.h"
+#include "pw_log/log.h"
+#include "pw_status/status.h"
+
+namespace pw::rpc {
+namespace {
+
+constexpr size_t kMaxPacketSize = 256;
+
+struct TestParams {
+ size_t packet_size = 0;
+ size_t max_frame_size = 0;
+};
+
+constexpr std::array<TestParams, 8> kTestCases = {
+ // Packet fits in one frame.
+ TestParams{.packet_size = 5, .max_frame_size = 100},
+ // Typical parameters for RPC packet and mailbox frame size.
+ TestParams{.packet_size = 100, .max_frame_size = 128},
+ // Smallest packet.
+ TestParams{.packet_size = 1, .max_frame_size = 16},
+ // Small packet, small frame.
+ TestParams{.packet_size = 16, .max_frame_size = 5},
+ // Odd-sized packet, small frame.
+ TestParams{.packet_size = 77, .max_frame_size = 16},
+ // Frame size and packet size off by one.
+ TestParams{.packet_size = 11, .max_frame_size = 10},
+ // Almost at the limit.
+ TestParams{.packet_size = kMaxPacketSize - 1,
+ .max_frame_size = kMaxPacketSize - 2},
+ // At the limit.
+ TestParams{.packet_size = kMaxPacketSize,
+ .max_frame_size = kMaxPacketSize}};
+
+void MakePacket(ByteSpan dst_buffer) {
+ static uint32_t rg_seed = 0x123;
+ unsigned char c = 0;
+ for (auto& i : dst_buffer) {
+ i = std::byte{c++};
+ }
+ std::mt19937 rg(rg_seed++);
+ std::shuffle(dst_buffer.begin(), dst_buffer.end(), rg);
+}
+
+void CopyFrame(RpcFrame frame, std::vector<std::byte>& dst) {
+ std::copy(frame.header.begin(), frame.header.end(), std::back_inserter(dst));
+ std::copy(
+ frame.payload.begin(), frame.payload.end(), std::back_inserter(dst));
+}
+
+TEST(SimpleRpcFrameEncodeDecodeTest, EncodeThenDecode) {
+ for (auto test_case : kTestCases) {
+ const size_t packet_size = test_case.packet_size;
+ const size_t max_frame_size = test_case.max_frame_size;
+ PW_LOG_INFO("EncodeThenDecode: packet_size = %d, max_frame_size = %d",
+ static_cast<int>(packet_size),
+ static_cast<int>(max_frame_size));
+
+ std::vector<std::byte> src(packet_size);
+ MakePacket(src);
+
+ std::vector<std::byte> encoded;
+ std::vector<std::byte> decoded;
+
+ SimpleRpcPacketEncoder<kMaxPacketSize> encoder;
+
+ ASSERT_EQ(encoder.Encode(src,
+ max_frame_size,
+ [&encoded](RpcFrame& frame) {
+ CopyFrame(frame, encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ SimpleRpcPacketDecoder<kMaxPacketSize> decoder;
+
+ ASSERT_EQ(decoder.Decode(encoded,
+ [&decoded](ConstByteSpan packet) {
+ std::copy(packet.begin(),
+ packet.end(),
+ std::back_inserter(decoded));
+ }),
+ OkStatus());
+
+ EXPECT_TRUE(std::equal(src.begin(), src.end(), decoded.begin()));
+ }
+}
+
+TEST(SimpleRpcFrameEncodeDecodeTest, OneByteAtTimeDecoding) {
+ for (auto test_case : kTestCases) {
+ const size_t packet_size = test_case.packet_size;
+ const size_t max_frame_size = test_case.max_frame_size;
+ PW_LOG_INFO("EncodeThenDecode: packet_size = %d, max_frame_size = %d",
+ static_cast<int>(packet_size),
+ static_cast<int>(max_frame_size));
+
+ std::vector<std::byte> src(packet_size);
+ MakePacket(src);
+
+ std::vector<std::byte> encoded;
+ std::vector<std::byte> decoded;
+
+ SimpleRpcPacketEncoder<kMaxPacketSize> encoder;
+
+ ASSERT_EQ(encoder.Encode(src,
+ max_frame_size,
+ [&encoded](RpcFrame& frame) {
+ CopyFrame(frame, encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ SimpleRpcPacketDecoder<kMaxPacketSize> decoder;
+
+ for (std::byte b : encoded) {
+ auto buffer_span = span(&b, 1);
+ ASSERT_EQ(decoder.Decode(buffer_span,
+ [&decoded](ConstByteSpan packet) {
+ std::copy(packet.begin(),
+ packet.end(),
+ std::back_inserter(decoded));
+ }),
+ OkStatus());
+ }
+
+ EXPECT_TRUE(std::equal(src.begin(), src.end(), decoded.begin()));
+ }
+}
+
+TEST(SimpleRpcFrameTest, MissingFirstFrame) {
+ // Sends two packets, the first packet is missing its first frame. The decoder
+ // ignores the remaining frames of the first packet but still picks up the
+ // second packet.
+ constexpr size_t kPacketSize = 77;
+ constexpr size_t kMaxFrameSize = 16;
+
+ std::vector<std::byte> src1(kPacketSize);
+ MakePacket(src1);
+
+ std::vector<std::byte> src2(kPacketSize);
+ MakePacket(src2);
+
+ std::vector<std::byte> decoded;
+
+ SimpleRpcPacketEncoder<kMaxPacketSize> encoder;
+ struct EncodeState {
+ size_t frame_counter = 0;
+ std::vector<std::byte> encoded;
+ } state;
+
+ ASSERT_EQ(encoder.Encode(src1,
+ kMaxFrameSize,
+ [&state](RpcFrame& frame) {
+ state.frame_counter++;
+ if (state.frame_counter > 1) {
+ // Skip the first frame.
+ CopyFrame(frame, state.encoded);
+ }
+ return OkStatus();
+ }),
+ OkStatus());
+
+ ASSERT_EQ(encoder.Encode(src2,
+ kMaxFrameSize,
+ [&state](RpcFrame& frame) {
+ CopyFrame(frame, state.encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ SimpleRpcPacketDecoder<kMaxPacketSize> decoder;
+
+ ASSERT_EQ(decoder.Decode(state.encoded,
+ [&decoded](ConstByteSpan packet) {
+ std::copy(packet.begin(),
+ packet.end(),
+ std::back_inserter(decoded));
+ }),
+ OkStatus());
+
+ EXPECT_TRUE(std::equal(src2.begin(), src2.end(), decoded.begin()));
+}
+
+TEST(SimpleRpcFrameTest, MissingInternalFrame) {
+ // Sends two packets, the first packet is missing its second frame. The
+ // decoder ignores the remaining frames of the first packet and the second
+ // packet as well but eventually stumbles upon the frame header in the third
+ // packet and processes that packet.
+ constexpr size_t kPacketSize = 77;
+ constexpr size_t kMaxFrameSize = 16;
+
+ std::vector<std::byte> src1(kPacketSize);
+ MakePacket(src1);
+
+ std::vector<std::byte> src2(kPacketSize);
+ MakePacket(src2);
+
+ std::vector<std::byte> src3(kPacketSize);
+ MakePacket(src3);
+
+ std::vector<std::byte> decoded;
+
+ SimpleRpcPacketEncoder<kMaxPacketSize> encoder;
+ struct EncodeState {
+ size_t frame_counter = 0;
+ std::vector<std::byte> encoded;
+ } encode_state;
+
+ ASSERT_EQ(encoder.Encode(src1,
+ kMaxFrameSize,
+ [&encode_state](RpcFrame& frame) {
+ encode_state.frame_counter++;
+ if (encode_state.frame_counter != 2) {
+ // Skip the second frame.
+ CopyFrame(frame, encode_state.encoded);
+ }
+ return OkStatus();
+ }),
+ OkStatus());
+
+ ASSERT_EQ(encoder.Encode(src2,
+ kMaxFrameSize,
+ [&encode_state](RpcFrame& frame) {
+ CopyFrame(frame, encode_state.encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ ASSERT_EQ(encoder.Encode(src3,
+ kMaxFrameSize,
+ [&encode_state](RpcFrame& frame) {
+ CopyFrame(frame, encode_state.encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ SimpleRpcPacketDecoder<kMaxPacketSize> decoder;
+
+ // First packet is decoded but it doesn't have correct bytes, as one of its
+ // frames has never been received. Second packet is not received because its
+ // header has been consumed by the first packet. By that point the decoder
+ // knows that something is wrong and tries to recover as soon as it receives
+ // bytes that look as the valid header. So we eventually receive the third
+ // packet and it is correct.
+ struct DecodeState {
+ std::vector<std::byte> decoded1;
+ std::vector<std::byte> decoded2;
+ size_t packet_counter = 0;
+ } decode_state;
+
+ ASSERT_EQ(
+ decoder.Decode(encode_state.encoded,
+ [&decode_state](ConstByteSpan packet) {
+ decode_state.packet_counter++;
+ if (decode_state.packet_counter == 1) {
+ std::copy(packet.begin(),
+ packet.end(),
+ std::back_inserter(decode_state.decoded1));
+ }
+ if (decode_state.packet_counter == 2) {
+ std::copy(packet.begin(),
+ packet.end(),
+ std::back_inserter(decode_state.decoded2));
+ }
+ }),
+ OkStatus());
+
+ EXPECT_EQ(decode_state.packet_counter, 2ul);
+
+ EXPECT_EQ(decode_state.decoded1.size(), src1.size());
+ EXPECT_FALSE(
+ std::equal(src1.begin(), src1.end(), decode_state.decoded1.begin()));
+
+ EXPECT_TRUE(
+ std::equal(src3.begin(), src3.end(), decode_state.decoded2.begin()));
+}
+
+TEST(SimpleRpcPacketEncoder, PacketTooBig) {
+ SimpleRpcPacketEncoder<kMaxPacketSize> encoder;
+ constexpr size_t kMaxFrameSize = 100;
+ std::array<std::byte, kMaxPacketSize + 1> src{};
+
+ EXPECT_EQ(
+ encoder.Encode(src, kMaxFrameSize, [](RpcFrame&) { return OkStatus(); }),
+ Status::FailedPrecondition());
+}
+
+TEST(SimpleRpcPacketEncoder, MaxFrameSizeTooSmall) {
+ SimpleRpcPacketEncoder<kMaxPacketSize> encoder;
+ std::array<std::byte, kMaxPacketSize> src{};
+
+ EXPECT_EQ(encoder.Encode(
+ src, encoder.kHeaderSize, [](RpcFrame&) { return OkStatus(); }),
+ Status::FailedPrecondition());
+
+ EXPECT_EQ(
+ encoder.Encode(
+ src, encoder.kHeaderSize + 1, [](RpcFrame&) { return OkStatus(); }),
+ OkStatus());
+}
+
+TEST(SimpleRpcFrameTest, EncoderBufferLargerThanDecoderBuffer) {
+ constexpr size_t kLargePacketSize = 150;
+ constexpr size_t kSmallPacketSize = 120;
+ constexpr size_t kMaxFrameSize = 16;
+
+ // Decoder isn't able to receive the whole packet because it needs to be
+ // buffered but the internal buffer is too small; the packet is thus
+ // discarded. The second packet is received without issues as it's small
+ // enough to fit in the decoder buffer.
+ constexpr size_t kEncoderMaxPacketSize = 256;
+ constexpr size_t kDecoderMaxPacketSize = 128;
+
+ std::vector<std::byte> src1(kLargePacketSize);
+ MakePacket(src1);
+
+ std::vector<std::byte> src2(kSmallPacketSize);
+ MakePacket(src1);
+
+ std::vector<std::byte> encoded;
+ std::vector<std::byte> decoded;
+
+ SimpleRpcPacketEncoder<kEncoderMaxPacketSize> encoder;
+
+ ASSERT_EQ(encoder.Encode(src1,
+ kMaxFrameSize,
+ [&encoded](RpcFrame& frame) {
+ CopyFrame(frame, encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ ASSERT_EQ(encoder.Encode(src2,
+ kMaxFrameSize,
+ [&encoded](RpcFrame& frame) {
+ CopyFrame(frame, encoded);
+ return OkStatus();
+ }),
+ OkStatus());
+
+ SimpleRpcPacketDecoder<kDecoderMaxPacketSize> decoder;
+
+ // We have to decode piecemeal here because otherwise the decoder can just
+ // pluck the packet from `encoded` without internally buffering it.
+ for (std::byte b : encoded) {
+ auto buffer_span = span(&b, 1);
+ ASSERT_EQ(decoder.Decode(buffer_span,
+ [&decoded](ConstByteSpan packet) {
+ std::copy(packet.begin(),
+ packet.end(),
+ std::back_inserter(decoded));
+ }),
+ OkStatus());
+ }
+
+ EXPECT_TRUE(std::equal(src2.begin(), src2.end(), decoded.begin()));
+}
+
+} // namespace
+} // namespace pw::rpc