1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
From 1ce0622b3dd38e05e461821bd72dc3e1da9d5d3f Mon Sep 17 00:00:00 2001
From: "M. Greyson Christoforo" <grey@christoforo.net>
Date: Tue, 19 Aug 2025 02:36:22 -0600
Subject: [PATCH] some pcap decode improvements
fix print alignment
add prints for cmd and cmd_len
scaling fix
remove pdf
parse timestamp info from meter reading message
add walrus
---
apps/decode_pcap.py | 60 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 56 insertions(+), 4 deletions(-)
diff --git a/apps/decode_pcap.py b/apps/decode_pcap.py
index 67cb4a0..3fd462f 100755
--- a/apps/decode_pcap.py
+++ b/apps/decode_pcap.py
@@ -69,15 +69,34 @@ def decode_date(date_bytes):
def print_pkt(timestamp, pkt):
- print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)), end=" ")
- len1, flag1, src, dst, unk1, unk2, unk3 = struct.unpack(">BBIIBBB", pkt[0:13])
- print(f"len={len1:02x} flag={flag1:02x} {src=:08x} {dst=:08x} {unk1:02x}{unk2:02x}{unk3:02x}", end=" ")
- if (src & 0x80000000) or (dst == 0 and len1 >= 35):
+ print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)), end=" ")
+ if pkt[0] >= 2:
+ len_bytes = 1
+ len1 = pkt[0]
+ cmd_start = 15
+ else:
+ len_bytes = 2
+ len1 = (pkt[0] << 8) | pkt[1]
+ cmd_start = 20
+ payload = pkt[len_bytes:-2]
+ flag1, src, dst = struct.unpack(">BII", payload[0:9])
+ print(f"len={pkt[0:len_bytes].hex().ljust(4)} flag={flag1:02x} src={src:08x} dst={dst:08x} {payload[9:12].hex()} {payload[12:15].hex()}", end=" ")
+ pkt = pkt[-1 + len_bytes::]
+
+ if (0x10 & flag1):
ts_h, ts_m, ts_s = decode_ts(pkt[13:16])
print(f"ts={ts_h:02}:{ts_m:02}:{ts_s:06.3f}", end=" ")
else:
print("rpt=" + pkt[13:14].hex() + " " + pkt[14:16].hex(), end=" ")
+ if src & 0x80000000 == 0:
+ try:
+ cmd_len = payload[cmd_start]
+ cmd = bytes((payload[cmd_start+2],))
+ print(f"cmd_len={cmd_len} cmd={cmd.hex()}", end=" ")
+ except Exception as e:
+ pass
+
if dst == 0 and len1 >= 35: # flood broadcast message
unk4, unk5, hop, unk7, addr, unk8, len2 = struct.unpack(">BBBBIIB", pkt[16:29])
print(f"{unk4:02x}{unk5:02x} {hop=:02x} {unk7:02x} {addr=:08x} {unk8:08x} len={len2:02x}", end=" ")
@@ -180,6 +199,39 @@ def print_pkt(timestamp, pkt):
else:
print()
+ if src & 0x80000000 == 0 and len(payload) > cmd_start:
+ cmd_len = payload[cmd_start]
+ if cmd_len in (15, 171) and len(payload) >= cmd_start + 1 + cmd_len - 2:
+ cmd_payload = payload[cmd_start + 1:cmd_start + 1 + cmd_len]
+ cmd = cmd_payload[1]
+ if cmd == 0xce: # hourly usage data, every 6 hours
+ print()
+
+ main_reading = cmd_payload[44:47].hex()
+ print(f" Meter reading for meter #{src}: {main_reading} kWh")
+
+ n_hours = cmd_payload[9]
+ if n_hours > 17:
+ print(f" Number of hourly readings is too high: {n_hours}")
+ n_hours = 17
+ hourly_readings = [reading / 100 for reading in struct.unpack(">" + "H"*n_hours, cmd_payload[10:10 + 2*n_hours])]
+ readings_str = ", ".join(f"{reading:.2f}" for reading in hourly_readings)
+ print(f" Hourly readings: {readings_str}")
+
+ print()
+
+ elif cmd == 0x23: # usage message
+ if main_reading := struct.unpack("<i", cmd_payload[15:19])[0]/1000:
+ print()
+
+ YY, MM, DD, hh, mm, ss = struct.unpack("BBBBBB", cmd_payload[8:14])
+ ts_theirs = datetime.datetime(2000+YY, MM, DD, hh, mm, ss)
+ print(f" Reading for meter number {src} @ {ts_theirs.isoformat()} = {main_reading} kWh")
+
+ print()
+
+# for testing
+#sys.argv= ["junk", "/tmp/elster-042.pcap"]
if len(sys.argv) < 2:
sys.stderr.write("Usage: decode_pcap.py input_file...\n")
|