/**
   * Sends a specific <tt>Request</tt> to the STUN server associated with this
   * <tt>StunCandidateHarvest</tt>.
   *
   * @param request the <tt>Request</tt> to send to the STUN server associated with this
   *     <tt>StunCandidateHarvest</tt>
   * @param firstRequest <tt>true</tt> if the specified <tt>request</tt> should be sent as the first
   *     request in the terms of STUN; otherwise, <tt>false</tt>
   * @return the <tt>TransactionID</tt> of the STUN client transaction through which the specified
   *     <tt>Request</tt> has been sent to the STUN server associated with this
   *     <tt>StunCandidateHarvest</tt>
   * @param transactionID the <tt>TransactionID</tt> of <tt>request</tt> because <tt>request</tt>
   *     only has it as a <tt>byte</tt> array and <tt>TransactionID</tt> is required for the
   *     <tt>applicationData</tt> property value
   * @throws StunException if anything goes wrong while sending the specified <tt>Request</tt> to
   *     the STUN server associated with this <tt>StunCandidateHarvest</tt>
   */
  protected TransactionID sendRequest(
      Request request, boolean firstRequest, TransactionID transactionID) throws StunException {
    if (!firstRequest && (longTermCredentialSession != null))
      longTermCredentialSession.addAttributes(request);

    StunStack stunStack = harvester.getStunStack();
    TransportAddress stunServer = harvester.stunServer;
    TransportAddress hostCandidateTransportAddress = hostCandidate.getTransportAddress();

    if (transactionID == null) {
      byte[] transactionIDAsBytes = request.getTransactionID();

      transactionID =
          (transactionIDAsBytes == null)
              ? TransactionID.createNewTransactionID()
              : TransactionID.createTransactionID(harvester.getStunStack(), transactionIDAsBytes);
    }
    synchronized (requests) {
      try {
        transactionID =
            stunStack.sendRequest(
                request, stunServer, hostCandidateTransportAddress, this, transactionID);
      } catch (IllegalArgumentException iaex) {
        if (logger.isLoggable(Level.INFO)) {
          logger.log(
              Level.INFO,
              "Failed to send "
                  + request
                  + " through "
                  + hostCandidateTransportAddress
                  + " to "
                  + stunServer,
              iaex);
        }
        throw new StunException(StunException.ILLEGAL_ARGUMENT, iaex.getMessage(), iaex);
      } catch (IOException ioex) {
        if (logger.isLoggable(Level.INFO)) {
          logger.log(
              Level.INFO,
              "Failed to send "
                  + request
                  + " through "
                  + hostCandidateTransportAddress
                  + " to "
                  + stunServer,
              ioex);
        }
        throw new StunException(StunException.NETWORK_ERROR, ioex.getMessage(), ioex);
      }

      requests.put(transactionID, request);
    }
    return transactionID;
  }
  /**
   * Creates a <tt>ServerReflexiveCandidate</tt> using {@link #hostCandidate} as its base and the
   * <tt>XOR-MAPPED-ADDRESS</tt> attribute in <tt>response</tt> for the actual
   * <tt>TransportAddress</tt> of the new candidate. If the message is malformed and/or does not
   * contain the corresponding attribute, this method simply has no effect.
   *
   * @param response the STUN <tt>Response</tt> which is supposed to contain the address we should
   *     use for the new candidate
   */
  protected void createServerReflexiveCandidate(Response response) {
    TransportAddress addr = getMappedAddress(response);

    if (addr != null) {
      ServerReflexiveCandidate srvrRflxCand = createServerReflexiveCandidate(addr);

      if (srvrRflxCand != null) {
        try {
          addCandidate(srvrRflxCand);
        } finally {
          // Free srvrRflxCand if it has not been consumed.
          if (!containsCandidate(srvrRflxCand)) {
            try {
              srvrRflxCand.free();
            } catch (Exception ex) {
              if (logger.isLoggable(Level.FINE)) {
                logger.log(
                    Level.FINE,
                    "Failed to free" + " ServerReflexiveCandidate: " + srvrRflxCand,
                    ex);
              }
            }
          }
        }
      }
    }
  }