switch to new API

This renames all the existing API methods, to use a consistent
"d=get_XYZ()" (for Deferred mode) or "dg.wormhole_got_XYZ()" (for Delegated
mode). It updates cmd_send/cmd_receive/cmd_ssh to use the new API.

Since we now have get_welcome(), apps handle the Welcome message with a
Deferred callback instead of registering a "welcome handler". This lets us
make sure we've finished printing any server message-of-the-day or "you
should update your client" message to stdout before using stdio to ask for
the wormhole code. (Previously, the code-input prompt was overwritten by the
server message, and it was ugly). refs #145. This approach adds an extra
roundtrip to the receiver, but we can fix that (see #145 for details).

Because of that change, the server-is-being-slow message is printed at a
slightly different time, so those tests needed some extra work to exercise it
properly.
This commit is contained in:
Brian Warner 2017-05-12 13:12:36 -07:00
parent 2312f2ccce
commit 7955a36bfd
9 changed files with 264 additions and 269 deletions

View File

@ -33,7 +33,6 @@ class Boss(object):
_url = attrib(validator=instance_of(type(u"")))
_appid = attrib(validator=instance_of(type(u"")))
_versions = attrib(validator=instance_of(dict))
_welcome_handler = attrib() # TODO: validator: callable
_reactor = attrib()
_journal = attrib(validator=provides(_interfaces.IJournal))
_tor_manager = attrib() # TODO: ITorManager or None
@ -185,11 +184,11 @@ class Boss(object):
raise WelcomeError(welcome["error"])
# TODO: it'd be nice to not call the handler when we're in
# S3_closing or S4_closed states. I tried to implement this with
# rx_Welcome as an @input, but in the error case I'd be
# rx_welcome as an @input, but in the error case I'd be
# delivering a new input (rx_error or something) while in the
# middle of processing the rx_welcome input, and I wasn't sure
# Automat would handle that correctly.
self._welcome_handler(welcome) # can raise WelcomeError too
self._W.got_welcome(welcome) # TODO: let this raise WelcomeError?
except WelcomeError as welcome_error:
self.rx_unwelcome(welcome_error)
@m.input()
@ -244,7 +243,7 @@ class Boss(object):
self._their_versions = bytes_to_dict(plaintext)
# but this part is app-to-app
app_versions = self._their_versions.get("app_versions", {})
self._W.got_version(app_versions)
self._W.got_versions(app_versions)
@m.output()
def S_send(self, plaintext):

View File

@ -10,7 +10,7 @@ from ..transit import TransitReceiver
from ..errors import TransferError, WormholeClosedError, NoTorError
from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr,
estimate_free_space)
from .welcome import CLIWelcomeHandler
from .welcome import handle_welcome
APPID = u"lothar.com/wormhole/text-or-file-xfer"
@ -68,13 +68,10 @@ class Receiver:
# with the user handing off the wormhole code
yield self._tor_manager.start()
wh = CLIWelcomeHandler(self.args.relay_url, __version__,
self.args.stderr)
w = create(self.args.appid or APPID, self.args.relay_url,
self._reactor,
tor_manager=self._tor_manager,
timing=self.args.timing,
welcome_handler=wh.handle_welcome)
timing=self.args.timing)
self._w = w # so tests can wait on events too
# I wanted to do this instead:
@ -112,6 +109,10 @@ class Receiver:
@inlineCallbacks
def _go(self, w):
welcome = yield w.get_welcome()
handle_welcome(welcome, self.args.relay_url, __version__,
self.args.stderr)
yield self._handle_code(w)
def on_slow_key():
@ -125,7 +126,7 @@ class Receiver:
# of that possibility, it's probably not appropriate to give up
# automatically after some timeout. The user can express their
# impatience by quitting the program with control-C.
yield w.when_key()
yield w.get_unverified_key()
finally:
if not notify.called:
notify.cancel()
@ -147,7 +148,7 @@ class Receiver:
# It would be reasonable to give up after waiting here for too
# long.
verifier_bytes = yield w.when_verified()
verifier_bytes = yield w.get_verifier()
finally:
if not notify.called:
notify.cancel()
@ -183,12 +184,12 @@ class Receiver:
def _send_data(self, data, w):
data_bytes = dict_to_bytes(data)
w.send(data_bytes)
w.send_message(data_bytes)
@inlineCallbacks
def _get_data(self, w):
# this may raise WrongPasswordError
them_bytes = yield w.when_received()
them_bytes = yield w.get_message()
them_d = bytes_to_dict(them_bytes)
if "error" in them_d:
raise TransferError(them_d["error"])
@ -210,7 +211,7 @@ class Receiver:
if not used_completion:
print(" (note: you can use <Tab> to complete words)",
file=self.args.stderr)
yield w.when_code()
yield w.get_code()
def _show_verifier(self, verifier_bytes):
verifier_hex = bytes_to_hexstr(verifier_bytes)

View File

