2017-02-24 02:11:07 +00:00
|
|
|
from __future__ import print_function, absolute_import, unicode_literals
|
2017-02-25 02:30:00 +00:00
|
|
|
import re
|
|
|
|
import six
|
2017-02-22 19:26:11 +00:00
|
|
|
from zope.interface import implementer
|
2017-02-23 23:57:24 +00:00
|
|
|
from attr import attrs, attrib
|
|
|
|
from attr.validators import provides, instance_of
|
2017-02-25 02:30:00 +00:00
|
|
|
from twisted.python import log
|
2017-02-22 19:26:11 +00:00
|
|
|
from automat import MethodicalMachine
|
|
|
|
from . import _interfaces
|
2017-02-26 11:57:58 +00:00
|
|
|
from ._nameplate import Nameplate
|
2017-02-22 19:26:11 +00:00
|
|
|
from ._mailbox import Mailbox
|
|
|
|
from ._send import Send
|
|
|
|
from ._order import Order
|
|
|
|
from ._key import Key
|
|
|
|
from ._receive import Receive
|
|
|
|
from ._rendezvous import RendezvousConnector
|
2017-03-03 14:22:40 +00:00
|
|
|
from ._lister import Lister
|
2017-02-22 19:26:11 +00:00
|
|
|
from ._code import Code
|
2017-02-26 11:57:58 +00:00
|
|
|
from ._terminator import Terminator
|
2017-03-07 11:09:25 +00:00
|
|
|
from .errors import ServerError, LonelyError, WrongPasswordError, KeyFormatError
|
2017-02-22 20:51:53 +00:00
|
|
|
from .util import bytes_to_dict
|
2017-02-22 19:26:11 +00:00
|
|
|
|
2017-02-23 02:21:47 +00:00
|
|
|
@attrs
|
2017-02-22 21:45:18 +00:00
|
|
|
@implementer(_interfaces.IBoss)
|
2017-02-23 23:57:24 +00:00
|
|
|
class Boss(object):
|
2017-02-24 01:29:56 +00:00
|
|
|
_W = attrib()
|
2017-02-23 02:21:47 +00:00
|
|
|
_side = attrib(validator=instance_of(type(u"")))
|
|
|
|
_url = attrib(validator=instance_of(type(u"")))
|
|
|
|
_appid = attrib(validator=instance_of(type(u"")))
|
2017-03-04 11:43:42 +00:00
|
|
|
_welcome_handler = attrib() # TODO: validator: callable
|
2017-02-23 02:21:47 +00:00
|
|
|
_reactor = attrib()
|
|
|
|
_journal = attrib(validator=provides(_interfaces.IJournal))
|
2017-03-04 12:07:31 +00:00
|
|
|
_tor_manager = attrib() # TODO: ITorManager or None
|
2017-02-23 02:21:47 +00:00
|
|
|
_timing = attrib(validator=provides(_interfaces.ITiming))
|
2017-02-15 20:11:17 +00:00
|
|
|
m = MethodicalMachine()
|
2017-03-02 03:55:13 +00:00
|
|
|
@m.setTrace()
|
2017-03-03 07:59:24 +00:00
|
|
|
def set_trace(): pass # pragma: no cover
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-02-24 01:29:56 +00:00
|
|
|
def __attrs_post_init__(self):
|
2017-02-26 11:57:58 +00:00
|
|
|
self._N = Nameplate()
|
2017-02-23 02:21:47 +00:00
|
|
|
self._M = Mailbox(self._side)
|
|
|
|
self._S = Send(self._side, self._timing)
|
|
|
|
self._O = Order(self._side, self._timing)
|
2017-02-25 02:30:00 +00:00
|
|
|
self._K = Key(self._appid, self._side, self._timing)
|
2017-02-23 02:21:47 +00:00
|
|
|
self._R = Receive(self._side, self._timing)
|
2017-02-24 01:29:56 +00:00
|
|
|
self._RC = RendezvousConnector(self._url, self._appid, self._side,
|
|
|
|
self._reactor, self._journal,
|
2017-03-04 12:07:31 +00:00
|
|
|
self._tor_manager, self._timing)
|
2017-03-03 14:22:40 +00:00
|
|
|
self._L = Lister()
|
2017-02-23 02:21:47 +00:00
|
|
|
self._C = Code(self._timing)
|
2017-02-26 11:57:58 +00:00
|
|
|
self._T = Terminator()
|
2017-02-22 19:26:11 +00:00
|
|
|
|
2017-02-26 11:57:58 +00:00
|
|
|
self._N.wire(self._M, self._RC, self._T)
|
|
|
|
self._M.wire(self._N, self._RC, self._O, self._T)
|
2017-02-22 19:26:11 +00:00
|
|
|
self._S.wire(self._M)
|
|
|
|
self._O.wire(self._K, self._R)
|
|
|
|
self._K.wire(self, self._M, self._R)
|
2017-03-03 07:59:45 +00:00
|
|
|
self._R.wire(self, self._S)
|
2017-03-03 14:22:40 +00:00
|
|
|
self._RC.wire(self, self._N, self._M, self._C, self._L, self._T)
|
|
|
|
self._L.wire(self._RC, self._C)
|
|
|
|
self._C.wire(self, self._RC, self._L)
|
2017-02-26 11:57:58 +00:00
|
|
|
self._T.wire(self, self._RC, self._N, self._M)
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-02-24 01:29:56 +00:00
|
|
|
self._next_tx_phase = 0
|
|
|
|
self._next_rx_phase = 0
|
|
|
|
self._rx_phases = {} # phase -> plaintext
|
|
|
|
|
2017-02-25 02:30:00 +00:00
|
|
|
self._result = "empty"
|
|
|
|
|
2017-02-15 20:11:17 +00:00
|
|
|
# these methods are called from outside
|
|
|
|
def start(self):
|
2017-02-22 20:51:53 +00:00
|
|
|
self._RC.start()
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-03-02 03:55:13 +00:00
|
|
|
def _set_trace(self, client_name, which, logger):
|
|
|
|
names = {"B": self, "N": self._N, "M": self._M, "S": self._S,
|
|
|
|
"O": self._O, "K": self._K, "R": self._R,
|
2017-03-03 14:22:40 +00:00
|
|
|
"RC": self._RC, "L": self._L, "C": self._C,
|
2017-03-02 03:55:13 +00:00
|
|
|
"T": self._T}
|
|
|
|
for machine in which.split():
|
|
|
|
def tracer(old_state, input, new_state, machine=machine):
|
|
|
|
print("%s.%s[%s].%s -> [%s]" % (client_name, machine,
|
|
|
|
old_state, input, new_state))
|
|
|
|
names[machine].set_trace(tracer)
|
|
|
|
|
2017-03-03 22:19:48 +00:00
|
|
|
def serialize(self):
|
|
|
|
raise NotImplemented
|
|
|
|
|
2017-02-15 20:11:17 +00:00
|
|
|
# and these are the state-machine transition functions, which don't take
|
|
|
|
# args
|
2017-02-22 19:26:11 +00:00
|
|
|
@m.state(initial=True)
|
2017-03-03 07:59:24 +00:00
|
|
|
def S0_empty(self): pass # pragma: no cover
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.state()
|
2017-03-03 07:59:24 +00:00
|
|
|
def S1_lonely(self): pass # pragma: no cover
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.state()
|
2017-03-03 07:59:24 +00:00
|
|
|
def S2_happy(self): pass # pragma: no cover
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.state()
|
2017-03-03 07:59:24 +00:00
|
|
|
def S3_closing(self): pass # pragma: no cover
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.state(terminal=True)
|
2017-03-03 07:59:24 +00:00
|
|
|
def S4_closed(self): pass # pragma: no cover
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-02-23 00:56:39 +00:00
|
|
|
# from the Wormhole
|
|
|
|
|
|
|
|
# input/allocate/set_code are regular methods, not state-transition
|
|
|
|
# inputs. We expect them to be called just after initialization, while
|
|
|
|
# we're in the S0_empty state. You must call exactly one of them, and the
|
|
|
|
# call must happen while we're in S0_empty, which makes them good
|
|
|
|
# candiates for being a proper @m.input, but set_code() will immediately
|
|
|
|
# (reentrantly) cause self.got_code() to be fired, which is messy. These
|
|
|
|
# are all passthroughs to the Code machine, so one alternative would be
|
|
|
|
# to have Wormhole call Code.{input,allocate,set_code} instead, but that
|
|
|
|
# would require the Wormhole to be aware of Code (whereas right now
|
|
|
|
# Wormhole only knows about this Boss instance, and everything else is
|
|
|
|
# hidden away).
|
2017-02-24 02:23:55 +00:00
|
|
|
def input_code(self, stdio):
|
|
|
|
self._C.input_code(stdio)
|
|
|
|
def allocate_code(self, code_length):
|
|
|
|
self._C.allocate_code(code_length)
|
2017-02-23 00:56:39 +00:00
|
|
|
def set_code(self, code):
|
2017-03-07 11:09:25 +00:00
|
|
|
if ' ' in code:
|
|
|
|
raise KeyFormatError("code (%s) contains spaces." % code)
|
2017-02-23 00:56:39 +00:00
|
|
|
self._C.set_code(code)
|
|
|
|
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-24 01:29:56 +00:00
|
|
|
def send(self, plaintext): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-22 19:26:11 +00:00
|
|
|
def close(self): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-03-03 07:55:59 +00:00
|
|
|
# from RendezvousConnector. rx_error an error message from the server
|
|
|
|
# (probably because of something we did, or due to CrowdedError). error
|
|
|
|
# is when an exception happened while it tried to deliver something else
|
2017-02-24 02:11:07 +00:00
|
|
|
@m.input()
|
|
|
|
def rx_welcome(self, welcome): pass
|
2017-03-03 07:55:59 +00:00
|
|
|
@m.input()
|
|
|
|
def rx_error(self, errmsg, orig): pass
|
|
|
|
@m.input()
|
|
|
|
def error(self, err): pass
|
2017-02-24 02:11:07 +00:00
|
|
|
|
2017-02-23 00:56:39 +00:00
|
|
|
# from Code (provoked by input/allocate/set_code)
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-23 00:56:39 +00:00
|
|
|
def got_code(self, code): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-03-04 09:55:42 +00:00
|
|
|
# Key sends (got_key, got_verifier, scared)
|
2017-02-22 19:26:11 +00:00
|
|
|
# Receive sends (got_message, happy, scared)
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-22 19:26:11 +00:00
|
|
|
def happy(self): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-22 19:26:11 +00:00
|
|
|
def scared(self): pass
|
2017-02-25 02:30:00 +00:00
|
|
|
|
2017-02-22 19:26:11 +00:00
|
|
|
def got_message(self, phase, plaintext):
|
2017-02-22 20:51:53 +00:00
|
|
|
assert isinstance(phase, type("")), type(phase)
|
|
|
|
assert isinstance(plaintext, type(b"")), type(plaintext)
|
2017-02-22 19:26:11 +00:00
|
|
|
if phase == "version":
|
|
|
|
self.got_version(plaintext)
|
2017-02-25 02:30:00 +00:00
|
|
|
elif re.search(r'^\d+$', phase):
|
|
|
|
self.got_phase(int(phase), plaintext)
|
2017-02-22 19:26:11 +00:00
|
|
|
else:
|
2017-02-25 02:30:00 +00:00
|
|
|
# Ignore unrecognized phases, for forwards-compatibility. Use
|
|
|
|
# log.err so tests will catch surprises.
|
|
|
|
log.err("received unknown phase '%s'" % phase)
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-23 00:56:39 +00:00
|
|
|
def got_version(self, plaintext): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-22 19:26:11 +00:00
|
|
|
def got_phase(self, phase, plaintext): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-03-04 09:55:42 +00:00
|
|
|
def got_key(self, key): pass
|
|
|
|
@m.input()
|
2017-02-22 19:26:11 +00:00
|
|
|
def got_verifier(self, verifier): pass
|
|
|
|
|
2017-02-26 11:57:58 +00:00
|
|
|
# Terminator sends closed
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.input()
|
2017-02-22 19:26:11 +00:00
|
|
|
def closed(self): pass
|
2017-02-15 20:11:17 +00:00
|
|
|
|
|
|
|
|
2017-02-24 02:11:07 +00:00
|
|
|
@m.output()
|
|
|
|
def process_welcome(self, welcome):
|
2017-03-04 11:43:42 +00:00
|
|
|
self._welcome_handler(welcome)
|
2017-02-24 02:11:07 +00:00
|
|
|
|
2017-02-22 19:26:11 +00:00
|
|
|
@m.output()
|
2017-02-23 00:56:39 +00:00
|
|
|
def do_got_code(self, code):
|
2017-02-22 19:26:11 +00:00
|
|
|
nameplate = code.split("-")[0]
|
2017-02-26 11:57:58 +00:00
|
|
|
self._N.set_nameplate(nameplate)
|
2017-02-23 00:56:39 +00:00
|
|
|
self._K.got_code(code)
|
2017-02-23 02:21:47 +00:00
|
|
|
self._W.got_code(code)
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.output()
|
2017-02-22 20:51:53 +00:00
|
|
|
def process_version(self, plaintext):
|
2017-03-04 10:36:19 +00:00
|
|
|
# most of this is wormhole-to-wormhole, ignored for now
|
|
|
|
# in the future, this is how Dilation is signalled
|
2017-02-22 20:51:53 +00:00
|
|
|
self._their_versions = bytes_to_dict(plaintext)
|
2017-03-04 10:36:19 +00:00
|
|
|
# but this part is app-to-app
|
|
|
|
app_versions = self._their_versions.get("app_versions", {})
|
|
|
|
self._W.got_version(app_versions)
|
2017-02-15 20:11:17 +00:00
|
|
|
|
|
|
|
@m.output()
|
2017-02-24 01:29:56 +00:00
|
|
|
def S_send(self, plaintext):
|
2017-02-25 02:30:00 +00:00
|
|
|
assert isinstance(plaintext, type(b"")), type(plaintext)
|
2017-02-24 01:29:56 +00:00
|
|
|
phase = self._next_tx_phase
|
|
|
|
self._next_tx_phase += 1
|
2017-02-25 02:30:00 +00:00
|
|
|
self._S.send("%d" % phase, plaintext)
|
2017-02-15 20:11:17 +00:00
|
|
|
|
|
|
|
@m.output()
|
2017-03-03 07:55:59 +00:00
|
|
|
def close_error(self, errmsg, orig):
|
|
|
|
self._result = ServerError(errmsg)
|
2017-02-26 11:57:58 +00:00
|
|
|
self._T.close("errory")
|
2017-02-25 02:30:00 +00:00
|
|
|
@m.output()
|
2017-02-22 19:26:11 +00:00
|
|
|
def close_scared(self):
|
2017-02-25 02:30:00 +00:00
|
|
|
self._result = WrongPasswordError()
|
2017-02-26 11:57:58 +00:00
|
|
|
self._T.close("scary")
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.output()
|
2017-02-22 19:26:11 +00:00
|
|
|
def close_lonely(self):
|
2017-03-03 07:55:59 +00:00
|
|
|
self._result = LonelyError()
|
2017-02-26 11:57:58 +00:00
|
|
|
self._T.close("lonely")
|
2017-02-22 19:26:11 +00:00
|
|
|
@m.output()
|
|
|
|
def close_happy(self):
|
2017-02-25 02:30:00 +00:00
|
|
|
self._result = "happy"
|
2017-02-26 11:57:58 +00:00
|
|
|
self._T.close("happy")
|
2017-02-15 20:11:17 +00:00
|
|
|
|
|
|
|
@m.output()
|
2017-03-04 09:55:42 +00:00
|
|
|
def W_got_key(self, key):
|
|
|
|
self._W.got_key(key)
|
|
|
|
@m.output()
|
2017-02-23 02:21:47 +00:00
|
|
|
def W_got_verifier(self, verifier):
|
|
|
|
self._W.got_verifier(verifier)
|
2017-02-22 19:26:11 +00:00
|
|
|
@m.output()
|
2017-02-23 02:21:47 +00:00
|
|
|
def W_received(self, phase, plaintext):
|
2017-02-25 02:30:00 +00:00
|
|
|
assert isinstance(phase, six.integer_types), type(phase)
|
2017-02-24 01:29:56 +00:00
|
|
|
# we call Wormhole.received() in strict phase order, with no gaps
|
|
|
|
self._rx_phases[phase] = plaintext
|
|
|
|
while self._next_rx_phase in self._rx_phases:
|
2017-02-24 02:23:55 +00:00
|
|
|
self._W.received(self._rx_phases.pop(self._next_rx_phase))
|
2017-02-24 01:29:56 +00:00
|
|
|
self._next_rx_phase += 1
|
2017-02-15 20:11:17 +00:00
|
|
|
|
2017-03-03 07:55:59 +00:00
|
|
|
@m.output()
|
|
|
|
def W_close_with_error(self, err):
|
|
|
|
self._result = err # exception
|
|
|
|
self._W.closed(self._result)
|
|
|
|
|
2017-02-15 20:11:17 +00:00
|
|
|
@m.output()
|
2017-02-23 02:21:47 +00:00
|
|
|
def W_closed(self):
|
2017-03-03 07:55:59 +00:00
|
|
|
# result is either "happy" or a WormholeError of some sort
|
2017-02-25 02:30:00 +00:00
|
|
|
self._W.closed(self._result)
|
2017-02-22 19:26:11 +00:00
|
|
|
|
2017-02-24 01:29:56 +00:00
|
|
|
S0_empty.upon(close, enter=S3_closing, outputs=[close_lonely])
|
2017-02-22 19:26:11 +00:00
|
|
|
S0_empty.upon(send, enter=S0_empty, outputs=[S_send])
|
2017-02-24 02:11:07 +00:00
|
|
|
S0_empty.upon(rx_welcome, enter=S0_empty, outputs=[process_welcome])
|
2017-02-23 00:56:39 +00:00
|
|
|
S0_empty.upon(got_code, enter=S1_lonely, outputs=[do_got_code])
|
2017-02-25 02:30:00 +00:00
|
|
|
S0_empty.upon(rx_error, enter=S3_closing, outputs=[close_error])
|
2017-03-03 07:55:59 +00:00
|
|
|
S0_empty.upon(error, enter=S4_closed, outputs=[W_close_with_error])
|
|
|
|
|
2017-02-24 02:11:07 +00:00
|
|
|
S1_lonely.upon(rx_welcome, enter=S1_lonely, outputs=[process_welcome])
|
2017-02-22 19:26:11 +00:00
|
|
|
S1_lonely.upon(happy, enter=S2_happy, outputs=[])
|
|
|
|
S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared])
|
|
|
|
S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely])
|
|
|
|
S1_lonely.upon(send, enter=S1_lonely, outputs=[S_send])
|
2017-03-04 09:55:42 +00:00
|
|
|
S1_lonely.upon(got_key, enter=S1_lonely, outputs=[W_got_key])
|
2017-02-23 02:21:47 +00:00
|
|
|
S1_lonely.upon(got_verifier, enter=S1_lonely, outputs=[W_got_verifier])
|
2017-02-25 02:30:00 +00:00
|
|
|
S1_lonely.upon(rx_error, enter=S3_closing, outputs=[close_error])
|
2017-03-03 07:55:59 +00:00
|
|
|
S1_lonely.upon(error, enter=S4_closed, outputs=[W_close_with_error])
|
|
|
|
|
2017-02-24 02:11:07 +00:00
|
|
|
S2_happy.upon(rx_welcome, enter=S2_happy, outputs=[process_welcome])
|
2017-02-23 02:21:47 +00:00
|
|
|
S2_happy.upon(got_phase, enter=S2_happy, outputs=[W_received])
|
2017-02-22 19:26:11 +00:00
|
|
|
S2_happy.upon(got_version, enter=S2_happy, outputs=[process_version])
|
|
|
|
S2_happy.upon(scared, enter=S3_closing, outputs=[close_scared])
|
|
|
|
S2_happy.upon(close, enter=S3_closing, outputs=[close_happy])
|
|
|
|
S2_happy.upon(send, enter=S2_happy, outputs=[S_send])
|
2017-02-25 02:30:00 +00:00
|
|
|
S2_happy.upon(rx_error, enter=S3_closing, outputs=[close_error])
|
2017-03-03 07:55:59 +00:00
|
|
|
S2_happy.upon(error, enter=S4_closed, outputs=[W_close_with_error])
|
2017-02-22 19:26:11 +00:00
|
|
|
|
2017-02-24 02:11:07 +00:00
|
|
|
S3_closing.upon(rx_welcome, enter=S3_closing, outputs=[])
|
2017-02-25 02:30:00 +00:00
|
|
|
S3_closing.upon(rx_error, enter=S3_closing, outputs=[])
|
2017-02-22 19:26:11 +00:00
|
|
|
S3_closing.upon(got_phase, enter=S3_closing, outputs=[])
|
|
|
|
S3_closing.upon(got_version, enter=S3_closing, outputs=[])
|
|
|
|
S3_closing.upon(happy, enter=S3_closing, outputs=[])
|
|
|
|
S3_closing.upon(scared, enter=S3_closing, outputs=[])
|
|
|
|
S3_closing.upon(close, enter=S3_closing, outputs=[])
|
|
|
|
S3_closing.upon(send, enter=S3_closing, outputs=[])
|
2017-02-23 02:21:47 +00:00
|
|
|
S3_closing.upon(closed, enter=S4_closed, outputs=[W_closed])
|
2017-03-03 07:55:59 +00:00
|
|
|
S3_closing.upon(error, enter=S4_closed, outputs=[W_close_with_error])
|
2017-02-22 19:26:11 +00:00
|
|
|
|
2017-02-24 02:11:07 +00:00
|
|
|
S4_closed.upon(rx_welcome, enter=S4_closed, outputs=[])
|
2017-02-22 19:26:11 +00:00
|
|
|
S4_closed.upon(got_phase, enter=S4_closed, outputs=[])
|
|
|
|
S4_closed.upon(got_version, enter=S4_closed, outputs=[])
|
|
|
|
S4_closed.upon(happy, enter=S4_closed, outputs=[])
|
|
|
|
S4_closed.upon(scared, enter=S4_closed, outputs=[])
|
|
|
|
S4_closed.upon(close, enter=S4_closed, outputs=[])
|
|
|
|
S4_closed.upon(send, enter=S4_closed, outputs=[])
|
2017-03-03 07:55:59 +00:00
|
|
|
S4_closed.upon(error, enter=S4_closed, outputs=[])
|