Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-magic-wormhole-mailbox-server
for openSUSE:Factory checked in at 2026-07-06 13:13:52
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-magic-wormhole-mailbox-server (Old)
and
/work/SRC/openSUSE:Factory/.python-magic-wormhole-mailbox-server.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-magic-wormhole-mailbox-server"
Mon Jul 6 13:13:52 2026 rev:10 rq:1364028 version:0.8.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-magic-wormhole-mailbox-server/python-magic-wormhole-mailbox-server.changes
2026-04-26 21:14:00.528029180 +0200
+++
/work/SRC/openSUSE:Factory/.python-magic-wormhole-mailbox-server.new.1982/python-magic-wormhole-mailbox-server.changes
2026-07-06 13:15:18.252929873 +0200
@@ -1,0 +2,12 @@
+Mon Jun 29 08:42:27 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.8.0:
+ * Server header properly reports version
+ * introduce a ``"your-address"`` key to the ``"welcome"``
+ message (to reflect the IP address and port back, #63)
+ * non-numeric nameplates rejected with error (@meejah)
+ * more-complete sequence and state diagrams (@meejah)
+ * update Munin plugin shebang (@warner)
+ * Munin plugins open db read-only (@warner)
+
+-------------------------------------------------------------------
Old:
----
magic_wormhole_mailbox_server-0.6.0.tar.gz
New:
----
magic_wormhole_mailbox_server-0.8.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-magic-wormhole-mailbox-server.spec ++++++
--- /var/tmp/diff_new_pack.ZPmwA8/_old 2026-07-06 13:15:18.760947445 +0200
+++ /var/tmp/diff_new_pack.ZPmwA8/_new 2026-07-06 13:15:18.764947583 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-magic-wormhole-mailbox-server
-Version: 0.6.0
+Version: 0.8.0
Release: 0
Summary: Key exchange and control message server for Magic-Wormhole
License: MIT
++++++ magic_wormhole_mailbox_server-0.6.0.tar.gz ->
magic_wormhole_mailbox_server-0.8.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/magic_wormhole_mailbox_server-0.6.0/NEWS.md
new/magic_wormhole_mailbox_server-0.8.0/NEWS.md
--- old/magic_wormhole_mailbox_server-0.6.0/NEWS.md 2026-02-13
08:26:00.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/NEWS.md 2026-05-15
19:20:26.000000000 +0200
@@ -1,4 +1,5 @@
+
User-visible changes in "magic-wormhole-mailbox-server":
## Upcoming
@@ -6,6 +7,22 @@
* (put release-notes here when merging / proposing a PR)
+## Release 0.8.0 (15-May-2026)
+
+* Server header properly reports version (#27)
+* introduce a ``"your-address"`` key to the ``"welcome"`` message (to reflect
the IP address and port back, #63)
+
+
+## Release 0.7.0 (14-May-2026)
+
+* CI no longer tests Python 3.9 @meejah
+* CI now tests 3.14 @meejah
+* non-numeric nameplates rejected with error (@meejah)
+* more-complete sequence and state diagrams (@meejah)
+* update Munin plugin shebang (@warner)
+* Munin plugins open db read-only (@warner)
+
+
## Release 0.6.0 (13-Feb-2026)
* CI no longer tests Python 3.8 (it is EOL)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/magic_wormhole_mailbox_server-0.6.0/PKG-INFO
new/magic_wormhole_mailbox_server-0.8.0/PKG-INFO
--- old/magic_wormhole_mailbox_server-0.6.0/PKG-INFO 2026-02-13
08:33:43.829119400 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/PKG-INFO 2026-05-15
19:20:42.453664800 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: magic-wormhole-mailbox-server
-Version: 0.6.0
+Version: 0.8.0
Summary: Securely transfer data between computers
Home-page: https://github.com/warner/magic-wormhole-mailbox-server
Author: Brian Warner
@@ -141,4 +141,4 @@
This library is released under the MIT license, see LICENSE for details.
-This library is compatible with python3 (3.9 and higher).
+This library is compatible with python3 (3.10 and higher).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/magic_wormhole_mailbox_server-0.6.0/README.md
new/magic_wormhole_mailbox_server-0.8.0/README.md
--- old/magic_wormhole_mailbox_server-0.6.0/README.md 2025-03-26
20:28:12.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/README.md 2026-04-05
09:03:32.000000000 +0200
@@ -99,4 +99,4 @@
This library is released under the MIT license, see LICENSE for details.
-This library is compatible with python3 (3.9 and higher).
+This library is compatible with python3 (3.10 and higher).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/magic_wormhole_mailbox_server-0.6.0/docs/Makefile
new/magic_wormhole_mailbox_server-0.8.0/docs/Makefile
--- old/magic_wormhole_mailbox_server-0.6.0/docs/Makefile 2019-03-04
04:38:50.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/docs/Makefile 2026-04-05
09:03:32.000000000 +0200
@@ -12,7 +12,12 @@
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-.PHONY: help Makefile
+.PHONY: help Makefile diagrams
+
+diagrams:
+ plantuml -Tpng happy-plant.seq
+ seqdiag --no-transparency --antialias -T png happy.seq
+ dot -Tpng states.dot > states.png
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/magic_wormhole_mailbox_server-0.6.0/docs/states.dot
new/magic_wormhole_mailbox_server-0.8.0/docs/states.dot
--- old/magic_wormhole_mailbox_server-0.6.0/docs/states.dot 1970-01-01
01:00:00.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/docs/states.dot 2026-04-05
09:03:32.000000000 +0200
@@ -0,0 +1,74 @@
+/*digraph {
+ title [label="Mailbox\lServer Machine" style="dotted"]
+
+ start -> opened [label="open(side)"];
+
+ opened -> opened [label="open(side)"];
+ opened -> opened [label="add_message(sided_message)"];
+ opened -> closing [label="close(side, mood)"];
+
+ closing -> closing [label="close(side, mood)"];
+}
+*/
+
+
+// note: all messages have an "id" and a "type"
+// and the server sends back an "ack" for every one
+// but that ack etc isn't covered in these diagrams
+
+digraph {
+ node [fontname = "Source Sans Pro" fontsize = 22];
+ edge [fontname = "Source Code Pro" fontsize = 18 fontcolor=blue];
+ graph [fontname = "Source Sans Pro" fontsize = 22];
+
+ title [label="Mailbox Server" style="dotted" fontsize=32];
+
+ ranksep = 1;
+
+ start [label="START\n(perm=none)"];
+ start_permissions [label="START\n(perm=hashcash)"];
+ start_reconn [label="RECONNECT\n(mailbox, perm=none)"];
+ start_reconn_perm [label="RECONNECT\n(mailbox, perm=hashcash)"];
+ done [label="DONE\nmood=" shape=box style=filled];
+
+ {rank=same; start start_permissions start_reconn start_reconn_perm}
+ start [shape=box, style=bold];
+ start -> bound [label="bind(appid, side)"];
+
+ # blue, to match seqdiag section on "nameplate allocated"
+ bound [fillcolor=cadetblue1, style=filled];
+ have_nameplate [fillcolor=cadetblue1, style=filled];
+ claimed [fillcolor=cadetblue1, style=filled];
+
+ start_permissions [shape=box, style=bold];
+ start_permissions -> granted [label="submit_permissions()" fontcolor=red];
+ granted -> bound [label="bind(appid, side)"];
+
+ start_reconn [shape=box, style=bold];
+ start_reconn -> open [label="open(mailbox_id)\l-> messages: send(side,
phase, body)\l"];
+
+ start_reconn_perm [shape=box, style=bold];
+ start_reconn_perm -> reconn_granted [label="submit_permissions()"
fontcolor=red];
+ open [fillcolor=green style=filled];
+ reconn_granted -> open [label="open(mailbox_id)\l-> messages: send(side,
phase, body)\l"];
+
+ bound -> have_nameplate [label="allocate()\l-> nameplate_id\l"]
+ # allocate() really does do a claim() .. but you have to call it
explicitly too
+ have_nameplate -> claimed [label="claim(nameplate, side)\l-> mailbox_id\l"
fontcolor=darkgreen]
+ have_nameplate -> done [label="release(nameplate)" fontcolor=red]
+
+ # ths is on the "join" side; they are told the nameplate number
+ bound -> claimed [label="claim(nameplate, side)\l-> mailbox_id\l"
fontcolor=darkgreen]
+ claimed -> unclaimed [label="release(nameplate)" fontcolor=red]
+
+ # note: allowing two different paths to 'unclaimed' is I think
+ # _allowed_ currently by the server, but better to define it with
+ # juts one way probably.
+
+ unclaimed -> open [label="open(mailbox_id)\l-> messages: send(side, phase,
body)\l"]
+ #claimed -> open [label="open(mailbox_id)\l-> send(all_messages)\l"]
+ #open -> open [label="release(nameplate)"]
+ open -> open [label="add_message(msg)\l-> send(side, phase, body)\l"]
+ open -> done [label="close(mailbox_id)" fontcolor=red]
+ # XXX will get all message already in the box, how to represent?
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_active
new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_active
--- old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_active
2019-03-04 04:38:50.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_active
2026-05-14 21:00:08.000000000 +0200
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""
Use the following in /etc/munin/plugin-conf.d/wormhole :
@@ -32,11 +32,11 @@
usagedbfile = os.environ["usagedb"]
assert os.path.exists(usagedbfile)
-usage_db = sqlite3.connect(usagedbfile)
+usage_db = sqlite3.connect("file:%s?mode=ro" % usagedbfile, uri=True)
channeldbfile = os.environ["channeldb"]
assert os.path.exists(channeldbfile)
-channel_db = sqlite3.connect(channeldbfile)
+channel_db = sqlite3.connect("file:%s?mode=ro" % channeldbfile, uri=True)
MINUTE = 60.0
updated,rebooted = usage_db.execute("SELECT `updated`,`rebooted` FROM
`current`").fetchone()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_errors
new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_errors
--- old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_errors
2019-03-04 04:38:50.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_errors
2026-05-14 21:00:08.000000000 +0200
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""
Use the following in /etc/munin/plugin-conf.d/wormhole :
@@ -31,7 +31,7 @@
usagedbfile = os.environ["usagedb"]
assert os.path.exists(usagedbfile)
-usage_db = sqlite3.connect(usagedbfile)
+usage_db = sqlite3.connect("file:%s?mode=ro" % usagedbfile, uri=True)
MINUTE = 60.0
updated,rebooted = usage_db.execute("SELECT `updated`,`rebooted` FROM
`current`").fetchone()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_event_rate
new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_event_rate
--- old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_event_rate
2019-03-04 04:38:50.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_event_rate
2026-05-14 21:00:08.000000000 +0200
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""
Use the following in /etc/munin/plugin-conf.d/wormhole :
@@ -41,7 +41,7 @@
usagedbfile = os.environ["usagedb"]
assert os.path.exists(usagedbfile)
-usage_db = sqlite3.connect(usagedbfile)
+usage_db = sqlite3.connect("file:%s?mode=ro" % usagedbfile, uri=True)
MINUTE = 60.0
updated,rebooted = usage_db.execute("SELECT `updated`,`rebooted` FROM
`current`").fetchone()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_events
new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_events
--- old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_events
2019-03-04 04:38:50.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_events
2026-05-14 21:00:08.000000000 +0200
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""
Use the following in /etc/munin/plugin-conf.d/wormhole :
@@ -40,7 +40,7 @@
usagedbfile = os.environ["usagedb"]
assert os.path.exists(usagedbfile)
-usage_db = sqlite3.connect(usagedbfile)
+usage_db = sqlite3.connect("file:%s?mode=ro" % usagedbfile, uri=True)
MINUTE = 60.0
updated,rebooted,blur = usage_db.execute(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_events_alltime
new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_events_alltime
--- old/magic_wormhole_mailbox_server-0.6.0/misc/munin/wormhole_events_alltime
2019-03-04 04:38:50.000000000 +0100
+++ new/magic_wormhole_mailbox_server-0.8.0/misc/munin/wormhole_events_alltime
2026-05-14 21:00:08.000000000 +0200
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""
Use the following in /etc/munin/plugin-conf.d/wormhole :
@@ -40,7 +40,7 @@
usagedbfile = os.environ["usagedb"]
assert os.path.exists(usagedbfile)
-usage_db = sqlite3.connect(usagedbfile)
+usage_db = sqlite3.connect("file:%s?mode=ro" % usagedbfile, uri=True)
MINUTE = 60.0
updated,rebooted = usage_db.execute("SELECT `updated`,`rebooted` FROM
`current`").fetchone()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/magic_wormhole_mailbox_server.egg-info/PKG-INFO
new/magic_wormhole_mailbox_server-0.8.0/src/magic_wormhole_mailbox_server.egg-info/PKG-INFO
---
old/magic_wormhole_mailbox_server-0.6.0/src/magic_wormhole_mailbox_server.egg-info/PKG-INFO
2026-02-13 08:33:43.000000000 +0100
+++
new/magic_wormhole_mailbox_server-0.8.0/src/magic_wormhole_mailbox_server.egg-info/PKG-INFO
2026-05-15 19:20:42.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: magic-wormhole-mailbox-server
-Version: 0.6.0
+Version: 0.8.0
Summary: Securely transfer data between computers
Home-page: https://github.com/warner/magic-wormhole-mailbox-server
Author: Brian Warner
@@ -141,4 +141,4 @@
This library is released under the MIT license, see LICENSE for details.
-This library is compatible with python3 (3.9 and higher).
+This library is compatible with python3 (3.10 and higher).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/magic_wormhole_mailbox_server.egg-info/SOURCES.txt
new/magic_wormhole_mailbox_server-0.8.0/src/magic_wormhole_mailbox_server.egg-info/SOURCES.txt
---
old/magic_wormhole_mailbox_server-0.6.0/src/magic_wormhole_mailbox_server.egg-info/SOURCES.txt
2026-02-13 08:33:43.000000000 +0100
+++
new/magic_wormhole_mailbox_server-0.8.0/src/magic_wormhole_mailbox_server.egg-info/SOURCES.txt
2026-05-15 19:20:42.000000000 +0200
@@ -11,6 +11,7 @@
docs/conf.py
docs/index.rst
docs/server-protocol.md
+docs/states.dot
docs/welcome.md
misc/migrate_channel_db.py
misc/migrate_usage_db.py
@@ -49,5 +50,6 @@
src/wormhole_mailbox_server/test/test_stats.py
src/wormhole_mailbox_server/test/test_util.py
src/wormhole_mailbox_server/test/test_web.py
+src/wormhole_mailbox_server/test/test_websocket.py
src/wormhole_mailbox_server/test/test_ws_client.py
src/wormhole_mailbox_server/test/ws_client.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/_version.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/_version.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/_version.py
2026-02-13 08:33:43.829119400 +0100
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/_version.py
2026-05-15 19:20:42.457664700 +0200
@@ -8,11 +8,11 @@
version_json = '''
{
- "date": "2026-02-13T00:33:32-0700",
+ "date": "2026-05-15T11:20:33-0600",
"dirty": false,
"error": null,
- "full-revisionid": "348c907e14032a68301cf0a9017bc78e272c5094",
- "version": "0.6.0"
+ "full-revisionid": "67cdaf56113b37ac29d4bd35398bdd086191a41e",
+ "version": "0.8.0"
}
''' # END VERSION_JSON
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/server.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/server.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/server.py
2025-10-08 18:43:07.000000000 +0200
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/server.py
2026-05-14 21:52:17.000000000 +0200
@@ -1,4 +1,4 @@
-import os, random, base64
+import os, random, base64, re
from collections import namedtuple
from twisted.python import log
from twisted.application import service
@@ -6,6 +6,16 @@
def generate_mailbox_id():
return base64.b32encode(os.urandom(8)).lower().strip(b"=").decode("ascii")
+NAMEPLATE_RE = re.compile(r'^\d+$')
+
+def check_valid_nameplate(n):
+ if not isinstance(n, str):
+ raise ValueError("nameplate %r is %s not str" % (n, type(n)))
+ if len(n) > 40:
+ raise ValueError("nameplate %s .. is too long, %d>40" % (repr(n)[:50],
len(n)))
+ if not NAMEPLATE_RE.search(n):
+ raise ValueError("nameplate %s has non-digits" % (n,))
+
class CrowdedError(Exception):
pass
class ReclaimedError(Exception):
@@ -244,6 +254,7 @@
# * a mailbox 'side' will be attached, with opened=True
assert isinstance(name, str), type(name)
assert isinstance(side, str), type(side)
+ check_valid_nameplate(name)
db = self._db
row = db.execute("SELECT * FROM `nameplates`"
" WHERE `app_id`=? AND `name`=?",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/server_websocket.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/server_websocket.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/server_websocket.py
2025-10-08 18:43:07.000000000 +0200
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/server_websocket.py
2026-05-15 19:14:29.000000000 +0200
@@ -2,7 +2,7 @@
from twisted.internet import reactor
from twisted.python import log
from autobahn.twisted import websocket
-from .server import CrowdedError, ReclaimedError, SidedMessage
+from .server import CrowdedError, ReclaimedError, SidedMessage,
check_valid_nameplate
from .util import dict_to_bytes, bytes_to_dict
# The WebSocket allows the client to send "commands" to the server, and the
@@ -108,16 +108,62 @@
self._mailbox = None
self._mailbox_id = None
self._did_close = False
+ self._peer_addr_port = None
def onConnect(self, request):
- rv = self.factory.server
+ rv = self.factory._server
+ # Caddy uses capitalized headers like X-Real-IP and X-Real-Port, which
+ # you see if you forward Caddy to netcat. But the twisted/autobahn
+ # Request object lowercases everything.
+ #
+ # We only use this for assistance in NAT hole-punching, so it
+ # doesn't matter if the client is able to inject their own
+ # headers and spoof somebody else's IP address, they're only
+ # hurting themselves
+ if "x-real-ip" in request.headers:
+ peer_host = request.headers.get("x-real-ip") # either 1.2.3.4 or
2600:..:1234
+ peer_port = request.headers.get("x-real-port")
+ # assume frontends like Caddy don't give us v4-in-v6 addrs
+ peer_type = "ipv6" if ":" in peer_host else "ipv4"
+ else:
+ peer = request.peer
+ peer_type = peer.split(":", maxsplit=1)[0]
+ peer_port = peer.rsplit(":", maxsplit=1)[-1]
+ peer_host = peer[len(peer_type)+1:(-len(peer_port)-1)]
+ # this gets me [tcp6, ::1, 53276] for a client using
ws://localhost:4000/v1
+ # or ws://[::1]:4000/v1
+ # and [tcp6, ::ffff:127.0.0.1, 53279] when using
ws://127.0.0.1:4000/v1
+ if peer_type == "tcp6":
+ peer_type = "ipv6"
+ if peer_host.startswith("::ffff:"):
+ peer_host = peer_host.rsplit(":", maxsplit=1)[-1]
+ peer_type = "ipv4"
+ else:
+ peer_type = "ipv4"
+ self._peer_addr_port = (peer_type, peer_host, int(peer_port))
+
if rv.get_log_requests():
- log.msg(f"ws client connecting: {request.peer}")
+ v = 4 if peer_type == "ipv4" else 6
+ log.msg(f"ws client connecting: tcp{v}:{peer_host}:{peer_port}")
self._reactor = self.factory.reactor
+ # can return (name, dict) or name here, where name is
+ # WebSocket subprotocol name and dict is extra headers (if
+ # provided) to send
+
+ def get_your_address(self):
+ (peer_type, peer_host, peer_port) = self._peer_addr_port
+ you = { "port": peer_port }
+ if peer_type == "ipv4":
+ you["ipv4"] = peer_host
+ elif peer_type == "ipv6":
+ you["ipv6"] = peer_host
+ return you
def onOpen(self):
- rv = self.factory.server
- self.send("welcome", welcome=rv.get_welcome())
+ rv = self.factory._server
+ welcome = rv.get_welcome().copy()
+ welcome["your-address"] = self.get_your_address()
+ self.send("welcome", welcome=welcome)
def onMessage(self, payload, isBinary):
server_rx = time.time()
@@ -167,7 +213,7 @@
raise Error("bind requires 'appid'")
if "side" not in msg:
raise Error("bind requires 'side'")
- self._app = self.factory.server.get_app(msg["appid"])
+ self._app = self.factory._server.get_app(msg["appid"])
self._side = msg["side"]
client_version = msg.get("client_version", (None, None))
# e.g. ("python", "0.xyz") . <=0.10.5 did not send client_version
@@ -196,7 +242,7 @@
raise Error("only one claim per connection")
self._did_claim = True
nameplate_id = msg["nameplate"]
- assert isinstance(nameplate_id, str), type(nameplate_id)
+ check_valid_nameplate(nameplate_id)
self._nameplate_id = nameplate_id
try:
mailbox_id = self._app.claim_nameplate(nameplate_id, self._side,
@@ -212,6 +258,8 @@
raise Error("only one release per connection")
if "nameplate" in msg:
if self._nameplate_id is not None:
+ # we only care about equality, don't bother with
+ # check_valid_nameplate()
if msg["nameplate"] != self._nameplate_id:
raise Error("release and claim must use same nameplate")
nameplate_id = msg["nameplate"]
@@ -304,5 +352,9 @@
def __init__(self, url, server):
websocket.WebSocketServerFactory.__init__(self, url)
self.setProtocolOptions(autoPingInterval=60, autoPingTimeout=600)
- self.server = server
+ # note: Autobahn uses "self.factory.server" for the Server
+ # version string, so we musn't use that as well.
+ self._server = server
+ from . import __version__
+ self.server = f"Magic Wormhole Mailbox {__version__}"
self.reactor = reactor # for tests to control
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_server.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_server.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_server.py
2025-10-08 18:43:07.000000000 +0200
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_server.py
2026-05-15 19:14:29.000000000 +0200
@@ -6,6 +6,7 @@
SidedMessage, CrowdedError, AppNamespace)
from ..database import create_channel_db, create_usage_db
+npid = "1"
class Server(_Util, ServerBase, unittest.TestCase):
def test_apps(self):
@@ -323,17 +324,17 @@
app = rv.get_app(APPID)
# Exercise the first-vs-second newness tests
- app.claim_nameplate("np-1", "side1", 1)
- app.claim_nameplate("np-2", "side1", 1)
- app.claim_nameplate("np-2", "side2", 2)
- app.claim_nameplate("np-3", "side1", 60)
- new_nameplates.add("np-3")
- app.claim_nameplate("np-4", "side1", 1)
- app.claim_nameplate("np-4", "side2", 60)
- new_nameplates.add("np-4")
- app.claim_nameplate("np-5", "side1", 60)
- app.claim_nameplate("np-5", "side2", 61)
- new_nameplates.add("np-5")
+ app.claim_nameplate("1", "side1", 1)
+ app.claim_nameplate("2", "side1", 1)
+ app.claim_nameplate("2", "side2", 2)
+ app.claim_nameplate("3", "side1", 60)
+ new_nameplates.add("3")
+ app.claim_nameplate("4", "side1", 1)
+ app.claim_nameplate("4", "side2", 60)
+ new_nameplates.add("4")
+ app.claim_nameplate("5", "side1", 60)
+ app.claim_nameplate("5", "side2", 61)
+ new_nameplates.add("5")
rv.prune_all_apps(now=123, old=50)
@@ -344,6 +345,14 @@
db.execute("SELECT * FROM `mailboxes`").fetchall()}
self.assertEqual(len(new_nameplates), len(mailboxes))
+ self.assertRaises(ValueError,
+ app.claim_nameplate, "letters", "side1", 1)
+ long_but_ok = "1234"*10
+ app.claim_nameplate(long_but_ok, "side1", 1)
+ too_long = long_but_ok + "5"
+ self.assertRaises(ValueError,
+ app.claim_nameplate, too_long, "side1", 1)
+
def test_mailboxes(self):
db = create_channel_db(":memory:")
rv = make_server(db, blur_usage=3600)
@@ -404,7 +413,7 @@
mbid = "mbid"
if nameplate:
- mbid = app.claim_nameplate("npid", "side1", when[mailbox])
+ mbid = app.claim_nameplate(npid, "side1", when[mailbox])
mb = app.open_mailbox(mbid, "side1", when[mailbox])
# the pruning algorithm doesn't care about the age of messages,
@@ -519,7 +528,7 @@
rv = make_server(db, blur_usage=3600, usage_db=usage_db)
APPID = "appid"
app = rv.get_app(APPID)
- app.claim_nameplate("npid", "side1", 10) # start time is 10
+ app.claim_nameplate(npid, "side1", 10) # start time is 10
rv.prune_all_apps(now=123, old=50)
# start time should be rounded to top of the hour (blur_usage=3600)
row = usage_db.execute("SELECT * FROM `nameplates`").fetchone()
@@ -537,7 +546,7 @@
rv = make_server(db, blur_usage=None, usage_db=usage_db)
APPID = "appid"
app = rv.get_app(APPID)
- app.claim_nameplate("npid", "side1", 10) # start time is 10
+ app.claim_nameplate(npid, "side1", 10) # start time is 10
rv.prune_all_apps(now=123, old=50)
row = usage_db.execute("SELECT * FROM `nameplates`").fetchone()
self.assertEqual(row["started"], 10)
@@ -615,17 +624,32 @@
def test_welcome_error(self):
db = create_channel_db(":memory:")
s = make_server(db, signal_error="error!")
- self.assertEqual(s.get_welcome(), {"error": "error!"})
+ self.assertEqual(
+ s.get_welcome(),
+ {
+ "error": "error!",
+ }
+ )
def test_welcome_advertise_version(self):
db = create_channel_db(":memory:")
s = make_server(db, advertise_version="version")
- self.assertEqual(s.get_welcome(), {"current_cli_version": "version"})
+ self.assertEqual(
+ s.get_welcome(),
+ {
+ "current_cli_version": "version",
+ }
+ )
def test_welcome_message_of_the_day(self):
db = create_channel_db(":memory:")
s = make_server(db, welcome_motd="hello world")
- self.assertEqual(s.get_welcome(), {"motd": "hello world"})
+ self.assertEqual(
+ s.get_welcome(),
+ {
+ "motd": "hello world",
+ }
+ )
# exercise _find_available_nameplate_id failing
# exercise CrowdedError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_stats.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_stats.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_stats.py
2025-10-08 18:43:07.000000000 +0200
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_stats.py
2026-05-14 21:00:08.000000000 +0200
@@ -3,6 +3,8 @@
from ..database import create_channel_db, create_usage_db
from ..server import make_server, CrowdedError
+np1 = "1"
+
class _Make:
def make(self, blur_usage=None, with_usage_db=True):
self._cdb = create_channel_db(":memory:")
@@ -68,27 +70,27 @@
class Nameplate(_Make, unittest.TestCase):
def test_nameplate_happy(self):
s, db, app = self.make()
- app.claim_nameplate("n1", "s1", 1)
- app.claim_nameplate("n1", "s2", 3)
- app.release_nameplate("n1", "s1", 6)
+ app.claim_nameplate(np1, "s1", 1)
+ app.claim_nameplate(np1, "s2", 3)
+ app.release_nameplate(np1, "s1", 6)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[])
- app.release_nameplate("n1", "s2", 10)
+ app.release_nameplate(np1, "s2", 10)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[dict(app_id="appid", result="happy",
started=1, waiting_time=2, total_time=9)])
def test_nameplate_lonely(self):
s, db, app = self.make()
- app.claim_nameplate("n1", "s1", 1)
- app.release_nameplate("n1", "s1", 6)
+ app.claim_nameplate(np1, "s1", 1)
+ app.release_nameplate(np1, "s1", 6)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[dict(app_id="appid", result="lonely",
started=1, waiting_time=None, total_time=5)])
def test_nameplate_pruney(self):
s, db, app = self.make()
- app.claim_nameplate("n1", "s1", 1)
+ app.claim_nameplate(np1, "s1", 1)
app.prune(10, 5) # prune at t=10, anything earlier than 5 is "old"
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[dict(app_id="appid", result="pruney",
@@ -96,16 +98,16 @@
def test_nameplate_crowded(self):
s, db, app = self.make()
- app.claim_nameplate("n1", "s1", 1)
- app.claim_nameplate("n1", "s2", 2)
+ app.claim_nameplate(np1, "s1", 1)
+ app.claim_nameplate(np1, "s2", 2)
with self.assertRaises(CrowdedError):
- app.claim_nameplate("n1", "s3", 3)
+ app.claim_nameplate(np1, "s3", 3)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[])
- app.release_nameplate("n1", "s1", 4)
+ app.release_nameplate(np1, "s1", 4)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[])
- app.release_nameplate("n1", "s2", 5)
+ app.release_nameplate(np1, "s2", 5)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[])
#print(self._cdb.execute("SELECT * FROM `nameplates`").fetchall())
@@ -113,17 +115,17 @@
# TODO: to get "crowded", we need all three sides to release the
# nameplate, even though the third side threw CrowdedError and thus
# probably doesn't think it has a claim
- app.release_nameplate("n1", "s3", 6)
+ app.release_nameplate(np1, "s3", 6)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[dict(app_id="appid", result="crowded",
started=1, waiting_time=1, total_time=5)])
def test_nameplate_crowded_pruned(self):
s, db, app = self.make()
- app.claim_nameplate("n1", "s1", 1)
- app.claim_nameplate("n1", "s2", 2)
+ app.claim_nameplate(np1, "s1", 1)
+ app.claim_nameplate(np1, "s2", 2)
with self.assertRaises(CrowdedError):
- app.claim_nameplate("n1", "s3", 3)
+ app.claim_nameplate(np1, "s3", 3)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[])
app.prune(10, 5)
@@ -133,18 +135,18 @@
def test_no_db(self):
s, db, app = self.make(with_usage_db=False)
- app.claim_nameplate("n1", "s1", 1)
- app.release_nameplate("n1", "s1", 6)
+ app.claim_nameplate(np1, "s1", 1)
+ app.release_nameplate(np1, "s1", 6)
s.dump_stats(3, 1)
def test_nameplate_happy_blur_usage(self):
s, db, app = self.make(blur_usage=20)
- app.claim_nameplate("n1", "s1", 21)
- app.claim_nameplate("n1", "s2", 23)
- app.release_nameplate("n1", "s1", 26)
+ app.claim_nameplate(np1, "s1", 21)
+ app.claim_nameplate(np1, "s2", 23)
+ app.release_nameplate(np1, "s1", 26)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[])
- app.release_nameplate("n1", "s2", 30)
+ app.release_nameplate(np1, "s2", 30)
self.assertEqual(db.execute("SELECT * FROM `nameplates`").fetchall(),
[dict(app_id="appid", result="happy",
started=20, waiting_time=2, total_time=9)])
@@ -152,8 +154,8 @@
class Mailbox(_Make, unittest.TestCase):
def test_mailbox_prune_quiet(self):
s, db, app = self.make()
- app.claim_nameplate("n1", "s1", 1)
- app.release_nameplate("n1", "s1", 2)
+ app.claim_nameplate(np1, "s1", 1)
+ app.release_nameplate(np1, "s1", 2)
app.prune(10, 5)
self.assertEqual(db.execute("SELECT * FROM `mailboxes`").fetchall(),
[dict(app_id="appid", for_nameplate=1,
result="pruney",
@@ -161,9 +163,9 @@
def test_mailbox_lonely(self):
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox.close("s1", "mood-ignored", 4)
self.assertEqual(db.execute("SELECT * FROM `mailboxes`").fetchall(),
[dict(app_id="appid", for_nameplate=1,
result="lonely",
@@ -171,9 +173,9 @@
def test_mailbox_happy(self):
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox1 = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox2 = app.open_mailbox(mid, "s2", 4)
mbox1.close("s1", "happy", 5)
mbox2.close("s2", "happy", 6)
@@ -183,9 +185,9 @@
def test_mailbox_happy_blur_usage(self):
s, db, app = self.make(blur_usage=20)
- mid = app.claim_nameplate("n1", "s1", 21)
+ mid = app.claim_nameplate(np1, "s1", 21)
mbox1 = app.open_mailbox(mid, "s1", 22)
- app.release_nameplate("n1", "s1", 23)
+ app.release_nameplate(np1, "s1", 23)
mbox2 = app.open_mailbox(mid, "s2", 24)
mbox1.close("s1", "happy", 25)
mbox2.close("s2", "happy", 26)
@@ -198,9 +200,9 @@
# connect, but then at least one side says they're lonely when they
# close.
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox1 = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox2 = app.open_mailbox(mid, "s2", 4)
mbox1.close("s1", "lonely", 5)
mbox2.close("s2", "happy", 6)
@@ -210,9 +212,9 @@
def test_mailbox_scary(self):
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox1 = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox2 = app.open_mailbox(mid, "s2", 4)
mbox1.close("s1", "scary", 5)
mbox2.close("s2", "happy", 6)
@@ -222,9 +224,9 @@
def test_mailbox_errory(self):
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox1 = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox2 = app.open_mailbox(mid, "s2", 4)
mbox1.close("s1", "errory", 5)
mbox2.close("s2", "happy", 6)
@@ -234,9 +236,9 @@
def test_mailbox_errory_scary(self):
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox1 = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox2 = app.open_mailbox(mid, "s2", 4)
mbox1.close("s1", "errory", 5)
mbox2.close("s2", "scary", 6)
@@ -246,9 +248,9 @@
def test_mailbox_crowded(self):
s, db, app = self.make()
- mid = app.claim_nameplate("n1", "s1", 1)
+ mid = app.claim_nameplate(np1, "s1", 1)
mbox1 = app.open_mailbox(mid, "s1", 2)
- app.release_nameplate("n1", "s1", 3)
+ app.release_nameplate(np1, "s1", 3)
mbox2 = app.open_mailbox(mid, "s2", 4)
with self.assertRaises(CrowdedError):
app.open_mailbox(mid, "s3", 5)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_web.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_web.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_web.py
2025-10-08 18:43:07.000000000 +0200
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_web.py
2026-05-15 19:14:29.000000000 +0200
@@ -10,6 +10,9 @@
from .common import ServerBase, _Util
from .ws_client import WSFactory
+np1 = "1"
+np2 = "2"
+
class WebSocketProtocolOptions(unittest.TestCase):
@mock.patch('wormhole_mailbox_server.web.WebSocketServerFactory')
def test_set(self, fake_factory):
@@ -62,7 +65,6 @@
expected = "ws client connecting: tcp4:127.0.0.1:%d" % client_port
self.assertEqual(l.mock_calls, [mock.call(expected)])
-
@inlineCallbacks
def test_no_log_http(self):
yield self._setup_relay(do_listen=True, web_log_requests=False)
@@ -111,8 +113,7 @@
def check_welcome(self, data):
self.failUnlessIn("welcome", data)
- self.assertEqual(data["welcome"],
- {"current_cli_version": "advertised.version"})
+ self.assertEqual(data["welcome"]["current_cli_version"],
"advertised.version")
@inlineCallbacks
def test_welcome(self):
@@ -222,7 +223,7 @@
app = self._server.get_app("appid")
nameplate_id1 = app.allocate_nameplate("side", 0)
- app.claim_nameplate("np2", "side", 0)
+ app.claim_nameplate(np2, "side", 0)
c1.send("list")
m = yield c1.next_non_ack()
@@ -232,7 +233,7 @@
self.assertEqual(type(n), dict)
self.assertEqual(list(n.keys()), ["id"])
nids.add(n["id"])
- self.assertEqual(nids, {nameplate_id1, "np2"})
+ self.assertEqual(nids, {nameplate_id1, np2})
@inlineCallbacks
def test_allocate(self):
@@ -279,21 +280,21 @@
self.assertEqual(err["type"], "error")
self.assertEqual(err["error"], "claim requires 'nameplate'")
- c1.send("claim", nameplate="np1")
+ c1.send("claim", nameplate=np1)
m = yield c1.next_non_ack()
self.assertEqual(m["type"], "claimed")
mailbox_id = m["mailbox"]
self.assertEqual(type(mailbox_id), str)
- c1.send("claim", nameplate="np1")
+ c1.send("claim", nameplate=np1)
err = yield c1.next_non_ack()
self.assertEqual(err["type"], "error", err)
self.assertEqual(err["error"], "only one claim per connection")
nids = app.get_nameplate_ids()
self.assertEqual(len(nids), 1)
- self.assertEqual("np1", list(nids)[0])
- np_row, side_rows = self._nameplate(app, "np1")
+ self.assertEqual(np1, list(nids)[0])
+ np_row, side_rows = self._nameplate(app, np1)
self.assertEqual(len(side_rows), 1)
self.assertEqual(side_rows[0]["side"], "side")
@@ -310,11 +311,11 @@
c1.send("bind", appid="appid", side="side")
app = self._server.get_app("appid")
- app.claim_nameplate("np1", "side1", 0)
- app.claim_nameplate("np1", "side2", 0)
+ app.claim_nameplate(np1, "side1", 0)
+ app.claim_nameplate(np1, "side2", 0)
# the third claim will signal crowding
- c1.send("claim", nameplate="np1")
+ c1.send("claim", nameplate=np1)
err = yield c1.next_non_ack()
self.assertEqual(err["type"], "error")
self.assertEqual(err["error"], "crowded")
@@ -326,7 +327,7 @@
c1.send("bind", appid="appid", side="side")
app = self._server.get_app("appid")
- app.claim_nameplate("np1", "side2", 0)
+ app.claim_nameplate(np1, "side2", 0)
c1.send("release") # didn't do claim first
err = yield c1.next_non_ack()
@@ -334,14 +335,14 @@
self.assertEqual(err["error"],
"release without nameplate must follow claim")
- c1.send("claim", nameplate="np1")
+ c1.send("claim", nameplate=np1)
yield c1.next_non_ack()
c1.send("release")
m = yield c1.next_non_ack()
self.assertEqual(m["type"], "released", m)
- np_row, side_rows = self._nameplate(app, "np1")
+ np_row, side_rows = self._nameplate(app, np1)
claims = [(row["side"], row["claimed"]) for row in side_rows]
self.assertIn(("side", False), claims)
self.assertIn(("side2", True), claims)
@@ -357,10 +358,10 @@
yield c1.next_non_ack()
c1.send("bind", appid="appid", side="side")
- c1.send("claim", nameplate="np1")
+ c1.send("claim", nameplate=np1)
yield c1.next_non_ack()
- c1.send("release", nameplate="np1")
+ c1.send("release", nameplate=np1)
m = yield c1.next_non_ack()
self.assertEqual(m["type"], "released", m)
@@ -370,7 +371,7 @@
yield c1.next_non_ack()
c1.send("bind", appid="appid", side="side")
- c1.send("release", nameplate="np1") # didn't do claim first, ignored
+ c1.send("release", nameplate=np1) # didn't do claim first, ignored
m = yield c1.next_non_ack()
self.assertEqual(m["type"], "released", m)
@@ -380,10 +381,10 @@
yield c1.next_non_ack()
c1.send("bind", appid="appid", side="side")
- c1.send("claim", nameplate="np1")
+ c1.send("claim", nameplate=np1)
yield c1.next_non_ack()
- c1.send("release", nameplate="np2") # mismatching nameplate
+ c1.send("release", nameplate=np2) # mismatching nameplate
err = yield c1.next_non_ack()
self.assertEqual(err["type"], "error")
self.assertEqual(err["error"],
@@ -435,8 +436,8 @@
c1.send("bind", appid="appid", side="side")
app = self._server.get_app("appid")
- mbid = app.claim_nameplate("np1", "side1", 0)
- app.claim_nameplate("np1", "side2", 0)
+ mbid = app.claim_nameplate(np1, "side1", 0)
+ app.claim_nameplate(np1, "side2", 0)
# the third open will signal crowding
c1.send("open", mailbox=mbid)
@@ -550,8 +551,8 @@
c1.send("bind", appid="appid", side="side")
app = self._server.get_app("appid")
- mbid = app.claim_nameplate("np1", "side1", 0)
- app.claim_nameplate("np1", "side2", 0)
+ mbid = app.claim_nameplate(np1, "side1", 0)
+ app.claim_nameplate(np1, "side2", 0)
# a close that allocates a third side will signal crowding
c1.send("close", mailbox=mbid)
@@ -591,12 +592,12 @@
c.send("bind", appid="appid", side="side")
app = self._server.get_app("appid")
- c.send("claim", nameplate="np1")
+ c.send("claim", nameplate=np1)
m = yield c.next_non_ack()
self.assertEqual(m["type"], "claimed")
mailbox_id = m["mailbox"]
self.assertEqual(type(mailbox_id), str)
- np_row, side_rows = self._nameplate(app, "np1")
+ np_row, side_rows = self._nameplate(app, np1)
claims = [(row["side"], row["claimed"]) for row in side_rows]
self.assertEqual(claims, [("side", True)])
c.close()
@@ -605,11 +606,11 @@
c = yield self.make_client()
yield c.next_non_ack()
c.send("bind", appid="appid", side="side")
- c.send("claim", nameplate="np1") # idempotent
+ c.send("claim", nameplate=np1) # idempotent
m = yield c.next_non_ack()
self.assertEqual(m["type"], "claimed")
self.assertEqual(m["mailbox"], mailbox_id) # mailbox id is stable
- np_row, side_rows = self._nameplate(app, "np1")
+ np_row, side_rows = self._nameplate(app, np1)
claims = [(row["side"], row["claimed"]) for row in side_rows]
self.assertEqual(claims, [("side", True)])
c.close()
@@ -620,10 +621,10 @@
c.send("bind", appid="appid", side="side")
# we haven't done a claim with this particular connection, but we can
# still send a release as long as we include the nameplate
- c.send("release", nameplate="np1") # release-without-claim
+ c.send("release", nameplate=np1) # release-without-claim
m = yield c.next_non_ack()
self.assertEqual(m["type"], "released")
- np_row, side_rows = self._nameplate(app, "np1")
+ np_row, side_rows = self._nameplate(app, np1)
self.assertEqual(np_row, None)
c.close()
yield c.d
@@ -632,10 +633,10 @@
yield c.next_non_ack()
c.send("bind", appid="appid", side="side")
# and the release is idempotent, when done on separate connections
- c.send("release", nameplate="np1")
+ c.send("release", nameplate=np1)
m = yield c.next_non_ack()
self.assertEqual(m["type"], "released")
- np_row, side_rows = self._nameplate(app, "np1")
+ np_row, side_rows = self._nameplate(app, np1)
self.assertEqual(np_row, None)
c.close()
yield c.d
@@ -654,14 +655,14 @@
c = yield self.make_client()
yield c.next_non_ack()
c.send("bind", appid="appid", side="side")
- c.send("claim", nameplate="np2")
+ c.send("claim", nameplate=np2)
m = yield c.next_non_ack()
self.assertEqual(m["type"], "claimed")
- app.claim_nameplate("np2", "side2", 0)
- c.send("release", nameplate="np2")
+ app.claim_nameplate(np2, "side2", 0)
+ c.send("release", nameplate=np2)
m = yield c.next_non_ack()
self.assertEqual(m["type"], "released")
- np_row, side_rows = self._nameplate(app, "np2")
+ np_row, side_rows = self._nameplate(app, np2)
claims = sorted([(row["side"], row["claimed"]) for row in side_rows])
self.assertEqual(claims, [("side", 0), ("side2", 1)])
c.close()
@@ -670,12 +671,12 @@
c = yield self.make_client()
yield c.next_non_ack()
c.send("bind", appid="appid", side="side")
- c.send("claim", nameplate="np2") # new claim is forbidden
+ c.send("claim", nameplate=np2) # new claim is forbidden
err = yield c.next_non_ack()
self.assertEqual(err["type"], "error")
self.assertEqual(err["error"], "reclaimed")
- np_row, side_rows = self._nameplate(app, "np2")
+ np_row, side_rows = self._nameplate(app, np2)
claims = sorted([(row["side"], row["claimed"]) for row in side_rows])
self.assertEqual(claims, [("side", 0), ("side2", 1)])
c.close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_websocket.py
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_websocket.py
---
old/magic_wormhole_mailbox_server-0.6.0/src/wormhole_mailbox_server/test/test_websocket.py
1970-01-01 01:00:00.000000000 +0100
+++
new/magic_wormhole_mailbox_server-0.8.0/src/wormhole_mailbox_server/test/test_websocket.py
2026-05-15 19:14:29.000000000 +0200
@@ -0,0 +1,182 @@
+import json
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks
+from twisted.internet.address import IPv4Address
+from ..server_websocket import WebSocketServerFactory
+from autobahn.twisted.testing import create_pumper, create_memory_agent,
MemoryReactorClock
+from autobahn.twisted.websocket import WebSocketClientProtocol
+
+
+class FakeServer:
+ """
+ Fake enough of the internal 'Server' object to appease the
+ WebSocket server.
+
+ """
+ def get_welcome(self):
+ return {
+ "motd": "fake message of the day"
+ }
+
+ def get_log_requests(self):
+ return False
+
+
+class WebSocket(unittest.TestCase):
+ """
+ Details of the server WebSocket protocol
+ """
+
+ def setUp(self):
+ self.pumper = create_pumper()
+ self.reactor = MemoryReactorClock()
+ return self.pumper.start()
+
+ def tearDown(self):
+ return self.pumper.stop()
+
+ def create_server_protocol(self):
+ """
+ Used by the Agent to create the in-memory transport server-side
+ WebSocket protocol (we actually create the 'real' protocol
+ objects since that is what we're testing here.)
+ """
+ factory = WebSocketServerFactory(
+ "ws://localhost:4000/v1",
+ FakeServer(),
+ )
+ addr = IPv4Address("TCP", "localhost", 4000)
+ return factory.buildProtocol(addr)
+
+ @inlineCallbacks
+ def test_server_version_string(self):
+ """
+ Our server version string from Autobahn should make sense
+ """
+ server_header = None
+ agent = create_memory_agent(
+ self.reactor,
+ self.pumper,
+ self.create_server_protocol
+ )
+
+ class FakeClient(WebSocketClientProtocol):
+ def onConnect(self, cr):
+ nonlocal server_header
+ server_header = cr.headers.get("server")
+
+ proto = yield agent.open("ws://localhost:4000/v1", dict(), FakeClient)
+ proto.sendClose()
+ yield proto.is_closed
+
+ assert "Magic Wormhole" in server_header, "Incorrect Server: header
sent"
+
+ @inlineCallbacks
+ def test_reflected_address(self):
+ """
+ The Welcome message should include our address information
+ """
+ welcome = None
+ agent = create_memory_agent(
+ self.reactor,
+ self.pumper,
+ self.create_server_protocol
+ )
+
+ class FakeClient(WebSocketClientProtocol):
+ def onMessage(self, payload, isBinary):
+ js = json.loads(payload)
+ nonlocal welcome
+ if welcome is None:
+ welcome = js.get("welcome", None)
+ return super().onMessage(payload, isBinary)
+
+ proto = yield agent.open("ws://localhost:4000/v1", dict(), FakeClient)
+ proto.sendClose()
+ yield proto.is_closed
+
+ assert welcome is not None, "Failed to receive Welcome message"
+ ya = welcome.get("your-address", None)
+ assert ya, "Expected 'your-address' in Welcome message"
+ assert ya["port"] == 31337
+ assert "ipv4" in ya or "ipv6" in ya, "Expected either IPv4 or IPv6
address"
+
+ @inlineCallbacks
+ def test_reflected_caddy(self):
+ """
+ The Welcome message should include our address information
+ when sent via x-real-ip headers
+ """
+ welcome = None
+
+ headers = {
+ "x-real-ip": "127.1.2.3",
+ "x-real-port": "54321",
+ }
+
+ agent = create_memory_agent(
+ self.reactor,
+ self.pumper,
+ self.create_server_protocol,
+ )
+
+ class FakeClient(WebSocketClientProtocol):
+ def onMessage(self, payload, isBinary):
+ js = json.loads(payload)
+ nonlocal welcome
+ if welcome is None:
+ welcome = js.get("welcome", None)
+ return super().onMessage(payload, isBinary)
+
+ proto = yield agent.open("ws://localhost:4000/v1", {"headers":
headers}, FakeClient)
+ proto.sendClose()
+ yield proto.is_closed
+
+ assert welcome is not None, "Failed to receive Welcome message"
+ self.assertEqual(
+ welcome["your-address"],
+ {
+ "ipv4": "127.1.2.3",
+ "port": 54321,
+ }
+ )
+
+ @inlineCallbacks
+ def test_reflected_caddyv6(self):
+ """
+ The Welcome message should include our address information
+ when sent via x-real-ip headers (IPv6 version)
+ """
+ welcome = None
+
+ headers = {
+ "x-real-ip": "::1",
+ "x-real-port": "54321",
+ }
+
+ agent = create_memory_agent(
+ self.reactor,
+ self.pumper,
+ self.create_server_protocol,
+ )
+
+ class FakeClient(WebSocketClientProtocol):
+ def onMessage(self, payload, isBinary):
+ js = json.loads(payload)
+ nonlocal welcome
+ if welcome is None:
+ welcome = js.get("welcome", None)
+ return super().onMessage(payload, isBinary)
+
+ proto = yield agent.open("ws://localhost:4000/v1", {"headers":
headers}, FakeClient)
+ proto.sendClose()
+ yield proto.is_closed
+
+ assert welcome is not None, "Failed to receive Welcome message"
+ self.assertEqual(
+ welcome["your-address"],
+ {
+ "ipv6": "::1",
+ "port": 54321,
+ }
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/magic_wormhole_mailbox_server-0.6.0/tox.ini
new/magic_wormhole_mailbox_server-0.8.0/tox.ini
--- old/magic_wormhole_mailbox_server-0.6.0/tox.ini 2025-10-08
18:43:07.000000000 +0200
+++ new/magic_wormhole_mailbox_server-0.8.0/tox.ini 2026-05-14
21:00:08.000000000 +0200
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
-envlist = {py37,py38,py39,py310,py311,pypy}
+envlist = {py310,py311,py312,py313,py314,pypy}
skip_missing_interpreters = True
minversion = 2.4.0