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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshsocket
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Lightweight WebSocket mesh networking — broadcast, direct routing, and request/response over a simple JSON wire protocol.
5
5
  Author: Carter Beaudoin
6
6
  License-Expression: MIT
@@ -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.3.0"
10
+ __version__ = "0.4.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshsocket
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Lightweight WebSocket mesh networking — broadcast, direct routing, and request/response over a simple JSON wire protocol.
5
5
  Author: Carter Beaudoin
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshsocket"
7
- version = "0.3.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
- data = payload.get('payload')
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
- return response
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