diff --git a/docs/boss.dot b/docs/boss.dot index b2a40f5..03062d3 100644 --- a/docs/boss.dot +++ b/docs/boss.dot @@ -12,12 +12,12 @@ digraph { {rank=same; P0_code S0} P0_code [shape="box" style="dashed" - label="Code.input\n or Code.allocate\n or Code.set"] + label="input -> Code.input\n or allocate -> Code.allocate\n or set_code -> Code.set_code"] P0_code -> S0 S0 [label="S0: empty"] S0 -> P0_build [label="set_code"] - P0_build [shape="box" label="M.set_nameplate\nK.set_code"] + P0_build [shape="box" label="W.got_code\nM.set_nameplate\nK.got_code"] P0_build -> S1 S1 [label="S1: lonely" color="orange"] @@ -52,7 +52,7 @@ digraph { {rank=same; Other S_closed} Other [shape="box" style="dashed" - label="send -> S.send\ngot_verifier -> A.got_verifier" + label="send -> S.send\ngot_verifier -> A.got_verifier\nallocate -> C.allocate\ninput -> C.input\nset_code -> C.set_code" ] diff --git a/docs/code.dot b/docs/code.dot index 5f63fc1..1a67209 100644 --- a/docs/code.dot +++ b/docs/code.dot @@ -7,9 +7,9 @@ digraph { {rank=same; S3 P1_generate} start -> S0 [style="invis"] S0 [label="S0:\nunknown"] - S0 -> P0_set_code [label="set"] - P0_set_code [shape="box" label="B.set_code"] - P0_set_code -> S4 + S0 -> P0_got_code [label="set"] + P0_got_code [shape="box" label="B.got_code"] + P0_got_code -> S4 S4 [label="S4: known" color="green"] S0 -> P0_list_nameplates [label="input"] @@ -32,7 +32,7 @@ digraph { P3_completion [shape="box" label="do completion"] P3_completion -> S3 - S3 -> P0_set_code [label="" + S3 -> P0_got_code [label="" color="orange" fontcolor="orange"] S0 -> P0_allocate [label="allocate"] @@ -41,6 +41,6 @@ digraph { S1 [label="S1:\nallocating"] S1 -> P1_generate [label="rx_allocated"] P1_generate [shape="box" label="generate\nrandom code"] - P1_generate -> P0_set_code + P1_generate -> P0_got_code } diff --git a/docs/key.dot b/docs/key.dot index de75c2b..5e4e23c 100644 --- a/docs/key.dot +++ b/docs/key.dot @@ -11,7 +11,7 @@ digraph { start [label="Key\nMachine" style="dotted"] S0 [label="S0: know\nnothing"] - S0 -> P0_build [label="set_code"] + S0 -> P0_build [label="got_code"] P0_build [shape="box" label="build_pake\nM.add_message(pake)"] P0_build -> S1 diff --git a/docs/machines.dot b/docs/machines.dot index 9d22173..ee5a7a3 100644 --- a/docs/machines.dot +++ b/docs/machines.dot @@ -1,5 +1,5 @@ digraph { - App [shape="box" color="blue" fontcolor="blue"] + Wormhole [shape="oval" color="blue" fontcolor="blue"] Boss [shape="box" label="Boss\n(manager)" color="blue" fontcolor="blue"] Mailbox [shape="box" color="blue" fontcolor="blue"] @@ -18,9 +18,9 @@ digraph { Connection -> websocket [color="blue"] #Connection -> Order [color="blue"] - App -> Boss [style="dashed" label="start\nset_code\nsend\nclose\n(once)"] - #App -> Boss [color="blue"] - Boss -> App [style="dashed" label="got_verifier\nreceived\nclosed\n(once)"] + Wormhole -> Boss [style="dashed" label="allocate\ninput\nset_code\nsend\nclose\n(once)"] + #Wormhole -> Boss [color="blue"] + Boss -> Wormhole [style="dashed" label="got_code\ngot_verifier\nreceived\nclosed\n(once)"] #Boss -> Connection [color="blue"] Boss -> Connection [style="dashed" label="start"] @@ -34,7 +34,7 @@ digraph { #Boss -> Mailbox [color="blue"] Mailbox -> Boss [style="dashed" label="closed\n(once)"] Mailbox -> Order [style="dashed" label="got_message (once)"] - Boss -> Key [style="dashed" label="set_code"] + Boss -> Key [style="dashed" label="got_code"] Key -> Boss [style="dashed" label="got_verifier\nscared"] Order -> Key [style="dashed" label="got_pake"] Order -> Receive [style="dashed" label="got_message"] @@ -73,10 +73,10 @@ digraph { Code -> Nameplates [style="dashed" label="refresh_nameplates" ] + Boss -> Code [style="dashed" + label="allocate\ninput\nset_code"] Code -> Boss [style="dashed" - label="set_code"] - App -> Code [style="dashed" - label="allocate\ninput\nset"] + label="got_code"] } diff --git a/docs/mailbox.dot b/docs/mailbox.dot index 2f64062..1353a14 100644 --- a/docs/mailbox.dot +++ b/docs/mailbox.dot @@ -156,3 +156,30 @@ digraph { P5_close [shape="box" label="tx_close" style="dashed" color="orange"] } + +/* + +Can this be split into one machine for the Nameplate, and a second for the +Mailbox? + +Nameplate: + +* 0: know nothing (connected, not connected) +* 1: know nameplate, never claimed, need to claim +* 2: maybe claimed, need to claim +* 3: definitely claimed, need to claim +* 4: definitely claimed, need to release +* 5: maybe released +* 6: definitely released + +Mailbox: +* 0: unknown +* 1: know mailbox, need open, not open +* 2: know mailbox, need open, maybe open +* 3: definitely open, need open +* 4: need closed, maybe open +* 5: need closed, maybe closed ? +* 6: definitely closed + + +*/ \ No newline at end of file diff --git a/src/wormhole/_boss.py b/src/wormhole/_boss.py index db5ead0..bb15e82 100644 --- a/src/wormhole/_boss.py +++ b/src/wormhole/_boss.py @@ -53,15 +53,34 @@ class Boss: @m.state(terminal=True) def S4_closed(self): pass - # from the Application, or some sort of top-level shim + # from the Wormhole + + # input/allocate/set_code are regular methods, not state-transition + # inputs. We expect them to be called just after initialization, while + # we're in the S0_empty state. You must call exactly one of them, and the + # call must happen while we're in S0_empty, which makes them good + # candiates for being a proper @m.input, but set_code() will immediately + # (reentrantly) cause self.got_code() to be fired, which is messy. These + # are all passthroughs to the Code machine, so one alternative would be + # to have Wormhole call Code.{input,allocate,set_code} instead, but that + # would require the Wormhole to be aware of Code (whereas right now + # Wormhole only knows about this Boss instance, and everything else is + # hidden away). + def input(self, stdio): + self._C.input(stdio) + def allocate(self, code_length): + self._C.allocate(code_length) + def set_code(self, code): + self._C.set_code(code) + @m.input() def send(self, phase, plaintext): pass @m.input() def close(self): pass - # from Code (which may be provoked by the Application) + # from Code (provoked by input/allocate/set_code) @m.input() - def set_code(self, code): pass + def got_code(self, code): pass # Key sends (got_verifier, scared) # Receive sends (got_message, happy, scared) @@ -77,7 +96,7 @@ class Boss: else: self.got_phase(phase, plaintext) @m.input() - def got_version(self, version): pass + def got_version(self, plaintext): pass @m.input() def got_phase(self, phase, plaintext): pass @m.input() @@ -89,10 +108,10 @@ class Boss: @m.output() - def got_code(self, code): + def do_got_code(self, code): nameplate = code.split("-")[0] self._M.set_nameplate(nameplate) - self._K.set_code(code) + self._K.got_code(code) @m.output() def process_version(self, plaintext): self._their_versions = bytes_to_dict(plaintext) @@ -125,7 +144,7 @@ class Boss: self._A.closed(result) S0_empty.upon(send, enter=S0_empty, outputs=[S_send]) - S0_empty.upon(set_code, enter=S1_lonely, outputs=[got_code]) + S0_empty.upon(got_code, enter=S1_lonely, outputs=[do_got_code]) S1_lonely.upon(happy, enter=S2_happy, outputs=[]) S1_lonely.upon(scared, enter=S3_closing, outputs=[close_scared]) S1_lonely.upon(close, enter=S3_closing, outputs=[close_lonely]) diff --git a/src/wormhole/_code.py b/src/wormhole/_code.py index 2cc80e0..efad69d 100644 --- a/src/wormhole/_code.py +++ b/src/wormhole/_code.py @@ -1,5 +1,7 @@ import os from zope.interface import implementer +from attr import attrs, attrib +from attr.validators import provides from automat import MethodicalMachine from . import _interfaces from .wordlist import (byte_to_even_word, byte_to_odd_word, @@ -17,12 +19,12 @@ def make_code(nameplate, code_length): words.append(byte_to_even_word[os.urandom(1)].lower()) return "%s-%s" % (nameplate, "-".join(words)) +@attrs @implementer(_interfaces.ICode) class Code(object): + _timing = attrib(validator=provides(_interfaces.ITiming)) m = MethodicalMachine() - def __init__(self, code_length, timing): - self._code_length = code_length - self._timing = timing + def wire(self, wormhole, rendezvous_connector, nameplate_lister): self._W = _interfaces.IWormhole(wormhole) self._RC = _interfaces.IRendezvousConnector(rendezvous_connector) @@ -41,9 +43,9 @@ class Code(object): # from App @m.input() - def allocate(self): pass + def allocate(self, code_length): pass @m.input() - def input(self): pass + def input(self, stdio): pass @m.input() def set(self, code): pass @@ -67,7 +69,12 @@ class Code(object): def NL_refresh_nameplates(self): self._NL.refresh_nameplates() @m.output() - def RC_tx_allocate(self): + def start_input_and_NL_refresh_nameplates(self, stdio): + self._stdio = stdio + self._NL.refresh_nameplates() + @m.output() + def RC_tx_allocate(self, code_length): + self._code_length = code_length self._RC.tx_allocate() @m.output() def do_completion_nameplates(self): @@ -85,23 +92,23 @@ class Code(object): @m.output() def generate_and_set(self, nameplate): self._code = make_code(nameplate, self._code_length) - self._W_set_code() + self._W_got_code() @m.output() - def W_set_code(self, code): + def W_got_code(self, code): self._code = code - self._W_set_code() + self._W_got_code() - def _W_set_code(self): - self._W.set_code(self._code) + def _W_got_code(self): + self._W.got_code(self._code) S0_unknown.upon(allocate, enter=S1_allocating, outputs=[RC_tx_allocate]) S1_allocating.upon(rx_allocated, enter=S4_known, outputs=[generate_and_set]) - S0_unknown.upon(set, enter=S4_known, outputs=[W_set_code]) + S0_unknown.upon(set, enter=S4_known, outputs=[W_got_code]) S0_unknown.upon(input, enter=S2_typing_nameplate, - outputs=[NL_refresh_nameplates]) + outputs=[start_input_and_NL_refresh_nameplates]) S2_typing_nameplate.upon(tab, enter=S2_typing_nameplate, outputs=[do_completion_nameplates]) S2_typing_nameplate.upon(got_nameplates, enter=S2_typing_nameplate, @@ -109,4 +116,4 @@ class Code(object): S2_typing_nameplate.upon(hyphen, enter=S3_typing_code, outputs=[lookup_wordlist]) S3_typing_code.upon(tab, enter=S3_typing_code, outputs=[do_completion_code]) - S3_typing_code.upon(RETURN, enter=S4_known, outputs=[W_set_code]) + S3_typing_code.upon(RETURN, enter=S4_known, outputs=[W_got_code]) diff --git a/src/wormhole/_key.py b/src/wormhole/_key.py index 760d679..edbcda7 100644 --- a/src/wormhole/_key.py +++ b/src/wormhole/_key.py @@ -1,5 +1,7 @@ from hashlib import sha256 from zope.interface import implementer +from attr import attrs, attrib +from attr.validators import provides from spake2 import SPAKE2_Symmetric from hkdf import Hkdf from nacl.secret import SecretBox @@ -48,11 +50,12 @@ def encrypt_data(key, plaintext): nonce = utils.random(SecretBox.NONCE_SIZE) return box.encrypt(plaintext, nonce) +@attrs @implementer(_interfaces.IKey) class Key(object): + _timing = attrib(validator=provides(_interfaces.ITiming)) m = MethodicalMachine() - def __init__(self, timing): - self._timing = timing + def wire(self, boss, mailbox, receive): self._B = _interfaces.IBoss(boss) self._M = _interfaces.IMailbox(mailbox) @@ -69,7 +72,7 @@ class Key(object): # from Boss @m.input() - def set_code(self, code): pass + def got_code(self, code): pass # from Ordering def got_pake(self, body): @@ -106,6 +109,6 @@ class Key(object): self._B.got_verifier(derive_key(key, b"wormhole:verifier")) self._R.got_key(key) - S0_know_nothing.upon(set_code, enter=S1_know_code, outputs=[build_pake]) + S0_know_nothing.upon(got_code, enter=S1_know_code, outputs=[build_pake]) S1_know_code.upon(got_pake_good, enter=S2_know_key, outputs=[compute_key]) S1_know_code.upon(got_pake_bad, enter=S3_scared, outputs=[scared]) diff --git a/src/wormhole/_mailbox.py b/src/wormhole/_mailbox.py index 93b5ba5..5469806 100644 --- a/src/wormhole/_mailbox.py +++ b/src/wormhole/_mailbox.py @@ -1,13 +1,16 @@ from zope.interface import implementer +from attr import attrs, attrib +from attr.validators import instance_of from automat import MethodicalMachine from . import _interfaces +@attrs @implementer(_interfaces.IMailbox) class Mailbox(object): + _side = attrib(validator=instance_of(type(u""))) m = MethodicalMachine() - def __init__(self, side): - self._side = side + def __init__(self): self._mood = None self._nameplate = None self._mailbox = None diff --git a/src/wormhole/_order.py b/src/wormhole/_order.py index d803fca..73934e8 100644 --- a/src/wormhole/_order.py +++ b/src/wormhole/_order.py @@ -1,13 +1,17 @@ from zope.interface import implementer +from attr import attrs, attrib +from attr.validators import provides, instance_of from automat import MethodicalMachine from . import _interfaces +@attrs @implementer(_interfaces.IOrder) class Order(object): + _side = attrib(validator=instance_of(type(u""))) + _timing = attrib(validator=provides(_interfaces.ITiming)) m = MethodicalMachine() - def __init__(self, side, timing): - self._side = side - self._timing = timing + + def __init__(self): self._key = None self._queue = [] def wire(self, key, receive): diff --git a/src/wormhole/_receive.py b/src/wormhole/_receive.py index 5d9e803..8fec9a8 100644 --- a/src/wormhole/_receive.py +++ b/src/wormhole/_receive.py @@ -1,15 +1,20 @@ from zope.interface import implementer +from attr import attrs, attrib +from attr.validators import provides, instance_of from automat import MethodicalMachine from . import _interfaces from ._key import derive_phase_key, decrypt_data, CryptoError +@attrs @implementer(_interfaces.IReceive) class Receive(object): + _side = attrib(validator=instance_of(type(u""))) + _timing = attrib(validator=provides(_interfaces.ITiming)) m = MethodicalMachine() - def __init__(self, side, timing): - self._side = side - self._timing = timing + + def __init__(self): self._key = None + def wire(self, boss, key, send): self._B = _interfaces.IBoss(boss) self._K = _interfaces.IKey(key) diff --git a/src/wormhole/_rendezvous.py b/src/wormhole/_rendezvous.py index fac6fe9..52a32fe 100644 --- a/src/wormhole/_rendezvous.py +++ b/src/wormhole/_rendezvous.py @@ -54,12 +54,12 @@ class WSFactory(websocket.WebSocketClientFactory): @attrs @implementer(_interfaces.IRendezvousConnector) class RendezvousConnector(object): - _url = attrib(instance_of(type(u""))) - _appid = attrib(instance_of(type(u""))) - _side = attrib(instance_of(type(u""))) + _url = attrib(validator=instance_of(type(u""))) + _appid = attrib(validator=instance_of(type(u""))) + _side = attrib(validator=instance_of(type(u""))) _reactor = attrib() - _journal = attrib(provides(_interfaces.IJournal)) - _timing = attrib(provides(_interfaces.ITiming)) + _journal = attrib(validator=provides(_interfaces.IJournal)) + _timing = attrib(validator=provides(_interfaces.ITiming)) def __init__(self): self._ws = None diff --git a/src/wormhole/wormhole.py b/src/wormhole/wormhole.py index 863d638..ec6b8e1 100644 --- a/src/wormhole/wormhole.py +++ b/src/wormhole/wormhole.py @@ -23,7 +23,7 @@ def wormhole(appid, relay_url, reactor, tor_manager=None, timing=None, # * if not: # * -class _JournaledWormhole(service.MultiService): +class _JournaledWormhole(object): def __init__(self, reactor, journal_manager, event_dispatcher, event_dispatcher_args=()): pass