/**
   * Handles a specific STUN error <tt>Response</tt> with error code "438 Stale Nonce" to a specific
   * STUN <tt>Request</tt>.
   *
   * @param response the received STUN error <tt>Response</tt> with error code "438 Stale Nonce"
   *     which is to be handled
   * @param request the STUN <tt>Request</tt> to which <tt>response</tt> responds
   * @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
   * @return <tt>true</tt> if the specified STUN error <tt>response</tt> was successfully handled;
   *     <tt>false</tt>, otherwise
   * @throws StunException if anything goes wrong while handling the specified "438 Stale Nonce"
   *     error <tt>response</tt>
   */
  private boolean processStaleNonce(Response response, Request request, TransactionID transactionID)
      throws StunException {
    /*
     * The request MUST contain USERNAME, REALM, NONCE and MESSAGE-INTEGRITY
     * attributes.
     */
    boolean challenge;

    if (request.getAttributeCount() > 0) {
      char[] includedRequestAttributeTypes =
          new char[] {
            Attribute.USERNAME, Attribute.REALM, Attribute.NONCE, Attribute.MESSAGE_INTEGRITY
          };
      challenge = true;

      for (char includedRequestAttributeType : includedRequestAttributeTypes) {
        if (!request.containsAttribute(includedRequestAttributeType)) {
          challenge = false;
          break;
        }
      }
    } else challenge = false;

    return (challenge && processChallenge(response, request, transactionID));
  }
  /**
   * Handles a specific STUN error <tt>Response</tt> with error code "401 Unauthorized" to a
   * specific STUN <tt>Request</tt>.
   *
   * @param response the received STUN error <tt>Response</tt> with error code "401 Unauthorized"
   *     which is to be handled
   * @param request the STUN <tt>Request</tt> to which <tt>response</tt> responds
   * @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
   * @return <tt>true</tt> if the specified STUN error <tt>response</tt> was successfully handled;
   *     <tt>false</tt>, otherwise
   * @throws StunException if anything goes wrong while handling the specified "401 Unauthorized"
   *     error <tt>response</tt>
   */
  private boolean processUnauthorized(
      Response response, Request request, TransactionID transactionID) throws StunException {
    /*
     * If the response is a challenge, retry the request with a new
     * transaction.
     */
    boolean challenge = true;

    /*
     * The client SHOULD omit the USERNAME, MESSAGE-INTEGRITY, REALM, and
     * NONCE attributes from the "First Request".
     */
    if (request.getAttributeCount() > 0) {
      char[] excludedRequestAttributeTypes =
          new char[] {
            Attribute.USERNAME, Attribute.MESSAGE_INTEGRITY, Attribute.REALM, Attribute.NONCE
          };

      for (char excludedRequestAttributeType : excludedRequestAttributeTypes) {
        if (request.containsAttribute(excludedRequestAttributeType)) {
          challenge = false;
          break;
        }
      }
    }

    return (challenge && processChallenge(response, request, transactionID));
  }
  /**
   * 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);
    }
  }