/**
  * 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);
    }
  }