Add transport-neutral packet codec
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from transport_policy import Delivery, delivery_for_packet_type
|
||||
|
||||
MAX_MESSAGE_BYTES = 64 * 1024
|
||||
|
||||
|
||||
class PacketCodecError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EncodedPacket:
|
||||
packet_type: str
|
||||
payload: bytes
|
||||
delivery: Delivery
|
||||
|
||||
|
||||
def _reject_json_constant(value: str) -> None:
|
||||
raise PacketCodecError(f"non-finite JSON constant is not allowed: {value}")
|
||||
|
||||
|
||||
def encode_packet(packet: dict[str, Any]) -> EncodedPacket:
|
||||
if not isinstance(packet, dict):
|
||||
raise PacketCodecError("packet must be a JSON object")
|
||||
packet_type = packet.get("type")
|
||||
if not isinstance(packet_type, str) or not packet_type:
|
||||
raise PacketCodecError("packet type must be a non-empty string")
|
||||
try:
|
||||
payload = json.dumps(packet, separators=(",", ":"), allow_nan=False).encode("utf-8")
|
||||
except (TypeError, ValueError) as error:
|
||||
raise PacketCodecError(f"packet is not JSON serializable: {error}") from error
|
||||
if len(payload) > MAX_MESSAGE_BYTES:
|
||||
raise PacketCodecError("packet exceeds maximum message size")
|
||||
return EncodedPacket(packet_type, payload, delivery_for_packet_type(packet_type))
|
||||
|
||||
|
||||
def decode_packet(payload: bytes | bytearray | memoryview) -> dict[str, Any]:
|
||||
raw = bytes(payload)
|
||||
if len(raw) > MAX_MESSAGE_BYTES:
|
||||
raise PacketCodecError("packet exceeds maximum message size")
|
||||
try:
|
||||
text = raw.decode("utf-8", errors="strict")
|
||||
except UnicodeDecodeError as error:
|
||||
raise PacketCodecError("packet is not valid UTF-8") from error
|
||||
try:
|
||||
packet = json.loads(text, parse_constant=_reject_json_constant)
|
||||
except (json.JSONDecodeError, PacketCodecError) as error:
|
||||
raise PacketCodecError(f"invalid JSON packet: {error}") from error
|
||||
if not isinstance(packet, dict):
|
||||
raise PacketCodecError("packet must be a JSON object")
|
||||
packet_type = packet.get("type")
|
||||
if not isinstance(packet_type, str) or not packet_type:
|
||||
raise PacketCodecError("packet type must be a non-empty string")
|
||||
return packet
|
||||
Reference in New Issue
Block a user