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