working on fixes

This commit is contained in:
Brian Warner 2017-02-24 18:30:00 -08:00
parent b7df5e21eb
commit 41b7bcfed5
10 changed files with 110 additions and 29 deletions

View File

@ -17,6 +17,9 @@ digraph {
S0 [label="S0: empty"] S0 [label="S0: empty"]
S0 -> P0_build [label="set_code"] S0 -> P0_build [label="set_code"]
S0 -> P_close_error [label="rx_error"]
P_close_error [shape="box" label="M.close(errory)"]
P_close_error -> S_closing
S0 -> P_close_lonely [label="close"] S0 -> P_close_lonely [label="close"]
P0_build [shape="box" label="W.got_code\nM.set_nameplate\nK.got_code"] P0_build [shape="box" label="W.got_code\nM.set_nameplate\nK.got_code"]
@ -25,6 +28,7 @@ digraph {
S1 -> S2 [label="happy"] S1 -> S2 [label="happy"]
S1 -> P_close_error [label="rx_error"]
S1 -> P_close_scary [label="scared" color="red"] S1 -> P_close_scary [label="scared" color="red"]
S1 -> P_close_lonely [label="close"] S1 -> P_close_lonely [label="close"]
P_close_lonely [shape="box" label="M.close(lonely)"] P_close_lonely [shape="box" label="M.close(lonely)"]
@ -39,22 +43,23 @@ digraph {
P2_close -> S_closing P2_close -> S_closing
S2 -> P2_got_message [label="got_message"] S2 -> P2_got_message [label="got_message"]
P2_got_message [shape="box" label="A.received"] P2_got_message [shape="box" label="W.received"]
P2_got_message -> S2 P2_got_message -> S2
S2 -> P_close_error [label="rx_error"]
S2 -> P_close_scary [label="scared" color="red"] S2 -> P_close_scary [label="scared" color="red"]
S_closing [label="closing"] S_closing [label="closing"]
S_closing -> P_closed [label="closed"] S_closing -> P_closed [label="closed"]
S_closing -> S_closing [label="got_message\nhappy\nscared\nclose"] S_closing -> S_closing [label="got_message\nhappy\nscared\nclose"]
P_closed [shape="box" label="A.closed(reason)"] P_closed [shape="box" label="W.closed(reason)"]
P_closed -> S_closed P_closed -> S_closed
S_closed [label="closed"] S_closed [label="closed"]
{rank=same; Other S_closed} {rank=same; Other S_closed}
Other [shape="box" style="dashed" Other [shape="box" style="dashed"
label="rx_welcome -> process\nsend -> S.send\ngot_verifier -> A.got_verifier\nallocate -> C.allocate\ninput -> C.input\nset_code -> C.set_code" label="rx_welcome -> process\nsend -> S.send\ngot_verifier -> W.got_verifier\nallocate -> C.allocate\ninput -> C.input\nset_code -> C.set_code"
] ]

View File

@ -24,7 +24,7 @@ digraph {
#Boss -> Connection [color="blue"] #Boss -> Connection [color="blue"]
Boss -> Connection [style="dashed" label="start"] Boss -> Connection [style="dashed" label="start"]
Connection -> Boss [style="dashed" label="rx_welcome"] Connection -> Boss [style="dashed" label="rx_welcome\nrx_error"]
Boss -> Send [style="dashed" label="send"] Boss -> Send [style="dashed" label="send"]

View File

@ -1,7 +1,10 @@
from __future__ import print_function, absolute_import, unicode_literals from __future__ import print_function, absolute_import, unicode_literals
import re
import six
from zope.interface import implementer from zope.interface import implementer
from attr import attrs, attrib from attr import attrs, attrib
from attr.validators import provides, instance_of from attr.validators import provides, instance_of
from twisted.python import log
from automat import MethodicalMachine from automat import MethodicalMachine
from . import _interfaces from . import _interfaces
from ._mailbox import Mailbox from ._mailbox import Mailbox
@ -12,8 +15,12 @@ from ._receive import Receive
from ._rendezvous import RendezvousConnector from ._rendezvous import RendezvousConnector
from ._nameplate import NameplateListing from ._nameplate import NameplateListing
from ._code import Code from ._code import Code
from .errors import WrongPasswordError
from .util import bytes_to_dict from .util import bytes_to_dict
class WormholeError(Exception):
pass
@attrs @attrs
@implementer(_interfaces.IBoss) @implementer(_interfaces.IBoss)
class Boss(object): class Boss(object):
@ -30,7 +37,7 @@ class Boss(object):
self._M = Mailbox(self._side) self._M = Mailbox(self._side)
self._S = Send(self._side, self._timing) self._S = Send(self._side, self._timing)
self._O = Order(self._side, self._timing) self._O = Order(self._side, self._timing)
self._K = Key(self._appid, self._timing) self._K = Key(self._appid, self._side, self._timing)
self._R = Receive(self._side, self._timing) self._R = Receive(self._side, self._timing)
self._RC = RendezvousConnector(self._url, self._appid, self._side, self._RC = RendezvousConnector(self._url, self._appid, self._side,
self._reactor, self._journal, self._reactor, self._journal,
@ -51,6 +58,8 @@ class Boss(object):
self._next_rx_phase = 0 self._next_rx_phase = 0
self._rx_phases = {} # phase -> plaintext self._rx_phases = {} # phase -> plaintext
self._result = "empty"
# these methods are called from outside # these methods are called from outside
def start(self): def start(self):
self._RC.start() self._RC.start()
@ -107,13 +116,20 @@ class Boss(object):
def happy(self): pass def happy(self): pass
@m.input() @m.input()
def scared(self): pass def scared(self): pass
@m.input()
def rx_error(self, err, orig): pass
def got_message(self, phase, plaintext): def got_message(self, phase, plaintext):
assert isinstance(phase, type("")), type(phase) assert isinstance(phase, type("")), type(phase)
assert isinstance(plaintext, type(b"")), type(plaintext) assert isinstance(plaintext, type(b"")), type(plaintext)
if phase == "version": if phase == "version":
self.got_version(plaintext) self.got_version(plaintext)
elif re.search(r'^\d+$', phase):
self.got_phase(int(phase), plaintext)
else: else:
self.got_phase(phase, plaintext) # Ignore unrecognized phases, for forwards-compatibility. Use
# log.err so tests will catch surprises.
log.err("received unknown phase '%s'" % phase)
@m.input() @m.input()
def got_version(self, plaintext): pass def got_version(self, plaintext): pass
@m.input() @m.input()
@ -143,18 +159,26 @@ class Boss(object):
@m.output() @m.output()
def S_send(self, plaintext): def S_send(self, plaintext):
assert isinstance(plaintext, type(b"")), type(plaintext)
phase = self._next_tx_phase phase = self._next_tx_phase
self._next_tx_phase += 1 self._next_tx_phase += 1
self._S.send(phase, plaintext) self._S.send("%d" % phase, plaintext)
@m.output() @m.output()
def close_error(self, err, orig):
self._result = WormholeError(err)
self._M.close("errory")
@m.output()
def close_scared(self): def close_scared(self):
self._result = WrongPasswordError()
self._M.close("scary") self._M.close("scary")
@m.output() @m.output()
def close_lonely(self): def close_lonely(self):
self._result = WormholeError("lonely")
self._M.close("lonely") self._M.close("lonely")
@m.output() @m.output()
def close_happy(self): def close_happy(self):
self._result = "happy"
self._M.close("happy") self._M.close("happy")
@m.output() @m.output()
@ -162,6 +186,7 @@ class Boss(object):
self._W.got_verifier(verifier) self._W.got_verifier(verifier)
@m.output() @m.output()
def W_received(self, phase, plaintext): def W_received(self, phase, plaintext):
assert isinstance(phase, six.integer_types), type(phase)
# we call Wormhole.received() in strict phase order, with no gaps # we call Wormhole.received() in strict phase order, with no gaps
self._rx_phases[phase] = plaintext self._rx_phases[phase] = plaintext
while self._next_rx_phase in self._rx_phases: while self._next_rx_phase in self._rx_phases:
@ -170,27 +195,30 @@ class Boss(object):
@m.output() @m.output()
def W_closed(self): def W_closed(self):
result = "???" self._W.closed(self._result)
self._W.closed(result)
S0_empty.upon(close, enter=S3_closing, outputs=[close_lonely]) S0_empty.upon(close, enter=S3_closing, outputs=[close_lonely])
S0_empty.upon(send, enter=S0_empty, outputs=[S_send]) S0_empty.upon(send, enter=S0_empty, outputs=[S_send])
S0_empty.upon(rx_welcome, enter=S0_empty, outputs=[process_welcome]) S0_empty.upon(rx_welcome, enter=S0_empty, outputs=[process_welcome])
S0_empty.upon(got_code, enter=S1_lonely, outputs=[do_got_code]) S0_empty.upon(got_code, enter=S1_lonely, outputs=[do_got_code])
S0_empty.upon(rx_error, enter=S3_closing, outputs=[close_error])
S1_lonely.upon(rx_welcome, enter=S1_lonely, outputs=[process_welcome]) S1_lonely.upon(rx_welcome, enter=S1_lonely, outputs=[process_welcome])
S1_lonely.upon(happy, enter=S2_happy, outputs=[]) S1_lonely.upon(happy, enter=S2_happy, outputs=[])
S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared]) S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared])
S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely]) S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely])
S1_lonely.upon(send, enter=S1_lonely, outputs=[S_send]) S1_lonely.upon(send, enter=S1_lonely, outputs=[S_send])
S1_lonely.upon(got_verifier, enter=S1_lonely, outputs=[W_got_verifier]) S1_lonely.upon(got_verifier, enter=S1_lonely, outputs=[W_got_verifier])
S1_lonely.upon(rx_error, enter=S3_closing, outputs=[close_error])
S2_happy.upon(rx_welcome, enter=S2_happy, outputs=[process_welcome]) S2_happy.upon(rx_welcome, enter=S2_happy, outputs=[process_welcome])
S2_happy.upon(got_phase, enter=S2_happy, outputs=[W_received]) S2_happy.upon(got_phase, enter=S2_happy, outputs=[W_received])
S2_happy.upon(got_version, enter=S2_happy, outputs=[process_version]) S2_happy.upon(got_version, enter=S2_happy, outputs=[process_version])
S2_happy.upon(scared, enter=S3_closing, outputs=[close_scared]) S2_happy.upon(scared, enter=S3_closing, outputs=[close_scared])
S2_happy.upon(close, enter=S3_closing, outputs=[close_happy]) S2_happy.upon(close, enter=S3_closing, outputs=[close_happy])
S2_happy.upon(send, enter=S2_happy, outputs=[S_send]) S2_happy.upon(send, enter=S2_happy, outputs=[S_send])
S2_happy.upon(rx_error, enter=S3_closing, outputs=[close_error])
S3_closing.upon(rx_welcome, enter=S3_closing, outputs=[]) S3_closing.upon(rx_welcome, enter=S3_closing, outputs=[])
S3_closing.upon(rx_error, enter=S3_closing, outputs=[])
S3_closing.upon(got_phase, enter=S3_closing, outputs=[]) S3_closing.upon(got_phase, enter=S3_closing, outputs=[])
S3_closing.upon(got_version, enter=S3_closing, outputs=[]) S3_closing.upon(got_version, enter=S3_closing, outputs=[])
S3_closing.upon(happy, enter=S3_closing, outputs=[]) S3_closing.upon(happy, enter=S3_closing, outputs=[])

