From 4d49eb701b6341cdfd9a2b05fe11baf845f40e5a Mon Sep 17 00:00:00 2001 From: Lloyd Date: Mon, 13 Apr 2026 16:49:02 +0100 Subject: [PATCH] feat: add owner_info field to repeater configuration and add getter for protocol request handling --- config.yaml.example | 3 + repeater/handler_helpers/protocol_request.py | 141 +++++++++++++++++++ repeater/main.py | 1 + 3 files changed, 145 insertions(+) diff --git a/config.yaml.example b/config.yaml.example index a8292b8..37ecdef 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -25,6 +25,9 @@ repeater: # If both identity_file and identity_key are set, identity_key takes precedence # identity_key: null + # Owner information (shown to clients requesting owner info) + owner_info: "" + # Duplicate packet cache TTL in seconds cache_ttl: 3600 diff --git a/repeater/handler_helpers/protocol_request.py b/repeater/handler_helpers/protocol_request.py index ddd54e7..4295693 100644 --- a/repeater/handler_helpers/protocol_request.py +++ b/repeater/handler_helpers/protocol_request.py @@ -33,6 +33,7 @@ class ProtocolRequestHelper: radio=None, engine=None, neighbor_tracker=None, + config=None, ): self.identity_manager = identity_manager @@ -41,6 +42,7 @@ class ProtocolRequestHelper: self.radio = radio self.engine = engine self.neighbor_tracker = neighbor_tracker + self.config = config or {} # Dictionary of core handlers keyed by dest_hash self.handlers = {} @@ -61,6 +63,9 @@ class ProtocolRequestHelper: # Build request handlers dict request_handlers = { REQ_TYPE_GET_STATUS: self._handle_get_status, + REQ_TYPE_GET_ACCESS_LIST: self._make_handle_get_access_list(identity_acl), + REQ_TYPE_GET_NEIGHBOURS: self._handle_get_neighbours, + REQ_TYPE_GET_OWNER_INFO: self._handle_get_owner_info, } # Create core handler @@ -227,3 +232,139 @@ class ProtocolRequestHelper: ) return stats + + def _make_handle_get_access_list(self, identity_acl): + """Create a closure for GET_ACCESS_LIST bound to a specific identity ACL.""" + def _handler(client, timestamp: int, req_data: bytes): + return self._handle_get_access_list(client, timestamp, req_data, identity_acl) + return _handler + + def _handle_get_access_list(self, client, timestamp: int, req_data: bytes, identity_acl): + """Return ACL entries: [pub_key_prefix(6) + permissions(1)] per client. + + Admin-only. Matches C++ simple_repeater handleRequest REQ_TYPE_GET_ACCESS_LIST. + """ + if not hasattr(client, "is_admin") or not client.is_admin(): + logger.debug("GET_ACCESS_LIST rejected: client is not admin") + return None + + # req_data[0] and req_data[1] are reserved bytes; must both be 0 + if len(req_data) >= 2 and (req_data[0] != 0 or req_data[1] != 0): + logger.debug("GET_ACCESS_LIST: reserved bytes non-zero, ignoring") + return None + + result = bytearray() + for ci in identity_acl.get_all_clients(): + if ci.permissions == 0: + continue # skip deleted entries + pubkey = ci.id.get_public_key() + result.extend(pubkey[:6]) # 6-byte pub_key prefix + result.append(ci.permissions & 0xFF) + + logger.debug("GET_ACCESS_LIST: returning %d entries", len(result) // 7) + return bytes(result) + + def _handle_get_neighbours(self, client, timestamp: int, req_data: bytes): + """Return paginated, sorted neighbour list. + + Matches C++ simple_repeater handleRequest REQ_TYPE_GET_NEIGHBOURS. + Request: version(1) + count(1) + offset(2 LE) + order_by(1) + pubkey_prefix_len(1) + random(4) + Response: total_count(2 LE) + results_count(2 LE) + entries + Each entry: pubkey_prefix(N) + heard_seconds_ago(4 LE) + snr(1 signed) + """ + if len(req_data) < 7: + logger.debug("GET_NEIGHBOURS: req_data too short (%d bytes)", len(req_data)) + return None + + request_version = req_data[0] + if request_version != 0: + logger.debug("GET_NEIGHBOURS: unsupported version %d", request_version) + return None + + count = req_data[1] + offset = struct.unpack_from("= total_count: + break + if len(results) + entry_size > max_results_bytes: + break + + pubkey_hex, heard_ago, snr_int = entries[idx] + try: + pubkey_bytes = bytes.fromhex(pubkey_hex) + except (ValueError, TypeError): + continue + results.extend(pubkey_bytes[:pubkey_prefix_len]) + results.extend(struct.pack("