#!/usr/bin/env python3 # Copyright (C) 2025 Aaron Brown # SPDX-License-Identifier: GPL-2.0-only """Convert MeshCore JSON radio log to pcapng format. Usage: python3 meshcore_json2pcap.py input.json [output.pcapng] The JSON input is an array of objects with fields: timestamp: milliseconds since epoch snr: signal-to-noise ratio (float, dB) packet: hex-encoded raw packet bytes Output is pcapng (section header + interface description + enhanced packet blocks) using LINKTYPE 147 (DLT_USER0). A 6-byte radio metadata header is prepended to each packet: byte 0: version (0) byte 1: pad (0) bytes 2-3: header length as uint16 LE (6) bytes 4-5: SNR as int16 LE (value * 100) """ import argparse import json import struct import sys import os PCAPNG_SNAPLEN = 65535 # Link-layer type for MeshCore packets. We use DLT_USER0 (147) because # Wireshark's Lua API can only register dissectors on built-in encap types # (USER0-USER15). This value will be replaced with an officially assigned # LINKTYPE once one is registered with tcpdump.org -- that change will break # existing pcapng files. LINKTYPE_MESHCORE = 147 def _pad4(length): """Return the number of padding bytes needed to align to 4 bytes.""" return (4 - length % 4) % 4 def _write_block(f, block_type, body): """Write a pcapng block: type(4) + total_length(4) + body + pad + total_length(4).""" pad = _pad4(len(body)) total_length = 12 + len(body) + pad f.write(struct.pack("> 32) & 0xFFFFFFFF ts_low = ts_us & 0xFFFFFFFF if radio_header: snr_int = int(round(snr * 100)) radio_hdr = struct.pack("