class TorConfig(object):
    """This class abstracts out Tor's config, and can be used both to
    create torrc files from nothing and track live configuration of a Tor
    instance.

    Also, it gives easy access to all the configuration options
    present. This is initialized at "bootstrap" time, providing
    attribute-based access thereafter. Note that after you set some
    number of items, you need to do a save() before these are sent to
    Tor (and then they will be done as one SETCONF).

    You may also use this class to construct a configuration from
    scratch (e.g. to give to :func:`txtorcon.launch_tor`). In this
    case, values are reflected right away. (If we're not bootstrapped
    to a Tor, this is the mode).

    Note that you do not need to call save() if you're just using
    TorConfig to create a .torrc file or for input to launch_tor().

    This class also listens for CONF_CHANGED events to update the
    cached data in the event other controllers (etc) changed it.

    There is a lot of magic attribute stuff going on in here (which
    might be a bad idea, overall) but the *intent* is that you can
    just set Tor options and it will all Just Work. For config items
    that take multiple values, set that to a list. For example::

        conf = TorConfig(...)
        conf.SOCKSPort = [9050, 1337]
        conf.HiddenServices.append(HiddenService(...))

    (Incoming objects, like lists, are intercepted and wrapped).

    FIXME: when is CONF_CHANGED introduced in Tor? Can we do anything
    like it for prior versions?

    FIXME:

        - HiddenServiceOptions is special: GETCONF on it returns
          several (well, two) values. Besides adding the two keys 'by
          hand' do we need to do anything special? Can't we just depend
          on users doing 'conf.hiddenservicedir = foo' AND
          'conf.hiddenserviceport = bar' before a save() ?

        - once I determine a value is default, is there any way to
          actually get what this value is?

    """

    @staticmethod
    @defer.inlineCallbacks
    def from_protocol(proto):
        """
        This creates and returns a ready-to-go TorConfig instance from the
        given protocol, which should be an instance of
        TorControlProtocol.
        """
        cfg = TorConfig(control=proto)
        yield cfg.post_bootstrap
        defer.returnValue(cfg)

    def __init__(self, control=None):
        self.config = {}
        '''Current configuration, by keys.'''

        if control is None:
            self._protocol = None
            self.__dict__['_accept_all_'] = None

        else:
            self._protocol = ITorControlProtocol(control)

        self.unsaved = OrderedDict()
        '''Configuration that has been changed since last save().'''

        self.parsers = {}
        '''Instances of the parser classes, subclasses of TorConfigType'''

        self.list_parsers = set(['hiddenservices', 'ephemeralonionservices'])
        '''All the names (keys from .parsers) that are a List of something.'''

        # during bootstrapping we decide whether we support the
        # following features. A thing goes in here if TorConfig
        # behaves differently depending upon whether it shows up in
        # "GETINFO config/names"
        self._supports = dict(
            HiddenServiceDirGroupReadable=False
        )
        self._defaults = dict()

        self.post_bootstrap = defer.Deferred()
        if self.protocol:
            if self.protocol.post_bootstrap:
                self.protocol.post_bootstrap.addCallback(
                    self.bootstrap).addErrback(self.post_bootstrap.errback)
            else:
                self.bootstrap()

        else:
            self.do_post_bootstrap(self)

        self.__dict__['_setup_'] = None

    def socks_endpoint(self, reactor, port=None):
        """
        Returns a TorSocksEndpoint configured to use an already-configured
        SOCKSPort from the Tor we're connected to. By default, this
        will be the very first SOCKSPort.

        :param port: a str, the first part of the SOCKSPort line (that
            is, a port like "9151" or a Unix socket config like
            "unix:/path". You may also specify a port as an int.

        If you need to use a particular port that may or may not
        already be configured, see the async method
        :meth:`txtorcon.TorConfig.create_socks_endpoint`
        """

        if len(self.SocksPort) == 0:
            raise RuntimeError(
                "No SOCKS ports configured"
            )

        socks_config = None
        if port is None:
            socks_config = self.SocksPort[0]
        else:
            port = str(port)  # in case e.g. an int passed in
            if ' ' in port:
                raise ValueError(
                    "Can't specify options; use create_socks_endpoint instead"
                )

            for idx, port_config in enumerate(self.SocksPort):
                # "SOCKSPort" is a gnarly beast that can have a bunch
                # of options appended, so we have to split off the
                # first thing which *should* be the port (or can be a
                # string like 'unix:')
                if port_config.split()[0] == port:
                    socks_config = port_config
                    break
        if socks_config is None:
            raise RuntimeError(
                "No SOCKSPort configured for port {}".format(port)
            )

        return _endpoint_from_socksport_line(reactor, socks_config)

    @defer.inlineCallbacks
    def create_socks_endpoint(self, reactor, socks_config):
        """
        Creates a new TorSocksEndpoint instance given a valid
        configuration line for ``SocksPort``; if this configuration
        isn't already in the underlying tor, we add it. Note that this
        method may call :meth:`txtorcon.TorConfig.save()` on this instance.

        Note that calling this with `socks_config=None` is equivalent
        to calling `.socks_endpoint` (which is not async).

        XXX socks_config should be .. i dunno, but there's fucking
        options and craziness, e.g. default Tor Browser Bundle is:
        ['9150 IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth',
        '9155']

        XXX maybe we should say "socks_port" as the 3rd arg, insist
        it's an int, and then allow/support all the other options
        (e.g. via kwargs)

        XXX we could avoid the "maybe call .save()" thing; worth it?
        (actually, no we can't or the Tor won't have it config'd)
        """

        yield self.post_bootstrap

        if socks_config is None:
            if len(self.SocksPort) == 0:
                raise RuntimeError(
                    "socks_port is None and Tor has no SocksPorts configured"
                )
            socks_config = self.SocksPort[0]
        else:
            if not any([socks_config in port for port in self.SocksPort]):
                # need to configure Tor
                self.SocksPort.append(socks_config)
                try:
                    yield self.save()
                except TorProtocolError as e:
                    extra = ''
                    if socks_config.startswith('unix:'):
                        # XXX so why don't we check this for the
                        # caller, earlier on?
                        extra = '\nNote Tor has specific ownership/permissions ' +\
                                'requirements for unix sockets and parent dir.'
                    raise RuntimeError(
                        "While configuring SOCKSPort to '{}', error from"
                        " Tor: {}{}".format(
                            socks_config, e, extra
                        )
                    )

        defer.returnValue(
            _endpoint_from_socksport_line(reactor, socks_config)
        )

    # FIXME should re-name this to "tor_protocol" to be consistent
    # with other things? Or rename the other things?
    """
    read-only access to TorControlProtocol. Call attach_protocol() to
    set it, which can only be done if we don't already have a
    protocol.
    """
    def _get_protocol(self):
        return self.__dict__['_protocol']
    protocol = property(_get_protocol)
    tor_protocol = property(_get_protocol)

    def attach_protocol(self, proto):
        """
        returns a Deferred that fires once we've set this object up to
        track the protocol. Fails if we already have a protocol.
        """
        if self._protocol is not None:
            raise RuntimeError("Already have a protocol.")
        # make sure we have nothing in self.unsaved
        self.save()
        self.__dict__['_protocol'] = proto

        # FIXME some of this is duplicated from ctor
        del self.__dict__['_accept_all_']
        self.__dict__['post_bootstrap'] = defer.Deferred()
        if proto.post_bootstrap:
            proto.post_bootstrap.addCallback(self.bootstrap)
        return self.__dict__['post_bootstrap']

    def __setattr__(self, name, value):
        """
        we override this so that we can provide direct attribute
        access to our config items, and move them into self.unsaved
        when they've been changed. hiddenservices have to be special
        unfortunately. the _setup_ thing is so that we can set up the
        attributes we need in the constructor without uusing __dict__
        all over the place.
        """

        # appease flake8's hatred of lambda :/
        def has_setup_attr(o):
            return '_setup_' in o.__dict__

        def has_accept_all_attr(o):
            return '_accept_all_' in o.__dict__

        def is_hidden_services(s):
            return s.lower() == "hiddenservices"

        if has_setup_attr(self):
            name = self._find_real_name(name)
            if not has_accept_all_attr(self) and not is_hidden_services(name):
                value = self.parsers[name].validate(value, self, name)
            if isinstance(value, list):
                value = _ListWrapper(
                    value, functools.partial(self.mark_unsaved, name))

            name = self._find_real_name(name)
            self.unsaved[name] = value

        else:
            super(TorConfig, self).__setattr__(name, value)

    def _maybe_create_listwrapper(self, rn):
        if rn.lower() in self.list_parsers and rn not in self.config:
            self.config[rn] = _ListWrapper([], functools.partial(
                self.mark_unsaved, rn))

    def __getattr__(self, name):
        """
        on purpose, we don't return self.unsaved if the key is in there
        because I want the config to represent the running Tor not
        ``things which might get into the running Tor if save() were
        to be called''
        """
        rn = self._find_real_name(name)
        if '_accept_all_' in self.__dict__ and rn in self.unsaved:
            return self.unsaved[rn]
        self._maybe_create_listwrapper(rn)
        v = self.config[rn]
        if v == DEFAULT_VALUE:
            v = self.__dict__['_defaults'].get(rn, DEFAULT_VALUE)
        return v

    def __contains__(self, item):
        if item in self.unsaved and '_accept_all_' in self.__dict__:
            return True
        return item in self.config

    def __iter__(self):
        '''
        FIXME needs proper iterator tests in test_torconfig too
        '''
        for x in self.config.__iter__():
            yield x
        for x in self.__dict__['unsaved'].__iter__():
            yield x

    def get_type(self, name):
        """
        return the type of a config key.

        :param: name the key

        FIXME can we do something more-clever than this for client
        code to determine what sort of thing a key is?
        """

        # XXX FIXME uhm...how to do all the different types of hidden-services?
        if name.lower() == 'hiddenservices':
            return FilesystemOnionService
        return type(self.parsers[name])

    def _conf_changed(self, arg):
        """
        internal callback. from control-spec:

        4.1.18. Configuration changed

          The syntax is:
             StartReplyLine *(MidReplyLine) EndReplyLine

             StartReplyLine = "650-CONF_CHANGED" CRLF
             MidReplyLine = "650-" KEYWORD ["=" VALUE] CRLF
             EndReplyLine = "650 OK"

          Tor configuration options have changed (such as via a SETCONF or
          RELOAD signal). KEYWORD and VALUE specify the configuration option
          that was changed.  Undefined configuration options contain only the
          KEYWORD.
        """

        conf = parse_keywords(arg, multiline_values=False)
        for (k, v) in conf.items():
            # v will be txtorcon.DEFAULT_VALUE already from
            # parse_keywords if it was unspecified
            real_name = self._find_real_name(k)
            if real_name in self.parsers:
                v = self.parsers[real_name].parse(v)
            self.config[real_name] = v

    def bootstrap(self, arg=None):
        '''
        This only takes args so it can be used as a callback. Don't
        pass an arg, it is ignored.
        '''
        try:
            d = self.protocol.add_event_listener(
                'CONF_CHANGED', self._conf_changed)
        except RuntimeError:
            # for Tor versions which don't understand CONF_CHANGED
            # there's nothing we can really do.
            log.msg(
                "Can't listen for CONF_CHANGED event; won't stay up-to-date "
                "with other clients.")
            d = defer.succeed(None)
        d.addCallback(lambda _: self.protocol.get_info_raw("config/names"))
        d.addCallback(self._do_setup)
        d.addCallback(self.do_post_bootstrap)
        d.addErrback(self.do_post_errback)

    def do_post_errback(self, f):
        self.post_bootstrap.errback(f)
        return None

    def do_post_bootstrap(self, arg):
        if not self.post_bootstrap.called:
            self.post_bootstrap.callback(self)
        return self

    def needs_save(self):
        return len(self.unsaved) > 0

    def mark_unsaved(self, name):
        name = self._find_real_name(name)
        if name in self.config and name not in self.unsaved:
            self.unsaved[name] = self.config[self._find_real_name(name)]

    def save(self):
        """
        Save any outstanding items. This returns a Deferred which will
        errback if Tor was unhappy with anything, or callback with
        this TorConfig object on success.
        """

        if not self.needs_save():
            return defer.succeed(self)

        args = []
        directories = []
        for (key, value) in self.unsaved.items():
            if key == 'HiddenServices':
                self.config['HiddenServices'] = value
                # using a list here because at least one unit-test
                # cares about order -- and conceivably order *could*
                # matter here, to Tor...
                services = list()
                # authenticated services get flattened into the HiddenServices list...
                for hs in value:
                    if IOnionClient.providedBy(hs):
                        parent = IOnionClient(hs).parent
                        if parent not in services:
                            services.append(parent)
                    elif isinstance(hs, (EphemeralOnionService, EphemeralHiddenService)):
                        raise ValueError(
                            "Only filesystem based Onion services may be added"
                            " via TorConfig.hiddenservices; ephemeral services"
                            " must be created with 'create_onion_service'."
                        )
                    else:
                        if hs not in services:
                            services.append(hs)

                for hs in services:
                    for (k, v) in hs.config_attributes():
                        if k == 'HiddenServiceDir':
                            if v not in directories:
                                directories.append(v)
                                args.append(k)
                                args.append(v)
                            else:
                                raise RuntimeError("Trying to add hidden service with same HiddenServiceDir: %s" % v)
                        else:
                            args.append(k)
                            args.append(v)
                continue

            if isinstance(value, list):
                for x in value:
                    # FIXME XXX
                    if x is not DEFAULT_VALUE:
                        args.append(key)
                        args.append(str(x))

            else:
                args.append(key)
                args.append(value)

            # FIXME in future we should wait for CONF_CHANGED and
            # update then, right?
            real_name = self._find_real_name(key)
            if not isinstance(value, list) and real_name in self.parsers:
                value = self.parsers[real_name].parse(value)
            self.config[real_name] = value

        # FIXME might want to re-think this, but currently there's no
        # way to put things into a config and get them out again
        # nicely...unless you just don't assign a protocol
        if self.protocol:
            d = self.protocol.set_conf(*args)
            d.addCallback(self._save_completed)
            return d

        else:
            self._save_completed()
            return defer.succeed(self)

    def _save_completed(self, *args):
        '''internal callback'''
        self.__dict__['unsaved'] = {}
        return self

    def _find_real_name(self, name):
        keys = list(self.__dict__['parsers'].keys()) + list(self.__dict__['config'].keys())
        for x in keys:
            if x.lower() == name.lower():
                return x
        return name

    @defer.inlineCallbacks
    def _get_defaults(self):
        try:
            defaults_raw = yield self.protocol.get_info_raw("config/defaults")
            defaults = {}
            for line in defaults_raw.split('\n')[1:]:
                k, v = line.split(' ', 1)
                if k in defaults:
                    if isinstance(defaults[k], list):
                        defaults[k].append(v)
                    else:
                        defaults[k] = [defaults[k], v]
                else:
                    defaults[k] = v
        except TorProtocolError:
            # must be a version of Tor without config/defaults
            defaults = dict()
        defer.returnValue(defaults)

    @defer.inlineCallbacks
    def _do_setup(self, data):
        defaults = self.__dict__['_defaults'] = yield self._get_defaults()

        for line in data.split('\n'):
            if line == "config/names=":
                continue

            (name, value) = line.split()
            if name in self._supports:
                self._supports[name] = True

            if name == 'HiddenServiceOptions':
                # set up the "special-case" hidden service stuff
                servicelines = yield self.protocol.get_conf_raw(
                    'HiddenServiceOptions')
                self._setup_hidden_services(servicelines)
                continue

            # there's a whole bunch of FooPortLines (where "Foo" is
            # "Socks", "Control", etc) and some have defaults, some
            # don't but they all have FooPortLines, FooPort, and
            # __FooPort definitions so we only "do stuff" for the
            # "FooPortLines"
            if name.endswith('PortLines'):
                rn = self._find_real_name(name[:-5])
                self.parsers[rn] = String()  # not Port() because options etc
                self.list_parsers.add(rn)
                v = yield self.protocol.get_conf(name[:-5])
                v = v[name[:-5]]

                initial = []
                if v == DEFAULT_VALUE or v == 'auto':
                    try:
                        initial = defaults[name[:-5]]
                    except KeyError:
                        default_key = '__{}'.format(name[:-5])
                        default = yield self.protocol.get_conf_single(default_key)
                        if not default:
                            initial = []
                        else:
                            initial = [default]
                else:
                    initial = [self.parsers[rn].parse(v)]
                self.config[rn] = _ListWrapper(
                    initial, functools.partial(self.mark_unsaved, rn))

            # XXX for Virtual check that it's one of the *Ports things
            # (because if not it should be an error)
            if value in ('Dependant', 'Dependent', 'Virtual'):
                continue

            # there's a thing called "Boolean+Auto" which is -1 for
            # auto, 0 for false and 1 for true. could be nicer if it
            # was called AutoBoolean or something, but...
            value = value.replace('+', '_')

            inst = None
            # FIXME: put parser classes in dict instead?
            for cls in config_types:
                if cls.__name__ == value:
                    inst = cls()
            if not inst:
                raise RuntimeError("Don't have a parser for: " + value)
            v = yield self.protocol.get_conf(name)
            v = v[name]

            rn = self._find_real_name(name)
            self.parsers[rn] = inst
            if is_list_config_type(inst.__class__):
                self.list_parsers.add(rn)
                parsed = self.parsers[rn].parse(v)
                if parsed == [DEFAULT_VALUE]:
                    parsed = defaults.get(rn, [])
                self.config[rn] = _ListWrapper(
                    parsed, functools.partial(self.mark_unsaved, rn))

            else:
                if v == '' or v == DEFAULT_VALUE:
                    parsed = self.parsers[rn].parse(defaults.get(rn, DEFAULT_VALUE))
                else:
                    parsed = self.parsers[rn].parse(v)
                self.config[rn] = parsed

        # get any ephemeral services we own, or detached services.
        # these are *not* _ListWrappers because we don't care if they
        # change, nothing in Tor's config exists for these (probably
        # begging the question: why are we putting them in here at all
        # then...?)
        try:
            ephemeral = yield self.protocol.get_info('onions/current')
        except Exception:
            self.config['EphemeralOnionServices'] = []
        else:
            onions = []
            for line in ephemeral['onions/current'].split('\n'):
                onion = line.strip()
                if onion:
                    onions.append(
                        EphemeralOnionService(
                            self,
                            ports=[],  # no way to discover ports=
                            hostname=onion,
                            private_key=DISCARD,  # we don't know it, anyway
                            version=2,
                            detach=False,
                        )
                    )
            self.config['EphemeralOnionServices'] = onions

        try:
            detached = yield self.protocol.get_info('onions/detached')
        except Exception:
            self.config['DetachedOnionServices'] = []
        else:
            onions = []
            for line in detached['onions/detached'].split('\n'):
                onion = line.strip()
                if onion:
                    onions.append(
                        EphemeralOnionService(
                            self,
                            ports=[],  # no way to discover original ports=
                            hostname=onion,
                            detach=True,
                            private_key=DISCARD,
                        )
                    )
            self.config['DetachedOnionServices'] = onions
        defer.returnValue(self)

    def _setup_hidden_services(self, servicelines):

        def maybe_add_hidden_service():
            if directory is not None:
                if directory not in directories:
                    directories.append(directory)
                    if not auth:
                        service = FilesystemOnionService(
                            self, directory, ports, ver, group_read
                        )
                        hs.append(service)
                    else:
                        auth_type, clients = auth.split(' ', 1)
                        clients = clients.split(',')
                        if auth_type == 'basic':
                            auth0 = AuthBasic(clients)
                        elif auth_type == 'stealth':
                            auth0 = AuthStealth(clients)
                        else:
                            raise ValueError(
                                "Unknown auth type '{}'".format(auth_type)
                            )
                        parent_service = FilesystemAuthenticatedOnionService(
                            self, directory, ports, auth0, ver, group_read
                        )
                        for client_name in parent_service.client_names():
                            hs.append(parent_service.get_client(client_name))
                else:
                    raise RuntimeError("Trying to add hidden service with same HiddenServiceDir: %s" % directory)

        hs = []
        directory = None
        directories = []
        ports = []
        ver = None
        group_read = None
        auth = None
        for line in servicelines.split('\n'):
            if not len(line.strip()):
                continue

            if line == 'HiddenServiceOptions':
                continue
            k, v = line.split('=')
            if k == 'HiddenServiceDir':
                maybe_add_hidden_service()
                directory = v
                _directory = directory
                directory = os.path.abspath(directory)
                if directory != _directory:
                    warnings.warn(
                        "Directory path: %s changed to absolute path: %s" % (_directory, directory),
                        RuntimeWarning
                    )
                ports = []
                ver = None
                auth = None
                group_read = 0

            elif k == 'HiddenServicePort':
                ports.append(v)

            elif k == 'HiddenServiceVersion':
                ver = int(v)

            elif k == 'HiddenServiceAuthorizeClient':
                if auth is not None:
                    # definitely error, or keep going?
                    raise ValueError("Multiple HiddenServiceAuthorizeClient lines for one service")
                auth = v

            elif k == 'HiddenServiceDirGroupReadable':
                group_read = int(v)

            else:
                raise RuntimeError("Can't parse HiddenServiceOptions: " + k)

        maybe_add_hidden_service()

        name = 'HiddenServices'
        self.config[name] = _ListWrapper(
            hs, functools.partial(self.mark_unsaved, name))

    def config_args(self):
        '''
        Returns an iterator of 2-tuples (config_name, value), one for each
        configuration option in this config. This is more-or-less an
        internal method, but see, e.g., launch_tor()'s implementation
        if you think you need to use this for something.

        See :meth:`txtorcon.TorConfig.create_torrc` which returns a
        string which is also a valid ``torrc`` file
        '''

        everything = dict()
        everything.update(self.config)
        everything.update(self.unsaved)

        for (k, v) in list(everything.items()):
            if type(v) is _ListWrapper:
                if k.lower() == 'hiddenservices':
                    for x in v:
                        for (kk, vv) in x.config_attributes():
                            yield (str(kk), str(vv))

                else:
                    # FIXME actually, is this right? don't we want ALL
                    # the values in one string?!
                    for x in v:
                        yield (str(k), str(x))

            else:
                yield (str(k), str(v))

    def create_torrc(self):
        rtn = StringIO()

        for (k, v) in self.config_args():
            rtn.write(u'%s %s\n' % (k, v))

        return rtn.getvalue() def launch_tor(config, reactor,
               tor_binary=None,
               progress_updates=None,
               connection_creator=None,
               timeout=None,
               kill_on_stderr=True,
               stdout=None, stderr=None):
    def parse(self, s):
    def validate(self, s, instance, name):
    def parse(self, s):
    def validate(self, s, instance, name):
    def parse(self, s):
    def validate(self, s, instance, name):
    def parse(self, s):
    def validate(self, s, instance, name):
    def parse(self, s):
    def parse(self, s):
    def parse(self, s):
    def validate(self, obj, instance, name):
