First wave of work

This commit is contained in:
Jack Kingsman
2026-03-06 17:41:37 -08:00
parent b5e2a4c269
commit 9c54ea623e
18 changed files with 256 additions and 42 deletions

View File

@@ -127,6 +127,7 @@ export function deleteContact(publicKey: string): Promise<{ status: string }> {
export interface MessagePath {
path: string;
received_at: number;
path_len?: number;
}
export interface Message {

View File

@@ -19,6 +19,7 @@ from app.decoder import (
decrypt_group_text,
derive_public_key,
derive_shared_secret,
extract_payload,
parse_packet,
try_decrypt_dm,
try_decrypt_packet_with_channel_key,
@@ -81,8 +82,31 @@ class TestPacketParsing:
assert result.route_type == RouteType.DIRECT
assert result.payload_type == PayloadType.TEXT_MESSAGE
assert result.path_length == 3
assert result.path_hash_size == 1
assert result.path_byte_length == 3
assert result.payload == b"msg"
def test_parse_packet_with_two_byte_hops(self):
"""Packets with multi-byte hop identifiers decode hop count separately from byte length."""
packet = bytes([0x0A, 0x42, 0x01, 0x02, 0x03, 0x04]) + b"msg"
result = parse_packet(packet)
assert result is not None
assert result.route_type == RouteType.DIRECT
assert result.payload_type == PayloadType.TEXT_MESSAGE
assert result.path_length == 2
assert result.path_hash_size == 2
assert result.path_byte_length == 4
assert result.path == bytes([0x01, 0x02, 0x03, 0x04])
assert result.payload == b"msg"
def test_extract_payload_with_two_byte_hops(self):
"""Payload extraction skips the full path byte length for multi-byte hops."""
packet = bytes([0x15, 0x42, 0xAA, 0xBB, 0xCC, 0xDD]) + b"payload_data"
assert extract_payload(packet) == b"payload_data"
def test_parse_transport_flood_skips_transport_code(self):
"""TRANSPORT_FLOOD packets have 4-byte transport code to skip."""
# Header: route_type=TRANSPORT_FLOOD(0), payload_type=GROUP_TEXT(5)

View File

@@ -682,7 +682,7 @@ class TestDirectMessageDirectionDetection:
message_broadcasts = [b for b in broadcasts if b["type"] == "message"]
assert len(message_broadcasts) == 1
assert message_broadcasts[0]["data"]["paths"] == [
{"path": "", "received_at": SENDER_TIMESTAMP}
{"path": "", "received_at": SENDER_TIMESTAMP, "path_len": 0}
]
@pytest.mark.asyncio

View File

@@ -652,6 +652,21 @@ class TestAppriseFormatBody:
assert "`20`" in body
assert "`27`" in body
def test_dm_with_multi_byte_path(self):
from app.fanout.apprise_mod import _format_body
body = _format_body(
{
"type": "PRIV",
"text": "hi",
"sender_name": "Alice",
"paths": [{"path": "20273031", "path_len": 2}],
},
include_path=True,
)
assert "`2027`" in body
assert "`3031`" in body
def test_dm_no_path_shows_direct(self):
from app.fanout.apprise_mod import _format_body

View File

@@ -678,6 +678,7 @@ class TestMessageBroadcastStructure:
assert broadcast["paths"] is not None
assert len(broadcast["paths"]) == 1
assert broadcast["paths"][0]["path"] == "" # Empty string = direct/flood
assert broadcast["paths"][0]["path_len"] == 0
class TestRawPacketStorage:
@@ -927,6 +928,7 @@ class TestCreateDMMessageFromDecrypted:
our_public_key=self.FACE12_PUB,
received_at=1700000001,
path="aabbcc", # Path through 3 repeaters
path_len=3,
outgoing=False,
)
@@ -937,6 +939,7 @@ class TestCreateDMMessageFromDecrypted:
assert broadcast["paths"] is not None
assert len(broadcast["paths"]) == 1
assert broadcast["paths"][0]["path"] == "aabbcc"
assert broadcast["paths"][0]["path_len"] == 3
assert broadcast["paths"][0]["received_at"] == 1700000001

View File

@@ -36,12 +36,13 @@ class TestMessageRepositoryAddPath:
msg_id = await _create_message(test_db)
result = await MessageRepository.add_path(
message_id=msg_id, path="1A2B", received_at=1700000000
message_id=msg_id, path="1A2B", received_at=1700000000, path_len=1
)
assert len(result) == 1
assert result[0].path == "1A2B"
assert result[0].received_at == 1700000000
assert result[0].path_len == 1
@pytest.mark.asyncio
async def test_add_path_to_message_with_existing_paths(self, test_db):

View File

@@ -125,6 +125,39 @@ class TestOutgoingDMBroadcast:
assert exc_info.value.status_code == 409
assert "ambiguous" in exc_info.value.detail.lower()
@pytest.mark.asyncio
async def test_send_dm_add_contact_preserves_out_path_hash_mode(self, test_db):
"""Direct-send contact export includes the inferred path hash mode for multi-byte routes."""
mc = _make_mc()
pub_key = "cd" * 32
await ContactRepository.upsert(
{
"public_key": pub_key,
"name": "Bob",
"type": 0,
"flags": 0,
"last_path": "11223344",
"last_path_len": 2,
"last_advert": None,
"lat": None,
"lon": None,
"last_seen": None,
"on_radio": False,
"last_contacted": None,
}
)
with (
patch("app.routers.messages.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
await send_direct_message(SendDirectMessageRequest(destination=pub_key, text="hi"))
add_contact_arg = mc.commands.add_contact.await_args.args[0]
assert add_contact_arg["out_path"] == "11223344"
assert add_contact_arg["out_path_len"] == 2
assert add_contact_arg["out_path_hash_mode"] == 1
class TestOutgoingChannelBroadcast:
"""Test that outgoing channel messages are broadcast via broadcast_event for fanout dispatch."""