Skip to content

Commit 97f517c

Browse files
fixed resize that treated bits as bytes, caused messages to have 8x their original size, and added unit test to check for it (#12)
1 parent 5f645fd commit 97f517c

2 files changed

Lines changed: 234 additions & 1 deletion

File tree

src/encoder/asn1_encoder.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ EncodeResult<EncodedMessage> Asn1E3Encoder::encode(const Pdu& pdu) {
6666
}
6767

6868
// Resize buffer to actual encoded size
69-
buffer.resize(static_cast<size_t>(enc_rval.encoded));
69+
const size_t encoded_bytes = (static_cast<size_t>(enc_rval.encoded) + 7) / 8;
70+
buffer.resize(encoded_bytes);
7071

7172
ASN_STRUCT_FREE(asn_DEF_E3_PDU, asn1_pdu);
7273

tests/test_asn1_size.cpp

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/**
2+
* @file test_asn1_size.cpp
3+
* @brief Tests for the ASN.1 APER encoder.
4+
*
5+
* Verifies three properties of the encoder:
6+
*
7+
* (1) The encoded buffer size is bounded as a small linear function of
8+
* the payload size — i.e., encoded ≤ 2·payload + a fixed envelope
9+
* allowance — across a range of payload sizes from tens of bytes
10+
* to several kilobytes.
11+
*
12+
* (2) Encode → decode round-trips preserve the original payload
13+
* byte-for-byte, with no truncation, reordering, or trailing
14+
* contamination.
15+
*
16+
* (3) The encoded size grows linearly with payload size, so the
17+
* difference between encodings of different-sized payloads tracks
18+
* the payload delta plus the fixed envelope.
19+
*
20+
* SPDX-License-Identifier: Apache-2.0
21+
*/
22+
23+
#include "test_framework.hpp"
24+
#include "libe3/libe3.hpp"
25+
#include "libe3/e3_encoder.hpp"
26+
#include "libe3/types.hpp"
27+
28+
#include <vector>
29+
#include <cstdint>
30+
31+
using namespace libe3;
32+
33+
// ---------------------------------------------------------------------------
34+
// Helpers
35+
// ---------------------------------------------------------------------------
36+
37+
/**
38+
* @brief Maximum byte overhead the E3-PDU + per-type CHOICE wrapper may add.
39+
*
40+
* The ASN.1 APER envelope (message_id, dApp/RAN-function identifiers,
41+
* length prefixes, CHOICE tag) typically occupies a few tens of bytes
42+
* for the E3-PDU types in this library. 256 B of headroom keeps the
43+
* bound robust to schema changes that legitimately grow the envelope.
44+
*/
45+
static constexpr size_t ENVELOPE_OVERHEAD_MAX = 256;
46+
47+
/**
48+
* @brief Upper bound on the encoded buffer size for a payload of N bytes.
49+
*
50+
* A well-formed APER encoder produces output of roughly N bytes plus a
51+
* small fixed envelope. We use 2·N + ENVELOPE_OVERHEAD_MAX as the
52+
* assertion threshold — comfortable headroom for any reasonable
53+
* encoder/schema variation.
54+
*/
55+
static size_t expected_max_encoded(size_t payload_bytes) {
56+
return 2 * payload_bytes + ENVELOPE_OVERHEAD_MAX;
57+
}
58+
59+
/**
60+
* @brief Build a deterministic payload of a given size.
61+
*
62+
* Uses a simple LCG so every byte position is distinguishable; round-trip
63+
* checks can therefore detect any byte reordering, truncation, or
64+
* modification of the payload.
65+
*/
66+
static std::vector<uint8_t> make_payload(size_t n) {
67+
std::vector<uint8_t> v(n);
68+
uint32_t s = 0x9E3779B9u; // golden-ratio LCG seed
69+
for (size_t i = 0; i < n; ++i) {
70+
s = s * 1103515245u + 12345u;
71+
v[i] = static_cast<uint8_t>(s >> 16);
72+
}
73+
return v;
74+
}
75+
76+
static std::unique_ptr<E3Encoder> make_encoder() {
77+
auto enc = create_encoder(EncodingFormat::ASN1);
78+
ASSERT_TRUE(enc != nullptr);
79+
return enc;
80+
}
81+
82+
// ---------------------------------------------------------------------------
83+
// Tests
84+
// ---------------------------------------------------------------------------
85+
86+
/**
87+
* Encoded size of a small DAppReport (~70 B payload) stays within
88+
* 2·payload + envelope.
89+
*/
90+
TEST(Asn1Size_DAppReport_smallPayload) {
91+
auto enc = make_encoder();
92+
auto payload = make_payload(70);
93+
94+
Pdu pdu(PduType::DAPP_REPORT);
95+
pdu.message_id = 42;
96+
DAppReport rep;
97+
rep.dapp_identifier = 1;
98+
rep.ran_function_identifier = 1;
99+
rep.report_data = payload;
100+
pdu.choice = rep;
101+
102+
auto result = enc->encode(pdu);
103+
ASSERT_TRUE(result.has_value());
104+
105+
const size_t encoded_size = result->buffer.size();
106+
ASSERT_GT(encoded_size, 0u);
107+
ASSERT_LE(encoded_size, expected_max_encoded(payload.size()));
108+
}
109+
110+
/**
111+
* Encoded size of a 225-byte XAppControlAction stays within
112+
* 2·payload + envelope.
113+
*/
114+
TEST(Asn1Size_XAppControlAction_mediumPayload) {
115+
auto enc = make_encoder();
116+
auto payload = make_payload(225);
117+
118+
Pdu pdu(PduType::XAPP_CONTROL_ACTION);
119+
pdu.message_id = 7;
120+
XAppControlAction action;
121+
action.dapp_identifier = 1;
122+
action.ran_function_identifier = 1;
123+
action.xapp_control_data = payload;
124+
pdu.choice = action;
125+
126+
auto result = enc->encode(pdu);
127+
ASSERT_TRUE(result.has_value());
128+
129+
const size_t encoded_size = result->buffer.size();
130+
ASSERT_GT(encoded_size, payload.size()); // sanity: at least N bytes
131+
ASSERT_LE(encoded_size, expected_max_encoded(payload.size()));
132+
}
133+
134+
/**
135+
* Encoded size of an IndicationMessage with an 8 KB payload stays within
136+
* 2·payload + envelope.
137+
*/
138+
TEST(Asn1Size_IndicationMessage_largePayload) {
139+
auto enc = make_encoder();
140+
auto payload = make_payload(8192);
141+
142+
Pdu pdu(PduType::INDICATION_MESSAGE);
143+
pdu.message_id = 234; // E3-MessageID is constrained to INTEGER (1..1000)
144+
IndicationMessage msg;
145+
msg.dapp_identifier = 1;
146+
msg.ran_function_identifier = 1;
147+
msg.protocol_data = payload;
148+
pdu.choice = msg;
149+
150+
auto result = enc->encode(pdu);
151+
ASSERT_TRUE(result.has_value());
152+
153+
const size_t encoded_size = result->buffer.size();
154+
ASSERT_GT(encoded_size, payload.size());
155+
ASSERT_LE(encoded_size, expected_max_encoded(payload.size()));
156+
}
157+
158+
/**
159+
* Encode → decode round-trip: the decoded payload must equal the
160+
* original byte-for-byte.
161+
*/
162+
TEST(Asn1Size_DAppReport_roundTrip_preservesPayload) {
163+
auto enc = make_encoder();
164+
auto payload = make_payload(225);
165+
166+
Pdu pdu(PduType::DAPP_REPORT);
167+
pdu.message_id = 99;
168+
DAppReport rep;
169+
rep.dapp_identifier = 3;
170+
rep.ran_function_identifier = 1;
171+
rep.report_data = payload;
172+
pdu.choice = rep;
173+
174+
auto encoded = enc->encode(pdu);
175+
ASSERT_TRUE(encoded.has_value());
176+
177+
auto decoded = enc->decode(encoded->buffer.data(), encoded->buffer.size());
178+
ASSERT_TRUE(decoded.has_value());
179+
ASSERT_EQ(static_cast<int>(decoded->type),
180+
static_cast<int>(PduType::DAPP_REPORT));
181+
182+
auto* out = std::get_if<DAppReport>(&decoded->choice);
183+
ASSERT_TRUE(out != nullptr);
184+
ASSERT_EQ(out->report_data.size(), payload.size());
185+
for (size_t i = 0; i < payload.size(); ++i) {
186+
ASSERT_EQ(static_cast<int>(out->report_data[i]),
187+
static_cast<int>(payload[i]));
188+
}
189+
}
190+
191+
/**
192+
* Encoded size grows linearly with payload size: the delta between
193+
* small-payload and large-payload encodings tracks the payload delta
194+
* plus the fixed envelope.
195+
*/
196+
TEST(Asn1Size_growsLinearlyWithPayload) {
197+
auto enc = make_encoder();
198+
199+
auto encode_size = [&](size_t n) -> size_t {
200+
Pdu pdu(PduType::DAPP_REPORT);
201+
pdu.message_id = 1;
202+
DAppReport rep;
203+
rep.dapp_identifier = 1;
204+
rep.ran_function_identifier = 1;
205+
rep.report_data = make_payload(n);
206+
pdu.choice = rep;
207+
auto r = enc->encode(pdu);
208+
ASSERT_TRUE(r.has_value());
209+
return r->buffer.size();
210+
};
211+
212+
const size_t s_small = encode_size(64);
213+
const size_t s_medium = encode_size(512);
214+
const size_t s_large = encode_size(4096);
215+
216+
// Each payload size individually satisfies the linear bound.
217+
ASSERT_LE(s_small, expected_max_encoded(64));
218+
ASSERT_LE(s_medium, expected_max_encoded(512));
219+
ASSERT_LE(s_large, expected_max_encoded(4096));
220+
221+
// Growth rate from small to large tracks payload growth, not a
222+
// larger multiple.
223+
const size_t delta = s_large - s_small;
224+
const size_t naive_payload_delta = 4096 - 64; // 4032
225+
ASSERT_LE(delta, 2 * naive_payload_delta + ENVELOPE_OVERHEAD_MAX);
226+
}
227+
228+
// ---------------------------------------------------------------------------
229+
230+
int main() {
231+
return RUN_ALL_TESTS();
232+
}

0 commit comments

Comments
 (0)