View File

@ -1,5 +1,6 @@
from __future__ import print_function, absolute_import, unicode_literals from __future__ import print_function, absolute_import, unicode_literals
from hashlib import sha256 from hashlib import sha256
import six
from zope.interface import implementer from zope.interface import implementer
from attr import attrs, attrib from attr import attrs, attrib
from attr.validators import provides, instance_of from attr.validators import provides, instance_of
@ -22,10 +23,10 @@ def HKDF(skm, outlen, salt=None, CTXinfo=b""):
def derive_key(key, purpose, length=SecretBox.KEY_SIZE): def derive_key(key, purpose, length=SecretBox.KEY_SIZE):
if not isinstance(key, type(b"")): raise TypeError(type(key)) if not isinstance(key, type(b"")): raise TypeError(type(key))
if not isinstance(purpose, type(b"")): raise TypeError(type(purpose)) if not isinstance(purpose, type(b"")): raise TypeError(type(purpose))
if not isinstance(length, int): raise TypeError(type(length)) if not isinstance(length, six.integer_types): raise TypeError(type(length))
return HKDF(key, length, CTXinfo=purpose) return HKDF(key, length, CTXinfo=purpose)
def derive_phase_key(side, phase): def derive_phase_key(key, side, phase):
assert isinstance(side, type("")), type(side) assert isinstance(side, type("")), type(side)
assert isinstance(phase, type("")), type(phase) assert isinstance(phase, type("")), type(phase)
side_bytes = side.encode("ascii") side_bytes = side.encode("ascii")
@ -33,7 +34,7 @@ def derive_phase_key(side, phase):
purpose = (b"wormhole:phase:" purpose = (b"wormhole:phase:"
+ sha256(side_bytes).digest() + sha256(side_bytes).digest()
+ sha256(phase_bytes).digest()) + sha256(phase_bytes).digest())
return derive_key(purpose) return derive_key(key, purpose)
def decrypt_data(key, encrypted): def decrypt_data(key, encrypted):
assert isinstance(key, type(b"")), type(key) assert isinstance(key, type(b"")), type(key)
@ -55,6 +56,7 @@ def encrypt_data(key, plaintext):
@implementer(_interfaces.IKey) @implementer(_interfaces.IKey)
class Key(object): class Key(object):
_appid = attrib(validator=instance_of(type(u""))) _appid = attrib(validator=instance_of(type(u"")))
_side = attrib(validator=instance_of(type(u"")))
_timing = attrib(validator=provides(_interfaces.ITiming)) _timing = attrib(validator=provides(_interfaces.ITiming))
m = MethodicalMachine() m = MethodicalMachine()
@ -106,9 +108,13 @@ class Key(object):
assert isinstance(msg2, type(b"")) assert isinstance(msg2, type(b""))
with self._timing.add("pake2", waiting="crypto"): with self._timing.add("pake2", waiting="crypto"):
key = self._sp.finish(msg2) key = self._sp.finish(msg2)
self._my_versions = {}
self._M.add_message("version", self._my_versions)
self._B.got_verifier(derive_key(key, b"wormhole:verifier")) self._B.got_verifier(derive_key(key, b"wormhole:verifier"))
phase = "version"
data_key = derive_phase_key(key, self._side, phase)
my_versions = {} # TODO: get from Wormhole?
plaintext = dict_to_bytes(my_versions)
encrypted = encrypt_data(data_key, plaintext)
self._M.add_message(phase, encrypted)
self._R.got_key(key) self._R.got_key(key)
S0_know_nothing.upon(got_code, enter=S1_know_code, outputs=[build_pake]) S0_know_nothing.upon(got_code, enter=S1_know_code, outputs=[build_pake])

