more state-machine work
This commit is contained in:
		
							parent
							
								
									63ae3c63fc
								
							
						
					
					
						commit
						18f7ab9308
					
				| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from six.moves.urllib_parse import urlparse
 | 
					from six.moves.urllib_parse import urlparse
 | 
				
			||||||
from attr import attrs, attrib
 | 
					from attr import attrs, attrib
 | 
				
			||||||
from twisted.internet import protocol, reactor
 | 
					 | 
				
			||||||
from twisted.internet import defer, endpoints #, error
 | 
					from twisted.internet import defer, endpoints #, error
 | 
				
			||||||
from autobahn.twisted import websocket
 | 
					from autobahn.twisted import websocket
 | 
				
			||||||
from automat import MethodicalMachine
 | 
					from automat import MethodicalMachine
 | 
				
			||||||
| 
						 | 
					@ -45,30 +44,26 @@ class WSFactory(websocket.WebSocketClientFactory):
 | 
				
			||||||
        return proto
 | 
					        return proto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Dummy(protocol.Protocol):
 | 
					 | 
				
			||||||
    def connectionMade(self):
 | 
					 | 
				
			||||||
        print("connectionMade")
 | 
					 | 
				
			||||||
        reactor.callLater(1.0, self.factory.cm.onConnect, "fake ws")
 | 
					 | 
				
			||||||
        reactor.callLater(2.0, self.transport.loseConnection)
 | 
					 | 
				
			||||||
    def connectionLost(self, why):
 | 
					 | 
				
			||||||
        self.factory.cm.onClose(why)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# pip install (path to automat checkout)[visualize]
 | 
					# pip install (path to automat checkout)[visualize]
 | 
				
			||||||
# automat-visualize wormhole._connection
 | 
					# automat-visualize wormhole._connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WebSocketMachine(object):
 | 
					# We have one WSRelayClient for each wsurl we know about, and it lasts
 | 
				
			||||||
 | 
					# as long as its parent Wormhole does.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@attrs
 | 
				
			||||||
 | 
					class WSRelayClient(object):
 | 
				
			||||||
 | 
					    _wormhole = attrib()
 | 
				
			||||||
 | 
					    _ws_url = attrib()
 | 
				
			||||||
 | 
					    _reactor = attrib()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    m = MethodicalMachine()
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
    ALLOW_CLOSE = True
 | 
					    ALLOW_CLOSE = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, ws_url, reactor):
 | 
					    def __init__(self):
 | 
				
			||||||
        self._reactor = reactor
 | 
					        self._f = f = WSFactory(self._ws_url)
 | 
				
			||||||
        self._f = f = WSFactory(ws_url)
 | 
					 | 
				
			||||||
        f.setProtocolOptions(autoPingInterval=60, autoPingTimeout=600)
 | 
					        f.setProtocolOptions(autoPingInterval=60, autoPingTimeout=600)
 | 
				
			||||||
        f.connection_machine = self # calls onOpen and onClose
 | 
					        f.connection_machine = self # calls onOpen and onClose
 | 
				
			||||||
        #self._f = protocol.ClientFactory()
 | 
					        p = urlparse(self._ws_url)
 | 
				
			||||||
        #self._f.cm = self
 | 
					 | 
				
			||||||
        #self._f.protocol = Dummy
 | 
					 | 
				
			||||||
        p = urlparse(ws_url)
 | 
					 | 
				
			||||||
        self._ep = self._make_endpoint(p.hostname, p.port or 80)
 | 
					        self._ep = self._make_endpoint(p.hostname, p.port or 80)
 | 
				
			||||||
        self._connector = None
 | 
					        self._connector = None
 | 
				
			||||||
        self._done_d = defer.Deferred()
 | 
					        self._done_d = defer.Deferred()
 | 
				
			||||||
| 
						 | 
					@ -105,16 +100,16 @@ class WebSocketMachine(object):
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def d_errback(self, f): pass ; print("in d_errback", f)
 | 
					    def d_errback(self, f): pass ; print("in d_errback", f)
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def d_cancel(self): pass
 | 
					    def d_cancel(self, f): pass # XXX remove f
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def onOpen(self, ws): pass ; print("in onOpen")
 | 
					    def onOpen(self, ws): pass ; print("in onOpen")
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def onClose(self, f): pass
 | 
					    def onClose(self, f): pass # XXX maybe remove f
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def expire(self): pass
 | 
					    def expire(self): pass
 | 
				
			||||||
    if ALLOW_CLOSE:
 | 
					    if ALLOW_CLOSE:
 | 
				
			||||||
        @m.input()
 | 
					        @m.input()
 | 
				
			||||||
        def close(self): pass
 | 
					        def close(self, f): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def ep_connect(self):
 | 
					    def ep_connect(self):
 | 
				
			||||||
