mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-05-11 05:16:54 +00:00
refactor(meshchat): streamline transport identity handling and add extra config validation
This commit is contained in:
+68
-14
@@ -4386,22 +4386,13 @@ class ReticulumMeshChat:
|
||||
},
|
||||
status=422,
|
||||
)
|
||||
transport_identity = data.get("transport_identity")
|
||||
if (
|
||||
transport_identity is None
|
||||
or str(transport_identity).strip() == ""
|
||||
):
|
||||
return web.json_response(
|
||||
{
|
||||
"message": "Transport identity is required",
|
||||
},
|
||||
status=422,
|
||||
)
|
||||
interface_details["remote"] = str(remote).strip()
|
||||
interface_details["target_port"] = interface_target_port
|
||||
interface_details["transport_identity"] = str(
|
||||
transport_identity,
|
||||
).strip()
|
||||
InterfaceEditor.update_value(
|
||||
interface_details,
|
||||
data,
|
||||
"transport_identity",
|
||||
)
|
||||
|
||||
# handle I2P interface
|
||||
if interface_type == "I2PInterface":
|
||||
@@ -4802,6 +4793,42 @@ class ReticulumMeshChat:
|
||||
interface_details["command"] = interface_command
|
||||
interface_details["respawn_delay"] = interface_respawn_delay
|
||||
|
||||
_builtin_interface_types = frozenset(
|
||||
{
|
||||
"AutoInterface",
|
||||
"TCPClientInterface",
|
||||
"BackboneInterface",
|
||||
"I2PInterface",
|
||||
"TCPServerInterface",
|
||||
"UDPInterface",
|
||||
"RNodeInterface",
|
||||
"RNodeIPInterface",
|
||||
"RNodeMultiInterface",
|
||||
"SerialInterface",
|
||||
"KISSInterface",
|
||||
"AX25KISSInterface",
|
||||
"PipeInterface",
|
||||
},
|
||||
)
|
||||
if interface_type not in _builtin_interface_types:
|
||||
extra = data.get("extra_config")
|
||||
if extra is None:
|
||||
extra = {}
|
||||
if not isinstance(extra, dict):
|
||||
return web.json_response(
|
||||
{
|
||||
"message": "extra_config must be a JSON object",
|
||||
},
|
||||
status=422,
|
||||
)
|
||||
for key, value in extra.items():
|
||||
if key in {"name", "type", "allow_overwriting_interface"}:
|
||||
continue
|
||||
if value is None or value == "":
|
||||
interface_details.pop(key, None)
|
||||
else:
|
||||
interface_details[key] = value
|
||||
|
||||
# interface discovery options
|
||||
for discovery_key in (
|
||||
"discoverable",
|
||||
@@ -13706,6 +13733,27 @@ class ReticulumMeshChat:
|
||||
)
|
||||
return
|
||||
|
||||
if lu.startswith("meshchatx://docs") or lu.startswith("meshchat://docs"):
|
||||
from urllib.parse import parse_qsl, urlparse
|
||||
|
||||
parsed = urlparse(uri_raw)
|
||||
q = dict(parse_qsl(parsed.query, keep_blank_values=True))
|
||||
rel = (q.get("reticulum") or q.get("path") or "").strip()
|
||||
payload: dict = {
|
||||
"type": "lxm.ingest_uri.result",
|
||||
"status": "success",
|
||||
"message": "Opening documentation.",
|
||||
"ingest_type": "docs_view",
|
||||
}
|
||||
if rel:
|
||||
payload["docs_query"] = {"reticulum": rel}
|
||||
AsyncUtils.run_async(
|
||||
client.send_str(
|
||||
json.dumps(payload),
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
# Columba-style contact sharing URI:
|
||||
# lxma://<destination_hash_hex>:<public_key_hex>
|
||||
if uri.lower().startswith("lxma://"):
|
||||
@@ -16697,6 +16745,12 @@ def main():
|
||||
if args.no_crash_recovery:
|
||||
recovery.disable()
|
||||
|
||||
planned_storage_dir = args.storage_dir or os.path.join("storage")
|
||||
recovery.update_paths(
|
||||
storage_dir=planned_storage_dir,
|
||||
reticulum_config_dir=args.reticulum_config_dir,
|
||||
)
|
||||
|
||||
# check if we want to test exception messages
|
||||
if args.test_exception_message is not None:
|
||||
raise Exception(args.test_exception_message)
|
||||
|
||||
@@ -308,6 +308,62 @@ async def test_backbone_listener_mode_persists_options(temp_dir):
|
||||
assert saved["prefer_ipv6"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_backbone_connector_allows_empty_transport_identity(temp_dir):
|
||||
config = ConfigDict({"reticulum": {}, "interfaces": {}})
|
||||
|
||||
free_port = _free_port("tcp")
|
||||
async with make_app(temp_dir, config) as handler:
|
||||
payload = {
|
||||
"name": "BackboneOut",
|
||||
"type": "BackboneInterface",
|
||||
"target_host": "10.0.0.1",
|
||||
"target_port": str(free_port),
|
||||
}
|
||||
response = await handler(make_request(payload))
|
||||
body = json.loads(response.body)
|
||||
assert response.status == 200, body
|
||||
saved = config["interfaces"]["BackboneOut"]
|
||||
assert saved["remote"] == "10.0.0.1"
|
||||
assert str(saved["target_port"]) == str(free_port)
|
||||
assert "transport_identity" not in saved
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_external_interface_merges_extra_config(temp_dir):
|
||||
config = ConfigDict({"reticulum": {}, "interfaces": {}})
|
||||
|
||||
async with make_app(temp_dir, config) as handler:
|
||||
payload = {
|
||||
"name": "WeaveTest",
|
||||
"type": "WeaveInterface",
|
||||
"extra_config": {"listen_ip": "127.0.0.1", "listen_port": 4242},
|
||||
}
|
||||
response = await handler(make_request(payload))
|
||||
body = json.loads(response.body)
|
||||
assert response.status == 200, body
|
||||
saved = config["interfaces"]["WeaveTest"]
|
||||
assert saved["type"] == "WeaveInterface"
|
||||
assert saved["listen_ip"] == "127.0.0.1"
|
||||
assert saved["listen_port"] == 4242
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_external_interface_rejects_non_object_extra_config(temp_dir):
|
||||
config = ConfigDict({"reticulum": {}, "interfaces": {}})
|
||||
|
||||
async with make_app(temp_dir, config) as handler:
|
||||
payload = {
|
||||
"name": "Bad",
|
||||
"type": "FooInterface",
|
||||
"extra_config": "not-an-object",
|
||||
}
|
||||
response = await handler(make_request(payload))
|
||||
body = json.loads(response.body)
|
||||
assert response.status == 422
|
||||
assert "extra_config" in body["message"].lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_backbone_connector_mode_still_requires_remote(temp_dir):
|
||||
config = ConfigDict({"reticulum": {}, "interfaces": {}})
|
||||
|
||||
Reference in New Issue
Block a user