#include #include #include "dd3_legacy_core.h" #include "payload_codec.h" static constexpr uint8_t kMaxSamples = 30; static void fill_sparse_batch(BatchInput &in) { memset(&in, 0, sizeof(in)); in.sender_id = 1; in.batch_id = 42; in.t_last = 1700000000; in.present_mask = (1UL << 0) | (1UL << 2) | (1UL << 3) | (1UL << 10) | (1UL << 29); in.n = 5; in.battery_mV = 3750; in.err_m = 2; in.err_d = 1; in.err_tx = 3; in.err_last = 2; in.err_rx_reject = 1; in.energy_wh[0] = 100000; in.energy_wh[1] = 100001; in.energy_wh[2] = 100050; in.energy_wh[3] = 100050; in.energy_wh[4] = 100200; in.p1_w[0] = -120; in.p1_w[1] = -90; in.p1_w[2] = 1910; in.p1_w[3] = -90; in.p1_w[4] = 500; in.p2_w[0] = 50; in.p2_w[1] = -1950; in.p2_w[2] = 60; in.p2_w[3] = 2060; in.p2_w[4] = -10; in.p3_w[0] = 0; in.p3_w[1] = 10; in.p3_w[2] = -1990; in.p3_w[3] = 10; in.p3_w[4] = 20; } static void fill_full_batch(BatchInput &in) { memset(&in, 0, sizeof(in)); in.sender_id = 1; in.batch_id = 0xBEEF; in.t_last = 1769904999; in.present_mask = 0x3FFFFFFFUL; in.n = kMaxSamples; in.battery_mV = 4095; in.err_m = 10; in.err_d = 20; in.err_tx = 30; in.err_last = 3; in.err_rx_reject = 6; for (uint8_t i = 0; i < kMaxSamples; ++i) { in.energy_wh[i] = 500000UL + static_cast(i) * static_cast(i) * 3UL; in.p1_w[i] = static_cast(-1000 + static_cast(i) * 25); in.p2_w[i] = static_cast(500 - static_cast(i) * 30); in.p3_w[i] = static_cast(((i % 2) == 0 ? 100 : -100) + static_cast(i) * 5); } } static void assert_batch_equals(const BatchInput &expected, const BatchInput &actual) { TEST_ASSERT_EQUAL_UINT16(expected.sender_id, actual.sender_id); TEST_ASSERT_EQUAL_UINT16(expected.batch_id, actual.batch_id); TEST_ASSERT_EQUAL_UINT32(expected.t_last, actual.t_last); TEST_ASSERT_EQUAL_UINT32(expected.present_mask, actual.present_mask); TEST_ASSERT_EQUAL_UINT8(expected.n, actual.n); TEST_ASSERT_EQUAL_UINT16(expected.battery_mV, actual.battery_mV); TEST_ASSERT_EQUAL_UINT8(expected.err_m, actual.err_m); TEST_ASSERT_EQUAL_UINT8(expected.err_d, actual.err_d); TEST_ASSERT_EQUAL_UINT8(expected.err_tx, actual.err_tx); TEST_ASSERT_EQUAL_UINT8(expected.err_last, actual.err_last); TEST_ASSERT_EQUAL_UINT8(expected.err_rx_reject, actual.err_rx_reject); for (uint8_t i = 0; i < expected.n; ++i) { TEST_ASSERT_EQUAL_UINT32(expected.energy_wh[i], actual.energy_wh[i]); TEST_ASSERT_EQUAL_INT16(expected.p1_w[i], actual.p1_w[i]); TEST_ASSERT_EQUAL_INT16(expected.p2_w[i], actual.p2_w[i]); TEST_ASSERT_EQUAL_INT16(expected.p3_w[i], actual.p3_w[i]); } for (uint8_t i = expected.n; i < kMaxSamples; ++i) { TEST_ASSERT_EQUAL_UINT32(0, actual.energy_wh[i]); TEST_ASSERT_EQUAL_INT16(0, actual.p1_w[i]); TEST_ASSERT_EQUAL_INT16(0, actual.p2_w[i]); TEST_ASSERT_EQUAL_INT16(0, actual.p3_w[i]); } } static void test_encode_decode_roundtrip_schema_v3() { BatchInput in = {}; fill_sparse_batch(in); uint8_t encoded[256] = {}; size_t encoded_len = 0; TEST_ASSERT_TRUE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); TEST_ASSERT_TRUE(encoded_len > 24); BatchInput out = {}; TEST_ASSERT_TRUE(decode_batch(encoded, encoded_len, &out)); assert_batch_equals(in, out); } static void test_decode_rejects_bad_magic_schema_flags() { BatchInput in = {}; fill_sparse_batch(in); uint8_t encoded[256] = {}; size_t encoded_len = 0; TEST_ASSERT_TRUE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); BatchInput out = {}; uint8_t bad_magic[256] = {}; memcpy(bad_magic, encoded, encoded_len); bad_magic[0] = 0x00; TEST_ASSERT_FALSE(decode_batch(bad_magic, encoded_len, &out)); uint8_t bad_schema[256] = {}; memcpy(bad_schema, encoded, encoded_len); bad_schema[2] = 0x02; TEST_ASSERT_FALSE(decode_batch(bad_schema, encoded_len, &out)); uint8_t bad_flags[256] = {}; memcpy(bad_flags, encoded, encoded_len); bad_flags[3] = 0x00; TEST_ASSERT_FALSE(decode_batch(bad_flags, encoded_len, &out)); } static void test_decode_rejects_truncated_and_length_mismatch() { BatchInput in = {}; fill_sparse_batch(in); uint8_t encoded[256] = {}; size_t encoded_len = 0; TEST_ASSERT_TRUE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); BatchInput out = {}; TEST_ASSERT_FALSE(decode_batch(encoded, encoded_len - 1, &out)); TEST_ASSERT_FALSE(decode_batch(encoded, 12, &out)); uint8_t with_tail[257] = {}; memcpy(with_tail, encoded, encoded_len); with_tail[encoded_len] = 0xAA; TEST_ASSERT_FALSE(decode_batch(with_tail, encoded_len + 1, &out)); } static void test_encode_and_decode_reject_invalid_present_mask() { BatchInput in = {}; fill_sparse_batch(in); uint8_t encoded[256] = {}; size_t encoded_len = 0; in.present_mask = 0x40000000UL; TEST_ASSERT_FALSE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); fill_sparse_batch(in); TEST_ASSERT_TRUE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); BatchInput out = {}; uint8_t invalid_bits[256] = {}; memcpy(invalid_bits, encoded, encoded_len); invalid_bits[15] |= 0x40; TEST_ASSERT_FALSE(decode_batch(invalid_bits, encoded_len, &out)); uint8_t bitcount_mismatch[256] = {}; memcpy(bitcount_mismatch, encoded, encoded_len); bitcount_mismatch[16] = 0x01; // n=1 while mask has 5 bits set TEST_ASSERT_FALSE(decode_batch(bitcount_mismatch, encoded_len, &out)); } static void test_encode_rejects_invalid_n_and_regression_cases() { BatchInput in = {}; fill_sparse_batch(in); uint8_t encoded[256] = {}; size_t encoded_len = 0; in.n = 31; TEST_ASSERT_FALSE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); fill_sparse_batch(in); in.n = 0; in.present_mask = 1; TEST_ASSERT_FALSE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); fill_sparse_batch(in); in.n = 2; in.present_mask = 0x00000003UL; in.energy_wh[1] = in.energy_wh[0] - 1; TEST_ASSERT_FALSE(encode_batch(in, encoded, sizeof(encoded), &encoded_len)); fill_sparse_batch(in); TEST_ASSERT_FALSE(encode_batch(in, encoded, 10, &encoded_len)); } static const uint8_t VECTOR_SYNC_EMPTY[] = { 0xB3, 0xDD, 0x03, 0x01, 0x01, 0x00, 0x34, 0x12, 0xE4, 0x97, 0x7E, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}; static const uint8_t VECTOR_SPARSE_5[] = { 0xB3, 0xDD, 0x03, 0x01, 0x01, 0x00, 0x2A, 0x00, 0x00, 0xF1, 0x53, 0x65, 0x0D, 0x04, 0x00, 0x20, 0x05, 0xA6, 0x0E, 0x02, 0x01, 0x03, 0x02, 0x01, 0xA0, 0x86, 0x01, 0x00, 0x01, 0x31, 0x00, 0x96, 0x01, 0x88, 0xFF, 0x3C, 0xA0, 0x1F, 0x9F, 0x1F, 0x9C, 0x09, 0x32, 0x00, 0x9F, 0x1F, 0xB4, 0x1F, 0xA0, 0x1F, 0xAB, 0x20, 0x00, 0x00, 0x14, 0x9F, 0x1F, 0xA0, 0x1F, 0x14}; static const uint8_t VECTOR_FULL_30[] = { 0xB3, 0xDD, 0x03, 0x01, 0x01, 0x00, 0xEF, 0xBE, 0x67, 0x9B, 0x7E, 0x69, 0xFF, 0xFF, 0xFF, 0x3F, 0x1E, 0xFF, 0x0F, 0x0A, 0x14, 0x1E, 0x03, 0x06, 0x20, 0xA1, 0x07, 0x00, 0x03, 0x09, 0x0F, 0x15, 0x1B, 0x21, 0x27, 0x2D, 0x33, 0x39, 0x3F, 0x45, 0x4B, 0x51, 0x57, 0x5D, 0x63, 0x69, 0x6F, 0x75, 0x7B, 0x81, 0x01, 0x87, 0x01, 0x8D, 0x01, 0x93, 0x01, 0x99, 0x01, 0x9F, 0x01, 0xA5, 0x01, 0xAB, 0x01, 0x18, 0xFC, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0xF4, 0x01, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x64, 0x00, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03, 0x9A, 0x03, 0x85, 0x03}; static void test_payload_golden_vectors() { BatchInput expected_sync = {}; expected_sync.sender_id = 1; expected_sync.batch_id = 0x1234; expected_sync.t_last = 1769904100; expected_sync.present_mask = 0; expected_sync.n = 0; expected_sync.battery_mV = 3750; expected_sync.err_m = 0; expected_sync.err_d = 0; expected_sync.err_tx = 0; expected_sync.err_last = 0; expected_sync.err_rx_reject = 0; BatchInput expected_sparse = {}; fill_sparse_batch(expected_sparse); BatchInput expected_full = {}; fill_full_batch(expected_full); struct VectorCase { const char *name; const uint8_t *bytes; size_t len; const BatchInput *expected; } cases[] = { {"sync_empty", VECTOR_SYNC_EMPTY, sizeof(VECTOR_SYNC_EMPTY), &expected_sync}, {"sparse_5", VECTOR_SPARSE_5, sizeof(VECTOR_SPARSE_5), &expected_sparse}, {"full_30", VECTOR_FULL_30, sizeof(VECTOR_FULL_30), &expected_full}, }; for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); ++i) { BatchInput decoded = {}; TEST_ASSERT_TRUE_MESSAGE(decode_batch(cases[i].bytes, cases[i].len, &decoded), cases[i].name); assert_batch_equals(*cases[i].expected, decoded); uint8_t reencoded[512] = {}; size_t reencoded_len = 0; TEST_ASSERT_TRUE_MESSAGE(encode_batch(*cases[i].expected, reencoded, sizeof(reencoded), &reencoded_len), cases[i].name); TEST_ASSERT_EQUAL_UINT_MESSAGE(cases[i].len, reencoded_len, cases[i].name); TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(cases[i].bytes, reencoded, cases[i].len, cases[i].name); } } void setup() { dd3_legacy_core_force_link(); UNITY_BEGIN(); RUN_TEST(test_encode_decode_roundtrip_schema_v3); RUN_TEST(test_decode_rejects_bad_magic_schema_flags); RUN_TEST(test_decode_rejects_truncated_and_length_mismatch); RUN_TEST(test_encode_and_decode_reject_invalid_present_mask); RUN_TEST(test_encode_rejects_invalid_n_and_regression_cases); RUN_TEST(test_payload_golden_vectors); UNITY_END(); } void loop() {}