104 lines
3.9 KiB
Python
104 lines
3.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Chapter 10 Packet class for IRIG106 Chapter 10 frame parsing
|
||
|
|
"""
|
||
|
|
|
||
|
|
import struct
|
||
|
|
from typing import Dict, Optional
|
||
|
|
|
||
|
|
try:
|
||
|
|
from scapy.layers.inet import IP, UDP
|
||
|
|
except ImportError:
|
||
|
|
print("Error: scapy library not found. Install with: pip install scapy")
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
|
||
|
|
class Chapter10Packet:
|
||
|
|
"""Represents an IRIG106 Chapter 10 packet"""
|
||
|
|
|
||
|
|
def __init__(self, packet, original_frame_num: Optional[int] = None):
|
||
|
|
"""
|
||
|
|
Initialize Chapter 10 packet from raw scapy packet
|
||
|
|
|
||
|
|
Args:
|
||
|
|
packet: Raw scapy packet
|
||
|
|
original_frame_num: Original frame number in PCAP file
|
||
|
|
"""
|
||
|
|
self.raw_packet = packet
|
||
|
|
self.original_frame_num: Optional[int] = original_frame_num
|
||
|
|
|
||
|
|
# Extract basic packet info
|
||
|
|
self.timestamp = float(packet.time)
|
||
|
|
self.packet_size = len(packet)
|
||
|
|
|
||
|
|
# Extract IP/UDP info if available
|
||
|
|
if packet.haslayer(IP) and packet.haslayer(UDP):
|
||
|
|
ip_layer = packet[IP]
|
||
|
|
udp_layer = packet[UDP]
|
||
|
|
|
||
|
|
self.src_ip = ip_layer.src
|
||
|
|
self.dst_ip = ip_layer.dst
|
||
|
|
self.src_port = udp_layer.sport
|
||
|
|
self.dst_port = udp_layer.dport
|
||
|
|
self.payload = bytes(udp_layer.payload)
|
||
|
|
else:
|
||
|
|
self.src_ip = ""
|
||
|
|
self.dst_ip = ""
|
||
|
|
self.src_port = 0
|
||
|
|
self.dst_port = 0
|
||
|
|
self.payload = bytes()
|
||
|
|
|
||
|
|
# Parse Chapter 10 header
|
||
|
|
self.ch10_header = self._parse_ch10_header()
|
||
|
|
|
||
|
|
def _parse_ch10_header(self) -> Optional[Dict]:
|
||
|
|
"""Parse Chapter 10 header from payload"""
|
||
|
|
if len(self.payload) < 28: # Minimum payload size (4-byte prefix + 24-byte Ch10 header)
|
||
|
|
return None
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Look for Ch10 sync pattern in first several bytes
|
||
|
|
ch10_offset = None
|
||
|
|
for offset in range(min(8, len(self.payload) - 24)):
|
||
|
|
sync_pattern = struct.unpack('<H', self.payload[offset:offset+2])[0]
|
||
|
|
if sync_pattern == 0xEB25: # Ch10 sync pattern
|
||
|
|
ch10_offset = offset
|
||
|
|
break
|
||
|
|
|
||
|
|
if ch10_offset is None:
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Parse Chapter 10 header starting at found offset
|
||
|
|
base = ch10_offset
|
||
|
|
sync_pattern = struct.unpack('<H', self.payload[base:base+2])[0]
|
||
|
|
channel_id = struct.unpack('<H', self.payload[base+2:base+4])[0]
|
||
|
|
packet_length = struct.unpack('<I', self.payload[base+4:base+8])[0]
|
||
|
|
data_length = struct.unpack('<I', self.payload[base+8:base+12])[0]
|
||
|
|
header_version = self.payload[base+12]
|
||
|
|
sequence_number = self.payload[base+13]
|
||
|
|
packet_flags = self.payload[base+14]
|
||
|
|
data_type = self.payload[base+15]
|
||
|
|
rtc_low = struct.unpack('<I', self.payload[base+16:base+20])[0]
|
||
|
|
rtc_high = struct.unpack('<H', self.payload[base+20:base+22])[0]
|
||
|
|
checksum = struct.unpack('<H', self.payload[base+22:base+24])[0]
|
||
|
|
|
||
|
|
# Store the offset for reference
|
||
|
|
self.ch10_offset = ch10_offset
|
||
|
|
|
||
|
|
return {
|
||
|
|
'sync_pattern': f'0x{sync_pattern:04X}',
|
||
|
|
'channel_id': channel_id,
|
||
|
|
'packet_length': packet_length,
|
||
|
|
'data_length': data_length,
|
||
|
|
'header_version': header_version,
|
||
|
|
'sequence_number': sequence_number,
|
||
|
|
'packet_flags': f'0x{packet_flags:02X}',
|
||
|
|
'data_type': f'0x{data_type:02X}',
|
||
|
|
'rtc_low': rtc_low,
|
||
|
|
'rtc_high': rtc_high,
|
||
|
|
'checksum': f'0x{checksum:04X}',
|
||
|
|
'rtc_timestamp': (rtc_high << 32) | rtc_low,
|
||
|
|
'ch10_offset': ch10_offset
|
||
|
|
}
|
||
|
|
except (struct.error, IndexError):
|
||
|
|
return None
|