xref: /freebsd/crypto/openssl/test/recipes/70-test_quic_multistream_data/verify-qlog.py (revision e7be843b4a162e68651d3911f0357ed464915629)
1*e7be843bSPierre Pronchery#!/usr/bin/env python3
2*e7be843bSPierre Pronchery#
3*e7be843bSPierre Pronchery# Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
4*e7be843bSPierre Pronchery#
5*e7be843bSPierre Pronchery# Licensed under the Apache License 2.0 (the "License").  You may not use
6*e7be843bSPierre Pronchery# this file except in compliance with the License.  You can obtain a copy
7*e7be843bSPierre Pronchery# in the file LICENSE in the source distribution or at
8*e7be843bSPierre Pronchery# https://www.openssl.org/source/license.html
9*e7be843bSPierre Proncheryimport sys, os, os.path, glob, json, re
10*e7be843bSPierre Pronchery
11*e7be843bSPierre Proncheryre_version = re.compile(r'''^OpenSSL/[0-9]+\.[0-9]\.[0-9](-[^ ]+)? ([^)]+)''')
12*e7be843bSPierre Pronchery
13*e7be843bSPierre Proncheryclass Unexpected(Exception):
14*e7be843bSPierre Pronchery    def __init__(self, filename, msg):
15*e7be843bSPierre Pronchery        Exception.__init__(self, f"file {repr(filename)}: {msg}")
16*e7be843bSPierre Pronchery
17*e7be843bSPierre Proncheryclass Malformed(Exception):
18*e7be843bSPierre Pronchery    def __init__(self, line, msg):
19*e7be843bSPierre Pronchery        Exception.__init__(self, f"{line}: {msg}")
20*e7be843bSPierre Pronchery
21*e7be843bSPierre Proncheryevent_type_counts = {}
22*e7be843bSPierre Proncheryframe_type_counts = {}
23*e7be843bSPierre Pronchery
24*e7be843bSPierre Proncherydef load_file(filename):
25*e7be843bSPierre Pronchery    objs = []
26*e7be843bSPierre Pronchery    with open(filename, 'r') as fi:
27*e7be843bSPierre Pronchery        for line in fi:
28*e7be843bSPierre Pronchery            if line[0] != '\x1e':
29*e7be843bSPierre Pronchery                raise Unexpected(filename, "expected JSON-SEQ leader")
30*e7be843bSPierre Pronchery
31*e7be843bSPierre Pronchery            line = line[1:]
32*e7be843bSPierre Pronchery            try:
33*e7be843bSPierre Pronchery                objs.append(json.loads(line))
34*e7be843bSPierre Pronchery            except:
35*e7be843bSPierre Pronchery                fi.seek(0)
36*e7be843bSPierre Pronchery                fdata = fi.read()
37*e7be843bSPierre Pronchery                print(fdata)
38*e7be843bSPierre Pronchery                raise Malformed(line, "Malformed json input")
39*e7be843bSPierre Pronchery    return objs
40*e7be843bSPierre Pronchery
41*e7be843bSPierre Proncherydef check_header(filename, hdr):
42*e7be843bSPierre Pronchery    if not 'qlog_format' in hdr:
43*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have qlog_format in header line")
44*e7be843bSPierre Pronchery
45*e7be843bSPierre Pronchery    if not 'qlog_version' in hdr:
46*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have qlog_version in header line")
47*e7be843bSPierre Pronchery
48*e7be843bSPierre Pronchery    if not 'trace' in hdr:
49*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have trace in header line")
50*e7be843bSPierre Pronchery
51*e7be843bSPierre Pronchery    hdr_trace = hdr["trace"]
52*e7be843bSPierre Pronchery    if not 'common_fields' in hdr_trace:
53*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have common_fields in header line")
54*e7be843bSPierre Pronchery
55*e7be843bSPierre Pronchery    if not 'vantage_point' in hdr_trace:
56*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have vantage_point in header line")
57*e7be843bSPierre Pronchery
58*e7be843bSPierre Pronchery    if hdr_trace["vantage_point"].get('type') not in ('client', 'server'):
59*e7be843bSPierre Pronchery        raise Unexpected(filename, "unexpected vantage_point")
60*e7be843bSPierre Pronchery
61*e7be843bSPierre Pronchery    vp_name = hdr_trace["vantage_point"].get('name')
62*e7be843bSPierre Pronchery    if type(vp_name) != str:
63*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected vantage_point name")
64*e7be843bSPierre Pronchery
65*e7be843bSPierre Pronchery    if not re_version.match(vp_name):
66*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected correct vantage_point format")
67*e7be843bSPierre Pronchery
68*e7be843bSPierre Pronchery    hdr_common_fields = hdr_trace["common_fields"]
69*e7be843bSPierre Pronchery    if hdr_common_fields.get("time_format") != "delta":
70*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have expected time_format")
71*e7be843bSPierre Pronchery
72*e7be843bSPierre Pronchery    if hdr_common_fields.get("protocol_type") != ["QUIC"]:
73*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have expected protocol_type")
74*e7be843bSPierre Pronchery
75*e7be843bSPierre Pronchery    if hdr["qlog_format"] != "JSON-SEQ":
76*e7be843bSPierre Pronchery        raise Unexpected(filename, "unexpected qlog_format")
77*e7be843bSPierre Pronchery
78*e7be843bSPierre Pronchery    if hdr["qlog_version"] != "0.3":
79*e7be843bSPierre Pronchery        raise Unexpected(filename, "unexpected qlog_version")
80*e7be843bSPierre Pronchery
81*e7be843bSPierre Proncherydef check_event(filename, event):
82*e7be843bSPierre Pronchery    name = event.get("name")
83*e7be843bSPierre Pronchery
84*e7be843bSPierre Pronchery    if type(name) != str:
85*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected event to have name")
86*e7be843bSPierre Pronchery
87*e7be843bSPierre Pronchery    event_type_counts.setdefault(name, 0)
88*e7be843bSPierre Pronchery    event_type_counts[name] += 1
89*e7be843bSPierre Pronchery
90*e7be843bSPierre Pronchery    if type(event.get("time")) != int:
91*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected event to have time")
92*e7be843bSPierre Pronchery
93*e7be843bSPierre Pronchery    data = event.get('data')
94*e7be843bSPierre Pronchery    if type(data) != dict:
95*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected event to have data")
96*e7be843bSPierre Pronchery
97*e7be843bSPierre Pronchery    if "qlog_format" in event:
98*e7be843bSPierre Pronchery        raise Unexpected(filename, "event must not be header line")
99*e7be843bSPierre Pronchery
100*e7be843bSPierre Pronchery    if name in ('transport:packet_sent', 'transport:packet_received'):
101*e7be843bSPierre Pronchery        check_packet_header(filename, event, data.get('header'))
102*e7be843bSPierre Pronchery
103*e7be843bSPierre Pronchery        datagram_id = data.get('datagram_id')
104*e7be843bSPierre Pronchery        if type(datagram_id) != int:
105*e7be843bSPierre Pronchery            raise Unexpected(filename, "datagram ID must be integer")
106*e7be843bSPierre Pronchery
107*e7be843bSPierre Pronchery        for frame in data.get('frames', []):
108*e7be843bSPierre Pronchery            check_frame(filename, event, frame)
109*e7be843bSPierre Pronchery
110*e7be843bSPierre Proncherydef check_packet_header(filename, event, header):
111*e7be843bSPierre Pronchery    if type(header) != dict:
112*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected object for packet header")
113*e7be843bSPierre Pronchery
114*e7be843bSPierre Pronchery    # packet type -> has frames?
115*e7be843bSPierre Pronchery    packet_types = {
116*e7be843bSPierre Pronchery            'version_negotiation': False,
117*e7be843bSPierre Pronchery            'retry': False,
118*e7be843bSPierre Pronchery            'initial': True,
119*e7be843bSPierre Pronchery            'handshake': True,
120*e7be843bSPierre Pronchery            '0RTT': True,
121*e7be843bSPierre Pronchery            '1RTT': True,
122*e7be843bSPierre Pronchery    }
123*e7be843bSPierre Pronchery
124*e7be843bSPierre Pronchery    data = event['data']
125*e7be843bSPierre Pronchery    packet_type = header.get('packet_type')
126*e7be843bSPierre Pronchery    if packet_type not in packet_types:
127*e7be843bSPierre Pronchery        raise Unexpected(filename, f"unexpected packet type: {packet_type}")
128*e7be843bSPierre Pronchery
129*e7be843bSPierre Pronchery    if type(header.get('dcid')) != str:
130*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected packet event to have DCID")
131*e7be843bSPierre Pronchery    if packet_type != '1RTT' and type(header.get('scid')) != str:
132*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected packet event to have SCID")
133*e7be843bSPierre Pronchery
134*e7be843bSPierre Pronchery    if type(data.get('datagram_id')) != int:
135*e7be843bSPierre Pronchery        raise Unexpected(filename, "expected packet event to have datagram ID")
136*e7be843bSPierre Pronchery
137*e7be843bSPierre Pronchery    if packet_types[packet_type]:
138*e7be843bSPierre Pronchery        if type(header.get('packet_number')) != int:
139*e7be843bSPierre Pronchery            raise Unexpected(filename, f"expected packet event to have packet number")
140*e7be843bSPierre Pronchery        if type(data.get('frames')) != list:
141*e7be843bSPierre Pronchery            raise Unexpected(filename, "expected packet event to have frames")
142*e7be843bSPierre Pronchery
143*e7be843bSPierre Proncherydef check_frame(filename, event, frame):
144*e7be843bSPierre Pronchery    frame_type = frame.get('frame_type')
145*e7be843bSPierre Pronchery    if type(frame_type) != str:
146*e7be843bSPierre Pronchery        raise Unexpected(filename, "frame must have frame_type field")
147*e7be843bSPierre Pronchery
148*e7be843bSPierre Pronchery    frame_type_counts.setdefault(event['name'], {})
149*e7be843bSPierre Pronchery    counts = frame_type_counts[event['name']]
150*e7be843bSPierre Pronchery
151*e7be843bSPierre Pronchery    counts.setdefault(frame_type, 0)
152*e7be843bSPierre Pronchery    counts[frame_type] += 1
153*e7be843bSPierre Pronchery
154*e7be843bSPierre Proncherydef check_file(filename):
155*e7be843bSPierre Pronchery    objs = load_file(filename)
156*e7be843bSPierre Pronchery    if len(objs) < 2:
157*e7be843bSPierre Pronchery        raise Unexpected(filename, "must have at least two objects")
158*e7be843bSPierre Pronchery
159*e7be843bSPierre Pronchery    check_header(filename, objs[0])
160*e7be843bSPierre Pronchery    for event in objs[1:]:
161*e7be843bSPierre Pronchery        check_event(filename, event)
162*e7be843bSPierre Pronchery
163*e7be843bSPierre Proncherydef run():
164*e7be843bSPierre Pronchery    num_files = 0
165*e7be843bSPierre Pronchery
166*e7be843bSPierre Pronchery    # Check each file for validity.
167*e7be843bSPierre Pronchery    qlogdir = os.environ['QLOGDIR']
168*e7be843bSPierre Pronchery    for filename in glob.glob(os.path.join(qlogdir, '*.sqlog')):
169*e7be843bSPierre Pronchery        check_file(filename)
170*e7be843bSPierre Pronchery        num_files += 1
171*e7be843bSPierre Pronchery
172*e7be843bSPierre Pronchery    # Check that all supported events were generated.
173*e7be843bSPierre Pronchery    required_events = (
174*e7be843bSPierre Pronchery        "transport:parameters_set",
175*e7be843bSPierre Pronchery        "connectivity:connection_state_updated",
176*e7be843bSPierre Pronchery        "connectivity:connection_started",
177*e7be843bSPierre Pronchery        "transport:packet_sent",
178*e7be843bSPierre Pronchery        "transport:packet_received",
179*e7be843bSPierre Pronchery        "connectivity:connection_closed"
180*e7be843bSPierre Pronchery    )
181*e7be843bSPierre Pronchery
182*e7be843bSPierre Pronchery    if num_files < 300:
183*e7be843bSPierre Pronchery        raise Unexpected(qlogdir, f"unexpectedly few output files: {num_files}")
184*e7be843bSPierre Pronchery
185*e7be843bSPierre Pronchery    for required_event in required_events:
186*e7be843bSPierre Pronchery        count = event_type_counts.get(required_event, 0)
187*e7be843bSPierre Pronchery        if count < 100:
188*e7be843bSPierre Pronchery            raise Unexpected(qlogdir, f"unexpectedly low count of event '{required_event}': got {count}")
189*e7be843bSPierre Pronchery
190*e7be843bSPierre Pronchery    # For each direction, ensure that at least one of the tests we run generated
191*e7be843bSPierre Pronchery    # a given frame type.
192*e7be843bSPierre Pronchery    required_frame_types = (
193*e7be843bSPierre Pronchery        "padding",
194*e7be843bSPierre Pronchery        "ping",
195*e7be843bSPierre Pronchery        "ack",
196*e7be843bSPierre Pronchery
197*e7be843bSPierre Pronchery        "crypto",
198*e7be843bSPierre Pronchery        "handshake_done",
199*e7be843bSPierre Pronchery        "connection_close",
200*e7be843bSPierre Pronchery
201*e7be843bSPierre Pronchery        "path_challenge",
202*e7be843bSPierre Pronchery        "path_response",
203*e7be843bSPierre Pronchery
204*e7be843bSPierre Pronchery        "stream",
205*e7be843bSPierre Pronchery        "reset_stream",
206*e7be843bSPierre Pronchery        "stop_sending",
207*e7be843bSPierre Pronchery
208*e7be843bSPierre Pronchery        "new_connection_id",
209*e7be843bSPierre Pronchery        "retire_connection_id",
210*e7be843bSPierre Pronchery
211*e7be843bSPierre Pronchery        "max_streams",
212*e7be843bSPierre Pronchery        "streams_blocked",
213*e7be843bSPierre Pronchery
214*e7be843bSPierre Pronchery        "max_stream_data",
215*e7be843bSPierre Pronchery        "stream_data_blocked",
216*e7be843bSPierre Pronchery
217*e7be843bSPierre Pronchery        "max_data",
218*e7be843bSPierre Pronchery        "data_blocked",
219*e7be843bSPierre Pronchery
220*e7be843bSPierre Pronchery        "new_token",
221*e7be843bSPierre Pronchery    )
222*e7be843bSPierre Pronchery
223*e7be843bSPierre Pronchery    for required_frame_type in required_frame_types:
224*e7be843bSPierre Pronchery        sent_count = frame_type_counts.get('transport:packet_sent', {}).get(required_frame_type, 0)
225*e7be843bSPierre Pronchery        if sent_count < 1:
226*e7be843bSPierre Pronchery            raise Unexpected(qlogdir, f"unexpectedly did not send any '{required_frame_type}' frames")
227*e7be843bSPierre Pronchery
228*e7be843bSPierre Pronchery        received_count = frame_type_counts.get('transport:packet_received', {}).get(required_frame_type, 0)
229*e7be843bSPierre Pronchery        if received_count < 1:
230*e7be843bSPierre Pronchery            raise Unexpected(qlogdir, f"unexpectedly did not receive any '{required_frame_type}' frames")
231*e7be843bSPierre Pronchery
232*e7be843bSPierre Pronchery    return 0
233*e7be843bSPierre Pronchery
234*e7be843bSPierre Proncheryif __name__ == '__main__':
235*e7be843bSPierre Pronchery    sys.exit(run())
236