/**
   * Handles a specific <tt>IOException</tt> which was thrown during the execution of {@link
   * #runInConnectThread(DTLSProtocol, TlsPeer, DatagramTransport)} while trying to establish a DTLS
   * connection
   *
   * @param ioe the <tt>IOException</tt> to handle
   * @param msg the human-readable message to log about the specified <tt>ioe</tt>
   * @param i the number of tries remaining after the current one
   * @return <tt>true</tt> if the specified <tt>ioe</tt> was successfully handled; <tt>false</tt>,
   *     otherwise
   */
  private boolean handleRunInConnectThreadException(IOException ioe, String msg, int i) {
    // 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) return false;

    if (ioe instanceof TlsFatalAlert) {
      TlsFatalAlert tfa = (TlsFatalAlert) ioe;
      short alertDescription = tfa.getAlertDescription();

      if (alertDescription == AlertDescription.unexpected_message) {
        msg += " Received fatal unexpected message.";
        if (i == 0
            || !Thread.currentThread().equals(connectThread)
            || connector == null
            || mediaType == null) {
          msg += " Giving up after " + (CONNECT_TRIES - i) + " retries.";
        } else {
          msg += " Will retry.";
          logger.error(msg, ioe);

          return true;
        }
      } else {
        msg += " Received fatal alert " + alertDescription + ".";
      }
    }

    logger.error(msg, ioe);
    return false;
  }
  /**
   * Determines whether {@link #runInConnectThread(DTLSProtocol, TlsPeer, DatagramTransport)} is to
   * try to establish a DTLS connection.
   *
   * @param i the number of tries remaining after the current one
   * @param datagramTransport
   * @return <tt>true</tt> to try to establish a DTLS connection; otherwise, <tt>false</tt>
   */
  private boolean enterRunInConnectThreadLoop(int i, DatagramTransport datagramTransport) {
    if (i < 0 || i > CONNECT_TRIES) {
      return false;
    } else {
      Thread currentThread = Thread.currentThread();

      synchronized (this) {
        if (i > 0 && i < CONNECT_TRIES - 1) {
          boolean interrupted = false;

          try {
            wait(CONNECT_RETRY_INTERVAL);
          } catch (InterruptedException ie) {
            interrupted = true;
          }
          if (interrupted) currentThread.interrupt();
        }

        return currentThread.equals(this.connectThread)
            && datagramTransport.equals(this.datagramTransport);
      }
    }
  }
  /**
   * Gets the {@code SRTPTransformer} used by this instance. If {@link #_srtpTransformer} does not
   * exist (yet) and the state of this instance indicates that its initialization is in progess,
   * then blocks until {@code _srtpTransformer} is initialized and returns it.
   *
   * @return the {@code SRTPTransformer} used by this instance
   */
  private SinglePacketTransformer waitInitializeAndGetSRTPTransformer() {
    SinglePacketTransformer srtpTransformer = _srtpTransformer;

    if (srtpTransformer != null) return srtpTransformer;

    if (rtcpmux && Component.RTCP == componentID) return initializeSRTCPTransformerFromRtp();

    // XXX It is our explicit policy to rely on the SrtpListener to notify
    // the user that the session is not secure. Unfortunately, (1) the
    // SrtpListener is not supported by this DTLS SrtpControl implementation
    // and (2) encrypted packets may arrive soon enough to be let through
    // while _srtpTransformer is still initializing. Consequently, we will
    // block and wait for _srtpTransformer to initialize.
    boolean interrupted = false;

    try {
      synchronized (this) {
        do {
          srtpTransformer = _srtpTransformer;
          if (srtpTransformer != null) break; // _srtpTransformer is initialized

          if (connectThread == null) {
            // Though _srtpTransformer is NOT initialized, there is
            // no point in waiting because there is no one to
            // initialize it.
            break;
          }

          try {
            // It does not really matter (enough) how much we wait
            // here because we wait in a loop.
            long timeout = CONNECT_TRIES * CONNECT_RETRY_INTERVAL;

            wait(timeout);
          } catch (InterruptedException ie) {
            interrupted = true;
          }
        } while (true);
      }
    } finally {
      if (interrupted) Thread.currentThread().interrupt();
    }

    return srtpTransformer;
  }
  /** 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();
  }
  /**
   * Runs in {@link #connectThread} to initialize {@link #dtlsTransport}.
   *
   * @param dtlsProtocol
   * @param tlsPeer
   * @param datagramTransport
   */
  private void runInConnectThread(
      DTLSProtocol dtlsProtocol, TlsPeer tlsPeer, DatagramTransport datagramTransport) {
    DTLSTransport dtlsTransport = null;
    final boolean srtp = !transformEngine.isSrtpDisabled();
    int srtpProtectionProfile = 0;
    TlsContext tlsContext = null;

    // DTLS client
    if (dtlsProtocol instanceof DTLSClientProtocol) {
      DTLSClientProtocol dtlsClientProtocol = (DTLSClientProtocol) dtlsProtocol;
      TlsClientImpl tlsClient = (TlsClientImpl) tlsPeer;

      for (int i = CONNECT_TRIES - 1; i >= 0; i--) {
        if (!enterRunInConnectThreadLoop(i, datagramTransport)) break;
        try {
          dtlsTransport = dtlsClientProtocol.connect(tlsClient, datagramTransport);
          break;
        } catch (IOException ioe) {
          if (!handleRunInConnectThreadException(
              ioe, "Failed to connect this DTLS client to a DTLS" + " server!", i)) {
            break;
          }
        }
      }
      if (dtlsTransport != null && srtp) {
        srtpProtectionProfile = tlsClient.getChosenProtectionProfile();
        tlsContext = tlsClient.getContext();
      }
    }
    // DTLS server
    else if (dtlsProtocol instanceof DTLSServerProtocol) {
      DTLSServerProtocol dtlsServerProtocol = (DTLSServerProtocol) dtlsProtocol;
      TlsServerImpl tlsServer = (TlsServerImpl) tlsPeer;

      for (int i = CONNECT_TRIES - 1; i >= 0; i--) {
        if (!enterRunInConnectThreadLoop(i, datagramTransport)) break;
        try {
          dtlsTransport = dtlsServerProtocol.accept(tlsServer, datagramTransport);
          break;
        } catch (IOException ioe) {
          if (!handleRunInConnectThreadException(
              ioe, "Failed to accept a connection from a DTLS client!", i)) {
            break;
          }
        }
      }
      if (dtlsTransport != null && srtp) {
        srtpProtectionProfile = tlsServer.getChosenProtectionProfile();
        tlsContext = tlsServer.getContext();
      }
    } else {
      // It MUST be either a DTLS client or a DTLS server.
      throw new IllegalStateException("dtlsProtocol");
    }

    SinglePacketTransformer srtpTransformer =
        (dtlsTransport == null || !srtp)
            ? null
            : initializeSRTPTransformer(srtpProtectionProfile, tlsContext);
    boolean closeSRTPTransformer;

    synchronized (this) {
      if (Thread.currentThread().equals(this.connectThread)
          && datagramTransport.equals(this.datagramTransport)) {
        this.dtlsTransport = dtlsTransport;
        _srtpTransformer = srtpTransformer;
        notifyAll();
      }
      closeSRTPTransformer = (_srtpTransformer != srtpTransformer);
    }
    if (closeSRTPTransformer && srtpTransformer != null) srtpTransformer.close();
  }