/**
   * Gets the <tt>TransportAddress</tt> specified in the XOR-MAPPED-ADDRESS attribute of a specific
   * <tt>Response</tt>.
   *
   * @param response the <tt>Response</tt> from which the XOR-MAPPED-ADDRESS attribute is to be
   *     retrieved and its <tt>TransportAddress</tt> value is to be returned
   * @return the <tt>TransportAddress</tt> specified in the XOR-MAPPED-ADDRESS attribute of
   *     <tt>response</tt>
   */
  protected TransportAddress getMappedAddress(Response response) {
    Attribute attribute = response.getAttribute(Attribute.XOR_MAPPED_ADDRESS);

    if (attribute instanceof XorMappedAddressAttribute) {
      return ((XorMappedAddressAttribute) attribute).getAddress(response.getTransactionID());
    }

    // old STUN servers (RFC3489) send MAPPED-ADDRESS address
    attribute = response.getAttribute(Attribute.MAPPED_ADDRESS);

    if (attribute instanceof MappedAddressAttribute) {
      return ((MappedAddressAttribute) attribute).getAddress();
    } else return null;
  }
  /**
   * Notifies this <tt>StunCandidateHarvest</tt> that a specific STUN <tt>Response</tt> has been
   * received and it challenges a specific STUN <tt>Request</tt> for a long-term credential (as the
   * short-term credential mechanism does not utilize challenging).
   *
   * @param response the STUN <tt>Response</tt> which has been received
   * @param request the STUN <tt>Request</tt> to which <tt>response</tt> responds and which it
   *     challenges for a long-term credential
   * @return <tt>true</tt> if the challenge has been processed and this
   *     <tt>StunCandidateHarvest</tt> is to continue processing STUN <tt>Response</tt>s; otherwise,
   *     <tt>false</tt>
   * @param transactionID the <tt>TransactionID</tt> of <tt>response</tt> and <tt>request</tt>
   *     because <tt>response</tt> and <tt>request</tt> only have 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 processing the challenge
   */
  private boolean processChallenge(Response response, Request request, TransactionID transactionID)
      throws StunException {
    boolean retried = false;

    if (response.getAttributeCount() > 0) {
      /*
       * The response SHOULD NOT contain a USERNAME or
       * MESSAGE-INTEGRITY attribute.
       */
      char[] excludedResponseAttributeTypes =
          new char[] {Attribute.USERNAME, Attribute.MESSAGE_INTEGRITY};
      boolean challenge = true;

      for (char excludedResponseAttributeType : excludedResponseAttributeTypes) {
        if (response.containsAttribute(excludedResponseAttributeType)) {
          challenge = false;
          break;
        }
      }
      if (challenge) {
        // This response MUST include a REALM value.
        RealmAttribute realmAttribute = (RealmAttribute) response.getAttribute(Attribute.REALM);

        if (realmAttribute == null) challenge = false;
        else {
          // The response MUST include a NONCE.
          NonceAttribute nonceAttribute = (NonceAttribute) response.getAttribute(Attribute.NONCE);

          if (nonceAttribute == null) challenge = false;
          else {
            retried =
                processChallenge(
                    realmAttribute.getRealm(), nonceAttribute.getNonce(), request, transactionID);
          }
        }
      }
    }
    return retried;
  }
 /**
  * Completes the harvesting of <tt>Candidate</tt>s for {@link #hostCandidate}. Notifies {@link
  * #harvester} about the completion of the harvesting of <tt>Candidate</tt> for
  * <tt>hostCandidate</tt> performed by this <tt>StunCandidateHarvest</tt>.
  *
  * @param request the <tt>Request</tt> sent by this <tt>StunCandidateHarvest</tt> with which the
  *     harvesting of <tt>Candidate</tt>s for <tt>hostCandidate</tt> has completed
  * @param response the <tt>Response</tt> received by this <tt>StunCandidateHarvest</tt>, if any,
  *     with which the harvesting of <tt>Candidate</tt>s for <tt>hostCandidate</tt> has completed
  * @return <tt>true</tt> if the harvesting of <tt>Candidate</tt>s for <tt>hostCandidate</tt>
  *     performed by this <tt>StunCandidateHarvest</tt> has completed; otherwise, <tt>false</tt>
  */
 protected boolean completedResolvingCandidate(Request request, Response response) {
   if (!completedResolvingCandidate) {
     completedResolvingCandidate = true;
     try {
       if (((response == null) || !response.isSuccessResponse())
           && (longTermCredentialSession != null)) {
         harvester
             .getStunStack()
             .getCredentialsManager()
             .unregisterAuthority(longTermCredentialSession);
         longTermCredentialSession = null;
       }
     } finally {
       harvester.completedResolvingCandidate(this);
     }
   }
   return completedResolvingCandidate;
 }
  /**
   * Notifies this <tt>ResponseCollector</tt> that a STUN response described by the specified
   * <tt>StunResponseEvent</tt> has been received.
   *
   * @param event the <tt>StunResponseEvent</tt> which describes the received STUN response
   * @see ResponseCollector#processResponse(StunResponseEvent)
   */
  @Override
  public void processResponse(StunResponseEvent event) {
    TransactionID transactionID = event.getTransactionID();

    logger.finest("Received a message: tranid= " + transactionID);
    logger.finest("localCand= " + hostCandidate);

    /*
     * Clean up for the purposes of the workaround which determines the STUN
     * Request to which a STUN Response responds.
     */
    synchronized (requests) {
      requests.remove(transactionID);
    }

    // At long last, do start handling the received STUN Response.
    Response response = event.getResponse();
    Request request = event.getRequest();
    boolean completedResolvingCandidate = true;

    try {
      if (response.isSuccessResponse()) {
        // Authentication and Message-Integrity Mechanisms
        if (request.containsAttribute(Attribute.MESSAGE_INTEGRITY)) {
          MessageIntegrityAttribute messageIntegrityAttribute =
              (MessageIntegrityAttribute) response.getAttribute(Attribute.MESSAGE_INTEGRITY);

          /*
           * RFC 5389: If MESSAGE-INTEGRITY was absent, the response
           * MUST be discarded, as if it was never received.
           */
          if (messageIntegrityAttribute == null) return;

          UsernameAttribute usernameAttribute =
              (UsernameAttribute) request.getAttribute(Attribute.USERNAME);

          /*
           * For a request or indication message, the agent MUST
           * include the USERNAME and MESSAGE-INTEGRITY attributes in
           * the message.
           */
          if (usernameAttribute == null) return;
          if (!harvester
              .getStunStack()
              .validateMessageIntegrity(
                  messageIntegrityAttribute,
                  LongTermCredential.toString(usernameAttribute.getUsername()),
                  !request.containsAttribute(Attribute.REALM)
                      && !request.containsAttribute(Attribute.NONCE),
                  event.getRawMessage())) return;
        }

        processSuccess(response, request, transactionID);
      } else {
        ErrorCodeAttribute errorCodeAttr =
            (ErrorCodeAttribute) response.getAttribute(Attribute.ERROR_CODE);

        if ((errorCodeAttr != null) && (errorCodeAttr.getErrorClass() == 4)) {
          try {
            switch (errorCodeAttr.getErrorNumber()) {
              case 1: // 401 Unauthorized
                if (processUnauthorized(response, request, transactionID))
                  completedResolvingCandidate = false;
                break;
              case 38: // 438 Stale Nonce
                if (processStaleNonce(response, request, transactionID))
                  completedResolvingCandidate = false;
                break;
            }
          } catch (StunException sex) {
            completedResolvingCandidate = true;
          }
        }
        if (completedResolvingCandidate && processErrorOrFailure(response, request, transactionID))
          completedResolvingCandidate = false;
      }
    } finally {
      if (completedResolvingCandidate) completedResolvingCandidate(request, response);
    }
  }