#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) == " >"); }