@ -10,7 +10,7 @@ from ..errors import TransferError, WormholeClosedError, NoTorError
from wormhole import create, __version__
from ..transit import TransitSender
from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr
from .welcome import CLIWelcomeHandler
from .welcome import handle_welcome
APPID = u"lothar.com/wormhole/text-or-file-xfer"
VERIFY_TIMER = 1
@ -53,13 +53,10 @@ class Sender:
# with the user handing off the wormhole code
yield self._tor_manager.start()
wh = CLIWelcomeHandler(self._args.relay_url, __version__,
self._args.stderr)
w = create(self._args.appid or APPID, self._args.relay_url,
self._reactor,
tor_manager=self._tor_manager,
timing=self._timing,
welcome_handler=wh.handle_welcome)
timing=self._timing)
d = self._go(w)
# if we succeed, we should close and return the w.close results
@ -85,10 +82,14 @@ class Sender:
def _send_data(self, data, w):
data_bytes = dict_to_bytes(data)
w.send(data_bytes)
w.send_message(data_bytes)
@inlineCallbacks
def _go(self, w):
welcome = yield w.get_welcome()
handle_welcome(welcome, self._args.relay_url, __version__,
self._args.stderr)
# TODO: run the blocking zip-the-directory IO in a thread, let the
# wormhole exchange happen in parallel
offer, self._fd_to_send = self._build_offer()
@ -110,21 +111,21 @@ class Sender:
else:
w.allocate_code(args.code_length)
code = yield w.when_code()
code = yield w.get_code()
if not args.zeromode:
print(u"Wormhole code is: %s" % code, file=args.stderr)
# flush stderr so the code is displayed immediately
args.stderr.flush()
print(u"", file=args.stderr)
# We don't print a "waiting" message for when_key() here, even though
# we do that in cmd_receive.py, because it's not at all surprising to
# we waiting here for a long time. We'll sit in when_key() until the
# receiver has typed in the code and their PAKE message makes it to
# us.
yield w.when_key()
# We don't print a "waiting" message for get_unverified_key() here,
# even though we do that in cmd_receive.py, because it's not at all
# surprising to we waiting here for a long time. We'll sit in
# get_unverified_key() until the receiver has typed in the code and
# their PAKE message makes it to us.
yield w.get_unverified_key()
# TODO: don't stall on w.verify() unless they want it
# TODO: don't stall on w.get_verifier() unless they want it
def on_slow_connection():
print(u"Key established, waiting for confirmation...",
file=args.stderr)
@ -132,13 +133,13 @@ class Sender:
try:
# The usual sender-chooses-code sequence means the receiver's
# PAKE should be followed immediately by their VERSION, so
# w.when_verified() should fire right away. However if we're
# w.get_verifier() should fire right away. However if we're
# using the offline-codes sequence, and the receiver typed in
# their code first, and then they went offline, we might be
# sitting here for a while, so printing the "waiting" message
# seems like a good idea. It might even be appropriate to give up
# after a while.
verifier_bytes = yield w.when_verified() # might WrongPasswordError
verifier_bytes = yield w.get_verifier() # might WrongPasswordError
finally:
if not notify.called:
notify.cancel()
@ -162,7 +163,7 @@ class Sender:
}
self._send_data({u"transit": sender_transit}, w)
# TODO: move this down below w.get()
# TODO: move this down below w.get_message()
transit_key = w.derive_key(APPID+"/transit-key",
ts.TRANSIT_KEY_LENGTH)
ts.set_transit_key(transit_key)
@ -174,13 +175,13 @@ class Sender:
while True:
try:
them_d_bytes = yield w.when_received()
them_d_bytes = yield w.get_message()
except WormholeClosedError:
if done:
returnValue(None)
raise TransferError("unexpected close")
# TODO: when_received() fired, so now it's safe to use
# w.derive_key()
# TODO: get_message() fired, so get_verifier must have fired, so
# now it's safe to use w.derive_key()
them_d = bytes_to_dict(them_d_bytes)
#print("GOT", them_d)
recognized = False
@ -209,7 +210,7 @@ class Sender:
if ok.lower() == "no":
err = "sender rejected verification check, abandoned transfer"
reject_data = dict_to_bytes({"error": err})
w.send(reject_data)
w.send_message(reject_data)
raise TransferError(err)
def _handle_transit(self, receiver_transit):

View File

@ -1,24 +1,18 @@
from __future__ import print_function, absolute_import, unicode_literals
import sys
from ..wormhole import _WelcomeHandler
class CLIWelcomeHandler(_WelcomeHandler):
def __init__(self, url, cli_version, stderr=sys.stderr):
_WelcomeHandler.__init__(self, url, stderr)
self._current_version = cli_version
self._version_warning_displayed = False
def handle_welcome(self, welcome):
# Only warn if we're running a release version (e.g. 0.0.6, not
# 0.0.6+DISTANCE.gHASH). Only warn once.
if ("current_cli_version" in welcome
and "+" not in self._current_version
and not self._version_warning_displayed
and welcome["current_cli_version"] != self._current_version):
print("Warning: errors may occur unless both sides are running the same version", file=self.stderr)
print("Server claims %s is current, but ours is %s"
% (welcome["current_cli_version"], self._current_version),
file=self.stderr)
self._version_warning_displayed = True
_WelcomeHandler.handle_welcome(self, welcome)
def handle_welcome(welcome, relay_url, my_version, stderr):
if "motd" in welcome:
motd_lines = welcome["motd"].splitlines()
motd_formatted = "\n ".join(motd_lines)
print("Server (at %s) says:\n %s" % (relay_url, motd_formatted),
file=stderr)
# Only warn if we're running a release version (e.g. 0.0.6, not
# 0.0.6+DISTANCE.gHASH). Only warn once.
if ("current_cli_version" in welcome
and "+" not in my_version
and welcome["current_cli_version"] != my_version):
print("Warning: errors may occur unless both sides are running the same version", file=stderr)
print("Server claims %s is current, but ours is %s"
% (welcome["current_cli_version"], my_version),
file=stderr)

View File

