fix(mesh): Meshtastic 3ccc pkc_capable pill + Sideband image interop + critical CBOR wire-bloat fix
Merges in the meshtastic agent's now-finished work alongside this session's
continuation: stock-peer (3ccc) PKI-capability is now stamped through
get_contacts -> refresh_contacts -> MeshPeer.pkc_capable, so a directed DM to/from
a PKC-capable stock Meshtastic peer correctly shows the E2E pill on the Sent row,
not just received messages. Confirmed live: .198 sees "Meshtastic 3ccc" with
pkc_capable=true.
Also fixes two real interop/correctness bugs found while live-testing the
Reticulum <-> Sideband link:
- Receive: the daemon only ever read LXMF's plain-text content, silently
dropping native FIELD_IMAGE/FIELD_FILE_ATTACHMENTS fields — a stock
Sideband/NomadNet photo vanished into a blank-space message. Now decoded
into the same ContentInline typed envelope our own attachments use.
- Send: images to a non-archy (stock) peer now use native LXMF FIELD_IMAGE
instead of our own opaque CBOR wire format, which Sideband can't decode.
- Root cause of a garbled MC-chunk-fragment bug: TypedEnvelope.v/.sig (the
OUTER wrapper every message type uses) serialized raw bytes as a CBOR
array-of-integers instead of a native byte string, bloating every
message on the wire ~2-3.5x — enough to push even a tiny ReadReceipt
over the 140-byte single-frame chunking threshold. Root-caused by
reading ciborium's deserializer source directly (deserialize_bytes only
works within its internal scratch buffer; deserialize_byte_buf streams
unbounded).
Frontend: consolidated the attach/record buttons into a single animated "+"
menu (was overflowing the compose row).
857/857 tests pass. Verified live across all 5 deploy-roster nodes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f54c853128
commit
0eb5c258f5
@@ -202,10 +202,11 @@ class ReticulumDaemon:
|
||||
|
||||
# ---- RNS-thread callbacks → asyncio ----
|
||||
def _on_lxmf_delivery(self, message):
|
||||
import LXMF
|
||||
try:
|
||||
app_data = b""
|
||||
src = message.source_hash.hex() if message.source_hash else ""
|
||||
self._emit_threadsafe({
|
||||
event = {
|
||||
"event": "recv",
|
||||
"source_hash": src,
|
||||
"content": message.content_as_string() if hasattr(message, "content_as_string")
|
||||
@@ -213,7 +214,27 @@ class ReticulumDaemon:
|
||||
"title": message.title_as_string() if hasattr(message, "title_as_string") else "",
|
||||
"app_data": app_data.hex(),
|
||||
"stamp": getattr(message, "timestamp", None),
|
||||
})
|
||||
}
|
||||
# Native LXMF attachment fields (Sideband/NomadNet/stock clients use
|
||||
# these, NOT our own typed-envelope wire format) — a stock client's
|
||||
# photo/voice-memo/file arrives here, not in `content`, which is why
|
||||
# it was previously dropped silently (content was just blank/space).
|
||||
# See LXMF field format confirmed against Sideband's own source
|
||||
# (sbapp/sideband/core.py): FIELD_IMAGE = [format_str, bytes],
|
||||
# FIELD_AUDIO = [mode_byte, bytes], FIELD_FILE_ATTACHMENTS =
|
||||
# [[filename, bytes], ...].
|
||||
fields = getattr(message, "fields", None) or {}
|
||||
if LXMF.FIELD_IMAGE in fields:
|
||||
fmt, img_bytes = fields[LXMF.FIELD_IMAGE]
|
||||
event["image_format"] = str(fmt)
|
||||
event["image_b64"] = base64.b64encode(bytes(img_bytes)).decode("ascii")
|
||||
if LXMF.FIELD_FILE_ATTACHMENTS in fields:
|
||||
attachments = fields[LXMF.FIELD_FILE_ATTACHMENTS]
|
||||
if attachments:
|
||||
filename, file_bytes = attachments[0]
|
||||
event["attachment_filename"] = str(filename)
|
||||
event["attachment_b64"] = base64.b64encode(bytes(file_bytes)).decode("ascii")
|
||||
self._emit_threadsafe(event)
|
||||
except Exception as e: # never let a callback kill the RNS thread
|
||||
self._emit_threadsafe({"event": "error", "where": "delivery", "detail": str(e)})
|
||||
|
||||
@@ -293,9 +314,17 @@ class ReticulumDaemon:
|
||||
"opportunistic": LXMF.LXMessage.OPPORTUNISTIC,
|
||||
"propagated": LXMF.LXMessage.PROPAGATED}.get(
|
||||
req.get("method", "direct"), LXMF.LXMessage.DIRECT)
|
||||
# Native LXMF FIELD_IMAGE — for a stock Sideband/NomadNet peer, which
|
||||
# has no idea how to decode our own typed-envelope wire format. Rust
|
||||
# only sets these two keys when the peer isn't an archy contact (see
|
||||
# `is_archy_peer` gating in typed_messages.rs); [format, bytes] is the
|
||||
# wire shape confirmed against Sideband's own source.
|
||||
fields = {}
|
||||
if req.get("image_b64") and req.get("image_format"):
|
||||
fields[LXMF.FIELD_IMAGE] = [req["image_format"], base64.b64decode(req["image_b64"])]
|
||||
msg = LXMF.LXMessage(dest, self.delivery_destination,
|
||||
req.get("content", ""), req.get("title", ""),
|
||||
desired_method=method)
|
||||
desired_method=method, fields=fields or None)
|
||||
msg.register_delivery_callback(lambda m: self._emit_threadsafe(
|
||||
{"event": "delivered", "dest_hash": req["dest_hash"], "state": "delivered",
|
||||
"id": m.hash.hex() if m.hash else ""}))
|
||||
|
||||
Reference in New Issue
Block a user