meshsocket 0.3.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {meshsocket-0.3.0 → meshsocket-0.4.1}/PKG-INFO +1 -1
- {meshsocket-0.3.0 → meshsocket-0.4.1}/meshsocket/__init__.py +1 -1
- {meshsocket-0.3.0 → meshsocket-0.4.1}/meshsocket.egg-info/PKG-INFO +1 -1
- {meshsocket-0.3.0 → meshsocket-0.4.1}/pyproject.toml +1 -1
- {meshsocket-0.3.0 → meshsocket-0.4.1}/socket_server.py +56 -3
- {meshsocket-0.3.0 → meshsocket-0.4.1}/LICENSE +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/README.md +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/meshsocket.egg-info/SOURCES.txt +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/meshsocket.egg-info/dependency_links.txt +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/meshsocket.egg-info/requires.txt +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/meshsocket.egg-info/top_level.txt +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/setup.cfg +0 -0
- {meshsocket-0.3.0 → meshsocket-0.4.1}/socketCore.py +0 -0
|
@@ -7,4 +7,4 @@ so existing code keeps running while it migrates to the package name.
|
|
|
7
7
|
from socketCore import MeshSocket, LogColors, sanitize_identity, valid_ident, IDENT_RE, NAMESPACED_RE
|
|
8
8
|
|
|
9
9
|
__all__ = ["MeshSocket", "LogColors", "sanitize_identity", "valid_ident", "IDENT_RE", "NAMESPACED_RE"]
|
|
10
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.4.1"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meshsocket"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Lightweight WebSocket mesh networking — broadcast, direct routing, and request/response over a simple JSON wire protocol."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -19,6 +19,48 @@ class MeshServerConfigError(RuntimeError):
|
|
|
19
19
|
"""The server refused to start because its configuration is unsafe."""
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
# The key the relay writes on every routed payload (0.4.0): the sender's
|
|
23
|
+
# authenticated name, as this relay registered it at identify (through the
|
|
24
|
+
# carter-relay gateway that is the namespaced `<account>.<principal>_<name>`).
|
|
25
|
+
# Peers write `_from` too (on broadcasts, where it is left alone), so a receiver
|
|
26
|
+
# cannot tell this stamp from a peer's claim by the key alone: see RELAY_SENDER_KEY.
|
|
27
|
+
SENDER_KEY = "_from"
|
|
28
|
+
|
|
29
|
+
# The relay-only sender key (0.4.1). Set beside `_from` on every routed payload,
|
|
30
|
+
# and removed from every payload the relay forwards (routed, routed replies and
|
|
31
|
+
# broadcasts), so a receiver that finds it knows this relay wrote it: no peer can
|
|
32
|
+
# get its own copy through. Broadcasts never carry it (they are not stamped).
|
|
33
|
+
RELAY_SENDER_KEY = "_relayFrom"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def stamp_sender(payload: Any, sender_name: str) -> Any:
|
|
37
|
+
"""`payload` with `SENDER_KEY` and `RELAY_SENDER_KEY` set to `sender_name`,
|
|
38
|
+
overwriting any value the sender wrote. Only a dict payload is stamped. On an
|
|
39
|
+
E2EE envelope (`{"e2ee": 2, "s", "n", "ct"}`) the keys land on the outer,
|
|
40
|
+
unsealed object beside the ciphertext, which is where receivers read the
|
|
41
|
+
relay's stamp; the sealed body is never touched. Other payloads (lists,
|
|
42
|
+
strings, null) pass unchanged. Returns a copy; the caller's dict is not
|
|
43
|
+
modified."""
|
|
44
|
+
if not isinstance(payload, dict):
|
|
45
|
+
return payload
|
|
46
|
+
stamped = dict(payload)
|
|
47
|
+
stamped[SENDER_KEY] = sender_name
|
|
48
|
+
stamped[RELAY_SENDER_KEY] = sender_name
|
|
49
|
+
return stamped
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def strip_relay_sender(payload: Any) -> Any:
|
|
53
|
+
"""`payload` without a peer-written `RELAY_SENDER_KEY` on its outer object.
|
|
54
|
+
Only a dict payload changes (a copy; the caller's dict is not modified);
|
|
55
|
+
nested values and an E2EE envelope's sealed body are not touched, and
|
|
56
|
+
everything else, `_from` included, is delivered as sent."""
|
|
57
|
+
if not isinstance(payload, dict) or RELAY_SENDER_KEY not in payload:
|
|
58
|
+
return payload
|
|
59
|
+
stripped = dict(payload)
|
|
60
|
+
del stripped[RELAY_SENDER_KEY]
|
|
61
|
+
return stripped
|
|
62
|
+
|
|
63
|
+
|
|
22
64
|
def _env_int(name: str, default: int) -> int:
|
|
23
65
|
raw = os.getenv(name)
|
|
24
66
|
if raw is None or not raw.strip():
|
|
@@ -459,7 +501,8 @@ class MeshServer:
|
|
|
459
501
|
return {"error": "malformed route", "status": "failed"}
|
|
460
502
|
target_id = payload.get('target_id')
|
|
461
503
|
msg_type = payload.get('type')
|
|
462
|
-
|
|
504
|
+
# The target learns who sent this from the relay, not the sender.
|
|
505
|
+
data = stamp_sender(payload.get('payload'), client.name)
|
|
463
506
|
if not isinstance(target_id, str) or not isinstance(msg_type, str):
|
|
464
507
|
return {"error": "malformed route", "status": "failed"}
|
|
465
508
|
|
|
@@ -482,7 +525,8 @@ class MeshServer:
|
|
|
482
525
|
# The target never answered: say so instead of leaving the
|
|
483
526
|
# requester to wait out its own timeout with no signal.
|
|
484
527
|
return {"error": "target timeout", "status": "failed"}
|
|
485
|
-
|
|
528
|
+
# The target wrote this reply: it may not carry the relay-only key.
|
|
529
|
+
return strip_relay_sender(response)
|
|
486
530
|
|
|
487
531
|
@client.on("get_nodes")
|
|
488
532
|
async def on_get_nodes(payload):
|
|
@@ -499,7 +543,7 @@ class MeshServer:
|
|
|
499
543
|
return {"error": "malformed route", "status": "failed"}
|
|
500
544
|
target_name = payload.get('target_name')
|
|
501
545
|
msg_type = payload.get('type')
|
|
502
|
-
data = payload.get('payload')
|
|
546
|
+
data = stamp_sender(payload.get('payload'), client.name)
|
|
503
547
|
if not isinstance(target_name, str) or not isinstance(msg_type, str):
|
|
504
548
|
return {"error": "malformed route", "status": "failed"}
|
|
505
549
|
|
|
@@ -608,8 +652,17 @@ class MeshServer:
|
|
|
608
652
|
await self._fan_out(sends, targets, "roster")
|
|
609
653
|
|
|
610
654
|
async def broadcast(self, type: str, payload: dict, sender: MeshSocket | None = None):
|
|
655
|
+
# Broadcasts are NOT stamped (only routed payloads are; see stamp_sender).
|
|
656
|
+
# Peers already put their own `_from` on broadcasts and match replies
|
|
657
|
+
# against it: CAR-TER writes its bare name on an ack'd command and drops a
|
|
658
|
+
# `command_ack` whose `to` differs, and carterkit acks to the outer `_from`
|
|
659
|
+
# of a sealed frame when there is one. Behind the gateway the relay name is
|
|
660
|
+
# namespaced, so stamping broadcasts would break those acks.
|
|
661
|
+
# A peer-written RELAY_SENDER_KEY is removed (0.4.1): on a broadcast the
|
|
662
|
+
# receiver finds none, so it never mistakes a peer's claim for the relay's.
|
|
611
663
|
if not self.clients:
|
|
612
664
|
return
|
|
665
|
+
payload = strip_relay_sender(payload)
|
|
613
666
|
sends, targets = [], []
|
|
614
667
|
for peer in list(self.clients.values()):
|
|
615
668
|
if sender and not self._can_receive_broadcast(sender, peer):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|