mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-04-20 22:13:49 +00:00
Adds support for pull-based advert requests and responses.
This commit is contained in:
parent
01e3f21992
commit
0283003b04
4 changed files with 111 additions and 0 deletions
|
|
@ -7,6 +7,9 @@ from ..packets import ControlType, PacketType
|
|||
|
||||
logger = logging.getLogger("meshcore")
|
||||
|
||||
# Command codes
|
||||
CMD_REQUEST_ADVERT = 57 # 0x39
|
||||
|
||||
class ControlDataCommandHandler(CommandHandlerBase):
|
||||
"""Helper functions to handle binary requests through binary commands"""
|
||||
|
||||
|
|
@ -48,3 +51,46 @@ class ControlDataCommandHandler(CommandHandlerBase):
|
|||
else:
|
||||
res.payload["tag"] = tag
|
||||
return res
|
||||
|
||||
async def request_advert(self, prefix: bytes, path: bytes) -> Event:
|
||||
"""
|
||||
Request advertisement from a node via pull-based system.
|
||||
|
||||
Args:
|
||||
prefix: First byte of target node's public key (PATH_HASH_SIZE = 1)
|
||||
path: Path to reach the node (1-64 bytes)
|
||||
|
||||
Returns:
|
||||
Event with type OK on success, ERROR on failure.
|
||||
The actual response arrives asynchronously as ADVERT_RESPONSE event.
|
||||
|
||||
Raises:
|
||||
ValueError: If prefix is not 1 byte or path is empty/too long
|
||||
|
||||
Example:
|
||||
# Get repeater from contacts
|
||||
contacts = (await mc.commands.get_contacts()).payload
|
||||
repeater = next(c for c in contacts.values() if c['adv_type'] == 2)
|
||||
|
||||
# Extract prefix and path
|
||||
prefix = bytes.fromhex(repeater['public_key'])[:1]
|
||||
path = bytes(repeater.get('out_path', [])) or prefix
|
||||
|
||||
# Send request
|
||||
result = await mc.commands.request_advert(prefix, path)
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Failed: {result.payload}")
|
||||
return
|
||||
|
||||
# Wait for response
|
||||
response = await mc.wait_for_event(EventType.ADVERT_RESPONSE, timeout=30)
|
||||
if response:
|
||||
print(f"Node: {response.payload['node_name']}")
|
||||
"""
|
||||
if len(prefix) != 1:
|
||||
raise ValueError("Prefix must be exactly 1 byte (PATH_HASH_SIZE)")
|
||||
if not path or len(path) > 64:
|
||||
raise ValueError("Path must be 1-64 bytes")
|
||||
|
||||
cmd = bytes([CMD_REQUEST_ADVERT]) + prefix + bytes([len(path)]) + path
|
||||
return await self.send(cmd, [EventType.OK, EventType.ERROR])
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class EventType(Enum):
|
|||
NEIGHBOURS_RESPONSE = "neighbours_response"
|
||||
SIGN_START = "sign_start"
|
||||
SIGNATURE = "signature"
|
||||
ADVERT_RESPONSE = "advert_response"
|
||||
|
||||
# Command response types
|
||||
OK = "command_ok"
|
||||
|
|
|
|||
|
|
@ -59,3 +59,4 @@ class PacketType(Enum):
|
|||
BINARY_RESPONSE = 0x8C
|
||||
PATH_DISCOVERY_RESPONSE = 0x8D
|
||||
CONTROL_DATA = 0x8E
|
||||
ADVERT_RESPONSE = 0x8F
|
||||
|
|
|
|||
|
|
@ -729,6 +729,69 @@ class MessageReader:
|
|||
res = {"reason": "private_key_export_disabled"}
|
||||
await self.dispatcher.dispatch(Event(EventType.DISABLED, res))
|
||||
|
||||
elif packet_type_value == PacketType.ADVERT_RESPONSE.value:
|
||||
logger.debug(f"Received advert response: {data.hex()}")
|
||||
# PUSH_CODE_ADVERT_RESPONSE (0x8F) format:
|
||||
# Byte 0: 0x8F (push code)
|
||||
# Bytes 1-4: tag (uint32)
|
||||
# Bytes 5-36: pubkey (32 bytes)
|
||||
# Byte 37: adv_type
|
||||
# Bytes 38-69: node_name (32 bytes)
|
||||
# Bytes 70-73: timestamp (uint32)
|
||||
# Byte 74: flags
|
||||
# [Optional fields based on flags]
|
||||
|
||||
if len(data) < 75:
|
||||
logger.error(f"Advert response too short: {len(data)} bytes, need at least 75")
|
||||
return
|
||||
|
||||
res = {}
|
||||
offset = 1 # Skip push code
|
||||
|
||||
res["tag"] = struct.unpack('<I', data[offset:offset+4])[0]
|
||||
offset += 4
|
||||
|
||||
res["pubkey"] = data[offset:offset+32].hex()
|
||||
offset += 32
|
||||
|
||||
res["adv_type"] = data[offset]
|
||||
offset += 1
|
||||
|
||||
res["node_name"] = data[offset:offset+32].decode('utf-8', errors='replace').rstrip('\x00')
|
||||
offset += 32
|
||||
|
||||
res["timestamp"] = struct.unpack('<I', data[offset:offset+4])[0]
|
||||
offset += 4
|
||||
|
||||
flags = data[offset]
|
||||
res["flags"] = flags
|
||||
offset += 1
|
||||
|
||||
# Parse optional fields based on flags
|
||||
if flags & 0x01: # has latitude
|
||||
if offset + 4 <= len(data):
|
||||
lat_i32 = struct.unpack('<i', data[offset:offset+4])[0]
|
||||
res["latitude"] = lat_i32 / 1e6
|
||||
offset += 4
|
||||
|
||||
if flags & 0x02: # has longitude
|
||||
if offset + 4 <= len(data):
|
||||
lon_i32 = struct.unpack('<i', data[offset:offset+4])[0]
|
||||
res["longitude"] = lon_i32 / 1e6
|
||||
offset += 4
|
||||
|
||||
if flags & 0x04: # has description
|
||||
if offset + 32 <= len(data):
|
||||
res["node_desc"] = data[offset:offset+32].decode('utf-8', errors='replace').rstrip('\x00')
|
||||
offset += 32
|
||||
|
||||
attributes = {
|
||||
"tag": res["tag"],
|
||||
"pubkey": res["pubkey"],
|
||||
}
|
||||
|
||||
await self.dispatcher.dispatch(Event(EventType.ADVERT_RESPONSE, res, attributes))
|
||||
|
||||
elif packet_type_value == PacketType.CONTROL_DATA.value:
|
||||
logger.debug("Received control data packet")
|
||||
res={}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue