Skip to content

Commit

Permalink
Merge d0c8975 into 1981a81
Browse files Browse the repository at this point in the history
  • Loading branch information
meejah committed Feb 17, 2017
2 parents 1981a81 + d0c8975 commit 34ea2b2
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 91 deletions.
96 changes: 96 additions & 0 deletions test/test_microdesc.py
@@ -0,0 +1,96 @@

from mock import Mock

from twisted.trial import unittest

from txtorcon._microdesc_parser import MicrodescriptorParser


class ParserTests(unittest.TestCase):

def test_two_no_w(self):
relays = []

def create_relay(**kw):
relays.append(kw)
m = MicrodescriptorParser(create_relay)

for line in [
'r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 11.11.11.11 443 80',
's Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof',
'r ekaf foooooooooooooooooooooooooo barbarbarbarbarbarbarbarbar 2011-11-11 16:30:00 22.22.22.22 443 80',
's Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof',
]:
m.feed_line(line)
m.done()

self.assertEqual(2, len(relays))
self.assertEqual('fake', relays[0]['nickname'])
self.assertEqual('ekaf', relays[1]['nickname'])
self.assertEqual('11.11.11.11', relays[0]['ip'])
self.assertEqual('22.22.22.22', relays[1]['ip'])

self.assertTrue('bandwidth' not in relays[0])
self.assertTrue('bandwidth' not in relays[1])
self.assertTrue('flags' in relays[0])
self.assertTrue('flags' in relays[1])
self.assertTrue('FutureProof' in relays[1]['flags'])

def test_two(self):
relays = []

def create_relay(**kw):
relays.append(kw)
m = MicrodescriptorParser(create_relay)

for line in [
'r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 11.11.11.11 443 80',
's Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof',
'r ekaf foooooooooooooooooooooooooo barbarbarbarbarbarbarbarbar 2011-11-11 16:30:00 22.22.22.22 443 80',
's Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof',
'w Bandwidth=518000',
'p accept 43,53,79-81',
]:
m.feed_line(line)
m.done()

self.assertEqual(2, len(relays))
self.assertEqual('fake', relays[0]['nickname'])
self.assertEqual('ekaf', relays[1]['nickname'])
self.assertEqual('11.11.11.11', relays[0]['ip'])
self.assertEqual('22.22.22.22', relays[1]['ip'])

self.assertTrue('bandwidth' not in relays[0])
self.assertTrue('bandwidth' in relays[1])
self.assertTrue('flags' in relays[0])
self.assertTrue('flags' in relays[1])
self.assertTrue('FutureProof' in relays[1]['flags'])

def test_bad_line(self):
relays = []

def create_relay(**kw):
relays.append(kw)
m = MicrodescriptorParser(create_relay)

with self.assertRaises(Exception) as ctx:
m.feed_line('x blam')
self.assertTrue('Unknown microdescriptor' in str(ctx.exception))
self.assertEqual(0, len(relays))

def test_single_ipv6(self):
relays = []

def create_relay(**kw):
relays.append(kw)
m = MicrodescriptorParser(create_relay)

for line in [
'r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 11.11.11.11 443 80',
'a [2001:0:0:0::0]:4321'
]:
m.feed_line(line)
m.done()

self.assertEqual(1, len(relays))
self.assertEqual(['[2001:0:0:0::0]:4321'], list(relays[0]['ip_v6']))
2 changes: 1 addition & 1 deletion test/test_torstate.py
Expand Up @@ -893,7 +893,7 @@ def test_invalid_routers(self):
self.fail()

except RuntimeError as e:
self.assertTrue('"s "' in str(e))
self.assertTrue('Illegal state' in str(e))