View File

@ -121,7 +121,10 @@ class Mailbox(object):
# from Send or Key # from Send or Key
@m.input() @m.input()
def add_message(self, phase, body): pass def add_message(self, phase, body):
assert isinstance(body, type(b"")), type(body)
#print("ADD_MESSAGE", phase, len(body))
pass
@m.output() @m.output()
@ -142,7 +145,7 @@ class Mailbox(object):
@m.output() @m.output()
def queue(self, phase, body): def queue(self, phase, body):
assert isinstance(phase, type("")), type(phase) assert isinstance(phase, type("")), type(phase)
assert isinstance(body, type(b"")), type(body) assert isinstance(body, type(b"")), (type(body), phase, body)
self._pending_outbound[phase] = body self._pending_outbound[phase] = body
@m.output() @m.output()
def store_mailbox_and_RC_tx_open_and_drain(self, mailbox): def store_mailbox_and_RC_tx_open_and_drain(self, mailbox):
@ -189,11 +192,11 @@ class Mailbox(object):
self._accept(phase, body) self._accept(phase, body)
def _accept(self, phase, body): def _accept(self, phase, body):
if phase not in self._processed: if phase not in self._processed:
self._O.got_message(phase, body)
self._processed.add(phase) self._processed.add(phase)
self._O.got_message(phase, body)
@m.output() @m.output()
def dequeue(self, phase, body): def dequeue(self, phase, body):
self._pending_outbound.pop(phase) self._pending_outbound.pop(phase, None)
@m.output() @m.output()
def record_mood(self, mood): def record_mood(self, mood):
self._mood = mood self._mood = mood
@ -235,8 +238,7 @@ class Mailbox(object):
S3B.upon(rx_claimed, enter=S3B, outputs=[]) S3B.upon(rx_claimed, enter=S3B, outputs=[])
S3B.upon(add_message, enter=S3B, outputs=[queue, RC_tx_add]) S3B.upon(add_message, enter=S3B, outputs=[queue, RC_tx_add])
S4A.upon(connected, enter=S4B, S4A.upon(connected, enter=S4B, outputs=[RC_tx_open, drain, RC_tx_release])
outputs=[RC_tx_open, drain, RC_tx_release])
S4A.upon(add_message, enter=S4A, outputs=[queue]) S4A.upon(add_message, enter=S4A, outputs=[queue])
S4B.upon(lost, enter=S4A, outputs=[]) S4B.upon(lost, enter=S4A, outputs=[])
S4B.upon(add_message, enter=S4B, outputs=[queue, RC_tx_add]) S4B.upon(add_message, enter=S4B, outputs=[queue, RC_tx_add])

