rewrite waiting-for-sender pacifier messages

re-enable the test, and add an extra one

The comments in cmd_send/cmd_receive now enumerate the four cases where we
might notice that things are taking too long, the three cases where we say
something about it, and the two cases where it might be appropriate to give
up automatically (although we don't do that anywhere yet).
This commit is contained in:
Brian Warner 2017-04-06 19:17:11 -07:00
parent 83e55f1f3e
commit 1a7b3baaf2
3 changed files with 100 additions and 45 deletions

View File

@ -13,7 +13,9 @@ from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr,
from .welcome import CLIWelcomeHandler
APPID = u"lothar.com/wormhole/text-or-file-xfer"
VERIFY_TIMER = 1
KEY_TIMER = 1.0
VERIFY_TIMER = 1.0
class RespondError(Exception):
def __init__(self, response):
@ -106,17 +108,45 @@ class TwistedReceiver:
@inlineCallbacks
def _go(self, w):
yield self._handle_code(w)
verifier = yield w.when_verified()
def on_slow_connection():
print(u"Key established, waiting for confirmation...",
file=self.args.stderr)
notify = self._reactor.callLater(VERIFY_TIMER, on_slow_connection)
def on_slow_key():
print(u"Waiting for sender...", file=self.args.stderr)
notify = self._reactor.callLater(KEY_TIMER, on_slow_key)
try:
yield w.when_version()
# We wait here until we connect to the server and see the senders
# PAKE message. If we used set_code() in the "human-selected
# offline codes" mode, then the sender might not have even
# started yet, so we might be sitting here for a while. Because
# 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()
finally:
if not notify.called:
notify.cancel()
self._show_verifier(verifier)
def on_slow_verification():
print(u"Key established, waiting for confirmation...",
file=self.args.stderr)
notify = self._reactor.callLater(VERIFY_TIMER, on_slow_verification)
try:
# We wait here until we've seen their VERSION message (which they
# send after seeing our PAKE message, and has the side-effect of
# verifying that we both share the same key). There is a
# round-trip between these two events, and we could experience a
# significant delay here if:
# * the relay server is being restarted
# * the network is very slow
# * the sender is very slow
# * the sender has quit (in which case we may wait forever)
# It would be reasonable to give up after waiting here for too
# long.
verifier_bytes = yield w.when_verified()
finally:
if not notify.called:
notify.cancel()
self._show_verifier(verifier_bytes)
want_offer = True
done = False
@ -177,8 +207,8 @@ class TwistedReceiver:
file=self.args.stderr)
yield w.when_code()
def _show_verifier(self, verifier):
verifier_hex = bytes_to_hexstr(verifier)
def _show_verifier(self, verifier_bytes):
verifier_hex = bytes_to_hexstr(verifier_bytes)
if self.args.verify:
self._msg(u"Verifier %s." % verifier_hex)

View File

