#include #include "secsgem/secs2/codec.hpp" #include "secsgem/secs2/item.hpp" #include "secsgem/secs2/message.hpp" using namespace secsgem::secs2; TEST_CASE("encode known byte layouts") { CHECK(encode(Item::ascii("ABC")) == std::vector{0x41, 0x03, 'A', 'B', 'C'}); CHECK(encode(Item::u1(uint8_t{255})) == std::vector{0xA5, 0x01, 0xFF}); CHECK(encode(Item::u4(uint32_t{0x01020304})) == std::vector{0xB1, 0x04, 0x01, 0x02, 0x03, 0x04}); CHECK(encode(Item::boolean(true)) == std::vector{0x25, 0x01, 0x01}); CHECK(encode(Item::binary({0xDE, 0xAD})) == std::vector{0x21, 0x02, 0xDE, 0xAD}); // L [2] CHECK(encode(Item::list({Item::ascii("MD"), Item::u2(uint16_t{258})})) == std::vector{0x01, 0x02, 0x41, 0x02, 'M', 'D', 0xA9, 0x02, 0x01, 0x02}); } TEST_CASE("round-trip scalar items") { auto rt = [](const Item& i) { return decode(encode(i)); }; CHECK(rt(Item::ascii("hello world")) == Item::ascii("hello world")); CHECK(rt(Item::ascii("")) == Item::ascii("")); CHECK(rt(Item::u1(uint8_t{0})) == Item::u1(uint8_t{0})); CHECK(rt(Item::u2(uint16_t{0xBEEF})) == Item::u2(uint16_t{0xBEEF})); CHECK(rt(Item::u4(uint32_t{0xDEADBEEF})) == Item::u4(uint32_t{0xDEADBEEF})); CHECK(rt(Item::u8(uint64_t{0x0102030405060708ULL})) == Item::u8(uint64_t{0x0102030405060708ULL})); CHECK(rt(Item::i1(int8_t{-5})) == Item::i1(int8_t{-5})); CHECK(rt(Item::i2(int16_t{-1234})) == Item::i2(int16_t{-1234})); CHECK(rt(Item::i4(int32_t{-123456})) == Item::i4(int32_t{-123456})); CHECK(rt(Item::i8(int64_t{-9000000000LL})) == Item::i8(int64_t{-9000000000LL})); CHECK(rt(Item::binary({1, 2, 3, 255, 0})) == Item::binary({1, 2, 3, 255, 0})); CHECK(rt(Item::boolean({1, 0, 1})) == Item::boolean({1, 0, 1})); } TEST_CASE("round-trip floating point preserves bit pattern") { CHECK(decode(encode(Item::f4(3.14159f))) == Item::f4(3.14159f)); CHECK(decode(encode(Item::f8(2.718281828459045))) == Item::f8(2.718281828459045)); CHECK(decode(encode(Item::f4({1.0f, -2.5f, 0.0f}))) == Item::f4({1.0f, -2.5f, 0.0f})); } TEST_CASE("round-trip nested lists") { Item nested = Item::list({ Item::ascii("EQUIP-01"), Item::list({Item::u4(uint32_t{12}), Item::u4(uint32_t{34})}), Item::list({}), // empty list Item::binary({0xFF}), }); CHECK(decode(encode(nested)) == nested); } TEST_CASE("multi-element numeric arrays") { auto v = Item::u2({1, 2, 3, 4, 5}); CHECK(v.size() == 5); CHECK(decode(encode(v)) == v); } TEST_CASE("decode rejects truncated input") { CHECK_THROWS_AS(decode(std::vector{0x41, 0x03, 'A'}), CodecError); // ascii len 3, 1 byte CHECK_THROWS_AS(decode(std::vector{0x41}), CodecError); // missing length byte CHECK_THROWS_AS(decode(std::vector{}), CodecError); // empty } TEST_CASE("decode rejects trailing bytes") { CHECK_THROWS_AS(decode(std::vector{0xA5, 0x01, 0xFF, 0x00}), CodecError); } TEST_CASE("message body round-trip") { Item body = Item::list({Item::ascii("MDLN-1"), Item::ascii("1.0.0")}); Message m(1, 2, false, body); auto bytes = m.encode_body(); Message decoded = Message::from_body(1, 2, false, bytes); REQUIRE(decoded.body.has_value()); CHECK(*decoded.body == body); CHECK(decoded.stream == 1); CHECK(decoded.function == 2); } TEST_CASE("empty message body") { Message m(1, 1, true); // S1F1 W, header-only CHECK(m.encode_body().empty()); Message decoded = Message::from_body(1, 1, true, {}); CHECK_FALSE(decoded.body.has_value()); } TEST_CASE("SML rendering") { Item body = Item::list({Item::ascii("MDLN"), Item::u4(uint32_t{42})}); CHECK(to_sml(body) == " >"); } TEST_CASE("JIS-8 encode/decode (E5 §9.5)") { // Format byte for JIS-8 = 0x11 << 2 | 0x01 = 0x45 with 1-byte length. // 3 bytes payload "abc" (we don't bother with real JIS chars; the wire // format is byte-identical to ASCII, only the format code differs). Item j = Item::jis8("abc"); auto bytes = encode(j); CHECK(bytes == std::vector{0x45, 0x03, 'a', 'b', 'c'}); Item back = decode(bytes); CHECK(back.format() == Format::JIS8); CHECK(back == j); } TEST_CASE("C2 (Unicode 2-byte) encode/decode (E5 §9.5)") { // 0x12 << 2 | 0x01 = 0x49 format byte, 1-byte length, then 2 bytes per // code point big-endian. Code points: U+00E9 (é), U+4E2D (中). Item c = Item::c2({0x00E9, 0x4E2D}); auto bytes = encode(c); CHECK(bytes == std::vector{0x49, 0x04, 0x00, 0xE9, 0x4E, 0x2D}); Item back = decode(bytes); CHECK(back.format() == Format::C2); CHECK(back == c); } TEST_CASE("JIS-8 and C2 disambiguate from ASCII / U2 by Format") { // Same backing storage, different format code → not equal. CHECK(Item::jis8("hi") != Item::ascii("hi")); CHECK(Item::c2({0x41, 0x42}) != Item::u2({0x41, 0x42})); } TEST_CASE("SML rendering tags JIS-8 with J and C2 with C") { CHECK(to_sml(Item::jis8("hi")) == ""); CHECK(to_sml(Item::c2({0x41, 0x42})) == ""); }