314 lines
12 KiB
C++
314 lines
12 KiB
C++
// 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().data(),
|
|
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
|