View File

@ -25,6 +25,7 @@ class Order(object):
def S1_yes_pake(self): pass def S1_yes_pake(self): pass
def got_message(self, phase, body): def got_message(self, phase, body):
#print("ORDER[%s].got_message(%s)" % (self._side, phase))
assert isinstance(phase, type("")), type(phase) assert isinstance(phase, type("")), type(phase)
assert isinstance(body, type(b"")), type(body) assert isinstance(body, type(b"")), type(body)
if phase == "pake": if phase == "pake":

View File

@ -35,7 +35,7 @@ class Receive(object):
assert isinstance(phase, type("")), type(phase) assert isinstance(phase, type("")), type(phase)
assert isinstance(body, type(b"")), type(body) assert isinstance(body, type(b"")), type(body)
assert self._key assert self._key
data_key = derive_phase_key(self._side, phase) data_key = derive_phase_key(self._key, self._side, phase)
try: try:
plaintext = decrypt_data(data_key, body) plaintext = decrypt_data(data_key, body)
except CryptoError: except CryptoError:

View File

@ -26,12 +26,12 @@ class WSClient(websocket.WebSocketClientProtocol):
self._RC.ws_open(self) self._RC.ws_open(self)
def onMessage(self, payload, isBinary): def onMessage(self, payload, isBinary):
#print("onMessage")
assert not isBinary assert not isBinary
try: try:
self._RC.ws_message(payload) self._RC.ws_message(payload)
except: except:
print("LOGGING") from twisted.python.failure import Failure
print("LOGGING", Failure())
log.err() log.err()
raise raise
@ -57,6 +57,10 @@ class WSFactory(websocket.WebSocketClientFactory):
#proto.wormhole_open = False #proto.wormhole_open = False
return proto return proto
def dmsg(side, text):
offset = int(side, 16) % 20
print(" "*offset, text)
@attrs @attrs
@implementer(_interfaces.IRendezvousConnector) @implementer(_interfaces.IRendezvousConnector)
class RendezvousConnector(object): class RendezvousConnector(object):
@ -66,7 +70,7 @@ class RendezvousConnector(object):
_reactor = attrib() _reactor = attrib()
_journal = attrib(validator=provides(_interfaces.IJournal)) _journal = attrib(validator=provides(_interfaces.IJournal))
_timing = attrib(validator=provides(_interfaces.ITiming)) _timing = attrib(validator=provides(_interfaces.ITiming))
DEBUG = False DEBUG = True
def __attrs_post_init__(self): def __attrs_post_init__(self):
self._ws = None self._ws = None
@ -124,6 +128,8 @@ class RendezvousConnector(object):
# from our WSClient (the WebSocket protocol) # from our WSClient (the WebSocket protocol)
def ws_open(self, proto): def ws_open(self, proto):
if self.DEBUG:
dmsg(self._side, "R.connected")
self._ws = proto self._ws = proto
self._tx("bind", appid=self._appid, side=self._side) self._tx("bind", appid=self._appid, side=self._side)
self._C.connected() self._C.connected()
@ -132,7 +138,11 @@ class RendezvousConnector(object):
def ws_message(self, payload): def ws_message(self, payload):
msg = bytes_to_dict(payload) msg = bytes_to_dict(payload)
if self.DEBUG and msg["type"]!="ack": print("DIS", msg["type"], msg) if self.DEBUG and msg["type"]!="ack":
dmsg(self._side, "R.rx(%s %s%s)" %
(msg["type"], msg.get("phase",""),
"[mine]" if msg.get("side","") == self._side else "",
))
self._timing.add("ws_receive", _side=self._side, message=msg) self._timing.add("ws_receive", _side=self._side, message=msg)
mtype = msg["type"] mtype = msg["type"]
meth = getattr(self, "_response_handle_"+mtype, None) meth = getattr(self, "_response_handle_"+mtype, None)
@ -143,6 +153,8 @@ class RendezvousConnector(object):
return meth(msg) return meth(msg)
def ws_close(self, wasClean, code, reason): def ws_close(self, wasClean, code, reason):
if self.DEBUG:
dmsg(self._side, "R.lost")
self._ws = None self._ws = None
self._C.lost() self._C.lost()
self._M.lost() self._M.lost()
@ -158,9 +170,10 @@ class RendezvousConnector(object):
# their receives, and vice versa. They are also correlated with the # their receives, and vice versa. They are also correlated with the
# ACKs we get back from the server (which we otherwise ignore). There # ACKs we get back from the server (which we otherwise ignore). There
# are so few messages, 16 bits is enough to be mostly-unique. # are so few messages, 16 bits is enough to be mostly-unique.
if self.DEBUG: print("SEND", mtype)
kwargs["id"] = bytes_to_hexstr(os.urandom(2)) kwargs["id"] = bytes_to_hexstr(os.urandom(2))
kwargs["type"] = mtype kwargs["type"] = mtype
if self.DEBUG:
dmsg(self._side, "R.tx(%s %s)" % (mtype.upper(), kwargs.get("phase", "")))
payload = dict_to_bytes(kwargs) payload = dict_to_bytes(kwargs)
self._timing.add("ws_send", _side=self._side, **kwargs) self._timing.add("ws_send", _side=self._side, **kwargs)
self._ws.sendMessage(payload, False) self._ws.sendMessage(payload, False)
@ -184,6 +197,11 @@ class RendezvousConnector(object):
def _response_handle_ack(self, msg): def _response_handle_ack(self, msg):
pass pass
def _response_handle_error(self, msg):
err = msg["error"]
orig = msg["orig"]
self._B.rx_error(err, orig)
def _response_handle_welcome(self, msg): def _response_handle_welcome(self, msg):
self._B.rx_welcome(msg["welcome"]) self._B.rx_welcome(msg["welcome"])