def is_list_config_type(klass):
def _wrapture(orig):
    def foo(*args):
    def __init__(self, thelist, on_modify_cb):
    def __repr__(self):
    def __init__(self, config, thedir, ports,
                 auth=[], ver=2, group_readable=0):
    def __setattr__(self, name, value):
    def private_key(self):
    def clients(self):
    def hostname(self):
    def client_keys(self):
    def config_attributes(self):
def _is_valid_keyblob(key_blob_or_type):
    def __init__(self, ports, key_blob_or_type='NEW:BEST', auth=[], ver=2):
    def add_to_tor(self, protocol):
    def remove_from_tor(self, protocol):
def _endpoint_from_socksport_line(reactor, socks_config):
    def from_protocol(proto):
    def __init__(self, control=None):
    def socks_endpoint(self, reactor, port=None):
    def create_socks_endpoint(self, reactor, socks_config):
    def _get_protocol(self):
    def attach_protocol(self, proto):
    def __setattr__(self, name, value):
        def has_setup_attr(o):
        def has_accept_all_attr(o):
        def is_hidden_services(s):
    def _maybe_create_listwrapper(self, rn):
    def __getattr__(self, name):
    def __contains__(self, item):
    def __iter__(self):
    def get_type(self, name):
    def _conf_changed(self, arg):
    def bootstrap(self, arg=None):
    def do_post_errback(self, f):
    def do_post_bootstrap(self, arg):
    def needs_save(self):
    def mark_unsaved(self, name):
    def save(self):
    def _save_completed(self, *args):
    def _find_real_name(self, name):
    def _get_defaults(self):
    def _do_setup(self, data):
    def _setup_hidden_services(self, servicelines):
        def maybe_add_hidden_service():
    def config_args(self):
    def create_torrc(self):
