async def test_session_resumption(server: Server, configuration: QuicConfiguration):
    port = server.session_resumption_port or server.port
    saved_ticket = None

    def session_ticket_handler(ticket):
        nonlocal saved_ticket
        saved_ticket = ticket

    # connect a first time, receive a ticket
    async with connect(
        server.host,
        port,
        configuration=configuration,
        session_ticket_handler=session_ticket_handler,
    ) as protocol:
        await protocol.ping()

        # some servers don't send the ticket immediately
        await asyncio.sleep(1)

    # connect a second time, with the ticket
    if saved_ticket is not None:
        configuration.session_ticket = saved_ticket
        async with connect(server.host, port, configuration=configuration) as protocol:
            await protocol.ping()

            # check session was resumed
            if protocol._quic.tls.session_resumed:
                server.result |= Result.R

            # check early data was accepted
            if protocol._quic.tls.early_data_accepted:
                server.result |= Result.Z
Exemple #2
0
async def test_session_resumption(config, **kwargs):
    saved_ticket = None

    def session_ticket_handler(ticket):
        nonlocal saved_ticket
        saved_ticket = ticket

    # connect a first time, receive a ticket
    async with connect(
        config.host,
        config.port,
        session_ticket_handler=session_ticket_handler,
        **kwargs
    ) as connection:
        await connection.ping()

    # connect a second time, with the ticket
    if saved_ticket is not None:
        async with connect(
            config.host, config.port, session_ticket=saved_ticket, **kwargs
        ) as connection:
            await connection.ping()

        # check session was resumed
        if connection._connection.tls.session_resumed:
            config.result |= Result.R

        # check early data was accepted
        if connection._connection.tls.early_data_accepted:
            config.result |= Result.Z
Exemple #3
0
async def test_http_3(server: Server, configuration: QuicConfiguration):
    if server.path is None:
        return

    configuration.alpn_protocols = H3_ALPN
    async with connect(
            server.host,
            server.port,
            configuration=configuration,
            create_protocol=HttpClient,
    ) as protocol:
        protocol = cast(HttpClient, protocol)

        # perform HTTP request
        events = await protocol.get("https://{}:{}{}".format(
            server.host, server.port, server.path))
        if events and isinstance(events[0], HeadersReceived):
            server.result |= Result.D
            server.result |= Result.three

        # perform more HTTP requests to use QPACK dynamic tables
        for i in range(2):
            events = await protocol.get("https://{}:{}{}".format(
                server.host, server.port, server.path))
        if events and isinstance(events[0], HeadersReceived):
            http = cast(H3Connection, protocol._http)
            protocol._quic._logger.info(
                "QPACK decoder bytes RX %d TX %d",
                http._decoder_bytes_received,
                http._decoder_bytes_sent,
            )
            protocol._quic._logger.info(
                "QPACK encoder bytes RX %d TX %d",
                http._encoder_bytes_received,
                http._encoder_bytes_sent,
            )
            if (http._decoder_bytes_received and http._decoder_bytes_sent
                    and http._encoder_bytes_received
                    and http._encoder_bytes_sent):
                server.result |= Result.d

        # check push support
        if server.push_path is not None:
            protocol.pushes.clear()
            await protocol.get("https://{}:{}{}".format(
                server.host, server.port, server.push_path))
            await asyncio.sleep(0.5)
            for push_id, events in protocol.pushes.items():
                if (len(events) >= 3
                        and isinstance(events[0], PushPromiseReceived)
                        and isinstance(events[1], HeadersReceived)
                        and isinstance(events[2], DataReceived)):
                    protocol._quic._logger.info(
                        "Push promise %d for %s received (status %s)",
                        push_id,
                        dict(events[0].headers)[b":path"].decode("ascii"),
                        int(dict(events[1].headers)[b":status"]),
                    )

                    server.result |= Result.p
async def test_handshake_and_close(server: Server, configuration: QuicConfiguration):
    async with connect(
        server.host, server.port, configuration=configuration
    ) as protocol:
        await protocol.ping()
        server.result |= Result.H
    server.result |= Result.C
