/**
   * Generates an authorisation header in response to wwwAuthHeader.
   *
   * @param method method of the request being authenticated
   * @param uri digest-uri
   * @param requestBody the body of the request.
   * @param authHeader the challenge that we should respond to
   * @param userCredentials username and pass
   * @return an authorisation header in response to authHeader.
   * @throws OperationFailedException if auth header was malformated.
   */
  private AuthorizationHeader getAuthorization(
      String method,
      String uri,
      String requestBody,
      WWWAuthenticateHeader authHeader,
      UserCredentialHash userCredentials) {
    String response = null;

    // JvB: authHeader.getQop() is a quoted _list_ of qop values
    // (e.g. "auth,auth-int") Client is supposed to pick one
    String qopList = authHeader.getQop();
    String qop = (qopList != null) ? "auth" : null;
    String nc_value = "00000001";
    String cnonce = "xyz";

    response =
        MessageDigestAlgorithm.calculateResponse(
            authHeader.getAlgorithm(),
            userCredentials.getHashUserDomainPassword(),
            authHeader.getNonce(),
            nc_value, // JvB added
            cnonce, // JvB added
            method,
            uri,
            requestBody,
            qop,
            sipStack.getStackLogger()); // jvb changed

    AuthorizationHeader authorization = null;
    try {
      if (authHeader instanceof ProxyAuthenticateHeader) {
        authorization = headerFactory.createProxyAuthorizationHeader(authHeader.getScheme());
      } else {
        authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
      }

      authorization.setUsername(userCredentials.getUserName());
      authorization.setRealm(authHeader.getRealm());
      authorization.setNonce(authHeader.getNonce());
      authorization.setParameter("uri", uri);
      authorization.setResponse(response);
      if (authHeader.getAlgorithm() != null) {
        authorization.setAlgorithm(authHeader.getAlgorithm());
      }

      if (authHeader.getOpaque() != null) {
        authorization.setOpaque(authHeader.getOpaque());
      }

      // jvb added
      if (qop != null) {
        authorization.setQop(qop);
        authorization.setCNonce(cnonce);
        authorization.setNonceCount(Integer.parseInt(nc_value));
      }

      authorization.setResponse(response);

    } catch (ParseException ex) {
      throw new RuntimeException("Failed to create an authorization header!");
    }

    return authorization;
  }
  /**
   * Generates an authorisation header in response to wwwAuthHeader.
   *
   * @param method method of the request being authenticated
   * @param uri digest-uri
   * @param requestBody the body of the request.
   * @param authHeader the challenge that we should respond to
   * @param username
   * @param password
   * @return an authorisation header in response to authHeader.
   * @throws OperationFailedException if auth header was malformated.
   */
  public static AuthorizationHeader getAuthorizationHeader(
      String method,
      String uri,
      String requestBody,
      WWWAuthenticateHeader authHeader,
      String username,
      String password) {
    String response = null;
    HeaderFactory headerFactory = SipFactories.headerFactory;

    // JvB: authHeader.getQop() is a quoted _list_ of qop values
    // (e.g. "auth,auth-int") Client is supposed to pick one
    String qopList = authHeader.getQop();
    String qop = (qopList != null) ? "auth" : null;
    String nc_value = "00000001";
    String cnonce = "xyz";

    try {
      response =
          MessageDigestResponseAlgorithm.calculateResponse(
              authHeader.getAlgorithm(),
              username,
              authHeader.getRealm(),
              password,
              authHeader.getNonce(),
              nc_value, // JvB added
              cnonce, // JvB added
              method,
              uri,
              requestBody,
              qop); // jvb changed
    } catch (NullPointerException exc) {
      throw new IllegalStateException("The authenticate header was malformatted", exc);
    }

    AuthorizationHeader authorization = null;
    try {
      if (authHeader instanceof ProxyAuthenticateHeader) {
        authorization = headerFactory.createProxyAuthorizationHeader(authHeader.getScheme());
      } else {
        authorization = headerFactory.createAuthorizationHeader(authHeader.getScheme());
      }

      authorization.setUsername(username);
      authorization.setRealm(authHeader.getRealm());
      authorization.setNonce(authHeader.getNonce());
      authorization.setParameter("uri", uri);
      authorization.setResponse(response);
      if (authHeader.getAlgorithm() != null) {
        authorization.setAlgorithm(authHeader.getAlgorithm());
      }

      if (authHeader.getOpaque() != null && authHeader.getOpaque().length() > 0) {
        authorization.setOpaque(authHeader.getOpaque());
      }

      // jvb added
      if (qop != null) {
        authorization.setQop(qop);
        authorization.setCNonce(cnonce);
        authorization.setNonceCount(Integer.parseInt(nc_value));
      }

      authorization.setResponse(response);

    } catch (ParseException ex) {
      throw new SecurityException("Failed to create an authorization header!");
    }

    return authorization;
  }
  /*
   * (non-Javadoc)
   *
   * @see gov.nist.javax.sip.clientauthutils.AuthenticationHelper#handleChallenge(javax.sip.message.Response,
   *      javax.sip.ClientTransaction, javax.sip.SipProvider)
   */
  public ClientTransaction handleChallenge(
      Response challenge,
      ClientTransaction challengedTransaction,
      SipProvider transactionCreator,
      int cacheTime)
      throws SipException, NullPointerException {
    try {
      if (sipStack.isLoggingEnabled()) {
        sipStack.getStackLogger().logDebug("handleChallenge: " + challenge);
      }

      SIPRequest challengedRequest = ((SIPRequest) challengedTransaction.getRequest());

      Request reoriginatedRequest = null;
      /*
       * If the challenged request is part of a Dialog and the
       * Dialog is confirmed the re-originated request should be
       * generated as an in-Dialog request.
       */
      if (challengedRequest.getToTag() != null
          || challengedTransaction.getDialog() == null
          || challengedTransaction.getDialog().getState() != DialogState.CONFIRMED) {
        reoriginatedRequest = (Request) challengedRequest.clone();
      } else {
        /*
         * Re-originate the request by consulting the dialog. In particular
         * the route set could change between the original request and the
         * in-dialog challenge.
         */
        reoriginatedRequest =
            challengedTransaction.getDialog().createRequest(challengedRequest.getMethod());
        Iterator<String> headerNames = challengedRequest.getHeaderNames();
        while (headerNames.hasNext()) {
          String headerName = headerNames.next();
          if (reoriginatedRequest.getHeader(headerName) != null) {
            ListIterator<Header> iterator = reoriginatedRequest.getHeaders(headerName);
            while (iterator.hasNext()) {
              reoriginatedRequest.addHeader(iterator.next());
            }
          }
        }
      }

      // remove the branch id so that we could use the request in a new
      // transaction
      removeBranchID(reoriginatedRequest);

      if (challenge == null || reoriginatedRequest == null) {
        throw new NullPointerException("A null argument was passed to handle challenge.");
      }

      ListIterator authHeaders = null;

      if (challenge.getStatusCode() == Response.UNAUTHORIZED) {
        authHeaders = challenge.getHeaders(WWWAuthenticateHeader.NAME);
      } else if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) {
        authHeaders = challenge.getHeaders(ProxyAuthenticateHeader.NAME);
      } else {
        throw new IllegalArgumentException("Unexpected status code ");
      }

      if (authHeaders == null) {
        throw new IllegalArgumentException(
            "Could not find WWWAuthenticate or ProxyAuthenticate headers");
      }

      // Remove all authorization headers from the request (we'll re-add them
      // from cache)
      reoriginatedRequest.removeHeader(AuthorizationHeader.NAME);
      reoriginatedRequest.removeHeader(ProxyAuthorizationHeader.NAME);

      // rfc 3261 says that the cseq header should be augmented for the new
      // request. do it here so that the new dialog (created together with
      // the new client transaction) takes it into account.
      // Bug report - Fredrik Wickstrom
      CSeqHeader cSeq = (CSeqHeader) reoriginatedRequest.getHeader((CSeqHeader.NAME));
      try {
        cSeq.setSeqNumber(cSeq.getSeqNumber() + 1l);
      } catch (InvalidArgumentException ex) {
        throw new SipException("Invalid CSeq -- could not increment : " + cSeq.getSeqNumber());
      }

      /* Resolve this to the next hop based on the previous lookup. If we are not using
       * lose routing (RFC2543) then just attach hop as a maddr param.
       */
      if (challengedRequest.getRouteHeaders() == null) {
        Hop hop = ((SIPClientTransaction) challengedTransaction).getNextHop();
        SipURI sipUri = (SipURI) reoriginatedRequest.getRequestURI();
        sipUri.setMAddrParam(hop.getHost());
        if (hop.getPort() != -1) sipUri.setPort(hop.getPort());
      }
      ClientTransaction retryTran = transactionCreator.getNewClientTransaction(reoriginatedRequest);

      WWWAuthenticateHeader authHeader = null;
      SipURI requestUri = (SipURI) challengedTransaction.getRequest().getRequestURI();
      while (authHeaders.hasNext()) {
        authHeader = (WWWAuthenticateHeader) authHeaders.next();
        String realm = authHeader.getRealm();
        AuthorizationHeader authorization = null;
        String sipDomain;
        if (this.accountManager instanceof SecureAccountManager) {
          UserCredentialHash credHash =
              ((SecureAccountManager) this.accountManager)
                  .getCredentialHash(challengedTransaction, realm);
          URI uri = reoriginatedRequest.getRequestURI();
          sipDomain = credHash.getSipDomain();
          authorization =
              this.getAuthorization(
                  reoriginatedRequest.getMethod(),
                  uri.toString(),
                  (reoriginatedRequest.getContent() == null)
                      ? ""
                      : new String(reoriginatedRequest.getRawContent()),
                  authHeader,
                  credHash);
        } else {
          UserCredentials userCreds =
              ((AccountManager) this.accountManager).getCredentials(challengedTransaction, realm);
          sipDomain = userCreds.getSipDomain();
          if (userCreds == null)
            throw new SipException("Cannot find user creds for the given user name and realm");

          // we haven't yet authenticated this realm since we were
          // started.

          authorization =
              this.getAuthorization(
                  reoriginatedRequest.getMethod(),
                  reoriginatedRequest.getRequestURI().toString(),
                  (reoriginatedRequest.getContent() == null)
                      ? ""
                      : new String(reoriginatedRequest.getRawContent()),
                  authHeader,
                  userCreds);
        }
        sipStack
            .getStackLogger()
            .logDebug("Created authorization header: " + authorization.toString());

        if (cacheTime != 0)
          cachedCredentials.cacheAuthorizationHeader(sipDomain, authorization, cacheTime);

        reoriginatedRequest.addHeader(authorization);
      }

      if (sipStack.isLoggingEnabled()) {
        sipStack.getStackLogger().logDebug("Returning authorization transaction." + retryTran);
      }
      return retryTran;
    } catch (SipException ex) {
      throw ex;
    } catch (Exception ex) {
      sipStack.getStackLogger().logError("Unexpected exception ", ex);
      throw new SipException("Unexpected exception ", ex);
    }
  }