@ -118,30 +118,34 @@ class Sender:
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()
# TODO: don't stall on w.verify() unless they want it
def on_slow_connection():
print(u"Key established, waiting for confirmation...",
file=args.stderr)
#notify = self._reactor.callLater(VERIFY_TIMER, on_slow_connection)
# TODO: don't stall on w.verify() unless they want it
#try:
# verifier_bytes = yield w.when_verified() # might WrongPasswordError
#finally:
# if not notify.called:
# notify.cancel()
verifier_bytes = yield w.when_verified()
notify = self._reactor.callLater(VERIFY_TIMER, on_slow_connection)
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
# 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
finally:
if not notify.called:
notify.cancel()
if args.verify:
verifier = bytes_to_hexstr(verifier_bytes)
while True:
ok = six.moves.input("Verifier %s. ok? (yes/no): " % verifier)
if ok.lower() == "yes":
break
if ok.lower() == "no":
err = "sender rejected verification check, abandoned transfer"
reject_data = dict_to_bytes({"error": err})
w.send(reject_data)
raise TransferError(err)
self._check_verifier(w, verifier_bytes) # blocks, can TransferError
if self._fd_to_send:
ts = TransitSender(args.transit_helper,
@ -197,6 +201,18 @@ class Sender:
if not recognized:
log.msg("unrecognized message %r" % (them_d,))
def _check_verifier(self, w, verifier_bytes):
verifier = bytes_to_hexstr(verifier_bytes)
while True:
ok = six.moves.input("Verifier %s. ok? (yes/no): " % verifier)
if ok.lower() == "yes":
break
if ok.lower() == "no":
err = "sender rejected verification check, abandoned transfer"
reject_data = dict_to_bytes({"error": err})
w.send(reject_data)
raise TransferError(err)
def _handle_transit(self, receiver_transit):
ts = self._transit_sender
ts.add_connection_hints(receiver_transit.get("hints-v1", []))

View File

@ -281,7 +281,8 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
def _do_test(self, as_subprocess=False,
mode="text", addslash=False, override_filename=False,
fake_tor=False, overwrite=False, mock_accept=False):
assert mode in ("text", "file", "empty-file", "directory", "slow-text")
assert mode in ("text", "file", "empty-file", "directory",
"slow-text", "slow-sender-text")
if fake_tor:
assert not as_subprocess
send_cfg = config("send")
@ -302,7 +303,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
receive_dir = self.mktemp()
os.mkdir(receive_dir)
if mode in ("text", "slow-text"):
if mode in ("text", "slow-text", "slow-sender-text"):
send_cfg.text = message
elif mode in ("file", "empty-file"):
@ -428,20 +429,22 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
) as mrx_tm:
receive_d = cmd_receive.receive(recv_cfg)
else:
send_d = cmd_send.send(send_cfg)
receive_d = cmd_receive.receive(recv_cfg)
KEY_TIMER = 0 if mode == "slow-sender-text" else 1.0
with mock.patch.object(cmd_receive, "KEY_TIMER", KEY_TIMER):
send_d = cmd_send.send(send_cfg)
receive_d = cmd_receive.receive(recv_cfg)
# The sender might fail, leaving the receiver hanging, or vice
# versa. Make sure we don't wait on one side exclusively
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)
elif mock_accept:
with mock.patch.object(cmd_receive.six.moves, 'input', return_value='y'):
yield gatherResults([send_d, receive_d], True)
else:
yield gatherResults([send_d, receive_d], True)
VERIFY_TIMER = 0 if mode == "slow-text" else 1.0
with mock.patch.object(cmd_receive, "VERIFY_TIMER", VERIFY_TIMER):
with mock.patch.object(cmd_send, "VERIFY_TIMER", VERIFY_TIMER):
if mock_accept:
with mock.patch.object(cmd_receive.six.moves,
'input', return_value='y'):
yield gatherResults([send_d, receive_d], True)
else:
yield gatherResults([send_d, receive_d], True)
if fake_tor:
expected_endpoints = [("127.0.0.1", self.relayport)]
@ -512,9 +515,14 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
.format(NL=NL), send_stderr)
# check receiver
if mode == "text" or mode == "slow-text":
if mode in ("text", "slow-text", "slow-sender-text"):
self.assertEqual(receive_stdout, message+NL)
self.assertEqual(receive_stderr, key_established)
if mode == "text":
self.assertEqual(receive_stderr, "")
elif mode == "slow-text":
self.assertEqual(receive_stderr, key_established)
elif mode == "slow-sender-text":
self.assertEqual(receive_stderr, "Waiting for sender...\n")
elif mode == "file":
self.failUnlessEqual(receive_stdout, "")
self.failUnlessIn("Receiving file ({size:s}) into: {name}"
@ -578,7 +586,8 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
def test_slow_text(self):
return self._do_test(mode="slow-text")
test_slow_text.skip = "pending rethink"
def test_slow_sender_text(self):
return self._do_test(mode="slow-sender-text")
@inlineCallbacks
def _do_test_fail(self, mode, failmode):