@ -433,9 +433,16 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
receive_d = cmd_receive.receive(recv_cfg)
else:
KEY_TIMER = 0 if mode == "slow-sender-text" else 1.0
rxw = []
with mock.patch.object(cmd_receive, "KEY_TIMER", KEY_TIMER):
send_d = cmd_send.send(send_cfg)
receive_d = cmd_receive.receive(recv_cfg)
receive_d = cmd_receive.receive(recv_cfg,
_debug_stash_wormhole=rxw)
# we need to keep KEY_TIMER patched until the receiver
# gets far enough to start the timer, which happens after
# the code is set
if mode == "slow-sender-text":
yield rxw[0].get_unverified_key()
# The sender might fail, leaving the receiver hanging, or vice
# versa. Make sure we don't wait on one side exclusively
@ -832,26 +839,26 @@ class NotWelcome(ServerBase, unittest.TestCase):
class Cleanup(ServerBase, unittest.TestCase):
def setUp(self):
d = super(Cleanup, self).setUp()
self.cfg = cfg = config("send")
def make_config(self):
cfg = config("send")
# common options for all tests in this suite
cfg.hide_progress = True
cfg.relay_url = self.relayurl
cfg.transit_helper = ""
cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO()
return d
return cfg
@inlineCallbacks
@mock.patch('sys.stdout')
def test_text(self, stdout):
# the rendezvous channel should be deleted after success
self.cfg.text = "hello"
self.cfg.code = "1-abc"
cfg = self.make_config()
cfg.text = "hello"
cfg.code = "1-abc"
send_d = cmd_send.send(self.cfg)
receive_d = cmd_receive.receive(self.cfg)
send_d = cmd_send.send(cfg)
receive_d = cmd_receive.receive(cfg)
yield send_d
yield receive_d
@ -863,12 +870,14 @@ class Cleanup(ServerBase, unittest.TestCase):
def test_text_wrong_password(self):
# if the password was wrong, the rendezvous channel should still be
# deleted
self.cfg.text = "secret message"
self.cfg.code = "1-abc"
send_d = cmd_send.send(self.cfg)
send_cfg = self.make_config()
send_cfg.text = "secret message"
send_cfg.code = "1-abc"
send_d = cmd_send.send(send_cfg)
self.cfg.code = "1-WRONG"
receive_d = cmd_receive.receive(self.cfg)
rx_cfg = self.make_config()
rx_cfg.code = "1-WRONG"
receive_d = cmd_receive.receive(rx_cfg)
# both sides should be capable of detecting the mismatch
yield self.assertFailure(send_d, WrongPasswordError)
@ -951,12 +960,9 @@ class AppID(ServerBase, unittest.TestCase):
self.assertEqual(used[0]["app_id"], u"appid2")
class Welcome(unittest.TestCase):
def do(self, welcome_message, my_version="2.0", twice=False):
def do(self, welcome_message, my_version="2.0"):
stderr = io.StringIO()
h = welcome.CLIWelcomeHandler("url", my_version, stderr)
h.handle_welcome(welcome_message)
if twice:
h.handle_welcome(welcome_message)
welcome.handle_welcome(welcome_message, "url", my_version, stderr)
return stderr.getvalue()
def test_empty(self):
@ -973,15 +979,6 @@ class Welcome(unittest.TestCase):
"Server claims 3.0 is current, but ours is 2.0\n")
self.assertEqual(stderr, expected)
def test_version_old_twice(self):
stderr = self.do({"current_cli_version": "3.0"}, twice=True)
# the handler should only emit the version warning once, even if we
# get multiple Welcome messages (which could happen if we lose the
# connection and then reconnect)
expected = ("Warning: errors may occur unless both sides are running the same version\n" +
"Server claims 3.0 is current, but ours is 2.0\n")
self.assertEqual(stderr, expected)
def test_version_unreleased(self):
stderr = self.do({"current_cli_version": "3.0"},
my_version="2.5+middle.something")

View File

@ -1152,15 +1152,15 @@ class Boss(unittest.TestCase):
def build(self):
events = []
wormhole = Dummy("w", events, None,
"got_code", "got_key", "got_verifier", "got_version",
"got_welcome",
"got_code", "got_key", "got_verifier", "got_versions",
"received", "closed")
self._welcome_handler = mock.Mock()
versions = {"app": "version1"}
reactor = None
journal = ImmediateJournal()
tor_manager = None
b = MockBoss(wormhole, "side", "url", "appid", versions,
self._welcome_handler, reactor, journal, tor_manager,
reactor, journal, tor_manager,
timing.DebugTiming())
b._T = Dummy("t", events, ITerminator, "close")
b._S = Dummy("s", events, ISend, "send")
@ -1179,8 +1179,11 @@ class Boss(unittest.TestCase):
self.assertEqual(events, [("w.got_code", "1-code")])
events[:] = []
b.rx_welcome("welcome")
self.assertEqual(self._welcome_handler.mock_calls, [mock.call("welcome")])
welcome = {"howdy": "how are ya"}
b.rx_welcome(welcome)
self.assertEqual(events, [("w.got_welcome", welcome),
])
events[:] = []
# pretend a peer message was correctly decrypted
b.got_key(b"key")
@ -1190,7 +1193,7 @@ class Boss(unittest.TestCase):
b.got_message("0", b"msg1")
self.assertEqual(events, [("w.got_key", b"key"),
("w.got_verifier", b"verifier"),
("w.got_version", {}),
("w.got_versions", {}),
("w.received", b"msg1"),
])
events[:] = []
@ -1206,6 +1209,12 @@ class Boss(unittest.TestCase):
b.closed()
self.assertEqual(events, [("w.closed", "happy")])
def test_unwelcome(self):
b, events = self.build()
unwelcome = {"error": "go away"}
b.rx_welcome(unwelcome)
self.assertEqual(events, [("t.close", "unwelcome")])
def test_lonely(self):
b, events = self.build()
b.set_code("1-code")

