hardcoded.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. """
  2. Hard-coded Command Classes for Trixy Network Protocol
  3. This module implements the hard-coded commands specified in CLAUDE.md for
  4. performance optimization. These commands are sent as simple text strings
  5. rather than serialized objects to minimize overhead.
  6. Hard-coded Commands (all start with "TRXI"):
  7. - TRXINOOP - No-op/heartbeat command
  8. - TRXIPING - Ping command for connectivity testing
  9. - TRXIPONG - Pong response to ping
  10. - TRXIPRNT <string> - Print string command (for testing)
  11. - TRXYHELO - Hello command (for debugging)
  12. These commands bypass normal serialization for maximum performance and
  13. are handled specially by the protocol layer.
  14. """
  15. import time
  16. from typing import Optional
  17. from dataclasses import dataclass, field
  18. from .base import TrixyCommand, CommandResponse, ValidationError
  19. def pprint(message: str) -> None:
  20. """Hard-coded command logging function."""
  21. print(f"[CMD.HARDCODED] {message}")
  22. @dataclass
  23. class NoOpCommand(TrixyCommand):
  24. """
  25. No-operation command for heartbeat/keepalive functionality.
  26. This command does nothing but confirm the connection is alive.
  27. Used for periodic heartbeat between client and server.
  28. """
  29. def validate(self) -> bool:
  30. """Validate no-op command (always valid)."""
  31. return True
  32. def get_command_name(self) -> str:
  33. """Get command name."""
  34. return "TRXINOOP"
  35. def get_timeout(self) -> float:
  36. """Short timeout for heartbeat."""
  37. return 5.0
  38. def get_priority(self) -> int:
  39. """Low priority for heartbeat."""
  40. return 90
  41. @dataclass
  42. class PingCommand(TrixyCommand):
  43. """
  44. Ping command for connectivity testing and latency measurement.
  45. The recipient should respond with a PongCommand containing the
  46. same ping_id for round-trip time calculation.
  47. """
  48. ping_id: str = field(default_factory=lambda: str(int(time.time() * 1000)))
  49. def validate(self) -> bool:
  50. """Validate ping command."""
  51. if not self.ping_id:
  52. raise ValidationError("PingCommand must have a ping_id")
  53. return True
  54. def get_command_name(self) -> str:
  55. """Get command name."""
  56. return "TRXIPING"
  57. def get_response_class(self):
  58. """Expect a pong response."""
  59. return PongCommand
  60. def get_timeout(self) -> float:
  61. """Short timeout for ping."""
  62. return 10.0
  63. def get_priority(self) -> int:
  64. """High priority for connectivity testing."""
  65. return 20
  66. @dataclass
  67. class PongCommand(TrixyCommand):
  68. """
  69. Pong response to ping command.
  70. This command is sent in response to a PingCommand and should
  71. contain the same ping_id for latency calculation.
  72. """
  73. ping_id: str = field(default="")
  74. original_timestamp: Optional[float] = field(default=None)
  75. def validate(self) -> bool:
  76. """Validate pong command."""
  77. if not self.ping_id:
  78. raise ValidationError("PongCommand must have a ping_id")
  79. return True
  80. def get_command_name(self) -> str:
  81. """Get command name."""
  82. return "TRXIPONG"
  83. def get_timeout(self) -> float:
  84. """Short timeout for pong."""
  85. return 5.0
  86. def get_priority(self) -> int:
  87. """High priority for connectivity testing."""
  88. return 20
  89. def calculate_rtt(self) -> Optional[float]:
  90. """
  91. Calculate round-trip time if original timestamp is available.
  92. Returns:
  93. Optional[float]: RTT in seconds or None if no original timestamp
  94. """
  95. if self.original_timestamp:
  96. return time.time() - self.original_timestamp
  97. return None
  98. @dataclass
  99. class PrintCommand(TrixyCommand):
  100. """
  101. Print command for debugging and testing.
  102. This command instructs the recipient to print/log the provided text.
  103. Useful for debugging network connectivity and command flow.
  104. """
  105. text: str = field(default="")
  106. def validate(self) -> bool:
  107. """Validate print command."""
  108. if not isinstance(self.text, str):
  109. raise ValidationError("PrintCommand text must be a string")
  110. return True
  111. def get_command_name(self) -> str:
  112. """Get command name."""
  113. return f"TRXIPRNT {self.text}"
  114. def get_timeout(self) -> float:
  115. """Short timeout for print."""
  116. return 5.0
  117. def get_priority(self) -> int:
  118. """Low priority for debugging."""
  119. return 80
  120. @dataclass
  121. class HelloCommand(TrixyCommand):
  122. """
  123. Hello command for debugging and initial handshake.
  124. This command can be used for debugging connectivity or as part
  125. of an initial handshake sequence.
  126. """
  127. message: Optional[str] = field(default=None)
  128. def validate(self) -> bool:
  129. """Validate hello command."""
  130. return True
  131. def get_command_name(self) -> str:
  132. """Get command name."""
  133. if self.message:
  134. return f"TRXYHELO {self.message}"
  135. return "TRXYHELO"
  136. def get_timeout(self) -> float:
  137. """Short timeout for hello."""
  138. return 10.0
  139. def get_priority(self) -> int:
  140. """Normal priority for hello."""
  141. return 50
  142. # Response classes for hard-coded commands
  143. @dataclass
  144. class PingPongResponse(CommandResponse):
  145. """Response for ping/pong commands with latency information."""
  146. ping_id: str = field(default="")
  147. rtt_ms: Optional[float] = field(default=None)
  148. def validate(self) -> bool:
  149. """Validate ping-pong response."""
  150. if not self.ping_id:
  151. raise ValidationError("PingPongResponse must have a ping_id")
  152. return True
  153. def get_response_name(self) -> str:
  154. """Get response name."""
  155. return "PingPongResponse"
  156. # Utility functions for hard-coded commands
  157. def create_heartbeat() -> NoOpCommand:
  158. """
  159. Create a heartbeat (no-op) command.
  160. Returns:
  161. NoOpCommand: Heartbeat command
  162. """
  163. return NoOpCommand()
  164. def create_ping(ping_id: Optional[str] = None) -> PingCommand:
  165. """
  166. Create a ping command with optional custom ping ID.
  167. Args:
  168. ping_id: Optional ping ID (uses timestamp if None)
  169. Returns:
  170. PingCommand: Ping command
  171. """
  172. cmd = PingCommand()
  173. if ping_id:
  174. cmd.ping_id = ping_id
  175. return cmd
  176. def create_pong(ping_id: str, original_timestamp: Optional[float] = None) -> PongCommand:
  177. """
  178. Create a pong response to a ping command.
  179. Args:
  180. ping_id: Ping ID from the original ping command
  181. original_timestamp: Timestamp from original ping for RTT calculation
  182. Returns:
  183. PongCommand: Pong command
  184. """
  185. return PongCommand(
  186. ping_id=ping_id,
  187. original_timestamp=original_timestamp
  188. )
  189. def create_debug_print(text: str) -> PrintCommand:
  190. """
  191. Create a debug print command.
  192. Args:
  193. text: Text to print
  194. Returns:
  195. PrintCommand: Print command
  196. """
  197. return PrintCommand(text=text)
  198. def create_hello(message: Optional[str] = None) -> HelloCommand:
  199. """
  200. Create a hello command.
  201. Args:
  202. message: Optional hello message
  203. Returns:
  204. HelloCommand: Hello command
  205. """
  206. return HelloCommand(message=message)
  207. def is_hardcoded_command(command_text: str) -> bool:
  208. """
  209. Check if a command text is a hard-coded command.
  210. Args:
  211. command_text: Command text to check
  212. Returns:
  213. bool: True if it's a hard-coded command
  214. """
  215. hardcoded_prefixes = ["TRXINOOP", "TRXIPING", "TRXIPONG", "TRXIPRNT", "TRXYHELO"]
  216. return any(command_text.startswith(prefix) for prefix in hardcoded_prefixes)
  217. def parse_hardcoded_command(command_text: str) -> TrixyCommand:
  218. """
  219. Parse a hard-coded command text into a command object.
  220. Args:
  221. command_text: Command text to parse
  222. Returns:
  223. TrixyCommand: Parsed command object
  224. Raises:
  225. ValidationError: If command text is invalid
  226. """
  227. command_text = command_text.strip()
  228. if command_text == "TRXINOOP":
  229. return NoOpCommand()
  230. elif command_text.startswith("TRXIPING"):
  231. parts = command_text.split(' ', 1)
  232. ping_id = parts[1] if len(parts) > 1 else str(int(time.time() * 1000))
  233. return PingCommand(ping_id=ping_id)
  234. elif command_text.startswith("TRXIPONG"):
  235. parts = command_text.split(' ', 1)
  236. ping_id = parts[1] if len(parts) > 1 else ""
  237. return PongCommand(ping_id=ping_id)
  238. elif command_text.startswith("TRXIPRNT"):
  239. parts = command_text.split(' ', 1)
  240. text = parts[1] if len(parts) > 1 else ""
  241. return PrintCommand(text=text)
  242. elif command_text.startswith("TRXYHELO"):
  243. parts = command_text.split(' ', 1)
  244. message = parts[1] if len(parts) > 1 else None
  245. return HelloCommand(message=message)
  246. else:
  247. raise ValidationError(f"Unknown hard-coded command: {command_text}")
  248. def format_hardcoded_command(command: TrixyCommand) -> str:
  249. """
  250. Format a command object as hard-coded command text.
  251. Args:
  252. command: Command object to format
  253. Returns:
  254. str: Hard-coded command text
  255. Raises:
  256. ValidationError: If command is not a hard-coded command
  257. """
  258. if isinstance(command, NoOpCommand):
  259. return "TRXINOOP"
  260. elif isinstance(command, PingCommand):
  261. return f"TRXIPING {command.ping_id}"
  262. elif isinstance(command, PongCommand):
  263. return f"TRXIPONG {command.ping_id}"
  264. elif isinstance(command, PrintCommand):
  265. return f"TRXIPRNT {command.text}"
  266. elif isinstance(command, HelloCommand):
  267. if command.message:
  268. return f"TRXYHELO {command.message}"
  269. return "TRXYHELO"
  270. else:
  271. raise ValidationError(f"Command {type(command)} is not a hard-coded command")
  272. # Module exports
  273. __all__ = [
  274. "NoOpCommand",
  275. "PingCommand",
  276. "PongCommand",
  277. "PrintCommand",
  278. "HelloCommand",
  279. "PingPongResponse",
  280. "create_heartbeat",
  281. "create_ping",
  282. "create_pong",
  283. "create_debug_print",
  284. "create_hello",
  285. "is_hardcoded_command",
  286. "parse_hardcoded_command",
  287. "format_hardcoded_command",
  288. ]