1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_hdlc/encoded_size.h"
16
17 #include <array>
18 #include <cstddef>
19 #include <cstdint>
20
21 #include "pw_bytes/array.h"
22 #include "pw_hdlc/decoder.h"
23 #include "pw_hdlc/encoder.h"
24 #include "pw_result/result.h"
25 #include "pw_stream/memory_stream.h"
26 #include "pw_unit_test/framework.h"
27 #include "pw_varint/varint.h"
28
29 namespace pw::hdlc {
30 namespace {
31
32 // The varint-encoded address that represents the value that will result in the
33 // largest on-the-wire address after HDLC escaping.
34 constexpr auto kWidestVarintAddress =
35 bytes::String("\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x03");
36
37 // This is the decoded varint value of kWidestVarintAddress. This is
38 // pre-calculated as a constant to simplify tests.
39 constexpr uint64_t kWidestAddress = 0xbf7efdfbf7efdfbf;
40
41 // UI frames created by WriteUIFrame() will never be have an escaped control
42 // field, but it's technically possible for other HDLC frame types to produce
43 // control bytes that would need to be escaped.
44 constexpr size_t kEscapedControlCost = kControlSize;
45
46 // UI frames created by WriteUIFrame() will never have an escaped control
47 // field, but it's technically possible for other HDLC frame types to produce
48 // control bytes that would need to be escaped.
49 constexpr size_t kEscapedFcsCost = kMaxEscapedFcsSize - kFcsSize;
50
51 // Due to API limitations, the worst case buffer calculations used by the HDLC
52 // encoder/decoder can't be fully saturated. This constexpr value accounts for
53 // this by expressing the delta between the constant largest testable HDLC frame
54 // and the calculated worst-case-scenario.
55 constexpr size_t kTestLimitationsOverhead =
56 kEscapedControlCost + kEscapedFcsCost;
57
58 // A payload only containing bytes that need to be escaped.
59 constexpr auto kFullyEscapedPayload =
60 bytes::String("\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e");
61
62 constexpr uint8_t kEscapeAddress = static_cast<uint8_t>(kFlag);
63 constexpr uint8_t kNoEscapeAddress = 6;
64
TEST(EncodedSize,Constants_WidestAddress)65 TEST(EncodedSize, Constants_WidestAddress) {
66 uint64_t address = 0;
67 size_t address_size =
68 varint::Decode(kWidestVarintAddress, &address, kAddressFormat);
69 EXPECT_EQ(address_size, 10u);
70 EXPECT_EQ(address_size, kMaxAddressSize);
71 EXPECT_EQ(kMaxEscapedVarintAddressSize, 19u);
72 EXPECT_EQ(EscapedSize(kWidestVarintAddress), kMaxEscapedVarintAddressSize);
73 EXPECT_EQ(address, kWidestAddress);
74 EXPECT_EQ(varint::EncodedSize(kWidestAddress), 10u);
75 }
76
TEST(EncodedSize,EscapedSize_AllEscapeBytes)77 TEST(EncodedSize, EscapedSize_AllEscapeBytes) {
78 EXPECT_EQ(EscapedSize(kFullyEscapedPayload), kFullyEscapedPayload.size() * 2);
79 }
80
TEST(EncodedSize,EscapedSize_NoEscapeBytes)81 TEST(EncodedSize, EscapedSize_NoEscapeBytes) {
82 constexpr auto kData = bytes::String("\x01\x23\x45\x67\x89\xab\xcd\xef");
83 EXPECT_EQ(EscapedSize(kData), kData.size());
84 }
85
TEST(EncodedSize,EscapedSize_SomeEscapeBytes)86 TEST(EncodedSize, EscapedSize_SomeEscapeBytes) {
87 constexpr auto kData = bytes::String("\x7epabu\x7d");
88 EXPECT_EQ(EscapedSize(kData), kData.size() + 2);
89 }
90
TEST(EncodedSize,EscapedSize_Address)91 TEST(EncodedSize, EscapedSize_Address) {
92 EXPECT_EQ(EscapedSize(kWidestVarintAddress),
93 varint::EncodedSize(kWidestAddress) * 2 - 1);
94 }
95
TEST(EncodedSize,MaxEncodedSize_Overload)96 TEST(EncodedSize, MaxEncodedSize_Overload) {
97 EXPECT_EQ(MaxEncodedFrameSize(kFullyEscapedPayload.size()),
98 MaxEncodedFrameSize(kWidestAddress, kFullyEscapedPayload));
99 }
100
TEST(EncodedSize,MaxEncodedSize_EmptyPayload)101 TEST(EncodedSize, MaxEncodedSize_EmptyPayload) {
102 EXPECT_EQ(14u, MaxEncodedFrameSize(kNoEscapeAddress, {}));
103 EXPECT_EQ(14u, MaxEncodedFrameSize(kEscapeAddress, {}));
104 }
105
TEST(EncodedSize,MaxEncodedSize_PayloadWithoutEscapes)106 TEST(EncodedSize, MaxEncodedSize_PayloadWithoutEscapes) {
107 constexpr auto data = bytes::Array<0x00, 0x01, 0x02, 0x03>();
108 EXPECT_EQ(18u, MaxEncodedFrameSize(kNoEscapeAddress, data));
109 EXPECT_EQ(18u, MaxEncodedFrameSize(kEscapeAddress, data));
110 }
111
TEST(EncodedSize,MaxEncodedSize_PayloadWithOneEscape)112 TEST(EncodedSize, MaxEncodedSize_PayloadWithOneEscape) {
113 constexpr auto data = bytes::Array<0x00, 0x01, 0x7e, 0x03>();
114 EXPECT_EQ(19u, MaxEncodedFrameSize(kNoEscapeAddress, data));
115 EXPECT_EQ(19u, MaxEncodedFrameSize(kEscapeAddress, data));
116 }
117
TEST(EncodedSize,MaxEncodedSize_PayloadWithAllEscapes)118 TEST(EncodedSize, MaxEncodedSize_PayloadWithAllEscapes) {
119 constexpr auto data = bytes::Initialized<8>(0x7e);
120 EXPECT_EQ(30u, MaxEncodedFrameSize(kNoEscapeAddress, data));
121 EXPECT_EQ(30u, MaxEncodedFrameSize(kEscapeAddress, data));
122 }
123
TEST(EncodedSize,MaxPayload_UndersizedFrame)124 TEST(EncodedSize, MaxPayload_UndersizedFrame) {
125 EXPECT_EQ(MaxSafePayloadSize(4), 0u);
126 }
127
TEST(EncodedSize,MaxPayload_SmallFrame)128 TEST(EncodedSize, MaxPayload_SmallFrame) {
129 EXPECT_EQ(MaxSafePayloadSize(128), 48u);
130 }
131
TEST(EncodedSize,MaxPayload_MediumFrame)132 TEST(EncodedSize, MaxPayload_MediumFrame) {
133 EXPECT_EQ(MaxSafePayloadSize(512), 240u);
134 }
135
TEST(EncodedSize,FrameToPayloadInversion_Odd)136 TEST(EncodedSize, FrameToPayloadInversion_Odd) {
137 static constexpr size_t kIntendedPayloadSize = 1234567891;
138 EXPECT_EQ(MaxSafePayloadSize(MaxEncodedFrameSize(kIntendedPayloadSize)),
139 kIntendedPayloadSize);
140 }
141
TEST(EncodedSize,PayloadToFrameInversion_Odd)142 TEST(EncodedSize, PayloadToFrameInversion_Odd) {
143 static constexpr size_t kIntendedFrameSize = 1234567891;
144 EXPECT_EQ(MaxEncodedFrameSize(MaxSafePayloadSize(kIntendedFrameSize)),
145 kIntendedFrameSize);
146 }
147
TEST(EncodedSize,FrameToPayloadInversion_Even)148 TEST(EncodedSize, FrameToPayloadInversion_Even) {
149 static constexpr size_t kIntendedPayloadSize = 42;
150 EXPECT_EQ(MaxSafePayloadSize(MaxEncodedFrameSize(kIntendedPayloadSize)),
151 kIntendedPayloadSize);
152 }
153
TEST(EncodedSize,PayloadToFrameInversion_Even)154 TEST(EncodedSize, PayloadToFrameInversion_Even) {
155 static constexpr size_t kIntendedFrameSize = 42;
156 // Because of HDLC encoding overhead requirements, the last byte of the
157 // intended frame size is wasted because it doesn't allow sufficient space for
158 // another byte since said additional byte could require escaping, therefore
159 // requiring a second byte to increase the safe payload size by one.
160 const size_t max_frame_usage =
161 MaxEncodedFrameSize(MaxSafePayloadSize(kIntendedFrameSize));
162 EXPECT_EQ(max_frame_usage, kIntendedFrameSize - 1);
163
164 // There's no further change if the inversion is done again since the frame
165 // size is aligned to the reduced bounds.
166 EXPECT_EQ(MaxEncodedFrameSize(MaxSafePayloadSize(max_frame_usage)),
167 kIntendedFrameSize - 1);
168 }
169
TEST(EncodedSize,MostlyEscaped)170 TEST(EncodedSize, MostlyEscaped) {
171 constexpr auto kMostlyEscapedPayload =
172 bytes::String(":)\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e");
173 constexpr size_t kUnescapedBytes =
174 2 * kMostlyEscapedPayload.size() - EscapedSize(kMostlyEscapedPayload);
175 // Subtracting 2 should still leave enough space since two bytes won't need
176 // to be escaped.
177 constexpr size_t kExpectedMaxFrameSize =
178 MaxEncodedFrameSize(kMostlyEscapedPayload.size()) - kUnescapedBytes;
179 std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
180 stream::MemoryWriter writer(dest_buffer);
181 EXPECT_EQ(kUnescapedBytes, 2u);
182 EXPECT_EQ(OkStatus(),
183 WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer));
184 EXPECT_EQ(writer.size(),
185 kExpectedMaxFrameSize - kTestLimitationsOverhead - kUnescapedBytes);
186 }
187
TEST(EncodedSize,BigAddress_SaturatedPayload)188 TEST(EncodedSize, BigAddress_SaturatedPayload) {
189 constexpr size_t kExpectedMaxFrameSize =
190 MaxEncodedFrameSize(kFullyEscapedPayload.size());
191 std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
192 stream::MemoryWriter writer(dest_buffer);
193 EXPECT_EQ(OkStatus(),
194 WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer));
195 EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead);
196 }
197
TEST(EncodedSize,BigAddress_OffByOne)198 TEST(EncodedSize, BigAddress_OffByOne) {
199 constexpr size_t kExpectedMaxFrameSize =
200 MaxEncodedFrameSize(kFullyEscapedPayload.size()) - 1;
201 std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
202 stream::MemoryWriter writer(dest_buffer);
203 EXPECT_EQ(Status::ResourceExhausted(),
204 WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer));
205 }
206
TEST(EncodedSize,SmallAddress_SaturatedPayload)207 TEST(EncodedSize, SmallAddress_SaturatedPayload) {
208 constexpr auto kSmallerEscapedAddress = bytes::String("\x7e\x7d");
209 // varint::Decode() is not constexpr, so this is a hard-coded and then runtime
210 // validated.
211 constexpr size_t kVarintDecodedAddress = 7999;
212 constexpr size_t kExpectedMaxFrameSize =
213 MaxEncodedFrameSize(kVarintDecodedAddress, kFullyEscapedPayload);
214 std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
215 stream::MemoryWriter writer(dest_buffer);
216
217 uint64_t address = 0;
218 size_t address_size =
219 varint::Decode(kSmallerEscapedAddress, &address, kAddressFormat);
220 EXPECT_EQ(address, kVarintDecodedAddress);
221 EXPECT_EQ(address_size, 2u);
222
223 EXPECT_EQ(OkStatus(), WriteUIFrame(address, kFullyEscapedPayload, writer));
224 EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead);
225 }
226
TEST(EncodedSize,SmallAddress_OffByOne)227 TEST(EncodedSize, SmallAddress_OffByOne) {
228 constexpr auto kSmallerEscapedAddress = bytes::String("\x7e\x7d");
229 // varint::Decode() is not constexpr, so this is a hard-coded and then runtime
230 // validated.
231 constexpr size_t kVarintDecodedAddress = 7999;
232 constexpr size_t kExpectedMaxFrameSize =
233 MaxEncodedFrameSize(kVarintDecodedAddress, kFullyEscapedPayload);
234 std::array<std::byte, kExpectedMaxFrameSize - 1> dest_buffer;
235 stream::MemoryWriter writer(dest_buffer);
236
237 uint64_t address = 0;
238 size_t address_size =
239 varint::Decode(kSmallerEscapedAddress, &address, kAddressFormat);
240 EXPECT_EQ(address, kVarintDecodedAddress);
241 EXPECT_EQ(address_size, 2u);
242
243 EXPECT_EQ(Status::ResourceExhausted(),
244 WriteUIFrame(address, kFullyEscapedPayload, writer));
245 }
246
TEST(DecodedSize,BigAddress_SaturatedPayload)247 TEST(DecodedSize, BigAddress_SaturatedPayload) {
248 constexpr auto kNoEscapePayload =
249 bytes::String("The decoder needs the most space when there's no escapes");
250 constexpr size_t kExpectedMaxFrameSize =
251 MaxEncodedFrameSize(kNoEscapePayload.size());
252 std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
253 stream::MemoryWriter writer(dest_buffer);
254 EXPECT_EQ(OkStatus(),
255 WriteUIFrame(kNoEscapeAddress, kNoEscapePayload, writer));
256
257 // Allocate at least enough real buffer space.
258 constexpr size_t kDecoderBufferSize =
259 Decoder::RequiredBufferSizeForFrameSize(kExpectedMaxFrameSize);
260 std::array<std::byte, kDecoderBufferSize> buffer;
261
262 // Pretend the supported frame size is whatever the final size of the encoded
263 // frame was.
264 const size_t max_frame_size =
265 Decoder::RequiredBufferSizeForFrameSize(writer.size());
266
267 Decoder decoder(ByteSpan(buffer).first(max_frame_size));
268 for (const std::byte b : writer.WrittenData()) {
269 Result<Frame> frame = decoder.Process(b);
270 if (frame.ok()) {
271 EXPECT_EQ(frame->address(), kNoEscapeAddress);
272 EXPECT_EQ(frame->data().size(), kNoEscapePayload.size());
273 EXPECT_TRUE(std::memcmp(frame->data().data(),
274 kNoEscapePayload.data(),
275 kNoEscapePayload.size()) == 0);
276 }
277 }
278 }
279
TEST(DecodedSize,BigAddress_OffByOne)280 TEST(DecodedSize, BigAddress_OffByOne) {
281 constexpr auto kNoEscapePayload =
282 bytes::String("The decoder needs the most space when there's no escapes");
283 constexpr size_t kExpectedMaxFrameSize =
284 MaxEncodedFrameSize(kNoEscapePayload.size());
285 std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
286 stream::MemoryWriter writer(dest_buffer);
287 EXPECT_EQ(OkStatus(),
288 WriteUIFrame(kNoEscapeAddress, kNoEscapePayload, writer));
289
290 // Allocate at least enough real buffer space.
291 constexpr size_t kDecoderBufferSize =
292 Decoder::RequiredBufferSizeForFrameSize(kExpectedMaxFrameSize);
293 std::array<std::byte, kDecoderBufferSize> buffer;
294
295 // Pretend the supported frame size is whatever the final size of the encoded
296 // frame was.
297 const size_t max_frame_size =
298 Decoder::RequiredBufferSizeForFrameSize(writer.size());
299
300 Decoder decoder(ByteSpan(buffer).first(max_frame_size - 1));
301 for (size_t i = 0; i < writer.size(); i++) {
302 Result<Frame> frame = decoder.Process(writer[i]);
303 if (i < writer.size() - 1) {
304 EXPECT_EQ(frame.status(), Status::Unavailable());
305 } else {
306 EXPECT_EQ(frame.status(), Status::ResourceExhausted());
307 }
308 }
309 }
310
311 } // namespace
312 } // namespace pw::hdlc
313