class TorConfigType(object):
class Boolean(TorConfigType):
class Boolean_Auto(TorConfigType):
class Integer(TorConfigType):
class SignedInteger(Integer):
class Port(Integer):
class TimeInterval(Integer):
class TimeMsecInterval(TorConfigType):
class DataSize(Integer):
class Float(TorConfigType):
class Time(TorConfigType):
class CommaList(TorConfigType):
class TimeIntervalCommaList(CommaList):
class RouterList(CommaList):
class String(TorConfigType):
class Filename(String):
class LineList(TorConfigType):
class _ListWrapper(list):
class HiddenService(object):
class EphemeralHiddenService(object):
class TorConfig(object): class FilesystemOnionService(object):
    """
    An Onion service whose keys are stored on disk.
    """

    @staticmethod
    @defer.inlineCallbacks
    def create(reactor, config, hsdir, ports,
               version=3,
               group_readable=False,
               progress=None,
               await_all_uploads=None):
        """
        returns a new FilesystemOnionService after adding it to the
        provided config and ensuring at least one of its descriptors
        is uploaded.

        :param config: a :class:`txtorcon.TorConfig` instance

        :param ports: a list of ports to make available; any of these
            can be 2-tuples of (remote, local) if you want to expose a
            particular port locally (otherwise, an available one is
            chosen)

        :param hsdir: the directory in which to store private keys

        :param version: 2 or 3, which kind of service to create

        :param group_readable: if True, the Tor option
            `HiddenServiceDirGroupReadable` is set to 1 for this service

        :param progress: a callable taking (percent, tag, description)
            that is called periodically to report progress.

        :param await_all_uploads: if True, the Deferred only fires
            after ALL descriptor uploads have completed (otherwise, it
            fires when at least one has completed).

        See also :meth:`txtorcon.Tor.create_onion_service` (which
        ultimately calls this).
        """

        # if hsdir is relative, it's "least surprising" (IMO) to make
        # it into a absolute path here -- otherwise, it's relative to
        # whatever Tor's cwd is.
        hsdir = _canonical_hsdir(hsdir)
        processed_ports = yield _validate_ports(reactor, ports)

        fhs = FilesystemOnionService(config, hsdir, processed_ports, version=version, group_readable=group_readable)
        config.HiddenServices.append(fhs)
        # we .save() down below, after setting HS_DESC listener

        # XXX I *hate* this version checking crap. Can we discover a
        # different way if this Tor supports proper HS_DESC stuff? I
        # think part of the problem here is that "some" Tors have
        # HS_DESC event, but it's not .. sufficient?
        uploaded = [None]
        if not version_at_least(config.tor_protocol.version, 0, 2, 7, 2):
            if progress:
                progress(
                    102, "wait_desctiptor",
                    "Adding an onion service to Tor requires at least version"
                )
                progress(
                    103, "wait_desctiptor",
                    "0.2.7.2 so that HS_DESC events work properly and we can"
                )
                progress(
                    104, "wait_desctiptor",
                    "detect our desctiptor being uploaded."
                )
                progress(
                    105, "wait_desctiptor",
                    "Your version is '{}'".format(config.tor_protocol.version),
                )
                progress(
                    106, "wait_desctiptor",
                    "So, we'll just declare it done right now..."
                )
                uploaded[0] = defer.succeed(None)
        else:
            # XXX actually, there's some versions of Tor when v3
            # filesystem services could be added but they didn't send
            # HS_DESC updates -- did any of these actually get
            # released?!
            uploaded[0] = _await_descriptor_upload(config.tor_protocol, fhs, progress, await_all_uploads)

        yield config.save()
        yield uploaded[0]
        defer.returnValue(fhs)

    def __init__(self, config, thedir, ports, version=3, group_readable=0):
        """
        Do not instantiate directly; use
        :func:`txtorcon.onion.FilesystemOnionService.create`
        """
        self._config = config
        self._dir = os.path.realpath(thedir)

        _validate_ports_low_level(ports)
        from .torconfig import _ListWrapper
        self._ports = _ListWrapper(
            ports,
            functools.partial(config.mark_unsaved, 'HiddenServices'),
        )
        self._version = version
        self._group_readable = group_readable
        self._hostname = None
        self._private_key = None

    @property
    def hostname(self):
        if self._hostname is None:
            try:
                with open(os.path.join(self._dir, 'hostname'), 'r') as f:
                    self._hostname = f.read().strip()
            except IOError:
                # not clear under what circumstances this happens
                # (i.e. we can create a new onion, but somehow not
                # read the hostname file) but ... safety?
                self._hostname = None
        return self._hostname

    @property
    def private_key(self):
        # XXX there's also a file called 'hs_ed25519_public_key' but I
        # think we can just ignore that? .. or do we need a v3-only
        # accessor for .public_key() as well?
        if self._private_key is None:
            if self.version == 2:
                try:
                    with open(os.path.join(self._dir, 'private_key'), 'r') as f:
                        self._private_key = f.read().strip()
                except IOError:
                    # not clear under what circumstances this happens
                    # (i.e. we can create a new onion, but somehow not
                    # read the private_key file) but ... safety?
                    self._private_key = None
            elif self.version == 3:
                # XXX see tor bug #20699 -- would be Really Nice to
                # not have to deal with binary data here (well, more
                # for ADD_ONION, but still)
                try:
                    with open(os.path.join(self._dir, 'hs_ed25519_secret_key'), 'rb') as f:
                        self._private_key = f.read().strip()
                except IOError:
                    # not clear under what circumstances this happens
                    # (i.e. we can create a new onion, but somehow not
                    # read the private key file) but ... safety?
                    self._private_key = None
            else:
                raise RuntimeError(
                    "Don't know how to load private_key for version={} "
                    "Onion service".format(self.version)
                )
        return self._private_key

    @property
    def ports(self):
        return self._ports

    @ports.setter
    def ports(self, ports):
        # XXX FIXME need to update Tor's notion of config and/or
        # reject this request after we *have* updated Tor..."or
        # something"
        from .torconfig import _ListWrapper
        self._ports = _ListWrapper(
            ports,
            functools.partial(self._config.mark_unsaved, 'HiddenServices'),
        )
        self._config.mark_unsaved('HiddenServices')

    @property
    def directory(self):
        return self._dir

    @directory.setter
    def directory(self, d):
        self._dir = d
        self._config.mark_unsaved('HiddenServices')

    @property
    def dir(self):
        # deprecated
        return self.directory

    @dir.setter
    def dir(self, d):
        # deprecated
        self.directory = d

    @property
    def group_readable(self):
        return self._group_readable

    @property
    def version(self):
        return self._version

    @version.setter
    def version(self, v):
        self._version = v
        self._config.mark_unsaved('HiddenServices')

    def config_attributes(self):
        """
        Helper method used by TorConfig when generating a torrc file and
        SETCONF commands
        """

        rtn = [('HiddenServiceDir', str(self._dir))]
        if self._config._supports['HiddenServiceDirGroupReadable'] \
           and self.group_readable:
            rtn.append(('HiddenServiceDirGroupReadable', str(1)))
        for x in self.ports:
            rtn.append(('HiddenServicePort', str(x)))
        if self.version:
            rtn.append(('HiddenServiceVersion', str(self.version)))
        return rtn class FilesystemAuthenticatedOnionService(object):
    """
    An Onion service whose keys are stored on disk by Tor and which
    does authentication.
    """

    @staticmethod
    @defer.inlineCallbacks
    def create(reactor, config, hsdir, ports,
               auth=None,
               version=3,
               group_readable=False,
               progress=None,
               await_all_uploads=None):
        """
        returns a new FilesystemAuthenticatedOnionService after adding it
        to the provided config and ensureing at least one of its
        descriptors is uploaded.

        :param config: a :class:`txtorcon.TorConfig` instance

        :param ports: a list of ports to make available; any of these
            can be 2-tuples of (remote, local) if you want to expose a
            particular port locally (otherwise, an available one is
            chosen)

        :param auth: an instance of :class:`txtorcon.AuthBasic` or
            :class:`txtorcon.AuthStealth`

        :param version: 2 or 3, which kind of service to create

        :param group_readable: if True, the Tor option
            `HiddenServiceDirGroupReadable` is set to 1 for this service

        :param progress: a callable taking (percent, tag, description)
            that is called periodically to report progress.

        :param await_all_uploads: if True, the Deferred only fires
            after ALL descriptor uploads have completed (otherwise, it
            fires when at least one has completed).
        """
        # if hsdir is relative, it's "least surprising" (IMO) to make
        # it into a relative path here -- otherwise, it's relative to
        # whatever Tor's cwd is. Issue similar warning to Tor?
        hsdir = _canonical_hsdir(hsdir)
        processed_ports = yield _validate_ports(reactor, ports)

        fhs = FilesystemAuthenticatedOnionService(
            config, hsdir, processed_ports, auth,
            version=version,
            group_readable=group_readable,
        )
        config.HiddenServices.append(fhs)

        def translate_progress(pct, tag, description):
            # XXX fixme actually translate..
            if progress:
                progress(pct, tag, description)

        # most of this code same as non-authenticated version; can we share?
        # we .save() down below, after setting HS_DESC listener
        uploaded = [None]
        if not version_at_least(config.tor_protocol.version, 0, 2, 7, 2):
            translate_progress(
                102, "wait_desctiptor",
                "Adding an onion service to Tor requires at least version"
            )
            translate_progress(
                103, "wait_desctiptor",
                "0.2.7.2 so that HS_DESC events work properly and we can"
            )
            translate_progress(
                104, "wait_desctiptor",
                "detect our desctiptor being uploaded."
            )
            translate_progress(
                105, "wait_desctiptor",
                "Your version is '{}'".format(config.tor_protocol.version),
            )
            translate_progress(
                106, "wait_desctiptor",
                "So, we'll just declare it done right now..."
            )
            uploaded[0] = defer.succeed(None)
        else:
            # XXX actually, there's some versions of Tor when v3
            # filesystem services could be added but they didn't send
            # HS_DESC updates -- did any of these actually get
            # released?!
            uploaded[0] = _await_descriptor_upload(config.tor_protocol, fhs, progress, await_all_uploads)

        yield config.save()
        yield uploaded[0]
        defer.returnValue(fhs)

    def __init__(self, config, thedir, ports, auth, version=3, group_readable=0):
        # XXX do we need version here? probably...
        self._config = config
        self._dir = thedir
        self._ports = ports
        assert auth is not None, "Must provide an auth= instance"
        if not isinstance(auth, (AuthBasic, AuthStealth)):
            raise ValueError("auth= must be one of AuthBasic or AuthStealth")
        self._auth = auth
        # dict: name -> IAuthenticatedOnionClient
        self._clients = None
        self._expected_clients = auth.client_names()
        self._version = version
        self._group_readable = group_readable
        self._client_keys = None

    @property
    def hidden_service_directory(self):
        return self._dir

    @property
    def group_readable(self):
        return self._group_readable

    @property
    def ports(self):
        return self._ports

    @property
    def version(self):
        return self._version

    # basically everything in HiddenService, except the only API we
    # provide is "clients" because there's a separate .onion hostname
    # and authentication token per client.

    def get_permanent_id(self):
        """
        IAuthenticatedOnionClients API
        """
        with open(os.path.join(self._dir, "private_key"), "rb") as f:
            private_key = serialization.load_pem_private_key(
                f.read(),
                password=None,
                backend=default_backend(),
            )
        return _compute_permanent_id(private_key)

    def client_names(self):
        """
        IAuthenticatedOnionClients API
        """
        if self._clients is None:
            self._parse_hostname()
        return self._clients.keys()

    def get_client(self, name):
        """
        IAuthenticatedOnionClients API
        """
        if self._clients is None:
            self._parse_hostname()
        try:
            return self._clients[name]
        except KeyError:
            raise KeyError("No such client '{}'".format(name))

    def add_client(self, name, hostname, ports, token):
        if self._clients is None:
            self._parse_hostname()
        client = FilesystemAuthenticatedOnionServiceClient(
            parent=self,
            name=name,
            hostname=hostname,
            ports=ports, token=token,
        )
        self._clients[client.name] = client
        self._config.HiddenServices.append(client)

    def _private_key(self, name):
        if self._client_keys is None:
            self._parse_client_keys()
        return self._client_keys[name]

    def _parse_client_keys(self):
        try:
            with open(os.path.join(self._dir, 'client_keys'), 'r') as f:
                keys = _parse_client_keys(f)
        except IOError:
            keys = []
        self._client_keys = {}
        for auth in keys:
            self._client_keys[auth.name] = auth

    def _parse_hostname(self):
        clients = {}
        try:
            with open(os.path.join(self._dir, 'hostname')) as f:
                for idx, line in enumerate(f.readlines()):
                    # lines are like: hex.onion hex # client: name
                    m = re.match("(.*) (.*) # client: (.*)", line)
                    hostname, cookie, name = m.groups()
                    # -> for auth'd services we end up with multiple
                    # -> HiddenService instances now (because different
                    # -> hostnames)
                    clients[name] = FilesystemAuthenticatedOnionServiceClient(
                        self, name, hostname,
                        ports=self._ports,
                        token=cookie,
                    )
        except IOError:
            self._clients = dict()
            return

        self._clients = clients
        if self._expected_clients:
            for expected in self._expected_clients:
                if expected not in self._clients:
                    raise RuntimeError(
                        "Didn't find expected client '{}'".format(expected)
                    )

    def config_attributes(self):
        """
        Helper method used by TorConfig when generating a torrc file and
        SETCONF commands
        """

        rtn = [('HiddenServiceDir', str(self._dir))]
        if self._config._supports['HiddenServiceDirGroupReadable'] \
           and self.group_readable:
            rtn.append(('HiddenServiceDirGroupReadable', str(1)))
        for port in self.ports:
            rtn.append(('HiddenServicePort', str(port)))
        if self._version:
            rtn.append(('HiddenServiceVersion', str(self._version)))
        if self._clients:
            rtn.append((
                'HiddenServiceAuthorizeClient',
                "{} {}".format(self._auth.auth_type, ','.join(self.client_names()))
            ))
        else:
            rtn.append((
                'HiddenServiceAuthorizeClient',
                "{} {}".format(self._auth.auth_type, ','.join(self._expected_clients))
            ))
        return rtn class EphemeralOnionService(object):
    """
    An Onion service whose keys live in memory and are not persisted
    by Tor.

    It is up to the application developer to retrieve and store the
    private key if this service is ever to be brought online again.
    """

    @staticmethod
    @defer.inlineCallbacks
    def create(reactor, config, ports,
               detach=False,
               private_key=None,  # or DISCARD
               version=None,
               progress=None,
               await_all_uploads=None,
               single_hop=False):
        """
        returns a new EphemeralOnionService after adding it to the
        provided config and ensuring at least one of its descriptors
        is uploaded.

        :param config: a :class:`txtorcon.TorConfig` instance

        :param ports: a list of ports to make available; any of these
            can be 2-tuples of (remote, local) if you want to expose a
            particular port locally (otherwise, an available one is
            chosen)

        :param private_key: None, `DISCARD`, or a private key blob

        :param detach: if True, tell Tor to NOT associate this control
            connection with the lifetime of the created service

        :param version: 2 or 3, which kind of service to create

        :param progress: a callable taking (percent, tag, description)
            that is called periodically to report progress.

        :param await_all_uploads: if True, the Deferred only fires
            after ALL descriptor uploads have completed (otherwise, it
            fires when at least one has completed).

        :param single_hop: if True, pass the `NonAnonymous` flag. Note
            that Tor options `HiddenServiceSingleHopMode`,
            `HiddenServiceNonAnonymousMode` must be set to `1` and there
            must be no `SOCKSPort` configured for this to actually work.

        See also :meth:`txtorcon.Tor.create_onion_service` (which
        ultimately calls this).
        """
        version = 2 if version is None else version
        assert version in (2, 3)

        processed_ports = yield _validate_ports(reactor, ports)

        onion = EphemeralOnionService(
            config, processed_ports,
            hostname=None,
            private_key=private_key,
            detach=detach,
            version=version,
            await_all_uploads=await_all_uploads,
            single_hop=single_hop,
        )

        yield _add_ephemeral_service(config, onion, progress, version, None, await_all_uploads)

        defer.returnValue(onion)

    def __init__(self, config, ports, hostname=None, private_key=None, version=3,
                 detach=False, await_all_uploads=None, single_hop=None, **kwarg):
        """
        Users should create instances of this class by using the async
        method :meth:`txtorcon.EphemeralOnionService.create`
        """

        # prior to 17.0.0, this took an argument called "ver" instead
        # of "version". So, we will silently upgrade that.
        if "ver" in kwarg:
            version = int(kwarg.pop("ver"))
        # any other kwargs are illegal
        if len(kwarg):
            raise ValueError(
                "Unknown kwargs: {}".format(", ".join(kwarg.keys()))
            )

        _validate_ports_low_level(ports)

        self._config = config
        self._ports = ports
        self._hostname = hostname
        self._private_key = private_key
        self._version = version
        self._detach = detach
        self._single_hop = single_hop

    # not putting an "add_to_tor" method here; that class is now
    # deprecated and you add one of these by using .create()

    @defer.inlineCallbacks
    def remove(self):
        """
        Issues a DEL_ONION call to our tor, removing this service.
        """
        cmd = 'DEL_ONION {}'.format(self._hostname[:-len('.onion')])
        res = yield self._config.tor_protocol.queue_command(cmd)
        if res.strip() != "OK":
            raise RuntimeError("Failed to remove service")

    @property
    def ports(self):
        return set(self._ports)

    @property
    def version(self):
        return self._version

    @property
    def hostname(self):
        return self._hostname

    @property
    def private_key(self):
        return self._private_key class EphemeralAuthenticatedOnionService(object):
    """
    An onion service with either 'stealth' or 'basic' authentication
    and keys stored in memory only (Tor doesn't store the private keys
    anywhere and erases them when shutting down).

    Use the async class-method ``create`` to make instances of this.
    """

    @staticmethod
    @defer.inlineCallbacks
    def create(reactor, config, ports,
               detach=False,
               private_key=None,  # or DISCARD or a key
               version=None,
               progress=None,
               auth=None,
               await_all_uploads=None,   # AuthBasic, or AuthStealth instance
               single_hop=False):

        """
        returns a new EphemeralAuthenticatedOnionService after adding it
        to the provided config and ensuring at least one of its
        descriptors is uploaded.

        :param config: a :class:`txtorcon.TorConfig` instance

        :param ports: a list of ports to make available; any of these
            can be 2-tuples of (remote, local) if you want to expose a
            particular port locally (otherwise, an available one is
            chosen)

        :param private_key: None, `DISCARD`, or a private key blob

        :param detach: if True, tell Tor to NOT associate this control
            connection with the lifetime of the created service

        :param version: 2 or 3, which kind of service to create

        :param progress: a callable taking (percent, tag, description)
            that is called periodically to report progress.

        :param await_all_uploads: if True, the Deferred only fires
            after ALL descriptor uploads have completed (otherwise, it
            fires when at least one has completed).

        :param single_hop: if True, pass the `NonAnonymous` flag. Note
            that Tor options `HiddenServiceSingleHopMode`,
            `HiddenServiceNonAnonymousMode` must be set to `1` and there
            must be no `SOCKSPort` configured for this to actually work.

        See also :meth:`txtorcon.Tor.create_onion_service` (which
        ultimately calls this).
        """
        if not isinstance(auth, (AuthBasic, AuthStealth)):
            raise ValueError(
                "'auth' should be an AuthBasic or AuthStealth instance"
            )

        if isinstance(auth, AuthStealth):
            raise ValueError(
                "Tor does not yet support ephemeral stealth-auth"
            )

        version = 2 if version is None else version
        assert version in (2, 3)

        processed_ports = yield _validate_ports(reactor, ports)

        onion = EphemeralAuthenticatedOnionService(
            config, processed_ports,
            private_key=private_key,
            detach=detach,
            version=version,
            single_hop=single_hop,
        )
        yield _add_ephemeral_service(config, onion, progress, version, auth, await_all_uploads)

        defer.returnValue(onion)

    def __init__(self, config, ports, hostname=None, private_key=None, auth=[], version=3,
                 detach=False, single_hop=None):
        """
        Users should create instances of this class by using the async
        method :meth:`txtorcon.EphemeralAuthenticatedOnionService.create`
        """

        _validate_ports_low_level(ports)

        self._config = config
        self._ports = ports
        self._hostname = hostname
        self._private_key = private_key
        self._version = version
        self._detach = detach
        self._clients = dict()
        self._single_hop = single_hop

    def get_permanent_id(self):
        """
        IAuthenticatedOnionClients API
        """
        assert '\n' not in self._private_key
        # why are we sometimes putting e.g. "RSA1024:xxxx" and
        # sometimes not? Should be one or the other
        if ':' in self._private_key:
            blob = self._private_key.split(':')[1].encode('ascii')
        else:
            blob = self._private_key.encode('ascii')
        keydata = b'-----BEGIN RSA PRIVATE KEY-----\n' + blob + b'\n-----END RSA PRIVATE KEY-----\n'
        # keydata = b'-----BEGIN PRIVATE KEY-----\n' + blob + b'\n-----END PRIVATE KEY-----\n'

        # XXX Hmm, it seems PyPy 5.8.0 *on travis* fails this (on my
        # system it works fine). Only relevant difference I can see is
        # openssl version 1.0.1f vs 1.0.1t
        private_key = serialization.load_pem_private_key(
            keydata,
            password=None,
            backend=default_backend(),
        )
        return _compute_permanent_id(private_key)

    def client_names(self):
        return self._clients.keys()

    def get_client(self, name):
        return self._clients[name]

    def _add_client(self, name, auth_token):
        self._clients[name] = EphemeralAuthenticatedOnionServiceClient(
            parent=self,
            name=name,
            token=auth_token,
        )

    @property
    def hostname(self):
        return self._hostname

    @property
    def ports(self):
        return set(self._ports)

    @property
    def version(self):
        return self._version

    @property
    def private_key(self):
        return self._private_key

    @defer.inlineCallbacks
    def remove(self):
        """
        Issues a DEL_ONION call to our tor, removing this service.
        """
        cmd = 'DEL_ONION {}'.format(self._hostname[:-len('.onion')])
        res = yield self._config.tor_protocol.queue_command(cmd)
        if res.strip() != "OK":
            raise RuntimeError("Failed to remove service") class AuthStealth(_AuthCommon):
    """
    Authentication details for 'stealth' auth.
    """
    auth_type = 'stealth'
    # note that _AuthCommon.__init__ takes 'clients' class AuthBasic(_AuthCommon):
    """
    Authentication details for 'basic' auth.
    """
    auth_type = 'basic'
    # note that _AuthCommon.__init__ takes 'clients' DISCARD = object() def _validate_ports_low_level(ports):
    """
    Internal helper.

    Validates the 'ports' argument to EphemeralOnionService or
    EphemeralAuthenticatedOnionService returning None on success or
    raising ValueError otherwise.

    This only accepts the "list of strings" variants; some
    higher-level APIs also allow lists of ints or lists of 2-tuples,
    but those must be converted to strings before they get here.
    """
    if not isinstance(ports, (list, tuple)):
        raise ValueError("'ports' must be a list of strings")
    if any([not isinstance(x, (six.text_type, str)) for x in ports]):
        raise ValueError("'ports' must be a list of strings")
    for port in ports:
        _validate_single_port_string(port) class FakeControlProtocol(object):
    """
    This is a little weird, but in most tests the answer at the top of
    the list is sent back immediately in an already-called
    Deferred. However, if the answer list is empty at the time of the
    call, instead the returned Deferred is added to the pending list
    and answer_pending() may be called to have the next Deferred
    fire. (see test_accept_all_postbootstrap for an example).

    It is done this way in case we need to have some other code run
    between the get_conf (or whatever) and the callback -- if the
    Deferred is already-fired when get_conf runs, there's a Very Good
    Chance (always?) that the callback just runs right away.
    """

    def __init__(self, answers):
        self.answers = answers
        self.pending = []
        self.post_bootstrap = defer.succeed(self)
        self.on_disconnect = defer.Deferred()
        self.sets = []
        self.events = {}  #: event type -> callback
        self.pending_events = {}  #: event type -> list
        self.is_owned = -1
        self.version = "0.2.8.0"
        # XXX can we get rud of 'commands' and just use pending?
        self.commands = []

    def queue_command(self, cmd):
        if len(self.answers) == 0:
            d = defer.Deferred()
            self.pending.append(d)
            self.commands.append((cmd, d))
            return d

        d = defer.succeed(self.answers[0])
        self.commands.append((cmd, d))
        self.answers = self.answers[1:]
        return d

    def event_happened(self, event_type, *args):
        '''
        Use this in your tests to send 650 events when an event-listener
        is added.  XXX Also if we've *already* added one? Do that if
        there's a use-case for it
        '''
        if event_type in self.events:
            self.events[event_type](*args)
        elif event_type in self.pending_events:
            self.pending_events[event_type].append(args)
        else:
            self.pending_events[event_type] = [args]

    def answer_pending(self, answer):
        d = self.pending[0]
        self.pending = self.pending[1:]
        d.callback(answer)

    def get_info(self, info):
        a = self.answers.pop()
        return defer.succeed(a)

    def get_info_raw(self, info):
        if len(self.answers) == 0:
            d = defer.Deferred()
            self.pending.append(d)
            return d

        d = defer.succeed(self.answers[0])
        self.answers = self.answers[1:]
        return d

    @defer.inlineCallbacks
    def get_info_incremental(self, info, cb):
        text = yield self.get_info_raw(info)
        for line in text.split('\r\n'):
            cb(line)
        defer.returnValue('')  # FIXME uh....what's up at torstate.py:350?

    def get_conf(self, info):
        if len(self.answers) == 0:
            d = defer.Deferred()
            self.pending.append(d)
            return d

        d = defer.succeed(self.answers[0])
        self.answers = self.answers[1:]
        return d

    get_conf_raw = get_conf  # up to test author ensure the answer is a raw string
    get_conf_single = get_conf

    def set_conf(self, *args):
        for i in range(0, len(args), 2):
            self.sets.append((args[i], args[i + 1]))
        return defer.succeed('')

    def add_event_listener(self, nm, cb):
        self.events[nm] = cb
        if nm in self.pending_events:
            for event in self.pending_events[nm]:
                cb(*event)
        return defer.succeed(None)

    def remove_event_listener(self, nm, cb):
        del self.events[nm]
