def from_wire_bytes(cls, data: bytes) -> "HandshakePacket": stream = BytesIO(data) auth_data_head = HandshakeHeader.from_wire_bytes( stream.read(HANDSHAKE_HEADER_PACKET_SIZE)) expected_length = (HANDSHAKE_HEADER_PACKET_SIZE + auth_data_head.signature_size + auth_data_head.ephemeral_key_size) if len(data) < expected_length: raise DecodingError( f"Invalid length for HandshakePacket: " f"expected={expected_length} actual={len(data)} " f"data={data.hex()}") id_signature = stream.read(auth_data_head.signature_size) ephemeral_public_key = stream.read(auth_data_head.ephemeral_key_size) enr_bytes = stream.read() if len(enr_bytes) > 0: try: enr = rlp.decode(enr_bytes, sedes=ENRSedes) except rlp.DecodingError as err: # re-raise using the local library DecodingError instead of the # rlp one raise DecodingError(str(err)) from err else: enr = None return cls( auth_data_head, id_signature, ephemeral_public_key, enr, )
def decode_message(data: bytes) -> AlexandriaMessage[Any]: message_id = data[0] try: message_class = MESSAGE_REGISTRY[message_id] except KeyError: raise DecodingError(f"Unknown message type: id={message_id}") try: payload_args = ssz.decode(data[1:], sedes=message_class.sedes) except DeserializationError as err: raise DecodingError(str(err)) from err return message_class.from_payload_args(payload_args)
def decode_packet( data: bytes, local_node_id: NodeID, ) -> AnyPacket: iv = data[:16] masking_key = cast(AES128Key, local_node_id[:16]) cipher_text_stream = aesctr_decrypt_stream(masking_key, iv, data[16:]) # Decode the header header_bytes = bytes(take(HEADER_PACKET_SIZE, cipher_text_stream)) header = Header.from_wire_bytes(header_bytes) auth_data_bytes = bytes(take(header.auth_data_size, cipher_text_stream)) auth_data: Union[MessagePacket, WhoAreYouPacket, HandshakePacket] if header.flag == 0: auth_data = MessagePacket.from_wire_bytes(auth_data_bytes) elif header.flag == 1: auth_data = WhoAreYouPacket.from_wire_bytes(auth_data_bytes) elif header.flag == 2: auth_data = HandshakePacket.from_wire_bytes(auth_data_bytes) else: raise DecodingError(f"Unable to decode datagram: {data.hex()}", data) message_cipher_text = data[16 + HEADER_PACKET_SIZE + header.auth_data_size:] return cast( AnyPacket, Packet(iv, header, auth_data, message_cipher_text, dest_node_id=local_node_id), )
def from_wire_bytes(cls, data: bytes) -> "Header": if len(data) != HEADER_PACKET_SIZE: raise DecodingError( f"Invalid length for Header: actual={len(data)} " f"expected={HEADER_PACKET_SIZE} data={data.hex()}") stream = BytesIO(data) protocol_id = stream.read(6) if protocol_id != PROTOCOL_ID: raise DecodingError(f"Invalid protocol: {protocol_id!r}") version = stream.read(2) if version != b"\x00\x01": raise DecodingError(f"Unsupported version: {version!r}") flag = stream.read(1)[0] aes_gcm_nonce = Nonce(stream.read(12)) auth_data_size = int.from_bytes(stream.read(2), "big") return cls(protocol_id, version, flag, aes_gcm_nonce, auth_data_size)
async def read_json( socket: trio.socket.SocketType, buffer: io.StringIO, decoder: json.JSONDecoder = decoder, ) -> JSON: request: JSON while True: data = await socket.recv(1024) buffer.write(data.decode()) bad_prefix, raw_request = strip_non_json_prefix(buffer.getvalue()) if bad_prefix: logger.info("Client started request with non json data: %r", bad_prefix) await write_error(socket, f"Cannot parse json: {bad_prefix}") raise DecodingError(f"Invalid JSON payload: prefix={bad_prefix}") if len(raw_request) > MAXIMUM_RPC_PAYLOAD_SIZE: error_msg = ( f"RPC payload exceeds maximum size: {len(raw_request)} " f"> {MAXIMUM_RPC_PAYLOAD_SIZE}") await write_error(socket, error_msg) raise DecodingError(error_msg) try: request, offset = decoder.raw_decode(raw_request) except json.JSONDecodeError: # invalid json request, keep reading data until a valid json is formed if raw_request: logger.debug( "Invalid JSON, waiting for rest of message: %r", raw_request, ) else: await trio.sleep(0.01) continue buffer.seek(0) buffer.write(raw_request[offset:]) buffer.truncate() break return request
async def _request( self, node_id: NodeID, endpoint: Endpoint, request: AlexandriaMessage[Any], response_class: Type[TAlexandriaMessage], request_id: Optional[bytes] = None, ) -> TAlexandriaMessage: # # Request ID Shenanigans # # We need a subscription API for alexandria messages in order to be # able to cleanly implement functionality like responding to ping # messages with pong messages. # # We accomplish this by monitoring all TALKREQUEST and TALKRESPONSE # messages that occur on the alexandria protocol. For TALKREQUEST based # messages we can naively monitor incoming messages and match them # against the protocol_id. For the TALKRESPONSE however, the only way # for us to know that an incoming message belongs to this protocol is # to match it against an in-flight request (which we implicitely know # is part of the alexandria protocol). # # In order to do this, we need to know which request ids are active for # Alexandria protocol messages. We do this by using a separate # RequestTrackerAPI. The `request_id` for messages is still acquired # from the core tracker located on the base protocol `ClientAPI`. We # then feed this into our local tracker, which allows us to query it # upon receiving an incoming TALKRESPONSE to see if the response is to # a message from this protocol. if request.type != AlexandriaMessageType.REQUEST: raise TypeError("%s is not of type REQUEST", request) if request_id is None: request_id = self.network.client.request_tracker.get_free_request_id( node_id) request_data = request.to_wire_bytes() if len(request_data) > MAX_PAYLOAD_SIZE: raise Exception( "Payload too large: payload=%d max_size=%d", len(request_data), MAX_PAYLOAD_SIZE, ) with self.request_tracker.reserve_request_id(node_id, request_id): response_data = await self.network.talk( node_id, protocol=ALEXANDRIA_PROTOCOL_ID, payload=request_data, endpoint=endpoint, request_id=request_id, ) response = decode_message(response_data) if type(response) is not response_class: raise DecodingError( f"Invalid response. expected={response_class} got={type(response)}" ) return response # type: ignore
def from_wire_bytes(cls, data: bytes) -> "WhoAreYouPacket": if len(data) != WHO_ARE_YOU_PACKET_SIZE: raise DecodingError( f"Invalid length for WhoAreYouPacket: length={len(data)} data={data.hex()}" ) stream = BytesIO(data) id_nonce = cast(IDNonce, stream.read(16)) enr_sequence_number = int.from_bytes(stream.read(8), "big") return cls(id_nonce, enr_sequence_number)
def from_wire_bytes(cls, data: bytes) -> "HandshakeHeader": if len(data) != 3: raise DecodingError( f"Invalid length for HandshakeHeader: length={len(data)} data={data.hex()}" ) version = data[0] signature_size = data[1] ephemeral_key_size = data[2] return cls(version, signature_size, ephemeral_key_size)
def from_wire_bytes(cls, data: bytes) -> "HandshakeHeader": if len(data) != HANDSHAKE_HEADER_PACKET_SIZE: raise DecodingError( f"Invalid length for HandshakeHeader: length={len(data)} data={data.hex()}" ) stream = BytesIO(data) source_node_id = NodeID(stream.read(32)) signature_size = stream.read(1)[0] ephemeral_key_size = stream.read(1)[0] return cls(source_node_id, signature_size, ephemeral_key_size)
def from_wire_bytes(cls, data: bytes) -> "WhoAreYouPacket": if len(data) != 52: raise DecodingError( f"Invalid length for WhoAreYouPacket: length={len(data)} data={data.hex()}" ) stream = BytesIO(data) request_nonce = cast(Nonce, stream.read(12)) id_nonce = cast(IDNonce, stream.read(32)) enr_sequence_number = int.from_bytes(stream.read(8), "big") return cls(request_nonce, id_nonce, enr_sequence_number)
def from_wire_bytes(cls, data: bytes) -> "Header": if len(data) != 43: raise DecodingError( f"Invalid length for Header: length={len(data)} data={data.hex()}" ) protocol_id = data[:8] remote_node_id = cast(NodeID, data[8:40]) flag = data[40] auth_data_size = int.from_bytes(data[41:43], "big") return cls(protocol_id, remote_node_id, flag, auth_data_size)
def from_wire_bytes(cls, data: bytes) -> "MessagePacket": if len(data) != MESSAGE_PACKET_SIZE: raise DecodingError( f"Invalid length for MessagePacket: length={len(data)} data={data.hex()}" ) return cls(NodeID(data))
def from_wire_bytes(cls, data: bytes) -> "MessagePacket": if len(data) != 12: raise DecodingError( f"Invalid length for MessagePacket: length={len(data)} data={data.hex()}" ) return cls(cast(Nonce, data))