ble-reticulum/migration/reports/fragmentation_cpp_equivalence_20260516_1718.md

26 KiB

C++ Protocol Core Fragmentation Equivalence Test

Date: 2026-05-16 Host: jp Python environment: rnsenv Repository: /usr/local/src/ble-reticulum

Command

cd /usr/local/src/ble-reticulum
python3 -m pytest migration/tests/test_fragmentation_cpp_equivalence.py -vv

Result

28 tests passed.

Coverage Summary

The C++ implementation was compared against the existing Python implementation for:

(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ cd /usr/local/src/ble-reticulum
python3 -m pytest migration/tests/test_fragmentation_cpp_equivalence.py -vv
======================================================================================== test session starts ========================================================================================
platform linux -- Python 3.13.12, pytest-9.0.3, pluggy-1.6.0 -- /home/jlpoole/rnsenv/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: anyio-4.12.1, asyncio-1.3.0
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 28 items                                                                                                                                                                                  

migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_single_fragment_packets PASSED                                                                   [  3%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_multi_fragment_packets PASSED                                                                    [  7%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_mtu_boundary_sizes[20] PASSED                                                                    [ 10%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_mtu_boundary_sizes[23] PASSED                                                                    [ 14%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_mtu_boundary_sizes[50] PASSED                                                                    [ 17%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_mtu_boundary_sizes[185] PASSED                                                                   [ 21%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEFragmenterCppEquivalence::test_empty_and_non_bytes_packet_errors PASSED                                                         [ 25%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_single_fragment_reassembly PASSED                                                               [ 28%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_multi_fragment_reassembly PASSED                                                                [ 32%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_out_of_order_fragments_with_start_first PASSED                                                  [ 35%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_malformed_fragments PASSED                                                                      [ 39%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_duplicate_fragments_same_data PASSED                                                            [ 42%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_duplicate_fragments_different_data PASSED                                                       [ 46%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_stale_buffer_cleanup PASSED                                                                     [ 50%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_statistics_reset PASSED                                                                         [ 53%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestBLEReassemblerCppEquivalence::test_internal_reassemble_method_matches_python PASSED                                                [ 57%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_frame_deframe_round_trips[] PASSED                                                                  [ 60%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_frame_deframe_round_trips[Hello, World!] PASSED                                                     [ 64%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_frame_deframe_round_trips[~\x01~] PASSED                                                            [ 67%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_frame_deframe_round_trips[}\x02}] PASSED                                                            [ 71%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_frame_deframe_round_trips[\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff] PASSED [ 75%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_many_hdlc_round_trips PASSED                                                                        [ 78%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_invalid_hdlc_escape_sequences_and_frames[] PASSED                                                   [ 82%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_invalid_hdlc_escape_sequences_and_frames[~] PASSED                                                  [ 85%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_invalid_hdlc_escape_sequences_and_frames[missing-flags] PASSED                                      [ 89%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_invalid_hdlc_escape_sequences_and_frames[~\x01~~] PASSED                                            [ 92%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_invalid_hdlc_escape_sequences_and_frames[~}~] PASSED                                                [ 96%]
migration/tests/test_fragmentation_cpp_equivalence.py::TestHDLCFramerCppEquivalence::test_non_bytes_errors PASSED                                                                             [100%]

======================================================================================== 28 passed in 0.43s =========================================================================================
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ 

Interpretation

This establishes phase-1 behavioral equivalence for the protocol-core fragmentation/reassembly layer. The C++ code is not yet integrated into BLEInterface.py for live Reticulum/BLE traffic.

Tests on Pi Zero 2W

Note: I had to install the following on zerodev1:

sudo apt install python3-pybind11/stable
sudo apt install python3-distlib/stable
sudo apt install python3-setuptools/stable
sudo apt install python3-pytest
sudo apt install python3-pytest-asyncio/stable

I performed:

git pull
git switch c++migration
cd /usr/local/src/ble-reticulum/migration/protocol_core
python3 setup.py build_ext --inplace

Example:

jlpoole@zerodev1:/usr/local/src/ble-reticulum/migration/protocol_core $ python3 setup.py build_ext --inplace
running build_ext
building 'ble_protocol_core_cpp' extension
creating build/temp.linux-aarch64-cpython-313/usr/local/src/ble-reticulum/migration/protocol_core
aarch64-linux-gnu-g++ -fno-strict-overflow -Wsign-compare -DNDEBUG -g -O2 -Wall -fPIC -I/usr/lib/python3/dist-packages/pybind11/include -I/usr/include/python3.13 -c /usr/local/src/ble-reticulum/migration/protocol_core/ble_protocol_core.cpp -o build/temp.linux-aarch64-cpython-313/usr/local/src/ble-reticulum/migration/protocol_core/ble_protocol_core.o -fvisibility=hidden -g0 -std=c++17
creating build/lib.linux-aarch64-cpython-313
aarch64-linux-gnu-g++ -fno-strict-overflow -Wsign-compare -DNDEBUG -g -O2 -Wall -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 build/temp.linux-aarch64-cpython-313/usr/local/src/ble-reticulum/migration/protocol_core/ble_protocol_core.o -L/usr/lib/aarch64-linux-gnu -o build/lib.linux-aarch64-cpython-313/ble_protocol_core_cpp.cpython-313-aarch64-linux-gnu.so
copying build/lib.linux-aarch64-cpython-313/ble_protocol_core_cpp.cpython-313-aarch64-linux-gnu.so -> 
jlpoole@zerodev1:/usr/local/src/ble-reticulum/migration/protocol_core

Results on Pi Zero 2W

jlpoole@zerodev1:/usr/local/src/ble-reticulum $ BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp python3 -m pytest migration/tests/test_fragmentation_backend_shim.py -q
====================================== test session starts ======================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 7 items                                                                               

migration/tests/test_fragmentation_backend_shim.py .......                                [100%]

======================================= 7 passed in 4.08s =======================================
jlpoole@zerodev1:/usr/local/src/ble-reticulum $ BLE_RETICULUM_FRAGMENTATION_BACKEND=auto python3 -m pytest migration/tests/test_fragmentation_backend_shim.py -q
====================================== test session starts ======================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 7 items                                                                               

migration/tests/test_fragmentation_backend_shim.py .......                                [100%]

======================================= 7 passed in 3.49s =======================================
jlpoole@zerodev1:/usr/local/src/ble-reticulum $ BLE_RETICULUM_FRAGMENTATION_BACKEND=python python3 -m pytest migration/tests/test_fragmentation_backend_shim.py -q
====================================== test session starts ======================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 7 items                                                                               

migration/tests/test_fragmentation_backend_shim.py .......                                [100%]

======================================= 7 passed in 3.48s =======================================
jlpoole@zerodev1:/usr/local/src/ble-reticulum $ BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp python3 -m pytest migration/tests/test_fragmentation_cpp_equivalence.py -q
====================================== test session starts ======================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 28 items                                                                              

migration/tests/test_fragmentation_cpp_equivalence.py ............................        [100%]

====================================== 28 passed in 1.25s =======================================
jlpoole@zerodev1:/usr/local/src/ble-reticulum $ BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp python3 -m pytest tests/test_fragmentation.py -q
====================================== test session starts ======================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 19 items                                                                              

tests/test_fragmentation.py ...................                                           [100%]

====================================== 19 passed in 1.17s =======================================
jlpoole@zerodev1:/usr/local/src/ble-reticulum $ 

zerodev2

jlpoole@zerodev2:/usr/local/src/ble-reticulum $ date
Sat May 16 07:13:26 PM PDT 2026
jlpoole@zerodev2:/usr/local/src/ble-reticulum $ # START COMMAND MASS PASTE
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp python3 -m pytest migration/tests/test_fragmentation_backend_shim.py -q
BLE_RETICULUM_FRAGMENTATION_BACKEND=auto python3 -m pytest migration/tests/test_fragmentation_backend_shim.py -q
BLE_RETICULUM_FRAGMENTATION_BACKEND=python python3 -m pytest migration/tests/test_fragmentation_backend_shim.py -q

# test cpp only
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp python3 -m pytest migration/tests/test_fragmentation_cpp_equivalence.py -q
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp python3 -m pytest tests/test_fragmentation.py -q
# END COMMAND MASS PASTE

===================================== test session starts =====================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 7 items                                                                             

migration/tests/test_fragmentation_backend_shim.py .......                              [100%]

====================================== 7 passed in 4.16s ======================================
===================================== test session starts =====================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 7 items                                                                             

migration/tests/test_fragmentation_backend_shim.py .......                              [100%]

====================================== 7 passed in 3.48s ======================================
===================================== test session starts =====================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 7 items                                                                             

migration/tests/test_fragmentation_backend_shim.py .......                              [100%]

====================================== 7 passed in 3.45s ======================================
===================================== test session starts =====================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 28 items                                                                            

migration/tests/test_fragmentation_cpp_equivalence.py ............................      [100%]

===================================== 28 passed in 1.25s ======================================
===================================== test session starts =====================================
platform linux -- Python 3.13.5, pytest-8.3.5, pluggy-1.5.0
rootdir: /usr/local/src/ble-reticulum
configfile: pytest.ini
plugins: typeguard-4.4.2, asyncio-0.25.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function
collected 19 items                                                                            

tests/test_fragmentation.py ...................                                         [100%]

===================================== 19 passed in 1.15s ======================================
jlpoole@zerodev2:/usr/local/src/ble-reticulum $ 

Real Life Tests

I tried running the Constitution real life tests, the results were the same, e.g. 36 seconds. I ask Codex:

I want to confirm that this command utilized the CPP library:
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp timeout 60 python3 examples/ble_dual_node_echo.py \
  --ble-role both \
     --message-file /home/jlpoole/US_Constitution.txt  \
  --peer 926e6d3b35b7d5940be7edeb47c41b78 \
  --announce-only-when-disconnected

and the response was "No, that command almost certainly did not use the C++ library."

Commands

zerodev1

# zerodev1 Command clump START
date
chronyc tracking
chronyc sources -v
echo .

PYTHONPATH=src:migration/protocol_core \
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp \
timeout 60 python3 examples/ble_dual_node_echo.py \
  --ble-role peripheral \
   --message-file /home/jlpoole/US_Constitution.txt  \
  --message-chunk-size 900 \
  --announce-only-when-disconnected \
  --verbosity "critical"
  
echo .
chronyc tracking
chronyc sources -v
# zerodev1 Command clump END

zerodev2

# zerodev2 Command clump START
date
chronyc tracking
chronyc sources -v
echo .

PYTHONPATH=src:migration/protocol_core \
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp \
timeout 60 python3 examples/ble_dual_node_echo.py \
  --ble-role both \
  --message-file /home/jlpoole/US_Constitution.txt \
  --peer 926e6d3b35b7d5940be7edeb47c41b78 \
  --announce-only-when-disconnected
  
echo .
chronyc tracking
chronyc sources -v
# zerodev2 Command clump END

Analysis of logs

(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum/scripts $ ./analyze_reticulum_file_transfer_20260516_1130.pl ../tmp/run11/20260516_1942_zerodev1_cross_Constitution_CPP.txt ../tmp/run11/20260516_1942_zerodev2_cross_Constitution_CPP.txt
Reticulum BLE file transfer analysis
Generated: 2026-05-16 20:06:22 PDT
Input files:
  ../tmp/run11/20260516_1942_zerodev1_cross_Constitution_CPP.txt
  ../tmp/run11/20260516_1942_zerodev2_cross_Constitution_CPP.txt

Log provenance summary:
  20260516_1942_zerodev1_cross_Constitution_CPP.txt receiver=zerodev1   date='Sat May 16 19:40:49 PDT 2026' command_lines=19 post_marker_lines=589
  20260516_1942_zerodev2_cross_Constitution_CPP.txt receiver=zerodev2   date='Sat May 16 07:40:51 PM PDT 2026' command_lines=18 post_marker_lines=585

Chrony clock notes from logs:
  20260516_1942_zerodev1_cross_Constitution_CPP.txt
    System time     : 0.000026803 seconds slow of NTP time
    System time     : 0.000000015 seconds fast of NTP time
  20260516_1942_zerodev2_cross_Constitution_CPP.txt
    System time     : 0.000093383 seconds slow of NTP time
    System time     : 0.000082251 seconds slow of NTP time

Declared outbound sends observed in logs:
  sender=zerodev1   file=US_Constitution.txt      chunks= 140 bytes=  44225 chunk_data_bytes=316
  sender=zerodev2   file=US_Constitution.txt      chunks= 148 bytes=  44225 chunk_data_bytes=n/a

Direction: zerodev1->zerodev2
  file                 : US_Constitution.txt
  chunks received      : 140 of 140
  completeness         : 100.00%
  missing chunks       : none
  duplicate chunks     : none
  payload bytes RX     : 44225
  first chunk RX       : 19:41:11.196
  last chunk RX        : 19:41:47.125
  receiver span        : 35.929 s
  sender span          : 14.294 s
  payload rate RX span : 1230.9 B/s  9847.2 bit/s
  payload rate TX span : 3094.0 B/s  24751.9 bit/s
  one-way latency       min/median/mean/p95/max/stddev: 247.972 / 11245.970 / 11221.742 / 21098.446 / 21883.147 / 6380.215 ms
  receiver inter-chunk gap min/median/mean/p95/max/stddev: 194.000 / 243.000 / 258.482 / 294.000 / 342.000 / 36.423 ms
  sender inter-chunk gap min/median/mean/p95/max/stddev: 101.903 / 102.917 / 102.833 / 103.330 / 108.473 / 0.971 ms

Direction: zerodev2->zerodev1
  file                 : US_Constitution.txt
  chunks received      : 148 of 148
  completeness         : 100.00%
  missing chunks       : none
  duplicate chunks     : none
  payload bytes RX     : 44225
  first chunk RX       : 19:41:11.099
  last chunk RX        : 19:41:45.468
  receiver span        : 34.369 s
  sender span          : 15.512 s
  payload rate RX span : 1286.8 B/s  10294.2 bit/s
  payload rate TX span : 2851.0 B/s  22807.9 bit/s
  one-way latency       min/median/mean/p95/max/stddev: 212.916 / 9686.723 / 9699.188 / 18206.516 / 19069.768 / 5489.943 ms
  receiver inter-chunk gap min/median/mean/p95/max/stddev: 145.000 / 243.000 / 233.803 / 246.000 / 292.000 / 23.638 ms
  sender inter-chunk gap min/median/mean/p95/max/stddev: 103.950 / 105.904 / 105.525 / 107.442 / 109.099 / 1.094 ms

Hello/handshake RX records:
  zerodev1   -> zerodev2   recv=19:41:10.864 latency= 102.376 ms message='hello back'
  zerodev2   -> zerodev1   recv=19:41:10.874 latency= 176.319 ms message='hello'

Caution: one-way latency assumes sender and receiver clocks are synchronized.
Your chronyc tracking output helps bound this error, but it is not a substitute for ACK/round-trip timing.
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum/scripts $ 


Sunday May 17, 2026 12:08 PM - after exensive testing and forensic runs on run 13, I finally rebooted both Pis and I extended the timeout from 60 to 90. The bilateral Constitution test worked.

rnsenv) jlpoole@jp /usr/local/src/ble-reticulum/scripts $ date; ./analyze_reticulum_file_transfer_20260516_1130.pl ../tmp/run15/20260517_0439_zerodev1_Constitution_CPP.txt ../tmp/run15/20260517_0439_zerodev2_Constitution_CPP.txt
Sun May 17 04:52:33 PDT 2026
Reticulum BLE file transfer analysis
Generated: 2026-05-17 04:52:33 PDT
Input files:
  ../tmp/run15/20260517_0439_zerodev1_Constitution_CPP.txt
  ../tmp/run15/20260517_0439_zerodev2_Constitution_CPP.txt

Log provenance summary:
  20260517_0439_zerodev1_Constitution_CPP.txt receiver=zerodev1   date='Sun May 17 04:44:06 PDT 2026' command_lines=21 post_marker_lines=591
  20260517_0439_zerodev2_Constitution_CPP.txt receiver=zerodev2   date='Sun May 17 04:44:08 AM PDT 2026' command_lines=20 post_marker_lines=587

Chrony clock notes from logs:
  20260517_0439_zerodev1_Constitution_CPP.txt
    System time     : 0.000602999 seconds fast of NTP time
    System time     : 0.000000797 seconds slow of NTP time
  20260517_0439_zerodev2_Constitution_CPP.txt
    System time     : 0.000199690 seconds slow of NTP time
    System time     : 0.000000200 seconds fast of NTP time

Declared outbound sends observed in logs:
  sender=zerodev1   file=US_Constitution.txt      chunks= 140 bytes=  44225 chunk_data_bytes=316
  sender=zerodev2   file=US_Constitution.txt      chunks= 148 bytes=  44225 chunk_data_bytes=n/a

Direction: zerodev1->zerodev2
  file                 : US_Constitution.txt
  chunks received      : 140 of 140
  completeness         : 100.00%
  missing chunks       : none
  duplicate chunks     : none
  payload bytes RX     : 44225
  first chunk RX       : 04:44:33.283
  last chunk RX        : 04:45:09.259
  receiver span        : 35.976 s
  sender span          : 14.299 s
  payload rate RX span : 1229.3 B/s  9834.3 bit/s
  payload rate TX span : 3092.8 B/s  24742.6 bit/s
  one-way latency       min/median/mean/p95/max/stddev: 246.163 / 11275.384 / 11238.875 / 21147.058 / 21922.931 / 6402.281 ms
  receiver inter-chunk gap min/median/mean/p95/max/stddev: 191.000 / 244.000 / 258.820 / 297.300 / 341.000 / 36.970 ms
  sender inter-chunk gap min/median/mean/p95/max/stddev: 101.924 / 102.945 / 102.872 / 104.299 / 109.563 / 0.917 ms

Direction: zerodev2->zerodev1
  file                 : US_Constitution.txt
  chunks received      : 148 of 148
  completeness         : 100.00%
  missing chunks       : none
  duplicate chunks     : none
  payload bytes RX     : 44225
  first chunk RX       : 04:44:33.284
  last chunk RX        : 04:45:07.703
  receiver span        : 34.419 s
  sender span          : 15.470 s
  payload rate RX span : 1284.9 B/s  10279.2 bit/s
  payload rate TX span : 2858.7 B/s  22869.8 bit/s
  one-way latency       min/median/mean/p95/max/stddev: 254.424 / 9806.025 / 9750.358 / 18371.803 / 19203.224 / 5533.436 ms
  receiver inter-chunk gap min/median/mean/p95/max/stddev: 145.000 / 243.000 / 234.143 / 246.700 / 292.000 / 24.080 ms
  sender inter-chunk gap min/median/mean/p95/max/stddev: 103.738 / 104.216 / 105.239 / 108.147 / 122.931 / 2.539 ms

Hello/handshake RX records:
  zerodev2   -> zerodev1   recv=04:44:32.951 latency= 118.171 ms message='hello'
  zerodev1   -> zerodev2   recv=04:44:32.963 latency= 113.954 ms message='hello back'

Caution: one-way latency assumes sender and receiver clocks are synchronized.
Your chronyc tracking output helps bound this error, but it is not a substitute for ACK/round-trip timing.
(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum/scripts $