diff --git a/migration/protocol_core/ble_protocol_core.cpp b/migration/protocol_core/ble_protocol_core.cpp index 23c31bf..a92b4a7 100644 --- a/migration/protocol_core/ble_protocol_core.cpp +++ b/migration/protocol_core/ble_protocol_core.cpp @@ -33,6 +33,17 @@ py::bytes string_to_py_bytes(const Bytes &value) { return py::bytes(value); } +std::string compute_identity_hash(py::object peer_identity_obj) { + const std::string identity_hex = + py::str(peer_identity_obj.attr("hex")()).cast(); + return identity_hex.substr(0, 16); +} + +std::string get_fragmenter_key(py::object peer_identity_obj, + py::object /*peer_address*/) { + return py::str(peer_identity_obj.attr("hex")()).cast(); +} + struct Buffer { std::map fragments; uint16_t total = 0; @@ -444,6 +455,13 @@ public: PYBIND11_MODULE(ble_protocol_core_cpp, m) { m.doc() = "C++ protocol-core implementation for BLE Reticulum fragmentation"; + m.def("compute_identity_hash", &compute_identity_hash, + py::arg("peer_identity"), + "Return the first 16 lowercase hex characters of a peer identity."); + m.def("get_fragmenter_key", &get_fragmenter_key, py::arg("peer_identity"), + py::arg("peer_address"), + "Return the full lowercase hex peer identity; peer_address is ignored."); + py::class_(m, "BLEFragmenter") .def(py::init(), py::arg("mtu") = 185) .def("fragment_packet", &BLEFragmenterCpp::fragment_packet, diff --git a/migration/reports/executive_summary_20260517_1227.txt b/migration/reports/executive_summary_20260517_1227.txt new file mode 100644 index 0000000..28a4f0a --- /dev/null +++ b/migration/reports/executive_summary_20260517_1227.txt @@ -0,0 +1,278 @@ + +============================================================ +BLE Reticulum Migration Executive Summary +============================================================ + +1. Counts by phase/status/tag +phase status tag symbol_c +-------------- -------------- ---------- -------- +0_inventory REVIEWED GLUE 39 + +0_inventory REVIEWED PLATFORM 33 + +0_inventory REVIEWED TEST 3 + +1_candidate REVIEWED CORE 2 + +1_protocol_cor FIELD_ACCEPTED CORE 14 +e + +2. Phase-1 C++ candidates +source_file class_name symbol_nam line_num tag status +-------------- -------------- ---------- -------- ------ ------------------------------------------------------------ +src/ble_reticu BLEFragmen 52 CORE FIELD_ACCEPTED +lum/BLEFragmen ter +tation.py + +src/ble_reticu BLEFragmenter __init__ 68 CORE FIELD_ACCEPTED +lum/BLEFragmen +tation.py + +src/ble_reticu BLEFragmenter fragment_p 82 CORE FIELD_ACCEPTED +lum/BLEFragmen acket +tation.py + +src/ble_reticu BLEFragmenter get_fragme 158 CORE FIELD_ACCEPTED +lum/BLEFragmen nt_overhea +tation.py d + +src/ble_reticu BLEReassem 176 CORE FIELD_ACCEPTED +lum/BLEFragmen bler +tation.py + +src/ble_reticu BLEReassembler __init__ 187 CORE FIELD_ACCEPTED +lum/BLEFragmen +tation.py + +src/ble_reticu BLEReassembler receive_fr 205 CORE FIELD_ACCEPTED +lum/BLEFragmen agment +tation.py + +src/ble_reticu BLEReassembler _reassembl 380 CORE FIELD_ACCEPTED +lum/BLEFragmen e +tation.py + +src/ble_reticu BLEReassembler cleanup_st 402 CORE FIELD_ACCEPTED +lum/BLEFragmen ale_buffer +tation.py s + +src/ble_reticu BLEReassembler get_statis 429 CORE FIELD_ACCEPTED +lum/BLEFragmen tics +tation.py + +src/ble_reticu BLEReassembler reset_stat 443 CORE FIELD_ACCEPTED +lum/BLEFragmen istics +tation.py + +src/ble_reticu HDLCFramer 450 CORE FIELD_ACCEPTED +lum/BLEFragmen +tation.py + +src/ble_reticu HDLCFramer frame_pack 464 CORE FIELD_ACCEPTED +lum/BLEFragmen et +tation.py + +src/ble_reticu HDLCFramer deframe_pa 491 CORE FIELD_ACCEPTED +lum/BLEFragmen cket +tation.py + +src/ble_reticu BLEInterface _get_fragm 1858 CORE REVIEWED +lum/BLEInterfa enter_key +ce.py + +src/ble_reticu BLEInterface _compute_i 1871 CORE REVIEWED +lum/BLEInterfa dentity_ha +ce.py sh + +3. Symbols marked TESTED or ACCEPTED +source_file class_name symbol_nam phase status notes_preview +-------------- -------------- ---------- -------- ------ ------------------------------------------------------------ +src/ble_reticu BLEFragmen 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen ter ol_core ACCEPT ol cell: BLEFragment +tation.py ED + +src/ble_reticu BLEFragmenter __init__ 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen ol_core ACCEPT ol cell: BLEFragment +tation.py ED + +src/ble_reticu BLEFragmenter fragment_p 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen acket ol_core ACCEPT ol cell: BLEFragment +tation.py ED + +src/ble_reticu BLEFragmenter get_fragme 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen nt_overhea ol_core ACCEPT ol cell: BLEFragment +tation.py d ED + +src/ble_reticu BLEReassem 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen bler ol_core ACCEPT ol cell: BLEReassemb +tation.py ED + +src/ble_reticu BLEReassembler __init__ 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen ol_core ACCEPT ol cell: BLEReassemb +tation.py ED + +src/ble_reticu BLEReassembler receive_fr 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen agment ol_core ACCEPT ol cell: BLEReassemb +tation.py ED + +src/ble_reticu BLEReassembler _reassembl 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen e ol_core ACCEPT ol cell: BLEReassemb +tation.py ED + +src/ble_reticu BLEReassembler cleanup_st 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen ale_buffer ol_core ACCEPT ol cell: BLEReassemb +tation.py s ED + +src/ble_reticu BLEReassembler get_statis 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen tics ol_core ACCEPT ol cell: BLEReassemb +tation.py ED + +src/ble_reticu BLEReassembler reset_stat 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen istics ol_core ACCEPT ol cell: BLEReassemb +tation.py ED + +src/ble_reticu HDLCFramer 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen ol_core ACCEPT ol cell: HDLCFramer +tation.py ED + +src/ble_reticu HDLCFramer frame_pack 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen et ol_core ACCEPT ol cell: HDLCFramer. +tation.py ED + +src/ble_reticu HDLCFramer deframe_pa 1_protoc FIELD_ Imported from Codex_response_20260616_1514.md; original symb +lum/BLEFragmen cket ol_core ACCEPT ol cell: HDLCFramer. +tation.py ED + +4. Remaining CORE symbols not accepted +source_file class_name symbol_nam line_num phase status rationale_preview +-------------- -------------- ---------- -------- ------ ------------------------------------------------------------ ------------------------------------------------------- +src/ble_reticu BLEInterface _get_fragm 1858 1_cand REVIEWED Pure identity-to-fragment-state key; cleanly separable. +lum/BLEInterfa enter_key idate +ce.py + +src/ble_reticu BLEInterface _compute_i 1871 1_cand REVIEWED Pure identity truncation rule; cleanly separable. +lum/BLEInterfa dentity_ha idate +ce.py sh + +5. Unknown or needs-review symbols + +6. Do-not-port-yet inventory +tag symbol_count +-------------- -------------- +GLUE 39 +PLATFORM 33 +TEST 3 + +7. Candidate next tasks +source_file class_name symbol_nam line_num tag phase status cpp_candidate rationale_preview +-------------- -------------- ---------- -------- ------ ------------------------------------------------------------ -------- ------------- ------------------------------------------------------- +src/ble_reticu BLEInterface _get_fragm 1858 CORE 1_candidate REVIEWED 1 Pure identity-to-fragment-state key; cleanly separable. +lum/BLEInterfa enter_key +ce.py + +src/ble_reticu BLEInterface _compute_i 1871 CORE 1_candidate REVIEWED 1 Pure identity truncation rule; cleanly separable. +lum/BLEInterfa dentity_ha +ce.py sh + +8. Latest review notes +reviewed_at reviewer source_fil class_na symbol old_status new_status note_preview +-------------- -------------- ---------- -------- ------ ------------------------------------------------------------ -------------- ------------------------------------------------------------ +2026-05-17 19: jlpoole + Code src/ble_re BLEFra FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL gmente ed after live bilateral Constitution tra + EFragmenta r + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLERea FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL ssembl ed after live bilateral Constitution tra + EFragmenta er + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re HDLCFr FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL amer ed after live bilateral Constitution tra + EFragmenta + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEFragm __init FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL enter __ ed after live bilateral Constitution tra + EFragmenta + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEFragm fragme FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL enter nt_pac ed after live bilateral Constitution tra + EFragmenta ket + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEFragm get_fr FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL enter agment ed after live bilateral Constitution tra + EFragmenta _overh + tion.py ead + +2026-05-17 19: jlpoole + Code src/ble_re BLEReass __init FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL embler __ ed after live bilateral Constitution tra + EFragmenta + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEReass _reass FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL embler emble ed after live bilateral Constitution tra + EFragmenta + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEReass cleanu FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL embler p_stal ed after live bilateral Constitution tra + EFragmenta e_buff + tion.py ers + +2026-05-17 19: jlpoole + Code src/ble_re BLEReass get_st FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL embler atisti ed after live bilateral Constitution tra + EFragmenta cs + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEReass receiv FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL embler e_frag ed after live bilateral Constitution tra + EFragmenta ment + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re BLEReass reset_ FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL embler statis ed after live bilateral Constitution tra + EFragmenta tics + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re HDLCFram defram FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL er e_pack ed after live bilateral Constitution tra + EFragmenta et + tion.py + +2026-05-17 19: jlpoole + Code src/ble_re HDLCFram frame_ FIELD_ACCEPTED FIELD_ACCEPTED 2026-05-17: C++ fragmentation/reassembly/HDLC backend accept +27:31 x + ChatGPT ticulum/BL er packet ed after live bilateral Constitution tra + EFragmenta + tion.py + +2026-05-16 22: codex src/ble_re BLEFra REVIEWED Imported from Codex Markdown review. Rationale: Pure BLE pac +50:00 ticulum/BL gmente ket fragmentation format; RNS only loggi + EFragmenta r + tion.py + +2026-05-16 22: codex src/ble_re BLEFragm __init REVIEWED Imported from Codex Markdown review. Rationale: MTU/header s +50:00 ticulum/BL enter __ izing logic. + EFragmenta + tion.py + +2026-05-16 22: codex src/ble_re BLEFragm fragme REVIEWED Imported from Codex Markdown review. Rationale: Core packet- +50:00 ticulum/BL enter nt_pac to-fragments encoding. + EFragmenta ket + tion.py + +2026-05-16 22: codex src/ble_re BLEFragm get_fr REVIEWED Imported from Codex Markdown review. Rationale: Pure sizing/ +50:00 ticulum/BL enter agment overhead calculation. + EFragmenta _overh + tion.py ead + +2026-05-16 22: codex src/ble_re BLERea REVIEWED Imported from Codex Markdown review. Rationale: Core fragmen +50:00 ticulum/BL ssembl t state machine; RNS only logging. + EFragmenta er + tion.py + +2026-05-16 22: codex src/ble_re BLEReass __init REVIEWED Imported from Codex Markdown review. Rationale: Protocol rea +50:00 ticulum/BL embler __ ssembly state. + EFragmenta + tion.py diff --git a/migration/sql/executive_summary.sql b/migration/sql/executive_summary.sql new file mode 100644 index 0000000..fc380e8 --- /dev/null +++ b/migration/sql/executive_summary.sql @@ -0,0 +1,145 @@ +-- +-- For ChatGPT's tracking +-- +-- 20260517 ChatGPT +-- $Header$ +-- +-- Example: +-- cd /usr/local/src/ble-reticulum/migration +-- sqlite3 ble_migration.sqlite ".read sql/executive_summary.sql" +-- +-- Purpose: +-- Executive summary of BLE Reticulum C++ migration status. + +.headers on +.mode column +.width 14 14 10 8 6 60 + +.print '' +.print '============================================================' +.print 'BLE Reticulum Migration Executive Summary' +.print '============================================================' + +.print '' +.print '1. Counts by phase/status/tag' +SELECT + phase, + status, + tag, + COUNT(*) AS symbol_count +FROM symbols +GROUP BY phase, status, tag +ORDER BY phase, status, tag; + +.print '' +.print '2. Phase-1 C++ candidates' +SELECT + source_file, + COALESCE(class_name, '') AS class_name, + symbol_name, + line_number, + tag, + status +FROM symbols +WHERE phase = '1_protocol_core' + OR cpp_candidate = 1 +ORDER BY source_file, line_number; + +.print '' +.print '3. Symbols marked TESTED or ACCEPTED' +SELECT + source_file, + COALESCE(class_name, '') AS class_name, + symbol_name, + phase, + status, + substr(COALESCE(notes, ''), 1, 80) AS notes_preview +FROM symbols +WHERE status IN ('TESTED', 'ACCEPTED', 'FIELD_ACCEPTED', 'WRAPPED_FOR_PYTHON') +ORDER BY source_file, line_number; + +.print '' +.print '4. Remaining CORE symbols not accepted' +SELECT + source_file, + COALESCE(class_name, '') AS class_name, + symbol_name, + line_number, + phase, + status, + substr(COALESCE(rationale, ''), 1, 80) AS rationale_preview +FROM symbols +WHERE tag = 'CORE' + AND status NOT IN ('ACCEPTED', 'FIELD_ACCEPTED', 'REJECTED', 'DEFERRED') +ORDER BY source_file, line_number; + +.print '' +.print '5. Unknown or needs-review symbols' +SELECT + source_file, + COALESCE(class_name, '') AS class_name, + symbol_name, + line_number, + tag, + status, + substr(COALESCE(rationale, ''), 1, 80) AS rationale_preview +FROM symbols +WHERE tag = 'UNKNOWN' + OR status = 'NEEDS_REVIEW' +ORDER BY source_file, line_number; + +.print '' +.print '6. Do-not-port-yet inventory' +SELECT + tag, + COUNT(*) AS symbol_count +FROM symbols +WHERE tag IN ('GLUE', 'PLATFORM', 'TEST') +GROUP BY tag +ORDER BY tag; + +.print '' +.print '7. Candidate next tasks' +SELECT + source_file, + COALESCE(class_name, '') AS class_name, + symbol_name, + line_number, + tag, + phase, + status, + cpp_candidate, + substr(COALESCE(rationale, ''), 1, 100) AS rationale_preview +FROM symbols +WHERE ( + tag = 'CORE' + AND status IN ('DISCOVERED', 'CLASSIFIED', 'TESTED', 'WRAPPED_FOR_PYTHON') + ) + OR ( + cpp_candidate = 1 + AND status NOT IN ('ACCEPTED', 'FIELD_ACCEPTED', 'REJECTED', 'DEFERRED') + ) +ORDER BY + CASE + WHEN source_file LIKE '%BLEInterface.py%' THEN 1 + WHEN source_file LIKE '%BLEFragmentation.py%' THEN 2 + ELSE 3 + END, + source_file, + line_number; + +.print '' +.print '8. Latest review notes' +SELECT + r.reviewed_at, + r.reviewer, + s.source_file, + COALESCE(s.class_name, '') AS class_name, + s.symbol_name, + r.old_status, + r.new_status, + substr(COALESCE(r.note, ''), 1, 100) AS note_preview +FROM reviews r +JOIN symbols s ON s.symbol_id = r.symbol_id +ORDER BY r.reviewed_at DESC +LIMIT 20; diff --git a/migration/sql/mark_fragmentation_cpp_field_accepted_20260517.sql b/migration/sql/mark_fragmentation_cpp_field_accepted_20260517.sql new file mode 100644 index 0000000..ad23ea5 --- /dev/null +++ b/migration/sql/mark_fragmentation_cpp_field_accepted_20260517.sql @@ -0,0 +1,78 @@ +-- 20260517 ChatGPT +-- $Header$ +-- +-- Example: +-- cd /usr/local/src/ble-reticulum/migration +-- sqlite3 ble_migration.sqlite ".read sql/mark_fragmentation_cpp_field_accepted_20260517.sql" +-- +-- Purpose: +-- Mark Phase 1 C++ fragmentation/reassembly/HDLC work as field accepted +-- after successful bilateral Constitution transfer using the C++ backend. + +BEGIN; + +UPDATE symbols + SET status = 'FIELD_ACCEPTED', + phase = '1_protocol_core', + notes = trim(COALESCE(notes, '') || char(10) || + '2026-05-17: FIELD_ACCEPTED. C++ protocol-core backend passed live bilateral Constitution transfer after rebooting devzero1 and increasing transfer timeout from 60 to 90 seconds. A forensic run directory was preserved for the pre-reboot hardware/timeout lock investigation.'), + updated_at = CURRENT_TIMESTAMP + WHERE source_file = 'src/ble_reticulum/BLEFragmentation.py' + AND ( + symbol_name IN ( + 'BLEFragmenter', + '__init__', + 'fragment_packet', + 'get_fragment_overhead', + 'BLEReassembler', + 'receive_fragment', + '_reassemble', + 'cleanup_stale_buffers', + 'get_statistics', + 'reset_statistics', + 'HDLCFramer', + 'frame_packet', + 'deframe_packet' + ) + OR class_name IN ('BLEFragmenter', 'BLEReassembler', 'HDLCFramer') + ); + +INSERT INTO reviews ( + symbol_id, + reviewer, + old_tag, + new_tag, + old_status, + new_status, + note +) +SELECT + symbol_id, + 'jlpoole + Codex + ChatGPT', + tag, + tag, + status, + 'FIELD_ACCEPTED', + '2026-05-17: C++ fragmentation/reassembly/HDLC backend accepted after live bilateral Constitution transfer. Reboot of devzero1 cleared suspected BLE/hardware lock after earlier 60-second timeout runs. Timeout increased to 90 seconds. Forensic run directory preserved.' +FROM symbols +WHERE source_file = 'src/ble_reticulum/BLEFragmentation.py' + AND ( + symbol_name IN ( + 'BLEFragmenter', + '__init__', + 'fragment_packet', + 'get_fragment_overhead', + 'BLEReassembler', + 'receive_fragment', + '_reassemble', + 'cleanup_stale_buffers', + 'get_statistics', + 'reset_statistics', + 'HDLCFramer', + 'frame_packet', + 'deframe_packet' + ) + OR class_name IN ('BLEFragmenter', 'BLEReassembler', 'HDLCFramer') + ); + +COMMIT; diff --git a/migration/tests/test_identity_helpers_cpp_equivalence.py b/migration/tests/test_identity_helpers_cpp_equivalence.py new file mode 100644 index 0000000..3b67a0a --- /dev/null +++ b/migration/tests/test_identity_helpers_cpp_equivalence.py @@ -0,0 +1,179 @@ +import os +import sys + +import pytest + + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +SRC_DIR = os.path.join(REPO_ROOT, "src") +CPP_BUILD_DIR = os.path.join(REPO_ROOT, "migration", "protocol_core") + +sys.path.insert(0, SRC_DIR) +sys.path.insert(0, CPP_BUILD_DIR) + +cpp = pytest.importorskip( + "ble_protocol_core_cpp", + reason=( + "compiled pybind11 module missing; build with " + "`python3 migration/protocol_core/setup.py build_ext --inplace`" + ), +) + +try: + from ble_reticulum.BLEInterface import BLEInterface +except Exception: + BLEInterface = None + +REFERENCE_MODE = "real_bleinterface" if BLEInterface is not None else "source_fallback" + + +def python_interface(): + if BLEInterface is None: + return None + return object.__new__(BLEInterface) + + +def py_compute_identity_hash(peer_identity): + if BLEInterface is None: + return peer_identity.hex()[:16] + return BLEInterface._compute_identity_hash(python_interface(), peer_identity) + + +def py_get_fragmenter_key(peer_identity, peer_address): + if BLEInterface is None: + return peer_identity.hex() + return BLEInterface._get_fragmenter_key( + python_interface(), peer_identity, peer_address + ) + + +def test_reference_mode_is_explicit(): + assert REFERENCE_MODE in {"real_bleinterface", "source_fallback"} + + +def test_real_bleinterface_reference_when_importable(): + if BLEInterface is None: + pytest.skip("BLEInterface dependencies are not importable; using source fallback") + + assert REFERENCE_MODE == "real_bleinterface" + assert python_interface() is not None + + +def test_source_fallback_matches_current_helper_logic_when_bleinterface_unavailable(): + if BLEInterface is not None: + pytest.skip("BLEInterface importable; source fallback not used") + + assert REFERENCE_MODE == "source_fallback" + interface_path = os.path.join(SRC_DIR, "ble_reticulum", "BLEInterface.py") + with open(interface_path, "r", encoding="utf-8") as handle: + source = handle.read() + assert "return peer_identity.hex()" in source + assert "return peer_identity.hex()[:16]" in source + + +def assert_same_exception(py_callable, cpp_callable): + with pytest.raises(Exception) as py_exc: + py_callable() + with pytest.raises(Exception) as cpp_exc: + cpp_callable() + + assert type(cpp_exc.value) is type(py_exc.value) + + +@pytest.mark.parametrize( + "identity", + [ + bytes.fromhex("00112233445566778899aabbccddeeff"), + b"\x00" * 16, + b"\xff" * 16, + bytes(range(16)), + ], +) +def test_normal_16_byte_identities(identity): + assert cpp.compute_identity_hash(identity) == py_compute_identity_hash(identity) + assert cpp.get_fragmenter_key(identity, "AA:BB:CC:DD:EE:FF") == py_get_fragmenter_key( + identity, "AA:BB:CC:DD:EE:FF" + ) + + +@pytest.mark.parametrize("identity", [b"", b"\x01", b"\x01\x23\x45\x67\x89\xab\xcd"]) +def test_shorter_identities(identity): + assert cpp.compute_identity_hash(identity) == py_compute_identity_hash(identity) + assert cpp.get_fragmenter_key(identity, None) == py_get_fragmenter_key(identity, None) + + +@pytest.mark.parametrize( + "identity", + [ + bytes(range(17)), + bytes(range(32)), + b"\x10\x20\x30\x40\x50\x60\x70\x80" + b"\x99" * 64, + ], +) +def test_longer_identities(identity): + assert cpp.compute_identity_hash(identity) == py_compute_identity_hash(identity) + assert cpp.get_fragmenter_key(identity, "") == py_get_fragmenter_key(identity, "") + + +@pytest.mark.parametrize("identity", [None, "00112233", 1234, object()]) +def test_none_or_invalid_identity_values(identity): + assert_same_exception( + lambda: py_compute_identity_hash(identity), + lambda: cpp.compute_identity_hash(identity), + ) + assert_same_exception( + lambda: py_get_fragmenter_key(identity, "AA:BB"), + lambda: cpp.get_fragmenter_key(identity, "AA:BB"), + ) + + +def test_peer_address_ignored_for_fragmenter_key(): + identity = bytes.fromhex("00112233445566778899aabbccddeeff") + addresses = [ + None, + "", + "AA:BB:CC:DD:EE:FF", + "11:22:33:44:55:66", + object(), + ] + + expected = py_get_fragmenter_key(identity, addresses[0]) + for address in addresses: + assert py_get_fragmenter_key(identity, address) == expected + assert cpp.get_fragmenter_key(identity, address) == expected + + +def test_stable_lowercase_hex_formatting(): + identity = bytes.fromhex("ABCDEF0123456789ABCDEF0123456789") + + assert py_compute_identity_hash(identity) == "abcdef0123456789" + assert cpp.compute_identity_hash(identity) == "abcdef0123456789" + assert py_get_fragmenter_key(identity, "ignored") == identity.hex() + assert cpp.get_fragmenter_key(identity, "ignored") == identity.hex() + assert cpp.compute_identity_hash(identity).islower() + assert cpp.get_fragmenter_key(identity, "ignored").islower() + + +def test_collision_sensitive_truncated_hash_edge_case(): + shared_prefix = bytes.fromhex("0011223344556677") + identity_a = shared_prefix + bytes.fromhex("0000000000000000") + identity_b = shared_prefix + bytes.fromhex("ffffffffffffffff") + + assert py_compute_identity_hash(identity_a) == py_compute_identity_hash(identity_b) + assert cpp.compute_identity_hash(identity_a) == cpp.compute_identity_hash(identity_b) + + assert py_get_fragmenter_key(identity_a, "addr1") != py_get_fragmenter_key( + identity_b, "addr2" + ) + assert cpp.get_fragmenter_key(identity_a, "addr1") != cpp.get_fragmenter_key( + identity_b, "addr2" + ) + + +def test_bytearray_matches_current_python_hex_behavior(): + identity = bytearray(bytes.fromhex("00112233445566778899aabbccddeeff")) + + assert cpp.compute_identity_hash(identity) == py_compute_identity_hash(identity) + assert cpp.get_fragmenter_key(identity, "ignored") == py_get_fragmenter_key( + identity, "ignored" + )