python ircd using asyncio
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

65 lines
1.7 KiB

2 years ago
  1. from dataclasses import dataclass
  2. from typing import List, Optional
  3. MAX_MESSAGE_SIZE = 512
  4. EXPECTED_ARG_COUNT = {
  5. "NICK": 1,
  6. "USER": 4,
  7. "JOIN": 1,
  8. "PRIVMSG": 2,
  9. }
  10. class ParsingError(Exception):
  11. message: Optional[str]
  12. def __init__(self, message: Optional[str] = None) -> None:
  13. self.message = message
  14. @dataclass
  15. class Message:
  16. cmd: str
  17. args: List[str]
  18. prefix: str = "localhost"
  19. def encode(self) -> bytes:
  20. prefix = self.prefix
  21. if prefix != "":
  22. prefix = f":{prefix} "
  23. # TODO: Raise exception if formatted message exceeds 512 bytes
  24. return f"{prefix}{self.cmd} {' '.join(self.args)}\r\n".encode("utf-8")
  25. def parse_message(raw: bytes) -> Message:
  26. if len(raw) > MAX_MESSAGE_SIZE:
  27. raise ParsingError(
  28. f"Message is {len(raw)} bytes, larger than allowed {MAX_MESSAGE_SIZE}"
  29. )
  30. if not raw.endswith(b"\r\n"):
  31. raise ParsingError("Message does not terminate in CRLF")
  32. tokens: List[str] = []
  33. raw_tokens: List[str] = raw.decode("utf-8").split(" ")
  34. for i, token in enumerate(raw_tokens):
  35. if token.startswith(":"):
  36. trailing = token[1:] + " " + " ".join(raw_tokens[i + 1 :])
  37. tokens.append(trailing)
  38. break
  39. tokens.append(token)
  40. if len(tokens) == 0:
  41. raise ParsingError("Message has no command")
  42. cmd = tokens[0].upper()
  43. if cmd in EXPECTED_ARG_COUNT and EXPECTED_ARG_COUNT[cmd] != len(tokens) - 1:
  44. raise ParsingError(
  45. f"{cmd} had {len(tokens)-1} arguments, expected {EXPECTED_ARG_COUNT[cmd]}"
  46. )
  47. tokens[-1] = tokens[-1].strip()
  48. return Message(cmd=tokens[0], args=tokens[1:])