def test_no_matcher(self): idle = State("I") other = State("O") fsm = FSM([idle, other]) idle.add_transition(Transition(other, None, None)) fsm.process("")
def test_handler_state(self): idle = State("I") cmd = State("C") idle.add_transitions([Transition(cmd, self.match, lambda x: idle)]) fsm = FSM([idle, cmd]) self.commands = [] self.assertEqual(fsm.state, idle) fsm.process("250 OK\n") self.assertEqual(fsm.state, idle)
def test_simple_machine(self): idle = State("I") cmd = State("C") idle.add_transitions([Transition(cmd, self.match, None)]) fsm = FSM([idle, cmd]) self.commands = [] self.assertEqual(fsm.state, idle) fsm.process("250 OK\n") self.assertEqual(fsm.state, cmd)
def parse_client_keys(stream): ''' This parses a hidden-service "client_keys" file, either stealth or basic (they're the same, except "stealth" includes a "client-key"). Returns a list of HiddenServiceClientAuth() instances. Note that the key does NOT include the "----BEGIN ---" markers, nor *any* embedded whitespace. It is *just* the key blob. ''' def parse_error(data): raise RuntimeError("Parse error at: " + data) class ParserState(object): def __init__(self): self.keys = [] self.reset() def reset(self): self.name = None self.cookie = None self.key = [] def create_key(self): if self.name is not None: self.keys.append(HiddenServiceClientAuth(self.name, self.cookie, self.key)) self.reset() def set_name(self, name): self.create_key() self.name = name.split()[1] def set_cookie(self, cookie): self.cookie = cookie.split()[1] if self.cookie.endswith('=='): self.cookie = self.cookie[:-2] def add_key_line(self, line): self.key.append(line) from txtorcon.spaghetti import FSM, State, Transition init = State('init') got_name = State('got_name') got_cookie = State('got_cookie') reading_key = State('got_key') parser_state = ParserState() # initial state; we want "client-name" or it's an error init.add_transitions([ Transition(got_name, lambda line: line.startswith('client-name '), parser_state.set_name), Transition(init, lambda line: not line.startswith('client-name '), parse_error), ]) # next up is "descriptor-cookie" or it's an error got_name.add_transitions([ Transition(got_cookie, lambda line: line.startswith('descriptor-cookie '), parser_state.set_cookie), Transition(init, lambda line: not line.startswith('descriptor-cookie '), parse_error), ]) # the "interesting bit": there's either a client-name if we're a # "basic" file, or an RSA key (with "client-key" before it) got_cookie.add_transitions([ Transition(reading_key, lambda line: line.startswith('client-key'), None), Transition(got_name, lambda line: line.startswith('client-name '), parser_state.set_name), ]) # if we're reading an RSA key, we accumulate it in current_key.key # until we hit a line starting with "client-name" reading_key.add_transitions([ Transition(reading_key, lambda line: not line.startswith('client-name'), parser_state.add_key_line), Transition(got_name, lambda line: line.startswith('client-name '), parser_state.set_name), ]) # create our FSM and parse the data fsm = FSM([init, got_name, got_cookie, reading_key]) for line in stream.readlines(): fsm.process(line.strip()) parser_state.create_key() # make sure we get the "last" one return parser_state.keys