|
| 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