Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

framing.py 4.9KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from __future__ import annotations
  2. import struct
  3. from typing import Any, Awaitable, Callable, NamedTuple, Optional, Sequence, Tuple
  4. from .. import extensions, frames
  5. from ..exceptions import PayloadTooBig, ProtocolError
  6. try:
  7. from ..speedups import apply_mask
  8. except ImportError:
  9. from ..utils import apply_mask
  10. class Frame(NamedTuple):
  11. fin: bool
  12. opcode: frames.Opcode
  13. data: bytes
  14. rsv1: bool = False
  15. rsv2: bool = False
  16. rsv3: bool = False
  17. @property
  18. def new_frame(self) -> frames.Frame:
  19. return frames.Frame(
  20. self.opcode,
  21. self.data,
  22. self.fin,
  23. self.rsv1,
  24. self.rsv2,
  25. self.rsv3,
  26. )
  27. def __str__(self) -> str:
  28. return str(self.new_frame)
  29. def check(self) -> None:
  30. return self.new_frame.check()
  31. @classmethod
  32. async def read(
  33. cls,
  34. reader: Callable[[int], Awaitable[bytes]],
  35. *,
  36. mask: bool,
  37. max_size: Optional[int] = None,
  38. extensions: Optional[Sequence[extensions.Extension]] = None,
  39. ) -> Frame:
  40. """
  41. Read a WebSocket frame.
  42. Args:
  43. reader: Coroutine that reads exactly the requested number of
  44. bytes, unless the end of file is reached.
  45. mask: Whether the frame should be masked i.e. whether the read
  46. happens on the server side.
  47. max_size: Maximum payload size in bytes.
  48. extensions: List of extensions, applied in reverse order.
  49. Raises:
  50. PayloadTooBig: If the frame exceeds ``max_size``.
  51. ProtocolError: If the frame contains incorrect values.
  52. """
  53. # Read the header.
  54. data = await reader(2)
  55. head1, head2 = struct.unpack("!BB", data)
  56. # While not Pythonic, this is marginally faster than calling bool().
  57. fin = True if head1 & 0b10000000 else False
  58. rsv1 = True if head1 & 0b01000000 else False
  59. rsv2 = True if head1 & 0b00100000 else False
  60. rsv3 = True if head1 & 0b00010000 else False
  61. try:
  62. opcode = frames.Opcode(head1 & 0b00001111)
  63. except ValueError as exc:
  64. raise ProtocolError("invalid opcode") from exc
  65. if (True if head2 & 0b10000000 else False) != mask:
  66. raise ProtocolError("incorrect masking")
  67. length = head2 & 0b01111111
  68. if length == 126:
  69. data = await reader(2)
  70. (length,) = struct.unpack("!H", data)
  71. elif length == 127:
  72. data = await reader(8)
  73. (length,) = struct.unpack("!Q", data)
  74. if max_size is not None and length > max_size:
  75. raise PayloadTooBig(f"over size limit ({length} > {max_size} bytes)")
  76. if mask:
  77. mask_bits = await reader(4)
  78. # Read the data.
  79. data = await reader(length)
  80. if mask:
  81. data = apply_mask(data, mask_bits)
  82. new_frame = frames.Frame(opcode, data, fin, rsv1, rsv2, rsv3)
  83. if extensions is None:
  84. extensions = []
  85. for extension in reversed(extensions):
  86. new_frame = extension.decode(new_frame, max_size=max_size)
  87. new_frame.check()
  88. return cls(
  89. new_frame.fin,
  90. new_frame.opcode,
  91. new_frame.data,
  92. new_frame.rsv1,
  93. new_frame.rsv2,
  94. new_frame.rsv3,
  95. )
  96. def write(
  97. self,
  98. write: Callable[[bytes], Any],
  99. *,
  100. mask: bool,
  101. extensions: Optional[Sequence[extensions.Extension]] = None,
  102. ) -> None:
  103. """
  104. Write a WebSocket frame.
  105. Args:
  106. frame: Frame to write.
  107. write: Function that writes bytes.
  108. mask: Whether the frame should be masked i.e. whether the write
  109. happens on the client side.
  110. extensions: List of extensions, applied in order.
  111. Raises:
  112. ProtocolError: If the frame contains incorrect values.
  113. """
  114. # The frame is written in a single call to write in order to prevent
  115. # TCP fragmentation. See #68 for details. This also makes it safe to
  116. # send frames concurrently from multiple coroutines.
  117. write(self.new_frame.serialize(mask=mask, extensions=extensions))
  118. # Backwards compatibility with previously documented public APIs
  119. from ..frames import ( # noqa: E402, F401, I001
  120. Close,
  121. prepare_ctrl as encode_data,
  122. prepare_data,
  123. )
  124. def parse_close(data: bytes) -> Tuple[int, str]:
  125. """
  126. Parse the payload from a close frame.
  127. Returns:
  128. Close code and reason.
  129. Raises:
  130. ProtocolError: If data is ill-formed.
  131. UnicodeDecodeError: If the reason isn't valid UTF-8.
  132. """
  133. close = Close.parse(data)
  134. return close.code, close.reason
  135. def serialize_close(code: int, reason: str) -> bytes:
  136. """
  137. Serialize the payload for a close frame.
  138. """
  139. return Close(code, reason).serialize()