Exemple #5
0
async def test_migration(server: Server, configuration: QuicConfiguration):
    async with connect(
        server.host, server.port, configuration=configuration
    ) as protocol:
        # cause some traffic
        await protocol.ping()

        # change connection ID and replace transport
        protocol.change_connection_id()
        protocol._transport.close()
        await loop.create_datagram_endpoint(lambda: protocol, local_addr=("::", 0))

        # cause more traffic
        await protocol.ping()

        # check log
        dcids = set()
        for stamp, category, event, data in configuration.quic_logger.to_dict()[
            "traces"
        ][0]["events"]:
            if (
                category == "transport"
                and event == "packet_received"
                and data["packet_type"] == "1RTT"
            ):
                dcids.add(data["header"]["dcid"])
        if len(dcids) == 2:
            server.result |= Result.M
async def test_quantum_readiness(server: Server, configuration: QuicConfiguration):
    configuration.quantum_readiness_test = True
    async with connect(
        server.host, server.port, configuration=configuration
    ) as protocol:
        await protocol.ping()
        server.result |= Result.Q
async def test_address_mobility(server: Server, configuration: QuicConfiguration):
    async with connect(
        server.host, server.port, configuration=configuration
    ) as protocol:
        # cause some traffic
        await protocol.ping()

        # replace transport
        protocol._transport.close()
        await loop.create_datagram_endpoint(lambda: protocol, local_addr=("::", 0))

        # change connection ID
        protocol.change_connection_id()

        # cause more traffic
        await protocol.ping()

        # check log
        path_challenges = 0
        for stamp, category, event, data in configuration.quic_logger.to_dict()[
            "traces"
        ][0]["events"]:
            if (
                category == "transport"
                and event == "packet_received"
                and data["packet_type"] == "1RTT"
            ):
                for frame in data["frames"]:
                    if frame["frame_type"] == "path_challenge":
                        path_challenges += 1

        if not path_challenges:
            protocol._quic._logger.warning("No PATH_CHALLENGE received")
        else:
            server.result |= Result.A
Exemple #8
0
async def test_spin_bit(config, **kwargs):
    async with connect(config.host, config.port, **kwargs) as connection:
        spin_bits = set()
        for i in range(5):
            await connection.ping()
            spin_bits.add(connection._connection._spin_bit_peer)
        if len(spin_bits) == 2:
            config.result |= Result.P
Exemple #9
0
async def http_client(host, port):
    async with connect(host, port) as connection:
        reader, writer = await connection.create_stream()
        writer.write(b"GET /\r\n")
        writer.write_eof()

        response = await reader.read()
        sys.stdout.buffer.write(response)
Exemple #10
0
async def http_client(host, port):
    async with connect(host, port) as connection:
        reader, writer = await connection.create_stream()
        writer.write(b"GET /\r\n")
        writer.write_eof()

        response = await reader.read()
        print(response.decode("utf8"))
Exemple #11
0
async def test_data_transfer(config, **kwargs):
    if config.path is None:
        return

    async with connect(config.host, config.port, **kwargs) as connection:
        response1 = await http_request(connection, config.path)
        response2 = await http_request(connection, config.path)

        if response1 and response2:
            config.result |= Result.D
Exemple #12
0
async def http_client(host, port):
    configuration = QuicConfiguration(alpn_protocols=["hq-23"])

    async with connect(host, port, configuration=configuration) as connection:
        reader, writer = await connection.create_stream()
        writer.write(b"GET /\r\n")
        writer.write_eof()

        response = await reader.read()
        sys.stdout.buffer.write(response)
Exemple #13
0
async def run(config: QuicConfiguration, host: str, port: int,
              requested_video: str) -> None:
    async with connect(
            host=host,
            port=port,
            configuration=config,
            create_protocol=VideoStreamClientProtocol,
    ) as client:
        client = cast(VideoStreamClientProtocol, client)
        await client.send_request_for_video(requested_video)
Exemple #14
0
async def test_key_update(config, **kwargs):
    async with connect(config.host, config.port, **kwargs) as connection:
        # cause some traffic
        await connection.ping()

        # request key update
        connection.request_key_update()

        # cause more traffic
        await connection.ping()

        config.result |= Result.U
