From 33fa6f6ede260372151fe8356a95135786f36556 Mon Sep 17 00:00:00 2001 From: laharah Date: Sat, 4 Jun 2016 23:52:32 -0700 Subject: [PATCH 1/9] tests for new establish_key wormhole hook defered should behave similarly to the `verify` hook --- src/wormhole/test/test_wormhole.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/wormhole/test/test_wormhole.py b/src/wormhole/test/test_wormhole.py index 67ad14f..7a701df 100644 --- a/src/wormhole/test/test_wormhole.py +++ b/src/wormhole/test/test_wormhole.py @@ -522,6 +522,32 @@ class Basic(unittest.TestCase): self.assertEqual(len(pieces), 3) # nameplate plus two words self.assert_(re.search(r'^\d+-\w+-\w+$', code), code) + def _test_establish_key_hook(self, established): + + timing = DebugTiming() + w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) + + if established is True: + w._key = b"key" + elif established is False: + w._key = None + else: + w._key = b"key" + w._error = WelcomeError() + + d = w.establish_key() + + if w._key is not None and established is True: + self.successResultOf(d) + elif established is False: + self.assertNot(d.called) + else: + self.failureResultOf(d) + + def test_establish_key_hook(self): + for established in (True, False, "error"): + self._test_establish_key_hook(established) + # make sure verify() can be called both before and after the verifier is # computed From 680b01b3fc69d016b3966f681dbca61338d893f2 Mon Sep 17 00:00:00 2001 From: laharah Date: Sat, 4 Jun 2016 23:21:44 -0700 Subject: [PATCH 2/9] added api hooks to get notification about key excange added more coverage to establish key API hook --- src/wormhole/test/test_wormhole.py | 14 ++++++++++---- src/wormhole/wormhole.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/wormhole/test/test_wormhole.py b/src/wormhole/test/test_wormhole.py index 7a701df..a8d59c9 100644 --- a/src/wormhole/test/test_wormhole.py +++ b/src/wormhole/test/test_wormhole.py @@ -522,11 +522,13 @@ class Basic(unittest.TestCase): self.assertEqual(len(pieces), 3) # nameplate plus two words self.assert_(re.search(r'^\d+-\w+-\w+$', code), code) - def _test_establish_key_hook(self, established): - + def _test_establish_key_hook(self, established, before): timing = DebugTiming() w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) + if before: + d = w.establish_key() + if established is True: w._key = b"key" elif established is False: @@ -535,7 +537,10 @@ class Basic(unittest.TestCase): w._key = b"key" w._error = WelcomeError() - d = w.establish_key() + if not before: + d = w.establish_key() + else: + w._maybe_notify_key() if w._key is not None and established is True: self.successResultOf(d) @@ -546,7 +551,8 @@ class Basic(unittest.TestCase): def test_establish_key_hook(self): for established in (True, False, "error"): - self._test_establish_key_hook(established) + for before in (True, False): + self._test_establish_key_hook(established, before) # make sure verify() can be called both before and after the verifier is # computed diff --git a/src/wormhole/wormhole.py b/src/wormhole/wormhole.py index c3b0c6f..d783e14 100644 --- a/src/wormhole/wormhole.py +++ b/src/wormhole/wormhole.py @@ -237,6 +237,7 @@ class _Wormhole: self._flag_need_to_see_mailbox_used = True self._flag_need_to_build_msg1 = True self._flag_need_to_send_PAKE = True + self._key_waiter = None self._key = None self._version_message = None @@ -283,6 +284,15 @@ class _Wormhole: # todo: restore-saved-state entry points + def establish_key(self): + """ + returns a Deferred that fires when we've established the shared key. + When successful, the Deferred fires with a simple `True`, otherwise + it fails. + + """ + return self._API_establish_key() + def verify(self): """Returns a Deferred that fires when we've heard back from the other side, and have confirmed that they used the right wormhole code. When @@ -559,6 +569,7 @@ class _Wormhole: def _event_established_key(self): self._timing.add("key established") + self._maybe_notify_key() # both sides send different (random) version messages self._send_version_message() @@ -569,6 +580,23 @@ class _Wormhole: self._maybe_check_version() self._maybe_send_phase_messages() + def _API_establish_key(self): + if self._error: return defer.fail(self._error) + if not self._key is None: + return defer.succeed(True) + self._key_waiter = defer.Deferred() + return self._key_waiter + + def _maybe_notify_key(self): + if self._key is None: + return + if self._error: + result = failure.Failure(self._error) + else: + result = True + if self._key_waiter and not self._key_waiter.called: + self._key_waiter.callback(result) + def _send_version_message(self): # this is encrypted like a normal phase message, and includes a # dictionary of version flags to let the other Wormhole know what From f786031f40aa129e8ba1540f9b7deb2c9244ce04 Mon Sep 17 00:00:00 2001 From: laharah Date: Sun, 5 Jun 2016 00:11:28 -0700 Subject: [PATCH 3/9] basic key established hook added to CLI send --- src/wormhole/cli/cmd_send.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wormhole/cli/cmd_send.py b/src/wormhole/cli/cmd_send.py index 6fa7446..aaa27cc 100644 --- a/src/wormhole/cli/cmd_send.py +++ b/src/wormhole/cli/cmd_send.py @@ -85,6 +85,9 @@ class Sender: print(u"Wormhole code is: %s" % code, file=args.stdout) print(u"", file=args.stdout) + key_established = yield w.establish_key() + print(u"Key established, waiting for confirmation...") + # TODO: don't stall on w.verify() unless they want it verifier_bytes = yield w.verify() # this may raise WrongPasswordError if args.verify: From a2ab1863cff016115ed70651be9ede0d541770dd Mon Sep 17 00:00:00 2001 From: laharah Date: Sun, 5 Jun 2016 02:55:31 -0700 Subject: [PATCH 4/9] added the new key waiter to be cleaned up on error in wormhole Also, set new print statment to print to args.stdout --- src/wormhole/cli/cmd_send.py | 3 ++- src/wormhole/wormhole.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wormhole/cli/cmd_send.py b/src/wormhole/cli/cmd_send.py index aaa27cc..353e138 100644 --- a/src/wormhole/cli/cmd_send.py +++ b/src/wormhole/cli/cmd_send.py @@ -86,7 +86,8 @@ class Sender: print(u"", file=args.stdout) key_established = yield w.establish_key() - print(u"Key established, waiting for confirmation...") + print(u"Key established, waiting for confirmation...", + file=args.stdout) # TODO: don't stall on w.verify() unless they want it verifier_bytes = yield w.verify() # this may raise WrongPasswordError diff --git a/src/wormhole/wormhole.py b/src/wormhole/wormhole.py index d783e14..fcb1719 100644 --- a/src/wormhole/wormhole.py +++ b/src/wormhole/wormhole.py @@ -289,7 +289,7 @@ class _Wormhole: returns a Deferred that fires when we've established the shared key. When successful, the Deferred fires with a simple `True`, otherwise it fails. - + """ return self._API_establish_key() @@ -852,6 +852,9 @@ class _Wormhole: if self._verifier_waiter and not self._verifier_waiter.called: if self.DEBUG: print("EB VW") self._verifier_waiter.errback(error) + if self._key_waiter and not self._key_waiter.called: + if self.DEBUG: print("EB KW") + self._key_waiter.errback(error) for d in self._receive_waiters.values(): if self.DEBUG: print("EB RW") d.errback(error) From 01318d113085fa811edb51e6f126a73cd32cf1ef Mon Sep 17 00:00:00 2001 From: laharah Date: Sun, 5 Jun 2016 03:10:32 -0700 Subject: [PATCH 5/9] added notification to cmd_receive plus reactor cleanup Added a try finally block around verify to ensure notification deferreds are being cleaned out of the reactor --- src/wormhole/cli/cmd_receive.py | 11 ++++++++++- src/wormhole/cli/cmd_send.py | 15 +++++++++++---- src/wormhole/wormhole.py | 3 +-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/wormhole/cli/cmd_receive.py b/src/wormhole/cli/cmd_receive.py index e458de3..07adb48 100644 --- a/src/wormhole/cli/cmd_receive.py +++ b/src/wormhole/cli/cmd_receive.py @@ -75,7 +75,16 @@ class TwistedReceiver: @inlineCallbacks def _go(self, w): yield self._handle_code(w) - verifier = yield w.verify() + yield w.establish_key() + def on_slow_connection(): + print(u"Key established, waiting for confirmation...", + file=self.args.stdout) + notify = self._reactor.callLater(1, on_slow_connection) + try: + verifier = yield w.verify() + finally: + if not notify.called: + notify.cancel() self._show_verifier(verifier) want_offer = True diff --git a/src/wormhole/cli/cmd_send.py b/src/wormhole/cli/cmd_send.py index 353e138..8ab914b 100644 --- a/src/wormhole/cli/cmd_send.py +++ b/src/wormhole/cli/cmd_send.py @@ -85,12 +85,19 @@ class Sender: print(u"Wormhole code is: %s" % code, file=args.stdout) print(u"", file=args.stdout) - key_established = yield w.establish_key() - print(u"Key established, waiting for confirmation...", - file=args.stdout) + yield w.establish_key() + def on_slow_connection(): + print(u"Key established, waiting for confirmation...", + file=args.stdout) + notify = self._reactor.callLater(1, on_slow_connection) # TODO: don't stall on w.verify() unless they want it - verifier_bytes = yield w.verify() # this may raise WrongPasswordError + try: + verifier_bytes = yield w.verify() # this may raise WrongPasswordError + finally: + if not notify.called: + notify.cancel() + if args.verify: verifier = bytes_to_hexstr(verifier_bytes) while True: diff --git a/src/wormhole/wormhole.py b/src/wormhole/wormhole.py index fcb1719..814f262 100644 --- a/src/wormhole/wormhole.py +++ b/src/wormhole/wormhole.py @@ -289,7 +289,6 @@ class _Wormhole: returns a Deferred that fires when we've established the shared key. When successful, the Deferred fires with a simple `True`, otherwise it fails. - """ return self._API_establish_key() @@ -582,7 +581,7 @@ class _Wormhole: def _API_establish_key(self): if self._error: return defer.fail(self._error) - if not self._key is None: + if self._key is not None: return defer.succeed(True) self._key_waiter = defer.Deferred() return self._key_waiter From 6bdaaf368c245ea6a0f20c199e3690bbc1ed29c2 Mon Sep 17 00:00:00 2001 From: laharah Date: Fri, 17 Jun 2016 22:03:58 -0700 Subject: [PATCH 6/9] Moved slow connection notification to stderr added test for slow connection notifications --- src/wormhole/cli/cmd_receive.py | 5 ++-- src/wormhole/cli/cmd_send.py | 5 ++-- src/wormhole/test/test_scripts.py | 39 ++++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/wormhole/cli/cmd_receive.py b/src/wormhole/cli/cmd_receive.py index 07adb48..c23b44a 100644 --- a/src/wormhole/cli/cmd_receive.py +++ b/src/wormhole/cli/cmd_receive.py @@ -12,6 +12,7 @@ from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr, estimate_free_space) APPID = u"lothar.com/wormhole/text-or-file-xfer" +VERIFY_TIMER = 1 class RespondError(Exception): def __init__(self, response): @@ -78,8 +79,8 @@ class TwistedReceiver: yield w.establish_key() def on_slow_connection(): print(u"Key established, waiting for confirmation...", - file=self.args.stdout) - notify = self._reactor.callLater(1, on_slow_connection) + file=self.args.stderr) + notify = self._reactor.callLater(VERIFY_TIMER, on_slow_connection) try: verifier = yield w.verify() finally: diff --git a/src/wormhole/cli/cmd_send.py b/src/wormhole/cli/cmd_send.py index 8ab914b..2c46af8 100644 --- a/src/wormhole/cli/cmd_send.py +++ b/src/wormhole/cli/cmd_send.py @@ -12,6 +12,7 @@ from ..transit import TransitSender from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr APPID = u"lothar.com/wormhole/text-or-file-xfer" +VERIFY_TIMER = 1 def send(args, reactor=reactor): """I implement 'wormhole send'. I return a Deferred that fires with None @@ -88,8 +89,8 @@ class Sender: yield w.establish_key() def on_slow_connection(): print(u"Key established, waiting for confirmation...", - file=args.stdout) - notify = self._reactor.callLater(1, on_slow_connection) + file=args.stderr) + notify = self._reactor.callLater(VERIFY_TIMER, on_slow_connection) # TODO: don't stall on w.verify() unless they want it try: diff --git a/src/wormhole/test/test_scripts.py b/src/wormhole/test/test_scripts.py index e57f2fb..2af0ce7 100644 --- a/src/wormhole/test/test_scripts.py +++ b/src/wormhole/test/test_scripts.py @@ -225,9 +225,9 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): @inlineCallbacks def _do_test(self, as_subprocess=False, mode="text", addslash=False, override_filename=False): - assert mode in ("text", "file", "directory") - send_cfg = config("send") - recv_cfg = config("receive") + assert mode in ("text", "file", "directory", "slow") + send_cfg = Config() + recv_cfg = Config() message = "blah blah blah ponies" for cfg in [send_cfg, recv_cfg]: @@ -244,7 +244,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): receive_dir = self.mktemp() os.mkdir(receive_dir) - if mode == "text": + if mode == "text" or mode == "slow": send_cfg.text = message elif mode == "file": @@ -348,8 +348,14 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): # The sender might fail, leaving the receiver hanging, or vice # versa. Make sure we don't wait on one side exclusively + if mode == "slow": + with mock.patch.object(cmd_send, "VERIFY_TIMER", 0), \ + mock.patch.object(cmd_receive, "VERIFY_TIMER", 0): + yield gatherResults([send_d, receive_d], True) + else: + yield gatherResults([send_d, receive_d], True) + - yield gatherResults([send_d, receive_d], True) send_stdout = send_cfg.stdout.getvalue() send_stderr = send_cfg.stderr.getvalue() receive_stdout = recv_cfg.stdout.getvalue() @@ -361,13 +367,21 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): self.maxDiff = None # show full output for assertion failures - self.failUnlessEqual(send_stderr, "", - (send_stdout, send_stderr)) - self.failUnlessEqual(receive_stderr, "", - (receive_stdout, receive_stderr)) + if mode != "slow": + self.failUnlessEqual(send_stderr, "", + (send_stdout, send_stderr)) + self.failUnlessEqual(receive_stderr, "", + (receive_stdout, receive_stderr)) + else: + self.assertEqual(send_stderr, + "Key established, waiting for confirmation...\n", + (send_stdout, send_stderr)) + self.assertEqual(receive_stderr, + "Key established, waiting for confirmation...\n", + (receive_stdout, receive_stderr)) # check sender - if mode == "text": + if mode == "text" or mode == "slow": expected = ("Sending text message ({bytes:d} Bytes){NL}" "On the other computer, please run: " "wormhole receive{NL}" @@ -401,7 +415,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): .format(NL=NL), send_stdout) # check receiver - if mode == "text": + if mode == "text" or mode == "slow": self.failUnlessEqual(receive_stdout, message+NL) elif mode == "file": self.failUnlessIn("Receiving file ({size:s}) into: {name}" @@ -448,6 +462,9 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): def test_directory_override(self): return self._do_test(mode="directory", override_filename=True) + def test_slow(self): + return self._do_test(mode="slow") + @inlineCallbacks def _do_test_fail(self, mode, failmode): assert mode in ("file", "directory") From 2d1f474c60631a0f2f9cebe5d190ac14ca53b336 Mon Sep 17 00:00:00 2001 From: laharah Date: Tue, 16 Aug 2016 02:40:47 -0700 Subject: [PATCH 7/9] rebased and fixed merge error --- src/wormhole/test/test_scripts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wormhole/test/test_scripts.py b/src/wormhole/test/test_scripts.py index 2af0ce7..10687ba 100644 --- a/src/wormhole/test/test_scripts.py +++ b/src/wormhole/test/test_scripts.py @@ -226,8 +226,8 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): def _do_test(self, as_subprocess=False, mode="text", addslash=False, override_filename=False): assert mode in ("text", "file", "directory", "slow") - send_cfg = Config() - recv_cfg = Config() + send_cfg = config("send") + recv_cfg = config("receive") message = "blah blah blah ponies" for cfg in [send_cfg, recv_cfg]: From face9423dd0abc88656f3064740bd24f147d1bce Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 16 Dec 2016 01:33:17 -0800 Subject: [PATCH 8/9] internals: ensure _API_establish_key is not called twice --- src/wormhole/test/test_wormhole.py | 7 +++++++ src/wormhole/wormhole.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/wormhole/test/test_wormhole.py b/src/wormhole/test/test_wormhole.py index a8d59c9..a59280a 100644 --- a/src/wormhole/test/test_wormhole.py +++ b/src/wormhole/test/test_wormhole.py @@ -554,6 +554,13 @@ class Basic(unittest.TestCase): for before in (True, False): self._test_establish_key_hook(established, before) + def test_establish_key_twice(self): + timing = DebugTiming() + w = wormhole._Wormhole(APPID, "relay_url", reactor, None, timing) + d = w.establish_key() + self.assertRaises(InternalError, w.establish_key) + del d + # make sure verify() can be called both before and after the verifier is # computed diff --git a/src/wormhole/wormhole.py b/src/wormhole/wormhole.py index 814f262..93d95b8 100644 --- a/src/wormhole/wormhole.py +++ b/src/wormhole/wormhole.py @@ -237,6 +237,7 @@ class _Wormhole: self._flag_need_to_see_mailbox_used = True self._flag_need_to_build_msg1 = True self._flag_need_to_send_PAKE = True + self._establish_key_called = False self._key_waiter = None self._key = None @@ -581,6 +582,8 @@ class _Wormhole: def _API_establish_key(self): if self._error: return defer.fail(self._error) + if self._establish_key_called: raise InternalError + self._establish_key_called = True if self._key is not None: return defer.succeed(True) self._key_waiter = defer.Deferred() From 30af04d245bd611de2c86c64c5193000c4ea6729 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 16 Dec 2016 01:33:45 -0800 Subject: [PATCH 9/9] test_scripts: rename mode to be "slow-text" --- src/wormhole/test/test_scripts.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wormhole/test/test_scripts.py b/src/wormhole/test/test_scripts.py index 10687ba..69f8a42 100644 --- a/src/wormhole/test/test_scripts.py +++ b/src/wormhole/test/test_scripts.py @@ -225,7 +225,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): @inlineCallbacks def _do_test(self, as_subprocess=False, mode="text", addslash=False, override_filename=False): - assert mode in ("text", "file", "directory", "slow") + assert mode in ("text", "file", "directory", "slow-text") send_cfg = config("send") recv_cfg = config("receive") message = "blah blah blah ponies" @@ -244,7 +244,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): receive_dir = self.mktemp() os.mkdir(receive_dir) - if mode == "text" or mode == "slow": + if mode == "text" or mode == "slow-text": send_cfg.text = message elif mode == "file": @@ -348,7 +348,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): # The sender might fail, leaving the receiver hanging, or vice # versa. Make sure we don't wait on one side exclusively - if mode == "slow": + if mode == "slow-text": with mock.patch.object(cmd_send, "VERIFY_TIMER", 0), \ mock.patch.object(cmd_receive, "VERIFY_TIMER", 0): yield gatherResults([send_d, receive_d], True) @@ -367,7 +367,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): self.maxDiff = None # show full output for assertion failures - if mode != "slow": + if mode != "slow-text": self.failUnlessEqual(send_stderr, "", (send_stdout, send_stderr)) self.failUnlessEqual(receive_stderr, "", @@ -381,7 +381,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): (receive_stdout, receive_stderr)) # check sender - if mode == "text" or mode == "slow": + if mode == "text" or mode == "slow-text": expected = ("Sending text message ({bytes:d} Bytes){NL}" "On the other computer, please run: " "wormhole receive{NL}" @@ -415,7 +415,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): .format(NL=NL), send_stdout) # check receiver - if mode == "text" or mode == "slow": + if mode == "text" or mode == "slow-text": self.failUnlessEqual(receive_stdout, message+NL) elif mode == "file": self.failUnlessIn("Receiving file ({size:s}) into: {name}" @@ -462,8 +462,8 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase): def test_directory_override(self): return self._do_test(mode="directory", override_filename=True) - def test_slow(self): - return self._do_test(mode="slow") + def test_slow_text(self): + return self._do_test(mode="slow-text") @inlineCallbacks def _do_test_fail(self, mode, failmode):