// Copyright 2022 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_hdlc/encoded_size.h" #include #include #include #include "gtest/gtest.h" #include "pw_bytes/array.h" #include "pw_hdlc/decoder.h" #include "pw_hdlc/encoder.h" #include "pw_hdlc/internal/encoder.h" #include "pw_result/result.h" #include "pw_stream/memory_stream.h" #include "pw_varint/varint.h" namespace pw::hdlc { namespace { // The varint-encoded address that represents the value that will result in the // largest on-the-wire address after HDLC escaping. constexpr auto kWidestVarintAddress = bytes::String("\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x03"); // This is the decoded varint value of kWidestVarintAddress. This is // pre-calculated as a constant to simplify tests. constexpr uint64_t kWidestAddress = 0xbf7efdfbf7efdfbf; // UI frames created by WriteUIFrame() will never be have an escaped control // field, but it's technically possible for other HDLC frame types to produce // control bytes that would need to be escaped. constexpr size_t kEscapedControlCost = kControlSize; // UI frames created by WriteUIFrame() will never have an escaped control // field, but it's technically possible for other HDLC frame types to produce // control bytes that would need to be escaped. constexpr size_t kEscapedFcsCost = kMaxEscapedFcsSize - kFcsSize; // Due to API limitations, the worst case buffer calculations used by the HDLC // encoder/decoder can't be fully saturated. This constexpr value accounts for // this by expressing the delta between the constant largest testable HDLC frame // and the calculated worst-case-scenario. constexpr size_t kTestLimitationsOverhead = kEscapedControlCost + kEscapedFcsCost; // A payload only containing bytes that need to be escaped. constexpr auto kFullyEscapedPayload = bytes::String("\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e"); constexpr uint8_t kEscapeAddress = static_cast(kFlag); constexpr uint8_t kNoEscapeAddress = 6; TEST(EncodedSize, Constants_WidestAddress) { uint64_t address = 0; size_t address_size = varint::Decode(kWidestVarintAddress, &address, kAddressFormat); EXPECT_EQ(address_size, 10u); EXPECT_EQ(address_size, kMaxAddressSize); EXPECT_EQ(kMaxEscapedVarintAddressSize, 19u); EXPECT_EQ(EscapedSize(kWidestVarintAddress), kMaxEscapedVarintAddressSize); EXPECT_EQ(address, kWidestAddress); EXPECT_EQ(varint::EncodedSize(kWidestAddress), 10u); } TEST(EncodedSize, EscapedSize_AllEscapeBytes) { EXPECT_EQ(EscapedSize(kFullyEscapedPayload), kFullyEscapedPayload.size() * 2); } TEST(EncodedSize, EscapedSize_NoEscapeBytes) { constexpr auto kData = bytes::String("\x01\x23\x45\x67\x89\xab\xcd\xef"); EXPECT_EQ(EscapedSize(kData), kData.size()); } TEST(EncodedSize, EscapedSize_SomeEscapeBytes) { constexpr auto kData = bytes::String("\x7epabu\x7d"); EXPECT_EQ(EscapedSize(kData), kData.size() + 2); } TEST(EncodedSize, EscapedSize_Address) { EXPECT_EQ(EscapedSize(kWidestVarintAddress), varint::EncodedSize(kWidestAddress) * 2 - 1); } TEST(EncodedSize, MaxEncodedSize_Overload) { EXPECT_EQ(MaxEncodedFrameSize(kFullyEscapedPayload.size()), MaxEncodedFrameSize(kWidestAddress, kFullyEscapedPayload)); } TEST(EncodedSize, MaxEncodedSize_EmptyPayload) { EXPECT_EQ(14u, MaxEncodedFrameSize(kNoEscapeAddress, {})); EXPECT_EQ(14u, MaxEncodedFrameSize(kEscapeAddress, {})); } TEST(EncodedSize, MaxEncodedSize_PayloadWithoutEscapes) { constexpr auto data = bytes::Array<0x00, 0x01, 0x02, 0x03>(); EXPECT_EQ(18u, MaxEncodedFrameSize(kNoEscapeAddress, data)); EXPECT_EQ(18u, MaxEncodedFrameSize(kEscapeAddress, data)); } TEST(EncodedSize, MaxEncodedSize_PayloadWithOneEscape) { constexpr auto data = bytes::Array<0x00, 0x01, 0x7e, 0x03>(); EXPECT_EQ(19u, MaxEncodedFrameSize(kNoEscapeAddress, data)); EXPECT_EQ(19u, MaxEncodedFrameSize(kEscapeAddress, data)); } TEST(EncodedSize, MaxEncodedSize_PayloadWithAllEscapes) { constexpr auto data = bytes::Initialized<8>(0x7e); EXPECT_EQ(30u, MaxEncodedFrameSize(kNoEscapeAddress, data)); EXPECT_EQ(30u, MaxEncodedFrameSize(kEscapeAddress, data)); } TEST(EncodedSize, MaxPayload_UndersizedFrame) { EXPECT_EQ(MaxSafePayloadSize(4), 0u); } TEST(EncodedSize, MaxPayload_SmallFrame) { EXPECT_EQ(MaxSafePayloadSize(128), 48u); } TEST(EncodedSize, MaxPayload_MediumFrame) { EXPECT_EQ(MaxSafePayloadSize(512), 240u); } TEST(EncodedSize, FrameToPayloadInversion_Odd) { static constexpr size_t kIntendedPayloadSize = 1234567891; EXPECT_EQ(MaxSafePayloadSize(MaxEncodedFrameSize(kIntendedPayloadSize)), kIntendedPayloadSize); } TEST(EncodedSize, PayloadToFrameInversion_Odd) { static constexpr size_t kIntendedFrameSize = 1234567891; EXPECT_EQ(MaxEncodedFrameSize(MaxSafePayloadSize(kIntendedFrameSize)), kIntendedFrameSize); } TEST(EncodedSize, FrameToPayloadInversion_Even) { static constexpr size_t kIntendedPayloadSize = 42; EXPECT_EQ(MaxSafePayloadSize(MaxEncodedFrameSize(kIntendedPayloadSize)), kIntendedPayloadSize); } TEST(EncodedSize, PayloadToFrameInversion_Even) { static constexpr size_t kIntendedFrameSize = 42; // Because of HDLC encoding overhead requirements, the last byte of the // intended frame size is wasted because it doesn't allow sufficient space for // another byte since said additional byte could require escaping, therefore // requiring a second byte to increase the safe payload size by one. const size_t max_frame_usage = MaxEncodedFrameSize(MaxSafePayloadSize(kIntendedFrameSize)); EXPECT_EQ(max_frame_usage, kIntendedFrameSize - 1); // There's no further change if the inversion is done again since the frame // size is aligned to the reduced bounds. EXPECT_EQ(MaxEncodedFrameSize(MaxSafePayloadSize(max_frame_usage)), kIntendedFrameSize - 1); } TEST(EncodedSize, MostlyEscaped) { constexpr auto kMostlyEscapedPayload = bytes::String(":)\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e"); constexpr size_t kUnescapedBytes = 2 * kMostlyEscapedPayload.size() - EscapedSize(kMostlyEscapedPayload); // Subtracting 2 should still leave enough space since two bytes won't need // to be escaped. constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kMostlyEscapedPayload.size()) - kUnescapedBytes; std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); EXPECT_EQ(kUnescapedBytes, 2u); EXPECT_EQ(OkStatus(), WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer)); EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead - kUnescapedBytes); } TEST(EncodedSize, BigAddress_SaturatedPayload) { constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kFullyEscapedPayload.size()); std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); EXPECT_EQ(OkStatus(), WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer)); EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead); } TEST(EncodedSize, BigAddress_OffByOne) { constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kFullyEscapedPayload.size()) - 1; std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); EXPECT_EQ(Status::ResourceExhausted(), WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer)); } TEST(EncodedSize, SmallAddress_SaturatedPayload) { constexpr auto kSmallerEscapedAddress = bytes::String("\x7e\x7d"); // varint::Decode() is not constexpr, so this is a hard-coded and then runtime // validated. constexpr size_t kVarintDecodedAddress = 7999; constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kVarintDecodedAddress, kFullyEscapedPayload); std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); uint64_t address = 0; size_t address_size = varint::Decode(kSmallerEscapedAddress, &address, kAddressFormat); EXPECT_EQ(address, kVarintDecodedAddress); EXPECT_EQ(address_size, 2u); EXPECT_EQ(OkStatus(), WriteUIFrame(address, kFullyEscapedPayload, writer)); EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead); } TEST(EncodedSize, SmallAddress_OffByOne) { constexpr auto kSmallerEscapedAddress = bytes::String("\x7e\x7d"); // varint::Decode() is not constexpr, so this is a hard-coded and then runtime // validated. constexpr size_t kVarintDecodedAddress = 7999; constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kVarintDecodedAddress, kFullyEscapedPayload); std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); uint64_t address = 0; size_t address_size = varint::Decode(kSmallerEscapedAddress, &address, kAddressFormat); EXPECT_EQ(address, kVarintDecodedAddress); EXPECT_EQ(address_size, 2u); EXPECT_EQ(Status::ResourceExhausted(), WriteUIFrame(address, kFullyEscapedPayload, writer)); } TEST(DecodedSize, BigAddress_SaturatedPayload) { constexpr auto kNoEscapePayload = bytes::String("The decoder needs the most space when there's no escapes"); constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kNoEscapePayload.size()); std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); EXPECT_EQ(OkStatus(), WriteUIFrame(kNoEscapeAddress, kNoEscapePayload, writer)); // Allocate at least enough real buffer space. constexpr size_t kDecoderBufferSize = Decoder::RequiredBufferSizeForFrameSize(kExpectedMaxFrameSize); std::array buffer; // Pretend the supported frame size is whatever the final size of the encoded // frame was. const size_t max_frame_size = Decoder::RequiredBufferSizeForFrameSize(writer.size()); Decoder decoder(ByteSpan(buffer).first(max_frame_size)); for (const std::byte b : writer.WrittenData()) { Result frame = decoder.Process(b); if (frame.ok()) { EXPECT_EQ(frame->address(), kNoEscapeAddress); EXPECT_EQ(frame->data().size(), kNoEscapePayload.size()); EXPECT_TRUE(std::memcmp(frame->data().begin(), kNoEscapePayload.begin(), kNoEscapePayload.size()) == 0); } } } TEST(DecodedSize, BigAddress_OffByOne) { constexpr auto kNoEscapePayload = bytes::String("The decoder needs the most space when there's no escapes"); constexpr size_t kExpectedMaxFrameSize = MaxEncodedFrameSize(kNoEscapePayload.size()); std::array dest_buffer; stream::MemoryWriter writer(dest_buffer); EXPECT_EQ(OkStatus(), WriteUIFrame(kNoEscapeAddress, kNoEscapePayload, writer)); // Allocate at least enough real buffer space. constexpr size_t kDecoderBufferSize = Decoder::RequiredBufferSizeForFrameSize(kExpectedMaxFrameSize); std::array buffer; // Pretend the supported frame size is whatever the final size of the encoded // frame was. const size_t max_frame_size = Decoder::RequiredBufferSizeForFrameSize(writer.size()); Decoder decoder(ByteSpan(buffer).first(max_frame_size - 1)); for (size_t i = 0; i < writer.size(); i++) { Result frame = decoder.Process(writer[i]); if (i < writer.size() - 1) { EXPECT_EQ(frame.status(), Status::Unavailable()); } else { EXPECT_EQ(frame.status(), Status::ResourceExhausted()); } } } } // namespace } // namespace pw::hdlc