mirror of
https://github.com/esphome/esphome.git
synced 2026-05-25 10:26:10 +08:00
[api] Add unit tests for 48-bit MAC varint encoder
Verifies encode_varint_raw_48bit and calc_uint64_48bit_force for the 8 corner-case MAC addresses requested in review: 00:00:00:00:00:00, 11:00:00:00:00:00, 00:AA:00:00:00:00, 00:00:BB:00:00:00, 00:00:00:CC:00:00, 00:00:00:00:DD:00, 00:00:00:00:00:EE, FF:FF:FF:FF:FF:FF For each value the test asserts byte-identical output to the reference encode_varint_raw_64 loop, the expected encoded byte length, agreement with calc_uint64_48bit_force, and round-trip through a generic varint decoder. Adds a 100-value deterministic-random sample across the full 48-bit space for additional coverage.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
#include "esphome/components/api/api_buffer.h"
|
||||
#include "esphome/components/api/proto.h"
|
||||
|
||||
namespace esphome::api::testing {
|
||||
|
||||
// Generic varint decoder, used to verify the encoded bytes round-trip back to
|
||||
// the original 48-bit MAC value, independent of the specialized encoder under
|
||||
// test.
|
||||
static uint64_t decode_varint(const uint8_t *buf, size_t len, size_t *consumed) {
|
||||
uint64_t value = 0;
|
||||
int shift = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
value |= static_cast<uint64_t>(buf[i] & 0x7F) << shift;
|
||||
if ((buf[i] & 0x80) == 0) {
|
||||
*consumed = i + 1;
|
||||
return value;
|
||||
}
|
||||
shift += 7;
|
||||
}
|
||||
*consumed = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reference encoder mirroring ProtoEncode::encode_varint_raw_64.
|
||||
static size_t reference_encode(uint64_t value, uint8_t *out) {
|
||||
uint8_t *p = out;
|
||||
if (value < 128) {
|
||||
*p++ = static_cast<uint8_t>(value);
|
||||
return p - out;
|
||||
}
|
||||
do {
|
||||
*p++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
} while (value > 0x7F);
|
||||
*p++ = static_cast<uint8_t>(value);
|
||||
return p - out;
|
||||
}
|
||||
|
||||
// Encode `mac` via the 48-bit fast path and verify:
|
||||
// - byte-identical output to the reference loop
|
||||
// - encoded byte length matches `expected_bytes`
|
||||
// - calc_uint64_48bit_force agrees on the size
|
||||
// - the bytes round-trip through a generic varint decoder
|
||||
static void verify_mac(uint64_t mac, size_t expected_bytes) {
|
||||
ASSERT_LT(mac, 1ULL << 48) << "test fixture mac exceeds 48 bits";
|
||||
|
||||
uint8_t ref_buf[16] = {0};
|
||||
size_t ref_len = reference_encode(mac, ref_buf);
|
||||
|
||||
APIBuffer api_buf;
|
||||
api_buf.resize(16);
|
||||
uint8_t *pos = api_buf.data();
|
||||
PROTO_ENCODE_DEBUG_INIT(&api_buf);
|
||||
ProtoEncode::encode_varint_raw_48bit(pos PROTO_ENCODE_DEBUG_ARG, mac);
|
||||
size_t new_len = pos - api_buf.data();
|
||||
|
||||
EXPECT_EQ(new_len, expected_bytes) << "mac=0x" << std::hex << mac << std::dec;
|
||||
EXPECT_EQ(ref_len, expected_bytes) << "reference disagrees on length for mac=0x" << std::hex << mac << std::dec;
|
||||
|
||||
for (size_t i = 0; i < new_len; i++) {
|
||||
EXPECT_EQ(api_buf.data()[i], ref_buf[i])
|
||||
<< "byte " << i << " differs for mac=0x" << std::hex << mac << " (got 0x" << static_cast<int>(api_buf.data()[i])
|
||||
<< ", expected 0x" << static_cast<int>(ref_buf[i]) << ")" << std::dec;
|
||||
}
|
||||
|
||||
size_t consumed = 0;
|
||||
uint64_t decoded = decode_varint(api_buf.data(), new_len, &consumed);
|
||||
EXPECT_EQ(consumed, new_len) << "decoder did not consume all bytes for mac=0x" << std::hex << mac << std::dec;
|
||||
EXPECT_EQ(decoded, mac) << "round-trip mismatch for mac=0x" << std::hex << mac << std::dec;
|
||||
|
||||
// Verify the size helper agrees. field_id_size = 1 (typical 1-byte tag).
|
||||
uint32_t calc_size = ProtoSize::calc_uint64_48bit_force(1, mac);
|
||||
EXPECT_EQ(calc_size, 1 + expected_bytes)
|
||||
<< "calc_uint64_48bit_force size mismatch for mac=0x" << std::hex << mac << std::dec;
|
||||
}
|
||||
|
||||
// Compute the canonical varint byte length for a value < 1<<48.
|
||||
static size_t expected_varint_len(uint64_t v) {
|
||||
if (v < (1ULL << 7))
|
||||
return 1;
|
||||
if (v < (1ULL << 14))
|
||||
return 2;
|
||||
if (v < (1ULL << 21))
|
||||
return 3;
|
||||
if (v < (1ULL << 28))
|
||||
return 4;
|
||||
if (v < (1ULL << 35))
|
||||
return 5;
|
||||
if (v < (1ULL << 42))
|
||||
return 6;
|
||||
return 7;
|
||||
}
|
||||
|
||||
// --- Specific MACs requested for verification ---
|
||||
|
||||
TEST(ProtoMacVarint, AllZeros) { verify_mac(0x000000000000ULL, 1); } // 00:00:00:00:00:00
|
||||
TEST(ProtoMacVarint, FirstByteOnly) { verify_mac(0x110000000000ULL, 7); } // 11:00:00:00:00:00
|
||||
TEST(ProtoMacVarint, SecondByteOnly) { verify_mac(0x00AA00000000ULL, 6); } // 00:AA:00:00:00:00
|
||||
TEST(ProtoMacVarint, ThirdByteOnly) { verify_mac(0x0000BB000000ULL, 5); } // 00:00:BB:00:00:00
|
||||
TEST(ProtoMacVarint, FourthByteOnly) { verify_mac(0x000000CC0000ULL, 4); } // 00:00:00:CC:00:00
|
||||
TEST(ProtoMacVarint, FifthByteOnly) { verify_mac(0x00000000DD00ULL, 3); } // 00:00:00:00:DD:00
|
||||
TEST(ProtoMacVarint, SixthByteOnly) { verify_mac(0x0000000000EEULL, 2); } // 00:00:00:00:00:EE
|
||||
TEST(ProtoMacVarint, AllOnes) { verify_mac(0xFFFFFFFFFFFFULL, 7); } // FF:FF:FF:FF:FF:FF
|
||||
|
||||
// 100 deterministic-random 48-bit MACs to catch regressions across the space.
|
||||
TEST(ProtoMacVarint, RandomSample) {
|
||||
std::mt19937_64 rng(0xC0FFEE);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
uint64_t mac = rng() & 0xFFFFFFFFFFFFULL;
|
||||
verify_mac(mac, expected_varint_len(mac));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::api::testing
|
||||
Reference in New Issue
Block a user