Merge remote-tracking branch 'origin/master' into 3578.remove-encode_tail_segment

This commit is contained in:
Jean-Paul Calderone 2021-01-04 15:08:27 -05:00
commit 3c203828c5
14 changed files with 345 additions and 341 deletions

0
newsfragments/3529.minor Normal file
View File

0
newsfragments/3534.minor Normal file
View File

0
newsfragments/3575.minor Normal file
View File

View File

@ -16,7 +16,7 @@ from six import ensure_text, ensure_str
import time
from zope.interface import implementer
from twisted.application import service
from foolscap.api import Referenceable, eventually
from foolscap.api import Referenceable
from allmydata.interfaces import InsufficientVersionError
from allmydata.introducer.interfaces import IIntroducerClient, \
RIIntroducerSubscriberClient_v2
@ -24,6 +24,9 @@ from allmydata.introducer.common import sign_to_foolscap, unsign_from_foolscap,\
get_tubid_string_from_ann
from allmydata.util import log, yamlutil, connection_status
from allmydata.util.rrefutil import add_version_to_remote_reference
from allmydata.util.observer import (
ObserverList,
)
from allmydata.crypto.error import BadSignature
from allmydata.util.assertutil import precondition
@ -62,8 +65,7 @@ class IntroducerClient(service.Service, Referenceable):
self._publisher = None
self._since = None
self._local_subscribers = [] # (servicename,cb,args,kwargs) tuples
self._subscribed_service_names = set()
self._local_subscribers = {} # {servicename: ObserverList}
self._subscriptions = set() # requests we've actually sent
# _inbound_announcements remembers one announcement per
@ -177,21 +179,21 @@ class IntroducerClient(service.Service, Referenceable):
return log.msg(*args, **kwargs)
def subscribe_to(self, service_name, cb, *args, **kwargs):
self._local_subscribers.append( (service_name,cb,args,kwargs) )
self._subscribed_service_names.add(service_name)
obs = self._local_subscribers.setdefault(service_name, ObserverList())
obs.subscribe(lambda key_s, ann: cb(key_s, ann, *args, **kwargs))
self._maybe_subscribe()
for index,(ann,key_s,when) in list(self._inbound_announcements.items()):
precondition(isinstance(key_s, bytes), key_s)
servicename = index[0]
if servicename == service_name:
eventually(cb, key_s, ann, *args, **kwargs)
obs.notify(key_s, ann)
def _maybe_subscribe(self):
if not self._publisher:
self.log("want to subscribe, but no introducer yet",
level=log.NOISY)
return
for service_name in self._subscribed_service_names:
for service_name in self._local_subscribers:
if service_name in self._subscriptions:
continue
self._subscriptions.add(service_name)
@ -270,7 +272,7 @@ class IntroducerClient(service.Service, Referenceable):
precondition(isinstance(key_s, bytes), key_s)
self._debug_counts["inbound_announcement"] += 1
service_name = str(ann["service-name"])
if service_name not in self._subscribed_service_names:
if service_name not in self._local_subscribers:
self.log("announcement for a service we don't care about [%s]"
% (service_name,), level=log.UNUSUAL, umid="dIpGNA")
self._debug_counts["wrong_service"] += 1
@ -341,9 +343,9 @@ class IntroducerClient(service.Service, Referenceable):
def _deliver_announcements(self, key_s, ann):
precondition(isinstance(key_s, bytes), key_s)
service_name = str(ann["service-name"])
for (service_name2,cb,args,kwargs) in self._local_subscribers:
if service_name2 == service_name:
eventually(cb, key_s, ann, *args, **kwargs)
obs = self._local_subscribers.get(service_name)
if obs is not None:
obs.notify(key_s, ann)
def connection_status(self):
assert self.running # startService builds _introducer_reconnector

View File

