class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError, e: self.assertTrue('No code set yet' in str(e))
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError, e: self.assertTrue("No code set yet" in str(e))
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): assert type(line) == bytes self.protocol.dataReceived(line.strip() + b"\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError as e: self.assertTrue('No code set yet' in str(e)) def test_statemachine_broadcast_unknown_code(self): try: self.protocol.code = 999 self.protocol._broadcast_response("foo") self.fail() except RuntimeError as e: self.assertTrue('Unknown code' in str(e)) def test_statemachine_is_finish(self): self.assertTrue(not self.protocol._is_finish_line('')) self.assertTrue(self.protocol._is_finish_line('.')) self.assertTrue(self.protocol._is_finish_line('300 ')) self.assertTrue(not self.protocol._is_finish_line('250-')) def test_statemachine_singleline(self): self.assertTrue(not self.protocol._is_single_line_response('foo')) def test_statemachine_continuation(self): try: self.protocol.code = 250 self.protocol._is_continuation_line("123 ") self.fail() except RuntimeError as e: self.assertTrue('Unexpected code' in str(e)) def test_statemachine_multiline(self): try: self.protocol.code = 250 self.protocol._is_multi_line("123 ") self.fail() except RuntimeError as e: self.assertTrue('Unexpected code' in str(e)) def test_response_with_no_request(self): with self.assertRaises(RuntimeError) as ctx: self.protocol.code = 200 self.protocol._broadcast_response('200 OK') self.assertTrue("didn't issue a command" in str(ctx.exception)) def auth_failed(self, msg): self.assertEqual(str(msg.value), '551 go away') self.got_auth_failed = True def test_authenticate_fail(self): self.got_auth_failed = False self.protocol._auth_failed = self.auth_failed self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=HASHEDPASSWORD VERSION Tor="0.2.2.35" OK''') self.send(b'551 go away\r\n') self.assertTrue(self.got_auth_failed) def test_authenticate_no_auth_line(self): try: self.protocol._do_authenticate('''PROTOCOLINFO 1 FOOAUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/dev/null" VERSION Tor="0.2.2.35" OK''') self.assertTrue(False) except RuntimeError as e: self.assertTrue('find AUTH line' in str(e)) def test_authenticate_not_enough_cookie_data(self): with tempfile.NamedTemporaryFile() as cookietmp: cookietmp.write(b'x' * 35) # too much data cookietmp.flush() try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % cookietmp.name) self.assertTrue(False) except RuntimeError as e: self.assertTrue('cookie to be 32' in str(e)) def test_authenticate_not_enough_safecookie_data(self): with tempfile.NamedTemporaryFile() as cookietmp: cookietmp.write(b'x' * 35) # too much data cookietmp.flush() try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % cookietmp.name) self.assertTrue(False) except RuntimeError as e: self.assertTrue('cookie to be 32' in str(e)) def test_authenticate_safecookie(self): with tempfile.NamedTemporaryFile() as cookietmp: cookiedata = bytes(bytearray([0] * 32)) cookietmp.write(cookiedata) cookietmp.flush() self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(cookietmp.name)) self.assertTrue( b'AUTHCHALLENGE SAFECOOKIE ' in self.transport.value()) x = self.transport.value().split()[-1] client_nonce = a2b_hex(x) self.transport.clear() server_nonce = bytes(bytearray([0] * 32)) server_hash = hmac_sha256( b"Tor safe cookie authentication server-to-controller hash", cookiedata + client_nonce + server_nonce, ) self.send(b'250 AUTHCHALLENGE SERVERHASH=' + base64.b16encode(server_hash) + b' SERVERNONCE=' + base64.b16encode(server_nonce) + b'\r\n') self.assertTrue(b'AUTHENTICATE ' in self.transport.value()) def test_authenticate_cookie_without_reading(self): server_nonce = bytes(bytearray([0] * 32)) server_hash = bytes(bytearray([0] * 32)) try: self.protocol._safecookie_authchallenge( '250 AUTHCHALLENGE SERVERHASH=%s SERVERNONCE=%s' % (base64.b16encode(server_hash), base64.b16encode(server_nonce))) self.assertTrue(False) except RuntimeError as e: self.assertTrue('not read' in str(e)) def test_authenticate_unexisting_cookie_file(self): unexisting_file = __file__ + "-unexisting" try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % unexisting_file) self.assertTrue(False) except RuntimeError: pass def test_authenticate_unexisting_safecookie_file(self): unexisting_file = __file__ + "-unexisting" try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertTrue(False) except RuntimeError: pass def test_authenticate_dont_send_cookiefile(self): try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE VERSION Tor="0.2.2.35" OK''') self.assertTrue(False) except RuntimeError: pass def test_authenticate_password_when_cookie_unavailable(self): unexisting_file = __file__ + "-unexisting" self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n', ) def test_authenticate_password_when_safecookie_unavailable(self): unexisting_file = __file__ + "-unexisting" self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE,HASHEDPASSWORD COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n', ) def test_authenticate_safecookie_wrong_hash(self): cookiedata = bytes(bytearray([0] * 32)) server_nonce = bytes(bytearray([0] * 32)) server_hash = bytes(bytearray([0] * 32)) # pretend we already did PROTOCOLINFO and read the cookie # file self.protocol._cookie_data = cookiedata self.protocol.client_nonce = server_nonce # all 0's anyway try: self.protocol._safecookie_authchallenge( '250 AUTHCHALLENGE SERVERHASH={} SERVERNONCE={}'.format( b2a_hex(server_hash).decode('ascii'), b2a_hex(server_nonce).decode('ascii'), )) self.assertTrue(False) except RuntimeError as e: self.assertTrue('hash not expected' in str(e)) def confirm_version_events(self, arg): self.assertEqual(self.protocol.version, 'foo') events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'.split( ) self.assertEqual(len(self.protocol.valid_events), len(events)) self.assertTrue(all(x in self.protocol.valid_events for x in events)) def test_bootstrap_callback(self): d = self.protocol.post_bootstrap d.addCallback(CallbackChecker(self.protocol)) d.addCallback(self.confirm_version_events) events = b'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._bootstrap() # answer all the requests generated by boostrapping etc. self.send(b"250-signal/names=") self.send(b"250 OK") self.send(b"250-version=foo") self.send(b"250 OK") self.send(b"250-events/names=" + events) self.send(b"250 OK") self.send(b"250 OK") # for USEFEATURE return d def test_bootstrap_tor_does_not_support_signal_names(self): self.protocol._bootstrap() self.send(b'552 Unrecognized key "signal/names"') valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"] self.assertEqual(self.protocol.valid_signals, valid_signals) def test_async(self): """ test the example from control-spec.txt to see that we handle interleaved async notifications properly. """ self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener('CIRC', lambda _: None) self.send(b"250 OK") d = self.protocol.get_conf("SOCKSPORT ORPORT") self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") self.send(b"250-SOCKSPORT=9050") self.send(b"250 ORPORT=0") return d def test_async_multiline(self): # same as above, but i think the 650's can be multline, # too. Like: # 650-CIRC 1000 EXTENDED moria1,moria2 0xBEEF # 650-EXTRAMAGIC=99 # 650 ANONYMITY=high self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener( 'CIRC', CallbackChecker( "1000 EXTENDED moria1,moria2\nEXTRAMAGIC=99\nANONYMITY=high")) self.send(b"250 OK") d = self.protocol.get_conf("SOCKSPORT ORPORT") d.addCallback(CallbackChecker({"ORPORT": "0", "SOCKSPORT": "9050"})) self.send(b"650-CIRC 1000 EXTENDED moria1,moria2") self.send(b"650-EXTRAMAGIC=99") self.send(b"650 ANONYMITY=high") self.send(b"250-SOCKSPORT=9050") self.send(b"250 ORPORT=0") return d def test_multiline_plus(self): """ """ d = self.protocol.get_info("FOO") d.addCallback(CallbackChecker({"FOO": "\na\nb\nc"})) self.send(b"250+FOO=") self.send(b"a") self.send(b"b") self.send(b"c") self.send(b".") self.send(b"250 OK") return d def test_multiline_plus_embedded_equals(self): """ """ d = self.protocol.get_info("FOO") d.addCallback(CallbackChecker({"FOO": "\na="})) self.send(b"250+FOO=") self.send(b"a=") self.send(b".") self.send(b"250 OK") return d def incremental_check(self, expected, actual): if '=' in actual: return self.assertEqual(expected, actual) def test_getinfo_incremental(self): d = self.protocol.get_info_incremental( "FOO", functools.partial(self.incremental_check, "bar")) self.send(b"250+FOO=") self.send(b"bar") self.send(b"bar") self.send(b".") self.send(b"250 OK") return d def test_getinfo_incremental_continuation(self): d = self.protocol.get_info_incremental( "FOO", functools.partial(self.incremental_check, "bar")) self.send(b"250-FOO=") self.send(b"250-bar") self.send(b"250-bar") self.send(b"250 OK") return d def test_getinfo_one_line(self): d = self.protocol.get_info("foo", ) self.send(b'250 foo=bar') d.addCallback( lambda _: functools.partial(self.incremental_check, "bar")) return d def test_getconf(self): d = self.protocol.get_conf("SOCKSPORT ORPORT") d.addCallback(CallbackChecker({'SocksPort': '9050', 'ORPort': '0'})) self.send(b"250-SocksPort=9050") self.send(b"250 ORPort=0") return d def test_getconf_raw(self): d = self.protocol.get_conf_raw("SOCKSPORT ORPORT") d.addCallback(CallbackChecker('SocksPort=9050\nORPort=0')) self.send(b"250-SocksPort=9050") self.send(b"250 ORPort=0") return d def test_getconf_single(self): d = self.protocol.get_conf_single("SOCKSPORT") d.addCallback(CallbackChecker('9050')) self.send(b"250 SocksPort=9050") return d def response_ok(self, v): self.assertEqual(v, '') def test_setconf(self): d = self.protocol.set_conf("foo", "bar").addCallback( functools.partial(self.response_ok)) self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value(), b"SETCONF foo=bar\r\n") def test_setconf_with_space(self): d = self.protocol.set_conf("foo", "a value with a space") d.addCallback(functools.partial(self.response_ok)) self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value(), b'SETCONF foo="a value with a space"\r\n') def test_setconf_multi(self): d = self.protocol.set_conf("foo", "bar", "baz", 1) self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b"SETCONF foo=bar baz=1\r\n", ) def test_quit(self): d = self.protocol.quit() self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b"QUIT\r\n", ) def test_dot(self): # just checking we don't expode self.protocol.graphviz_data() def test_debug(self): self.protocol.start_debug() self.assertTrue(exists('txtorcon-debug.log')) def error(self, failure): print("ERROR", failure) self.assertTrue(False) def test_twocommands(self): "Two commands on the wire before first response." d1 = self.protocol.get_conf("FOO") ht = {"a": "one", "b": "two"} d1.addCallback(CallbackChecker(ht)).addErrback(log.err) d2 = self.protocol.get_info_raw("BAR") d2.addCallback(CallbackChecker("bar")).addErrback(log.err) self.send(b"250-a=one") self.send(b"250-b=two") self.send(b"250 OK") self.send(b"250 bar") return d2 def test_signal_error(self): try: self.protocol.signal('FOO') self.fail() except Exception as e: self.assertTrue('Invalid signal' in str(e)) def test_signal(self): self.protocol.valid_signals = ['NEWNYM'] self.protocol.signal('NEWNYM') self.assertEqual( self.transport.value(), b'SIGNAL NEWNYM\r\n', ) def test_650_after_authenticate(self): self.protocol._set_valid_events('CONF_CHANGED') self.protocol.add_event_listener('CONF_CHANGED', CallbackChecker("Foo=bar")) self.send(b"250 OK") self.send(b"650-CONF_CHANGED") self.send(b"650-Foo=bar") def test_notify_after_getinfo(self): self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener( 'CIRC', CallbackChecker("1000 EXTENDED moria1,moria2")) self.send(b"250 OK") d = self.protocol.get_info("a") d.addCallback(CallbackChecker({'a': 'one'})).addErrback(self.fail) self.send(b"250-a=one") self.send(b"250 OK") self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") return d def test_notify_error(self): self.protocol._set_valid_events('CIRC') self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") def test_getinfo(self): d = self.protocol.get_info("version") d.addCallback(CallbackChecker({'version': '0.2.2.34'})) d.addErrback(self.fail) self.send(b"250-version=0.2.2.34") self.send(b"250 OK") self.assertEqual( self.transport.value(), b"GETINFO version\r\n", ) return d def test_getinfo_single(self): d = self.protocol.get_info_single("version") d.addCallback(CallbackChecker('0.2.2.34')) d.addErrback(self.fail) self.send(b"250-version=0.2.2.34") self.send(b"250 OK") self.assertEqual( self.transport.value(), b"GETINFO version\r\n", ) return d def test_getinfo_for_descriptor(self): descriptor_info = b"""250+desc/name/moria1= router moria1 128.31.0.34 9101 0 9131 platform Tor 0.2.5.0-alpha-dev on Linux protocols Link 1 2 Circuit 1 published 2013-07-05 23:48:52 fingerprint 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31 uptime 1818933 bandwidth 512000 62914560 1307929 extra-info-digest 17D0142F6EBCDF60160EB1794FA6C9717D581F8C caches-extra-info onion-key -----BEGIN RSA PUBLIC KEY----- MIGJAoGBALzd4bhz1usB7wpoaAvP+BBOnNIk7mByAKV6zvyQ0p1M09oEmxPMc3qD AAm276oJNf0eq6KWC6YprzPWFsXEIdXSqA6RWXCII1JG/jOoy6nt478BkB8TS9I9 1MJW27ppRaqnLiTmBmM+qzrsgJGwf+onAgUKKH2GxlVgahqz8x6xAgMBAAE= -----END RSA PUBLIC KEY----- signing-key -----BEGIN RSA PUBLIC KEY----- MIGJAoGBALtJ9uD7cD7iHjqNA3AgsX9prES5QN+yFQyr2uOkxzhvunnaf6SNhzWW bkfylnMrRm/qCz/czcjZO6N6EKHcXmypehvP566B7gAQ9vDsb+l7VZVWgXvzNc2s tl3P7qpC08rgyJh1GqmtQTCesIDqkEyWxwToympCt09ZQRq+fIttAgMBAAE= -----END RSA PUBLIC KEY----- hidden-service-dir contact 1024D/28988BF5 arma mit edu ntor-onion-key 9ZVjNkf/iLEnD685SpC5kcDytQ7u5ViiI9JOftdbE0k= reject *:* router-signature -----BEGIN SIGNATURE----- Y8Tj2e7mPbFJbguulkPEBVYzyO57p4btpWEXvRMD6vxIh/eyn25pehg5dUVBtZlL iO3EUE0AEYah2W9gdz8t+i3Dtr0zgqLS841GC/TyDKCm+MKmN8d098qnwK0NGF9q 01NZPuSqXM1b6hnl2espFzL7XL8XEGRU+aeg+f/ukw4= -----END SIGNATURE----- . 250 OK""" d = self.protocol.get_info("desc/name/moria1") d.addCallback( CallbackChecker({ 'desc/name/moria1': '\n' + '\n'.join(descriptor_info.decode('ascii').split('\n')[1:-2]) })) d.addErrback(self.fail) for line in descriptor_info.split(b'\n'): self.send(line) return d def test_getinfo_multiline(self): descriptor_info = b"""250+desc/name/moria1= router moria1 128.31.0.34 9101 0 9131 platform Tor 0.2.5.0-alpha-dev on Linux . 250 OK""" d = self.protocol.get_info("desc/name/moria1") gold = "\nrouter moria1 128.31.0.34 9101 0 9131\nplatform Tor 0.2.5.0-alpha-dev on Linux" d.addCallback(CallbackChecker({'desc/name/moria1': gold})) d.addErrback(self.fail) for line in descriptor_info.split(b'\n'): self.send(line) return d def test_addevent(self): self.protocol._set_valid_events('FOO BAR') self.protocol.add_event_listener('FOO', lambda _: None) # is it dangerous/ill-advised to depend on internal state of # class under test? d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value().split(b'\r\n')[-2], b"SETEVENTS FOO") self.transport.clear() self.protocol.add_event_listener('BAR', lambda _: None) d = self.protocol.defer self.send(b"250 OK") self.assertTrue(self.transport.value() == b"SETEVENTS FOO BAR\r\n" or self.transport.value() == b"SETEVENTS BAR FOO\r\n") self._wait(d) try: self.protocol.add_event_listener('SOMETHING_INVALID', lambda _: None) self.assertTrue(False) except Exception: pass def test_eventlistener(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener = EventListener() self.protocol.add_event_listener('STREAM', listener) d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC") self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC") self.assertEqual(listener.stream_events, 2) def test_eventlistener_error(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 do_error = False def __call__(self, data): self.stream_events += 1 if self.do_error: raise Exception("the bad thing happened") # we make sure the first listener has the errors to prove the # second one still gets called. listener0 = EventListener() listener0.do_error = True listener1 = EventListener() self.protocol.add_event_listener('STREAM', listener0) self.protocol.add_event_listener('STREAM', listener1) d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC") self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC") self.assertEqual(listener0.stream_events, 2) self.assertEqual(listener1.stream_events, 2) # should have logged the two errors logged = self.flushLoggedErrors() self.assertEqual(2, len(logged)) self.assertTrue("the bad thing happened" in str(logged[0])) self.assertTrue("the bad thing happened" in str(logged[1])) def test_remove_eventlistener(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener = EventListener() self.protocol.add_event_listener('STREAM', listener) self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n') self.protocol.lineReceived(b"250 OK") self.transport.clear() self.protocol.remove_event_listener('STREAM', listener) self.assertEqual(self.transport.value(), b'SETEVENTS \r\n') def test_remove_eventlistener_multiple(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener0 = EventListener() listener1 = EventListener() self.protocol.add_event_listener('STREAM', listener0) self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n') self.protocol.lineReceived(b"250 OK") self.transport.clear() # add another one, shouldn't issue a tor command self.protocol.add_event_listener('STREAM', listener1) self.assertEqual(self.transport.value(), b'') # remove one, should still not issue a tor command self.protocol.remove_event_listener('STREAM', listener0) self.assertEqual(self.transport.value(), b'') # remove the other one, NOW should issue a command self.protocol.remove_event_listener('STREAM', listener1) self.assertEqual(self.transport.value(), b'SETEVENTS \r\n') # try removing invalid event try: self.protocol.remove_event_listener('FOO', listener0) self.fail() except Exception as e: self.assertTrue('FOO' in str(e)) def test_continuation_line(self): d = self.protocol.get_info_raw("key") def check_continuation(v): self.assertEqual(v, "key=\nvalue0\nvalue1") d.addCallback(check_continuation) self.send(b"250+key=") self.send(b"value0") self.send(b"value1") self.send(b".") self.send(b"250 OK") return d def test_newdesc(self): """ FIXME: this test is now maybe a little silly, it's just testing multiline GETINFO... (Real test is in TorStateTests.test_newdesc_parse) """ self.protocol.get_info_raw( 'ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A') d = self.protocol.defer d.addCallback( CallbackChecker("""ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888""" )) self.send(b"250+ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=") self.send( b"r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80" ) self.send(b"s Exit Fast Guard HSDir Named Running Stable V2Dir Valid") self.send(b"w Bandwidth=518000") self.send( b"p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888" ) self.send(b".") self.send(b"250 OK") return d def test_plus_line_no_command(self): self.protocol.lineReceived(b"650+NS\r\n") self.protocol.lineReceived( b"r Gabor gFpAHsFOHGATy12ZUswRf0ZrqAU GG6GDp40cQfR3ODvkBT0r+Q09kw 2012-05-12 16:54:56 91.219.238.71 443 80\r\n" ) def test_minus_line_no_command(self): """ haven't seen 600's use - "in the wild" but don't see why it's not possible """ self.protocol._set_valid_events('NS') self.protocol.add_event_listener('NS', lambda _: None) self.protocol.lineReceived(b"650-NS\r\n") self.protocol.lineReceived(b"650 OK\r\n")
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): assert type(line) == bytes self.protocol.dataReceived(line.strip() + b"\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError as e: self.assertTrue('No code set yet' in str(e)) def test_statemachine_broadcast_unknown_code(self): try: self.protocol.code = 999 self.protocol._broadcast_response("foo") self.fail() except RuntimeError as e: self.assertTrue('Unknown code' in str(e)) def test_statemachine_is_finish(self): self.assertTrue(not self.protocol._is_finish_line('')) self.assertTrue(self.protocol._is_finish_line('.')) self.assertTrue(self.protocol._is_finish_line('300 ')) self.assertTrue(not self.protocol._is_finish_line('250-')) def test_statemachine_singleline(self): self.assertTrue(not self.protocol._is_single_line_response('foo')) def test_statemachine_continuation(self): try: self.protocol.code = 250 self.protocol._is_continuation_line("123 ") self.fail() except RuntimeError as e: self.assertTrue('Unexpected code' in str(e)) def test_statemachine_multiline(self): try: self.protocol.code = 250 self.protocol._is_multi_line("123 ") self.fail() except RuntimeError as e: self.assertTrue('Unexpected code' in str(e)) def test_response_with_no_request(self): with self.assertRaises(RuntimeError) as ctx: self.protocol.code = 200 self.protocol._broadcast_response('200 OK') self.assertTrue( "didn't issue a command" in str(ctx.exception) ) def auth_failed(self, msg): self.assertEqual(str(msg.value), '551 go away') self.got_auth_failed = True def test_authenticate_fail(self): self.got_auth_failed = False self.protocol._auth_failed = self.auth_failed self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=HASHEDPASSWORD VERSION Tor="0.2.2.35" OK''') self.send(b'551 go away\r\n') self.assertTrue(self.got_auth_failed) def test_authenticate_no_auth_line(self): try: self.protocol._do_authenticate('''PROTOCOLINFO 1 FOOAUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/dev/null" VERSION Tor="0.2.2.35" OK''') self.assertTrue(False) except RuntimeError as e: self.assertTrue('find AUTH line' in str(e)) def test_authenticate_not_enough_cookie_data(self): with tempfile.NamedTemporaryFile() as cookietmp: cookietmp.write(b'x' * 35) # too much data cookietmp.flush() try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % cookietmp.name) self.assertTrue(False) except RuntimeError as e: self.assertTrue('cookie to be 32' in str(e)) def test_authenticate_not_enough_safecookie_data(self): with tempfile.NamedTemporaryFile() as cookietmp: cookietmp.write(b'x' * 35) # too much data cookietmp.flush() try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % cookietmp.name) self.assertTrue(False) except RuntimeError as e: self.assertTrue('cookie to be 32' in str(e)) def test_authenticate_safecookie(self): with tempfile.NamedTemporaryFile() as cookietmp: cookiedata = bytes(bytearray([0] * 32)) cookietmp.write(cookiedata) cookietmp.flush() self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(cookietmp.name)) self.assertTrue( b'AUTHCHALLENGE SAFECOOKIE ' in self.transport.value() ) x = self.transport.value().split()[-1] client_nonce = a2b_hex(x) self.transport.clear() server_nonce = bytes(bytearray([0] * 32)) server_hash = hmac_sha256( b"Tor safe cookie authentication server-to-controller hash", cookiedata + client_nonce + server_nonce, ) self.send( b'250 AUTHCHALLENGE SERVERHASH=' + base64.b16encode(server_hash) + b' SERVERNONCE=' + base64.b16encode(server_nonce) + b'\r\n' ) self.assertTrue(b'AUTHENTICATE ' in self.transport.value()) def test_authenticate_cookie_without_reading(self): server_nonce = bytes(bytearray([0] * 32)) server_hash = bytes(bytearray([0] * 32)) try: self.protocol._safecookie_authchallenge( '250 AUTHCHALLENGE SERVERHASH=%s SERVERNONCE=%s' % (base64.b16encode(server_hash), base64.b16encode(server_nonce)) ) self.assertTrue(False) except RuntimeError as e: self.assertTrue('not read' in str(e)) def test_authenticate_unexisting_cookie_file(self): unexisting_file = __file__ + "-unexisting" try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE COOKIEFILE="%s" VERSION Tor="0.2.2.35" OK''' % unexisting_file) self.assertTrue(False) except RuntimeError: pass def test_authenticate_unexisting_safecookie_file(self): unexisting_file = __file__ + "-unexisting" try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertTrue(False) except RuntimeError: pass def test_authenticate_dont_send_cookiefile(self): try: self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE VERSION Tor="0.2.2.35" OK''') self.assertTrue(False) except RuntimeError: pass def test_authenticate_password_when_cookie_unavailable(self): unexisting_file = __file__ + "-unexisting" self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n', ) def test_authenticate_password_when_safecookie_unavailable(self): unexisting_file = __file__ + "-unexisting" self.protocol.password_function = lambda: 'foo' self.protocol._do_authenticate('''PROTOCOLINFO 1 AUTH METHODS=SAFECOOKIE,HASHEDPASSWORD COOKIEFILE="{}" VERSION Tor="0.2.2.35" OK'''.format(unexisting_file)) self.assertEqual( self.transport.value(), b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n', ) def test_authenticate_safecookie_wrong_hash(self): cookiedata = bytes(bytearray([0] * 32)) server_nonce = bytes(bytearray([0] * 32)) server_hash = bytes(bytearray([0] * 32)) # pretend we already did PROTOCOLINFO and read the cookie # file self.protocol._cookie_data = cookiedata self.protocol.client_nonce = server_nonce # all 0's anyway try: self.protocol._safecookie_authchallenge( '250 AUTHCHALLENGE SERVERHASH={} SERVERNONCE={}'.format( b2a_hex(server_hash).decode('ascii'), b2a_hex(server_nonce).decode('ascii'), ) ) self.assertTrue(False) except RuntimeError as e: self.assertTrue('hash not expected' in str(e)) def confirm_version_events(self, arg): self.assertEqual(self.protocol.version, 'foo') events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'.split() self.assertEqual(len(self.protocol.valid_events), len(events)) self.assertTrue(all(x in self.protocol.valid_events for x in events)) def test_bootstrap_callback(self): d = self.protocol.post_bootstrap d.addCallback(CallbackChecker(self.protocol)) d.addCallback(self.confirm_version_events) events = b'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._bootstrap() # answer all the requests generated by boostrapping etc. self.send(b"250-signal/names=") self.send(b"250 OK") self.send(b"250-version=foo") self.send(b"250 OK") self.send(b"250-events/names=" + events) self.send(b"250 OK") self.send(b"250 OK") # for USEFEATURE return d def test_bootstrap_tor_does_not_support_signal_names(self): self.protocol._bootstrap() self.send(b'552 Unrecognized key "signal/names"') valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"] self.assertEqual(self.protocol.valid_signals, valid_signals) def test_async(self): """ test the example from control-spec.txt to see that we handle interleaved async notifications properly. """ self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener('CIRC', lambda _: None) self.send(b"250 OK") d = self.protocol.get_conf("SOCKSPORT ORPORT") self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") self.send(b"250-SOCKSPORT=9050") self.send(b"250 ORPORT=0") return d def test_async_multiline(self): # same as above, but i think the 650's can be multline, # too. Like: # 650-CIRC 1000 EXTENDED moria1,moria2 0xBEEF # 650-EXTRAMAGIC=99 # 650 ANONYMITY=high self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener( 'CIRC', CallbackChecker( "1000 EXTENDED moria1,moria2\nEXTRAMAGIC=99\nANONYMITY=high" ) ) self.send(b"250 OK") d = self.protocol.get_conf("SOCKSPORT ORPORT") d.addCallback(CallbackChecker({"ORPORT": "0", "SOCKSPORT": "9050"})) self.send(b"650-CIRC 1000 EXTENDED moria1,moria2") self.send(b"650-EXTRAMAGIC=99") self.send(b"650 ANONYMITY=high") self.send(b"250-SOCKSPORT=9050") self.send(b"250 ORPORT=0") return d def test_multiline_plus(self): """ """ d = self.protocol.get_info("FOO") d.addCallback(CallbackChecker({"FOO": "\na\nb\nc"})) self.send(b"250+FOO=") self.send(b"a") self.send(b"b") self.send(b"c") self.send(b".") self.send(b"250 OK") return d def test_multiline_plus_embedded_equals(self): """ """ d = self.protocol.get_info("FOO") d.addCallback(CallbackChecker({"FOO": "\na="})) self.send(b"250+FOO=") self.send(b"a=") self.send(b".") self.send(b"250 OK") return d def incremental_check(self, expected, actual): if '=' in actual: return self.assertEqual(expected, actual) def test_getinfo_incremental(self): d = self.protocol.get_info_incremental( "FOO", functools.partial(self.incremental_check, "bar") ) self.send(b"250+FOO=") self.send(b"bar") self.send(b"bar") self.send(b".") self.send(b"250 OK") return d def test_getinfo_incremental_continuation(self): d = self.protocol.get_info_incremental( "FOO", functools.partial(self.incremental_check, "bar") ) self.send(b"250-FOO=") self.send(b"250-bar") self.send(b"250-bar") self.send(b"250 OK") return d def test_getinfo_one_line(self): d = self.protocol.get_info( "foo", ) self.send(b'250 foo=bar') d.addCallback(lambda _: functools.partial(self.incremental_check, "bar")) return d def test_getconf(self): d = self.protocol.get_conf("SOCKSPORT ORPORT") d.addCallback(CallbackChecker({'SocksPort': '9050', 'ORPort': '0'})) self.send(b"250-SocksPort=9050") self.send(b"250 ORPort=0") return d def test_getconf_raw(self): d = self.protocol.get_conf_raw("SOCKSPORT ORPORT") d.addCallback(CallbackChecker('SocksPort=9050\nORPort=0')) self.send(b"250-SocksPort=9050") self.send(b"250 ORPort=0") return d def test_getconf_single(self): d = self.protocol.get_conf_single("SOCKSPORT") d.addCallback(CallbackChecker('9050')) self.send(b"250 SocksPort=9050") return d def response_ok(self, v): self.assertEqual(v, '') def test_setconf(self): d = self.protocol.set_conf("foo", "bar").addCallback( functools.partial(self.response_ok) ) self.send(b"250 OK") self._wait(d) self.assertEqual(self.transport.value(), b"SETCONF foo=bar\r\n") def test_setconf_with_space(self): d = self.protocol.set_conf("foo", "a value with a space") d.addCallback(functools.partial(self.response_ok)) self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b'SETCONF foo="a value with a space"\r\n' ) def test_setconf_multi(self): d = self.protocol.set_conf("foo", "bar", "baz", 1) self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b"SETCONF foo=bar baz=1\r\n", ) def test_quit(self): d = self.protocol.quit() self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value(), b"QUIT\r\n", ) def test_dot(self): # just checking we don't expode self.protocol.graphviz_data() def test_debug(self): self.protocol.start_debug() self.assertTrue(exists('txtorcon-debug.log')) def error(self, failure): print("ERROR", failure) self.assertTrue(False) def test_twocommands(self): "Two commands on the wire before first response." d1 = self.protocol.get_conf("FOO") ht = {"a": "one", "b": "two"} d1.addCallback(CallbackChecker(ht)).addErrback(log.err) d2 = self.protocol.get_info_raw("BAR") d2.addCallback(CallbackChecker("bar")).addErrback(log.err) self.send(b"250-a=one") self.send(b"250-b=two") self.send(b"250 OK") self.send(b"250 bar") return d2 def test_signal_error(self): try: self.protocol.signal('FOO') self.fail() except Exception as e: self.assertTrue('Invalid signal' in str(e)) def test_signal(self): self.protocol.valid_signals = ['NEWNYM'] self.protocol.signal('NEWNYM') self.assertEqual( self.transport.value(), b'SIGNAL NEWNYM\r\n', ) def test_650_after_authenticate(self): self.protocol._set_valid_events('CONF_CHANGED') self.protocol.add_event_listener( 'CONF_CHANGED', CallbackChecker("Foo=bar") ) self.send(b"250 OK") self.send(b"650-CONF_CHANGED") self.send(b"650-Foo=bar") def test_notify_after_getinfo(self): self.protocol._set_valid_events('CIRC') self.protocol.add_event_listener( 'CIRC', CallbackChecker("1000 EXTENDED moria1,moria2") ) self.send(b"250 OK") d = self.protocol.get_info("a") d.addCallback(CallbackChecker({'a': 'one'})).addErrback(self.fail) self.send(b"250-a=one") self.send(b"250 OK") self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") return d def test_notify_error(self): self.protocol._set_valid_events('CIRC') self.send(b"650 CIRC 1000 EXTENDED moria1,moria2") def test_getinfo(self): d = self.protocol.get_info("version") d.addCallback(CallbackChecker({'version': '0.2.2.34'})) d.addErrback(self.fail) self.send(b"250-version=0.2.2.34") self.send(b"250 OK") self.assertEqual( self.transport.value(), b"GETINFO version\r\n", ) return d def test_getinfo_single(self): d = self.protocol.get_info_single("version") d.addCallback(CallbackChecker('0.2.2.34')) d.addErrback(self.fail) self.send(b"250-version=0.2.2.34") self.send(b"250 OK") self.assertEqual( self.transport.value(), b"GETINFO version\r\n", ) return d def test_getinfo_for_descriptor(self): descriptor_info = b"""250+desc/name/moria1= router moria1 128.31.0.34 9101 0 9131 platform Tor 0.2.5.0-alpha-dev on Linux protocols Link 1 2 Circuit 1 published 2013-07-05 23:48:52 fingerprint 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31 uptime 1818933 bandwidth 512000 62914560 1307929 extra-info-digest 17D0142F6EBCDF60160EB1794FA6C9717D581F8C caches-extra-info onion-key -----BEGIN RSA PUBLIC KEY----- MIGJAoGBALzd4bhz1usB7wpoaAvP+BBOnNIk7mByAKV6zvyQ0p1M09oEmxPMc3qD AAm276oJNf0eq6KWC6YprzPWFsXEIdXSqA6RWXCII1JG/jOoy6nt478BkB8TS9I9 1MJW27ppRaqnLiTmBmM+qzrsgJGwf+onAgUKKH2GxlVgahqz8x6xAgMBAAE= -----END RSA PUBLIC KEY----- signing-key -----BEGIN RSA PUBLIC KEY----- MIGJAoGBALtJ9uD7cD7iHjqNA3AgsX9prES5QN+yFQyr2uOkxzhvunnaf6SNhzWW bkfylnMrRm/qCz/czcjZO6N6EKHcXmypehvP566B7gAQ9vDsb+l7VZVWgXvzNc2s tl3P7qpC08rgyJh1GqmtQTCesIDqkEyWxwToympCt09ZQRq+fIttAgMBAAE= -----END RSA PUBLIC KEY----- hidden-service-dir contact 1024D/28988BF5 arma mit edu ntor-onion-key 9ZVjNkf/iLEnD685SpC5kcDytQ7u5ViiI9JOftdbE0k= reject *:* router-signature -----BEGIN SIGNATURE----- Y8Tj2e7mPbFJbguulkPEBVYzyO57p4btpWEXvRMD6vxIh/eyn25pehg5dUVBtZlL iO3EUE0AEYah2W9gdz8t+i3Dtr0zgqLS841GC/TyDKCm+MKmN8d098qnwK0NGF9q 01NZPuSqXM1b6hnl2espFzL7XL8XEGRU+aeg+f/ukw4= -----END SIGNATURE----- . 250 OK""" d = self.protocol.get_info("desc/name/moria1") d.addCallback(CallbackChecker({'desc/name/moria1': '\n' + '\n'.join(descriptor_info.decode('ascii').split('\n')[1:-2])})) d.addErrback(self.fail) for line in descriptor_info.split(b'\n'): self.send(line) return d def test_getinfo_multiline(self): descriptor_info = b"""250+desc/name/moria1= router moria1 128.31.0.34 9101 0 9131 platform Tor 0.2.5.0-alpha-dev on Linux . 250 OK""" d = self.protocol.get_info("desc/name/moria1") gold = "\nrouter moria1 128.31.0.34 9101 0 9131\nplatform Tor 0.2.5.0-alpha-dev on Linux" d.addCallback(CallbackChecker({'desc/name/moria1': gold})) d.addErrback(self.fail) for line in descriptor_info.split(b'\n'): self.send(line) return d def test_addevent(self): self.protocol._set_valid_events('FOO BAR') self.protocol.add_event_listener('FOO', lambda _: None) # is it dangerous/ill-advised to depend on internal state of # class under test? d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.assertEqual( self.transport.value().split(b'\r\n')[-2], b"SETEVENTS FOO" ) self.transport.clear() self.protocol.add_event_listener('BAR', lambda _: None) d = self.protocol.defer self.send(b"250 OK") self.assertTrue( self.transport.value() == b"SETEVENTS FOO BAR\r\n" or self.transport.value() == b"SETEVENTS BAR FOO\r\n" ) self._wait(d) try: self.protocol.add_event_listener( 'SOMETHING_INVALID', lambda _: None ) self.assertTrue(False) except Exception: pass def test_eventlistener(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener = EventListener() self.protocol.add_event_listener('STREAM', listener) d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC") self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC") self.assertEqual(listener.stream_events, 2) def test_eventlistener_error(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 do_error = False def __call__(self, data): self.stream_events += 1 if self.do_error: raise Exception("the bad thing happened") # we make sure the first listener has the errors to prove the # second one still gets called. listener0 = EventListener() listener0.do_error = True listener1 = EventListener() self.protocol.add_event_listener('STREAM', listener0) self.protocol.add_event_listener('STREAM', listener1) d = self.protocol.defer self.send(b"250 OK") self._wait(d) self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC") self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC") self.assertEqual(listener0.stream_events, 2) self.assertEqual(listener1.stream_events, 2) # should have logged the two errors logged = self.flushLoggedErrors() self.assertEqual(2, len(logged)) self.assertTrue("the bad thing happened" in str(logged[0])) self.assertTrue("the bad thing happened" in str(logged[1])) def test_remove_eventlistener(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener = EventListener() self.protocol.add_event_listener('STREAM', listener) self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n') self.protocol.lineReceived(b"250 OK") self.transport.clear() self.protocol.remove_event_listener('STREAM', listener) self.assertEqual(self.transport.value(), b'SETEVENTS \r\n') def test_remove_eventlistener_multiple(self): self.protocol._set_valid_events('STREAM') class EventListener(object): stream_events = 0 def __call__(self, data): self.stream_events += 1 listener0 = EventListener() listener1 = EventListener() self.protocol.add_event_listener('STREAM', listener0) self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n') self.protocol.lineReceived(b"250 OK") self.transport.clear() # add another one, shouldn't issue a tor command self.protocol.add_event_listener('STREAM', listener1) self.assertEqual(self.transport.value(), b'') # remove one, should still not issue a tor command self.protocol.remove_event_listener('STREAM', listener0) self.assertEqual(self.transport.value(), b'') # remove the other one, NOW should issue a command self.protocol.remove_event_listener('STREAM', listener1) self.assertEqual(self.transport.value(), b'SETEVENTS \r\n') # try removing invalid event try: self.protocol.remove_event_listener('FOO', listener0) self.fail() except Exception as e: self.assertTrue('FOO' in str(e)) def test_continuation_line(self): d = self.protocol.get_info_raw("key") def check_continuation(v): self.assertEqual(v, "key=\nvalue0\nvalue1") d.addCallback(check_continuation) self.send(b"250+key=") self.send(b"value0") self.send(b"value1") self.send(b".") self.send(b"250 OK") return d def test_newdesc(self): """ FIXME: this test is now maybe a little silly, it's just testing multiline GETINFO... (Real test is in TorStateTests.test_newdesc_parse) """ self.protocol.get_info_raw('ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A') d = self.protocol.defer d.addCallback(CallbackChecker("""ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A= r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80 s Exit Fast Guard HSDir Named Running Stable V2Dir Valid w Bandwidth=518000 p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888""")) self.send(b"250+ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=") self.send(b"r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80") self.send(b"s Exit Fast Guard HSDir Named Running Stable V2Dir Valid") self.send(b"w Bandwidth=518000") self.send(b"p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888") self.send(b".") self.send(b"250 OK") return d def test_plus_line_no_command(self): self.protocol.lineReceived(b"650+NS\r\n") self.protocol.lineReceived(b"r Gabor gFpAHsFOHGATy12ZUswRf0ZrqAU GG6GDp40cQfR3ODvkBT0r+Q09kw 2012-05-12 16:54:56 91.219.238.71 443 80\r\n") def test_minus_line_no_command(self): """ haven't seen 600's use - "in the wild" but don't see why it's not possible """ self.protocol._set_valid_events('NS') self.protocol.add_event_listener('NS', lambda _: None) self.protocol.lineReceived(b"650-NS\r\n") self.protocol.lineReceived(b"650 OK\r\n")