I think I just managed to forget that inbound_close requires we respond with a close ourselves. Also outbound open means we must add the subchannel to the inbound table, so we can receive any data on it at all.
668 lines
26 KiB
Python
668 lines
26 KiB
Python
from __future__ import print_function, unicode_literals
|
|
from zope.interface import alsoProvides
|
|
from twisted.trial import unittest
|
|
from twisted.internet.defer import Deferred
|
|
from twisted.internet.task import Clock, Cooperator
|
|
from twisted.internet.interfaces import IAddress
|
|
import mock
|
|
from ...eventual import EventualQueue
|
|
from ..._interfaces import ISend, IDilationManager, ITerminator
|
|
from ...util import dict_to_bytes
|
|
from ..._dilation import roles
|
|
from ..._dilation.manager import (Dilator, Manager, make_side,
|
|
OldPeerCannotDilateError,
|
|
UnknownDilationMessageType,
|
|
UnexpectedKCM,
|
|
UnknownMessageType)
|
|
from ..._dilation.connection import Open, Data, Close, Ack, KCM, Ping, Pong
|
|
from .common import clear_mock_calls
|
|
|
|
|
|
def make_dilator():
|
|
reactor = object()
|
|
clock = Clock()
|
|
eq = EventualQueue(clock)
|
|
term = mock.Mock(side_effect=lambda: True) # one write per Eventual tick
|
|
|
|
def term_factory():
|
|
return term
|
|
coop = Cooperator(terminationPredicateFactory=term_factory,
|
|
scheduler=eq.eventually)
|
|
send = mock.Mock()
|
|
alsoProvides(send, ISend)
|
|
dil = Dilator(reactor, eq, coop)
|
|
terminator = mock.Mock()
|
|
alsoProvides(terminator, ITerminator)
|
|
dil.wire(send, terminator)
|
|
return dil, send, reactor, eq, clock, coop
|
|
|
|
|
|
class TestDilator(unittest.TestCase):
|
|
def test_manager_and_endpoints(self):
|
|
dil, send, reactor, eq, clock, coop = make_dilator()
|
|
d1 = dil.dilate()
|
|
d2 = dil.dilate()
|
|
self.assertNoResult(d1)
|
|
self.assertNoResult(d2)
|
|
|
|
key = b"key"
|
|
transit_key = object()
|
|
with mock.patch("wormhole._dilation.manager.derive_key",
|
|
return_value=transit_key) as dk:
|
|
dil.got_key(key)
|
|
self.assertEqual(dk.mock_calls, [mock.call(key, b"dilation-v1", 32)])
|
|
self.assertIdentical(dil._transit_key, transit_key)
|
|
self.assertNoResult(d1)
|
|
self.assertNoResult(d2)
|
|
|
|
host_addr = dil._host_addr
|
|
|
|
peer_addr = object()
|
|
m_sca = mock.patch("wormhole._dilation.manager._SubchannelAddress",
|
|
return_value=peer_addr)
|
|
sc = mock.Mock()
|
|
m_sc = mock.patch("wormhole._dilation.manager.SubChannel",
|
|
return_value=sc)
|
|
scid0 = 0
|
|
|
|
m = mock.Mock()
|
|
alsoProvides(m, IDilationManager)
|
|
m.when_first_connected.return_value = wfc_d = Deferred()
|
|
with mock.patch("wormhole._dilation.manager.Manager",
|
|
return_value=m) as ml:
|
|
with mock.patch("wormhole._dilation.manager.make_side",
|
|
return_value="us"):
|
|
with m_sca, m_sc as m_sc_m:
|
|
dil.got_wormhole_versions({"can-dilate": ["1"]})
|
|
# that should create the Manager
|
|
self.assertEqual(ml.mock_calls, [mock.call(send, "us", transit_key,
|
|
None, reactor, eq, coop, host_addr, False)])
|
|
# and create subchannel0
|
|
self.assertEqual(m_sc_m.mock_calls,
|
|
[mock.call(scid0, m, host_addr, peer_addr)])
|
|
# and tell it to start, and get wait-for-it-to-connect Deferred
|
|
self.assertEqual(m.mock_calls, [mock.call.set_subchannel_zero(scid0, sc),
|
|
mock.call.start(),
|
|
mock.call.when_first_connected(),
|
|
])
|
|
clear_mock_calls(m)
|
|
self.assertNoResult(d1)
|
|
self.assertNoResult(d2)
|
|
|
|
ce = mock.Mock()
|
|
m_ce = mock.patch("wormhole._dilation.manager.ControlEndpoint",
|
|
return_value=ce)
|
|
lep = object()
|
|
m_sle = mock.patch("wormhole._dilation.manager.SubchannelListenerEndpoint",
|
|
return_value=lep)
|
|
|
|
with m_ce as m_ce_m, m_sle as m_sle_m:
|
|
wfc_d.callback(None)
|
|
eq.flush_sync()
|
|
self.assertEqual(m_ce_m.mock_calls, [mock.call(peer_addr)])
|
|
self.assertEqual(ce.mock_calls, [mock.call._subchannel_zero_opened(sc)])
|
|
self.assertEqual(m_sle_m.mock_calls, [mock.call(m, host_addr)])
|
|
self.assertEqual(m.mock_calls,
|
|
[mock.call.set_listener_endpoint(lep),
|
|
])
|
|
clear_mock_calls(m)
|
|
|
|
eps = self.successResultOf(d1)
|
|
self.assertEqual(eps, self.successResultOf(d2))
|
|
d3 = dil.dilate()
|
|
eq.flush_sync()
|
|
self.assertEqual(eps, self.successResultOf(d3))
|
|
|
|
# all subsequent DILATE-n messages should get passed to the manager
|
|
self.assertEqual(m.mock_calls, [])
|
|
pleasemsg = dict(type="please", side="them")
|
|
dil.received_dilate(dict_to_bytes(pleasemsg))
|
|
self.assertEqual(m.mock_calls, [mock.call.rx_PLEASE(pleasemsg)])
|
|
clear_mock_calls(m)
|
|
|
|
hintmsg = dict(type="connection-hints")
|
|
dil.received_dilate(dict_to_bytes(hintmsg))
|
|
self.assertEqual(m.mock_calls, [mock.call.rx_HINTS(hintmsg)])
|
|
clear_mock_calls(m)
|
|
|
|
# we're nominally the LEADER, and the leader would not normally be
|
|
# receiving a RECONNECT, but since we've mocked out the Manager it
|
|
# won't notice
|
|
dil.received_dilate(dict_to_bytes(dict(type="reconnect")))
|
|
self.assertEqual(m.mock_calls, [mock.call.rx_RECONNECT()])
|
|
clear_mock_calls(m)
|
|
|
|
dil.received_dilate(dict_to_bytes(dict(type="reconnecting")))
|
|
self.assertEqual(m.mock_calls, [mock.call.rx_RECONNECTING()])
|
|
clear_mock_calls(m)
|
|
|
|
dil.received_dilate(dict_to_bytes(dict(type="unknown")))
|
|
self.assertEqual(m.mock_calls, [])
|
|
self.flushLoggedErrors(UnknownDilationMessageType)
|
|
|
|
def test_peer_cannot_dilate(self):
|
|
dil, send, reactor, eq, clock, coop = make_dilator()
|
|
d1 = dil.dilate()
|
|
self.assertNoResult(d1)
|
|
|
|
dil._transit_key = b"\x01" * 32
|
|
dil.got_wormhole_versions({}) # missing "can-dilate"
|
|
eq.flush_sync()
|
|
f = self.failureResultOf(d1)
|
|
f.check(OldPeerCannotDilateError)
|
|
|
|
def test_disjoint_versions(self):
|
|
dil, send, reactor, eq, clock, coop = make_dilator()
|
|
d1 = dil.dilate()
|
|
self.assertNoResult(d1)
|
|
|
|
dil._transit_key = b"key"
|
|
dil.got_wormhole_versions({"can-dilate": [-1]})
|
|
eq.flush_sync()
|
|
f = self.failureResultOf(d1)
|
|
f.check(OldPeerCannotDilateError)
|
|
|
|
def test_early_dilate_messages(self):
|
|
dil, send, reactor, eq, clock, coop = make_dilator()
|
|
dil._transit_key = b"key"
|
|
d1 = dil.dilate()
|
|
host_addr = dil._host_addr
|
|
self.assertNoResult(d1)
|
|
pleasemsg = dict(type="please", side="them")
|
|
dil.received_dilate(dict_to_bytes(pleasemsg))
|
|
hintmsg = dict(type="connection-hints")
|
|
dil.received_dilate(dict_to_bytes(hintmsg))
|
|
|
|
m = mock.Mock()
|
|
alsoProvides(m, IDilationManager)
|
|
m.when_first_connected.return_value = Deferred()
|
|
|
|
scid0 = 0
|
|
sc = mock.Mock()
|
|
m_sc = mock.patch("wormhole._dilation.manager.SubChannel",
|
|
return_value=sc)
|
|
|
|
with mock.patch("wormhole._dilation.manager.Manager",
|
|
return_value=m) as ml:
|
|
with mock.patch("wormhole._dilation.manager.make_side",
|
|
return_value="us"):
|
|
with m_sc:
|
|
dil.got_wormhole_versions({"can-dilate": ["1"]})
|
|
self.assertEqual(ml.mock_calls, [mock.call(send, "us", b"key",
|
|
None, reactor, eq, coop, host_addr, False)])
|
|
self.assertEqual(m.mock_calls, [mock.call.set_subchannel_zero(scid0, sc),
|
|
mock.call.start(),
|
|
mock.call.rx_PLEASE(pleasemsg),
|
|
mock.call.rx_HINTS(hintmsg),
|
|
mock.call.when_first_connected()])
|
|
|
|
def test_transit_relay(self):
|
|
dil, send, reactor, eq, clock, coop = make_dilator()
|
|
dil._transit_key = b"key"
|
|
host_addr = dil._host_addr
|
|
relay = object()
|
|
d1 = dil.dilate(transit_relay_location=relay)
|
|
self.assertNoResult(d1)
|
|
|
|
scid0 = 0
|
|
sc = mock.Mock()
|
|
m_sc = mock.patch("wormhole._dilation.manager.SubChannel",
|
|
return_value=sc)
|
|
|
|
with mock.patch("wormhole._dilation.manager.Manager") as ml:
|
|
with mock.patch("wormhole._dilation.manager.make_side",
|
|
return_value="us"):
|
|
with m_sc:
|
|
dil.got_wormhole_versions({"can-dilate": ["1"]})
|
|
self.assertEqual(ml.mock_calls, [mock.call(send, "us", b"key",
|
|
relay, reactor, eq, coop, host_addr, False),
|
|
mock.call().set_subchannel_zero(scid0, sc),
|
|
mock.call().start(),
|
|
mock.call().when_first_connected()])
|
|
|
|
|
|
LEADER = "ff3456abcdef"
|
|
FOLLOWER = "123456abcdef"
|
|
|
|
|
|
def make_manager(leader=True):
|
|
class Holder:
|
|
pass
|
|
h = Holder()
|
|
h.send = mock.Mock()
|
|
alsoProvides(h.send, ISend)
|
|
if leader:
|
|
side = LEADER
|
|
else:
|
|
side = FOLLOWER
|
|
h.key = b"\x00" * 32
|
|
h.relay = None
|
|
h.reactor = object()
|
|
h.clock = Clock()
|
|
h.eq = EventualQueue(h.clock)
|
|
term = mock.Mock(side_effect=lambda: True) # one write per Eventual tick
|
|
|
|
def term_factory():
|
|
return term
|
|
h.coop = Cooperator(terminationPredicateFactory=term_factory,
|
|
scheduler=h.eq.eventually)
|
|
h.inbound = mock.Mock()
|
|
h.Inbound = mock.Mock(return_value=h.inbound)
|
|
h.outbound = mock.Mock()
|
|
h.Outbound = mock.Mock(return_value=h.outbound)
|
|
h.hostaddr = mock.Mock()
|
|
alsoProvides(h.hostaddr, IAddress)
|
|
with mock.patch("wormhole._dilation.manager.Inbound", h.Inbound):
|
|
with mock.patch("wormhole._dilation.manager.Outbound", h.Outbound):
|
|
m = Manager(h.send, side, h.key, h.relay, h.reactor, h.eq, h.coop, h.hostaddr)
|
|
return m, h
|
|
|
|
|
|
class TestManager(unittest.TestCase):
|
|
def test_make_side(self):
|
|
side = make_side()
|
|
self.assertEqual(type(side), type(u""))
|
|
self.assertEqual(len(side), 2 * 6)
|
|
|
|
def test_create(self):
|
|
m, h = make_manager()
|
|
|
|
def test_leader(self):
|
|
m, h = make_manager(leader=True)
|
|
self.assertEqual(h.send.mock_calls, [])
|
|
self.assertEqual(h.Inbound.mock_calls, [mock.call(m, h.hostaddr)])
|
|
self.assertEqual(h.Outbound.mock_calls, [mock.call(m, h.coop)])
|
|
|
|
m.start()
|
|
self.assertEqual(h.send.mock_calls, [
|
|
mock.call.send("dilate-0",
|
|
dict_to_bytes({"type": "please", "side": LEADER}))
|
|
])
|
|
clear_mock_calls(h.send)
|
|
|
|
wfc_d = m.when_first_connected()
|
|
self.assertNoResult(wfc_d)
|
|
|
|
# ignore early hints
|
|
m.rx_HINTS({})
|
|
self.assertEqual(h.send.mock_calls, [])
|
|
|
|
c = mock.Mock()
|
|
connector = mock.Mock(return_value=c)
|
|
with mock.patch("wormhole._dilation.manager.Connector", connector):
|
|
# receiving this PLEASE triggers creation of the Connector
|
|
m.rx_PLEASE({"side": FOLLOWER})
|
|
self.assertEqual(h.send.mock_calls, [])
|
|
self.assertEqual(connector.mock_calls, [
|
|
mock.call(b"\x00" * 32, None, m, h.reactor, h.eq,
|
|
False, # no_listen
|
|
None, # tor
|
|
None, # timing
|
|
LEADER, roles.LEADER),
|
|
])
|
|
self.assertEqual(c.mock_calls, [mock.call.start()])
|
|
clear_mock_calls(connector, c)
|
|
|
|
self.assertNoResult(wfc_d)
|
|
|
|
# now any inbound hints should get passed to our Connector
|
|
with mock.patch("wormhole._dilation.manager.parse_hint",
|
|
side_effect=["p1", None, "p3"]) as ph:
|
|
m.rx_HINTS({"hints": [1, 2, 3]})
|
|
self.assertEqual(ph.mock_calls, [mock.call(1), mock.call(2), mock.call(3)])
|
|
self.assertEqual(c.mock_calls, [mock.call.got_hints(["p1", "p3"])])
|
|
clear_mock_calls(ph, c)
|
|
|
|
# and we send out any (listening) hints from our Connector
|
|
m.send_hints([1, 2])
|
|
self.assertEqual(h.send.mock_calls, [
|
|
mock.call.send("dilate-1",
|
|
dict_to_bytes({"type": "connection-hints",
|
|
"hints": [1, 2]}))
|
|
])
|
|
clear_mock_calls(h.send)
|
|
|
|
# the first successful connection fires when_first_connected(), so
|
|
# the Dilator can create and return the endpoints
|
|
c1 = mock.Mock()
|
|
m.connector_connection_made(c1)
|
|
|
|
self.assertEqual(h.inbound.mock_calls, [mock.call.use_connection(c1)])
|
|
self.assertEqual(h.outbound.mock_calls, [mock.call.use_connection(c1)])
|
|
clear_mock_calls(h.inbound, h.outbound)
|
|
|
|
h.eq.flush_sync()
|
|
self.successResultOf(wfc_d) # fires with None
|
|
wfc_d2 = m.when_first_connected()
|
|
h.eq.flush_sync()
|
|
self.successResultOf(wfc_d2)
|
|
|
|
scid0 = 0
|
|
sc0 = mock.Mock()
|
|
m.set_subchannel_zero(scid0, sc0)
|
|
listen_ep = mock.Mock()
|
|
m.set_listener_endpoint(listen_ep)
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.set_subchannel_zero(scid0, sc0),
|
|
mock.call.set_listener_endpoint(listen_ep),
|
|
])
|
|
clear_mock_calls(h.inbound)
|
|
|
|
# the Leader making a new outbound channel should get scid=1
|
|
scid1 = 1
|
|
self.assertEqual(m.allocate_subchannel_id(), scid1)
|
|
r1 = Open(10, scid1) # seqnum=10
|
|
h.outbound.build_record = mock.Mock(return_value=r1)
|
|
m.send_open(scid1)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.build_record(Open, scid1),
|
|
mock.call.queue_and_send_record(r1),
|
|
])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
r2 = Data(11, scid1, b"data")
|
|
h.outbound.build_record = mock.Mock(return_value=r2)
|
|
m.send_data(scid1, b"data")
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.build_record(Data, scid1, b"data"),
|
|
mock.call.queue_and_send_record(r2),
|
|
])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
r3 = Close(12, scid1)
|
|
h.outbound.build_record = mock.Mock(return_value=r3)
|
|
m.send_close(scid1)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.build_record(Close, scid1),
|
|
mock.call.queue_and_send_record(r3),
|
|
])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
# ack the OPEN
|
|
m.got_record(Ack(10))
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.handle_ack(10)
|
|
])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
# test that inbound records get acked and routed to Inbound
|
|
h.inbound.is_record_old = mock.Mock(return_value=False)
|
|
scid2 = 2
|
|
o200 = Open(200, scid2)
|
|
m.got_record(o200)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.send_if_connected(Ack(200))
|
|
])
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.is_record_old(o200),
|
|
mock.call.update_ack_watermark(200),
|
|
mock.call.handle_open(scid2),
|
|
])
|
|
clear_mock_calls(h.outbound, h.inbound)
|
|
|
|
# old (duplicate) records should provoke new Acks, but not get
|
|
# forwarded
|
|
h.inbound.is_record_old = mock.Mock(return_value=True)
|
|
m.got_record(o200)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.send_if_connected(Ack(200))
|
|
])
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.is_record_old(o200),
|
|
])
|
|
clear_mock_calls(h.outbound, h.inbound)
|
|
|
|
# check Data and Close too
|
|
h.inbound.is_record_old = mock.Mock(return_value=False)
|
|
d201 = Data(201, scid2, b"data")
|
|
m.got_record(d201)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.send_if_connected(Ack(201))
|
|
])
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.is_record_old(d201),
|
|
mock.call.update_ack_watermark(201),
|
|
mock.call.handle_data(scid2, b"data"),
|
|
])
|
|
clear_mock_calls(h.outbound, h.inbound)
|
|
|
|
c202 = Close(202, scid2)
|
|
m.got_record(c202)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.send_if_connected(Ack(202))
|
|
])
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.is_record_old(c202),
|
|
mock.call.update_ack_watermark(202),
|
|
mock.call.handle_close(scid2),
|
|
])
|
|
clear_mock_calls(h.outbound, h.inbound)
|
|
|
|
# Now we lose the connection. The Leader should tell the other side
|
|
# that we're reconnecting.
|
|
|
|
m.connector_connection_lost()
|
|
self.assertEqual(h.send.mock_calls, [
|
|
mock.call.send("dilate-2",
|
|
dict_to_bytes({"type": "reconnect"}))
|
|
])
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.stop_using_connection()
|
|
])
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.stop_using_connection()
|
|
])
|
|
clear_mock_calls(h.send, h.inbound, h.outbound)
|
|
|
|
# leader does nothing (stays in FLUSHING) until the follower acks by
|
|
# sending RECONNECTING
|
|
|
|
# inbound hints should be ignored during FLUSHING
|
|
with mock.patch("wormhole._dilation.manager.parse_hint",
|
|
return_value=None) as ph:
|
|
m.rx_HINTS({"hints": [1, 2, 3]})
|
|
self.assertEqual(ph.mock_calls, []) # ignored
|
|
|
|
c2 = mock.Mock()
|
|
connector2 = mock.Mock(return_value=c2)
|
|
with mock.patch("wormhole._dilation.manager.Connector", connector2):
|
|
# this triggers creation of a new Connector
|
|
m.rx_RECONNECTING()
|
|
self.assertEqual(h.send.mock_calls, [])
|
|
self.assertEqual(connector2.mock_calls, [
|
|
mock.call(b"\x00" * 32, None, m, h.reactor, h.eq,
|
|
False, # no_listen
|
|
None, # tor
|
|
None, # timing
|
|
LEADER, roles.LEADER),
|
|
])
|
|
self.assertEqual(c2.mock_calls, [mock.call.start()])
|
|
clear_mock_calls(connector2, c2)
|
|
|
|
self.assertEqual(h.inbound.mock_calls, [])
|
|
self.assertEqual(h.outbound.mock_calls, [])
|
|
|
|
# and a new connection should re-register with Inbound/Outbound,
|
|
# which are responsible for re-sending unacked queued messages
|
|
c3 = mock.Mock()
|
|
m.connector_connection_made(c3)
|
|
|
|
self.assertEqual(h.inbound.mock_calls, [mock.call.use_connection(c3)])
|
|
self.assertEqual(h.outbound.mock_calls, [mock.call.use_connection(c3)])
|
|
clear_mock_calls(h.inbound, h.outbound)
|
|
|
|
def test_follower(self):
|
|
m, h = make_manager(leader=False)
|
|
|
|
m.start()
|
|
self.assertEqual(h.send.mock_calls, [
|
|
mock.call.send("dilate-0",
|
|
dict_to_bytes({"type": "please", "side": FOLLOWER}))
|
|
])
|
|
clear_mock_calls(h.send)
|
|
|
|
c = mock.Mock()
|
|
connector = mock.Mock(return_value=c)
|
|
with mock.patch("wormhole._dilation.manager.Connector", connector):
|
|
# receiving this PLEASE triggers creation of the Connector
|
|
m.rx_PLEASE({"side": LEADER})
|
|
self.assertEqual(h.send.mock_calls, [])
|
|
self.assertEqual(connector.mock_calls, [
|
|
mock.call(b"\x00" * 32, None, m, h.reactor, h.eq,
|
|
False, # no_listen
|
|
None, # tor
|
|
None, # timing
|
|
FOLLOWER, roles.FOLLOWER),
|
|
])
|
|
self.assertEqual(c.mock_calls, [mock.call.start()])
|
|
clear_mock_calls(connector, c)
|
|
|
|
# get connected, then lose the connection
|
|
c1 = mock.Mock()
|
|
m.connector_connection_made(c1)
|
|
self.assertEqual(h.inbound.mock_calls, [mock.call.use_connection(c1)])
|
|
self.assertEqual(h.outbound.mock_calls, [mock.call.use_connection(c1)])
|
|
clear_mock_calls(h.inbound, h.outbound)
|
|
|
|
# now lose the connection. As the follower, we don't notify the
|
|
# leader, we just wait for them to notice
|
|
m.connector_connection_lost()
|
|
self.assertEqual(h.send.mock_calls, [])
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.stop_using_connection()
|
|
])
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.stop_using_connection()
|
|
])
|
|
clear_mock_calls(h.send, h.inbound, h.outbound)
|
|
|
|
# now we get a RECONNECT: we should send RECONNECTING
|
|
c2 = mock.Mock()
|
|
connector2 = mock.Mock(return_value=c2)
|
|
with mock.patch("wormhole._dilation.manager.Connector", connector2):
|
|
m.rx_RECONNECT()
|
|
self.assertEqual(h.send.mock_calls, [
|
|
mock.call.send("dilate-1",
|
|
dict_to_bytes({"type": "reconnecting"}))
|
|
])
|
|
self.assertEqual(connector2.mock_calls, [
|
|
mock.call(b"\x00" * 32, None, m, h.reactor, h.eq,
|
|
False, # no_listen
|
|
None, # tor
|
|
None, # timing
|
|
FOLLOWER, roles.FOLLOWER),
|
|
])
|
|
self.assertEqual(c2.mock_calls, [mock.call.start()])
|
|
clear_mock_calls(connector2, c2)
|
|
|
|
# while we're trying to connect, we get told to stop again, so we
|
|
# should abandon the connection attempt and start another
|
|
c3 = mock.Mock()
|
|
connector3 = mock.Mock(return_value=c3)
|
|
with mock.patch("wormhole._dilation.manager.Connector", connector3):
|
|
m.rx_RECONNECT()
|
|
self.assertEqual(c2.mock_calls, [mock.call.stop()])
|
|
self.assertEqual(connector3.mock_calls, [
|
|
mock.call(b"\x00" * 32, None, m, h.reactor, h.eq,
|
|
False, # no_listen
|
|
None, # tor
|
|
None, # timing
|
|
FOLLOWER, roles.FOLLOWER),
|
|
])
|
|
self.assertEqual(c3.mock_calls, [mock.call.start()])
|
|
clear_mock_calls(c2, connector3, c3)
|
|
|
|
m.connector_connection_made(c3)
|
|
# finally if we're already connected, rx_RECONNECT means we should
|
|
# abandon this connection (even though it still looks ok to us), then
|
|
# when the attempt is finished stopping, we should start another
|
|
|
|
m.rx_RECONNECT()
|
|
|
|
c4 = mock.Mock()
|
|
connector4 = mock.Mock(return_value=c4)
|
|
with mock.patch("wormhole._dilation.manager.Connector", connector4):
|
|
m.connector_connection_lost()
|
|
self.assertEqual(c3.mock_calls, [mock.call.disconnect()])
|
|
self.assertEqual(connector4.mock_calls, [
|
|
mock.call(b"\x00" * 32, None, m, h.reactor, h.eq,
|
|
False, # no_listen
|
|
None, # tor
|
|
None, # timing
|
|
FOLLOWER, roles.FOLLOWER),
|
|
])
|
|
self.assertEqual(c4.mock_calls, [mock.call.start()])
|
|
clear_mock_calls(c3, connector4, c4)
|
|
|
|
def test_mirror(self):
|
|
# receive a PLEASE with the same side as us: shouldn't happen
|
|
m, h = make_manager(leader=True)
|
|
|
|
m.start()
|
|
clear_mock_calls(h.send)
|
|
e = self.assertRaises(ValueError, m.rx_PLEASE, {"side": LEADER})
|
|
self.assertEqual(str(e), "their side shouldn't be equal: reflection?")
|
|
|
|
def test_ping_pong(self):
|
|
m, h = make_manager(leader=False)
|
|
|
|
m.got_record(KCM())
|
|
self.flushLoggedErrors(UnexpectedKCM)
|
|
|
|
m.got_record(Ping(1))
|
|
self.assertEqual(h.outbound.mock_calls,
|
|
[mock.call.send_if_connected(Pong(1))])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
m.got_record(Pong(2))
|
|
# currently ignored, will eventually update a timer
|
|
|
|
m.got_record("not recognized")
|
|
e = self.flushLoggedErrors(UnknownMessageType)
|
|
self.assertEqual(len(e), 1)
|
|
self.assertEqual(str(e[0].value), "not recognized")
|
|
|
|
m.send_ping(3)
|
|
self.assertEqual(h.outbound.mock_calls,
|
|
[mock.call.send_if_connected(Pong(3))])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
def test_subchannel(self):
|
|
m, h = make_manager(leader=True)
|
|
sc = object()
|
|
|
|
m.subchannel_pauseProducing(sc)
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.subchannel_pauseProducing(sc)])
|
|
clear_mock_calls(h.inbound)
|
|
|
|
m.subchannel_resumeProducing(sc)
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.subchannel_resumeProducing(sc)])
|
|
clear_mock_calls(h.inbound)
|
|
|
|
m.subchannel_stopProducing(sc)
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.subchannel_stopProducing(sc)])
|
|
clear_mock_calls(h.inbound)
|
|
|
|
p = object()
|
|
streaming = object()
|
|
|
|
m.subchannel_registerProducer(sc, p, streaming)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.subchannel_registerProducer(sc, p, streaming)])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
m.subchannel_unregisterProducer(sc)
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.subchannel_unregisterProducer(sc)])
|
|
clear_mock_calls(h.outbound)
|
|
|
|
m.subchannel_closed(4, sc)
|
|
self.assertEqual(h.inbound.mock_calls, [
|
|
mock.call.subchannel_closed(4, sc)])
|
|
self.assertEqual(h.outbound.mock_calls, [
|
|
mock.call.subchannel_closed(4, sc)])
|
|
clear_mock_calls(h.inbound, h.outbound)
|