#include "unity.h" #include #include #include #include "gip/protocol.hpp" void setUp() {} void tearDown() {} namespace { using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; class mock_transport : public xone::gip::transport { public: std::vector> frames; auto send_frame(std::span frame) -> int override { frames.emplace_back(frame.begin(), frame.end()); return 0; } }; class mock_listener : public xone::gip::client_listener { public: int added = 0; int removed = 0; std::vector> inputs; auto on_client_added(xone::gip::client&) -> void override { ++added; } auto on_client_removed(u8) -> void override { ++removed; } auto on_input(xone::gip::client&, std::span data) -> void override { inputs.emplace_back(data.begin(), data.end()); } }; // Build a controller -> host GIP frame with a simple (single-byte varint) // header. Payloads here stay below 128 bytes so the header is even without // the pad byte. auto make_frame(u8 command, u8 options, u8 sequence, std::vector const& payload) -> std::vector { std::vector f; f.push_back(command); f.push_back(options); f.push_back(sequence); u32 len = static_cast(payload.size()); do { u8 b = static_cast(len & 0x7f); len >>= 7; if (len) b |= 0x80; f.push_back(b); } while (len); f.insert(f.end(), payload.begin(), payload.end()); return f; } auto make_announce() -> std::vector { std::vector p(28, 0); p[8] = 0x5e; // vendor 0x045e (LE) p[9] = 0x04; p[10] = 0x12; // product 0x0b12 p[11] = 0x0b; p[12] = 0x05; // fw major p[14] = 0x0f; // fw minor return p; } auto make_identify() -> std::vector { // identify payload = 16 unknown bytes + 8 LE16 offsets + info elements. // The offsets are relative to the base right after the unknown header // (so they include the 16-byte offsets array itself: first element at 16). auto put16 = [](std::vector& v, u16 x) { v.push_back(static_cast(x & 0xFF)); v.push_back(static_cast(x >> 8)); }; std::vector elems; // classes at info offset 16: count=1, str_len=10, "controller" elems.push_back(1); put16(elems, 10); for (char c : std::string("controller")) elems.push_back(static_cast(c)); // firmware_versions at 29: count=1, {5, 15} elems.push_back(1); elems.push_back(5); elems.push_back(0); elems.push_back(15); elems.push_back(0); // capabilities_out at 34: count=1, 0x0f elems.push_back(1); elems.push_back(0x0f); // capabilities_in at 36: count=1, 0x0f elems.push_back(1); elems.push_back(0x0f); // interfaces at 38: count=1, 16-byte guid elems.push_back(1); for (int i = 0; i < 16; ++i) elems.push_back(static_cast(i)); u16 offsets[8] = {0, 29, 0, 34, 36, 16, 38, 0}; std::vector payload(16, 0); // unknown header for (u16 off : offsets) put16(payload, off); payload.insert(payload.end(), elems.begin(), elems.end()); return payload; } } // namespace void test_announce_requests_identification(void) { mock_transport transport; mock_listener listener; xone::gip::adapter adap{transport, listener}; TEST_ASSERT_EQUAL_INT(0, adap.process_buffer(make_frame(0x02, 0x21, 1, make_announce()))); TEST_ASSERT_EQUAL_INT(1, transport.frames.size()); auto const& f = transport.frames[0]; TEST_ASSERT_EQUAL_INT(4, f.size()); // command + options + seq + len TEST_ASSERT_EQUAL_UINT8(0x04, f[0]); // identify TEST_ASSERT_EQUAL_UINT8(0x21, f[1]); // id 1 | internal TEST_ASSERT_EQUAL_UINT8(0x01, f[2]); // sequence TEST_ASSERT_EQUAL_UINT8(0x00, f[3]); // zero packet length } void test_identify_adds_client_and_starts_auth(void) { mock_transport transport; mock_listener listener; xone::gip::adapter adap{transport, listener}; TEST_ASSERT_EQUAL_INT(0, adap.process_buffer(make_frame(0x04, 0x21, 2, make_identify()))); TEST_ASSERT_EQUAL_INT(1, listener.added); TEST_ASSERT_EQUAL_INT(1, adap.client_count()); // auth started: one AUTHENTICATE host hello frame was sent TEST_ASSERT_EQUAL_INT(1, transport.frames.size()); auto const& f = transport.frames[0]; TEST_ASSERT_EQUAL_UINT8(0x06, f[0]); // authenticate TEST_ASSERT_EQUAL_UINT8(0x31, f[1]); // id 1 | internal | ack TEST_ASSERT_EQUAL_INT(62, f.size()); // 4-byte header + 58-byte hello } void test_input_delivers_to_listener(void) { mock_transport transport; mock_listener listener; xone::gip::adapter adap{transport, listener}; std::vector payload = {0x11, 0x22, 0x33, 0x44}; TEST_ASSERT_EQUAL_INT(0, adap.process_buffer(make_frame(0x20, 0x01, 3, payload))); TEST_ASSERT_EQUAL_INT(1, listener.inputs.size()); TEST_ASSERT_EQUAL_UINT8_ARRAY(payload.data(), listener.inputs[0].data(), 4); } void test_status_disconnect_removes_client(void) { mock_transport transport; mock_listener listener; xone::gip::adapter adap{transport, listener}; adap.get_client(1); TEST_ASSERT_EQUAL_INT(1, adap.client_count()); std::vector payload = {0x00, 0, 0, 0}; // status, not connected TEST_ASSERT_EQUAL_INT(0, adap.process_buffer(make_frame(0x03, 0x21, 4, payload))); TEST_ASSERT_EQUAL_INT(1, listener.removed); TEST_ASSERT_EQUAL_INT(0, adap.client_count()); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_announce_requests_identification); RUN_TEST(test_identify_adds_client_and_starts_auth); RUN_TEST(test_input_delivers_to_listener); RUN_TEST(test_status_disconnect_removes_client); return UNITY_END(); }