/** Starts this <tt>PacketTransformer</tt>. */
  private synchronized void start() {
    if (this.datagramTransport != null) {
      if (this.connectThread == null && dtlsTransport == null) {
        logger.warn(
            getClass().getName()
                + " has been started but has failed to establish"
                + " the DTLS connection!");
      }
      return;
    }

    if (rtcpmux && Component.RTCP == componentID) {
      // In the case of rtcp-mux, the RTCP transformer does not create
      // a DTLS session. The SRTP context (_srtpTransformer) will be
      // initialized on demand using initializeSRTCPTransformerFromRtp().
      return;
    }

    AbstractRTPConnector connector = this.connector;

    if (connector == null) throw new NullPointerException("connector");

    DtlsControl.Setup setup = this.setup;
    SecureRandom secureRandom = DtlsControlImpl.createSecureRandom();
    final DTLSProtocol dtlsProtocolObj;
    final TlsPeer tlsPeer;

    if (DtlsControl.Setup.ACTIVE.equals(setup)) {
      dtlsProtocolObj = new DTLSClientProtocol(secureRandom);
      tlsPeer = new TlsClientImpl(this);
    } else {
      dtlsProtocolObj = new DTLSServerProtocol(secureRandom);
      tlsPeer = new TlsServerImpl(this);
    }
    tlsPeerHasRaisedCloseNotifyWarning = false;

    final DatagramTransportImpl datagramTransport = new DatagramTransportImpl(componentID);

    datagramTransport.setConnector(connector);

    Thread connectThread =
        new Thread() {
          @Override
          public void run() {
            try {
              runInConnectThread(dtlsProtocolObj, tlsPeer, datagramTransport);
            } finally {
              if (Thread.currentThread().equals(DtlsPacketTransformer.this.connectThread)) {
                DtlsPacketTransformer.this.connectThread = null;
              }
            }
          }
        };

    connectThread.setDaemon(true);
    connectThread.setName(DtlsPacketTransformer.class.getName() + ".connectThread");

    this.connectThread = connectThread;
    this.datagramTransport = datagramTransport;

    boolean started = false;

    try {
      connectThread.start();
      started = true;
    } finally {
      if (!started) {
        if (connectThread.equals(this.connectThread)) this.connectThread = null;
        if (datagramTransport.equals(this.datagramTransport)) this.datagramTransport = null;
      }
    }

    notifyAll();
  }
  /**
   * Gathers Jingle Nodes candidates for all host <tt>Candidate</tt>s that are already present in
   * the specified <tt>component</tt>. This method relies on the specified <tt>component</tt> to
   * already contain all its host candidates so that it would resolve them.
   *
   * @param component the {@link Component} that we'd like to gather candidate Jingle Nodes
   *     <tt>Candidate</tt>s for
   * @return the <tt>LocalCandidate</tt>s gathered by this <tt>CandidateHarvester</tt>
   */
  @Override
  public synchronized Collection<LocalCandidate> harvest(Component component) {
    logger.info("harvest Jingle Nodes");

    Collection<LocalCandidate> candidates = new HashSet<LocalCandidate>();
    String ip = null;
    int port = -1;

    /* if we have already a candidate (RTCP) allocated, get it */
    if (localAddressSecond != null && relayedAddressSecond != null) {
      LocalCandidate candidate =
          createJingleNodesCandidate(relayedAddressSecond, component, localAddressSecond);

      // try to add the candidate to the component and then only add it to
      // the harvest not redundant (not sure how it could be red. but ...)
      if (component.addLocalCandidate(candidate)) {
        candidates.add(candidate);
      }

      localAddressSecond = null;
      relayedAddressSecond = null;
      return candidates;
    }

    XMPPConnection conn = serviceNode.getConnection();
    JingleChannelIQ ciq = null;

    if (serviceNode != null) {
      final TrackerEntry preferred = serviceNode.getPreferedRelay();

      if (preferred != null) {
        ciq = SmackServiceNode.getChannel(conn, preferred.getJid());
      }
    }

    if (ciq != null) {
      ip = ciq.getHost();
      port = ciq.getRemoteport();

      if (logger.isInfoEnabled()) {
        logger.info(
            "JN relay: " + ip + " remote port:" + port + " local port: " + ciq.getLocalport());
      }

      if (ip == null || ciq.getRemoteport() == 0) {
        logger.warn("JN relay ignored because ip was null or port 0");
        return candidates;
      }

      // Drop the scope or interface name if the relay sends it
      // along in its IPv6 address. The scope/ifname is only valid on the
      // host that owns the IP and we don't need it here.
      int scopeIndex = ip.indexOf('%');
      if (scopeIndex > 0) {
        logger.warn("Dropping scope from assumed IPv6 address " + ip);
        ip = ip.substring(0, scopeIndex);
      }

      /* RTP */
      TransportAddress relayedAddress = new TransportAddress(ip, port, Transport.UDP);
      TransportAddress localAddress = new TransportAddress(ip, ciq.getLocalport(), Transport.UDP);

      LocalCandidate local = createJingleNodesCandidate(relayedAddress, component, localAddress);

      /* RTCP */
      relayedAddressSecond = new TransportAddress(ip, port + 1, Transport.UDP);
      localAddressSecond = new TransportAddress(ip, ciq.getLocalport() + 1, Transport.UDP);

      // try to add the candidate to the component and then only add it to
      // the harvest not redundant (not sure how it could be red. but ...)
      if (component.addLocalCandidate(local)) {
        candidates.add(local);
      }
    }

    return candidates;
  }
  /** {@inheritDoc} */
  @Override
  public RawPacket reverseTransform(RawPacket pkt) {
    byte[] buf = pkt.getBuffer();
    int off = pkt.getOffset();
    int len = pkt.getLength();

    if (isDtlsRecord(buf, off, len)) {
      if (rtcpmux && Component.RTCP == componentID) {
        // This should never happen.
        logger.warn(
            "Dropping a DTLS record, because it was received on the"
                + " RTCP channel while rtcpmux is in use.");
        return null;
      }

      boolean receive;

      synchronized (this) {
        if (datagramTransport == null) {
          receive = false;
        } else {
          datagramTransport.queueReceive(buf, off, len);
          receive = true;
        }
      }
      if (receive) {
        DTLSTransport dtlsTransport = this.dtlsTransport;

        if (dtlsTransport == null) {
          // The specified pkt looks like a DTLS record and it has
          // been consumed for the purposes of the secure channel
          // represented by this PacketTransformer.
          pkt = null;
        } else {
          try {
            int receiveLimit = dtlsTransport.getReceiveLimit();
            int delta = receiveLimit - len;

            if (delta > 0) {
              pkt.grow(delta);
              buf = pkt.getBuffer();
              off = pkt.getOffset();
              len = pkt.getLength();
            } else if (delta < 0) {
              pkt.shrink(-delta);
              buf = pkt.getBuffer();
              off = pkt.getOffset();
              len = pkt.getLength();
            }

            int received = dtlsTransport.receive(buf, off, len, DTLS_TRANSPORT_RECEIVE_WAITMILLIS);

            if (received <= 0) {
              // No application data was decoded.
              pkt = null;
            } else {
              delta = len - received;
              if (delta > 0) pkt.shrink(delta);
            }
          } catch (IOException ioe) {
            pkt = null;
            // SrtpControl.start(MediaType) starts its associated
            // TransformEngine. We will use that mediaType to signal
            // the normal stop then as well i.e. we will ignore
            // exception after the procedure to stop this
            // PacketTransformer has begun.
            if (mediaType != null && !tlsPeerHasRaisedCloseNotifyWarning) {
              logger.error("Failed to decode a DTLS record!", ioe);
            }
          }
        }
      } else {
        // The specified pkt looks like a DTLS record but it is
        // unexpected in the current state of the secure channel
        // represented by this PacketTransformer. This PacketTransformer
        // has not been started (successfully) or has been closed.
        pkt = null;
      }
    } else if (transformEngine.isSrtpDisabled()) {
      // In pure DTLS mode only DTLS records pass through.
      pkt = null;
    } else {
      // DTLS-SRTP has not been initialized yet or has failed to
      // initialize.
      SinglePacketTransformer srtpTransformer = waitInitializeAndGetSRTPTransformer();

      if (srtpTransformer != null) pkt = srtpTransformer.reverseTransform(pkt);
      else if (DROP_UNENCRYPTED_PKTS) pkt = null;
      // XXX Else, it is our explicit policy to let the received packet
      // pass through and rely on the SrtpListener to notify the user that
      // the session is not secured.
    }
    return pkt;
  }