Exemple #15
0
async def test_throughput(server: Server, configuration: QuicConfiguration):
    failures = 0
    if server.throughput_path is None:
        return

    for size in [5000000, 10000000]:
        path = server.throughput_path % {"size": size}
        print("Testing %d bytes download: %s" % (size, path))

        # perform HTTP request over TCP
        start = time.time()
        response = httpx.get("https://" + server.host + path, verify=False)
        tcp_octets = len(response.content)
        tcp_elapsed = time.time() - start
        assert tcp_octets == size, "HTTP/TCP response size mismatch"

        # perform HTTP request over QUIC
        if server.http3:
            configuration.alpn_protocols = H3_ALPN
            port = server.http3_port or server.port
        else:
            configuration.alpn_protocols = H0_ALPN
            port = server.port
        start = time.time()
        async with connect(
                server.host,
                port,
                configuration=configuration,
                create_protocol=HttpClient,
        ) as protocol:
            protocol = cast(HttpClient, protocol)

            http_events = await protocol.get("https://{}:{}{}".format(
                server.host, server.port, path))
            quic_elapsed = time.time() - start
            quic_octets = 0
            for http_event in http_events:
                if isinstance(http_event, DataReceived):
                    quic_octets += len(http_event.data)
        assert quic_octets == size, "HTTP/QUIC response size mismatch"

        print(" - HTTP/TCP  completed in %.3f s" % tcp_elapsed)
        print(" - HTTP/QUIC completed in %.3f s" % quic_elapsed)

        if quic_elapsed > 1.1 * tcp_elapsed:
            failures += 1
            print(" => FAIL")
        else:
            print(" => PASS")

    if failures == 0:
        server.result |= Result.T
Exemple #16
0
async def test_key_update(server: Server, configuration: QuicConfiguration):
    async with connect(server.host, server.port,
                       configuration=configuration) as protocol:
        # cause some traffic
        await protocol.ping()

        # request key update
        protocol.request_key_update()

        # cause more traffic
        await protocol.ping()

        server.result |= Result.U
Exemple #17
0
async def test_stateless_retry(server: Server,
                               configuration: QuicConfiguration):
    async with connect(server.host,
                       server.retry_port,
                       configuration=configuration) as protocol:
        await protocol.ping()

        # check log
        for stamp, category, event, data in configuration.quic_logger.to_dict(
        )["traces"][0]["events"]:
            if (category == "transport" and event == "packet_received"
                    and data["packet_type"] == "retry"):
                server.result |= Result.S
async def rsocket_connect(host: str, port: int, configuration: QuicConfiguration = None) -> Transport:
    if configuration is None:
        configuration = QuicConfiguration(
            is_client=True
        )

    async with connect(
            host,
            port,
            configuration=configuration,
            create_protocol=RSocketQuicProtocol,
    ) as client:
        yield RSocketQuicTransport(client)
Exemple #19
0
async def test_spin_bit(server: Server, configuration: QuicConfiguration):
    async with connect(server.host, server.port,
                       configuration=configuration) as protocol:
        for i in range(5):
            await protocol.ping()

        # check log
        spin_bits = set()
        for stamp, category, event, data in configuration.quic_logger.to_dict(
        )["traces"][0]["events"]:
            if category == "connectivity" and event == "spin_bit_updated":
                spin_bits.add(data["state"])
        if len(spin_bits) == 2:
            server.result |= Result.P
async def test_server_cid_change(server: Server, configuration: QuicConfiguration):
    async with connect(
        server.host, server.port, configuration=configuration
    ) as protocol:
        # cause some traffic
        await protocol.ping()

        # change connection ID
        protocol.change_connection_id()

        # cause more traffic
        await protocol.ping()

        server.result |= Result.M
Exemple #21
0
async def test_rebinding(server: Server, configuration: QuicConfiguration):
    async with connect(server.host, server.port,
                       configuration=configuration) as protocol:
        # cause some traffic
        await protocol.ping()

        # replace transport
        protocol._transport.close()
        await loop.create_datagram_endpoint(lambda: protocol,
                                            local_addr=("::", 0))

        # cause more traffic
        await protocol.ping()

        server.result |= Result.B
Exemple #22
0
async def test_version_negotiation(server: Server,
                                   configuration: QuicConfiguration):
    # force version negotiation
    configuration.supported_versions.insert(0, 0x1A2A3A4A)

    async with connect(server.host, server.port,
                       configuration=configuration) as protocol:
        await protocol.ping()

        # check log
        for stamp, category, event, data in configuration.quic_logger.to_dict(
        )["traces"][0]["events"]:
            if (category == "transport" and event == "packet_received"
                    and data["packet_type"] == "version_negotiation"):
                server.result |= Result.V