| 
						 | 
					@ -123,20 +118,26 @@ class WebSocketMachine(object):
 | 
				
			||||||
        self._d = self._ep.connect(self._f)
 | 
					        self._d = self._ep.connect(self._f)
 | 
				
			||||||
        self._d.addCallbacks(self.d_callback, self.d_errback)
 | 
					        self._d.addCallbacks(self.d_callback, self.d_errback)
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def handle_connection(self, ws):
 | 
					    def add_connection(self, ws):
 | 
				
			||||||
        print("handle_connection", ws)
 | 
					        print("add_connection", ws)
 | 
				
			||||||
        #self._wormhole.new_connection(Connection(ws))
 | 
					        self._connection = WSConnection(ws, self._wormhole.appid,
 | 
				
			||||||
 | 
					                                        self._wormhole.side, self)
 | 
				
			||||||
 | 
					        self._wormhole.add_connection(self._connection)
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def start_timer(self, f):
 | 
					    def remove_connection(self, f): # XXX remove f
 | 
				
			||||||
 | 
					        self._wormhole.remove_connection(self._connection)
 | 
				
			||||||
 | 
					        self._connection = None
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def start_timer(self, f): # XXX remove f
 | 
				
			||||||
        print("start_timer")
 | 
					        print("start_timer")
 | 
				
			||||||
        self._t = self._reactor.callLater(3.0, self.expire)
 | 
					        self._t = self._reactor.callLater(3.0, self.expire)
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def cancel_timer(self):
 | 
					    def cancel_timer(self, f): # XXX remove f
 | 
				
			||||||
        print("cancel_timer")
 | 
					        print("cancel_timer")
 | 
				
			||||||
        self._t.cancel()
 | 
					        self._t.cancel()
 | 
				
			||||||
        self._t = None
 | 
					        self._t = None
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def dropConnection(self):
 | 
					    def dropConnection(self, f): # XXX remove f
 | 
				
			||||||
        print("dropConnection")
 | 
					        print("dropConnection")
 | 
				
			||||||
        self._ws.dropConnection()
 | 
					        self._ws.dropConnection()
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
| 
						 | 
					@ -149,17 +150,19 @@ class WebSocketMachine(object):
 | 
				
			||||||
    first_time_connecting.upon(d_errback, enter=failed, outputs=[notify_fail])
 | 
					    first_time_connecting.upon(d_errback, enter=failed, outputs=[notify_fail])
 | 
				
			||||||
    first_time_connecting.upon(onClose, enter=failed, outputs=[notify_fail])
 | 
					    first_time_connecting.upon(onClose, enter=failed, outputs=[notify_fail])
 | 
				
			||||||
    if ALLOW_CLOSE:
 | 
					    if ALLOW_CLOSE:
 | 
				
			||||||
        first_time_connecting.upon(close, enter=disconnecting2, outputs=[d_cancel])
 | 
					        first_time_connecting.upon(close, enter=disconnecting2,
 | 
				
			||||||
 | 
					                                   outputs=[d_cancel])
 | 
				
			||||||
        disconnecting2.upon(d_errback, enter=closed, outputs=[])
 | 
					        disconnecting2.upon(d_errback, enter=closed, outputs=[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    negotiating.upon(onOpen, enter=open, outputs=[handle_connection])
 | 
					    negotiating.upon(onOpen, enter=open, outputs=[add_connection])
 | 
				
			||||||
    if ALLOW_CLOSE:
 | 
					    if ALLOW_CLOSE:
 | 
				
			||||||
        negotiating.upon(close, enter=disconnecting, outputs=[dropConnection])
 | 
					        negotiating.upon(close, enter=disconnecting, outputs=[dropConnection])
 | 
				
			||||||
    negotiating.upon(onClose, enter=failed, outputs=[notify_fail])
 | 
					    negotiating.upon(onClose, enter=failed, outputs=[notify_fail])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    open.upon(onClose, enter=waiting, outputs=[start_timer])
 | 
					    open.upon(onClose, enter=waiting, outputs=[remove_connection, start_timer])
 | 
				
			||||||
    if ALLOW_CLOSE:
 | 
					    if ALLOW_CLOSE:
 | 
				
			||||||
        open.upon(close, enter=disconnecting, outputs=[dropConnection])
 | 
					        open.upon(close, enter=disconnecting,
 | 
				
			||||||
 | 
					                  outputs=[dropConnection, remove_connection])
 | 
				
			||||||
    connecting.upon(d_callback, enter=negotiating, outputs=[])
 | 
					    connecting.upon(d_callback, enter=negotiating, outputs=[])
 | 
				
			||||||
    connecting.upon(d_errback, enter=waiting, outputs=[start_timer])
 | 
					    connecting.upon(d_errback, enter=waiting, outputs=[start_timer])
 | 
				
			||||||
    connecting.upon(onClose, enter=waiting, outputs=[start_timer])
 | 
					    connecting.upon(onClose, enter=waiting, outputs=[start_timer])
 | 
				
			||||||
| 
						 | 
					@ -172,7 +175,7 @@ class WebSocketMachine(object):
 | 
				
			||||||
        disconnecting.upon(onClose, enter=closed, outputs=[])
 | 
					        disconnecting.upon(onClose, enter=closed, outputs=[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def tryit(reactor):
 | 
					def tryit(reactor):
 | 
				
			||||||
    cm = WebSocketMachine("ws://127.0.0.1:4000/v1", reactor)
 | 
					    cm = WSRelayClient(None, "ws://127.0.0.1:4000/v1", reactor)
 | 
				
			||||||
    print("_ConnectionMachine created")
 | 
					    print("_ConnectionMachine created")
 | 
				
			||||||
    print("start:", cm.start())
 | 
					    print("start:", cm.start())
 | 
				
			||||||
    print("waiting on _done_d to finish")
 | 
					    print("waiting on _done_d to finish")
 | 
				
			||||||
| 
						 | 
					@ -202,12 +205,14 @@ if __name__ == "__main__":
 | 
				
			||||||
    from twisted.internet.task import react
 | 
					    from twisted.internet.task import react
 | 
				
			||||||
    react(tryit)
 | 
					    react(tryit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# a new WSConnection is created each time the WSRelayClient gets through
 | 
				
			||||||
 | 
					# negotiation
 | 
				
			||||||
@attrs
 | 
					@attrs
 | 
				
			||||||
class Connection(object):
 | 
					class WSConnection(object):
 | 
				
			||||||
    _ws = attrib()
 | 
					    _ws = attrib()
 | 
				
			||||||
    _appid = attrib()
 | 
					    _appid = attrib()
 | 
				
			||||||
    _side = attrib()
 | 
					    _side = attrib()
 | 
				
			||||||
    _ws_machine = attrib()
 | 
					    _wsrc = attrib()
 | 
				
			||||||
    m = MethodicalMachine()
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.state(initial=True)
 | 
					    @m.state(initial=True)
 | 
				
			||||||
| 
						 | 
					@ -232,13 +237,13 @@ class Connection(object):
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def ack_bind(self): pass
 | 
					    def ack_bind(self): pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def c_set_nameplate(self): pass
 | 
					    def wsc_set_nameplate(self): pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def c_set_mailbox(self, mailbox): pass
 | 
					    def wsc_set_mailbox(self, mailbox): pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def c_remove_nameplate(self): pass
 | 
					    def wsc_release_nameplate(self): pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def c_remove_mailbox(self): pass
 | 
					    def wsc_release_mailbox(self): pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def ack_close(self): pass
 | 
					    def ack_close(self): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -248,34 +253,32 @@ class Connection(object):
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def notify_bound(self):
 | 
					    def notify_bound(self):
 | 
				
			||||||
        self._nameplate_machine.bound()
 | 
					        self._nameplate_machine.bound()
 | 
				
			||||||
 | 
					        self._connection.make_listing_machine()
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def m_set_mailbox(self, mailbox):
 | 
					    def m_set_mailbox(self, mailbox):
 | 
				
			||||||
        self._mailbox_machine.m_set_mailbox(mailbox)
 | 
					        self._mailbox_machine.m_set_mailbox(mailbox)
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def request_close(self):
 | 
					    def request_close(self):
 | 
				
			||||||
        self._ws_machine.close()
 | 
					        self._wsrc.close()
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def notify_close(self):
 | 
					    def notify_close(self):
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unbound.upon(bind, enter=binding, outputs=[send_bind])
 | 
					    unbound.upon(bind, enter=binding, outputs=[send_bind])
 | 
				
			||||||
    binding.upon(ack_bind, enter=neither, outputs=[notify_bound])
 | 
					    binding.upon(ack_bind, enter=neither, outputs=[notify_bound])
 | 
				
			||||||
    neither.upon(c_set_nameplate, enter=has_nameplate, outputs=[])
 | 
					    neither.upon(wsc_set_nameplate, enter=has_nameplate, outputs=[])
 | 
				
			||||||
    neither.upon(c_set_mailbox, enter=has_mailbox, outputs=[m_set_mailbox])
 | 
					    neither.upon(wsc_set_mailbox, enter=has_mailbox, outputs=[m_set_mailbox])
 | 
				
			||||||
    has_nameplate.upon(c_set_mailbox, enter=has_both, outputs=[m_set_mailbox])
 | 
					    has_nameplate.upon(wsc_set_mailbox, enter=has_both, outputs=[m_set_mailbox])
 | 
				
			||||||
    has_nameplate.upon(c_remove_nameplate, enter=closing, outputs=[request_close])
 | 
					    has_nameplate.upon(wsc_release_nameplate, enter=closing, outputs=[request_close])
 | 
				
			||||||
    has_mailbox.upon(c_set_nameplate, enter=has_both, outputs=[])
 | 
					    has_mailbox.upon(wsc_set_nameplate, enter=has_both, outputs=[])
 | 
				
			||||||
    has_mailbox.upon(c_remove_mailbox, enter=closing, outputs=[request_close])
 | 
					    has_mailbox.upon(wsc_release_mailbox, enter=closing, outputs=[request_close])
 | 
				
			||||||
    has_both.upon(c_remove_nameplate, enter=has_mailbox, outputs=[])
 | 
					    has_both.upon(wsc_release_nameplate, enter=has_mailbox, outputs=[])
 | 
				
			||||||
    has_both.upon(c_remove_mailbox, enter=has_nameplate, outputs=[])
 | 
					    has_both.upon(wsc_release_mailbox, enter=has_nameplate, outputs=[])
 | 
				
			||||||
    closing.upon(ack_close, enter=closed, outputs=[])
 | 
					    closing.upon(ack_close, enter=closed, outputs=[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NameplateMachine(object):
 | 
					class NameplateMachine(object):
 | 
				
			||||||
    m = MethodicalMachine()
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def bound(self):
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @m.state(initial=True)
 | 
					    @m.state(initial=True)
 | 
				
			||||||
    def unclaimed(self): pass # but bound
 | 
					    def unclaimed(self): pass # but bound
 | 
				
			||||||
    @m.state()
 | 
					    @m.state()
 | 
				
			||||||
| 
						 | 
					@ -284,112 +287,135 @@ class NameplateMachine(object):
 | 
				
			||||||
    def claimed(self): pass
 | 
					    def claimed(self): pass
 | 
				
			||||||
    @m.state()
 | 
					    @m.state()
 | 
				
			||||||
    def releasing(self): pass
 | 
					    def releasing(self): pass
 | 
				
			||||||
 | 
					    @m.state(terminal=True)
 | 
				
			||||||
 | 
					    def done(self): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.input()
 | 
					 | 
				
			||||||
    def list_nameplates(self): pass
 | 
					 | 
				
			||||||
    @m.input()
 | 
					 | 
				
			||||||
    def got_nameplates(self, nameplates): pass # response("nameplates")
 | 
					 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def learned_nameplate(self, nameplate):
 | 
					    def learned_nameplate(self, nameplate):
 | 
				
			||||||
        """Call learned_nameplate() when you learn the nameplate: either
 | 
					        """Call learned_nameplate() when you learn the nameplate: either
 | 
				
			||||||
        through allocation or code entry"""
 | 
					        through allocation or code entry"""
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def claim_acked(self, mailbox): pass # response("claimed")
 | 
					    def rx_claimed(self, mailbox): pass # response("claimed")
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def release(self): pass
 | 
					    def nm_release_nameplate(self): pass
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def release_acked(self): pass # response("released")
 | 
					    def release_acked(self): pass # response("released")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.output()
 | 
					 | 
				
			||||||
    def send_list_nameplates(self):
 | 
					 | 
				
			||||||
        self._ws_send_command("list")
 | 
					 | 
				
			||||||
    @m.output()
 | 
					 | 
				
			||||||
    def notify_nameplates(self, nameplates):
 | 
					 | 
				
			||||||
        # tell somebody
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def send_claim(self, nameplate):
 | 
					    def send_claim(self, nameplate):
 | 
				
			||||||
        self._ws_send_command("claim", nameplate=nameplate)
 | 
					        self._ws_send_command("claim", nameplate=nameplate)
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def c_set_nameplate(self, mailbox):
 | 
					    def wsc_set_nameplate(self, mailbox):
 | 
				
			||||||
        self._connection_machine.set_nameplate()
 | 
					        self._connection_machine.wsc_set_nameplate()
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def c_set_mailbox(self, mailbox):
 | 
					    def wsc_set_mailbox(self, mailbox):
 | 
				
			||||||
        self._connection_machine.set_mailbox()
 | 
					        self._connection_machine.wsc_set_mailbox()
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def mm_set_mailbox(self, mailbox):
 | 
				
			||||||
 | 
					        self._mm.mm_set_mailbox()
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def send_release(self):
 | 
					    def send_release(self):
 | 
				
			||||||
        self._ws_send_command("release")
 | 
					        self._ws_send_command("release")
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def notify_released(self):
 | 
					    def wsc_release_nameplate(self):
 | 
				
			||||||
        # let someone know, when both the mailbox and the nameplate are
 | 
					        # let someone know, when both the mailbox and the nameplate are
 | 
				
			||||||
        # released, the websocket can be closed, and we're done
 | 
					        # released, the websocket can be closed, and we're done
 | 
				
			||||||
        pass
 | 
					        self._wsc.wsc_release_nameplate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unclaimed.upon(list_nameplates, enter=unclaimed, outputs=[send_list_nameplates])
 | 
					 | 
				
			||||||
    unclaimed.upon(got_nameplates, enter=unclaimed, outputs=[notify_nameplates])
 | 
					 | 
				
			||||||
    unclaimed.upon(learned_nameplate, enter=claiming, outputs=[send_claim])
 | 
					    unclaimed.upon(learned_nameplate, enter=claiming, outputs=[send_claim])
 | 
				
			||||||
    claiming.upon(claim_acked, enter=claimed, outputs=[c_set_nameplate,
 | 
					    claiming.upon(rx_claimed, enter=claimed, outputs=[wsc_set_nameplate,
 | 
				
			||||||
                                                       c_set_mailbox])
 | 
					                                                      mm_set_mailbox,
 | 
				
			||||||
    claiming.upon(learned_nameplate, enter=claiming, outputs=[])
 | 
					                                                      wsc_set_mailbox])
 | 
				
			||||||
    claimed.upon(release, enter=releasing, outputs=[send_release])
 | 
					    #claiming.upon(learned_nameplate, enter=claiming, outputs=[])
 | 
				
			||||||
    claimed.upon(learned_nameplate, enter=claimed, outputs=[])
 | 
					    claimed.upon(nm_release_nameplate, enter=releasing, outputs=[send_release])
 | 
				
			||||||
    releasing.upon(release, enter=releasing, outputs=[])
 | 
					    #claimed.upon(learned_nameplate, enter=claimed, outputs=[])
 | 
				
			||||||
    releasing.upon(release_acked, enter=unclaimed, outputs=[notify_released])
 | 
					    #releasing.upon(release, enter=releasing, outputs=[])
 | 
				
			||||||
    releasing.upon(learned_nameplate, enter=releasing, outputs=[])
 | 
					    releasing.upon(release_acked, enter=done, outputs=[wsc_release_nameplate])
 | 
				
			||||||
 | 
					    #releasing.upon(learned_nameplate, enter=releasing, outputs=[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NameplateListingMachine(object):
 | 
				
			||||||
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self._list_nameplate_waiters = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ideally, each API request would spawn a new "list_nameplates" message
 | 
				
			||||||
 | 
					    # to the server, so the response would be maximally fresh, but that would
 | 
				
			||||||
 | 
					    # require correlating server request+response messages, and the protocol
 | 
				
			||||||
 | 
					    # is intended to be less stateful than that. So we offer a weaker
 | 
				
			||||||
 | 
					    # freshness property: if no server requests are in flight, then a new API
 | 
				
			||||||
 | 
					    # request will provoke a new server request, and the result will be
 | 
				
			||||||
 | 
					    # fresh. But if a server request is already in flight when a second API
 | 
				
			||||||
 | 
					    # request arrives, both requests will be satisfied by the same response.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.state(initial=True)
 | 
				
			||||||
 | 
					    def idle(self): pass
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def requesting(self): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def list_nameplates(self): pass # returns Deferred
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def response(self, message): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def add_deferred(self):
 | 
				
			||||||
 | 
					        d = defer.Deferred()
 | 
				
			||||||
 | 
					        self._list_nameplate_waiters.append(d)
 | 
				
			||||||
 | 
					        return d
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def send_request(self):
 | 
				
			||||||
 | 
					        self._connection.send_command("list")
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def distribute_response(self, message):
 | 
				
			||||||
 | 
					        nameplates = parse(message)
 | 
				
			||||||
 | 
					        waiters = self._list_nameplate_waiters
 | 
				
			||||||
 | 
					        self._list_nameplate_waiters = []
 | 
				
			||||||
 | 
					        for d in waiters:
 | 
				
			||||||
 | 
					            d.callback(nameplates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    idle.upon(list_nameplates, enter=requesting,
 | 
				
			||||||
 | 
					              outputs=[add_deferred, send_request],
 | 
				
			||||||
 | 
					              collector=lambda outs: outs[0])
 | 
				
			||||||
 | 
					    idle.upon(response, enter=idle, outputs=[])
 | 
				
			||||||
 | 
					    requesting.upon(list_nameplates, enter=requesting,
 | 
				
			||||||
 | 
					                    outputs=[add_deferred],
 | 
				
			||||||
 | 
					                    collector=lambda outs: outs[0])
 | 
				
			||||||
 | 
					    requesting.upon(response, enter=idle, outputs=[distribute_response])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # nlm._connection = c = Connection(ws)
 | 
				
			||||||
 | 
					    # nlm.list_nameplates().addCallback(display_completions)
 | 
				
			||||||
 | 
					    # c.register_dispatch("nameplates", nlm.response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MailboxMachine(object):
 | 
					class MailboxMachine(object):
 | 
				
			||||||
    m = MethodicalMachine()
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.state()
 | 
					    @m.state()
 | 
				
			||||||
    def closed(initial=True): pass
 | 
					    def unknown(initial=True): pass
 | 
				
			||||||
    @m.state()
 | 
					    @m.state()
 | 
				
			||||||
    def open(): pass
 | 
					    def mailbox_unused(): pass
 | 
				
			||||||
    @m.state()
 | 
					    @m.state()
 | 
				
			||||||
    def key_established(): pass
 | 
					    def mailbox_used(): pass
 | 
				
			||||||
    @m.state()
 | 
					 | 
				
			||||||
    def key_verified(): pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def m_set_code(self, code): pass
 | 
					    def mm_set_mailbox(self, mailbox): pass
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def add_connection(self, connection): pass
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def rx_message(self): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.input()
 | 
					 | 
				
			||||||
    def m_set_mailbox(self, mailbox):
 | 
					 | 
				
			||||||
        """Call m_set_mailbox() when you learn the mailbox id, either from
 | 
					 | 
				
			||||||
        the response to claim_nameplate, or because we started from a
 | 
					 | 
				
			||||||
        Wormhole Seed"""
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
    @m.input()
 | 
					 | 
				
			||||||
    def message_pake(self, pake): pass # reponse["message"][phase=pake]
 | 
					 | 
				
			||||||
    @m.input()
 | 
					 | 
				
			||||||
    def message_version(self, version): # response["message"][phase=version]
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
    @m.input()
 | 
					 | 
				
			||||||
    def message_app(self, msg): # response["message"][phase=\d+]
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
    @m.input()
 | 
					    @m.input()
 | 
				
			||||||
    def close(self): pass
 | 
					    def close(self): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def send_pake(self, pake):
 | 
					    def open_mailbox(self):
 | 
				
			||||||
        self._ws_send_command("add", phase="pake", body=XXX(pake))
 | 
					        self._mm.mm_set_mailbox(self._mailbox)
 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def send_version(self, pake): # XXX remove pake=
 | 
					    def nm_release_nameplate(self):
 | 
				
			||||||
        plaintext = dict_to_bytes(self._my_versions)
 | 
					        self._nm.nm_release_nameplate()
 | 
				
			||||||
        phase = "version"
 | 
					 | 
				
			||||||
        data_key = self._derive_phase_key(self._side, phase)
 | 
					 | 
				
			||||||
        encrypted = self._encrypt_data(data_key, plaintext)
 | 
					 | 
				
			||||||
        self._msg_send(phase, encrypted)
 | 
					 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def c_remove_mailbox(self):
 | 
					    def wsc_release_mailbox(self):
 | 
				
			||||||
        self._connection.c_remove_mailbox()
 | 
					        self._wsc.wsc_release_mailbox()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # decrypt, deliver up to app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @m.output()
 | 
					    @m.output()
 | 
				
			||||||
    def open_mailbox(self, mailbox):
 | 
					    def open_mailbox(self, mailbox):
 | 
				
			||||||
        self._ws_send_command("open", mailbox=mailbox)
 | 
					        self._ws_send_command("open", mailbox=mailbox)
 | 
				
			||||||
| 
						 | 
					@ -398,8 +424,159 @@ class MailboxMachine(object):
 | 
				
			||||||
    def close_mailbox(self, mood):
 | 
					    def close_mailbox(self, mood):
 | 
				
			||||||
        self._ws_send_command("close", mood=mood)
 | 
					        self._ws_send_command("close", mood=mood)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    closed.upon(m_set_mailbox, enter=open, outputs=[open_mailbox])
 | 
					    unknown.upon(mm_set_mailbox, enter=mailbox_unused, outputs=[open_mailbox])
 | 
				
			||||||
    open.upon(message_pake, enter=key_established, outputs=[send_pake,
 | 
					    mailbox_unused.upon(rx_message, enter=mailbox_used,
 | 
				
			||||||
                                                            send_version])
 | 
					                        outputs=[nm_release_nameplate])
 | 
				
			||||||
    key_established.upon(message_version, enter=key_verified, outputs=[])
 | 
					    #open.upon(message_pake, enter=key_established, outputs=[send_pake,
 | 
				
			||||||
    key_verified.upon(close, enter=closed, outputs=[c_remove_mailbox])
 | 
					    #                                                        send_version])
 | 
				
			||||||
 | 
					    #key_established.upon(message_version, enter=key_verified, outputs=[])
 | 
				
			||||||
 | 
					    #key_verified.upon(close, enter=closed, outputs=[wsc_release_mailbox])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Wormhole:
 | 
				
			||||||
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, ws_url, reactor):
 | 
				
			||||||
 | 
					        self._relay_client = WSRelayClient(self, ws_url, reactor)
 | 
				
			||||||
 | 
					        # This records all the messages we want the relay to have. Each time
 | 
				
			||||||
 | 
					        # we establish a connection, we'll send them all (and the relay
 | 
				
			||||||
 | 
					        # server will filter out duplicates). If we add any while a
 | 
				
			||||||
 | 
					        # connection is established, we'll send the new ones.
 | 
				
			||||||
 | 
					        self._outbound_messages = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self):
 | 
				
			||||||
 | 
					        self._relay_client.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def closed(initial=True): pass
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def know_code_not_mailbox(): pass
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def know_code_and_mailbox(): pass # no longer need nameplate
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def waiting_to_verify(): pass # key is established, want any message
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def open(): pass # key is verified, can post app messages
 | 
				
			||||||
 | 
					    @m.state(terminal=True)
 | 
				
			||||||
 | 
					    def failed(): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def deliver_message(self, message): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def w_set_seed(self, code, mailbox):
 | 
				
			||||||
 | 
					        """Call w_set_seed when we sprout a Wormhole Seed, which
 | 
				
			||||||
 | 
					        contains both the code and the mailbox"""
 | 
				
			||||||
 | 
					        self.w_set_code(code)
 | 
				
			||||||
 | 
					        self.w_set_mailbox(mailbox)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def w_set_code(self, code):
 | 
				
			||||||
 | 
					        """Call w_set_code when you learn the code, probably because the user
 | 
				
			||||||
 | 
					        typed it in."""
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def w_set_mailbox(self, mailbox):
 | 
				
			||||||
 | 
					        """Call w_set_mailbox() when you learn the mailbox id, from the
 | 
				
			||||||
 | 
					        response to claim_nameplate"""
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def rx_pake(self, pake): pass # reponse["message"][phase=pake]
 | 
				
			||||||
 | 
					    def rx_version(self, version): # response["message"][phase=version]
 | 
				
			||||||
 | 
					        their_verifier = com
 | 
				
			||||||
 | 
					        if OK:
 | 
				
			||||||
 | 
					            self.verify_good(verifier)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.verify_bad(f)
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def verify_good(self, verifier): pass
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def verify_bad(self, f): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def compute_and_post_pake(self, code):
 | 
				
			||||||
 | 
					        self._code = code
 | 
				
			||||||
 | 
					        self._pake = compute(code)
 | 
				
			||||||
 | 
					        self._post(pake=self._pake)
 | 
				
			||||||
 | 
					        self._ws_send_command("add", phase="pake", body=XXX(pake))
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def set_mailbox(self, mailbox):
 | 
				
			||||||
 | 
					        self._mailbox = mailbox
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def set_seed(self, code, mailbox):
 | 
				
			||||||
 | 
					        self._code = code
 | 
				
			||||||
 | 
					        self._mailbox = mailbox
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def deliver_message(self, message):
 | 
				
			||||||
 | 
					        self._qc.deliver_message(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def notify_verified(self, verifier):
 | 
				
			||||||
 | 
					        for d in self._verify_waiters:
 | 
				
			||||||
 | 
					            d.callback(verifier)
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def notify_failed(self, f):
 | 
				
			||||||
 | 
					        for d in self._verify_waiters:
 | 
				
			||||||
 | 
					            d.errback(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def compute_key_and_post_version(self, pake):
 | 
				
			||||||
 | 
					        self._key = x
 | 
				
			||||||
 | 
					        self._verifier = x
 | 
				
			||||||
 | 
					        plaintext = dict_to_bytes(self._my_versions)
 | 
				
			||||||
 | 
					        phase = "version"
 | 
				
			||||||
 | 
					        data_key = self._derive_phase_key(self._side, phase)
 | 
				
			||||||
 | 
					        encrypted = self._encrypt_data(data_key, plaintext)
 | 
				
			||||||
 | 
					        self._msg_send(phase, encrypted)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    closed.upon(w_set_code, enter=know_code_not_mailbox,
 | 
				
			||||||
 | 
					                outputs=[compute_and_post_pake])
 | 
				
			||||||
 | 
					    know_code_not_mailbox.upon(w_set_mailbox, enter=know_code_and_mailbox,
 | 
				
			||||||
 | 
					                               outputs=[set_mailbox])
 | 
				
			||||||
 | 
					    know_code_and_mailbox.upon(rx_pake, enter=waiting_to_verify,
 | 
				
			||||||
 | 
					                               outputs=[compute_key_and_post_version])
 | 
				
			||||||
 | 
					    waiting_to_verify.upon(verify_good, enter=open, outputs=[notify_verified])
 | 
				
			||||||
 | 
					    waiting_to_verify.upon(verify_bad, enter=failed, outputs=[notify_failed])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueueConnect:
 | 
				
			||||||
 | 
					    m = MethodicalMachine()
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self._outbound_messages = []
 | 
				
			||||||
 | 
					        self._connection = None
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def disconnected(): pass
 | 
				
			||||||
 | 
					    @m.state()
 | 
				
			||||||
 | 
					    def connected(): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def deliver_message(self, message): pass
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def connect(self, connection): pass
 | 
				
			||||||
 | 
					    @m.input()
 | 
				
			||||||
 | 
					    def disconnect(self): pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def remember_connection(self, connection):
 | 
				
			||||||
 | 
					        self._connection = connection
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def forget_connection(self):
 | 
				
			||||||
 | 
					        self._connection = None
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def queue_message(self, message):
 | 
				
			||||||
 | 
					        self._outbound_messages.append(message)
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def send_message(self, message):
 | 
				
			||||||
 | 
					        self._connection.send(message)
 | 
				
			||||||
 | 
					    @m.output()
 | 
				
			||||||
 | 
					    def send_queued_messages(self, connection):
 | 
				
			||||||
 | 
					        for m in self._outbound_messages:
 | 
				
			||||||
 | 
					            connection.send(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disconnected.upon(deliver_message, enter=disconnected, outputs=[queue_message])
 | 
				
			||||||
 | 
					    disconnected.upon(connect, enter=connected, outputs=[remember_connection,
 | 
				
			||||||
 | 
					                                                         send_queued_messages])
 | 
				
			||||||
 | 
					    connected.upon(deliver_message, enter=connected,
 | 
				
			||||||
 | 
					                   outputs=[queue_message, send_message])
 | 
				
			||||||
 | 
					    connected.upon(disconnect, enter=disconnected, outputs=[forget_connection])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user