Organizes 11 projects for Cerbo GX/Venus OS into a single repository: - axiom-nmea: Raymarine LightHouse protocol decoder - dbus-generator-ramp: Generator current ramp controller - dbus-lightning: Blitzortung lightning monitor - dbus-meteoblue-forecast: Meteoblue weather forecast - dbus-no-foreign-land: noforeignland.com tracking - dbus-tides: Tide prediction from depth + harmonics - dbus-vrm-history: VRM cloud history proxy - dbus-windy-station: Windy.com weather upload - mfd-custom-app: MFD app deployment package - venus-html5-app: Custom Victron HTML5 app fork - watermaker: Watermaker PLC control UI Adds root README, .gitignore, project template, and per-project .gitignore files. Sensitive config files excluded via .gitignore with .example templates provided. Made-with: Cursor
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Analyze packet structure to understand why offsets vary.
|
|
Look for common headers and protobuf nesting patterns.
|
|
"""
|
|
|
|
import struct
|
|
|
|
def read_pcap(filename):
|
|
packets = []
|
|
with open(filename, 'rb') as f:
|
|
header = f.read(24)
|
|
magic = struct.unpack('<I', header[0:4])[0]
|
|
swapped = magic == 0xd4c3b2a1
|
|
endian = '>' if swapped else '<'
|
|
|
|
while True:
|
|
pkt_header = f.read(16)
|
|
if len(pkt_header) < 16:
|
|
break
|
|
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(f'{endian}IIII', pkt_header)
|
|
pkt_data = f.read(incl_len)
|
|
if len(pkt_data) < incl_len:
|
|
break
|
|
|
|
if len(pkt_data) > 42 and pkt_data[12:14] == b'\x08\x00':
|
|
ip_header_len = (pkt_data[14] & 0x0F) * 4
|
|
payload_start = 14 + ip_header_len + 8
|
|
if payload_start < len(pkt_data):
|
|
packets.append(pkt_data[payload_start:])
|
|
return packets
|
|
|
|
packets = read_pcap("raymarine_sample_TWD_62-70_HDG_29-35.pcap")
|
|
|
|
# Group by size
|
|
by_size = {}
|
|
for pkt in packets:
|
|
pkt_len = len(pkt)
|
|
if pkt_len not in by_size:
|
|
by_size[pkt_len] = []
|
|
by_size[pkt_len].append(pkt)
|
|
|
|
print("=" * 70)
|
|
print("PACKET HEADER ANALYSIS")
|
|
print("=" * 70)
|
|
|
|
for pkt_len in sorted(by_size.keys()):
|
|
if pkt_len < 100:
|
|
continue
|
|
|
|
pkts = by_size[pkt_len][:3] # First 3 packets of each size
|
|
|
|
print(f"\n{'='*70}")
|
|
print(f"PACKET SIZE: {pkt_len} bytes ({len(by_size[pkt_len])} total)")
|
|
print("=" * 70)
|
|
|
|
pkt = pkts[0]
|
|
|
|
# Show first 128 bytes as hex
|
|
print("\nFirst 128 bytes (hex):")
|
|
for row in range(0, min(128, pkt_len), 16):
|
|
hex_str = ' '.join(f'{b:02x}' for b in pkt[row:row+16])
|
|
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in pkt[row:row+16])
|
|
print(f" 0x{row:04x}: {hex_str:<48} {ascii_str}")
|
|
|
|
# Look for the GPS pattern (0x09 followed by lat, 0x11 followed by lon)
|
|
gps_offset = None
|
|
for i in range(0x20, min(pkt_len - 18, 0x60)):
|
|
if pkt[i] == 0x09 and i + 9 < pkt_len and pkt[i + 9] == 0x11:
|
|
lat = struct.unpack('<d', pkt[i+1:i+9])[0]
|
|
lon = struct.unpack('<d', pkt[i+10:i+18])[0]
|
|
if -90 <= lat <= 90 and -180 <= lon <= 180 and abs(lat) > 1:
|
|
gps_offset = i
|
|
print(f"\n GPS found at offset 0x{i:04x}: {lat:.6f}, {lon:.6f}")
|
|
break
|
|
|
|
# Look for string patterns (device name)
|
|
for i in range(0x10, min(pkt_len - 10, 0x40)):
|
|
if pkt[i:i+5] == b'AXIOM':
|
|
print(f" 'AXIOM' string at offset 0x{i:04x}")
|
|
# Show surrounding context
|
|
end = min(i + 30, pkt_len)
|
|
print(f" Context: {pkt[i:end]}")
|
|
break
|
|
|
|
# Analyze protobuf wire types at key offsets
|
|
print(f"\n Protobuf tags at key offsets:")
|
|
for offset in [0x0070, 0x00a0, 0x00a7, 0x00c5, 0x00fc]:
|
|
if offset < pkt_len:
|
|
tag = pkt[offset]
|
|
wire_type = tag & 0x07
|
|
field_num = tag >> 3
|
|
wire_names = {0: 'varint', 1: 'fixed64', 2: 'length', 5: 'fixed32'}
|
|
print(f" 0x{offset:04x}: tag=0x{tag:02x} (field {field_num}, {wire_names.get(wire_type, '?')})")
|