robosim.network.msgpack_utils
utils.py - Network utils
1"""utils.py - Network utils""" 2from typing import Any 3from datetime import datetime 4import socket 5 6import msgpack 7 8from robosim.utils import logger 9 10_SERIALIZABLE_TYPES = (str, int, float, bool, bytes, type(None)) 11 12 13def send_packet(sock: socket.socket, data: dict, close_connection: bool=False, add_timestmap: bool=True): 14 """pack the message in messagepack format + length-prefixed for TCP stream then send it through the socket""" 15 assert isinstance(data, dict), (data, type(data)) 16 if add_timestmap: 17 assert "timestamp" not in data.keys(), data 18 data["timestamp"] = datetime.now().isoformat() 19 try: 20 packed_data = msgpack.packb(data) 21 except TypeError as e: 22 _find_bad(data) 23 raise e 24 packet = len(packed_data).to_bytes(4, "big") + packed_data # <SIZE-4b><packed_data> 25 logger.trace(f"Sending {len(packet)} bytes through the wire") 26 sock.sendall(packet) 27 28 if close_connection: 29 sock.close() 30 31def recv_packet(sock: socket.socket) -> dict: 32 """reads a packet from the server. First 4 bytes for len, then the rest of the message""" 33 packet_len = int.from_bytes(_recvall(sock, 4), "big") 34 packet: dict = msgpack.unpackb(_recvall(sock, packet_len), raw=False) 35 assert isinstance(packet, dict), f"packet is not dictionary: {type(packet)}" 36 return packet 37 38def _find_bad(d: Any, path: str="root"): 39 """ran in case of a TypeError in send_packet, so we know which part of the dict is not serializable""" 40 if isinstance(d, dict): 41 for k, v in d.items(): 42 _find_bad(v, f"{path}[{k!r}]") # !r calls repr() 43 elif isinstance(d, (list, tuple)): 44 for i, v in enumerate(d): 45 _find_bad(v, f"{path}[{i}]") 46 elif not isinstance(d, _SERIALIZABLE_TYPES): 47 logger.error(f"Unserializable at {path}: {type(d).__name__}") 48 # the 'else' is implied because only 'good' types should be left here 49 50def _recvall(sock: socket.socket, n: int) -> bytes: 51 """recvall receives all n bytes at once from a tcp socket""" 52 buf = bytearray() 53 while len(buf) < n: 54 if not (chunk := sock.recv(n - len(buf))): 55 raise ConnectionError 56 buf.extend(chunk) 57 return bytes(buf)
def
send_packet( sock: socket.socket, data: dict, close_connection: bool = False, add_timestmap: bool = True):
14def send_packet(sock: socket.socket, data: dict, close_connection: bool=False, add_timestmap: bool=True): 15 """pack the message in messagepack format + length-prefixed for TCP stream then send it through the socket""" 16 assert isinstance(data, dict), (data, type(data)) 17 if add_timestmap: 18 assert "timestamp" not in data.keys(), data 19 data["timestamp"] = datetime.now().isoformat() 20 try: 21 packed_data = msgpack.packb(data) 22 except TypeError as e: 23 _find_bad(data) 24 raise e 25 packet = len(packed_data).to_bytes(4, "big") + packed_data # <SIZE-4b><packed_data> 26 logger.trace(f"Sending {len(packet)} bytes through the wire") 27 sock.sendall(packet) 28 29 if close_connection: 30 sock.close()
pack the message in messagepack format + length-prefixed for TCP stream then send it through the socket
def
recv_packet(sock: socket.socket) -> dict:
32def recv_packet(sock: socket.socket) -> dict: 33 """reads a packet from the server. First 4 bytes for len, then the rest of the message""" 34 packet_len = int.from_bytes(_recvall(sock, 4), "big") 35 packet: dict = msgpack.unpackb(_recvall(sock, packet_len), raw=False) 36 assert isinstance(packet, dict), f"packet is not dictionary: {type(packet)}" 37 return packet
reads a packet from the server. First 4 bytes for len, then the rest of the message