async def test_dispatcher_subscribe_request_response(tester, alice, bob): carol = tester.node() driver_a = tester.session_pair(alice, bob) driver_b = tester.session_pair(carol, alice) await driver_a.handshake() await driver_b.handshake() async with tester.dispatcher_pair(alice, bob) as (alice_dispatcher, _): async with tester.dispatcher_pair(alice, carol): request = OutboundMessage( PingMessage(b"\x12", alice.enr.sequence_number), bob.endpoint, bob.node_id, ) async with alice_dispatcher.subscribe_request( request, PongMessage) as subscription: await driver_a.initiator.send_ping(b"\x12") await driver_b.initiator.send_ping(b"\x34") await driver_b.recipient.send_pong(b"\x34") await driver_a.recipient.send_pong(b"\x12") with trio.fail_after(1): response = await subscription.receive() assert response.sender_node_id == bob.node_id assert response.message.request_id == b"\x12"
def test_handshake_packet_encoding(enr): initiator_key = b"\x01" * 16 nonce = b"\x02" * 12 source_node_id = b"\x03" * 32 dest_node_id = b"\x04" * 32 message = PingMessage(1, 0) auth_data = HandshakePacket( auth_data_head=HandshakeHeader( version=1, signature_size=64, ephemeral_key_size=33, ), id_signature=b"\x05" * 64, ephemeral_public_key=b"\x06" * 33, record=enr, ) packet = Packet.prepare( nonce=nonce, initiator_key=initiator_key, message=message, auth_data=auth_data, source_node_id=source_node_id, dest_node_id=dest_node_id, ) packet_wire_bytes = packet.to_wire_bytes() result = decode_packet(packet_wire_bytes, dest_node_id) assert result == packet
async def test_dispatcher_send_message_with_existing_session( driver, alice, bob, paired_dispatchers): alice_dispatcher, _ = paired_dispatchers async with bob.events.ping_received.subscribe_and_wait(): await alice_dispatcher.send_message( OutboundMessage( PingMessage(b"\x12", alice.enr.sequence_number), bob.endpoint, bob.node_id, )) async with bob.events.ping_received.subscribe_and_wait(): await alice_dispatcher.send_message( OutboundMessage( PingMessage(b"\x34", alice.enr.sequence_number), bob.endpoint, bob.node_id, ))
async def ping(self, endpoint: Endpoint, node_id: NodeID) -> InboundMessage[PongMessage]: with self._get_request_id(node_id) as request_id: request = AnyOutboundMessage( PingMessage(request_id, self.enr_manager.enr.sequence_number), endpoint, node_id, ) async with self.dispatcher.subscribe_request( request, PongMessage) as subscription: with trio.fail_after(REQUEST_RESPONSE_TIMEOUT): return await subscription.receive()
async def send_ping( self, endpoint: Endpoint, node_id: NodeID, *, request_id: Optional[int] = None, ) -> int: with self._get_request_id(node_id, request_id) as message_request_id: message = AnyOutboundMessage( PingMessage(message_request_id, self.enr_manager.enr.sequence_number), endpoint, node_id, ) await self.dispatcher.send_message(message) return message_request_id
async def test_dispatcher_send_message_creates_session(tester, driver, alice, paired_dispatchers): carol = tester.node() alice_dispatcher, _ = paired_dispatchers async with alice.events.session_created.subscribe_and_wait(): await alice_dispatcher.send_message( OutboundMessage( PingMessage(b"\x12", alice.enr.sequence_number), carol.endpoint, carol.node_id, ))
async def test_dispatcher_bidirectional_communication(driver, alice, bob, paired_dispatchers): alice_dispatcher, bob_dispatcher = paired_dispatchers async with alice.events.session_handshake_complete.subscribe_and_wait(): async with bob.events.session_handshake_complete.subscribe_and_wait(): async with alice.events.ping_sent.subscribe_and_wait(): async with bob.events.ping_received.subscribe_and_wait(): await alice_dispatcher.send_message( OutboundMessage( PingMessage(b"\x12", alice.enr.sequence_number), bob.endpoint, bob.node_id, ))
async def ping( self, node_id: NodeID, endpoint: Endpoint, *, request_id: Optional[bytes] = None, ) -> InboundMessage[PongMessage]: with self.request_tracker.reserve_request_id(node_id, request_id) as request_id: request = AnyOutboundMessage( PingMessage(request_id, self.enr_manager.enr.sequence_number), endpoint, node_id, ) async with self.dispatcher.subscribe_request( request, PongMessage) as subscription: return await subscription.receive()
def test_who_are_you_packet_encoding(): initiator_key = b"\x01" * 16 aes_gcm_nonce = b"\x02" * 12 dest_node_id = b"\x04" * 32 message = PingMessage(b"\x01", 0) auth_data = WhoAreYouPacket(id_nonce=b"\x06" * 16, enr_sequence_number=0x07) packet = Packet.prepare( aes_gcm_nonce=aes_gcm_nonce, initiator_key=initiator_key, message=message, auth_data=auth_data, dest_node_id=dest_node_id, ) packet_wire_bytes = packet.to_wire_bytes() result = decode_packet(packet_wire_bytes, dest_node_id) assert result == packet
def test_message_packet_encoding(): initiator_key = b"\x01" * 16 aes_gcm_nonce = b"\x02" * 12 source_node_id = b"\x03" * 32 dest_node_id = b"\x04" * 32 message = PingMessage(b"\x01", 0) auth_data = MessagePacket(source_node_id) packet = Packet.prepare( aes_gcm_nonce=aes_gcm_nonce, initiator_key=initiator_key, message=message, auth_data=auth_data, dest_node_id=dest_node_id, ) packet_wire_bytes = packet.to_wire_bytes() result = decode_packet(packet_wire_bytes, dest_node_id) assert result == packet
def do_message_packet_fixture_decoding_test(fixture): dest_node_id = decode_hex(fixture["dest-node-id"]) expected_auth_data = MessagePacket(source_node_id=decode_hex( fixture["src-node-id"]), ) expected_message = PingMessage( request_id=decode_hex(fixture["packet"]["message"]["req-id"]), enr_seq=to_int(hexstr=fixture["packet"]["message"]["enr-seq"]), ) encoded_packet = decode_hex(fixture["encoded"]) packet = decode_packet(encoded_packet, dest_node_id) assert packet.auth_data == expected_auth_data aes_gcm_nonce = decode_hex(fixture["nonce"]) actual_message = decode_message( decryption_key=decode_hex(fixture["read-key"]), aes_gcm_nonce=aes_gcm_nonce, message_cipher_text=packet.message_cipher_text, authenticated_data=packet.challenge_data, ) assert actual_message == expected_message
async def send_ping( self, node_id: NodeID, endpoint: Endpoint, *, enr_seq: Optional[int] = None, request_id: Optional[bytes] = None, ) -> bytes: if enr_seq is None: enr_seq = self.enr_manager.enr.sequence_number with self.request_tracker.reserve_request_id( node_id, request_id) as message_request_id: message = AnyOutboundMessage( PingMessage(message_request_id, enr_seq), endpoint, node_id, ) await self.dispatcher.send_message(message) return message_request_id
def test_who_are_you_packet_encoding(): initiator_key = b"\x01" * 16 nonce = b"\x02" * 12 source_node_id = b"\x03" * 32 dest_node_id = b"\x04" * 32 message = PingMessage(1, 0) auth_data = WhoAreYouPacket(request_nonce=b"\x05" * 12, id_nonce=b"\x06" * 32, enr_sequence_number=0x07) packet = Packet.prepare( nonce=nonce, initiator_key=initiator_key, message=message, auth_data=auth_data, source_node_id=source_node_id, dest_node_id=dest_node_id, ) packet_wire_bytes = packet.to_wire_bytes() result = decode_packet(packet_wire_bytes, dest_node_id) assert result == packet
async def test_session_unexpected_packets(driver): recipient = driver.recipient.node initiator = driver.initiator.node assert driver.initiator.session.is_before_handshake assert driver.recipient.session.is_before_handshake # initiate the handshake await driver.initiator.send_ping(b"\x12") assert driver.initiator.session.is_during_handshake assert driver.recipient.session.is_before_handshake with trio.fail_after(2): # The recipient should discard any non `MessagePacket` at this stage # since it is impossible for the initiator to have valid `SessionKeys` # since the recipient has not yet sent the `WhoAreYouPacket` async with driver.recipient.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.who_are_you(dest_node_id=recipient.node_id,) ) async with driver.recipient.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.handshake( source_node_id=initiator.node_id, dest_node_id=recipient.node_id, ) ) # The initiator should discard any non `WhoAreYouPacket` packet at this stage since it is # impossible for the recipient to have valid `SessionKeys` since the # initiator has not yet sent the `HandshakePacket` async with driver.initiator.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.message( source_node_id=recipient.node_id, dest_node_id=initiator.node_id, ) ) async with driver.initiator.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.handshake( source_node_id=recipient.node_id, dest_node_id=initiator.node_id, ) ) # Transmit the initation packet await driver.transmit_one(driver.initiator) assert driver.initiator.session.is_during_handshake assert driver.recipient.session.is_during_handshake with trio.fail_after(2): # The recipient should discard a WhoAreYouPacket since there is no # reason for the initiator to send such a packet. async with driver.recipient.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.who_are_you(dest_node_id=recipient.node_id,) ) # The initiator should discard a HandshakePacket since there is no # reason for the recipient to send such a packet async with driver.initiator.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.handshake( source_node_id=recipient.node_id, dest_node_id=initiator.node_id, ) ) # Transmit the who-are-you packet await driver.transmit_one(driver.recipient) assert driver.initiator.session.is_after_handshake assert driver.recipient.session.is_during_handshake with trio.fail_after(2): # The recipient should discard a WhoAreYouPacket since there is no # reason for the initiator to send such a packet. async with driver.recipient.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.who_are_you(dest_node_id=recipient.node_id,) ) # The recipient should buffer any message packets it receives at this # stage since they could be valid packets recieved out of order since # the initiator can now have valid session keys. await driver.send_packet( PacketFactory.message( aes_gcm_nonce=driver.initiator.session.get_encryption_nonce(), initiator_key=driver.initiator.session.keys.encryption_key, message=PingMessage(b"\x34", initiator.enr.sequence_number), source_node_id=initiator.node_id, dest_node_id=recipient.node_id, ) ) # The initiator should discard a HandshakePacket since there is no # reason for the recipient to send such a packet async with driver.initiator.events.packet_discarded.subscribe_and_wait(): await driver.send_packet( PacketFactory.handshake( source_node_id=recipient.node_id, dest_node_id=initiator.node_id, ) ) # Transmit the handshake packet await driver.transmit_one(driver.initiator) # handshake should be complete now assert driver.initiator.session.is_after_handshake assert driver.recipient.session.is_after_handshake async with driver.transmit(): initiation_ping = await driver.recipient.next_message() out_of_order_ping = await driver.recipient.next_message() assert initiation_ping.message.request_id == b"\x12" assert out_of_order_ping.message.request_id == b"\x34"
def do_handshake_packet_fixture_decoding_test(fixture): source_node_id = decode_hex(fixture["src-node-id"]) dest_node_id = decode_hex(fixture["dest-node-id"]) encoded_packet = decode_hex(fixture["encoded"]) ping_enr_seq = to_int(hexstr=fixture["packet"]["message"]["enr-seq"]) who_are_you_enr_seq = to_int( hexstr=fixture["handshake-inputs"]["whoareyou"]["enr-seq"]) if who_are_you_enr_seq == ping_enr_seq and who_are_you_enr_seq != 0: should_have_record = False else: should_have_record = True # ephemeral_private_key = decode_hex(fixture['handshake-inputs']['ephemeral-key']) ephemeral_public_key = decode_hex( fixture["handshake-inputs"]["ephemeral-pubkey"]) # ephemeral_private_key = decode_hex(fixture["handshake-inputs"]["ephemeral-key"]) # request_nonce = decode_hex(fixture['handshake-inputs']['whoareyou']['request-nonce']) challenge_data = decode_hex( fixture["handshake-inputs"]["whoareyou"]["challenge-data"]) masking_iv, static_header, who_are_you = extract_challenge_data( challenge_data) id_nonce = decode_hex(fixture["handshake-inputs"]["whoareyou"]["id-nonce"]) assert who_are_you.id_nonce == id_nonce aes_gcm_nonce = decode_hex(fixture["nonce"]) # TODO: why doesn't this match # assert static_header.aes_gcm_nonce == aes_gcm_nonce signature_inputs = V4HandshakeScheme.signature_inputs_cls( iv=masking_iv, header=static_header, who_are_you=WhoAreYouPacket(id_nonce, who_are_you_enr_seq), ephemeral_public_key=ephemeral_public_key, recipient_node_id=dest_node_id, ) id_nonce_signature = V4HandshakeScheme.create_id_nonce_signature( signature_inputs=signature_inputs, private_key=NODE_KEY_A, ) packet = decode_packet(encoded_packet, dest_node_id) expected_auth_data = HandshakePacket( auth_data_head=HandshakeHeader(source_node_id, 64, 33), id_signature=id_nonce_signature, ephemeral_public_key=ephemeral_public_key, record=packet.auth_data.record, ) assert expected_auth_data == packet.auth_data assert packet.header.aes_gcm_nonce == aes_gcm_nonce if should_have_record: assert packet.auth_data.record is not None assert packet.auth_data.record.node_id == source_node_id else: assert packet.auth_data.record is None expected_message = PingMessage( request_id=decode_hex(fixture["packet"]["message"]["req-id"]), enr_seq=to_int(hexstr=fixture["packet"]["message"]["enr-seq"]), ) actual_message = decode_message( decryption_key=decode_hex(fixture["read-key"]), aes_gcm_nonce=aes_gcm_nonce, message_cipher_text=packet.message_cipher_text, authenticated_data=packet.challenge_data, ) assert expected_message == actual_message
async def send_ping(self, request_id: Optional[int] = None) -> PingMessage: if request_id is None: request_id = secrets.randbits(32) message = PingMessage(request_id, self.node.enr.sequence_number) await self.send_message(message) return message