John draft, I asked Codex to review and it started changing, I intervened and asked to keep Codex review separate, I think it sucessfuly backed out its edits. Committing for posterity, there is a style difference between me and Codex.
This commit is contained in:
parent
197b46b4bb
commit
294a17660d
1 changed files with 505 additions and 48 deletions
|
|
@ -1,4 +1,18 @@
|
||||||
# History of a BOB to CY Link
|
# History of a BOB to CY Link
|
||||||
|
This is a forensic analysis to help understand and fix the failure of a LINK between two isolated units forced to use an intermediary for transport. This arrangement is the heart of a "mesh" network. The units are LilyGO T-Beam Supremes named: BOB, CY, DAN & ED. Additional units of AMY, FLO & GUY may be involved in later testing.
|
||||||
|
|
||||||
|
## Background & Tools
|
||||||
|
The version of this exercise, 204_established_identities, is:
|
||||||
|
```bash
|
||||||
|
commit 197b46b4bba2b49f38cddd51b25a7f264b3f348b (HEAD -> feature/fieldtest-beacon-sd-provision)
|
||||||
|
Author: John Poole <jlpoole56@gmail.com>
|
||||||
|
Date: Fri May 29 10:58:14 2026 -0700
|
||||||
|
```
|
||||||
|
`platformio.ini` was modified so that DAN & CY had additional debugging activated in microReticulum's Transport.cpp through the use of the build directive of `-D EX204_RNS_TRACE=1`.
|
||||||
|
|
||||||
|
The log captures were performed using 4 serial console connections (ED was defunct) using an bash script, `monitor_tbeam`, that adds colors using the Perl script: `color_rx_tx.pl`.
|
||||||
|
|
||||||
|
Finally all the logs were moved into their own run directory with the Perl script: `clump_tbeam_logs.pl`.
|
||||||
|
|
||||||
Source logs: `/home/jlpoole/logs/20260528_2319`
|
Source logs: `/home/jlpoole/logs/20260528_2319`
|
||||||
|
|
||||||
|
|
@ -7,46 +21,9 @@ block between BOB and CY was enabled, so direct BOB<->CY LoRa frames were
|
||||||
dropped below microReticulum in `TBeamSupremeLoRaInterface`. Packets physically
|
dropped below microReticulum in `TBeamSupremeLoRaInterface`. Packets physically
|
||||||
transmitted by DAN were still accepted by both BOB and CY.
|
transmitted by DAN were still accepted by both BOB and CY.
|
||||||
|
|
||||||
## Key Evidence With Exact Log Lines
|
### Time
|
||||||
|
`main.cpp` disciplines the time among the T-Beams, up to 24 hour drift is allowed. The workstation, jp, that the logging and serial port monitor occurred is disciplined to a local stratum 1 time server. Time stamps from the bash logging script exhibited some delay.
|
||||||
|
|
||||||
These are the most pertinent lines in `file:line:entry` form.
|
|
||||||
|
|
||||||
```text
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:85:20260528_232309.309 TX ANNOUNCE: Bob
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:88:20260528_232316.679 01-02:10:30.334 [NOT] MR TRANSPORT PATH: dest=f04ce61418eecae211de3f78d0e652e6 hops=2 via=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:89:20260528_232316.682 RX ANNOUNCE: label=Cy hash=f04ce61418eecae211de3f78d0e652e6 phy=Dan(3)
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:97:20260528_232322.256 TX LINKREQUEST: opening link to Cy slot=19
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:98:20260528_232322.386 01-02:10:36.041 [NOT] MR TRANSPORT OUT: type=LINKREQUEST dest=f04ce61418eecae211de3f78d0e652e6 hops=2 next=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1680:20260528_232322.594 23:23:59.788 [NOT] MR TRANSPORT FWD: type=LINKREQUEST dest=f04ce61418eecae211de3f78d0e652e6 remaining=1 next=f04ce61418eecae211de3f78d0e652e6 iface=Interface[TBeamSupremeLoRa]
|
|
||||||
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1681:20260528_232322.595 23:23:59.789 [---] Transport::inbound: Packet is next-hop LINKREQUEST
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1314:20260528_232322.767 18:52:08.577 [---] Transport::inbound: packet: ht=0 tt=0 dt=0 pt=2 hp=1 ti= dh=f04ce61418eecae211de3f78d0e652e6 ph=387de6033cddc91ac6c583721fa79eb84b58bfc84dc4b8f35fb1d98ef0f3bffc
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1320:20260528_232322.773 18:52:08.583 [---] Transport::inbound: Packet is LINKREQUEST
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1322:20260528_232322.774 18:52:08.584 [---] Transport::inbound: Found local destination for LINKREQUEST
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1323:20260528_232322.775 18:52:08.585 [---] ***** Accepting link request
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1335:20260528_232322.846 18:52:08.656 [DBG] Link 387de6033cddc91ac6c583721fa79eb8 requesting proof
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1347:20260528_232322.910 18:52:08.720 [NOT] MR TRANSPORT OUT: type=PROOF dest=387de6033cddc91ac6c583721fa79eb8 path=unknown
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:103:20260528_232323.747 LINK ACTIVE: initiator link established to Cy hash=387de6033cddc91ac6c583721fa79eb8
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:110:20260528_232411.271 TX LINK: Hi from Bob iter=5 to=Cy
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:112:20260528_232411.306 01-02:11:24.961 [NOT] MR TRANSPORT OUT: type=DATA dest=387de6033cddc91ac6c583721fa79eb8 path=unknown
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1885:20260528_232411.501 23:24:48.694 [---] Transport::inbound: packet: ht=0 tt=0 dt=3 pt=0 hp=0 ti= dh=387de6033cddc91ac6c583721fa79eb8 ph=838aaf52dc8f5a71ca7d878154504fce9471a5e40cad556f55739287c01720f2
|
|
||||||
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1893:20260528_232411.507 23:24:48.700 [---] Transport::inbound: Link inbound/outbound interfaes are same, transporting on same interface
|
|
||||||
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1894:20260528_232411.509 23:24:48.701 [NOT] MR TRANSPORT LINKFWD: dest=387de6033cddc91ac6c583721fa79eb8 hops=1 link_remaining=1 link_hops=1 iface=Interface[TBeamSupremeLoRa]
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:113:20260528_232411.702 RNSDEC ms=3378664 board=library role=unknown event=attempt link_id=387de6033cddc91ac6c583721fa79eb8 token_len=80 token_crc32=808C9304 sign_key_crc32=928C394D enc_key_crc32=D6DE582A link_obj={Link:387de6033cddc91ac6c583721fa79eb8}
|
|
||||||
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:114:20260528_232411.704 RX LINK ignored: self_or_wrong_recipient text=Hi from Bob iter=5 to=Cy
|
|
||||||
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1534:20260528_232411.704 18:52:57.513 [---] Transport::inbound: packet: ht=0 tt=0 dt=3 pt=0 hp=1 ti= dh=387de6033cddc91ac6c583721fa79eb8 ph=838aaf52dc8f5a71ca7d878154504fce9471a5e40cad556f55739287c01720f2
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1541:20260528_232411.711 18:52:57.519 [---] Transport::inbound: Packet is DATA
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1542:20260528_232411.712 18:52:57.520 [---] Transport::inbound: Packet is DATA for a LINK
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1543:20260528_232411.712 18:52:57.520 [---] Transport::inbound: Packet is DATA for an active LINK
|
|
||||||
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1545:20260528_232411.714 RNSDEC ms=1197054 board=library role=unknown event=attempt link_id=387de6033cddc91ac6c583721fa79eb8 token_len=80 token_crc32=808C9304 sign_key_crc32=928C394D enc_key_crc32=D6DE582A link_obj={Link:387de6033cddc91ac6c583721fa79eb8}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Identity Legend
|
## Identity Legend
|
||||||
|
|
||||||
|
|
@ -65,8 +42,128 @@ The application policy is that BOB is the initiator for this BOB/CY pair. The
|
||||||
logs do not show the identity comparison itself, but they do show BOB initiating
|
logs do not show the identity comparison itself, but they do show BOB initiating
|
||||||
the LinkRequest and CY accepting the inbound request.
|
the LinkRequest and CY accepting the inbound request.
|
||||||
|
|
||||||
## 1. Announcements Establish the Path
|
## Software isolation of BOB and CY
|
||||||
|
To simulate BOB and CY being out of radio range, we resorted to blocking packets at each unit. Here is a Codex explanation of the blocking mechanism designed to be outside of the Reticulum code.
|
||||||
|
|
||||||
|
Blocking Implementation
|
||||||
|
|
||||||
|
Here is Codex's explanation of our blockage:
|
||||||
|
|
||||||
|
We are simulating BOB/CY RF separation below microReticulum, inside the T-Beam LoRa interface adapter. microReticulum packets are not inspected, parsed, or altered for the block decision.
|
||||||
|
|
||||||
|
Each transmitted LoRa frame gets a small 4-byte simulation envelope prepended by TBeamSupremeLoRaInterface:
|
||||||
|
|
||||||
|
|
||||||
|
[ SIM PHY ENVELOPE ][ local LoRa fragment header ][ raw microReticulum packet bytes ... ]
|
||||||
|
|
||||||
|
SIM PHY ENVELOPE:
|
||||||
|
byte 0: 0xC2 magic
|
||||||
|
byte 1: 0x04 exercise marker
|
||||||
|
byte 2: 0x01 envelope version
|
||||||
|
byte 3: NODE_SLOT physical transmitter ID
|
||||||
|
|
||||||
|
NODE_SLOT:
|
||||||
|
1 = BOB
|
||||||
|
2 = CY
|
||||||
|
3 = DAN
|
||||||
|
4 = ED
|
||||||
|
|
||||||
|
On receive, the LoRa adapter strips that 4-byte envelope before passing bytes to microReticulum. So microReticulum receives the same byte stream it would have received without this simulation wrapper.
|
||||||
|
|
||||||
|
The block rule is only:
|
||||||
|
if local node is BOB and physical transmitter is CY: drop before microReticulum
|
||||||
|
if local node is CY and physical transmitter is BOB: drop before microReticulum
|
||||||
|
otherwise: strip envelope and deliver to microReticulum
|
||||||
|
|
||||||
|
Example: direct BOB -> CY is blocked before microReticulum sees it:
|
||||||
|
|
||||||
|
LoRa air frame:
|
||||||
|
c2 04 01 01 | H | <microReticulum packet bytes>
|
||||||
|
^^
|
||||||
|
physical_tx = 1 = BOB
|
||||||
|
|
||||||
|
CY receives:
|
||||||
|
local = 2 = CY
|
||||||
|
physical_tx = 1 = BOB
|
||||||
|
decision = DROP
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
InterfaceImpl::handle_incoming() is not called.
|
||||||
|
microReticulum never sees this packet.
|
||||||
|
|
||||||
|
Example: BOB traffic transported/rebroadcast by DAN is allowed:
|
||||||
|
|
||||||
|
LoRa air frame:
|
||||||
|
|
||||||
|
c2 04 01 03 | H | <microReticulum packet bytes>
|
||||||
|
^^
|
||||||
|
physical_tx = 3 = DAN
|
||||||
|
|
||||||
|
CY receives:
|
||||||
|
|
||||||
|
local = 2 = CY
|
||||||
|
physical_tx = 3 = DAN
|
||||||
|
decision = ACCEPT
|
||||||
|
|
||||||
|
Adapter strips envelope:
|
||||||
|
H | <microReticulum packet bytes>
|
||||||
|
|
||||||
|
Then calls:
|
||||||
|
InterfaceImpl::handle_incoming(<microReticulum packet bytes>)
|
||||||
|
|
||||||
|
So the important point is: the BOB/CY block is a physical-link simulation in TBeamSupremeLoRaInterface, not a microReticulum-layer rule. A packet physically emitted by DAN or ED is allowed through even if the Reticulum payload originated from BOB or CY.
|
||||||
|
|
||||||
|
|
||||||
|
# Analysis
|
||||||
|
## 1. Announcements Establish the Path
|
||||||
|
### ANNOUNCEMENT
|
||||||
|
An ANNOUNCEMENT is a packet that contains a DESTINATION, aka "DESTINATION HASH", and PUBLIC KEY, and possibly other information. See https://reticulum.network/manual/understanding.html#public-key-announcements
|
||||||
|
|
||||||
|
#### DESTINATION
|
||||||
|
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead Reticulum uses the singular concept of destinations. Destinations are represented as a 16 byte hash. The inbound_destination is computed at main.cpp (line 1020):
|
||||||
|
```cpp
|
||||||
|
inbound_destination = RNS::Destination(local_identity,
|
||||||
|
RNS::Type::Destination::IN,
|
||||||
|
RNS::Type::Destination::SINGLE,
|
||||||
|
APP_NAME,
|
||||||
|
APP_ASPECT);
|
||||||
|
```
|
||||||
|
and APP_NAME and APP_ASPECT are arbitrary strings defined at:
|
||||||
|
```cpp
|
||||||
|
static constexpr const char* APP_NAME = "microreticulum";
|
||||||
|
static constexpr const char* APP_ASPECT = "linkping";
|
||||||
|
```
|
||||||
|
So BOB's destination hash is derived from:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Bob's fixed identity
|
||||||
|
+ app name: microreticulum
|
||||||
|
+ aspect: linkping
|
||||||
|
```
|
||||||
|
microReticulum expands that into a destination name and hashes it. The relevant library code is:
|
||||||
|
```cpp
|
||||||
|
Bytes addr_hash_material = name_hash(app_name, aspects);
|
||||||
|
if (identity) {
|
||||||
|
addr_hash_material << identity.hash();
|
||||||
|
}
|
||||||
|
return Identity::truncated_hash(addr_hash_material);
|
||||||
|
```
|
||||||
|
For purposes of this analysis, BOB's destination hash is: e431430abeca68dca8411f50ca9864b0
|
||||||
|
|
||||||
|
#### PUUBLIC KEY
|
||||||
|
PUBLIC KEY was generated from the Reticulum utility `rnid`. BOB's identity consists of:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(rnsenv) jlpoole@jp /usr/local/src/microreticulum/microReticulumTbeam/exercises/204_established_identities/identities $ cat BOB.identity_info.txt
|
||||||
|
[2026-05-28 11:26:35] Loaded Identity <5769e13e1214e62b96e43c17bd47085e> from ./BOB.identity
|
||||||
|
[2026-05-28 11:26:35] Public Key : 4705bf1ab8a17cc3bb07d1fa51d4fe59dc92e4e93fd9d55124c808079d33744e894241f52583b3abc7bcaf48a5fad31554c2dee142dbbc3c4d4c5e3855d94814
|
||||||
|
[2026-05-28 11:26:35] Private Key : Hidden
|
||||||
|
(rnsenv) jlpoole@jp /usr/local/src/microreticulum/microReticulumTbeam/exercises/204_established_identities/identities $
|
||||||
|
```
|
||||||
|
Therein is BOB's Public KEY: 4705...4814.
|
||||||
|
|
||||||
|
#### BOB's ANNOUNCEMENT
|
||||||
BOB announces directly. DAN receives the BOB announce and records a direct
|
BOB announces directly. DAN receives the BOB announce and records a direct
|
||||||
one-hop path to BOB:
|
one-hop path to BOB:
|
||||||
|
|
||||||
|
|
@ -77,7 +174,7 @@ DAN line 1425: MR TRANSPORT PATH: dest=e431430abeca68dca8411f50ca9864b0 hops=1 v
|
||||||
DAN line 1436: RX ANNOUNCE: label=Bob hash=e431430abeca68dca8411f50ca9864b0 phy=Bob(1)
|
DAN line 1436: RX ANNOUNCE: label=Bob hash=e431430abeca68dca8411f50ca9864b0 phy=Bob(1)
|
||||||
```
|
```
|
||||||
|
|
||||||
DAN rebroadcasts BOB's announce with DAN as the transport ID:
|
DAN rebroadcasts BOB's announce with DAN as the transport ID (d052...a30):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DAN line 1444: Rebroadcasting announce for e431430abeca68dca8411f50ca9864b0 with hop count 1
|
DAN line 1444: Rebroadcasting announce for e431430abeca68dca8411f50ca9864b0 with hop count 1
|
||||||
|
|
@ -95,16 +192,15 @@ CY line 1517: Destination e431430abeca68dca8411f50ca9864b0 is now 2 hops away vi
|
||||||
CY line 1518: MR TRANSPORT PATH: dest=e431430abeca68dca8411f50ca9864b0 hops=2 via=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
CY line 1518: MR TRANSPORT PATH: dest=e431430abeca68dca8411f50ca9864b0 hops=2 via=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
||||||
CY line 1529: RX ANNOUNCE: label=Bob hash=e431430abeca68dca8411f50ca9864b0 phy=Dan(3)
|
CY line 1529: RX ANNOUNCE: label=Bob hash=e431430abeca68dca8411f50ca9864b0 phy=Dan(3)
|
||||||
```
|
```
|
||||||
|
Line 1529 above confirms that CY received that rebroadcast from DAN and CY now knows Bob is reachable via DAN.
|
||||||
|
|
||||||
The reverse direction is also known. BOB repeatedly receives CY announces via
|
Likewise, for BOB, at line 89 below, the reverse direction is also known. BOB repeatedly receives CY announces via DAN and records a two-hop path to CY:
|
||||||
DAN and records a two-hop path to CY:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
BOB line 88: MR TRANSPORT PATH: dest=f04ce61418eecae211de3f78d0e652e6 hops=2 via=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
BOB line 88: MR TRANSPORT PATH: dest=f04ce61418eecae211de3f78d0e652e6 hops=2 via=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
||||||
BOB line 89: RX ANNOUNCE: label=Cy hash=f04ce61418eecae211de3f78d0e652e6 phy=Dan(3)
|
BOB line 89: RX ANNOUNCE: label=Cy hash=f04ce61418eecae211de3f78d0e652e6 phy=Dan(3)
|
||||||
```
|
```
|
||||||
|
At this point, BOB & CY have each other's destinations and know that the other is reachable through DAN. The rule employed in this exercise is that the "lower" identity is the unit that initiates a LINK. (This would not be a real life scenario, but this rule is adopted to keep transmissions over the LoRa to a minimum and to easily progress with linking.) In this exercise, each unit is aware of the other's id, sort of an address book, so BOB can determine it is its responsibility to initiate a LINK with CY.
|
||||||
At this point, BOB knows that CY is reachable through DAN.
|
|
||||||
|
|
||||||
## 2. BOB Initiates the LinkRequest
|
## 2. BOB Initiates the LinkRequest
|
||||||
|
|
||||||
|
|
@ -116,6 +212,7 @@ two-hop destination via DAN, BOB sends the LinkRequest to DAN as the next hop:
|
||||||
BOB line 97: TX LINKREQUEST: opening link to Cy slot=19
|
BOB line 97: TX LINKREQUEST: opening link to Cy slot=19
|
||||||
BOB line 98: MR TRANSPORT OUT: type=LINKREQUEST dest=f04ce61418eecae211de3f78d0e652e6 hops=2 next=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
BOB line 98: MR TRANSPORT OUT: type=LINKREQUEST dest=f04ce61418eecae211de3f78d0e652e6 hops=2 next=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
||||||
```
|
```
|
||||||
|
In line 98 above, "dest" stands for destination, e.g. "f04c...e6", and utilizes the value from the "hash" value in BOB's log, line 89 above.
|
||||||
|
|
||||||
DAN receives that transported LinkRequest. DAN recognizes that it is the
|
DAN receives that transported LinkRequest. DAN recognizes that it is the
|
||||||
designated next hop and forwards the request toward CY:
|
designated next hop and forwards the request toward CY:
|
||||||
|
|
@ -125,7 +222,7 @@ DAN line 1680: MR TRANSPORT FWD: type=LINKREQUEST dest=f04ce61418eecae211de3f78d
|
||||||
DAN line 1681: Transport::inbound: Packet is next-hop LINKREQUEST
|
DAN line 1681: Transport::inbound: Packet is next-hop LINKREQUEST
|
||||||
```
|
```
|
||||||
|
|
||||||
CY also sees one direct physical BOB frame and drops it at the simulated PHY
|
Be aware that CY also sees one direct physical BOB frame and drops it at the simulated PHY
|
||||||
layer. This is expected and is not a microReticulum decision:
|
layer. This is expected and is not a microReticulum decision:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -166,8 +263,7 @@ BOB line 102: MR TRANSPORT OUT: type=DATA dest=387de6033cddc91ac6c583721fa79eb8
|
||||||
BOB line 103: LINK ACTIVE: initiator link established to Cy hash=387de6033cddc91ac6c583721fa79eb8
|
BOB line 103: LINK ACTIVE: initiator link established to Cy hash=387de6033cddc91ac6c583721fa79eb8
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the key success point: with ED removed, BOB successfully establishes a
|
BOB successfully establishes a Link to CY through DAN. (Previously in the 4 unit test, this event never occurred. This is the key success point: with ED removed.)
|
||||||
Link to CY through DAN.
|
|
||||||
|
|
||||||
## 4. First Data Packet After Link Establishment
|
## 4. First Data Packet After Link Establishment
|
||||||
|
|
||||||
|
|
@ -233,3 +329,364 @@ expected `RX LINK` application payload line.
|
||||||
5. The failure point is after Link data forwarding on the same LoRa interface.
|
5. The failure point is after Link data forwarding on the same LoRa interface.
|
||||||
The forwarded packet is heard by BOB and apparently reaches CY, but CY does
|
The forwarded packet is heard by BOB and apparently reaches CY, but CY does
|
||||||
not produce the expected decrypted application payload log.
|
not produce the expected decrypted application payload log.
|
||||||
|
|
||||||
|
# Appendix
|
||||||
|
## Key Evidence With Exact Log Lines
|
||||||
|
|
||||||
|
These are the most pertinent lines in `file:line:entry` form.
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:85:20260528_232309.309 TX ANNOUNCE: Bob
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:88:20260528_232316.679 01-02:10:30.334 [NOT] MR TRANSPORT PATH: dest=f04ce61418eecae211de3f78d0e652e6 hops=2 via=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:89:20260528_232316.682 RX ANNOUNCE: label=Cy hash=f04ce61418eecae211de3f78d0e652e6 phy=Dan(3)
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:97:20260528_232322.256 TX LINKREQUEST: opening link to Cy slot=19
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:98:20260528_232322.386 01-02:10:36.041 [NOT] MR TRANSPORT OUT: type=LINKREQUEST dest=f04ce61418eecae211de3f78d0e652e6 hops=2 next=d0524d8f1d98fc39f13772655640ea30 iface=Interface[TBeamSupremeLoRa]
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1680:20260528_232322.594 23:23:59.788 [NOT] MR TRANSPORT FWD: type=LINKREQUEST dest=f04ce61418eecae211de3f78d0e652e6 remaining=1 next=f04ce61418eecae211de3f78d0e652e6 iface=Interface[TBeamSupremeLoRa]
|
||||||
|
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1681:20260528_232322.595 23:23:59.789 [---] Transport::inbound: Packet is next-hop LINKREQUEST
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1314:20260528_232322.767 18:52:08.577 [---] Transport::inbound: packet: ht=0 tt=0 dt=0 pt=2 hp=1 ti= dh=f04ce61418eecae211de3f78d0e652e6 ph=387de6033cddc91ac6c583721fa79eb84b58bfc84dc4b8f35fb1d98ef0f3bffc
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1320:20260528_232322.773 18:52:08.583 [---] Transport::inbound: Packet is LINKREQUEST
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1322:20260528_232322.774 18:52:08.584 [---] Transport::inbound: Found local destination for LINKREQUEST
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1323:20260528_232322.775 18:52:08.585 [---] ***** Accepting link request
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1335:20260528_232322.846 18:52:08.656 [DBG] Link 387de6033cddc91ac6c583721fa79eb8 requesting proof
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1347:20260528_232322.910 18:52:08.720 [NOT] MR TRANSPORT OUT: type=PROOF dest=387de6033cddc91ac6c583721fa79eb8 path=unknown
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:103:20260528_232323.747 LINK ACTIVE: initiator link established to Cy hash=387de6033cddc91ac6c583721fa79eb8
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:110:20260528_232411.271 TX LINK: Hi from Bob iter=5 to=Cy
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:112:20260528_232411.306 01-02:11:24.961 [NOT] MR TRANSPORT OUT: type=DATA dest=387de6033cddc91ac6c583721fa79eb8 path=unknown
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1885:20260528_232411.501 23:24:48.694 [---] Transport::inbound: packet: ht=0 tt=0 dt=3 pt=0 hp=0 ti= dh=387de6033cddc91ac6c583721fa79eb8 ph=838aaf52dc8f5a71ca7d878154504fce9471a5e40cad556f55739287c01720f2
|
||||||
|
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1893:20260528_232411.507 23:24:48.700 [---] Transport::inbound: Link inbound/outbound interfaes are same, transporting on same interface
|
||||||
|
/home/jlpoole/logs/20260528_2319/DAN_raw_20260528_231958.log:1894:20260528_232411.509 23:24:48.701 [NOT] MR TRANSPORT LINKFWD: dest=387de6033cddc91ac6c583721fa79eb8 hops=1 link_remaining=1 link_hops=1 iface=Interface[TBeamSupremeLoRa]
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:113:20260528_232411.702 RNSDEC ms=3378664 board=library role=unknown event=attempt link_id=387de6033cddc91ac6c583721fa79eb8 token_len=80 token_crc32=808C9304 sign_key_crc32=928C394D enc_key_crc32=D6DE582A link_obj={Link:387de6033cddc91ac6c583721fa79eb8}
|
||||||
|
/home/jlpoole/logs/20260528_2319/BOB_raw_20260528_231955.log:114:20260528_232411.704 RX LINK ignored: self_or_wrong_recipient text=Hi from Bob iter=5 to=Cy
|
||||||
|
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1534:20260528_232411.704 18:52:57.513 [---] Transport::inbound: packet: ht=0 tt=0 dt=3 pt=0 hp=1 ti= dh=387de6033cddc91ac6c583721fa79eb8 ph=838aaf52dc8f5a71ca7d878154504fce9471a5e40cad556f55739287c01720f2
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1541:20260528_232411.711 18:52:57.519 [---] Transport::inbound: Packet is DATA
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1542:20260528_232411.712 18:52:57.520 [---] Transport::inbound: Packet is DATA for a LINK
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1543:20260528_232411.712 18:52:57.520 [---] Transport::inbound: Packet is DATA for an active LINK
|
||||||
|
/home/jlpoole/logs/20260528_2319/CY_raw_20260528_231957.log:1545:20260528_232411.714 RNSDEC ms=1197054 board=library role=unknown event=attempt link_id=387de6033cddc91ac6c583721fa79eb8 token_len=80 token_crc32=808C9304 sign_key_crc32=928C394D enc_key_crc32=D6DE582A link_obj={Link:387de6033cddc91ac6c583721fa79eb8}
|
||||||
|
```
|
||||||
|
## bash script monitor_tbeam
|
||||||
|
```bash
|
||||||
|
(rnsenv) jlpoole@jp ~ $ cat /usr/bin/monitor_tbeam
|
||||||
|
#!/bin/bash
|
||||||
|
# 20260528 ChatGPT
|
||||||
|
# $Header: https://ares/svn/workstation/trunk/scripts/monitor_tbeam.sh 46 2026-05-29 05:04:50Z jlpoole $
|
||||||
|
# $HeadURL: https://ares/svn/workstation/trunk/scripts/monitor_tbeam.sh $
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# monitor_tbeam ED
|
||||||
|
#
|
||||||
|
# Install:
|
||||||
|
# sudo ln -s /home/jlpoole/workstation/scripts/monitor_tbeam.sh /usr/bin/monitor_tbeam
|
||||||
|
# sudo chmod +x /home/jlpoole/workstation/scripts/monitor_tbeam.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
export TBEAM="$1"
|
||||||
|
|
||||||
|
if [ -z "${TBEAM}" ]; then
|
||||||
|
echo "Usage: monitor_tbeam ED"
|
||||||
|
echo "Example: monitor_tbeam GUY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source ~/rnsenv/bin/activate
|
||||||
|
|
||||||
|
mkdir -p ~/logs
|
||||||
|
|
||||||
|
LOGFILE=~/logs/${TBEAM}_raw_$(date +%Y%m%d_%H%M%S).log
|
||||||
|
|
||||||
|
pio device monitor -b 115200 -p "/dev/ttyt${TBEAM}" 2>&1 \
|
||||||
|
| perl -MTime::HiRes=time -MPOSIX=strftime -ne '
|
||||||
|
BEGIN {
|
||||||
|
$| = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s/\r?\n$//;
|
||||||
|
|
||||||
|
my $t = time();
|
||||||
|
my $s = int($t);
|
||||||
|
my $ms = int(($t - $s) * 1000);
|
||||||
|
|
||||||
|
my $stamp = strftime("%Y%m%d_%H%M%S", localtime($s));
|
||||||
|
|
||||||
|
printf "%s.%03d %s\n", $stamp, $ms, $_;
|
||||||
|
' \
|
||||||
|
| tee "$LOGFILE" \
|
||||||
|
| /usr/bin/color_rx_tx.pl
|
||||||
|
(rnsenv) jlpoole@jp ~ $
|
||||||
|
|
||||||
|
```
|
||||||
|
## Perl script color_rx_tx.pl
|
||||||
|
```perl
|
||||||
|
jlpoole@jp ~/workstation/perl $ cat color_rx_tx.pl
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
# 20260528 ChatGPT
|
||||||
|
# $Header: https://ares/svn/workstation/trunk/perl/color_rx_tx.pl 44 2026-05-28 23:36:24Z jlpoole $
|
||||||
|
# $HeadURL: https://ares/svn/workstation/trunk/perl/color_rx_tx.pl $
|
||||||
|
#
|
||||||
|
# sudo ln -s /home/jlpoole/workstation/perl/color_rx_tx.pl /usr/bin/color_rx_tx.pl
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
|
||||||
|
my $BLUE = "\e[96m"; # light blue / cyan
|
||||||
|
my $GREEN = "\e[92m"; # light green
|
||||||
|
my $RESET = "\e[0m";
|
||||||
|
|
||||||
|
while (my $line = <STDIN>) {
|
||||||
|
$line =~ s/\r?\n$//;
|
||||||
|
|
||||||
|
if ($line =~ /\bTX\b/) {
|
||||||
|
print $BLUE, $line, $RESET, "\n";
|
||||||
|
}
|
||||||
|
elsif ($line =~ /\bRX\b/) {
|
||||||
|
print $GREEN, $line, $RESET, "\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print $line, "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jlpoole@jp ~/workstation/perl $
|
||||||
|
|
||||||
|
```
|
||||||
|
## Perl script clump_tbeam_logs.pl
|
||||||
|
```perl
|
||||||
|
jlpoole@jp ~/workstation/perl $ cat clump_tbeam_logs.pl
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# chmod 755 clump_tbeam_logs.pl
|
||||||
|
# ./clump_tbeam_logs.pl
|
||||||
|
# ./clump_tbeam_logs.pl --dry-run
|
||||||
|
# ./clump_tbeam_logs.pl --dir ~/logs --window 60 --min-files 4
|
||||||
|
#
|
||||||
|
# 20260528 ChatGPT
|
||||||
|
# $Header: https://ares/svn/workstation/trunk/perl/clump_tbeam_logs.pl 44 2026-05-28 23:36:24Z jlpoole $
|
||||||
|
# $HeadURL: https://ares/svn/workstation/trunk/perl/clump_tbeam_logs.pl $
|
||||||
|
#
|
||||||
|
=pod
|
||||||
|
|
||||||
|
chmod +x clump_tbeam_logs.pl
|
||||||
|
sudo ln -s /home/jlpoole/workstation/perl/clump_tbeam_logs.pl /usr/bin/clump_tbeam_logs.pl
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Getopt::Long qw(GetOptions);
|
||||||
|
use File::Basename qw(basename);
|
||||||
|
use File::Path qw(make_path);
|
||||||
|
use File::Spec;
|
||||||
|
use Time::Piece;
|
||||||
|
use List::Util qw(max);
|
||||||
|
|
||||||
|
my $log_dir = "$ENV{HOME}/logs";
|
||||||
|
my $window = 60; # seconds
|
||||||
|
my $min_files = 2; # use 4 if you want to require four consoles
|
||||||
|
my $dry_run = 0;
|
||||||
|
my $help = 0;
|
||||||
|
|
||||||
|
GetOptions(
|
||||||
|
'dir=s' => \$log_dir,
|
||||||
|
'window=i' => \$window,
|
||||||
|
'min-files=i' => \$min_files,
|
||||||
|
'dry-run' => \$dry_run,
|
||||||
|
'help' => \$help,
|
||||||
|
) or die "Bad option. Try --help\n";
|
||||||
|
|
||||||
|
if ($help) {
|
||||||
|
print <<"HELP";
|
||||||
|
Usage:
|
||||||
|
./clump_tbeam_logs.pl [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--dir DIR Log directory. Default: ~/logs
|
||||||
|
--window SECONDS Timestamp grouping window. Default: 60
|
||||||
|
--min-files N Minimum matching files required. Default: 2
|
||||||
|
--dry-run Show what would happen, but do not move files.
|
||||||
|
--help Show this help.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
./clump_tbeam_logs.pl --dir ~/logs --window 60 --min-files 4
|
||||||
|
HELP
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Directory not found: $log_dir\n" unless -d $log_dir;
|
||||||
|
|
||||||
|
my @units = qw(AMY BOB CY DAN ED FLO GUY);
|
||||||
|
my $unit_re = join '|', @units;
|
||||||
|
|
||||||
|
print "\nContents of $log_dir:\n";
|
||||||
|
print "-" x 72, "\n";
|
||||||
|
|
||||||
|
my @all_paths = glob(File::Spec->catfile($log_dir, '*'));
|
||||||
|
|
||||||
|
my @listed = sort {
|
||||||
|
(stat($b))[9] <=> (stat($a))[9]
|
||||||
|
} @all_paths;
|
||||||
|
|
||||||
|
for my $path (@listed) {
|
||||||
|
my @st = stat($path);
|
||||||
|
next unless @st;
|
||||||
|
|
||||||
|
my $mtime = localtime($st[9])->strftime('%Y-%m-%d %H:%M:%S');
|
||||||
|
my $size = $st[7];
|
||||||
|
my $name = basename($path);
|
||||||
|
|
||||||
|
printf "%s %10d %s\n", $mtime, $size, $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
print "-" x 72, "\n\n";
|
||||||
|
|
||||||
|
my @candidates;
|
||||||
|
|
||||||
|
for my $path (@all_paths) {
|
||||||
|
next unless -f $path;
|
||||||
|
|
||||||
|
my $base = basename($path);
|
||||||
|
|
||||||
|
next unless $base =~ /(?:^|[_\-.])($unit_re)(?:[_\-.]|$)/i;
|
||||||
|
my $unit = uc($1);
|
||||||
|
|
||||||
|
next unless $base =~ /(\d{8}_\d{6})/;
|
||||||
|
my $stamp = $1;
|
||||||
|
|
||||||
|
my $tp;
|
||||||
|
eval {
|
||||||
|
$tp = Time::Piece->strptime($stamp, '%Y%m%d_%H%M%S');
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
warn "Skipping file with bad timestamp: $base\n";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $minute_stamp = substr($stamp, 0, 13); # YYYYMMDD_HHMM
|
||||||
|
|
||||||
|
push @candidates, {
|
||||||
|
path => $path,
|
||||||
|
base => $base,
|
||||||
|
unit => $unit,
|
||||||
|
stamp => $stamp,
|
||||||
|
minute_stamp => $minute_stamp,
|
||||||
|
epoch => $tp->epoch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
die "No matching T-Beam log files found in $log_dir\n"
|
||||||
|
unless @candidates;
|
||||||
|
|
||||||
|
@candidates = sort {
|
||||||
|
$b->{epoch} <=> $a->{epoch}
|
||||||
|
} @candidates;
|
||||||
|
|
||||||
|
my $seed = $candidates[0];
|
||||||
|
|
||||||
|
my @window_matches = grep {
|
||||||
|
abs($_->{epoch} - $seed->{epoch}) <= $window
|
||||||
|
} @candidates;
|
||||||
|
|
||||||
|
# Keep only the newest file per unit inside the time window.
|
||||||
|
my %best_for_unit;
|
||||||
|
|
||||||
|
for my $item (@window_matches) {
|
||||||
|
my $unit = $item->{unit};
|
||||||
|
|
||||||
|
if (!exists $best_for_unit{$unit}
|
||||||
|
|| $item->{epoch} > $best_for_unit{$unit}->{epoch}) {
|
||||||
|
$best_for_unit{$unit} = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my @selected = sort {
|
||||||
|
$a->{epoch} <=> $b->{epoch}
|
||||||
|
} values %best_for_unit;
|
||||||
|
|
||||||
|
if (@selected < $min_files) {
|
||||||
|
print "Newest matching file:\n";
|
||||||
|
printf " %s %s %s\n", $seed->{unit}, $seed->{stamp}, $seed->{base};
|
||||||
|
|
||||||
|
print "\nFiles found within +/- $window seconds:\n";
|
||||||
|
for my $item (@selected) {
|
||||||
|
printf " %-3s %s %s\n",
|
||||||
|
$item->{unit}, $item->{stamp}, $item->{base};
|
||||||
|
}
|
||||||
|
|
||||||
|
die "\nOnly found " . scalar(@selected) .
|
||||||
|
" matching file(s), but --min-files is $min_files. No files moved.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my %minute_count;
|
||||||
|
for my $item (@selected) {
|
||||||
|
$minute_count{ $item->{minute_stamp} }++;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $max_count = max(values %minute_count);
|
||||||
|
|
||||||
|
my @mode_minutes = sort grep {
|
||||||
|
$minute_count{$_} == $max_count
|
||||||
|
} keys %minute_count;
|
||||||
|
|
||||||
|
# Tie-breaker: use the newest file's minute if it is one of the modes;
|
||||||
|
# otherwise use the latest mode minute.
|
||||||
|
my $dir_stamp = $seed->{minute_stamp};
|
||||||
|
|
||||||
|
if (!grep { $_ eq $dir_stamp } @mode_minutes) {
|
||||||
|
$dir_stamp = $mode_minutes[-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
my $target_dir = File::Spec->catdir($log_dir, $dir_stamp);
|
||||||
|
|
||||||
|
print "Seed file:\n";
|
||||||
|
printf " %-3s %s %s\n", $seed->{unit}, $seed->{stamp}, $seed->{base};
|
||||||
|
|
||||||
|
print "\nSelected files within +/- $window seconds:\n";
|
||||||
|
for my $item (@selected) {
|
||||||
|
printf " %-3s %s %s\n",
|
||||||
|
$item->{unit}, $item->{stamp}, $item->{base};
|
||||||
|
}
|
||||||
|
|
||||||
|
print "\nMost common minute stamp: $dir_stamp\n";
|
||||||
|
print "Target directory: $target_dir\n\n";
|
||||||
|
|
||||||
|
if ($dry_run) {
|
||||||
|
print "DRY RUN: no files will be moved.\n\n";
|
||||||
|
} else {
|
||||||
|
if (!-d $target_dir) {
|
||||||
|
print "Creating directory: $target_dir\n";
|
||||||
|
make_path($target_dir) or die "Unable to create $target_dir: $!\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $item (@selected) {
|
||||||
|
my $src = $item->{path};
|
||||||
|
my $dst = File::Spec->catfile($target_dir, $item->{base});
|
||||||
|
|
||||||
|
if (-e $dst) {
|
||||||
|
die "Refusing to overwrite existing file: $dst\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dry_run) {
|
||||||
|
print "Would move: $src\n";
|
||||||
|
print " to: $dst\n";
|
||||||
|
} else {
|
||||||
|
print "Moving: $item->{base}\n";
|
||||||
|
rename($src, $dst) or die "rename failed for $src -> $dst: $!\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print "\nDone.\n";
|
||||||
|
jlpoole@jp ~/workstation/perl $
|
||||||
|
|
||||||
|
```
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue