implement new split nameplate/mailbox/terminator

fails even worse than before, of course
This commit is contained in:
Brian Warner 2017-02-26 03:57:58 -08:00
parent 02bea00366
commit 26adaabe18
9 changed files with 394 additions and 236 deletions

View File

@ -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"]

View File

@ -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"]

View File

@ -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):

View File

@ -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

View File

@ -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=[])

View File

@ -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=[])

View File

@ -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])

View File

@ -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)

104
src/wormhole/_terminator.py Normal file
View File

@ -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])