57 lines
1.9 KiB
Python
57 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import socket
|
|
import struct
|
|
|
|
|
|
DDP_DEFAULT_PORT = 4048
|
|
DDP_MAX_DATA_LENGTH = 1440
|
|
DDP_VERSION_1 = 0x40
|
|
DDP_PUSH_FLAG = 0x01
|
|
DDP_RGB888 = 0x0B
|
|
DDP_DESTINATION_ID = 1
|
|
|
|
|
|
class DDPClient:
|
|
def __init__(self, port: int = DDP_DEFAULT_PORT) -> None:
|
|
self.port = int(port)
|
|
self._sequence = 1
|
|
|
|
def count_packets(self, host_payloads: dict[str, bytes]) -> int:
|
|
packet_count = 0
|
|
for payload in host_payloads.values():
|
|
chunks, remainder = divmod(len(payload), DDP_MAX_DATA_LENGTH)
|
|
packet_count += chunks + (1 if remainder else 0)
|
|
return packet_count
|
|
|
|
def build_packets(self, host_payloads: dict[str, bytes]) -> list[tuple[str, bytes]]:
|
|
packets: list[tuple[str, bytes]] = []
|
|
sequence = self._next_sequence()
|
|
for host, payload in host_payloads.items():
|
|
for offset in range(0, len(payload), DDP_MAX_DATA_LENGTH):
|
|
chunk = payload[offset : offset + DDP_MAX_DATA_LENGTH]
|
|
last = offset + DDP_MAX_DATA_LENGTH >= len(payload)
|
|
header = struct.pack(
|
|
"!BBBBLH",
|
|
DDP_VERSION_1 | (DDP_PUSH_FLAG if last else 0),
|
|
sequence,
|
|
DDP_RGB888,
|
|
DDP_DESTINATION_ID,
|
|
offset,
|
|
len(chunk),
|
|
)
|
|
packets.append((host, header + chunk))
|
|
return packets
|
|
|
|
def send(self, host_payloads: dict[str, bytes]) -> int:
|
|
packets = self.build_packets(host_payloads)
|
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
for host, packet in packets:
|
|
sock.sendto(packet, (host, self.port))
|
|
return len(packets)
|
|
|
|
def _next_sequence(self) -> int:
|
|
sequence = self._sequence
|
|
self._sequence = 1 if self._sequence >= 15 else self._sequence + 1
|
|
return sequence
|