implemented anon binary requests

This commit is contained in:
Florent 2026-02-02 11:55:18 -04:00
parent ac82eeb905
commit 3b46986dfa
5 changed files with 137 additions and 3 deletions

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "meshcore"
version = "2.2.5"
version = "2.2.6"
authors = [
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
{ name="Alex Wolden", email="awolden@gmail.com" },

View file

@ -3,7 +3,7 @@ import logging
import random
from typing import Any, Callable, Coroutine, Dict, List, Optional, Union
from meshcore.packets import BinaryReqType
from meshcore.packets import BinaryReqType, AnonReqType
from ..events import Event, EventDispatcher, EventType
from ..reader import MessageReader
@ -187,3 +187,24 @@ class CommandHandlerBase:
self._reader.register_binary_request(pubkey_prefix.hex(), exp_tag, request_type, actual_timeout, context=context)
return result
async def send_anon_req(self, dst: DestinationType, request_type: AnonReqType, data: Optional[bytes] = None, context={}, timeout=None, min_timeout=0) -> Event:
dst_bytes = _validate_destination(dst, prefix_length=32)
pubkey_prefix = _validate_destination(dst, prefix_length=6)
logger.debug(f"Anon Binary request to {dst_bytes.hex()}")
data = b"\x39" + dst_bytes + request_type.value.to_bytes(1, "little", signed=False) + (data if data else b"")
result = await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
# Register the request with the reader if we have both reader and request_type
if (result.type == EventType.MSG_SENT and
self._reader is not None and
request_type is not None):
exp_tag = result.payload["expected_ack"].hex()
# Use provided timeout or fallback to suggested timeout (with 5s default)
actual_timeout = timeout if timeout is not None and timeout > 0 else result.payload.get("suggested_timeout", 4000) / 800.0
actual_timeout = min_timeout if actual_timeout < min_timeout else actual_timeout
self._reader.register_binary_request(pubkey_prefix.hex(), exp_tag, request_type, actual_timeout, context=context)
return result

View file

@ -1,10 +1,12 @@
import asyncio
import logging
import random
import io
from .base import CommandHandlerBase
from ..events import EventType
from ..packets import BinaryReqType
from ..packets import AnonReqType
logger = logging.getLogger("meshcore")
@ -242,3 +244,109 @@ class BinaryCommandHandler(CommandHandlerBase):
res["neighbours"] += next_res["neighbours"]
return res
async def req_regions_async(self, contact, timeout=0, min_timeout=0):
req = b"\0" # The return path, I currently do nothing with, so direct only
return await self.send_anon_req(
contact,
AnonReqType.REGIONS,
data=req,
timeout=timeout
)
async def req_regions_sync(self, contact, timeout=0, min_timeout=0):
res = await self.req_regions_async(contact, timeout, min_timeout)
if res.type == EventType.ERROR:
return None
timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
timeout = timeout if timeout > min_timeout else min_timeout
if self.dispatcher is None:
return None
region_event = await self.dispatcher.wait_for_event(
EventType.BINARY_RESPONSE,
attribute_filters={"tag": res.payload["expected_ack"].hex()},
timeout=timeout,
)
if region_event is None:
return None
pkt = bytes().fromhex(region_event.payload["data"])
pbuf = io.BytesIO(pkt)
tag_again = pbuf.read(4)
return pbuf.read().decode("utf-8", "ignore").strip("\x00")
async def req_owner_async(self, contact, timeout=0, min_timeout=0):
req = b"\0"
return await self.send_anon_req(
contact,
AnonReqType.OWNER,
data=req,
timeout=timeout
)
async def req_owner_sync(self, contact, timeout=0, min_timeout=0):
res = await self.req_owner_async(contact, timeout, min_timeout)
if res.type == EventType.ERROR:
return None
timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
timeout = timeout if timeout > min_timeout else min_timeout
if self.dispatcher is None:
return None
owner_event = await self.dispatcher.wait_for_event(
EventType.BINARY_RESPONSE,
attribute_filters={"tag": res.payload["expected_ack"].hex()},
timeout=timeout,
)
if owner_event is None:
return None
pkt = bytes().fromhex(owner_event.payload["data"])
pbuf = io.BytesIO(pkt)
tag_again = pbuf.read(4)
strings = pbuf.read().decode("utf-8", "ignore").split("\n", 1)
return dict(name=strings[0], owner=strings[1].strip("\x00"))
async def req_basic_async(self, contact, timeout=0, min_timeout=0):
req = b"\0"
return await self.send_anon_req(
contact,
AnonReqType.BASIC,
data=req,
timeout=timeout
)
async def req_basic_sync(self, contact, timeout=0, min_timeout=0):
res = await self.req_basic_async(contact, timeout, min_timeout)
if res.type == EventType.ERROR:
return None
timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
timeout = timeout if timeout > min_timeout else min_timeout
if self.dispatcher is None:
return None
basic_event = await self.dispatcher.wait_for_event(
EventType.BINARY_RESPONSE,
attribute_filters={"tag": res.payload["expected_ack"].hex()},
timeout=timeout,
)
if basic_event is None:
return None
return basic_event.payload

View file

@ -1,5 +1,10 @@
from enum import Enum
class AnonReqType(Enum):
REGIONS = 0x01
OWNER = 0x02
BASIC = 0x03 # just remote clock
class BinaryReqType(Enum):
STATUS = 0x01
KEEP_ALIVE = 0x02

View file

@ -597,10 +597,10 @@ class MessageReader:
)
elif packet_type_value == PacketType.BINARY_RESPONSE.value:
logger.debug(f"Received binary data: {data.hex()}")
dbuf.read(1)
tag = dbuf.read(4).hex()
response_data = dbuf.read()
logger.debug(f"Received binary data: {data.hex()}, tag {tag}, data {response_data.hex()}")
# Always dispatch generic BINARY_RESPONSE
binary_res = {"tag": tag, "data": response_data.hex()}