Ejemplo n.º 1
0
class VByteChannel(object):
    """Producer/consumer end-point for byte data.

    :param reactor:  reactor driving the socket's event handling
    :param buf_len:  buffer length for read operations
    :type  buf_len:  int

    This class is primarily intended for debugging byte producer/consumer
    I/O chains.

    """
    def __init__(self, reactor, buf_len=4096):
        self.__reactor = reactor
        self.__buf_len = buf_len

        self.__bc_consumed = 0
        self.__bc_consume_lim = 0
        self.__bc_producer = None
        self.__bc_eod = False
        self.__bc_eod_clean = None
        self.__bc_rbuf = VByteBuffer()
        self.__bc_rbuf_len = buf_len
        self.__bc_reader = None
        self.__bc_aborted = False
        self.__bc_cond = Condition()
        self.__bc_scheduled_lim_update = False

        self.__bp_produced = 0
        self.__bp_produce_lim = 0
        self.__bp_consumer = None
        self.__bp_eod = False
        self.__bp_eod_clean = None
        self.__bp_wbuf = VByteBuffer()
        self.__bp_wbuf_len = buf_len
        self.__bp_writer = None
        self.__bp_sent_eod = False
        self.__bp_aborted = False
        self.__bp_cond = Condition()
        self.__bp_scheduled_produce = False

        self.__bc_iface = self.__bp_iface = None

        # Set up a local logger for convenience
        self.__logger = VLogger(prefix='ByteChannel')
        self.__logger.add_watcher(self.reactor.log)

    def __del__(self):
        self.__logger.debug('Dereferenced')

    def recv(self, max_read, timeout=None):
        """Receive input data from byte channel.

        :param max_read: max bytes to read (unlimited if None)
        :type  max_read: int
        :param timeout:  timeout in seconds (blocking if None)
        :type  timeout:  float
        :returns:        data read (empty if input was closed)
        :rtype:          bytes
        :raises:         :exc:`versile.reactor.io.VIOTimeout`\ ,
                         :exc:`versile.reactor.io.VIOError`

        """
        if timeout:
            start_time = time.time()
        with self.__bc_cond:
            while True:
                if self.__bc_rbuf:
                    if max_read is None:
                        result = self.__bc_rbuf.pop()
                    elif max_read > 0:
                        result = self.__bc_rbuf.pop(max_read)
                    else:
                        result = b''

                    # Trigger updating can_produce in reactor thread
                    if not self.__bc_scheduled_lim_update:
                        self.__bc_scheduled_lim_update = True
                        self.reactor.schedule(0.0, self.__bc_lim_update)

                    return result
                elif self.__bc_aborted:
                    raise VIOError('Byte input was aborted')
                elif self.__bc_eod:
                    if self.__bc_eod_clean:
                        return b''
                    else:
                        raise VIOError('Byte input was closed but not cleanly')

                if timeout == 0.0:
                    raise VIOTimeout()
                elif timeout is not None and timeout > 0.0:
                    current_time = time.time()
                    if current_time > start_time + timeout:
                        raise VIOTimeout()
                    wait_time = start_time + timeout - current_time
                    self.__bc_cond.wait(wait_time)
                else:
                    self.__bc_cond.wait()

    def send(self, data, timeout=None):
        """Receive input data from byte channel.

        :param data:     data to write
        :type  data:     bytes
        :type  max_read: int
        :param timeout:  timeout in seconds (blocking if None)
        :type  timeout:  float
        :returns:        number bytes written
        :rtype:          int
        :raises:         :exc:`versile.reactor.io.VIOTimeout`\ ,
                         :exc:`versile.reactor.io.VIOError`

        """
        if timeout:
            start_time = time.time()
        with self.__bp_cond:
            while True:
                if self.__bp_aborted:
                    raise VIOError('Byte output was aborted')
                elif self.__bp_eod:
                    raise VIOError('Byte output was closed')
                if not data:
                    return 0
                max_write = self.__bp_wbuf_len - len(self.__bp_wbuf)
                if max_write > 0:
                    write_data = data[:max_write]
                    self.__bp_wbuf.append(write_data)

                    # Trigger reactor production
                    if not self.__bp_scheduled_produce:
                        self.__bp_scheduled_produce = True
                        self.reactor.schedule(0.0, self.__bp_do_produce)

                    return len(write_data)

                if timeout == 0.0:
                    raise VIOTimeout()
                elif timeout is not None and timeout > 0.0:
                    current_time = time.time()
                    if current_time > start_time + timeout:
                        raise VIOTimeout()
                    wait_time = start_time + timeout - current_time
                    self.__bc_cond.wait(wait_time)
                else:
                    self.__bc_cond.wait()

    def close(self):
        """Closes the connection."""
        def _close():
            if not self.__bp_aborted and not self.__bp_eod:
                self.__bp_eod = True
                self.__bp_eod_clean = True
                self.__bp_do_produce()
            if not self.__bc_aborted and not self.__bc_eod:
                self.__bc_eod = True
                self.__bc_eod_clean = True

        self.reactor.schedule(0.0, _close)

    def abort(self):
        """Aborts the connection."""
        def _abort():
            self._bc_abort()
            self._bp_abort()

        self.reactor.schedule(0.0, _abort)

    @property
    def byte_consume(self):
        """Holds the Byte Consumer interface to the serializer."""
        cons = None
        if self.__bc_iface:
            cons = self.__bc_iface()
        if not cons:
            cons = _VByteConsumer(self)
            self.__bc_iface = weakref.ref(cons)
        return cons

    @property
    def byte_produce(self):
        """Holds the Byte Producer interface to the serializer."""
        prod = None
        if self.__bp_iface:
            prod = self.__bp_iface()
        if not prod:
            prod = _VByteProducer(self)
            self.__bp_iface = weakref.ref(prod)
        return prod

    @property
    def byte_io(self):
        """Byte interface (\ :class:`versile.reactor.io.VByteIOPair`\ )."""
        return VByteIOPair(self.byte_consume, self.byte_produce)

    @property
    def reactor(self):
        """Holds the object's reactor."""
        return self.__reactor

    @peer
    def _bc_consume(self, data, clim):
        if self.__bc_eod:
            raise VIOError('Consumer already received end-of-data')
        elif not self._bc_producer:
            raise VIOError('No connected producer')
        elif not data:
            raise VIOError('No data to consume')
        max_cons = self.__lim(self.__bc_consumed, self.__bc_consume_lim)
        if max_cons == 0:
            raise VIOError('Consume limit exceeded')
        if clim is not None and clim > 0:
            max_cons = min(max_cons, clim)

        with self.__bc_cond:
            buf_len = len(self.__bc_rbuf)
            self.__bc_rbuf.append_list(data.pop_list(max_cons))
            self.__bc_consumed += len(self.__bc_rbuf) - buf_len

            # Update consume limit
            max_add = self.__lim(len(self.__bc_rbuf), self.__bc_rbuf_len)
            if max_add >= 0:
                self.__bc_consume_lim = self.__bc_consumed + max_add
            else:
                self.__bc_consume_lim = -1

            # Notify data is available
            self.__bc_cond.notify_all()

        return self.__bc_consume_lim

    @peer
    def _bc_end_consume(self, clean):
        if self.__bc_eod:
            return
        self.__bc_eod = True
        self.__bc_eod_clean = clean

        with self.__bc_cond:
            self.__bc_cond.notify_all()

    def _bc_abort(self):
        if not self.__bc_aborted:
            with self.__bc_cond:
                self.__bc_aborted = True
                self.__bc_eod = True
                if self.__bc_producer:
                    self.__bc_producer.abort()
                    self._bc_detach()
                self.__bc_cond.notify_all()

    def _bc_attach(self, producer, rthread=False):
        # Ensure 'attach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._bc_attach, producer, rthread=True)
            return

        if self.__bc_producer is producer:
            return
        if self.__bc_eod:
            raise VIOError('Consumer already received end-of-data')
        elif self.__bc_producer:
            raise VIOError('Producer already connected')
        self.__bc_producer = producer
        self.__bc_consumed = 0
        self.__bc_consume_lim = self.__lim(len(self.__bc_rbuf),
                                           self.__bc_rbuf_len)
        producer.attach(self.byte_consume)
        producer.can_produce(self.__bc_consume_lim)

        # Notify attached chain
        try:
            producer.control.notify_consumer_attached(self.byte_consume)
        except VIOMissingControl:
            pass

    def _bc_detach(self, rthread=False):
        # Ensure 'detach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._bc_detach, rthread=True)
            return

        if self.__bc_producer:
            prod, self.__bc_producer = self.__bc_producer, None
            self.__bc_consumed = self.__bc_consume_lim = 0
            prod.detach()

    @peer
    def _bp_can_produce(self, limit):
        if not self.__bp_consumer:
            raise VIOError('No attached consumer')
        if limit is None or limit < 0:
            if (not self.__bp_produce_lim is None
                    and not self.__bp_produce_lim < 0):
                self.__bp_produce_lim = limit
                if not self.__bp_scheduled_produce:
                    self.__bp_scheduled_produce = True
                    self.reactor.schedule(0.0, self.__bp_do_produce)
        else:
            if (self.__bp_produce_lim is not None
                    and 0 <= self.__bp_produce_lim < limit):
                self.__bp_produce_lim = limit
                if not self.__bp_scheduled_produce:
                    self.__bp_scheduled_produce = True
                    self.reactor.schedule(0.0, self.__bp_do_produce)

    def _bp_abort(self):
        if not self.__bp_aborted:
            with self.__bp_cond:
                self.__bp_aborted = True
                self.__bp_wbuf.remove()
                if self.__bp_consumer:
                    self.__bp_consumer.abort()
                    self._bp_detach()
                self.__bp_cond.notify_all()

    def _bp_attach(self, consumer, rthread=False):
        # Ensure 'attach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._bp_attach, consumer, rthread=True)
            return

        if self.__bp_consumer is consumer:
            return
        if self.__bp_consumer:
            raise VIOError('Consumer already attached')
        elif self.__bp_eod:
            raise VIOError('Producer already reached end-of-data')
        self.__bp_consumer = consumer
        self.__bp_produced = self.__bp_produce_lim = 0
        consumer.attach(self.byte_produce)

        # Notify attached chain
        try:
            consumer.control.notify_producer_attached(self.byte_produce)
        except VIOMissingControl:
            pass

    def _bp_detach(self, rthread=False):
        # Ensure 'detach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._bp_detach, rthread=True)
            return

        if self.__bp_consumer:
            cons, self.__bp_consumer = self.__bp_consumer, None
            cons.detach()
            self.__bp_produced = self.__bp_produce_lim = 0

    @property
    def _bc_control(self):
        return VIOControl()

    @property
    def _bc_producer(self):
        return self.__bc_producer

    @property
    def _bc_flows(self):
        return (self.entity_produce, )

    @property
    def _bc_twoway(self):
        return True

    @property
    def _bc_reverse(self):
        return self.byte_produce

    @property
    def _bp_control(self):
        return VIOControl()

    @property
    def _bp_consumer(self):
        return self.__bp_consumer

    @property
    def _bp_flows(self):
        return (self.entity_consume, )

    @property
    def _bp_twoway(self):
        return True

    @property
    def _bp_reverse(self):
        return self.byte_consume

    def __bc_lim_update(self):
        self.__bc_scheduled_lim_update = False

        if not self.__bc_producer or self.__bc_aborted or self.__bc_eod:
            return

        old_lim = self.__bc_consume_lim
        self.__bc_consume_lim = self.__lim(len(self.__bc_rbuf),
                                           self.__bc_rbuf_len)
        if old_lim != self.__bc_consume_lim:
            self.__bc_producer.can_produce(self.__bc_consume_lim)

    def __bp_do_produce(self):
        self.__bp_scheduled_produce = False

        if not self.__bp_consumer:
            return

        with self.__bp_cond:
            if self.__bp_eod:
                # If end-of-data was reached notify consumer
                if self.__bp_consumer and not self.__bp_sent_eod:
                    self.__bp_consumer.end_consume(self.__bp_eod_clean)
                    self.__bp_sent_eod = True
                return

            if (self.__bp_produce_lim is not None
                    and 0 <= self.__bp_produce_lim <= self.__bp_produced):
                return

            old_lim = self.__bp_produce_lim
            max_write = self.__lim(self.__bp_produced, self.__bp_produce_lim)
            if max_write != 0 and self.__bp_wbuf:
                old_len = len(self.__bp_wbuf)
                new_lim = self.__bp_consumer.consume(self.__bp_wbuf)
                self.__bp_produced += self.__bp_wbuf_len - len(self.__bp_wbuf)
                self.__bp_produce_lim = new_lim
                if old_len != len(self.__bp_wbuf):
                    self.__bp_cond.notify_all()

            # Schedule another if produce limit was updated and buffer has data
            if self.__bp_wbuf and self.__bp_produce_lim != old_lim:
                if not self.__bp_scheduled_produce:
                    self.__bp_scheduled_produce = True
                    self.reactor.schedule(0.0, self.__bp_do_produce)

    @classmethod
    def __lim(self, base, *lims):
        """Return smallest (lim-base) limit, or -1 if all limits are <0"""
        result = -1
        for lim in lims:
            if lim is not None and lim >= 0:
                lim = max(lim - base, 0)
                if result < 0:
                    result = lim
                result = min(result, lim)
        return result
