blob: 6dde795027afa1a18f044815c2fa19c8b1f0df7b [file] [log] [blame]
// 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 <array>
#include <cstddef>
#include <cstdint>
#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<uint8_t>(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<std::byte, kExpectedMaxFrameSize> 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<std::byte, kExpectedMaxFrameSize> 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<std::byte, kExpectedMaxFrameSize> 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<std::byte, kExpectedMaxFrameSize> 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<std::byte, kExpectedMaxFrameSize - 1> 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<std::byte, kExpectedMaxFrameSize> 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<std::byte, kDecoderBufferSize> 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> 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<std::byte, kExpectedMaxFrameSize> 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<std::byte, kDecoderBufferSize> 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> 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