View File

@ -13,6 +13,9 @@ class Send(object):
_timing = attrib(validator=provides(_interfaces.ITiming)) _timing = attrib(validator=provides(_interfaces.ITiming))
m = MethodicalMachine() m = MethodicalMachine()
def __attrs_post_init__(self):
self._queue = []
def wire(self, mailbox): def wire(self, mailbox):
self._M = _interfaces.IMailbox(mailbox) self._M = _interfaces.IMailbox(mailbox)
@ -49,7 +52,8 @@ class Send(object):
self._encrypt_and_send(phase, plaintext) self._encrypt_and_send(phase, plaintext)
def _encrypt_and_send(self, phase, plaintext): def _encrypt_and_send(self, phase, plaintext):
data_key = derive_phase_key(self._side, phase) assert self._key
data_key = derive_phase_key(self._key, self._side, phase)
encrypted = encrypt_data(data_key, plaintext) encrypted = encrypt_data(data_key, plaintext)
self._M.add_message(phase, encrypted) self._M.add_message(phase, encrypted)

View File

@ -7,7 +7,7 @@ from ._interfaces import IWormhole
from .util import bytes_to_hexstr from .util import bytes_to_hexstr
from .timing import DebugTiming from .timing import DebugTiming
from .journal import ImmediateJournal from .journal import ImmediateJournal
from ._boss import Boss from ._boss import Boss, WormholeError
# We can provide different APIs to different apps: # We can provide different APIs to different apps:
# * Deferreds # * Deferreds
@ -61,6 +61,9 @@ class _DelegatedWormhole(object):
def closed(self, result): def closed(self, result):
self._delegate.wormhole_closed(result) self._delegate.wormhole_closed(result)
class WormholeClosed(Exception):
pass
@implementer(IWormhole) @implementer(IWormhole)
class _DeferredWormhole(object): class _DeferredWormhole(object):
def __init__(self): def __init__(self):
@ -70,6 +73,7 @@ class _DeferredWormhole(object):
self._verifier_observers = [] self._verifier_observers = []
self._received_data = [] self._received_data = []
self._received_observers = [] self._received_observers = []
self._closed_observers = []
def _set_boss(self, boss): def _set_boss(self, boss):
self._boss = boss self._boss = boss
@ -107,6 +111,9 @@ class _DeferredWormhole(object):
self._boss.send(plaintext) self._boss.send(plaintext)
def close(self): def close(self):
self._boss.close() self._boss.close()
d = defer.Deferred()
self._closed_observers.append(d)
return d
# from below # from below
def got_code(self, code): def got_code(self, code):
@ -127,7 +134,17 @@ class _DeferredWormhole(object):
self._received_data.append(plaintext) self._received_data.append(plaintext)
def closed(self, result): def closed(self, result):
print("closed", result) print("closed", result, type(result))
if isinstance(result, WormholeError):
e = result
else:
e = WormholeClosed(result)
for d in self._verifier_observers:
d.errback(e)
for d in self._received_observers:
d.errback(e)
for d in self._closed_observers:
d.callback(result)
def _wormhole(appid, relay_url, reactor, delegate=None, def _wormhole(appid, relay_url, reactor, delegate=None,
tor_manager=None, timing=None, tor_manager=None, timing=None,