View File

@ -12,23 +12,6 @@ from ..errors import (WrongPasswordError,
APPID = "appid"
class Welcome(unittest.TestCase):
def test_tolerate_no_current_version(self):
w = wormhole._WelcomeHandler("relay_url")
w.handle_welcome({})
def test_print_motd(self):
stderr = io.StringIO()
w = wormhole._WelcomeHandler("relay_url", stderr=stderr)
w.handle_welcome({"motd": "message of\nthe day"})
self.assertEqual(stderr.getvalue(),
"Server (at relay_url) says:\n message of\n the day\n")
# motd can be displayed multiple times
w.handle_welcome({"motd": "second message"})
self.assertEqual(stderr.getvalue(),
("Server (at relay_url) says:\n message of\n the day\n"
"Server (at relay_url) says:\n second message\n"))
# event orderings to exercise:
#
# * normal sender: set_code, send_phase1, connected, claimed, learn_msg2,
@ -42,21 +25,24 @@ class Welcome(unittest.TestCase):
class Delegate:
def __init__(self):
self.welcome = None
self.code = None
self.key = None
self.verifier = None
self.version = None
self.versions = None
self.messages = []
self.closed = None
def wormhole_code(self, code):
def wormhole_got_welcome(self, welcome):
self.welcome = welcome
def wormhole_got_code(self, code):
self.code = code
def wormhole_key(self, key):
def wormhole_got_unverified_key(self, key):
self.key = key
def wormhole_verified(self, verifier):
def wormhole_got_verifier(self, verifier):
self.verifier = verifier
def wormhole_version(self, version):
self.version = version
def wormhole_received(self, data):
def wormhole_got_versions(self, versions):
self.versions = versions
def wormhole_got_message(self, data):
self.messages.append(data)
def wormhole_closed(self, result):
self.closed = result
@ -76,12 +62,12 @@ class Delegated(ServerBase, unittest.TestCase):
w2.set_code(dg.code)
yield poll_until(lambda: dg.key is not None)
yield poll_until(lambda: dg.verifier is not None)
yield poll_until(lambda: dg.version is not None)
yield poll_until(lambda: dg.versions is not None)
w1.send(b"ping")
got = yield w2.when_received()
w1.send_message(b"ping")
got = yield w2.get_message()
self.assertEqual(got, b"ping")
w2.send(b"pong")
w2.send_message(b"pong")
yield poll_until(lambda: dg.messages)
self.assertEqual(dg.messages[0], b"pong")
@ -124,7 +110,7 @@ class Wormholes(ServerBase, unittest.TestCase):
def test_allocate_default(self):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
mo = re.search(r"^\d+-\w+-\w+$", code)
self.assert_(mo, code)
# w.close() fails because we closed before connecting
@ -134,7 +120,7 @@ class Wormholes(ServerBase, unittest.TestCase):
def test_allocate_more_words(self):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code(3)
code = yield w1.when_code()
code = yield w1.get_code()
mo = re.search(r"^\d+-\w+-\w+-\w+$", code)
self.assert_(mo, code)
yield self.assertFailure(w1.close(), LonelyError)
@ -149,11 +135,11 @@ class Wormholes(ServerBase, unittest.TestCase):
w2 = wormhole.create(APPID, self.relayurl, reactor)
#w2.debug_set_trace(" W2")
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code)
yield w1.when_key()
yield w2.when_key()
yield w1.get_unverified_key()
yield w2.get_unverified_key()
key1 = w1.derive_key("purpose", 16)
self.assertEqual(len(key1), 16)
@ -163,29 +149,29 @@ class Wormholes(ServerBase, unittest.TestCase):
with self.assertRaises(TypeError):
w1.derive_key(12345, 16)
verifier1 = yield w1.when_verified()
verifier2 = yield w2.when_verified()
verifier1 = yield w1.get_verifier()
verifier2 = yield w2.get_verifier()
self.assertEqual(verifier1, verifier2)
self.successResultOf(w1.when_key())
self.successResultOf(w2.when_key())
self.successResultOf(w1.get_unverified_key())
self.successResultOf(w2.get_unverified_key())
version1 = yield w1.when_version()
version2 = yield w2.when_version()
versions1 = yield w1.get_versions()
versions2 = yield w2.get_versions()
# app-versions are exercised properly in test_versions, this just
# tests the defaults
self.assertEqual(version1, {})
self.assertEqual(version2, {})
self.assertEqual(versions1, {})
self.assertEqual(versions2, {})
w1.send(b"data1")
w2.send(b"data2")
dataX = yield w1.when_received()
dataY = yield w2.when_received()
w1.send_message(b"data1")
w2.send_message(b"data2")
dataX = yield w1.get_message()
dataY = yield w2.get_message()
self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1")
version1_again = yield w1.when_version()
self.assertEqual(version1, version1_again)
versions1_again = yield w1.get_versions()
self.assertEqual(versions1, versions1_again)
c1 = yield w1.close()
self.assertEqual(c1, "happy")
@ -193,19 +179,19 @@ class Wormholes(ServerBase, unittest.TestCase):
self.assertEqual(c2, "happy")
@inlineCallbacks
def test_when_code_early(self):
def test_get_code_early(self):
w1 = wormhole.create(APPID, self.relayurl, reactor)
d = w1.when_code()
d = w1.get_code()
w1.set_code("1-abc")
code = self.successResultOf(d)
self.assertEqual(code, "1-abc")
yield self.assertFailure(w1.close(), LonelyError)
@inlineCallbacks
def test_when_code_late(self):
def test_get_code_late(self):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("1-abc")
d = w1.when_code()
d = w1.get_code()
code = self.successResultOf(d)
self.assertEqual(code, "1-abc")
yield self.assertFailure(w1.close(), LonelyError)
@ -218,12 +204,12 @@ class Wormholes(ServerBase, unittest.TestCase):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code)
w1.send(b"data")
w2.send(b"data")
dataX = yield w1.when_received()
dataY = yield w2.when_received()
w1.send_message(b"data")
w2.send_message(b"data")
dataX = yield w1.get_message()
dataY = yield w2.get_message()
self.assertEqual(dataX, b"data")
self.assertEqual(dataY, b"data")
yield w1.close()
@ -234,13 +220,13 @@ class Wormholes(ServerBase, unittest.TestCase):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code)
w1.send(b"data1")
dataY = yield w2.when_received()
w1.send_message(b"data1")
dataY = yield w2.get_message()
self.assertEqual(dataY, b"data1")
d = w1.when_received()
w2.send(b"data2")
d = w1.get_message()
w2.send_message(b"data2")
dataX = yield d
self.assertEqual(dataX, b"data2")
yield w1.close()
@ -251,10 +237,10 @@ class Wormholes(ServerBase, unittest.TestCase):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code)
w1.send(b"data1")
dataY = yield w2.when_received()
w1.send_message(b"data1")
dataY = yield w2.get_message()
self.assertEqual(dataY, b"data1")
yield w1.close()
yield w2.close()
@ -262,9 +248,9 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks
def test_early(self):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w1.send(b"data1")
w1.send_message(b"data1")
w2 = wormhole.create(APPID, self.relayurl, reactor)
d = w2.when_received()
d = w2.get_message()
w1.set_code("123-abc-def")
w2.set_code("123-abc-def")
dataY = yield d
@ -278,8 +264,8 @@ class Wormholes(ServerBase, unittest.TestCase):
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant")
w1.send(b"data1"), w2.send(b"data2")
dl = yield self.doBoth(w1.when_received(), w2.when_received())
w1.send_message(b"data1"), w2.send_message(b"data2")
dl = yield self.doBoth(w1.get_message(), w2.get_message())
(dataX, dataY) = dl
self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1")
@ -300,8 +286,8 @@ class Wormholes(ServerBase, unittest.TestCase):
yield poll_until(lambda: w2._boss._K._debug_pake_stashed)
h.choose_words("purple-elephant")
w1.send(b"data1"), w2.send(b"data2")
dl = yield self.doBoth(w1.when_received(), w2.when_received())
w1.send_message(b"data1"), w2.send_message(b"data2")
dl = yield self.doBoth(w1.get_message(), w2.get_message())
(dataX, dataY) = dl
self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1")
@ -315,13 +301,13 @@ class Wormholes(ServerBase, unittest.TestCase):
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant")
w1.send(b"data1"), w2.send(b"data2")
w1.send(b"data3"), w2.send(b"data4")
dl = yield self.doBoth(w1.when_received(), w2.when_received())
w1.send_message(b"data1"), w2.send_message(b"data2")
w1.send_message(b"data3"), w2.send_message(b"data4")
dl = yield self.doBoth(w1.get_message(), w2.get_message())
(dataX, dataY) = dl
self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1")
dl = yield self.doBoth(w1.when_received(), w2.when_received())
dl = yield self.doBoth(w1.get_message(), w2.get_message())
(dataX, dataY) = dl
self.assertEqual(dataX, b"data4")
self.assertEqual(dataY, b"data3")
@ -337,19 +323,19 @@ class Wormholes(ServerBase, unittest.TestCase):
w2.set_code("123-foo")
# let it connect and become HAPPY
yield w1.when_version()
yield w2.when_version()
yield w1.get_versions()
yield w2.get_versions()
yield w1.close()
yield w2.close()
# once closed, all Deferred-yielding API calls get an immediate error
f = self.failureResultOf(w1.when_code(), WormholeClosed)
f = self.failureResultOf(w1.get_code(), WormholeClosed)
self.assertEqual(f.value.args[0], "happy")
self.failureResultOf(w1.when_key(), WormholeClosed)
self.failureResultOf(w1.when_verified(), WormholeClosed)
self.failureResultOf(w1.when_version(), WormholeClosed)
self.failureResultOf(w1.when_received(), WormholeClosed)
self.failureResultOf(w1.get_unverified_key(), WormholeClosed)
self.failureResultOf(w1.get_verifier(), WormholeClosed)
self.failureResultOf(w1.get_versions(), WormholeClosed)
self.failureResultOf(w1.get_message(), WormholeClosed)
@inlineCallbacks
@ -357,20 +343,20 @@ class Wormholes(ServerBase, unittest.TestCase):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code+"not")
code2 = yield w2.when_code()
code2 = yield w2.get_code()
self.assertNotEqual(code, code2)
# That's enough to allow both sides to discover the mismatch, but
# only after the confirmation message gets through. API calls that
# don't wait will appear to work until the mismatched confirmation
# message arrives.
w1.send(b"should still work")
w2.send(b"should still work")
w1.send_message(b"should still work")
w2.send_message(b"should still work")
key2 = yield w2.when_key() # should work
key2 = yield w2.get_unverified_key() # should work
# w2 has just received w1.PAKE, and is about to send w2.VERSION
key1 = yield w1.when_key() # should work
key1 = yield w1.get_unverified_key() # should work
# w1 has just received w2.PAKE, and is about to send w1.VERSION, and
# then will receive w2.VERSION. When it sees w2.VERSION, it will
# learn about the WrongPasswordError.
@ -378,54 +364,54 @@ class Wormholes(ServerBase, unittest.TestCase):
# API calls that wait (i.e. get) will errback. We collect all these
# Deferreds early to exercise the wait-then-fail path
d1_verified = w1.when_verified()
d1_version = w1.when_version()
d1_received = w1.when_received()
d2_verified = w2.when_verified()
d2_version = w2.when_version()
d2_received = w2.when_received()
d1_verified = w1.get_verifier()
d1_versions = w1.get_versions()
d1_received = w1.get_message()
d2_verified = w2.get_verifier()
d2_versions = w2.get_versions()
d2_received = w2.get_message()
# wait for each side to notice the failure
yield self.assertFailure(w1.when_verified(), WrongPasswordError)
yield self.assertFailure(w2.when_verified(), WrongPasswordError)
yield self.assertFailure(w1.get_verifier(), WrongPasswordError)
yield self.assertFailure(w2.get_verifier(), WrongPasswordError)
# and then wait for the rest of the loops to fire. if we had+used
# eventual-send, this wouldn't be a problem
yield pause_one_tick()
# now all the rest should have fired already
self.failureResultOf(d1_verified, WrongPasswordError)
self.failureResultOf(d1_version, WrongPasswordError)
self.failureResultOf(d1_versions, WrongPasswordError)
self.failureResultOf(d1_received, WrongPasswordError)
self.failureResultOf(d2_verified, WrongPasswordError)
self.failureResultOf(d2_version, WrongPasswordError)
self.failureResultOf(d2_versions, WrongPasswordError)
self.failureResultOf(d2_received, WrongPasswordError)
# and at this point, with the failure safely noticed by both sides,
# new when_key() calls should signal the failure, even before we
# close
# new get_unverified_key() calls should signal the failure, even
# before we close
# any new calls in the error state should immediately fail
self.failureResultOf(w1.when_key(), WrongPasswordError)
self.failureResultOf(w1.when_verified(), WrongPasswordError)
self.failureResultOf(w1.when_version(), WrongPasswordError)
self.failureResultOf(w1.when_received(), WrongPasswordError)
self.failureResultOf(w2.when_key(), WrongPasswordError)
self.failureResultOf(w2.when_verified(), WrongPasswordError)
self.failureResultOf(w2.when_version(), WrongPasswordError)
self.failureResultOf(w2.when_received(), WrongPasswordError)
self.failureResultOf(w1.get_unverified_key(), WrongPasswordError)
self.failureResultOf(w1.get_verifier(), WrongPasswordError)
self.failureResultOf(w1.get_versions(), WrongPasswordError)
self.failureResultOf(w1.get_message(), WrongPasswordError)
self.failureResultOf(w2.get_unverified_key(), WrongPasswordError)
self.failureResultOf(w2.get_verifier(), WrongPasswordError)
self.failureResultOf(w2.get_versions(), WrongPasswordError)
self.failureResultOf(w2.get_message(), WrongPasswordError)
yield self.assertFailure(w1.close(), WrongPasswordError)
yield self.assertFailure(w2.close(), WrongPasswordError)
# API calls should still get the error, not WormholeClosed
self.failureResultOf(w1.when_key(), WrongPasswordError)
self.failureResultOf(w1.when_verified(), WrongPasswordError)
self.failureResultOf(w1.when_version(), WrongPasswordError)
self.failureResultOf(w1.when_received(), WrongPasswordError)
self.failureResultOf(w2.when_key(), WrongPasswordError)
self.failureResultOf(w2.when_verified(), WrongPasswordError)
self.failureResultOf(w2.when_version(), WrongPasswordError)
self.failureResultOf(w2.when_received(), WrongPasswordError)
self.failureResultOf(w1.get_unverified_key(), WrongPasswordError)
self.failureResultOf(w1.get_verifier(), WrongPasswordError)
self.failureResultOf(w1.get_versions(), WrongPasswordError)
self.failureResultOf(w1.get_message(), WrongPasswordError)
self.failureResultOf(w2.get_unverified_key(), WrongPasswordError)
self.failureResultOf(w2.get_verifier(), WrongPasswordError)
self.failureResultOf(w2.get_versions(), WrongPasswordError)
self.failureResultOf(w2.get_message(), WrongPasswordError)
@inlineCallbacks
def test_wrong_password_with_spaces(self):
@ -442,21 +428,21 @@ class Wormholes(ServerBase, unittest.TestCase):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code)
v1 = yield w1.when_verified() # early
v2 = yield w2.when_verified()
v1 = yield w1.get_verifier() # early
v2 = yield w2.get_verifier()
self.failUnlessEqual(type(v1), type(b""))
self.failUnlessEqual(v1, v2)
w1.send(b"data1")
w2.send(b"data2")
dataX = yield w1.when_received()
dataY = yield w2.when_received()
w1.send_message(b"data1")
w2.send_message(b"data2")
dataX = yield w1.get_message()
dataY = yield w2.get_message()
self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1")
# calling when_verified() this late should fire right away
v1_late = self.successResultOf(w2.when_verified())
# calling get_verifier() this late should fire right away
v1_late = self.successResultOf(w2.get_verifier())
self.assertEqual(v1_late, v1)
yield w1.close()
@ -470,11 +456,11 @@ class Wormholes(ServerBase, unittest.TestCase):
w2 = wormhole.create(APPID, self.relayurl, reactor,
versions={"w2": 456})
w1.allocate_code()
code = yield w1.when_code()
code = yield w1.get_code()
w2.set_code(code)
w1_versions = yield w2.when_version()
w1_versions = yield w2.get_versions()
self.assertEqual(w1_versions, {"w1": 123})
w2_versions = yield w1.when_version()
w2_versions = yield w1.get_versions()
self.assertEqual(w2_versions, {"w2": 456})
yield w1.close()
yield w2.close()
@ -496,8 +482,8 @@ class Wormholes(ServerBase, unittest.TestCase):
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant")
w1.send(b"data1"), w2.send(b"data2")
dl = yield self.doBoth(w1.when_received(), w2.when_received())
w1.send_message(b"data1"), w2.send_message(b"data2")
dl = yield self.doBoth(w1.get_message(), w2.get_message())
(dataX, dataY) = dl
self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1")
@ -537,7 +523,7 @@ class Errors(ServerBase, unittest.TestCase):
def test_allocate_and_set_code(self):
w = wormhole.create(APPID, self.relayurl, reactor)
w.allocate_code()
yield w.when_code()
yield w.get_code()
with self.assertRaises(OnlyOneCodeError):
w.set_code("123-nope")
yield self.assertFailure(w.close(), LonelyError)
@ -550,8 +536,8 @@ class Reconnection(ServerBase, unittest.TestCase):
w1._boss._RC._debug_record_inbound_f = w1_in.append
#w1.debug_set_trace("W1")
w1.allocate_code()
code = yield w1.when_code()
w1.send(b"data1") # will be queued until wormhole is established
code = yield w1.get_code()
w1.send_message(b"data1") # queued until wormhole is established
# now wait until we've deposited all our messages on the server
def seen_our_pake():
@ -577,11 +563,11 @@ class Reconnection(ServerBase, unittest.TestCase):
#w2.debug_set_trace(" W2")
w2.set_code(code)
dataY = yield w2.when_received()
dataY = yield w2.get_message()
self.assertEqual(dataY, b"data1")
w2.send(b"data2")
dataX = yield w1.when_received()
w2.send_message(b"data2")
dataX = yield w1.get_message()
self.assertEqual(dataX, b"data2")
c1 = yield w1.close()

