test_framing.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import pytest
  2. from cq2_sdk.transport.framing import (
  3. HEADER_SIZE,
  4. PLAIN_HEADER_SIZE,
  5. WIRE_HEADER_SIZE,
  6. FrameHeader,
  7. decode_frame,
  8. decode_header,
  9. encode_frame,
  10. )
  11. from cq2_sdk.transport.encode6 import decode6, encode6
  12. from cq2_sdk.messages import constants as C
  13. HEARTBEAT_HEADER = bytes.fromhex(
  14. "0000000060ea0000b0cdac6a000000000000000095000000000000000000000070000000"
  15. )
  16. HEARTBEAT_WIRE_HEADER = bytes.fromhex(
  17. "5d5452415d4c67445d545078495e55525d5452415d545241"
  18. "5d54507a5d5452415d5452415d5452415d54527b5d545241"
  19. )
  20. def test_encode6_matches_captured_heartbeat_header():
  21. assert encode6(HEARTBEAT_HEADER) == HEARTBEAT_WIRE_HEADER
  22. def test_decode6_restores_captured_heartbeat_header():
  23. assert decode6(HEARTBEAT_WIRE_HEADER, expected_size=36) == HEARTBEAT_HEADER
  24. def test_decode_captured_heartbeat_uses_verified_header_field_order():
  25. header = decode_header(HEARTBEAT_WIRE_HEADER)
  26. assert header.param3 == 0
  27. assert header.msg_id == C.CS_60000
  28. assert header.recog == 1789709744
  29. assert header.param1 == 0
  30. assert header.param2 == 0
  31. assert header.sdata == 149
  32. assert header.codec == 0
  33. assert header.body_len == 0
  34. assert header.sequence == 112
  35. def test_decode6_rejects_non_wire_character():
  36. with pytest.raises(ValueError, match="outside 6-bit wire range"):
  37. decode6(b"<~")
  38. def test_header_is_48_bytes_on_wire():
  39. raw = encode_frame(1, 0, 0, 0, 0, b"")
  40. assert len(raw) == WIRE_HEADER_SIZE
  41. assert PLAIN_HEADER_SIZE == 36
  42. assert WIRE_HEADER_SIZE == 48
  43. assert HEADER_SIZE == WIRE_HEADER_SIZE
  44. def test_encode_decode_roundtrip():
  45. body = b"hello"
  46. raw = encode_frame(
  47. C.CS_2001,
  48. recog=900000,
  49. param1=12345,
  50. param2=0,
  51. param3=7,
  52. body=body,
  53. sdata=11,
  54. sequence=42,
  55. )
  56. assert len(raw) == WIRE_HEADER_SIZE + len(body)
  57. hdr, decoded_body = decode_frame(raw)
  58. assert hdr.param3 == 7
  59. assert hdr.msg_id == C.CS_2001
  60. assert hdr.recog == 900000
  61. assert hdr.param1 == 12345
  62. assert hdr.param2 == 0
  63. assert hdr.sdata == 11
  64. assert hdr.body_len == 5
  65. assert hdr.codec == 0
  66. assert hdr.sequence == 42
  67. assert decoded_body == body
  68. def test_body_length_field_matches():
  69. body = b"x" * 100
  70. raw = encode_frame(1, 0, 0, 0, 0, body)
  71. hdr, _ = decode_frame(raw)
  72. assert hdr.body_len == 100
  73. def test_empty_body():
  74. raw = encode_frame(C.CS_100, 0, 0, 0, 0, b"")
  75. hdr, body = decode_frame(raw)
  76. assert body == b""
  77. def test_truncated_body_raises():
  78. raw = encode_frame(1, 0, 0, 0, 0, b"hello world")
  79. truncated = raw[:WIRE_HEADER_SIZE + 4] # cut off in the middle of the body
  80. with pytest.raises(ValueError, match="truncated"):
  81. decode_frame(truncated)
  82. def test_too_short_raises():
  83. with pytest.raises(ValueError, match="too short"):
  84. decode_header(b"\x00" * 10)
  85. def test_negative_recog_allowed():
  86. # recog is signed int; can be negative for error codes
  87. raw = encode_frame(1, recog=-1, param1=0, param2=0, param3=0, body=b"")
  88. hdr, _ = decode_frame(raw)
  89. assert hdr.recog == -1
  90. def test_unsupported_body_codec_raises():
  91. raw = encode_frame(1, 0, 0, 0, 0, b"compressed", codec=1)
  92. with pytest.raises(ValueError, match="unsupported body codec: 1"):
  93. decode_frame(raw)
  94. def test_decode_header_reads_the_first_wire_header_from_a_complete_frame():
  95. raw = encode_frame(529, 1, 0, 0, 0, b"response", sequence=8)
  96. header = decode_header(raw)
  97. assert header.msg_id == 529
  98. assert header.sequence == 8
  99. def test_constants_loaded():
  100. # Smoke check: a few known constants are importable and distinct
  101. assert C.CS_2001 == 2001
  102. assert C.SC_529 == 529
  103. assert C.SC_530 == 530
  104. assert C.ALL_TASKS == [f"t{n:02d}" for n in range(1, 14)]