1#!/usr/bin/env python3 2# Copyright 2019 Oticon A/S 3# Copyright 2023 Nordic Semiconductor ASA 4# SPDX-License-Identifier: Apache-2.0 5 6import time 7import argparse 8import struct 9from csv_common import * 10 11# default max packet length (phdr + access address + pdu + crc) 12SNAPLEN = 512 13 14def write(outfile, *inputs, snaplen = SNAPLEN, basetime=None): 15 #For information on the pcap format see https://wiki.wireshark.org/Development/LibpcapFileFormat 16 17 buf = bytearray(30) 18 19 if basetime == None: 20 basetime = int(time.time() * 1000000) 21 22 # write pcap header 23 struct.pack_into('<IHHiIII', buf, 0, 24 0xa1b2c3d4, # magic_number 25 2, # version_major 26 4, # version_minor 27 0, # thiszone 28 0, # sigfigs 29 snaplen, # snaplen 30 215) # network, 215 = DLT_IEEE802_15_4_NONASK_PHY ; https://www.tcpdump.org/linktypes.html 31 outfile.write(buf[:24]) 32 33 inputs = [ CSVFile(f) for f in inputs ] 34 rows = [ next(cf, None) for cf in inputs ] 35 36 while True: 37 min_ts = None 38 min_idx = None 39 for idx, row in enumerate(rows): 40 if not row: 41 continue 42 ts = row['start_time'] 43 if min_ts == None or ts < min_ts: 44 min_ts = ts 45 min_idx = idx 46 47 if min_idx == None: 48 break 49 50 row = rows[min_idx] 51 rows[min_idx] = next(inputs[min_idx], None) 52 53 orig_len = int(row['packet_size'], 10) 54 if orig_len == 0: 55 continue 56 57 modulation = int(row['modulation'], 10) 58 if modulation != 256: 59 continue 60 61 freq = float(row['center_freq']) 62 if freq >= 1.0 and freq < 81.0: 63 rf_channel = int((freq - 1.0) / 2) 64 elif freq >= 2401.0 and freq < 2481.0: 65 rf_channel = int((freq - 2401.0) / 2) 66 else: 67 raise ValueError 68 69 try: 70 pdu_crc = bytes.fromhex(row['packet']) 71 except: #In case the packet is broken mid byte 72 pdu_crc = bytes.fromhex("00") 73 74 if len(pdu_crc) != orig_len: # Let's handle this somehow gracefully 75 print("Truncated input file (partial packet), writing partial packet in output") 76 orig_len = len(pdu_crc) 77 orig_len += 5; # 4 bytes preamble + 1 bytes address/SFD 78 incl_len = min(orig_len, snaplen) 79 80 access_address = int(row['phy_address'], 16) 81 82 ts = basetime + min_ts 83 ts_sec = ts // 1000000 84 ts_usec = ts % 1000000 85 86 struct.pack_into('<IIII', buf, 0, 87 # pcap record header, 16 bytes 88 ts_sec, 89 ts_usec, 90 incl_len, 91 orig_len) 92 outfile.write(buf[:16]) 93 94 struct.pack_into('<IB', buf, 0, 95 #Header 96 0, # preamble (0) 97 access_address) #SFD 98 outfile.write(buf[:5]) 99 100 #The packet starting from the lenght byte 101 outfile.write(pdu_crc[:(incl_len - 5)]) 102 103def parse_args(): 104 parser = argparse.ArgumentParser(description='Convert BabbleSim Phy csv files to pcap') 105 parser.add_argument( 106 '-er', '--epoch_real', 107 action='store_true', 108 dest='epoch_real', 109 required=False, 110 help='If set, the pcap timestamps will be offset based on the host time when the simulation was run. Otherwise they are directly the simulation timestamps', 111 ) 112 parser.add_argument( 113 '-es', '--epoch_simu', 114 action='store_false', 115 dest='epoch_real', 116 required=False, 117 help='If set, pcap timestamp are directly the simulation timestamps (default)', 118 ) 119 parser.add_argument('-o', '--output', 120 dest='output', 121 metavar='OUTFILE', 122 help='Write to this pcap file (required)', 123 required=True, 124 type=argparse.FileType(mode='wb')) 125 parser.add_argument( 126 dest='inputs', 127 metavar='INFILE', 128 help='Input csv file(s) (at least one is required)', 129 nargs='+', 130 type=open_input) 131 return parser.parse_args() 132 133args = parse_args() 134 135if (args.epoch_real == True): 136 basetime = int(min([ p[0] for p in args.inputs ]) * 1000000) 137else: 138 basetime = 0 139 140write(args.output, *[ p[1] for p in args.inputs ], basetime=basetime) 141 142# vim: set ts=4 sw=4 noet: 143