diff options
Diffstat (limited to 'pw_rpc_transport/simple_framing_test.cc')
-rw-r--r-- | pw_rpc_transport/simple_framing_test.cc | 381 |
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 |