123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- #!/usr/bin/env python3
- #
- # Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
- #
- # Licensed under the Apache License 2.0 (the "License"). You may not use
- # this file except in compliance with the License. You can obtain a copy
- # in the file LICENSE in the source distribution or at
- # https://www.openssl.org/source/license.html
- import sys, os, os.path, glob, json, re
- re_version = re.compile(r'''^OpenSSL/[0-9]+\.[0-9]\.[0-9](-[^ ]+)? ([^)]+)''')
- class Unexpected(Exception):
- def __init__(self, filename, msg):
- Exception.__init__(self, f"file {repr(filename)}: {msg}")
- class Malformed(Exception):
- def __init__(self, line, msg):
- Exception.__init__(self, f"{line}: {msg}")
- event_type_counts = {}
- frame_type_counts = {}
- def load_file(filename):
- objs = []
- with open(filename, 'r') as fi:
- for line in fi:
- if line[0] != '\x1e':
- raise Unexpected(filename, "expected JSON-SEQ leader")
- line = line[1:]
- try:
- objs.append(json.loads(line))
- except:
- fi.seek(0)
- fdata = fi.read()
- print(fdata)
- raise Malformed(line, "Malformed json input")
- return objs
- def check_header(filename, hdr):
- if not 'qlog_format' in hdr:
- raise Unexpected(filename, "must have qlog_format in header line")
- if not 'qlog_version' in hdr:
- raise Unexpected(filename, "must have qlog_version in header line")
- if not 'trace' in hdr:
- raise Unexpected(filename, "must have trace in header line")
- hdr_trace = hdr["trace"]
- if not 'common_fields' in hdr_trace:
- raise Unexpected(filename, "must have common_fields in header line")
- if not 'vantage_point' in hdr_trace:
- raise Unexpected(filename, "must have vantage_point in header line")
- if hdr_trace["vantage_point"].get('type') not in ('client', 'server'):
- raise Unexpected(filename, "unexpected vantage_point")
- vp_name = hdr_trace["vantage_point"].get('name')
- if type(vp_name) != str:
- raise Unexpected(filename, "expected vantage_point name")
- if not re_version.match(vp_name):
- raise Unexpected(filename, "expected correct vantage_point format")
- hdr_common_fields = hdr_trace["common_fields"]
- if hdr_common_fields.get("time_format") != "delta":
- raise Unexpected(filename, "must have expected time_format")
- if hdr_common_fields.get("protocol_type") != ["QUIC"]:
- raise Unexpected(filename, "must have expected protocol_type")
- if hdr["qlog_format"] != "JSON-SEQ":
- raise Unexpected(filename, "unexpected qlog_format")
- if hdr["qlog_version"] != "0.3":
- raise Unexpected(filename, "unexpected qlog_version")
- def check_event(filename, event):
- name = event.get("name")
- if type(name) != str:
- raise Unexpected(filename, "expected event to have name")
- event_type_counts.setdefault(name, 0)
- event_type_counts[name] += 1
- if type(event.get("time")) != int:
- raise Unexpected(filename, "expected event to have time")
- data = event.get('data')
- if type(data) != dict:
- raise Unexpected(filename, "expected event to have data")
- if "qlog_format" in event:
- raise Unexpected(filename, "event must not be header line")
- if name in ('transport:packet_sent', 'transport:packet_received'):
- check_packet_header(filename, event, data.get('header'))
- datagram_id = data.get('datagram_id')
- if type(datagram_id) != int:
- raise Unexpected(filename, "datagram ID must be integer")
- for frame in data.get('frames', []):
- check_frame(filename, event, frame)
- def check_packet_header(filename, event, header):
- if type(header) != dict:
- raise Unexpected(filename, "expected object for packet header")
- # packet type -> has frames?
- packet_types = {
- 'version_negotiation': False,
- 'retry': False,
- 'initial': True,
- 'handshake': True,
- '0RTT': True,
- '1RTT': True,
- }
- data = event['data']
- packet_type = header.get('packet_type')
- if packet_type not in packet_types:
- raise Unexpected(filename, f"unexpected packet type: {packet_type}")
- if type(header.get('dcid')) != str:
- raise Unexpected(filename, "expected packet event to have DCID")
- if packet_type != '1RTT' and type(header.get('scid')) != str:
- raise Unexpected(filename, "expected packet event to have SCID")
- if type(data.get('datagram_id')) != int:
- raise Unexpected(filename, "expected packet event to have datagram ID")
- if packet_types[packet_type]:
- if type(header.get('packet_number')) != int:
- raise Unexpected(filename, f"expected packet event to have packet number")
- if type(data.get('frames')) != list:
- raise Unexpected(filename, "expected packet event to have frames")
- def check_frame(filename, event, frame):
- frame_type = frame.get('frame_type')
- if type(frame_type) != str:
- raise Unexpected(filename, "frame must have frame_type field")
- frame_type_counts.setdefault(event['name'], {})
- counts = frame_type_counts[event['name']]
- counts.setdefault(frame_type, 0)
- counts[frame_type] += 1
- def check_file(filename):
- objs = load_file(filename)
- if len(objs) < 2:
- raise Unexpected(filename, "must have at least two objects")
- check_header(filename, objs[0])
- for event in objs[1:]:
- check_event(filename, event)
- def run():
- num_files = 0
- # Check each file for validity.
- qlogdir = os.environ['QLOGDIR']
- for filename in glob.glob(os.path.join(qlogdir, '*.sqlog')):
- check_file(filename)
- num_files += 1
- # Check that all supported events were generated.
- required_events = (
- "transport:parameters_set",
- "connectivity:connection_state_updated",
- "connectivity:connection_started",
- "transport:packet_sent",
- "transport:packet_received",
- "connectivity:connection_closed"
- )
- if num_files < 300:
- raise Unexpected(qlogdir, f"unexpectedly few output files: {num_files}")
- for required_event in required_events:
- count = event_type_counts.get(required_event, 0)
- if count < 100:
- raise Unexpected(qlogdir, f"unexpectedly low count of event '{required_event}': got {count}")
- # For each direction, ensure that at least one of the tests we run generated
- # a given frame type.
- required_frame_types = (
- "padding",
- "ping",
- "ack",
- "crypto",
- "handshake_done",
- "connection_close",
- "path_challenge",
- "path_response",
- "stream",
- "reset_stream",
- "stop_sending",
- "new_connection_id",
- "retire_connection_id",
- "max_streams",
- "streams_blocked",
- "max_stream_data",
- "stream_data_blocked",
- "max_data",
- "data_blocked",
- "new_token",
- )
- for required_frame_type in required_frame_types:
- sent_count = frame_type_counts.get('transport:packet_sent', {}).get(required_frame_type, 0)
- if sent_count < 1:
- raise Unexpected(qlogdir, f"unexpectedly did not send any '{required_frame_type}' frames")
- received_count = frame_type_counts.get('transport:packet_received', {}).get(required_frame_type, 0)
- if received_count < 1:
- raise Unexpected(qlogdir, f"unexpectedly did not receive any '{required_frame_type}' frames")
- return 0
- if __name__ == '__main__':
- sys.exit(run())
|