#include #include "secsgem/hsms/header.hpp" #include "secsgem/secs2/codec.hpp" using namespace secsgem::hsms; namespace s2 = secsgem::secs2; TEST_CASE("data message header round-trip") { Header h = Header::data_message(0x1234, 1, 13, true, 0xAABBCCDD); auto bytes = h.encode(); Header back = Header::decode(bytes.data()); CHECK(back == h); CHECK(back.stype == SType::Data); CHECK(back.w_bit()); CHECK(back.stream() == 1); CHECK(back.function() == 13); CHECK(back.session_id == 0x1234); CHECK(back.system_bytes == 0xAABBCCDD); } TEST_CASE("data message header byte layout") { Header h = Header::data_message(0x0001, 1, 1, true, 0x00000001); auto b = h.encode(); std::array expected{0x00, 0x01, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; CHECK(b == expected); } TEST_CASE("W-bit clear when no reply expected") { Header h = Header::data_message(0, 6, 11, false, 7); CHECK_FALSE(h.w_bit()); CHECK((h.byte2 & 0x80) == 0); CHECK(h.stream() == 6); } TEST_CASE("control header layout (Select.req)") { Header h = Header::control(SType::SelectReq, 0x00000005); auto b = h.encode(); std::array expected{0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05}; CHECK(b == expected); Header back = Header::decode(b.data()); CHECK(back == h); CHECK(back.stype == SType::SelectReq); } TEST_CASE("select.rsp carries status in byte3") { Header h = Header::control(SType::SelectRsp, 5, kControlSessionId, 0, static_cast(SelectStatus::Ok)); CHECK(h.byte3 == 0); Header back = Header::decode(h.encode().data()); CHECK(back.stype == SType::SelectRsp); CHECK(back.byte3 == static_cast(SelectStatus::Ok)); } TEST_CASE("frame round-trip with body") { s2::Item body_item = s2::Item::list({s2::Item::ascii("MDLN"), s2::Item::ascii("1.0")}); std::vector body = s2::encode(body_item); Frame f(Header::data_message(0, 1, 2, false, 42), body); auto wire = f.encode(); // length prefix counts header + body uint32_t len = (wire[0] << 24) | (wire[1] << 16) | (wire[2] << 8) | wire[3]; CHECK(len == kHeaderSize + body.size()); CHECK(wire.size() == kLengthPrefixSize + len); Frame back = Frame::decode(wire.data() + kLengthPrefixSize, len); CHECK(back.header == f.header); CHECK(back.body == body); CHECK(s2::decode(back.body) == body_item); } TEST_CASE("control frame has empty body") { Frame f(Header::control(SType::LinktestReq, 99)); auto wire = f.encode(); uint32_t len = (wire[0] << 24) | (wire[1] << 16) | (wire[2] << 8) | wire[3]; CHECK(len == kHeaderSize); Frame back = Frame::decode(wire.data() + kLengthPrefixSize, len); CHECK(back.body.empty()); CHECK(back.header.stype == SType::LinktestReq); } TEST_CASE("frame decode rejects short payload") { std::vector tooShort(5, 0); CHECK_THROWS_AS(Frame::decode(tooShort.data(), tooShort.size()), FrameError); } TEST_CASE("E37 SType / status / reject-reason wire values") { // SType (E37 §8.3). Defined set is {0,1,2,3,4,5,6,7,9}. CHECK(static_cast(SType::Data) == 0); CHECK(static_cast(SType::SelectReq) == 1); CHECK(static_cast(SType::SelectRsp) == 2); CHECK(static_cast(SType::DeselectReq) == 3); CHECK(static_cast(SType::DeselectRsp) == 4); CHECK(static_cast(SType::LinktestReq) == 5); CHECK(static_cast(SType::LinktestRsp) == 6); CHECK(static_cast(SType::RejectReq) == 7); CHECK(static_cast(SType::SeparateReq) == 9); // Select.rsp status (E37 §7.2). CHECK(static_cast(SelectStatus::Ok) == 0); CHECK(static_cast(SelectStatus::AlreadyActive) == 1); CHECK(static_cast(SelectStatus::NotReady) == 2); CHECK(static_cast(SelectStatus::ConnectExhaust) == 3); // Deselect.rsp status (E37 §7.4). CHECK(static_cast(DeselectStatus::Ok) == 0); CHECK(static_cast(DeselectStatus::NotEstablished) == 1); CHECK(static_cast(DeselectStatus::Busy) == 2); // Reject reason (E37 §7.7). CHECK(static_cast(RejectReason::StypeNotSupported) == 1); CHECK(static_cast(RejectReason::PtypeNotSupported) == 2); CHECK(static_cast(RejectReason::TransactionNotOpen) == 3); CHECK(static_cast(RejectReason::EntityNotSelected) == 4); }