Ejemplo n.º 2
0
class VOPBridge(object):
    """A reactor interface for the :term:`VOP` protocol.

    Handles :term:`VOP` handshake and setup of a :term:`VOL` link,
    negotiating a byte transport for the connection.

    .. note::

        The :term:`VOP` specification states that each side of a
        :term:`VOP` connection takes either the role of \"client\" or
        \"server\"\ . The classes :class:`VOPClientBridge` and
        :class:`VOPServerBridge` provide interfaces for the respective
        roles. The :class:`VOPBridge` class is abstract and should not
        be directly instantiated.

    :param reactor:  channel reactor
    :param vec:      :term:`VEC` channel for link
    :type  vec:      :class:`versile.reactor.io.VByteIOPair`
    :param vts:      factory for :term:`VTS` transport (or None)
    :type  vts:      callable
    :param tls:      factory for :term:`TLS` transport (or None)
    :type  tls:      callable
    :param insecure: if True allow unencrypted connections
    :type  insecure: boolean
    :raises:         :exc:`versile.reactor.io.VIOException`

    *vec* should be a byte I/O pair which will be connected to the
    internal (plaintext) side of the protocol's negotiated byte
    transport mechanism.

    The *vts* and *tls* arguments are functions which produce byte
    transports for the corresponding protocols. If not None then that
    transport is enabled for the :term:`VOP` handshake.

    *vts* and *tls* should take a reactor as an argument and return a
    4-tuple (transport_consumer, transport_producer, vec_consumer,
    vec_producer) where each consumer is a
    :class:`versile.reactor.io.VByteConsumer` and each producer is a
    :class:`versile.reactor.io.VByteProducer`\ . The first two
    elements are the external transport connecters and the last two
    elements are the internal connecters for serialized :term:`VEC`
    data.

    """
    def __init__(self, reactor, vec, vts=None, tls=None, insecure=False):
        self.__reactor = reactor

        self._vec_consumer = vec.consumer
        self._vec_producer = vec.producer

        if not (vts or tls or insecure):
            raise VIOException('No transports enabled')

        self._vts_factory = vts
        self._tls_factory = tls
        self._allow_insecure = insecure

        self._handshaking = True
        self._handshake_error = False
        self._handshake_consumed = 0
        self._handshake_produced = 0

        self.__tc_producer = None
        self._tc_cons_lim = 0

        self.__tp_consumer = None
        self._tp_prod_lim = 0

        self.__ec_producer = None
        self._ec_cons_lim = 0

        self.__ep_consumer = None
        self._ep_prod_lim = 0

        self.__tc_iface = self.__tp_iface = None
        self.__ec_iface = self.__ep_iface = None

        # Set up a local logger for convenience
        self._logger = VLogger(prefix='VOP')
        self._logger.add_watcher(self.reactor.log)

    def __del__(self):
        #self._logger.debug('Dereferenced')
        pass

    @property
    def external_consume(self):
        """Holds an external interface to a :term:`VOP` protocol consumer."""
        cons = None
        if self.__ec_iface:
            cons = self.__ec_iface()
        if not cons:
            cons = _VExternalConsumer(self)
            self.__ec_iface = weakref.ref(cons)
        return cons

    @property
    def external_produce(self):
        """Holds an external interface to a :term:`VOP` protocol producer."""
        prod = None
        if self.__ep_iface:
            prod = self.__ep_iface()
        if not prod:
            prod = _VExternalProducer(self)
            self.__ep_iface = weakref.ref(prod)
        return prod

    @property
    def external_io(self):
        """External I/O (\ :class:`versile.reactor.io.VByteIOPair`\ )."""
        return VByteIOPair(self.external_consume, self.external_produce)

    @property
    def reactor(self):
        """The object's reactor.

        See :class:`versile.reactor.IVReactorObject`

        """
        return self.__reactor

    @property
    def _transport_consume(self):
        """Holds an internal interface to the external consumer."""
        cons = None
        if self.__tc_iface:
            cons = self.__tc_iface()
        if not cons:
            cons = _VTransportConsumer(self)
            self.__tc_iface = weakref.ref(cons)
        return cons

    @property
    def _transport_produce(self):
        """Holds an internal interface to the external producer."""
        prod = None
        if self.__tp_iface:
            prod = self.__tp_iface()
        if not prod:
            prod = _VTransportProducer(self)
            self.__tp_iface = weakref.ref(prod)
        return prod

    @property
    def _transport_io(self):
        """Transport I/O (\ :class:`versile.reactor.io.VByteIOPair`\ )."""
        return VByteIOPair(self._transport_consume, self._transport_produce)

    @peer
    def _tc_consume(self, data, clim):
        if not self._tc_producer:
            raise VIOError('No connected producer')
        elif not data:
            raise VIOError('No data to consume')
        elif self._handshake_error:
            raise VIOError('Error during handshaking')

        if self._handshaking:
            raise VIOError('Handshaking not completed')

        if self._ep_consumer:
            _lim = self._ep_consumer.consume(data, clim)
            self._ep_prod_lim = _lim
            if _lim >= 0:
                _lim = max(_lim - self._handshake_produced, 0)
            self._tc_cons_lim = _lim

        return self._tc_cons_lim

    @peer
    def _tc_end_consume(self, clean):
        if self._handshake_error:
            return

        if self._handshaking:
            self._handshake_abort()
        else:
            if self._ep_consumer:
                return self._ep_consumer.end_consume(clean)
            else:
                self._tc_abort()

    def _tc_abort(self):
        if self._handshaking and not self._handshake_error:
            self._handshake_abort()
        else:
            if self._ep_consumer:
                self._ep_consumer.abort()
                self._ep_detach()
            if self._tc_producer:
                self._tc_producer.abort()
                self._tc_detach()

    def _tc_attach(self, producer, rthread=False):
        # Ensure 'attach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._tc_attach, producer, rthread=True)
            return

        if self._handshake_error:
            raise VIOError('Earlier error during handshaking')
        elif self._tc_producer is producer:
            return
        elif self._tc_producer:
            raise VIOError('Producer already connected')

        self.__tc_producer = producer
        self._tc_cons_lim = 0
        producer.attach(self._transport_consume)

        if not self._handshaking:
            _lim = self._ec_cons_lim
            if _lim >= 0:
                _lim -= self._handshake_consumed
            producer.can_produce(_lim)

        try:
            producer.control.notify_consumer_attached(self._transport_consume)
        except VIOMissingControl:
            pass

    def _tc_detach(self, rthread=False):
        # Ensure 'detach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._tc_detach, rthread=True)
            return

        if self.__tc_producer:
            prod, self.__tc_producer = self.__tc_producer, None
            self._tc_cons_lim = 0
            prod.detach()

    @peer
    def _tp_can_produce(self, limit):
        if self._handshake_error:
            raise VIOError('Earlier error during handshaking')
        elif not self._tp_consumer:
            raise VIOError('No attached consumer')

        self._tp_prod_lim = limit

        if not self._handshaking and self._ec_producer:
            if limit >= 0:
                limit += self._handshake_consumed
            self._ec_producer.can_produce(limit)

    def _tp_abort(self):
        self._ec_abort()

    def _tp_attach(self, consumer, rthread=False):
        # Ensure 'attach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._tp_attach, consumer, rthread=True)
            return

        if self._handshake_error:
            raise VIOError('Earlier error during handshaking')
        elif self._tp_consumer is consumer:
            return
        elif self._tp_consumer:
            raise VIOError('Consumer already attached')

        self.__tp_consumer = consumer
        self._tp_prod_lim = 0
        consumer.attach(self._transport_produce)

        try:
            consumer.control.notify_producer_attached(self._transport_produce)
        except VIOMissingControl:
            pass

    def _tp_detach(self, rthread=False):
        # Ensure 'detach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._tp_detach, rthread=True)
            return

        if self.__tp_consumer:
            cons, self.__tp_consumer = self.__tp_consumer, None
            cons.detach()
            self._tp_prod_lim = 0

    @peer
    def _ec_consume(self, data, clim):
        if not self._ec_producer:
            raise VIOError('No connected external producer')
        elif not data:
            raise VIOError('No data to consume')
        elif self._handshake_error:
            raise VIOError('Earlier handshake error')

        # Handle handshake
        if self._handshaking:
            _len = len(data)
            self._handshake_consume(data, clim)
            if clim is not None:
                clim -= _len - len(data)

        # Handle post-handshake pass-through to transport
        if (not self._handshaking and self._tp_consumer and data
                and (clim is None or clim > 0)):
            _lim = self._tp_consumer.consume(data, clim)
            if _lim >= 0:
                _lim += self._handshake_consumed
            self._ec_cons_lim = _lim

        return self._ec_cons_lim

    @peer
    def _ec_end_consume(self, clean):
        if self._handshake_error:
            return

        if self._handshaking:
            self._handshake_abort()
        else:
            if self._tp_consumer:
                self._tp_consumer.end_consume(clean)

    def _ec_abort(self):
        if self._handshaking and not self._handshake_error:
            self._handshake_abort()
        else:
            if self._tp_consumer:
                self._tp_consumer.abort()
                self._tp_detach()
            if self._ec_producer:
                self._ec_producer.abort()
                self._ec_detach()

    def _ec_attach(self, producer, rthread=False):
        # Ensure 'attach' is performed from reactor thread
        if not rthread:
            self.reactor.execute(self._ec_attach, producer, rthread=True)
            return

        if self._handshake_error:
            raise VIOError('Earlier error during handshaking')
        elif self._ec_producer is producer:
            return
        elif self._ec_producer:
            raise VIOError('Producer already connected')

        self.__ec_producer = producer
        self._ec_cons_lim = 0
        producer.attach(self.external_consume)

        try:
            producer.control.notify_consumer_attached(self.external_consume)
        except VIOMissingControl:
            pass

        # Trigger any handshake actions
        self._handshake_producer_attached()

    def _ec_detach(self, rthread=False):
        # Ensure 'detach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._ec_detach, rthread=True)
            return

        if self.__ec_producer:
            prod, self.__ec_producer = self.__ec_producer, None
            self._ec_cons_lim = 0
            prod.detach()

    @peer
    def _ep_can_produce(self, limit):
        if self._handshake_error:
            raise VIOError('Earlier error during handshaking')
        elif not self._ep_consumer:
            raise VIOError('No attached consumer')

        self._ep_prod_lim = limit

        if self._handshaking:
            self._handshake_can_produce()
        else:
            if self._tc_producer:
                if limit >= 0:
                    limit = max(limit - self._handshake_produced, 0)
                self._tc_cons_lim = limit
                self._tc_producer.can_produce(limit)

    def _ep_abort(self):
        self._tc_abort()

    def _ep_attach(self, consumer, rthread=False):
        # Ensure 'attach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._ep_attach, consumer, rthread=True)
            return

        if self._handshake_error:
            raise VIOError('Earlier error during handshaking')
        elif self._ep_consumer is consumer:
            return
        elif self._ep_consumer:
            raise VIOError('Consumer already attached')

        self.__ep_consumer = consumer
        self._ep_prod_lim = 0
        consumer.attach(self.external_produce)

        try:
            consumer.control.notify_producer_attached(self.external_produce)
        except VIOMissingControl:
            pass

    def _ep_detach(self, rthread=False):
        # Ensure 'detach' is performed in reactor thread
        if not rthread:
            self.reactor.execute(self._ep_detach, rthread=True)
            return

        if self.__ep_consumer:
            cons, self.__ep_consumer = self.__ep_consumer, None
            cons.detach()
            self._ep_prod_lim = 0

    def _handshake_abort(self):
        if not self._handshake_error:
            self._logger.debug('Aborting')
            self._handshaking = False
            self._handshake_error = True
            self._ec_abort()
            self._ep_abort()

            # Abort VEC chain
            if self._vec_consumer:
                self._vec_consumer.abort()
            if self._vec_producer:
                self._vec_producer.abort()

            # Free up any held resources
            self._vec_consumer = None
            self._vec_producer = None
            self._vts_factory = None
            self._tls_factory = None

    @abstract
    def _handshake_producer_attached(self):
        """Notification handshake producer was attached."""
        raise NotImplementedError()

    @abstract
    def _handshake_consume(self, data, clim):
        """Consume handshake data."""
        raise NotImplementedError()

    @abstract
    def _handshake_can_produce(self):
        """Process can_produce during handshake."""
        raise NotImplementedError()

    def _handshake_complete(self, factory):
        """Finalizes handshake after sending/receiving hello messages."""
        self._handshaking = False

        # Initiate transport communication
        if (factory is None):
            # Plaintext transport
            self._tc_attach(self._vec_producer, True)
            self._tp_attach(self._vec_consumer, True)
        else:
            # Secure transport
            ext_cons, ext_prod, int_cons, int_prod = factory(self.reactor)
            self._tc_attach(ext_prod, True)
            self._tp_attach(ext_cons, True)
            int_cons.attach(self._vec_producer)
            int_prod.attach(self._vec_consumer)

        # Dereference any resouces held for the handshake
        self._vec_consumer = None
        self._vec_producer = None
        self._vts_factory = None
        self._tls_factory = None

        self._logger.debug('Completed handshake')

    @property
    def _tc_control(self):
        if self._ep_consumer:
            return self._ep_consumer.control
        else:
            return VIOControl()

    @property
    def _tc_producer(self):
        return self.__tc_producer

    @property
    def _tc_flows(self):
        return (self.external_produce, )

    @property
    def _tp_control(self):
        if self._ec_producer:
            return self._ec_producer.control
        else:
            return VIOControl()

    @property
    def _tc_twoway(self):
        return True

    @property
    def _tc_reverse(self):
        return self.transport_produce

    @property
    def _tp_consumer(self):
        return self.__tp_consumer

    @property
    def _tp_flows(self):
        return (self.external_consume, )

    @property
    def _tp_twoway(self):
        return True

    @property
    def _tp_reverse(self):
        return self.transport_consume

    @property
    def _ec_control(self):
        if self._tp_consumer:
            return self._tp_consumer.control
        else:
            return VIOControl()

    @property
    def _ec_producer(self):
        return self.__ec_producer

    @property
    def _ec_flows(self):
        return (self.transport_produce, )

    @property
    def _ec_twoway(self):
        return True

    @property
    def _ec_reverse(self):
        return self.external_produce

    @property
    def _ep_control(self):
        if self._tc_producer:
            return self._tc_producer.control
        else:
            return VIOControl()

    @property
    def _ep_consumer(self):
        return self.__ep_consumer

    @property
    def _ep_flows(self):
        return (self.transport_consume, )

    @property
    def _ep_twoway(self):
        return True

    @property
    def _ep_reverse(self):
        return self.external_consume