import os
import sys
from mock import Mock
from os.path import join
from unittest import skipIf
from twisted.trial import unittest
from twisted.internet import defer
from txtorcon import TorConfig
from txtorcon import torconfig
from txtorcon.onion import FilesystemOnionService
from txtorcon.onion import FilesystemAuthenticatedOnionService
from txtorcon.onion import EphemeralOnionService
from txtorcon.onion import EphemeralAuthenticatedOnionService
from txtorcon.onion import AuthStealth, AuthBasic, DISCARD
from txtorcon.onion import _validate_ports_low_level
from txtorcon.testutil import FakeControlProtocol
                pass

    def test_add(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"])
        proto = Mock()
        proto.queue_command = Mock(return_value="PrivateKey=blam\nServiceID=ohai")
        eph.add_to_tor(proto)

        self.assertEqual("blam", eph.private_key)
        self.assertEqual("ohai.onion", eph.hostname)

    def test_add_keyblob(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"], "alg:blam")
        proto = Mock()
        proto.queue_command = Mock(return_value="ServiceID=ohai")
        eph.add_to_tor(proto)

        self.assertEqual("alg:blam", eph.private_key)
        self.assertEqual("ohai.onion", eph.hostname)

    def test_descriptor_wait(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"])
        proto = Mock()
        proto.queue_command = Mock(return_value=defer.succeed("PrivateKey=blam\nServiceID=ohai\n"))

        eph.add_to_tor(proto)

        # get the event-listener callback that torconfig code added;
        # the first call [0] was to add_event_listener; we want the
        # [1] arg of that
        cb = proto.method_calls[0][1][1]

        # Tor doesn't actually provide the .onion, but we can test it anyway
        cb('UPLOADED ohai UNKNOWN somehsdir')
        cb('UPLOADED UNKNOWN UNKNOWN somehsdir')

        self.assertEqual("blam", eph.private_key)
        self.assertEqual("ohai.onion", eph.hostname)

    def test_remove(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"])
        eph.hostname = 'foo.onion'
        proto = Mock()
        proto.queue_command = Mock(return_value="OK")

        eph.remove_from_tor(proto)

    @defer.inlineCallbacks
    def test_remove_error(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"])
        eph.hostname = 'foo.onion'
        proto = Mock()
        proto.queue_command = Mock(return_value="it's not ok")

        try:
            yield eph.remove_from_tor(proto)
            self.fail("should have gotten exception")
        except RuntimeError:
            pass

    def test_failed_upload(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"])
        proto = Mock()
        proto.queue_command = Mock(return_value=defer.succeed("PrivateKey=seekrit\nServiceID=42\n"))

        d = eph.add_to_tor(proto)

        # get the event-listener callback that torconfig code added;
        # the first call [0] was to add_event_listener; we want the
        # [1] arg of that
        cb = proto.method_calls[0][1][1]

        # Tor leads with UPLOAD events for each attempt; we queue 2 of
        # these...
        cb('UPLOAD 42 UNKNOWN hsdir0')
        cb('UPLOAD 42 UNKNOWN hsdir1')

        # ...but fail them both
        cb('FAILED 42 UNKNOWN hsdir1 REASON=UPLOAD_REJECTED')
        cb('FAILED 42 UNKNOWN hsdir0 REASON=UPLOAD_REJECTED')

        self.assertEqual("seekrit", eph.private_key)
        self.assertEqual("42.onion", eph.hostname)
        self.assertTrue(d.called)
        d.addErrback(lambda e: self.assertTrue('Failed to upload' in str(e)))

    def test_single_failed_upload(self):
        eph = torconfig.EphemeralHiddenService(["80 127.0.0.1:80"])
        proto = Mock()
        proto.queue_command = Mock(return_value=defer.succeed("PrivateKey=seekrit\nServiceID=42\n"))

        d = eph.add_to_tor(proto)

        # get the event-listener callback that torconfig code added;
        # the first call [0] was to add_event_listener; we want the
        # [1] arg of that
        cb = proto.method_calls[0][1][1]

        # Tor leads with UPLOAD events for each attempt; we queue 2 of
        # these...
        cb('UPLOAD 42 UNKNOWN hsdir0')
        cb('UPLOAD 42 UNKNOWN hsdir1')

        # ...then fail one
        cb('FAILED 42 UNKNOWN hsdir1 REASON=UPLOAD_REJECTED')
        # ...and succeed on the last.
        cb('UPLOADED 42 UNKNOWN hsdir0')

        self.assertEqual("seekrit", eph.private_key)
        self.assertEqual("42.onion", eph.hostname)
        self.assertTrue(d.called)


class AuthenticatedFilesystemHiddenServiceTest(unittest.TestCase):

    def setUp(self):
        self.thedir = self.mktemp()
        os.mkdir(self.thedir)
        protocol = FakeControlProtocol([])
        self.config = TorConfig(protocol)

        self.hs = FilesystemAuthenticatedOnionService(