INCOMPATIBLE CHANGE: put offer/answer in their own keys
This moves us slowly towards a file-transfer protocol that exchanges multiple messages, with a single offer (sender->receiver) and answer (receiver->sender), and one or more connection hint messages (in either direction) that appear gradually over time as connection providers come online. At present the protocol still expects the whole hint list to be present in the offer/answer message.
This commit is contained in:
parent
96f25ec7a2
commit
ac1db705fe
|
@ -3,9 +3,10 @@ import os, sys, json, binascii, six, tempfile, zipfile
|
|||
from tqdm import tqdm
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from twisted.python import log
|
||||
from ..wormhole import wormhole
|
||||
from ..transit import TransitReceiver
|
||||
from ..errors import TransferError
|
||||
from ..errors import TransferError, WormholeClosedError
|
||||
|
||||
APPID = u"lothar.com/wormhole/text-or-file-xfer"
|
||||
|
||||
|
@ -29,24 +30,26 @@ class TwistedReceiver:
|
|||
assert isinstance(args.relay_url, type(u""))
|
||||
self.args = args
|
||||
self._reactor = reactor
|
||||
self._tor_manager = None
|
||||
|
||||
def msg(self, *args, **kwargs):
|
||||
print(*args, file=self.args.stdout, **kwargs)
|
||||
|
||||
@inlineCallbacks
|
||||
def go(self):
|
||||
tor_manager = None
|
||||
if self.args.tor:
|
||||
with self.args.timing.add("import", which="tor_manager"):
|
||||
from ..tor_manager import TorManager
|
||||
tor_manager = TorManager(self._reactor, timing=self.args.timing)
|
||||
self._tor_manager = TorManager(self._reactor,
|
||||
timing=self.args.timing)
|
||||
# For now, block everything until Tor has started. Soon: launch
|
||||
# tor in parallel with everything else, make sure the TorManager
|
||||
# can lazy-provide an endpoint, and overlap the startup process
|
||||
# with the user handing off the wormhole code
|
||||
yield tor_manager.start()
|
||||
yield self._tor_manager.start()
|
||||
|
||||
w = wormhole(APPID, self.args.relay_url, self._reactor,
|
||||
tor_manager, timing=self.args.timing)
|
||||
self._tor_manager, timing=self.args.timing)
|
||||
# I wanted to do this instead:
|
||||
#
|
||||
# try:
|
||||
|
@ -57,41 +60,60 @@ class TwistedReceiver:
|
|||
# but when _go had a UsageError, the stacktrace was always displayed
|
||||
# as coming from the "yield self._go" line, which wasn't very useful
|
||||
# for tracking it down.
|
||||
d = self._go(w, tor_manager)
|
||||
d = self._go(w)
|
||||
d.addBoth(w.close)
|
||||
yield d
|
||||
|
||||
@inlineCallbacks
|
||||
def _go(self, w, tor_manager):
|
||||
def _go(self, w):
|
||||
yield self.handle_code(w)
|
||||
verifier = yield w.verify()
|
||||
self.show_verifier(verifier)
|
||||
them_d = yield self.get_data(w)
|
||||
|
||||
want_offer = True
|
||||
done = False
|
||||
|
||||
while True:
|
||||
try:
|
||||
if "message" in them_d:
|
||||
self.handle_text(them_d, w)
|
||||
them_d = yield self.get_data(w)
|
||||
except WormholeClosedError:
|
||||
if done:
|
||||
returnValue(None)
|
||||
if "file" in them_d:
|
||||
f = self.handle_file(them_d)
|
||||
rp = yield self.establish_transit(w, them_d, tor_manager)
|
||||
yield self.transfer_data(rp, f)
|
||||
self.write_file(f)
|
||||
yield self.close_transit(rp)
|
||||
elif "directory" in them_d:
|
||||
f = self.handle_directory(them_d)
|
||||
rp = yield self.establish_transit(w, them_d, tor_manager)
|
||||
yield self.transfer_data(rp, f)
|
||||
self.write_directory(f)
|
||||
yield self.close_transit(rp)
|
||||
else:
|
||||
self.msg(u"I don't know what they're offering\n")
|
||||
self.msg(u"Offer details:", them_d)
|
||||
raise RespondError("unknown offer type")
|
||||
raise TransferError("unexpected close")
|
||||
if u"offer" in them_d:
|
||||
if not want_offer:
|
||||
raise TransferError("duplicate offer")
|
||||
try:
|
||||
self.parse_offer(them_d[u"offer"], w)
|
||||
except RespondError as r:
|
||||
data = json.dumps({"error": r.response}).encode("utf-8")
|
||||
w.send(data)
|
||||
raise TransferError(r.response)
|
||||
returnValue(None)
|
||||
log.msg("unrecognized message %r" % (them_d,))
|
||||
raise TransferError("expected offer, got none")
|
||||
|
||||
@inlineCallbacks
|
||||
def parse_offer(self, them_d, w):
|
||||
if "message" in them_d:
|
||||
self.handle_text(them_d, w)
|
||||
returnValue(None)
|
||||
if "file" in them_d:
|
||||
f = self.handle_file(them_d)
|
||||
rp = yield self.establish_transit(w, them_d)
|
||||
yield self.transfer_data(rp, f)
|
||||
self.write_file(f)
|
||||
yield self.close_transit(rp)
|
||||
elif "directory" in them_d:
|
||||
f = self.handle_directory(them_d)
|
||||
rp = yield self.establish_transit(w, them_d)
|
||||
yield self.transfer_data(rp, f)
|
||||
self.write_directory(f)
|
||||
yield self.close_transit(rp)
|
||||
else:
|
||||
self.msg(u"I don't know what they're offering\n")
|
||||
self.msg(u"Offer details: %r" % (them_d,))
|
||||
raise RespondError("unknown offer type")
|
||||
|
||||
@inlineCallbacks
|
||||
def handle_code(self, w):
|
||||
|
@ -122,7 +144,7 @@ class TwistedReceiver:
|
|||
def handle_text(self, them_d, w):
|
||||
# we're receiving a text message
|
||||
self.msg(them_d["message"])
|
||||
data = json.dumps({"message_ack": "ok"}).encode("utf-8")
|
||||
data = json.dumps({"answer": {"message_ack": "ok"}}).encode("utf-8")
|
||||
w.send(data)
|
||||
|
||||
def handle_file(self, them_d):
|
||||
|
@ -181,10 +203,10 @@ class TwistedReceiver:
|
|||
t.detail(answer="yes")
|
||||
|
||||
@inlineCallbacks
|
||||
def establish_transit(self, w, them_d, tor_manager):
|
||||
def establish_transit(self, w, them_d):
|
||||
transit_receiver = TransitReceiver(self.args.transit_helper,
|
||||
no_listen=self.args.no_listen,
|
||||
tor_manager=tor_manager,
|
||||
tor_manager=self._tor_manager,
|
||||
reactor=self._reactor,
|
||||
timing=self.args.timing)
|
||||
transit_key = w.derive_key(APPID+u"/transit-key",
|
||||
|
@ -193,11 +215,13 @@ class TwistedReceiver:
|
|||
direct_hints = yield transit_receiver.get_direct_hints()
|
||||
relay_hints = yield transit_receiver.get_relay_hints()
|
||||
data = json.dumps({
|
||||
"answer": {
|
||||
"file_ack": "ok",
|
||||
"transit": {
|
||||
"direct_connection_hints": direct_hints,
|
||||
"relay_connection_hints": relay_hints,
|
||||
},
|
||||
},
|
||||
}).encode("utf-8")
|
||||
w.send(data)
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import print_function
|
||||
import os, sys, json, binascii, six, tempfile, zipfile
|
||||
from tqdm import tqdm
|
||||
from twisted.python import log
|
||||
from twisted.protocols import basic
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from ..errors import TransferError
|
||||
from ..errors import TransferError, WormholeClosedError
|
||||
from ..wormhole import wormhole
|
||||
from ..transit import TransitSender
|
||||
|
||||
|
@ -56,6 +57,30 @@ def _send(reactor, w, args, tor_manager):
|
|||
print(u"On the other computer, please run: %s" % other_cmd,
|
||||
file=args.stdout)
|
||||
|
||||
if args.code:
|
||||
w.set_code(args.code)
|
||||
code = args.code
|
||||
else:
|
||||
code = yield w.get_code(args.code_length)
|
||||
|
||||
if not args.zeromode:
|
||||
print(u"Wormhole code is: %s" % code, file=args.stdout)
|
||||
print(u"", file=args.stdout)
|
||||
|
||||
# TODO: don't stall on w.verify() unless they want it
|
||||
verifier_bytes = yield w.verify() # this may raise WrongPasswordError
|
||||
if args.verify:
|
||||
verifier = binascii.hexlify(verifier_bytes).decode("ascii")
|
||||
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 = json.dumps({"error": err}).encode("utf-8")
|
||||
w.send(reject_data)
|
||||
raise TransferError(err)
|
||||
|
||||
transit_sender = None
|
||||
if fd_to_send:
|
||||
transit_sender = TransitSender(args.transit_helper,
|
||||
|
@ -68,64 +93,34 @@ def _send(reactor, w, args, tor_manager):
|
|||
direct_hints = yield transit_sender.get_direct_hints()
|
||||
transit_data["direct_connection_hints"] = direct_hints
|
||||
|
||||
if args.code:
|
||||
w.set_code(args.code)
|
||||
code = args.code
|
||||
else:
|
||||
code = yield w.get_code(args.code_length)
|
||||
|
||||
if not args.zeromode:
|
||||
print(u"Wormhole code is: %s" % code, file=args.stdout)
|
||||
print(u"", file=args.stdout)
|
||||
|
||||
# get the verifier, because that also lets us derive the transit key,
|
||||
# which we want to set before revealing the connection hints to the far
|
||||
# side, so we'll be ready for them when they connect
|
||||
verifier_bytes = yield w.verify()
|
||||
verifier = binascii.hexlify(verifier_bytes).decode("ascii")
|
||||
|
||||
if args.verify:
|
||||
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 = json.dumps({"error": err}).encode("utf-8")
|
||||
w.send(reject_data)
|
||||
raise TransferError(err)
|
||||
if fd_to_send is not None:
|
||||
# TODO: move this down below w.get()
|
||||
transit_key = w.derive_key(APPID+"/transit-key",
|
||||
transit_sender.TRANSIT_KEY_LENGTH)
|
||||
transit_sender.set_transit_key(transit_key)
|
||||
|
||||
my_offer_bytes = json.dumps(offer).encode("utf-8")
|
||||
my_offer_bytes = json.dumps({"offer": offer}).encode("utf-8")
|
||||
w.send(my_offer_bytes)
|
||||
|
||||
# this may raise WrongPasswordError
|
||||
them_answer_bytes = yield w.get()
|
||||
want_answer = True
|
||||
done = False
|
||||
|
||||
them_answer = json.loads(them_answer_bytes.decode("utf-8"))
|
||||
|
||||
if fd_to_send is None:
|
||||
if them_answer["message_ack"] == "ok":
|
||||
print(u"text message sent", file=args.stdout)
|
||||
returnValue(None) # terminates this function
|
||||
raise TransferError("error sending text: %r" % (them_answer,))
|
||||
|
||||
if "error" in them_answer:
|
||||
raise TransferError("remote error, transfer abandoned: %s"
|
||||
% them_answer["error"])
|
||||
if them_answer.get("file_ack") != "ok":
|
||||
raise TransferError("ambiguous response from remote, "
|
||||
"transfer abandoned: %s" % (them_answer,))
|
||||
tdata = them_answer["transit"]
|
||||
# XXX the downside of closing above, rather than here, is that it leaves
|
||||
# the channel claimed for a longer time
|
||||
#yield w.close()
|
||||
yield _send_file_twisted(tdata, transit_sender, fd_to_send,
|
||||
args.stdout, args.hide_progress, args.timing)
|
||||
while True:
|
||||
try:
|
||||
them_d_bytes = yield w.get()
|
||||
except WormholeClosedError:
|
||||
if done:
|
||||
returnValue(None)
|
||||
raise TransferError("unexpected close")
|
||||
# TODO: get() fired, so now it's safe to use w.derive_key()
|
||||
them_d = json.loads(them_d_bytes.decode("utf-8"))
|
||||
if u"answer" in them_d:
|
||||
if not want_answer:
|
||||
raise TransferError("duplicate answer")
|
||||
them_answer = them_d[u"answer"]
|
||||
yield handle_answer(them_answer, args, fd_to_send, transit_sender)
|
||||
done = True
|
||||
returnValue(None)
|
||||
log.msg("unrecognized message %r" % (them_d,))
|
||||
|
||||
def build_offer(args):
|
||||
offer = {}
|
||||
|
@ -199,6 +194,27 @@ def build_offer(args):
|
|||
|
||||
raise TypeError("'%s' is neither file nor directory" % args.what)
|
||||
|
||||
@inlineCallbacks
|
||||
def handle_answer(them_answer, args, fd_to_send, transit_sender):
|
||||
if fd_to_send is None:
|
||||
if them_answer["message_ack"] == "ok":
|
||||
print(u"text message sent", file=args.stdout)
|
||||
returnValue(None) # terminates this function
|
||||
raise TransferError("error sending text: %r" % (them_answer,))
|
||||
|
||||
if "error" in them_answer:
|
||||
raise TransferError("remote error, transfer abandoned: %s"
|
||||
% them_answer["error"])
|
||||
if them_answer.get("file_ack") != "ok":
|
||||
raise TransferError("ambiguous response from remote, "
|
||||
"transfer abandoned: %s" % (them_answer,))
|
||||
|
||||
tdata = them_answer["transit"]
|
||||
yield _send_file_twisted(tdata, transit_sender, fd_to_send,
|
||||
args.stdout, args.hide_progress,
|
||||
args.timing)
|
||||
|
||||
|
||||
@inlineCallbacks
|
||||
def _send_file_twisted(tdata, transit_sender, fd_to_send,
|
||||
stdout, hide_progress, timing):
|
||||
|
|
Loading…
Reference in New Issue
Block a user