/* * test_conformance.cpp — Spec conformance tests for Antheos Protocol v9 * * Verifies every wire example from Antheos_Protocol_1_0_v9.md byte-for-byte * using build->parse round-trips. C++17 port of test_conformance.c. * * Wire format reference: Antheos Protocol 1.0 Level 1, v9 ss6.5, ss9, ss10, ss11. * * Copyright (c) 2025-2026 Are Bjørby * SPDX-License-Identifier: MIT */ #include "test_common.hpp" #include "antheos.hpp" #include #include #include #include #include using namespace antheos; /* -- Parser capture state -- shared by all round-trip tests -- */ static struct { int word_count; int msg_count; uint8_t types[16]; uint8_t radixes[16]; uint8_t units[16]; char bodies[16][256]; size_t body_lens[16]; size_t msg_word_counts[8]; } g_conf; static Parser g_conf_parser; static void conf_parse_reset() { std::memset(&g_conf, 0, sizeof(g_conf)); g_conf_parser = Parser(); g_conf_parser.on_word([](wire::WordType type, wire::Radix radix, wire::Unit unit, const uint8_t* body, size_t len) { int i = g_conf.word_count; if (i < 16) { g_conf.types[i] = static_cast(type); g_conf.radixes[i] = static_cast(radix); g_conf.units[i] = static_cast(unit); if (len > 255) len = 255; std::memcpy(g_conf.bodies[i], body, len); g_conf.bodies[i][len] = '\0'; g_conf.body_lens[i] = len; } g_conf.word_count++; }); g_conf_parser.on_message([](size_t word_count) { int i = g_conf.msg_count; if (i < 8) g_conf.msg_word_counts[i] = word_count; g_conf.msg_count++; }); } static void conf_parse_bytes(const uint8_t* bytes, size_t len) { for (size_t i = 0; i < len; i++) g_conf_parser.feed(bytes[i]); } /* ================================================================ * ss6.5 Word Encoding Examples -- round-trip tests * ================================================================ */ /* ss6.5: SYMBOL 'E' -> [SOW]![SOB]E[EOW] = 12 21 1A 45 10 */ static void test_spec_word_symbol_E() { auto enc = wire::encode_symbol('E'); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = {0x12, 0x21, 0x1A, 0x45, 0x10}; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); /* Round-trip: wrap in SOM/EOM and parse */ std::vector frame; frame.push_back(wire::SOM); frame.insert(frame.end(), enc->begin(), enc->end()); frame.push_back(wire::EOM); conf_parse_reset(); conf_parse_bytes(frame.data(), frame.size()); ASSERT_EQ(g_conf.word_count, 1); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.radixes[0], 0); ASSERT_EQ(g_conf.units[0], 0); ASSERT_EQ((int)g_conf.body_lens[0], 1); ASSERT_EQ(g_conf.bodies[0][0], 'E'); } /* ss6.5: ID plain "example" -> [SOW]@[SOB]example[EOW] */ static void test_spec_word_id_oid() { auto enc = wire::encode_id_plain("example"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x40, 0x1A, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); /* Round-trip */ std::vector frame; frame.push_back(wire::SOM); frame.insert(frame.end(), enc->begin(), enc->end()); frame.push_back(wire::EOM); conf_parse_reset(); conf_parse_bytes(frame.data(), frame.size()); ASSERT_EQ(g_conf.word_count, 1); ASSERT_EQ(g_conf.types[0], '@'); ASSERT_EQ(g_conf.radixes[0], 0); ASSERT_EQ(g_conf.units[0], 0); ASSERT_EQ((int)g_conf.body_lens[0], 7); ASSERT_MEM_EQ(g_conf.bodies[0], "example", 7); } /* ss6.5: ID base32 "4T9X2" -> [SOW]@[SOR]U[SOB]4T9X2[EOW] */ static void test_spec_word_id_bid() { auto enc = wire::encode_id_base32("4T9X2"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); /* Round-trip */ std::vector frame; frame.push_back(wire::SOM); frame.insert(frame.end(), enc->begin(), enc->end()); frame.push_back(wire::EOM); conf_parse_reset(); conf_parse_bytes(frame.data(), frame.size()); ASSERT_EQ(g_conf.word_count, 1); ASSERT_EQ(g_conf.types[0], '@'); ASSERT_EQ(g_conf.radixes[0], 'U'); ASSERT_EQ(g_conf.units[0], 0); ASSERT_EQ((int)g_conf.body_lens[0], 5); ASSERT_MEM_EQ(g_conf.bodies[0], "4T9X2", 5); } /* ss6.5: INTEGER radix=D unit=B body="32" */ static void test_spec_word_integer_decimal_byte() { auto enc = wire::encode_integer(wire::Radix::Decimal, wire::Unit::Byte, "32"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x23, 0x04, 0x44, 0x07, 0x42, 0x1A, '3', '2', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); /* Round-trip */ std::vector frame; frame.push_back(wire::SOM); frame.insert(frame.end(), enc->begin(), enc->end()); frame.push_back(wire::EOM); conf_parse_reset(); conf_parse_bytes(frame.data(), frame.size()); ASSERT_EQ(g_conf.word_count, 1); ASSERT_EQ(g_conf.types[0], '#'); ASSERT_EQ(g_conf.radixes[0], 'D'); ASSERT_EQ(g_conf.units[0], 'B'); ASSERT_EQ((int)g_conf.body_lens[0], 2); ASSERT_MEM_EQ(g_conf.bodies[0], "32", 2); } /* ── The four ss6.5 word examples that had no byte-for-byte check ── * * ANTHEOS-DURATION-WORD-TYPE-UNREGISTERED was found by walking the ss6.2 word * type table against the enum, not by a test failing, so the row asked for the * coverage question to be answered for every row of ss6.5 rather than for its * own. The answer was four: DURATION, TIMESTAMP, BLOB and the hex INTEGER. The * first eight examples were covered, three of them only incidentally inside * frame tests. These use word_encode directly because ss6.5 gives wire bytes and * there is no convenience encoder for these three types — which is itself part * of why they were missed. */ static std::optional> conf_encode_word( wire::WordType type, wire::Radix radix, wire::Unit unit, std::string_view body) { return wire::word_encode(type, radix, unit, reinterpret_cast(body.data()), body.size()); } static void conf_roundtrip_one_word(const std::vector& enc, uint8_t type, uint8_t radix, uint8_t unit, std::string_view body) { std::vector frame; frame.push_back(wire::SOM); frame.insert(frame.end(), enc.begin(), enc.end()); frame.push_back(wire::EOM); conf_parse_reset(); conf_parse_bytes(frame.data(), frame.size()); ASSERT_EQ(g_conf.word_count, 1); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], type); ASSERT_EQ(g_conf.radixes[0], radix); ASSERT_EQ(g_conf.units[0], unit); ASSERT_EQ((int)g_conf.body_lens[0], (int)body.size()); ASSERT_MEM_EQ(g_conf.bodies[0], body.data(), body.size()); } /* ss6.5: DURATION "P3Y6M4DT12H30M5S" -> [SOW]+[SOB]P3Y6M4DT12H30M5S[EOW] * * Before ANTHEOS-DURATION-WORD-TYPE-UNREGISTERED the enum had no '+' member, so * word_encode REFUSED this and the parser drove to Error with zero words on the * spec's own bytes -- while iso::parse_duration round-tripped the identical body * perfectly. Three places agreed and the enum did not. */ static void test_spec_word_duration() { auto enc = conf_encode_word(wire::WordType::Duration, wire::Radix::None, wire::Unit::None, "P3Y6M4DT12H30M5S"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x2B, 0x1A, 'P','3','Y','6','M','4','D','T','1','2','H','3','0','M','5','S', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); conf_roundtrip_one_word(*enc, '+', 0, 0, "P3Y6M4DT12H30M5S"); /* Full stack: the body the parser delivered is the body iso:: accepts, and * the canonical emit is byte-identical to what ss6.5 shows. */ auto du = iso::parse_duration(std::string(g_conf.bodies[0], g_conf.body_lens[0])); ASSERT_TRUE(du.has_value()); ASSERT_TRUE(iso::emit_duration(*du) == "P3Y6M4DT12H30M5S"); /* ss6.1 puts DURATION in the both-forbidden row. */ ASSERT_TRUE(!conf_encode_word(wire::WordType::Duration, wire::Radix::Decimal, wire::Unit::None, "P3D").has_value()); ASSERT_TRUE(!conf_encode_word(wire::WordType::Duration, wire::Radix::None, wire::Unit::Byte, "P3D").has_value()); } /* ss6.5: TIMESTAMP "2026-02-08T12:00:00.000000Z" -> [SOW]&[SOB]...[EOW] */ static void test_spec_word_timestamp() { const std::string body = "2026-02-08T12:00:00.000000Z"; auto enc = conf_encode_word(wire::WordType::Timestamp, wire::Radix::None, wire::Unit::None, body); ASSERT_TRUE(enc.has_value()); /* ss6.2.2 fixes the canonical form at 27 bytes; SOW + WT + SOB + EOW = 4. */ ASSERT_EQ((int)body.size(), 27); ASSERT_EQ(enc->size(), body.size() + 4); ASSERT_EQ((*enc)[0], wire::SOW); ASSERT_EQ((*enc)[1], 0x26); ASSERT_EQ((*enc)[2], wire::SOB); ASSERT_MEM_EQ(enc->data() + 3, body.data(), body.size()); ASSERT_EQ((*enc)[enc->size() - 1], wire::EOW); conf_roundtrip_one_word(*enc, '&', 0, 0, body); auto ts = iso::parse_timestamp(std::string(g_conf.bodies[0], g_conf.body_lens[0])); ASSERT_TRUE(ts.has_value()); ASSERT_TRUE(iso::emit_timestamp(*ts) == body); ASSERT_TRUE(!conf_encode_word(wire::WordType::Timestamp, wire::Radix::Decimal, wire::Unit::None, body).has_value()); ASSERT_TRUE(!conf_encode_word(wire::WordType::Timestamp, wire::Radix::None, wire::Unit::Byte, body).has_value()); } /* ss6.5: BLOB radix=H unit=D body="1A00" -> declares a 6656-byte tail block. * * ANTHEOS-BLOB-RADIX-COVERAGE-GAP was a shipped defect in exactly this word -- * decode_blob_size fell through to 0 for a base-32 radix -- and the ss6.5 * example for it was still not a conformance check. */ static void test_spec_word_blob_hex_dword() { auto enc = conf_encode_word(wire::WordType::Blob, wire::Radix::Hex, wire::Unit::Dword, "1A00"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x2A, 0x04, 0x48, 0x07, 0x44, 0x1A, '1', 'A', '0', '0', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); /* Not parsed as a frame here: a BLOB declares a tail, so a bare SOM/EOM wrap * would leave the parser waiting for 0x1A00 bytes that do not follow. The * byte-for-byte encode is the ss6.5 claim; the tail path has its own tests. */ /* ss6.1: BLOB REQUIRES both flags. Omitting either is a refusal, which is * the half that went unenforced until v1.0.11. */ ASSERT_TRUE(!conf_encode_word(wire::WordType::Blob, wire::Radix::None, wire::Unit::Dword, "1A00").has_value()); ASSERT_TRUE(!conf_encode_word(wire::WordType::Blob, wire::Radix::Hex, wire::Unit::None, "1A00").has_value()); ASSERT_TRUE(!conf_encode_word(wire::WordType::Blob, wire::Radix::None, wire::Unit::None, "1A00").has_value()); } /* ss6.5: INTEGER radix=H unit=D body="FF00" -- the second INTEGER example. */ static void test_spec_word_integer_hex_dword() { auto enc = wire::encode_integer(wire::Radix::Hex, wire::Unit::Dword, "FF00"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x23, 0x04, 0x48, 0x07, 0x44, 0x1A, 'F', 'F', '0', '0', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); conf_roundtrip_one_word(*enc, '#', 'H', 'D', "FF00"); } /* ── The last four ss6.5 examples ── * * These bodies DID appear in the file before -- inside frame tests, where the * word is a component of a larger assertion. That is not the same claim as the * ss6.5 one, which is about the bytes of a single word, and the difference is * why the coverage sweep counted them as unanswered. With these, all twelve * ss6.5 word examples have a byte-for-byte check of their own. */ /* ss6.5: ID decimal 1 -> [SOW]@[SOR]D[SOB]1[EOW] */ static void test_spec_word_id_mid() { auto enc = wire::encode_id_decimal(1); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = {0x12, 0x40, 0x04, 0x44, 0x1A, '1', 0x10}; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); conf_roundtrip_one_word(*enc, '@', 'D', 0, "1"); /* ss6.1 puts ID in the radix-optional, unit-forbidden row. */ ASSERT_TRUE(!conf_encode_word(wire::WordType::Id, wire::Radix::Decimal, wire::Unit::Byte, "1").has_value()); } /* ss6.5: TEXT "temperature" -> [SOW]"[SOB]temperature[EOW] */ static void test_spec_word_text() { auto enc = wire::encode_text("temperature"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x22, 0x1A, 't','e','m','p','e','r','a','t','u','r','e', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); conf_roundtrip_one_word(*enc, '"', 0, 0, "temperature"); ASSERT_TRUE(!conf_encode_word(wire::WordType::Text, wire::Radix::Decimal, wire::Unit::None, "temperature").has_value()); } /* ss6.5: PATH "AA.BB.CC.DD" -> [SOW]/[SOB]AA.BB.CC.DD[EOW] */ static void test_spec_word_path() { auto enc = wire::encode_path("AA.BB.CC.DD"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x2F, 0x1A, 'A','A','.','B','B','.','C','C','.','D','D', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); conf_roundtrip_one_word(*enc, '/', 0, 0, "AA.BB.CC.DD"); } /* ss6.5: LOGICAL "!H&!Q" -> [SOW]?[SOB]!H&!Q[EOW] -- NOT hex AND NOT quadword */ static void test_spec_word_logical() { auto enc = wire::encode_logical("!H&!Q"); ASSERT_TRUE(enc.has_value()); const uint8_t expected[] = { 0x12, 0x3F, 0x1A, '!','H','&','!','Q', 0x10 }; ASSERT_EQ(enc->size(), sizeof(expected)); ASSERT_MEM_EQ(enc->data(), expected, sizeof(expected)); conf_roundtrip_one_word(*enc, '?', 0, 0, "!H&!Q"); } /* ================================================================ * ss9.1 BID Establishment -- 3 wire examples * ================================================================ */ /* ss9.1.4 example 1: Establish BID "4T9X2" */ static void test_spec_establish_bid() { auto f = bus::establish("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x45, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); /* Round-trip parse */ conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'E'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ss9.1.4 example 2: Conflict BID "4T9X2" */ static void test_spec_conflict_bid() { auto f = bus::conflict("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x43, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'C'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ss9.1.4 example 3: Exception "BID_OVERFLOW" */ static void test_spec_establish_failed() { auto f = bus::exception("BID_OVERFLOW"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x58, 0x10, 0x12, 0x22, 0x1A, 'B', 'I', 'D', '_', 'O', 'V', 'E', 'R', 'F', 'L', 'O', 'W', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'X'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "BID_OVERFLOW", 12); } /* ================================================================ * ss9.2 Scaleback -- 3 wire examples * ================================================================ */ /* ss9.2.3 example 1: Full scaleback -- exclusions "!H&!Q", head=32, tail=0 */ static void test_spec_scaleback_full() { auto f = bus::scaleback("!H&!Q", 32, 0); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x53, 0x10, 0x12, 0x3F, 0x1A, '!', 'H', '&', '!', 'Q', 0x10, 0x12, 0x23, 0x04, 0x44, 0x07, 0x57, 0x1A, '3', '2', 0x10, 0x12, 0x23, 0x04, 0x44, 0x07, 0x57, 0x1A, '0', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'S'); ASSERT_EQ(g_conf.types[1], '?'); ASSERT_MEM_EQ(g_conf.bodies[1], "!H&!Q", 5); ASSERT_EQ(g_conf.types[2], '#'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_EQ(g_conf.units[2], 'W'); ASSERT_MEM_EQ(g_conf.bodies[2], "32", 2); ASSERT_EQ(g_conf.types[3], '#'); ASSERT_EQ(g_conf.radixes[3], 'D'); ASSERT_EQ(g_conf.units[3], 'W'); ASSERT_MEM_EQ(g_conf.bodies[3], "0", 1); } /* ss9.2.3 example 2: Sizes only -- no exclusions, head=64, tail=0 */ static void test_spec_scaleback_sizes_only() { auto f = bus::scaleback("", 64, 0); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x53, 0x10, 0x12, 0x23, 0x04, 0x44, 0x07, 0x57, 0x1A, '6', '4', 0x10, 0x12, 0x23, 0x04, 0x44, 0x07, 0x57, 0x1A, '0', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 3); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'S'); ASSERT_EQ(g_conf.types[1], '#'); ASSERT_EQ(g_conf.radixes[1], 'D'); ASSERT_EQ(g_conf.units[1], 'W'); ASSERT_MEM_EQ(g_conf.bodies[1], "64", 2); ASSERT_EQ(g_conf.types[2], '#'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_EQ(g_conf.units[2], 'W'); ASSERT_MEM_EQ(g_conf.bodies[2], "0", 1); } /* ss9.2.3 example 3: Flags only -- exclusions "!Q&!T", no sizes */ static void test_spec_scaleback_flags_only() { auto f = bus::scaleback("!Q&!T", 0, 0); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x53, 0x10, 0x12, 0x3F, 0x1A, '!', 'Q', '&', '!', 'T', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'S'); ASSERT_EQ(g_conf.types[1], '?'); ASSERT_MEM_EQ(g_conf.bodies[1], "!Q&!T", 5); } /* ================================================================ * ss9.3 Path Addressing -- 2 wire examples * ================================================================ */ /* ss9.3.1: Broadcast "Hello" with path "AA" */ static void test_spec_broadcast_path_origin() { auto f = bus::broadcast_path("Hello", "AA"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x42, 0x10, 0x12, 0x22, 0x1A, 'H', 'e', 'l', 'l', 'o', 0x10, 0x12, 0x2F, 0x1A, 'A', 'A', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 3); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'B'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "Hello", 5); ASSERT_EQ(g_conf.types[2], '/'); ASSERT_MEM_EQ(g_conf.bodies[2], "AA", 2); } /* ss9.3.2: Relay "Reply" index=3 path="AA.BB.CC.DD" */ static void test_spec_relay_d_to_a() { auto f = bus::relay("Reply", 3, "AA.BB.CC.DD"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x52, 0x10, 0x12, 0x22, 0x1A, 'R', 'e', 'p', 'l', 'y', 0x10, 0x12, 0x23, 0x04, 0x44, 0x07, 0x42, 0x1A, '3', 0x10, 0x12, 0x2F, 0x1A, 'A', 'A', '.', 'B', 'B', '.', 'C', 'C', '.', 'D', 'D', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'R'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "Reply", 5); ASSERT_EQ(g_conf.types[2], '#'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_EQ(g_conf.units[2], 'B'); ASSERT_MEM_EQ(g_conf.bodies[2], "3", 1); ASSERT_EQ(g_conf.types[3], '/'); ASSERT_MEM_EQ(g_conf.bodies[3], "AA.BB.CC.DD", 11); } /* ================================================================ * ss9.4--9.9 Bus Verbs -- wire examples * ================================================================ */ /* ss9.4: Broadcast "Hello all" */ static void test_spec_broadcast_simple() { auto f = bus::broadcast("Hello all"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x42, 0x10, 0x12, 0x22, 0x1A, 'H', 'e', 'l', 'l', 'o', ' ', 'a', 'l', 'l', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'B'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "Hello all", 9); } /* ss9.5 example 1: Ping specific BID "4T9X2" */ static void test_spec_ping_specific() { auto f = bus::ping("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x50, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'P'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ss9.5 example 3: Ping broadcast (no BID) */ static void test_spec_ping_broadcast() { auto f = bus::ping_all(); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x50, 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 1); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'P'); } /* ss9.6: Discover request -- BID "4T9X2" */ static void test_spec_discover_request() { auto f = bus::discover("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x44, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'D'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ss9.6: Discover response -- "4T9X2" via "7M3K9" */ static void test_spec_discover_response() { auto f = bus::discover_response("4T9X2", "7M3K9"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x44, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '7', 'M', '3', 'K', '9', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 3); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'D'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'U'); ASSERT_MEM_EQ(g_conf.bodies[2], "7M3K9", 5); } /* ss9.7: Verify request -- BID "4T9X2" */ static void test_spec_verify_request() { auto f = bus::verify("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x56, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'V'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ss9.7: Verify response -- OID="example" DID="Thermostat" IID="SN00482" */ static void test_spec_verify_response() { auto f = bus::verify_response("example", "Thermostat", "SN00482"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x56, 0x10, 0x12, 0x40, 0x1A, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x10, 0x12, 0x40, 0x1A, 'T', 'h', 'e', 'r', 'm', 'o', 's', 't', 'a', 't', 0x10, 0x12, 0x40, 0x1A, 'S', 'N', '0', '0', '4', '8', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'V'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 0); ASSERT_MEM_EQ(g_conf.bodies[1], "example", 7); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 0); ASSERT_MEM_EQ(g_conf.bodies[2], "Thermostat", 10); ASSERT_EQ(g_conf.types[3], '@'); ASSERT_EQ(g_conf.radixes[3], 0); ASSERT_MEM_EQ(g_conf.bodies[3], "SN00482", 7); } /* ss9.8: Acknowledge BID "4T9X2" */ static void test_spec_acknowledge() { auto f = bus::acknowledge("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x57, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'W'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ss9.9: Exception "BID_OVERFLOW" */ static void test_spec_exception() { auto f = bus::exception("BID_OVERFLOW"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x58, 0x10, 0x12, 0x22, 0x1A, 'B', 'I', 'D', '_', 'O', 'V', 'E', 'R', 'F', 'L', 'O', 'W', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'X'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "BID_OVERFLOW", 12); } /* ================================================================ * ss10 Service Operations -- 3 wire examples * ================================================================ */ /* ss10.1: Query "temperature" */ static void test_spec_query() { auto f = service::query("temperature"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x51, 0x10, 0x12, 0x22, 0x1A, 't', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'Q'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "temperature", 11); } /* ss10.2: Offer BID="4T9X2" desc="temperature_celsius" */ static void test_spec_offer() { auto f = service::offer("4T9X2", "temperature_celsius"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x4F, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x12, 0x22, 0x1A, 't', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e', '_', 'c', 'e', 'l', 's', 'i', 'u', 's', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 3); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'O'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); ASSERT_EQ(g_conf.types[2], '"'); ASSERT_MEM_EQ(g_conf.bodies[2], "temperature_celsius", 19); } /* ss10.3: Accept BID="4T9X2" */ static void test_spec_accept() { auto f = service::accept("4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x41, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'A'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "4T9X2", 5); } /* ================================================================ * ss11 Session Operations -- wire examples * ================================================================ */ /* ss11.3: Call SID="A7K2M" MID=1 "get_reading" */ static void test_spec_session_call() { auto f = session::call("A7K2M", "", 1, "get_reading"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x4B, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x44, 0x1A, '1', 0x10, 0x12, 0x22, 0x1A, 'g', 'e', 't', '_', 'r', 'e', 'a', 'd', 'i', 'n', 'g', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'K'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_MEM_EQ(g_conf.bodies[2], "1", 1); ASSERT_EQ(g_conf.types[3], '"'); ASSERT_MEM_EQ(g_conf.bodies[3], "get_reading", 11); } /* ss11.3: Call response SID="A7K2M" MID=2 "23.5" */ static void test_spec_session_call_response() { auto f = session::call("A7K2M", "", 2, "23.5"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x4B, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x44, 0x1A, '2', 0x10, 0x12, 0x22, 0x1A, '2', '3', '.', '5', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'K'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_MEM_EQ(g_conf.bodies[2], "2", 1); ASSERT_EQ(g_conf.types[3], '"'); ASSERT_MEM_EQ(g_conf.bodies[3], "23.5", 4); } /* ss11.4: Status request SID="A7K2M" MID=2 */ static void test_spec_session_status_request() { auto f = session::status("A7K2M", "", 2); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x54, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x44, 0x1A, '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 3); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'T'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_MEM_EQ(g_conf.bodies[2], "2", 1); } /* ss11.4: Status response SID="A7K2M" MID=3 "ACTIVE" */ static void test_spec_session_status_response() { auto f = session::status_response("A7K2M", "", 3, "ACTIVE"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x54, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x44, 0x1A, '3', 0x10, 0x12, 0x22, 0x1A, 'A', 'C', 'T', 'I', 'V', 'E', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'T'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_MEM_EQ(g_conf.bodies[2], "3", 1); ASSERT_EQ(g_conf.types[3], '"'); ASSERT_MEM_EQ(g_conf.bodies[3], "ACTIVE", 6); } /* ss11.5: Notify SID="A7K2M" MID=3 "threshold_exceeded" */ static void test_spec_session_notify() { auto f = session::notify("A7K2M", "", 3, "threshold_exceeded"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x4E, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x44, 0x1A, '3', 0x10, 0x12, 0x22, 0x1A, 't', 'h', 'r', 'e', 's', 'h', 'o', 'l', 'd', '_', 'e', 'x', 'c', 'e', 'e', 'd', 'e', 'd', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'N'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_MEM_EQ(g_conf.bodies[2], "3", 1); ASSERT_EQ(g_conf.types[3], '"'); ASSERT_MEM_EQ(g_conf.bodies[3], "threshold_exceeded", 18); } /* ss11.6: Locate request SID="A7K2M" */ static void test_spec_session_locate_request() { auto f = session::locate("A7K2M"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x4C, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'L'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); } /* ss11.6: Locate response SID="A7K2M" at BID="4T9X2" */ static void test_spec_session_locate_response() { auto f = session::locate_response("A7K2M", "4T9X2"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x4C, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, '4', 'T', '9', 'X', '2', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 3); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'L'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'U'); ASSERT_MEM_EQ(g_conf.bodies[2], "4T9X2", 5); } /* ss11.7: Resume request SID="A7K2M" */ static void test_spec_session_resume() { auto f = session::resume("A7K2M"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x55, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'U'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); } /* ss11.7: Resume success -- Status response SID="A7K2M" MID=47 "ACTIVE" */ static void test_spec_session_resume_success() { auto f = session::status_response("A7K2M", "", 47, "ACTIVE"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x54, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x12, 0x40, 0x04, 0x44, 0x1A, '4', '7', 0x10, 0x12, 0x22, 0x1A, 'A', 'C', 'T', 'I', 'V', 'E', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 4); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'T'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); ASSERT_EQ(g_conf.types[2], '@'); ASSERT_EQ(g_conf.radixes[2], 'D'); ASSERT_MEM_EQ(g_conf.bodies[2], "47", 2); ASSERT_EQ(g_conf.types[3], '"'); ASSERT_MEM_EQ(g_conf.bodies[3], "ACTIVE", 6); } /* ss11.7: Resume failure -- Exception "SESSION_EXPIRED" */ static void test_spec_session_resume_failure() { auto f = bus::exception("SESSION_EXPIRED"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x58, 0x10, 0x12, 0x22, 0x1A, 'S', 'E', 'S', 'S', 'I', 'O', 'N', '_', 'E', 'X', 'P', 'I', 'R', 'E', 'D', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'X'); ASSERT_EQ(g_conf.types[1], '"'); ASSERT_MEM_EQ(g_conf.bodies[1], "SESSION_EXPIRED", 15); } /* ss11.8: Finish SID="A7K2M" */ static void test_spec_session_finish() { auto f = session::finish("A7K2M"); ASSERT_TRUE(f.has_value()); const uint8_t expected[] = { 0x02, 0x12, 0x21, 0x1A, 0x46, 0x10, 0x12, 0x40, 0x04, 0x55, 0x1A, 'A', '7', 'K', '2', 'M', 0x10, 0x03 }; ASSERT_EQ(f->size(), sizeof(expected)); ASSERT_MEM_EQ(f->data(), expected, sizeof(expected)); conf_parse_reset(); conf_parse_bytes(f->data(), f->size()); ASSERT_EQ(g_conf.word_count, 2); ASSERT_EQ(g_conf.msg_count, 1); ASSERT_EQ(g_conf.types[0], '!'); ASSERT_EQ(g_conf.bodies[0][0], 'F'); ASSERT_EQ(g_conf.types[1], '@'); ASSERT_EQ(g_conf.radixes[1], 'U'); ASSERT_MEM_EQ(g_conf.bodies[1], "A7K2M", 5); } /* ================================================================ * Suite entry point * ================================================================ */ void test_conformance_run(int& out_run, int& out_passed) { std::printf("\n[conformance -- v9 spec wire examples]\n"); /* ss6.5 Word Encoding */ TEST(test_spec_word_symbol_E); TEST(test_spec_word_id_oid); TEST(test_spec_word_id_bid); TEST(test_spec_word_integer_decimal_byte); TEST(test_spec_word_integer_hex_dword); TEST(test_spec_word_blob_hex_dword); TEST(test_spec_word_timestamp); TEST(test_spec_word_duration); TEST(test_spec_word_id_mid); TEST(test_spec_word_text); TEST(test_spec_word_path); TEST(test_spec_word_logical); /* ss9.1 BID Establishment */ TEST(test_spec_establish_bid); TEST(test_spec_conflict_bid); TEST(test_spec_establish_failed); /* ss9.2 Scaleback */ TEST(test_spec_scaleback_full); TEST(test_spec_scaleback_sizes_only); TEST(test_spec_scaleback_flags_only); /* ss9.3 Path Addressing */ TEST(test_spec_broadcast_path_origin); TEST(test_spec_relay_d_to_a); /* ss9.4-9.9 Bus Verbs */ TEST(test_spec_broadcast_simple); TEST(test_spec_ping_specific); TEST(test_spec_ping_broadcast); TEST(test_spec_discover_request); TEST(test_spec_discover_response); TEST(test_spec_verify_request); TEST(test_spec_verify_response); TEST(test_spec_acknowledge); TEST(test_spec_exception); /* ss10 Service Operations */ TEST(test_spec_query); TEST(test_spec_offer); TEST(test_spec_accept); /* ss11 Session Operations */ TEST(test_spec_session_call); TEST(test_spec_session_call_response); TEST(test_spec_session_status_request); TEST(test_spec_session_status_response); TEST(test_spec_session_notify); TEST(test_spec_session_locate_request); TEST(test_spec_session_locate_response); TEST(test_spec_session_resume); TEST(test_spec_session_resume_success); TEST(test_spec_session_resume_failure); TEST(test_spec_session_finish); out_run = tests_run; out_passed = tests_passed; }