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