magic-wormhole/src/wormhole/_boss.py

174 lines
5.9 KiB
Python
Raw Normal View History

from zope.interface import implementer
from automat import MethodicalMachine
from . import _interfaces
from ._mailbox import Mailbox
from ._send import Send
from ._order import Order
from ._key import Key
from ._receive import Receive
from ._rendezvous import RendezvousConnector
from ._nameplate import NameplateListing
from ._code import Code
2017-02-22 20:51:53 +00:00
from .util import bytes_to_dict
@implementer(_interfaces.IBoss)
class Boss:
2017-02-15 20:11:17 +00:00
m = MethodicalMachine()
def __init__(self, side, reactor, timing):
self._reactor = reactor
self._M = Mailbox(side)
self._S = Send(side, timing)
self._O = Order(side, timing)
self._K = Key(timing)
self._R = Receive(side, timing)
self._RC = RendezvousConnector(side, timing, reactor)
self._NL = NameplateListing()
self._C = Code(timing)
self._M.wire(self, self._RC, self._O)
self._S.wire(self._M)
self._O.wire(self._K, self._R)
self._K.wire(self, self._M, self._R)
self._R.wire(self, self._K, self._S)
2017-02-22 20:51:53 +00:00
self._RC.wire(self, self._M, self._C, self._NL)
self._NL.wire(self._RC, self._C)
self._C.wire(self, self._RC, self._NL)
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
# and these are the state-machine transition functions, which don't take
# args
@m.state(initial=True)
def S0_empty(self): pass
2017-02-15 20:11:17 +00:00
@m.state()
def S1_lonely(self): pass
2017-02-15 20:11:17 +00:00
@m.state()
def S2_happy(self): pass
2017-02-15 20:11:17 +00:00
@m.state()
def S3_closing(self): pass
2017-02-15 20:11:17 +00:00
@m.state(terminal=True)
def S4_closed(self): pass
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).
def input(self, stdio):
self._C.input(stdio)
def allocate(self, code_length):
self._C.allocate(code_length)
def set_code(self, code):
self._C.set_code(code)
2017-02-15 20:11:17 +00:00
@m.input()
2017-02-22 20:51:53 +00:00
def send(self, phase, plaintext): pass
2017-02-15 20:11:17 +00:00
@m.input()
def close(self): pass
2017-02-15 20:11:17 +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
# Key sends (got_verifier, scared)
# Receive sends (got_message, happy, scared)
2017-02-15 20:11:17 +00:00
@m.input()
def happy(self): pass
2017-02-15 20:11:17 +00:00
@m.input()
def scared(self): pass
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)
if phase == "version":
self.got_version(plaintext)
else:
self.got_phase(phase, plaintext)
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()
def got_phase(self, phase, plaintext): pass
2017-02-15 20:11:17 +00:00
@m.input()
def got_verifier(self, verifier): pass
# Mailbox sends closed
2017-02-15 20:11:17 +00:00
@m.input()
def closed(self): pass
2017-02-15 20:11:17 +00:00
@m.output()
2017-02-23 00:56:39 +00:00
def do_got_code(self, code):
nameplate = code.split("-")[0]
self._M.set_nameplate(nameplate)
2017-02-23 00:56:39 +00:00
self._K.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):
self._their_versions = bytes_to_dict(plaintext)
# ignored for now
2017-02-15 20:11:17 +00:00
@m.output()
2017-02-22 20:51:53 +00:00
def S_send(self, phase, plaintext):
self._S.send(phase, plaintext)
2017-02-15 20:11:17 +00:00
@m.output()
def close_scared(self):
self._M.close("scary")
2017-02-15 20:11:17 +00:00
@m.output()
def close_lonely(self):
self._M.close("lonely")
@m.output()
def close_happy(self):
self._M.close("happy")
2017-02-15 20:11:17 +00:00
@m.output()
def A_received(self, phase, plaintext):
self._A.received(phase, plaintext)
@m.output()
def A_got_verifier(self, verifier):
self._A.got_verifier(verifier)
2017-02-15 20:11:17 +00:00
@m.output()
def A_closed(self):
result = "???"
self._A.closed(result)
S0_empty.upon(send, enter=S0_empty, outputs=[S_send])
2017-02-23 00:56:39 +00:00
S0_empty.upon(got_code, enter=S1_lonely, outputs=[do_got_code])
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])
S1_lonely.upon(got_verifier, enter=S1_lonely, outputs=[A_got_verifier])
S2_happy.upon(got_phase, enter=S2_happy, outputs=[A_received])
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])
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=[])
S3_closing.upon(closed, enter=S4_closed, outputs=[A_closed])
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=[])