def test_routers_no_policy(self):
"""
Expand Down
196 changes: 196 additions & 0 deletions txtorcon/_microdesc_parser.py
@@ -0,0 +1,196 @@

from .util import find_keywords
import automat

class MicrodescriptorParser(object):
"""
Parsers microdescriptors line by line. New relays are emitted via
the 'create_relay' callback.
"""
_machine = automat.MethodicalMachine()

def __init__(self, create_relay):
self._create_relay = create_relay
self._relay_attrs = None

@_machine.input()
def line(self, line):
"""
A line has been received.
"""

@_machine.input()
def done(self, *args):
"""
All lines have been fed.
"""

@_machine.input()
def _r_line(self, *args):
pass

@_machine.input()
def _s_line(self, *args):
pass

@_machine.input()
def _w_line(self, *args):
pass

@_machine.input()
def _p_line(self, *args):
pass

@_machine.input()
def _a_line(self, *args):
pass

@_machine.output()
def create_relay(self, *args):
r = self._create_relay(**self._relay_attrs)
self._relay_attrs = None
return r

@_machine.output()
def start_relay(self, *args):
self._relay_attrs = dict()

@_machine.output()
def _parse_r(self, *args):
assert len(args) >= 8
self._relay_attrs.update(dict(
nickname=args[0],
idhash=args[1],
orhash=args[2],
modified=args[3] + ' ' + args[4],
ip=args[5],
orport=args[6],
dirport=args[7],
))

@_machine.output()
def _parse_s(self, *args):
self._relay_attrs['flags'] = args

@_machine.output()
def _parse_w(self, *args):
# should only contain "bandwidth" ...
kw = find_keywords(args)
assert 'Bandwidth' in kw
self._relay_attrs['bandwidth'] = kw['Bandwidth']

@_machine.output()
def _parse_a(self, *args):
try:
self._relay_attrs['ip_v6'].extend(args)
except KeyError:
self._relay_attrs['ip_v6'] = list(args)

@_machine.output()
def _error(self, *args):
raise RuntimeError("Illegal state in microdescriptor parser")

@_machine.state(initial=True)
def waiting_r(self):
"""
waiting for an 'r' line
"""

@_machine.state()
def waiting_s(self):
"""
waiting for an 's' line
"""

@_machine.state()
def waiting_w(self):
"""
waiting for an 's' line
"""

@_machine.state()
def error(self):
"""
something bad happened
"""

def feed_line(self, data):
args = data.split()
try:
{
'r': self._r_line,
's': self._s_line,
'w': self._w_line,
'p': self._p_line,
'a': self._a_line,
'OK': lambda: None, # ignore
'ns/all=': lambda: None, # ignore
'.': lambda: None, # ignore
}[args[0]](*args[1:])
except KeyError:
raise Exception(
'Unknown microdescriptor line: "{}"'.format(args[0])
)

waiting_r.upon(
_r_line,
enter=waiting_s,
outputs=[start_relay, _parse_r],
)
waiting_r.upon(
_p_line,
enter=waiting_r,
outputs=[],
)
waiting_r.upon(
done,
enter=waiting_r,
outputs=[],
)

waiting_s.upon(
_r_line,
enter=error,
outputs=[_error],
)
# waiting_s.upon(
# _r_line,
# enter=waiting_s,
# outputs=[create_relay, start_relay, _parse_r],
# )
waiting_s.upon(
_s_line,
enter=waiting_w,
outputs=[_parse_s],
)
waiting_s.upon(
_a_line,
enter=waiting_s,
outputs=[_parse_a],
)
waiting_s.upon(
done,
enter=waiting_r,
outputs=[create_relay],
)

waiting_w.upon(
_w_line,
enter=waiting_r,
outputs=[_parse_w, create_relay],
)
waiting_w.upon(
_r_line,
enter=waiting_s,
outputs=[create_relay, start_relay, _parse_r],
)
waiting_w.upon(
_a_line,
enter=waiting_w,
outputs=[_parse_a],
)
waiting_w.upon(
done,
enter=waiting_r,
outputs=[create_relay],
)

0 comments on commit 34ea2b2

Please sign in to comment.