View File

@ -15,18 +15,18 @@ from .util import to_bytes
# We can provide different APIs to different apps:
# * Deferreds
# w.when_code().addCallback(print_code)
# w.send(data)
# w.when_received().addCallback(got_data)
# w.get_code().addCallback(print_code)
# w.send_message(data)
# w.get_message().addCallback(got_data)
# w.close().addCallback(closed)
# * delegate callbacks (better for journaled environments)
# w = wormhole(delegate=app)
# w.send(data)
# w.send_message(data)
# app.wormhole_got_code(code)
# app.wormhole_got_verifier(verifier)
# app.wormhole_got_version(versions)
# app.wormhole_receive(data)
# app.wormhole_got_versions(versions)
# app.wormhole_got_message(data)
# w.close()
# app.wormhole_closed()
#
@ -34,18 +34,6 @@ from .util import to_bytes
# wormhole(delegate=app, delegate_prefix="wormhole_",
# delegate_args=(args, kwargs))
class _WelcomeHandler:
def __init__(self, url, stderr=sys.stderr):
self.relay_url = url
self.stderr = stderr
def handle_welcome(self, welcome):
if "motd" in welcome:
motd_lines = welcome["motd"].splitlines()
motd_formatted = "\n ".join(motd_lines)
print("Server (at %s) says:\n %s" %
(self.relay_url, motd_formatted), file=self.stderr)
@attrs
@implementer(IWormhole)
class _DelegatedWormhole(object):
@ -72,7 +60,7 @@ class _DelegatedWormhole(object):
## }
## return s
def send(self, plaintext):
def send_message(self, plaintext):
self._boss.send(plaintext)
def derive_key(self, purpose, length):
@ -94,23 +82,27 @@ class _DelegatedWormhole(object):
self._boss._set_trace(client_name, which, file)
# from below
def got_welcome(self, welcome):
self._delegate.wormhole_got_welcome(welcome)
def got_code(self, code):
self._delegate.wormhole_code(code)
self._delegate.wormhole_got_code(code)
def got_key(self, key):
self._delegate.wormhole_key(key)
self._delegate.wormhole_got_unverified_key(key)
self._key = key # for derive_key()
def got_verifier(self, verifier):
self._delegate.wormhole_verified(verifier)
def got_version(self, versions):
self._delegate.wormhole_version(versions)
self._delegate.wormhole_got_verifier(verifier)
def got_versions(self, versions):
self._delegate.wormhole_got_versions(versions)
def received(self, plaintext):
self._delegate.wormhole_received(plaintext)
self._delegate.wormhole_got_message(plaintext)
def closed(self, result):
self._delegate.wormhole_closed(result)
@implementer(IWormhole)
class _DeferredWormhole(object):
def __init__(self):
self._welcome = None
self._welcome_observers = []
self._code = None
self._code_observers = []
self._key = None
@ -129,7 +121,7 @@ class _DeferredWormhole(object):
self._boss = boss
# from above
def when_code(self):
def get_code(self):
# TODO: consider throwing error unless one of allocate/set/input_code
# was called first. It's legit to grab the Deferred before triggering
# the process that will cause it to fire, but forbidding that
@ -143,7 +135,16 @@ class _DeferredWormhole(object):
self._code_observers.append(d)
return d
def when_key(self):
def get_welcome(self):
if self._observer_result is not None:
return defer.fail(self._observer_result)
if self._welcome is not None:
return defer.succeed(self._welcome)
d = defer.Deferred()
self._welcome_observers.append(d)
return d
def get_unverified_key(self):
if self._observer_result is not None:
return defer.fail(self._observer_result)
if self._key is not None:
@ -152,7 +153,7 @@ class _DeferredWormhole(object):
self._key_observers.append(d)
return d
def when_verified(self):
def get_verifier(self):
if self._observer_result is not None:
return defer.fail(self._observer_result)
if self._verifier is not None:
@ -161,7 +162,7 @@ class _DeferredWormhole(object):
self._verifier_observers.append(d)
return d
def when_version(self):
def get_versions(self):
if self._observer_result is not None:
return defer.fail(self._observer_result)
if self._versions is not None:
@ -170,7 +171,7 @@ class _DeferredWormhole(object):
self._version_observers.append(d)
return d
def when_received(self):
def get_message(self):
if self._observer_result is not None:
return defer.fail(self._observer_result)
if self._received_data:
@ -187,7 +188,8 @@ class _DeferredWormhole(object):
self._boss.set_code(code)
# no .serialize in Deferred-mode
def send(self, plaintext):
def send_message(self, plaintext):
self._boss.send(plaintext)
def derive_key(self, purpose, length):
@ -217,6 +219,11 @@ class _DeferredWormhole(object):
self._boss._set_trace(client_name, which, file)
# from below
def got_welcome(self, welcome):
self._welcome = welcome
for d in self._welcome_observers:
d.callback(welcome)
self._welcome_observers[:] = []
def got_code(self, code):
self._code = code
for d in self._code_observers:
@ -227,12 +234,13 @@ class _DeferredWormhole(object):
for d in self._key_observers:
d.callback(key)
self._key_observers[:] = []
def got_verifier(self, verifier):
self._verifier = verifier
for d in self._verifier_observers:
d.callback(verifier)
self._verifier_observers[:] = []
def got_version(self, versions):
def got_versions(self, versions):
self._versions = versions
for d in self._version_observers:
d.callback(versions)
@ -245,7 +253,7 @@ class _DeferredWormhole(object):
self._received_data.append(plaintext)
def closed(self, result):
#print("closed", result, type(result))
#print("closed", result, type(result), file=sys.stderr)
if isinstance(result, Exception):
self._observer_result = self._closed_result = failure.Failure(result)
else:
@ -253,6 +261,8 @@ class _DeferredWormhole(object):
self._observer_result = WormholeClosed(result)
# but w.close() only gets error if we're unhappy
self._closed_result = result
for d in self._welcome_observers:
d.errback(self._observer_result)
for d in self._code_observers:
d.errback(self._observer_result)
for d in self._key_observers:
@ -270,13 +280,11 @@ class _DeferredWormhole(object):
def create(appid, relay_url, reactor, # use keyword args for everything else
versions={},
delegate=None, journal=None, tor_manager=None,
timing=None, welcome_handler=None,
timing=None,
stderr=sys.stderr):
timing = timing or DebugTiming()
side = bytes_to_hexstr(os.urandom(5))
journal = journal or ImmediateJournal()
if not welcome_handler:
welcome_handler = _WelcomeHandler(relay_url).handle_welcome
if delegate:
w = _DelegatedWormhole(delegate)
else:
@ -284,8 +292,7 @@ def create(appid, relay_url, reactor, # use keyword args for everything else
wormhole_versions = {} # will be used to indicate Wormhole capabilities
wormhole_versions["app_versions"] = versions # app-specific capabilities
b = Boss(w, side, relay_url, appid, wormhole_versions,
welcome_handler, reactor, journal,
tor_manager, timing)
reactor, journal, tor_manager, timing)
w._set_boss(b)
b.start()
return w