Exemple #23
0
async def run(host, port, path, **kwargs):
    async with connect(host, port, **kwargs) as connection:
        # perform HTTP/0.9 request
        reader, writer = await connection.create_stream()
        writer.write(("GET %s\r\n" % path).encode("utf8"))
        writer.write_eof()

        start = time.time()
        response = await reader.read()
        elapsed = time.time() - start
        print(response.decode("utf8"))

        octets = len(response)
        logger.info("Received %d bytes in %.1f s (%.3f Mbps)" %
                    (octets, elapsed, octets * 8 / elapsed / 1000000))
Exemple #24
0
async def test_version_negotiation(server: Server,
                                   configuration: QuicConfiguration):
    configuration.supported_versions = [
        0x1A2A3A4A, QuicProtocolVersion.DRAFT_22
    ]

    async with connect(server.host, server.port,
                       configuration=configuration) as protocol:
        await protocol.ping()

        # check log
        for stamp, category, event, data in configuration.quic_logger.to_dict(
        )["traces"][0]["events"]:
            if (category == "TRANSPORT" and event == "PACKET_RECEIVED"
                    and data["packet_type"] == "VERSION_NEGOTIATION"):
                server.result |= Result.V
Exemple #25
0
async def test_http_0(server: Server, configuration: QuicConfiguration):
    if server.path is None:
        return

    configuration.alpn_protocols = ["hq-22"]
    async with connect(
        server.host,
        server.port,
        configuration=configuration,
        create_protocol=HttpClient,
    ) as protocol:
        protocol = cast(HttpClient, protocol)

        # perform HTTP request
        events = await protocol.get(server.host, server.path)
        if events and isinstance(events[0], ResponseReceived):
            server.result |= Result.D
Exemple #26
0
async def test_http_0(server: Server, configuration: QuicConfiguration):
    if server.path is None:
        return

    configuration.alpn_protocols = H0_ALPN
    async with connect(
            server.host,
            server.port,
            configuration=configuration,
            create_protocol=HttpClient,
    ) as protocol:
        protocol = cast(HttpClient, protocol)

        # perform HTTP request
        events = await protocol.get("https://{}:{}{}".format(
            server.host, server.port, server.path))
        if events and isinstance(events[0], HeadersReceived):
            server.result |= Result.D
Exemple #27
0
async def test_http_3(server: Server, configuration: QuicConfiguration):
    if server.path is None:
        return

    configuration.alpn_protocols = H3_ALPN
    async with connect(
            server.host,
            server.port,
            configuration=configuration,
            create_protocol=HttpClient,
    ) as protocol:
        protocol = cast(HttpClient, protocol)

        # perform HTTP request
        events = await protocol.get("https://{}:{}{}".format(
            server.host, server.port, server.path))
        if events and isinstance(events[0], HeadersReceived):
            server.result |= Result.D
            server.result |= Result.three

        # perform more HTTP requests to use QPACK dynamic tables
        for i in range(2):
            events = await protocol.get("https://{}:{}{}".format(
                server.host, server.port, server.path))
        if events and isinstance(events[0], HeadersReceived):
            http = cast(H3Connection, protocol._http)
            protocol._quic._logger.info(
                "QPACK decoder bytes RX %d TX %d",
                http._decoder_bytes_received,
                http._decoder_bytes_sent,
            )
            protocol._quic._logger.info(
                "QPACK encoder bytes RX %d TX %d",
                http._encoder_bytes_received,
                http._encoder_bytes_sent,
            )
            if (http._decoder_bytes_received and http._decoder_bytes_sent
                    and http._encoder_bytes_received
                    and http._encoder_bytes_sent):
                server.result |= Result.d

            # check push support
            if protocol.pushes:
                server.result |= Result.p
Exemple #28
0
async def test_version_negotiation(config, **kwargs):
    async with connect(
        config.host, config.port, protocol_version=0x1A2A3A4A, **kwargs
    ) as connection:
        if connection._connection._version_negotiation_count == 1:
            config.result |= Result.V
Exemple #29
0
async def test_handshake_and_close(config, **kwargs):
    async with connect(config.host, config.port, **kwargs) as connection:
        config.result |= Result.H
        if connection._connection._stateless_retry_count == 1:
            config.result |= Result.S
    config.result |= Result.C