diff --git a/docs/boss.dot b/docs/boss.dot index 053d06a..7f8d255 100644 --- a/docs/boss.dot +++ b/docs/boss.dot @@ -18,11 +18,11 @@ digraph { 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 [shape="box" label="T.close(errory)"] P_close_error -> S_closing 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\nN.set_nameplate\nK.got_code"] P0_build -> S1 S1 [label="S1: lonely" color="orange"] @@ -31,15 +31,15 @@ digraph { S1 -> P_close_error [label="rx_error"] S1 -> P_close_scary [label="scared" color="red"] S1 -> P_close_lonely [label="close"] - P_close_lonely [shape="box" label="M.close(lonely)"] + P_close_lonely [shape="box" label="T.close(lonely)"] P_close_lonely -> S_closing - P_close_scary [shape="box" label="M.close(scary)" color="red"] + P_close_scary [shape="box" label="T.close(scary)" color="red"] P_close_scary -> S_closing [color="red"] S2 [label="S2: happy" color="green"] S2 -> P2_close [label="close"] - P2_close [shape="box" label="M.close(happy)"] + P2_close [shape="box" label="T.close(happy)"] P2_close -> S_closing S2 -> P2_got_message [label="got_message"] diff --git a/docs/terminator.dot b/docs/terminator.dot index 2fe01b9..749eb3c 100644 --- a/docs/terminator.dot +++ b/docs/terminator.dot @@ -3,37 +3,37 @@ digraph { title [label="Terminator\nMachine" style="dotted"] initial [style="invis"] - initial -> Snm [style="dashed"] + initial -> Snmo [style="dashed"] - Snm [label="Snm:\nnameplate active\nmailbox active" color="orange"] - Sn [label="Sn:\nnameplate active\nmailbox done"] - Sm [label="Sm:\nnameplate done\nmailbox active" color="green"] - S0 [label="S0:\nnameplate done\nmailbox done"] + Snmo [label="Snmo:\nnameplate active\nmailbox active\nopen" color="orange"] + Sno [label="Sno:\nnameplate active\nmailbox done\nopen"] + Smo [label="Smo:\nnameplate done\nmailbox active\nopen" color="green"] + S0o [label="S0o:\nnameplate done\nmailbox done\nopen"] - Snm -> Sn [label="mailbox_done"] - Snm -> Sm [label="nameplate_done" color="orange"] - Sn -> S0 [label="nameplate_done"] - Sm -> S0 [label="mailbox_done"] + Snmo -> Sno [label="mailbox_done"] + Snmo -> Smo [label="nameplate_done" color="orange"] + Sno -> S0o [label="nameplate_done"] + Smo -> S0o [label="mailbox_done"] - Snm -> Snm_closing [label="close"] - Sn -> Sn_closing [label="close"] - Sm -> Sm_closing [label="close" color="red"] - S0 -> P_stop [label="close"] + Snmo -> Snm [label="close"] + Sno -> Sn [label="close"] + Smo -> Sm [label="close" color="red"] + S0o -> P_stop [label="close"] - Snm_closing [label="Snm_closing:\nnameplate active\nmailbox active" + Snm [label="Snm:\nnameplate active\nmailbox active\nclosing" style="dashed"] - Sn_closing [label="Sn_closing:\nnameplate active\nmailbox done" + Sn [label="Sn:\nnameplate active\nmailbox done\nclosing" style="dashed"] - Sm_closing [label="Sm_closing:\nnameplate done\nmailbox active" + Sm [label="Sm:\nnameplate done\nmailbox active\nclosing" style="dashed" color="red"] - Snm_closing -> Sn_closing [label="mailbox_done"] - Snm_closing -> Sm_closing [label="nameplate_done"] - Sn_closing -> P_stop [label="nameplate_done"] - Sm_closing -> P_stop [label="mailbox_done" color="red"] + Snm -> Sn [label="mailbox_done"] + Snm -> Sm [label="nameplate_done"] + Sn -> P_stop [label="nameplate_done"] + Sm -> P_stop [label="mailbox_done" color="red"] {rank=same; S_stopping Pss S_stopped} - P_stop [shape="box" label="C.stop" color="red"] + P_stop [shape="box" label="RC.stop" color="red"] P_stop -> S_stopping [color="red"] S_stopping [label="S_stopping" color="red"] diff --git a/src/wormhole/_boss.py b/src/wormhole/_boss.py index 1b5dd9a..dbe59fa 100644 --- a/src/wormhole/_boss.py +++ b/src/wormhole/_boss.py @@ -7,14 +7,16 @@ from attr.validators import provides, instance_of from twisted.python import log from automat import MethodicalMachine from . import _interfaces +from ._nameplate import Nameplate 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 ._nameplate_lister import NameplateListing from ._code import Code +from ._terminator import Terminator from .errors import WrongPasswordError from .util import bytes_to_dict @@ -34,6 +36,7 @@ class Boss(object): m = MethodicalMachine() def __attrs_post_init__(self): + self._N = Nameplate() self._M = Mailbox(self._side) self._S = Send(self._side, self._timing) self._O = Order(self._side, self._timing) @@ -44,8 +47,10 @@ class Boss(object): self._timing) self._NL = NameplateListing() self._C = Code(self._timing) + self._T = Terminator() - self._M.wire(self, self._RC, self._O) + self._N.wire(self._M, self._RC, self._T) + self._M.wire(self._N, self._RC, self._O, self._T) self._S.wire(self._M) self._O.wire(self._K, self._R) self._K.wire(self, self._M, self._R) @@ -53,6 +58,7 @@ class Boss(object): 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) + self._T.wire(self, self._RC, self._N, self._M) self._next_tx_phase = 0 self._next_rx_phase = 0 @@ -137,7 +143,7 @@ class Boss(object): @m.input() def got_verifier(self, verifier): pass - # Mailbox sends closed + # Terminator sends closed @m.input() def closed(self): pass @@ -149,7 +155,7 @@ class Boss(object): @m.output() def do_got_code(self, code): nameplate = code.split("-")[0] - self._M.set_nameplate(nameplate) + self._N.set_nameplate(nameplate) self._K.got_code(code) self._W.got_code(code) @m.output() @@ -167,19 +173,19 @@ class Boss(object): @m.output() def close_error(self, err, orig): self._result = WormholeError(err) - self._M.close("errory") + self._T.close("errory") @m.output() def close_scared(self): self._result = WrongPasswordError() - self._M.close("scary") + self._T.close("scary") @m.output() def close_lonely(self): self._result = WormholeError("lonely") - self._M.close("lonely") + self._T.close("lonely") @m.output() def close_happy(self): self._result = "happy" - self._M.close("happy") + self._T.close("happy") @m.output() def W_got_verifier(self, verifier): diff --git a/src/wormhole/_interfaces.py b/src/wormhole/_interfaces.py index ae8b459..0fbeaa4 100644 --- a/src/wormhole/_interfaces.py +++ b/src/wormhole/_interfaces.py @@ -4,6 +4,8 @@ class IWormhole(Interface): pass class IBoss(Interface): pass +class INameplate(Interface): + pass class IMailbox(Interface): pass class ISend(Interface): @@ -20,6 +22,8 @@ class INameplateLister(Interface): pass class ICode(Interface): pass +class ITerminator(Interface): + pass class ITiming(Interface): pass diff --git a/src/wormhole/_mailbox.py b/src/wormhole/_mailbox.py index f3c9cda..b6a6f5d 100644 --- a/src/wormhole/_mailbox.py +++ b/src/wormhole/_mailbox.py @@ -14,16 +14,15 @@ class Mailbox(object): def setTrace(): pass def __attrs_post_init__(self): - self._mood = None - self._nameplate = None self._mailbox = None self._pending_outbound = {} self._processed = set() - def wire(self, boss, rendezvous_connector, ordering): - self._B = _interfaces.IBoss(boss) + def wire(self, nameplate, rendezvous_connector, ordering, terminator): + self._N = _interfaces.INameplate(nameplate) self._RC = _interfaces.IRendezvousConnector(rendezvous_connector) self._O = _interfaces.IOrder(ordering) + self._T = _interfaces.ITerminator(terminator) # all -A states: not connected # all -B states: yes connected @@ -35,73 +34,49 @@ class Mailbox(object): @m.state() def S0B(self): pass - # S1: nameplate known, not claimed + # S1: mailbox known, not opened @m.state() def S1A(self): pass - # S2: nameplate known, maybe claimed + # S2: mailbox known, opened + # We've definitely tried to open the mailbox at least once, but it must + # be re-opened with each connection, because open() is also subscribe() @m.state() def S2A(self): pass @m.state() def S2B(self): pass - # S3: nameplate claimed, mailbox known, maybe open + # S3: closing @m.state() def S3A(self): pass @m.state() def S3B(self): pass - # S4: mailbox maybe open, nameplate maybe released - # We've definitely opened the mailbox at least once, but it must be - # re-opened with each connection, because open() is also subscribe() - @m.state() - def S4A(self): pass - @m.state() - def S4B(self): pass - - # S5: mailbox maybe open, nameplate released - @m.state() - def S5A(self): pass - @m.state() - def S5B(self): pass - - # Src: waiting for release+close - @m.state() - def SrcA(self): pass - @m.state() - def SrcB(self): pass - # Sr: closed (or never opened), waiting for release - @m.state() - def SrA(self): pass - @m.state() - def SrB(self): pass - # Sc: released (or never claimed), waiting for close - @m.state() - def ScA(self): pass - @m.state() - def ScB(self): pass - # Ss: closed and released, waiting for stop - @m.state() - def SsB(self): pass + # S4: closed. We no longer care whether we're connected or not + #@m.state() + #def S4A(self): pass + #@m.state() + #def S4B(self): pass @m.state(terminal=True) - def Ss(self): pass + def S4(self): pass + S4A = S4 + S4B = S4 - # from Boss - @m.input() - def set_nameplate(self, nameplate): pass + # from Terminator @m.input() def close(self, mood): pass + # from Nameplate + @m.input() + def got_mailbox(self, mailbox): pass + # from RendezvousConnector @m.input() def connected(self): pass @m.input() def lost(self): pass - @m.input() - def rx_claimed(self, mailbox): pass - def rx_message(self, side, phase, body): assert isinstance(side, type("")), type(side) assert isinstance(phase, type("")), type(phase) @@ -115,11 +90,7 @@ class Mailbox(object): @m.input() def rx_message_theirs(self, phase, body): pass @m.input() - def rx_released(self): pass - @m.input() def rx_closed(self): pass - @m.input() - def stopped(self): pass # from Send or Key @m.input() @@ -130,16 +101,8 @@ class Mailbox(object): @m.output() - def record_nameplate(self, nameplate): - self._nameplate = nameplate - @m.output() - def record_nameplate_and_RC_tx_claim(self, nameplate): - self._nameplate = nameplate - self._RC.tx_claim(self._nameplate) - @m.output() - def RC_tx_claim(self): - # when invoked via M.connected(), we must use the stored nameplate - self._RC.tx_claim(self._nameplate) + def record_mailbox(self, mailbox): + self._mailbox = mailbox @m.output() def RC_tx_open(self): assert self._mailbox @@ -150,7 +113,7 @@ class Mailbox(object): assert isinstance(body, type(b"")), (type(body), phase, body) self._pending_outbound[phase] = body @m.output() - def store_mailbox_and_RC_tx_open_and_drain(self, mailbox): + def record_mailbox_and_RC_tx_open_and_drain(self, mailbox): self._mailbox = mailbox self._RC.tx_open(mailbox) self._drain() @@ -166,29 +129,15 @@ class Mailbox(object): assert isinstance(body, type(b"")), type(body) self._RC.tx_add(phase, body) @m.output() - def RC_tx_release(self): - self._RC.tx_release() - @m.output() - def RC_tx_release_and_accept(self, phase, body): - self._RC.tx_release() + def N_release_and_accept(self, phase, body): + self._N.release() self._accept(phase, body) @m.output() - def record_mood_and_RC_tx_release(self, mood): - self._mood = mood - self._RC.tx_release() - @m.output() - def record_mood_and_RC_tx_release_and_RC_tx_close(self, mood): - self._mood = mood - self._RC.tx_release() - self._RC.tx_close(self._mood) - @m.output() def RC_tx_close(self): assert self._mood - self._RC.tx_close(self._mood) - @m.output() - def record_mood_and_RC_tx_close(self, mood): - self._mood = mood - self._RC.tx_close(self._mood) + self._RC_tx_close() + def _RC_tx_close(self): + self._RC.tx_close(self._mailbox, self._mood) @m.output() def accept(self, phase, body): self._accept(phase, body) @@ -203,98 +152,49 @@ class Mailbox(object): def record_mood(self, mood): self._mood = mood @m.output() - def record_mood_and_RC_stop(self, mood): + def record_mood_and_RC_tx_close(self, mood): self._mood = mood - self._RC_stop() + self._RC_rx_close() @m.output() - def RC_stop(self): - self._RC_stop() - def _RC_stop(self): - self._RC.stop() + def ignore_mood_and_T_mailbox_done(self, mood): + self._T.mailbox_done() @m.output() - def W_closed(self): - self._B.closed() + def T_mailbox_done(self): + self._T.mailbox_done() S0A.upon(connected, enter=S0B, outputs=[]) - S0A.upon(set_nameplate, enter=S1A, outputs=[record_nameplate]) + S0A.upon(got_mailbox, enter=S1A, outputs=[record_mailbox]) S0A.upon(add_message, enter=S0A, outputs=[queue]) + S0A.upon(close, enter=S4A, outputs=[ignore_mood_and_T_mailbox_done]) S0B.upon(lost, enter=S0A, outputs=[]) - S0B.upon(set_nameplate, enter=S2B, outputs=[record_nameplate_and_RC_tx_claim]) S0B.upon(add_message, enter=S0B, outputs=[queue]) + S0B.upon(close, enter=S4B, outputs=[ignore_mood_and_T_mailbox_done]) + S0B.upon(got_mailbox, enter=S2B, + outputs=[record_mailbox_and_RC_tx_open_and_drain]) - S1A.upon(connected, enter=S2B, outputs=[RC_tx_claim]) + S1A.upon(connected, enter=S2B, outputs=[RC_tx_open, drain]) S1A.upon(add_message, enter=S1A, outputs=[queue]) + S1A.upon(close, enter=S4A, outputs=[ignore_mood_and_T_mailbox_done]) - S2A.upon(connected, enter=S2B, outputs=[RC_tx_claim]) + S2A.upon(connected, enter=S2B, outputs=[RC_tx_open, drain]) S2A.upon(add_message, enter=S2A, outputs=[queue]) + S2A.upon(close, enter=S3A, outputs=[record_mood]) S2B.upon(lost, enter=S2A, outputs=[]) - S2B.upon(add_message, enter=S2B, outputs=[queue]) - S2B.upon(rx_claimed, enter=S3B, - outputs=[store_mailbox_and_RC_tx_open_and_drain]) + S2B.upon(add_message, enter=S2B, outputs=[queue, RC_tx_add]) + S2B.upon(rx_message_theirs, enter=S2B, outputs=[N_release_and_accept]) + S2B.upon(rx_message_ours, enter=S2B, outputs=[dequeue]) + S2B.upon(close, enter=S3B, outputs=[record_mood_and_RC_tx_close]) - S3A.upon(connected, enter=S3B, outputs=[RC_tx_open, drain]) - S3A.upon(add_message, enter=S3A, outputs=[queue]) + S3A.upon(connected, enter=S3B, outputs=[RC_tx_close]) S3B.upon(lost, enter=S3A, outputs=[]) - S3B.upon(rx_message_theirs, enter=S4B, outputs=[RC_tx_release_and_accept]) - S3B.upon(rx_message_ours, enter=S3B, outputs=[dequeue]) - S3B.upon(rx_claimed, enter=S3B, outputs=[]) - S3B.upon(add_message, enter=S3B, outputs=[queue, RC_tx_add]) + S3B.upon(rx_closed, enter=S4B, outputs=[T_mailbox_done]) + S3B.upon(add_message, enter=S3B, outputs=[]) + S3B.upon(rx_message_theirs, enter=S3B, outputs=[]) + S3B.upon(rx_message_ours, enter=S3B, outputs=[]) - S4A.upon(connected, enter=S4B, outputs=[RC_tx_open, drain, RC_tx_release]) - S4A.upon(add_message, enter=S4A, outputs=[queue]) + S4A.upon(connected, enter=S4B, outputs=[]) S4B.upon(lost, enter=S4A, outputs=[]) - S4B.upon(add_message, enter=S4B, outputs=[queue, RC_tx_add]) - S4B.upon(rx_message_theirs, enter=S4B, outputs=[accept]) - S4B.upon(rx_message_ours, enter=S4B, outputs=[dequeue]) - S4B.upon(rx_released, enter=S5B, outputs=[]) - - S5A.upon(connected, enter=S5B, outputs=[RC_tx_open, drain]) - S5A.upon(add_message, enter=S5A, outputs=[queue]) - S5B.upon(lost, enter=S5A, outputs=[]) - S5B.upon(add_message, enter=S5B, outputs=[queue, RC_tx_add]) - S5B.upon(rx_message_theirs, enter=S5B, outputs=[accept]) - S5B.upon(rx_message_ours, enter=S5B, outputs=[dequeue]) - - if True: - S0A.upon(close, enter=SsB, outputs=[record_mood_and_RC_stop]) - S0B.upon(close, enter=SsB, outputs=[record_mood_and_RC_stop]) - S1A.upon(close, enter=SsB, outputs=[record_mood_and_RC_stop]) - S2A.upon(close, enter=SrA, outputs=[record_mood]) - S2B.upon(close, enter=SrB, outputs=[record_mood_and_RC_tx_release]) - S3A.upon(close, enter=SrcA, outputs=[record_mood]) - S3B.upon(close, enter=SrcB, - outputs=[record_mood_and_RC_tx_release_and_RC_tx_close]) - S4A.upon(close, enter=SrcA, outputs=[record_mood]) - S4B.upon(close, enter=SrcB, outputs=[record_mood_and_RC_tx_close]) - S5A.upon(close, enter=ScA, outputs=[record_mood]) - S5B.upon(close, enter=ScB, outputs=[record_mood_and_RC_tx_close]) - - SrcA.upon(connected, enter=SrcB, outputs=[RC_tx_release, RC_tx_close]) - SrcB.upon(lost, enter=SrcA, outputs=[]) - SrcB.upon(rx_closed, enter=SrB, outputs=[]) - SrcB.upon(rx_released, enter=ScB, outputs=[]) - - SrB.upon(lost, enter=SrA, outputs=[]) - SrA.upon(connected, enter=SrB, outputs=[RC_tx_release]) - SrB.upon(rx_released, enter=SsB, outputs=[RC_stop]) - - ScB.upon(lost, enter=ScA, outputs=[]) - ScB.upon(rx_closed, enter=SsB, outputs=[RC_stop]) - ScA.upon(connected, enter=ScB, outputs=[RC_tx_close]) - - SsB.upon(lost, enter=SsB, outputs=[]) - SsB.upon(stopped, enter=Ss, outputs=[W_closed]) - - SrcB.upon(rx_claimed, enter=SrcB, outputs=[]) - SrcB.upon(rx_message_theirs, enter=SrcB, outputs=[]) - SrcB.upon(rx_message_ours, enter=SrcB, outputs=[]) - SrB.upon(rx_claimed, enter=SrB, outputs=[]) - SrB.upon(rx_message_theirs, enter=SrB, outputs=[]) - SrB.upon(rx_message_ours, enter=SrB, outputs=[]) - ScB.upon(rx_claimed, enter=ScB, outputs=[]) - ScB.upon(rx_message_theirs, enter=ScB, outputs=[]) - ScB.upon(rx_message_ours, enter=ScB, outputs=[]) - SsB.upon(rx_claimed, enter=SsB, outputs=[]) - SsB.upon(rx_message_theirs, enter=SsB, outputs=[]) - SsB.upon(rx_message_ours, enter=SsB, outputs=[]) + S4.upon(add_message, enter=S4, outputs=[]) + S4.upon(rx_message_theirs, enter=S4, outputs=[]) + S4.upon(rx_message_ours, enter=S4, outputs=[]) diff --git a/src/wormhole/_nameplate.py b/src/wormhole/_nameplate.py index ee9631c..ddc6ac8 100644 --- a/src/wormhole/_nameplate.py +++ b/src/wormhole/_nameplate.py @@ -3,63 +3,142 @@ from zope.interface import implementer from automat import MethodicalMachine from . import _interfaces -@implementer(_interfaces.INameplateLister) -class NameplateListing(object): +@implementer(_interfaces.INameplate) +class Nameplate(object): m = MethodicalMachine() + @m.setTrace() + def setTrace(): pass - def wire(self, rendezvous_connector, code): + def __init__(self): + self._nameplate = None + + def wire(self, mailbox, rendezvous_connector, terminator): + self._M = _interfaces.IMailbox(mailbox) self._RC = _interfaces.IRendezvousConnector(rendezvous_connector) - self._C = _interfaces.ICode(code) + self._T = _interfaces.ITerminator(terminator) - # 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. + # all -A states: not connected + # all -B states: yes connected + # B states serialize as A, so they deserialize as unconnected + # S0: know nothing @m.state(initial=True) - def S0A_idle_disconnected(self): pass + def S0A(self): pass @m.state() - def S1A_wanting_disconnected(self): pass - @m.state() - def S0B_idle_connected(self): pass - @m.state() - def S1B_wanting_connected(self): pass + def S0B(self): pass + # S1: nameplate known, never claimed + @m.state() + def S1A(self): pass + + # S2: nameplate known, maybe claimed + @m.state() + def S2A(self): pass + @m.state() + def S2B(self): pass + + # S3: nameplate claimed + @m.state() + def S3A(self): pass + @m.state() + def S3B(self): pass + + # S4: maybe released + @m.state() + def S4A(self): pass + @m.state() + def S4B(self): pass + + # S5: released + # we no longer care whether we're connected or not + #@m.state() + #def S5A(self): pass + #@m.state() + #def S5B(self): pass + @m.state() + def S5(self): pass + S5A = S5 + S5B = S5 + + # from Boss + @m.input() + def set_nameplate(self, nameplate): pass + + # from Mailbox + @m.input() + def release(self): pass + + # from Terminator + @m.input() + def close(self): pass + + # from RendezvousConnector @m.input() def connected(self): pass @m.input() def lost(self): pass + @m.input() - def refresh_nameplates(self): pass + def rx_claimed(self, mailbox): pass @m.input() - def rx_nameplates(self, message): pass + def rx_released(self): pass + @m.output() - def RC_tx_list(self): - self._RC.tx_list() + def record_nameplate(self, nameplate): + self._nameplate = nameplate @m.output() - def C_got_nameplates(self, message): - self._C.got_nameplates(message["nameplates"]) + def record_nameplate_and_RC_tx_claim(self, nameplate): + self._nameplate = nameplate + self._RC.tx_claim(self._nameplate) + @m.output() + def RC_tx_claim(self): + # when invoked via M.connected(), we must use the stored nameplate + self._RC.tx_claim(self._nameplate) + @m.output() + def M_got_mailbox(self, mailbox): + self._M.got_mailbox(mailbox) + @m.output() + def RC_tx_release(self): + assert self._nameplate + self._RC.tx_release(self._nameplate) + @m.output() + def T_nameplate_done(self): + self._T.nameplate_done() - S0A_idle_disconnected.upon(connected, enter=S0B_idle_connected, outputs=[]) - S0B_idle_connected.upon(lost, enter=S0A_idle_disconnected, outputs=[]) + S0A.upon(set_nameplate, enter=S1A, outputs=[record_nameplate]) + S0A.upon(connected, enter=S0B, outputs=[]) + S0A.upon(close, enter=S5A, outputs=[T_nameplate_done]) + S0B.upon(set_nameplate, enter=S2B, + outputs=[record_nameplate_and_RC_tx_claim]) + S0B.upon(lost, enter=S0A, outputs=[]) + S0B.upon(close, enter=S5A, outputs=[T_nameplate_done]) - S0A_idle_disconnected.upon(refresh_nameplates, - enter=S1A_wanting_disconnected, outputs=[]) - S1A_wanting_disconnected.upon(refresh_nameplates, - enter=S1A_wanting_disconnected, outputs=[]) - S1A_wanting_disconnected.upon(connected, enter=S1B_wanting_connected, - outputs=[RC_tx_list]) - S0B_idle_connected.upon(refresh_nameplates, enter=S1B_wanting_connected, - outputs=[RC_tx_list]) - S0B_idle_connected.upon(rx_nameplates, enter=S0B_idle_connected, - outputs=[C_got_nameplates]) - S1B_wanting_connected.upon(lost, enter=S1A_wanting_disconnected, outputs=[]) - S1B_wanting_connected.upon(refresh_nameplates, enter=S1B_wanting_connected, - outputs=[RC_tx_list]) - S1B_wanting_connected.upon(rx_nameplates, enter=S0B_idle_connected, - outputs=[C_got_nameplates]) + S1A.upon(connected, enter=S2B, outputs=[RC_tx_claim]) + S1A.upon(close, enter=S5A, outputs=[T_nameplate_done]) + + S2A.upon(connected, enter=S2B, outputs=[RC_tx_claim]) + S2A.upon(close, enter=S4A, outputs=[]) + S2B.upon(lost, enter=S2A, outputs=[]) + S2B.upon(rx_claimed, enter=S3B, outputs=[M_got_mailbox]) + S2B.upon(close, enter=S4B, outputs=[RC_tx_release]) + + S3A.upon(connected, enter=S3B, outputs=[]) + S3A.upon(close, enter=S4A, outputs=[]) + S3B.upon(lost, enter=S3A, outputs=[]) + #S3B.upon(rx_claimed, enter=S3B, outputs=[]) # shouldn't happen + S3B.upon(release, enter=S4B, outputs=[RC_tx_release]) + S3B.upon(close, enter=S4B, outputs=[RC_tx_release]) + + S4A.upon(connected, enter=S4B, outputs=[RC_tx_release]) + S4A.upon(close, enter=S4A, outputs=[]) + S4B.upon(lost, enter=S4A, outputs=[]) + S4B.upon(rx_released, enter=S5B, outputs=[T_nameplate_done]) + S4B.upon(release, enter=S4B, outputs=[]) # mailbox is lazy + # Mailbox doesn't remember how many times it's sent a release, and will + # re-send a new one for each peer message it receives. Ignoring it here + # is easier than adding a new pair of states to Mailbox. + S4B.upon(close, enter=S4B, outputs=[]) + + S5A.upon(connected, enter=S5B, outputs=[]) + S5B.upon(lost, enter=S5A, outputs=[]) diff --git a/src/wormhole/_nameplate_lister.py b/src/wormhole/_nameplate_lister.py new file mode 100644 index 0000000..ee9631c --- /dev/null +++ b/src/wormhole/_nameplate_lister.py @@ -0,0 +1,65 @@ +from __future__ import print_function, absolute_import, unicode_literals +from zope.interface import implementer +from automat import MethodicalMachine +from . import _interfaces + +@implementer(_interfaces.INameplateLister) +class NameplateListing(object): + m = MethodicalMachine() + + def wire(self, rendezvous_connector, code): + self._RC = _interfaces.IRendezvousConnector(rendezvous_connector) + self._C = _interfaces.ICode(code) + + # 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 S0A_idle_disconnected(self): pass + @m.state() + def S1A_wanting_disconnected(self): pass + @m.state() + def S0B_idle_connected(self): pass + @m.state() + def S1B_wanting_connected(self): pass + + @m.input() + def connected(self): pass + @m.input() + def lost(self): pass + @m.input() + def refresh_nameplates(self): pass + @m.input() + def rx_nameplates(self, message): pass + + @m.output() + def RC_tx_list(self): + self._RC.tx_list() + @m.output() + def C_got_nameplates(self, message): + self._C.got_nameplates(message["nameplates"]) + + S0A_idle_disconnected.upon(connected, enter=S0B_idle_connected, outputs=[]) + S0B_idle_connected.upon(lost, enter=S0A_idle_disconnected, outputs=[]) + + S0A_idle_disconnected.upon(refresh_nameplates, + enter=S1A_wanting_disconnected, outputs=[]) + S1A_wanting_disconnected.upon(refresh_nameplates, + enter=S1A_wanting_disconnected, outputs=[]) + S1A_wanting_disconnected.upon(connected, enter=S1B_wanting_connected, + outputs=[RC_tx_list]) + S0B_idle_connected.upon(refresh_nameplates, enter=S1B_wanting_connected, + outputs=[RC_tx_list]) + S0B_idle_connected.upon(rx_nameplates, enter=S0B_idle_connected, + outputs=[C_got_nameplates]) + S1B_wanting_connected.upon(lost, enter=S1A_wanting_disconnected, outputs=[]) + S1B_wanting_connected.upon(refresh_nameplates, enter=S1B_wanting_connected, + outputs=[RC_tx_list]) + S1B_wanting_connected.upon(rx_nameplates, enter=S0B_idle_connected, + outputs=[C_got_nameplates]) diff --git a/src/wormhole/_rendezvous.py b/src/wormhole/_rendezvous.py index f6a34fa..0e7ab4d 100644 --- a/src/wormhole/_rendezvous.py +++ b/src/wormhole/_rendezvous.py @@ -106,11 +106,11 @@ class RendezvousConnector(object): assert isinstance(body, type(b"")), type(body) self._tx("add", phase=phase, body=bytes_to_hexstr(body)) - def tx_release(self): - self._tx("release") + def tx_release(self, nameplate): + self._tx("release", nameplate=nameplate) - def tx_close(self, mood): - self._tx("close", mood=mood) + def tx_close(self, mailbox, mood): + self._tx("close", mailbox=mailbox, mood=mood) def stop(self): d = defer.maybeDeferred(self._connector.stopService) diff --git a/src/wormhole/_terminator.py b/src/wormhole/_terminator.py new file mode 100644 index 0000000..45496f4 --- /dev/null +++ b/src/wormhole/_terminator.py @@ -0,0 +1,104 @@ +from __future__ import print_function, absolute_import, unicode_literals +from zope.interface import implementer +from automat import MethodicalMachine +from . import _interfaces + +@implementer(_interfaces.ITerminator) +class Terminator(object): + m = MethodicalMachine() + @m.setTrace() + def setTrace(): pass + + def __attrs_post_init__(self): + self._mood = None + + def wire(self, boss, rendezvous_connector, nameplate, mailbox): + self._B = _interfaces.IBoss(boss) + self._RC = _interfaces.IRendezvousConnector(rendezvous_connector) + self._N = _interfaces.INameplate(nameplate) + self._M = _interfaces.IMailbox(mailbox) + + # 4*2-1 main states: + # (nm, m, n, 0): nameplate and/or mailbox is active + # (o, ""): open (not-yet-closing), or trying to close + # S0 is special: we don't hang out in it + + # We start in Snmo (non-closing). When both nameplate and mailboxes are + # done, and we're closing, then we stop the RendezvousConnector + + @m.state(initial=True) + def Snmo(self): pass + @m.state() + def Smo(self): pass + @m.state() + def Sno(self): pass + @m.state() + def S0o(self): pass + + @m.state() + def Snm(self): pass + @m.state() + def Sm(self): pass + @m.state() + def Sn(self): pass + #@m.state() + #def S0(self): pass # unused + + @m.state() + def S_stopping(self): pass + @m.state() + def S_stopped(self, terminal=True): pass + + # from Boss + @m.input() + def close(self, mood): pass + + # from Nameplate + @m.input() + def nameplate_done(self): pass + + # from Mailbox + @m.input() + def mailbox_done(self): pass + + # from RendezvousConnector + @m.input() + def stopped(self): pass + + + @m.output() + def close_nameplate(self, mood): + self._N.close() # ignores mood + @m.output() + def close_mailbox(self, mood): + self._M.close(mood) + + @m.output() + def ignore_mood_and_RC_stop(self, mood): + self._RC.stop() + @m.output() + def RC_stop(self): + self._RC.stop() + @m.output() + def B_closed(self): + self._B.closed() + + Snmo.upon(mailbox_done, enter=Sno, outputs=[]) + Snmo.upon(close, enter=Snm, outputs=[close_nameplate, close_mailbox]) + Snmo.upon(nameplate_done, enter=Smo, outputs=[]) + + Sno.upon(close, enter=Sn, outputs=[close_nameplate, close_mailbox]) + Sno.upon(nameplate_done, enter=S0o, outputs=[]) + + Smo.upon(close, enter=Sm, outputs=[close_nameplate, close_mailbox]) + Smo.upon(mailbox_done, enter=S0o, outputs=[]) + + Snm.upon(mailbox_done, enter=Sn, outputs=[]) + Snm.upon(nameplate_done, enter=Sm, outputs=[]) + + Sn.upon(nameplate_done, enter=S_stopping, outputs=[RC_stop]) + S0o.upon(close, enter=S_stopping, + outputs=[close_nameplate, close_mailbox, ignore_mood_and_RC_stop]) + Sm.upon(mailbox_done, enter=S_stopping, outputs=[RC_stop]) + + S_stopping.upon(stopped, enter=S_stopped, outputs=[B_closed])