first-cut of state-machine style code
This commit is contained in:
parent
de8e0f0399
commit
555c23d4fe
22
docs/server-statemachine.dot
Normal file
22
docs/server-statemachine.dot
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
. thinking about state-machine from "hand-drawn" perspective
|
||||
. will it look the same as an Automat one?
|
||||
**/
|
||||
|
||||
digraph {
|
||||
listening -> wait_relay [label="connection_made"]
|
||||
|
||||
wait_relay -> wait_partner [label="please_relay\nFindPartner"]
|
||||
wait_relay -> wait_partner [label="please_relay_for_side\nFindPartner"]
|
||||
wait_relay -> done [label="invalid_token\nSend('bad handshake')\nDisconnect"]
|
||||
wait_relay -> done [label="connection_lost"]
|
||||
|
||||
wait_partner -> relaying [label="got_partner\nConnectPartner(partner)\nSend('ok')"]
|
||||
wait_partner -> done [label="got_bytes\nDisconnect"]
|
||||
wait_partner -> done [label="connection_lost"]
|
||||
|
||||
relaying -> relaying [label="got_bytes\nSend(bytes)"]
|
||||
relaying -> done [label="partner_connection_lost\nDisconnectMe"]
|
||||
relaying -> done [label="connection_lost\nDisconnectPartner"]
|
||||
}
|
||||
|
299
src/wormhole_transit_relay/server_state.py
Normal file
299
src/wormhole_transit_relay/server_state.py
Normal file
|
@ -0,0 +1,299 @@
|
|||
|
||||
import automat
|
||||
from zope.interface import (
|
||||
Interface,
|
||||
implementer,
|
||||
)
|
||||
|
||||
|
||||
class ITransitClient(Interface):
|
||||
def send(data):
|
||||
"""
|
||||
Send some byets to the client
|
||||
"""
|
||||
|
||||
def disconnect(reason):
|
||||
"""
|
||||
Disconnect the client transport
|
||||
"""
|
||||
|
||||
def connect_partner(other):
|
||||
"""
|
||||
Hook up to our partner.
|
||||
:param ITransitClient other: our partner
|
||||
"""
|
||||
|
||||
def disconnect_partner():
|
||||
"""
|
||||
Disconnect our partner's transport
|
||||
"""
|
||||
|
||||
|
||||
@implementer(ITransitClient)
|
||||
class TestClient(object):
|
||||
_partner = None
|
||||
_data = b""
|
||||
|
||||
def send_to_partner(self, data):
|
||||
print("{} GOT:{}".format(id(self), repr(data)))
|
||||
if self._partner:
|
||||
self._partner.send(data)
|
||||
|
||||
def send(self, data):
|
||||
print("{} SEND:{}".format(id(self), repr(data)))
|
||||
self._data += data
|
||||
|
||||
def disconnect(self):
|
||||
print("disconnect")
|
||||
|
||||
def connect_partner(self, other):
|
||||
print("connect_partner: {} <--> {}".format(id(self), id(other)))
|
||||
assert self._partner is None, "double partner"
|
||||
self._partner = other
|
||||
|
||||
def disconnect_partner(self):
|
||||
assert self._partner is not None, "no partner"
|
||||
print("disconnect_partner: {}".format(id(self._partner)))
|
||||
|
||||
|
||||
class PendingRequests(object):
|
||||
"""
|
||||
Tracks the tokens we have received from client connections and
|
||||
maps them to their partner connections
|
||||
"""
|
||||
|
||||
def register_token(self, *args):
|
||||
"""
|
||||
"""
|
||||
|
||||
|
||||
class TransitServer(object):
|
||||
"""
|
||||
Encapsulates the state-machine of the server side of a transit
|
||||
relay connection.
|
||||
|
||||
Once the protocol has been told to relay (or to relay for a side)
|
||||
it starts passing all received bytes to the other side until it
|
||||
closes.
|
||||
"""
|
||||
|
||||
_machine = automat.MethodicalMachine()
|
||||
_client = None
|
||||
|
||||
@_machine.input()
|
||||
def connection_made(self, client):
|
||||
"""
|
||||
A client has connected. May only be called once.
|
||||
|
||||
:param ITransitClient client: our client.
|
||||
"""
|
||||
# NB: the "only called once" is enforced by the state-machine;
|
||||
# this input is only valid for the "listening" state, to which
|
||||
# we never return.
|
||||
|
||||
@_machine.input()
|
||||
def please_relay(self, token):
|
||||
pass
|
||||
|
||||
@_machine.input()
|
||||
def please_relay_for_side(self, token, side):
|
||||
pass
|
||||
|
||||
@_machine.input()
|
||||
def bad_token(self):
|
||||
"""
|
||||
A bad token / relay line was received
|
||||
"""
|
||||
|
||||
@_machine.input()
|
||||
def got_partner(self, client):
|
||||
"""
|
||||
The partner for this relay session has been found
|
||||
"""
|
||||
|
||||
@_machine.input()
|
||||
def connection_lost(self):
|
||||
pass
|
||||
|
||||
@_machine.input()
|
||||
def partner_connection_lost(self):
|
||||
pass
|
||||
|
||||
@_machine.input()
|
||||
def got_bytes(self, data):
|
||||
"""
|
||||
Some bytes have arrived (that aren't part of the handshake)
|
||||
"""
|
||||
|
||||
@_machine.output()
|
||||
def _remember_client(self, client):
|
||||
self._client = client
|
||||
|
||||
@_machine.output()
|
||||
def _register_token(self, token):
|
||||
return self._real_register_token_for_side(token, None)
|
||||
|
||||
@_machine.output()
|
||||
def _register_token_for_side(self, token, side):
|
||||
return self._real_register_token_for_side(token, side)
|
||||
|
||||
@_machine.output()
|
||||
def _unregister(self):
|
||||
"""
|
||||
remove us from the thing that remembers tokens and sides
|
||||
"""
|
||||
|
||||
@_machine.output()
|
||||
def _send_bad(self):
|
||||
self._client.send("bad handshake\n")
|
||||
|
||||
@_machine.output()
|
||||
def _send_ok(self):
|
||||
self._client.send("ok\n")
|
||||
|
||||
@_machine.output()
|
||||
def _send(self, data):
|
||||
self._client.send(data)
|
||||
|
||||
@_machine.output()
|
||||
def _send_to_partner(self, data):
|
||||
self._client.send_to_partner(data)
|
||||
|
||||
@_machine.output()
|
||||
def _connect_partner(self, client):
|
||||
self._client.connect_partner(client)
|
||||
|
||||
@_machine.output()
|
||||
def _disconnect(self):
|
||||
self._client.disconnect()
|
||||
|
||||
@_machine.output()
|
||||
def _disconnect_partner(self):
|
||||
self._client.disconnect_partner()
|
||||
|
||||
def _real_register_token_for_side(self, token, side):
|
||||
"""
|
||||
basically, _got_handshake() + connection_got_token() from "real"
|
||||
code ...and if this is the "second" side, hook them up and
|
||||
pass .got_partner() input to both
|
||||
"""
|
||||
|
||||
@_machine.state(initial=True)
|
||||
def listening(self):
|
||||
"""
|
||||
Initial state, awaiting connection.
|
||||
"""
|
||||
|
||||
@_machine.state()
|
||||
def wait_relay(self):
|
||||
"""
|
||||
Waiting for a 'relay' message
|
||||
"""
|
||||
|
||||
@_machine.state()
|
||||
def wait_partner(self):
|
||||
"""
|
||||
Waiting for our partner to connect
|
||||
"""
|
||||
|
||||
@_machine.state()
|
||||
def relaying(self):
|
||||
"""
|
||||
Relaying bytes to our partner
|
||||
"""
|
||||
|
||||
@_machine.state()
|
||||
def done(self):
|
||||
"""
|
||||
Terminal state
|
||||
"""
|
||||
|
||||
listening.upon(
|
||||
connection_made,
|
||||
enter=wait_relay,
|
||||
outputs=[_remember_client],
|
||||
)
|
||||
|
||||
wait_relay.upon(
|
||||
please_relay,
|
||||
enter=wait_partner,
|
||||
outputs=[_register_token],
|
||||
)
|
||||
wait_relay.upon(
|
||||
please_relay_for_side,
|
||||
enter=wait_partner,
|
||||
outputs=[_register_token_for_side],
|
||||
)
|
||||
wait_relay.upon(
|
||||
bad_token,
|
||||
enter=done,
|
||||
outputs=[_send_bad, _disconnect],
|
||||
)
|
||||
wait_relay.upon(
|
||||
connection_lost,
|
||||
enter=done,
|
||||
outputs=[_disconnect],
|
||||
)
|
||||
|
||||
wait_partner.upon(
|
||||
got_partner,
|
||||
enter=relaying,
|
||||
outputs=[_send_ok, _connect_partner],
|
||||
)
|
||||
wait_partner.upon(
|
||||
connection_lost,
|
||||
enter=done,
|
||||
outputs=[_unregister],
|
||||
)
|
||||
|
||||
relaying.upon(
|
||||
got_bytes,
|
||||
enter=relaying,
|
||||
outputs=[_send_to_partner],
|
||||
)
|
||||
relaying.upon(
|
||||
connection_lost,
|
||||
enter=done,
|
||||
outputs=[_disconnect_partner, _unregister],
|
||||
)
|
||||
relaying.upon(
|
||||
partner_connection_lost,
|
||||
enter=done,
|
||||
outputs=[_disconnect, _unregister],
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
# actions:
|
||||
# - send("ok")
|
||||
# - send("bad handshake")
|
||||
# - disconnect
|
||||
# - ...
|
||||
|
||||
if __name__ == "__main__":
|
||||
server0 = TransitServer()
|
||||
client0 = TestClient()
|
||||
server1 = TransitServer()
|
||||
client1 = TestClient()
|
||||
server0.connection_made(client0)
|
||||
server0.please_relay(b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
|
||||
# this would be an error, because our partner hasn't shown up yet
|
||||
# print(server0.got_bytes(b"asdf"))
|
||||
|
||||
server1.connection_made(client1)
|
||||
server1.please_relay(b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
|
||||
# XXX the PendingRequests stuff should do this, going "by hand" for now
|
||||
server0.got_partner(client1)
|
||||
server1.got_partner(client0)
|
||||
|
||||
# should be connected now
|
||||
server0.got_bytes(b"asdf")
|
||||
# client1 should receive b"asdf"
|
||||
|
||||
server0.connection_lost()
|
||||
print("----[ received data on both sides ]----")
|
||||
print("client0:{}".format(repr(client0._data)))
|
||||
print("client1:{}".format(repr(client1._data)))
|
Loading…
Reference in New Issue
Block a user