Source code for FIREQ_SERVER.network.protocol
"""FIREQ network protocol specification.
The protocol supports a data packet that is made up of two parts:
- HEADER: a dictionary send over the network as a msgpack, preserving type
- DATA(optional): an optional data packet (bytes) following the header
The data part of the packet is only there if the header contains the keyword "tsize",
which determines the size in bytes of the trailing data.
The whole packet is serialized by setting the first 4 bytes as the size of the header,
followed by the bytes of the header and the optional data bytes.
"""
import copy
import struct
from dataclasses import dataclass
import msgpack
from FIREQ_SYSTEM.dma_node import DMAPayload
[docs]
@dataclass
class FIREQNetworkPacket:
"""Dataclass that incapsulates a network packet.
It is defined by a header and a data field.
The data field is a simple byte array.
"""
header: dict
data: bytes | None = None
[docs]
def to_buffers(self) -> tuple:
nheader = copy.deepcopy(self.header)
if self.data:
nheader["tsize"] = len(self.data)
header_bytes = msgpack.packb(nheader)
header_size_bytes = struct.pack(">I", len(header_bytes)) # 4 bytes, network byte order
if self.data:
return (header_size_bytes, header_bytes, self.data)
return (header_size_bytes, header_bytes)
[docs]
def get(self, key: str, default: object = None) -> object:
"""Get key value from the header dict."""
return self.header.get(key, default)
[docs]
@dataclass
class NetworkDMAPayload(DMAPayload):
"""Object that holds the payload for a DMA transfer and that can be transferred through the network.
It can be sent over a socket with the to_buffers method, using sock.sendmsg().
"""
[docs]
def to_buffers(self) -> tuple[bytes, bytes, bytes]:
"""Return a sequence of bytes-like objects ready for sendmsg().
The layout:
buf[0] : 4 bytes header size (big-endian unsigned int)
buf[1-N] : msgpack-packed header
buf[N+1: M] : raw binary payload
The msgpack header contains a "tsize" key that specifies the size of the trailing binary payload.
"""
header_bytes = msgpack.packb(
{
"type": "dma_package",
"source": self.source,
"shots": self.shots,
"format": self.dtype,
"tsize": len(self.data),
}
)
header_size_bytes = struct.pack(">I", len(header_bytes)) # 4 bytes, network byte order
return (header_size_bytes, header_bytes, self.data)
def unpack_header(serial: bytes) -> dict:
"""Unpack bytes to return a dictionary using msgpack."""
return msgpack.unpackb(serial, raw=False)
[docs]
def get_command(message: dict) -> str:
return message.get("cmd", "")
[docs]
def get_sweep_variables(message: dict) -> dict:
return message.get("variables", {})