@ -669,8 +669,8 @@ def create_connection_handlers(config, i2p_provider, tor_provider):
# create that handler, so hints which want it will be ignored.
handlers = {
"tcp": _make_tcp_handler(),
"tor": tor_provider.get_tor_handler(),
"i2p": i2p_provider.get_i2p_handler(),
"tor": tor_provider.get_client_endpoint(),
"i2p": i2p_provider.get_client_endpoint(),
}
log.msg(
format="built Foolscap connection handlers for: %(known_handlers)s",

View File

@ -1,149 +1,69 @@
import os
import mock
from twisted.trial import unittest
from twisted.internet import reactor, endpoints, defer
from twisted.internet.interfaces import IStreamClientEndpoint
from twisted.internet import reactor
from foolscap.connections import tcp
from testtools.matchers import (
MatchesDict,
IsInstance,
Equals,
)
from ..node import PrivacyError, config_from_string
from ..node import create_connection_handlers
from ..node import create_main_tub
from ..util.i2p_provider import create as create_i2p_provider
from ..util.tor_provider import create as create_tor_provider
from .common import (
SyncTestCase,
ConstantAddresses,
)
BASECONFIG = ""
class TCP(unittest.TestCase):
def test_default(self):
class CreateConnectionHandlersTests(SyncTestCase):
"""
Tests for the Foolscap connection handlers return by
``create_connection_handlers``.
"""
def test_foolscap_handlers(self):
"""
``create_connection_handlers`` returns a Foolscap connection handlers
dictionary mapping ``"tcp"`` to
``foolscap.connections.tcp.DefaultTCP``, ``"tor"`` to the supplied Tor
provider's handler, and ``"i2p"`` to the supplied I2P provider's
handler.
"""
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG,
)
_, foolscap_handlers = create_connection_handlers(config, mock.Mock(), mock.Mock())
self.assertIsInstance(
foolscap_handlers['tcp'],
tcp.DefaultTCP,
tor_endpoint = object()
tor = ConstantAddresses(handler=tor_endpoint)
i2p_endpoint = object()
i2p = ConstantAddresses(handler=i2p_endpoint)
_, foolscap_handlers = create_connection_handlers(
config,
i2p,
tor,
)
self.assertThat(
foolscap_handlers,
MatchesDict({
"tcp": IsInstance(tcp.DefaultTCP),
"i2p": Equals(i2p_endpoint),
"tor": Equals(tor_endpoint),
}),
)
class Tor(unittest.TestCase):
def test_disabled(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[tor]\nenabled = false\n",
)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertEqual(h, None)
def test_unimportable(self):
with mock.patch("allmydata.util.tor_provider._import_tor",
return_value=None):
config = config_from_string("fake.port", "no-basedir", BASECONFIG)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertEqual(h, None)
def test_default(self):
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.default_socks",
return_value=h1) as f:
config = config_from_string("fake.port", "no-basedir", BASECONFIG)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertEqual(f.mock_calls, [mock.call()])
self.assertIdentical(h, h1)
def _do_test_launch(self, executable):
# the handler is created right away
config = BASECONFIG+"[tor]\nlaunch = true\n"
if executable:
config += "tor.executable = %s\n" % executable
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.control_endpoint_maker",
return_value=h1) as f:
config = config_from_string("fake.port", ".", config)
tp = create_tor_provider("reactor", config)
h = tp.get_tor_handler()
private_dir = config.get_config_path("private")
exp = mock.call(tp._make_control_endpoint,
takes_status=True)
self.assertEqual(f.mock_calls, [exp])
self.assertIdentical(h, h1)
# later, when Foolscap first connects, Tor should be launched
reactor = "reactor"
tcp = object()
tcep = object()
launch_tor = mock.Mock(return_value=defer.succeed(("ep_desc", tcp)))
cfs = mock.Mock(return_value=tcep)
with mock.patch("allmydata.util.tor_provider._launch_tor", launch_tor):
with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
d = tp._make_control_endpoint(reactor,
update_status=lambda status: None)
cep = self.successResultOf(d)
launch_tor.assert_called_with(reactor, executable,
os.path.abspath(private_dir),
tp._txtorcon)
cfs.assert_called_with(reactor, "ep_desc")
self.assertIs(cep, tcep)
def test_launch(self):
self._do_test_launch(None)
def test_launch_executable(self):
self._do_test_launch("/special/tor")
def test_socksport_unix_endpoint(self):
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.socks_endpoint",
return_value=h1) as f:
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[tor]\nsocks.port = unix:/var/lib/fw-daemon/tor_socks.socket\n",
)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0][1][0]))
self.assertIdentical(h, h1)
def test_socksport_endpoint(self):
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.socks_endpoint",
return_value=h1) as f:
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[tor]\nsocks.port = tcp:127.0.0.1:1234\n",
)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0][1][0]))
self.assertIdentical(h, h1)
def test_socksport_endpoint_otherhost(self):
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.socks_endpoint",
return_value=h1) as f:
config = config_from_string(
"no-basedir",
"fake.port",
BASECONFIG + "[tor]\nsocks.port = tcp:otherhost:1234\n",
)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertTrue(IStreamClientEndpoint.providedBy(f.mock_calls[0][1][0]))
self.assertIdentical(h, h1)
def test_socksport_bad_endpoint(self):
config = config_from_string(
"fake.port",
@ -176,73 +96,8 @@ class Tor(unittest.TestCase):
str(ctx.exception)
)
def test_controlport(self):
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.control_endpoint",
return_value=h1) as f:
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[tor]\ncontrol.port = tcp:localhost:1234\n",
)
tor_provider = create_tor_provider(reactor, config)
h = tor_provider.get_tor_handler()
self.assertEqual(len(f.mock_calls), 1)
ep = f.mock_calls[0][1][0]
self.assertIsInstance(ep, endpoints.TCP4ClientEndpoint)
self.assertIdentical(h, h1)
class I2P(unittest.TestCase):
def test_disabled(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[i2p]\nenabled = false\n",
)
i2p_provider = create_i2p_provider(None, config)
h = i2p_provider.get_i2p_handler()
self.assertEqual(h, None)
def test_unimportable(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG,
)
with mock.patch("allmydata.util.i2p_provider._import_i2p",
return_value=None):
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
self.assertEqual(h, None)
def test_default(self):
config = config_from_string("fake.port", "no-basedir", BASECONFIG)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.default",
return_value=h1) as f:
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
self.assertEqual(f.mock_calls, [mock.call(reactor, keyfile=None)])
self.assertIdentical(h, h1)
def test_samport(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[i2p]\nsam.port = tcp:localhost:1234\n",
)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.sam_endpoint",
return_value=h1) as f:
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
self.assertEqual(len(f.mock_calls), 1)
ep = f.mock_calls[0][1][0]
self.assertIsInstance(ep, endpoints.TCP4ClientEndpoint)
self.assertIdentical(h, h1)
def test_samport_and_launch(self):
config = config_from_string(
"no-basedir",
@ -258,82 +113,6 @@ class I2P(unittest.TestCase):
str(ctx.exception)
)
def test_launch(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[i2p]\nlaunch = true\n",
)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.launch",
return_value=h1) as f:
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
exp = mock.call(i2p_configdir=None, i2p_binary=None)
self.assertEqual(f.mock_calls, [exp])
self.assertIdentical(h, h1)
def test_launch_executable(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[i2p]\nlaunch = true\n" + "i2p.executable = i2p\n",
)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.launch",
return_value=h1) as f:
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
exp = mock.call(i2p_configdir=None, i2p_binary="i2p")
self.assertEqual(f.mock_calls, [exp])
self.assertIdentical(h, h1)
def test_launch_configdir(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[i2p]\nlaunch = true\n" + "i2p.configdir = cfg\n",
)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.launch",
return_value=h1) as f:
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
exp = mock.call(i2p_configdir="cfg", i2p_binary=None)
self.assertEqual(f.mock_calls, [exp])
self.assertIdentical(h, h1)
def test_launch_configdir_and_executable(self):
config = config_from_string(
"no-basedir",
"fake.port",
BASECONFIG + "[i2p]\nlaunch = true\n" +
"i2p.executable = i2p\n" + "i2p.configdir = cfg\n",
)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.launch",
return_value=h1) as f:
i2p_provider = create_i2p_provider(reactor, config)
h = i2p_provider.get_i2p_handler()
exp = mock.call(i2p_configdir="cfg", i2p_binary="i2p")
self.assertEqual(f.mock_calls, [exp])
self.assertIdentical(h, h1)
def test_configdir(self):
config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[i2p]\ni2p.configdir = cfg\n",
)
h1 = mock.Mock()
with mock.patch("foolscap.connections.i2p.local_i2p",
return_value=h1) as f:
i2p_provider = create_i2p_provider(None, config)
h = i2p_provider.get_i2p_handler()
self.assertEqual(f.mock_calls, [mock.call("cfg")])
self.assertIdentical(h, h1)
class Connections(unittest.TestCase):
def setUp(self):
@ -341,7 +120,11 @@ class Connections(unittest.TestCase):
self.config = config_from_string("fake.port", self.basedir, BASECONFIG)
def test_default(self):
default_connection_handlers, _ = create_connection_handlers(self.config, mock.Mock(), mock.Mock())
default_connection_handlers, _ = create_connection_handlers(
self.config,
ConstantAddresses(handler=object()),
ConstantAddresses(handler=object()),
)
self.assertEqual(default_connection_handlers["tcp"], "tcp")
self.assertEqual(default_connection_handlers["tor"], "tor")
self.assertEqual(default_connection_handlers["i2p"], "i2p")
@ -352,23 +135,39 @@ class Connections(unittest.TestCase):
"no-basedir",
BASECONFIG + "[connections]\ntcp = tor\n",
)
default_connection_handlers, _ = create_connection_handlers(config, mock.Mock(), mock.Mock())
default_connection_handlers, _ = create_connection_handlers(
config,
ConstantAddresses(handler=object()),
ConstantAddresses(handler=object()),
)
self.assertEqual(default_connection_handlers["tcp"], "tor")
self.assertEqual(default_connection_handlers["tor"], "tor")
self.assertEqual(default_connection_handlers["i2p"], "i2p")
def test_tor_unimportable(self):
with mock.patch("allmydata.util.tor_provider._import_tor",
return_value=None):
self.config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[connections]\ntcp = tor\n",
"""
If the configuration calls for substituting Tor for TCP and
``foolscap.connections.tor`` is not importable then
``create_connection_handlers`` raises ``ValueError`` with a message
explaining this makes Tor unusable.
"""
self.config = config_from_string(
"fake.port",
"no-basedir",
BASECONFIG + "[connections]\ntcp = tor\n",
)
tor_provider = create_tor_provider(
reactor,
self.config,
import_tor=lambda: None,
)
with self.assertRaises(ValueError) as ctx:
default_connection_handlers, _ = create_connection_handlers(
self.config,
i2p_provider=ConstantAddresses(handler=object()),
tor_provider=tor_provider,
)
with self.assertRaises(ValueError) as ctx:
tor_provider = create_tor_provider(reactor, self.config)
default_connection_handlers, _ = create_connection_handlers(self.config, mock.Mock(), tor_provider)
self.assertEqual(
str(ctx.exception),
"'tahoe.cfg [connections] tcp='"
@ -383,7 +182,11 @@ class Connections(unittest.TestCase):
BASECONFIG + "[connections]\ntcp = unknown\n",
)
with self.assertRaises(ValueError) as ctx:
create_connection_handlers(config, mock.Mock(), mock.Mock())
create_connection_handlers(
config,
ConstantAddresses(handler=object()),
ConstantAddresses(handler=object()),
)
self.assertIn("'tahoe.cfg [connections] tcp='", str(ctx.exception))
self.assertIn("uses unknown handler type 'unknown'", str(ctx.exception))
@ -393,7 +196,11 @@ class Connections(unittest.TestCase):
"no-basedir",
BASECONFIG + "[connections]\ntcp = disabled\n",
)
default_connection_handlers, _ = create_connection_handlers(config, mock.Mock(), mock.Mock())
default_connection_handlers, _ = create_connection_handlers(
config,
ConstantAddresses(handler=object()),
ConstantAddresses(handler=object()),
)
self.assertEqual(default_connection_handlers["tcp"], None)
self.assertEqual(default_connection_handlers["tor"], "tor")
self.assertEqual(default_connection_handlers["i2p"], "i2p")
@ -408,7 +215,11 @@ class Privacy(unittest.TestCase):
)
with self.assertRaises(PrivacyError) as ctx:
create_connection_handlers(config, mock.Mock(), mock.Mock())
create_connection_handlers(
config,
ConstantAddresses(handler=object()),
ConstantAddresses(handler=object()),
)
self.assertEqual(
str(ctx.exception),
@ -423,7 +234,11 @@ class Privacy(unittest.TestCase):
BASECONFIG + "[connections]\ntcp = disabled\n" +
"[node]\nreveal-IP-address = false\n",
)
default_connection_handlers, _ = create_connection_handlers(config, mock.Mock(), mock.Mock())
default_connection_handlers, _ = create_connection_handlers(
config,
ConstantAddresses(handler=object()),
ConstantAddresses(handler=object()),
)
self.assertEqual(default_connection_handlers["tcp"], None)
def test_tub_location_auto(self):
@ -434,7 +249,14 @@ class Privacy(unittest.TestCase):
)
with self.assertRaises(PrivacyError) as ctx:
create_main_tub(config, {}, {}, {}, mock.Mock(), mock.Mock())
create_main_tub(
config,
tub_options={},
default_connection_handlers={},
foolscap_connection_handlers={},
i2p_provider=ConstantAddresses(),
tor_provider=ConstantAddresses(),
)
self.assertEqual(
str(ctx.exception),
"tub.location uses AUTO",

View File

@ -102,9 +102,35 @@ class HashUtilTests(unittest.TestCase):
got_a = base32.b2a(got)
self.failUnlessEqual(got_a, expected_a)
def test_known_answers(self):
# assert backwards compatibility
def test_storage_index_hash_known_answers(self):
"""
Verify backwards compatibility by comparing ``storage_index_hash`` outputs
for some well-known (to us) inputs.
"""
# This is a marginal case. b"" is not a valid aes 128 key. The
# implementation does nothing to avoid producing a result for it,
# though.
self._testknown(hashutil.storage_index_hash, b"qb5igbhcc5esa6lwqorsy7e6am", b"")
# This is a little bit more realistic though clearly this is a poor key choice.
self._testknown(hashutil.storage_index_hash, b"wvggbrnrezdpa5yayrgiw5nzja", b"x" * 16)
# Here's a much more realistic key that I generated by reading some
# bytes from /dev/urandom. I computed the expected hash value twice.
# First using hashlib.sha256 and then with sha256sum(1). The input
# string given to the hash function was "43:<storage index tag>,<key>"
# in each case.
self._testknown(
hashutil.storage_index_hash,
b"aarbseqqrpsfowduchcjbonscq",
base32.a2b(b"2ckv3dfzh6rgjis6ogfqhyxnzy"),
)
def test_known_answers(self):
"""
Verify backwards compatibility by comparing hash outputs for some
well-known (to us) inputs.
"""
self._testknown(hashutil.block_hash, b"msjr5bh4evuh7fa3zw7uovixfbvlnstr5b65mrerwfnvjxig2jvq", b"")
self._testknown(hashutil.uri_extension_hash, b"wthsu45q7zewac2mnivoaa4ulh5xvbzdmsbuyztq2a5fzxdrnkka", b"")
self._testknown(hashutil.plaintext_hash, b"5lz5hwz3qj3af7n6e3arblw7xzutvnd3p3fjsngqjcb7utf3x3da", b"")

View File

@ -277,6 +277,20 @@ class Provider(unittest.TestCase):
i2p.local_i2p.assert_called_with("configdir")
self.assertIs(h, handler)
def test_handler_launch_executable(self):
i2p = mock.Mock()
handler = object()
i2p.launch = mock.Mock(return_value=handler)
reactor = object()
with mock_i2p(i2p):
p = i2p_provider.create(reactor,
FakeConfig(launch=True,
**{"i2p.executable": "myi2p"}))
h = p.get_i2p_handler()
self.assertIs(h, handler)
i2p.launch.assert_called_with(i2p_configdir=None, i2p_binary="myi2p")
def test_handler_default(self):
i2p = mock.Mock()
handler = object()

View File

@ -15,7 +15,12 @@ from six import ensure_binary, ensure_text
import os, re, itertools
from base64 import b32decode
import json
from mock import Mock, patch
from operator import (
setitem,
)
from functools import (
partial,
)
from testtools.matchers import (
Is,
@ -84,7 +89,8 @@ class Node(testutil.SignalMixin, testutil.ReallyEqualMixin, AsyncTestCase):
def test_introducer_clients_unloadable(self):
"""
Error if introducers.yaml exists but we can't read it
``create_introducer_clients`` raises ``EnvironmentError`` if
``introducers.yaml`` exists but we can't read it.
"""
basedir = u"introducer.IntroducerNode.test_introducer_clients_unloadable"
os.mkdir(basedir)
@ -94,17 +100,10 @@ class Node(testutil.SignalMixin, testutil.ReallyEqualMixin, AsyncTestCase):
f.write(u'---\n')
os.chmod(yaml_fname, 0o000)
self.addCleanup(lambda: os.chmod(yaml_fname, 0o700))
# just mocking the yaml failure, as "yamlutil.safe_load" only
# returns None on some platforms for unreadable files
with patch("allmydata.client.yamlutil") as p:
p.safe_load = Mock(return_value=None)
fake_tub = Mock()
config = read_config(basedir, "portnum")
with self.assertRaises(EnvironmentError):
create_introducer_clients(config, fake_tub)
config = read_config(basedir, "portnum")
with self.assertRaises(EnvironmentError):
create_introducer_clients(config, Tub())
@defer.inlineCallbacks
def test_furl(self):
@ -1037,23 +1036,53 @@ class Signatures(SyncTestCase):
unsign_from_foolscap, (bad_msg, sig, b"v999-key"))
def test_unsigned_announcement(self):
ed25519.verifying_key_from_string(b"pub-v0-wodst6ly4f7i7akt2nxizsmmy2rlmer6apltl56zctn67wfyu5tq")
mock_tub = Mock()
"""
An incorrectly signed announcement is not delivered to subscribers.
"""
private_key, public_key = ed25519.create_signing_keypair()
public_key_str = ed25519.string_from_verifying_key(public_key)
ic = IntroducerClient(
mock_tub,
Tub(),
"pb://",
u"fake_nick",
"0.0.0",
"1.2.3",
(0, u"i am a nonce"),
"invalid",
FilePath(self.mktemp()),
)
received = {}
ic.subscribe_to("good-stuff", partial(setitem, received))
# Deliver a good message to prove our test code is valid.
ann = {"service-name": "good-stuff", "payload": "hello"}
ann_t = sign_to_foolscap(ann, private_key)
ic.got_announcements([ann_t])
self.assertEqual(
{public_key_str[len("pub-"):]: ann},
received,
)
received.clear()
# Now deliver one without a valid signature and observe that it isn't
# delivered to the subscriber.
ann = {"service-name": "good-stuff", "payload": "bad stuff"}
(msg, sig, key) = sign_to_foolscap(ann, private_key)
# Drop a base32 word from the middle of the key to invalidate the
# signature.
sig_a = bytearray(sig)
sig_a[20:22] = []
sig = bytes(sig_a)
ann_t = (msg, sig, key)
ic.got_announcements([ann_t])
# The received announcements dict should remain empty because we
# should not receive the announcement with the invalid signature.
self.assertEqual(
{},
received,
)
self.assertEqual(0, ic._debug_counts["inbound_announcement"])
ic.got_announcements([
(b"message", b"v0-aaaaaaa", b"v0-wodst6ly4f7i7akt2nxizsmmy2rlmer6apltl56zctn67wfyu5tq")
])
# we should have rejected this announcement due to a bad signature
self.assertEqual(0, ic._debug_counts["inbound_announcement"])
# add tests of StorageFarmBroker: if it receives duplicate announcements, it

View File

@ -101,3 +101,56 @@ class Observer(unittest.TestCase):
d.addCallback(_step2)
d.addCallback(_check2)
return d
def test_observer_list_reentrant(self):
"""
``ObserverList`` is reentrant.
"""
observed = []
def observer_one():
obs.unsubscribe(observer_one)
def observer_two():
observed.append(None)
obs = observer.ObserverList()
obs.subscribe(observer_one)
obs.subscribe(observer_two)
obs.notify()
self.assertEqual([None], observed)
def test_observer_list_observer_errors(self):
"""
An error in an earlier observer does not prevent notification from being
delivered to a later observer.
"""
observed = []
def observer_one():
raise Exception("Some problem here")
def observer_two():
observed.append(None)
obs = observer.ObserverList()
obs.subscribe(observer_one)
obs.subscribe(observer_two)
obs.notify()
self.assertEqual([None], observed)
self.assertEqual(1, len(self.flushLoggedErrors(Exception)))
def test_observer_list_propagate_keyboardinterrupt(self):
"""
``KeyboardInterrupt`` escapes ``ObserverList.notify``.
"""
def observer_one():
raise KeyboardInterrupt()
obs = observer.ObserverList()
obs.subscribe(observer_one)
with self.assertRaises(KeyboardInterrupt):
obs.notify()

View File

@ -349,6 +349,10 @@ class Provider(unittest.TestCase):
cfs2.assert_called_with(reactor, ep_desc)
def test_handler_socks_endpoint(self):
"""
If not configured otherwise, the Tor provider returns a Socks-based
handler.
"""
tor = mock.Mock()
handler = object()
tor.socks_endpoint = mock.Mock(return_value=handler)
@ -365,6 +369,46 @@ class Provider(unittest.TestCase):
tor.socks_endpoint.assert_called_with(ep)
self.assertIs(h, handler)
def test_handler_socks_unix_endpoint(self):
"""
``socks.port`` can be configured as a UNIX client endpoint.
"""
tor = mock.Mock()
handler = object()
tor.socks_endpoint = mock.Mock(return_value=handler)
ep = object()
cfs = mock.Mock(return_value=ep)
reactor = object()
with mock_tor(tor):
p = tor_provider.create(reactor,
FakeConfig(**{"socks.port": "unix:path"}))
with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
h = p.get_tor_handler()
cfs.assert_called_with(reactor, "unix:path")
tor.socks_endpoint.assert_called_with(ep)
self.assertIs(h, handler)
def test_handler_socks_tcp_endpoint(self):
"""
``socks.port`` can be configured as a UNIX client endpoint.
"""
tor = mock.Mock()
handler = object()
tor.socks_endpoint = mock.Mock(return_value=handler)
ep = object()
cfs = mock.Mock(return_value=ep)
reactor = object()
with mock_tor(tor):
p = tor_provider.create(reactor,
FakeConfig(**{"socks.port": "tcp:127.0.0.1:1234"}))
with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
h = p.get_tor_handler()
cfs.assert_called_with(reactor, "tcp:127.0.0.1:1234")
tor.socks_endpoint.assert_called_with(ep)
self.assertIs(h, handler)
def test_handler_control_endpoint(self):
tor = mock.Mock()
handler = object()

View File

@ -142,7 +142,9 @@ def a2b(cs):
# Add padding back, to make Python's base64 module happy:
while (len(cs) * 5) % 8 != 0:
cs += b"="
return base64.b32decode(cs)
# Let newbytes come through and still work on Python 2, where the base64
# module gets confused by them.
return base64.b32decode(backwardscompat_bytes(cs))
__all__ = ["b2a", "a2b", "b2a_or_none", "BASE32CHAR_3bits", "BASE32CHAR_1bits", "BASE32CHAR", "BASE32STR_anybytes", "could_be_base32_encoded"]

View File

@ -16,6 +16,9 @@ if PY2:
import weakref
from twisted.internet import defer
from foolscap.api import eventually
from twisted.logger import (
Logger,
)
"""The idiom we use is for the observed object to offer a method named
'when_something', which returns a deferred. That deferred will be fired when
@ -97,7 +100,10 @@ class LazyOneShotObserverList(OneShotObserverList):
self._fire(self._get_result())
class ObserverList(object):
"""A simple class to distribute events to a number of subscribers."""
"""
Immediately distribute events to a number of subscribers.
"""
_logger = Logger()
def __init__(self):
self._watchers = []
@ -109,8 +115,11 @@ class ObserverList(object):
self._watchers.remove(observer)
def notify(self, *args, **kwargs):
for o in self._watchers:
eventually(o, *args, **kwargs)
for o in self._watchers[:]:
try:
o(*args, **kwargs)
except Exception:
self._logger.failure("While notifying {o!r}", o=o)
class EventStreamObserver(object):
"""A simple class to distribute multiple events to a single subscriber.

View File

@ -17,23 +17,7 @@ from ..interfaces import (
IAddressFamily,
)
def create(reactor, config):
"""
Create a new _Provider service (this is an IService so must be
hooked up to a parent or otherwise started).
If foolscap.connections.tor or txtorcon are not installed, then
Provider.get_tor_handler() will return None. If tahoe.cfg wants
to start an onion service too, then this `create()` method will
throw a nice error (and startService will throw an ugly error).
"""
provider = _Provider(config, reactor)
provider.check_onion_config()
return provider
def _import_tor():
# this exists to be overridden by unit tests
try:
from foolscap.connections import tor
return tor
@ -47,6 +31,25 @@ def _import_txtorcon():
except ImportError: # pragma: no cover
return None
def create(reactor, config, import_tor=None, import_txtorcon=None):
"""
Create a new _Provider service (this is an IService so must be
hooked up to a parent or otherwise started).
If foolscap.connections.tor or txtorcon are not installed, then
Provider.get_tor_handler() will return None. If tahoe.cfg wants
to start an onion service too, then this `create()` method will
throw a nice error (and startService will throw an ugly error).
"""
if import_tor is None:
import_tor = _import_tor
if import_txtorcon is None:
import_txtorcon = _import_txtorcon
provider = _Provider(config, reactor, import_tor(), import_txtorcon())
provider.check_onion_config()
return provider
def data_directory(private_dir):
return os.path.join(private_dir, "tor-statedir")
@ -217,14 +220,14 @@ def create_config(reactor, cli_config):
@implementer(IAddressFamily)
class _Provider(service.MultiService):
def __init__(self, config, reactor):
def __init__(self, config, reactor, tor, txtorcon):
service.MultiService.__init__(self)
self._config = config
self._tor_launched = None
self._onion_ehs = None
self._onion_tor_control_proto = None
self._tor = _import_tor()
self._txtorcon = _import_txtorcon()
self._tor = tor
self._txtorcon = txtorcon
self._reactor = reactor
def _get_tor_config(self, *args, **kwargs):