View File

@ -41,14 +41,14 @@ def receive(reactor, appid, relay_url, code,
wh = wormhole.create(appid, relay_url, reactor, tor_manager=tm)
if code is None:
wh.allocate_code()
code = yield wh.when_code()
code = yield wh.get_code()
else:
wh.set_code(code)
# we'll call this no matter what, even if you passed in a code --
# maybe it should be only in the 'if' block above?
if on_code:
on_code(code)
data = yield wh.when_received()
data = yield wh.get_message()
data = json.loads(data.decode("utf-8"))
offer = data.get('offer', None)
if not offer:
@ -58,7 +58,8 @@ def receive(reactor, appid, relay_url, code,
msg = None
if 'message' in offer:
msg = offer['message']
wh.send(json.dumps({"answer": {"message_ack": "ok"}}).encode("utf-8"))
wh.send_message(json.dumps({"answer":
{"message_ack": "ok"}}).encode("utf-8"))
else:
raise Exception(
@ -104,20 +105,20 @@ def send(reactor, appid, relay_url, data, code,
wh = wormhole.create(appid, relay_url, reactor, tor_manager=tm)
if code is None:
wh.allocate_code()
code = yield wh.when_code()
code = yield wh.get_code()
else:
wh.set_code(code)
if on_code:
on_code(code)
wh.send(
wh.send_message(
json.dumps({
"offer": {
"message": data
}
}).encode("utf-8")
)
data = yield wh.when_received()
data = yield wh.get_message()
data = json.loads(data.decode("utf-8"))
answer = data.get('answer', None)
yield wh.close()