/**
   * Generate a unique token. The token is generated according to the following pattern. NOnceToken
   * = Base64 ( MD5 ( client-IP ":" time-stamp ":" private-key ) ).
   *
   * @param request HTTP Servlet request
   */
  protected String generateNonce(Request request) {

    long currentTime = System.currentTimeMillis();

    synchronized (lastTimestampLock) {
      if (currentTime > lastTimestamp) {
        lastTimestamp = currentTime;
      } else {
        currentTime = ++lastTimestamp;
      }
    }

    String ipTimeKey = request.getRemoteAddr() + ":" + currentTime + ":" + getKey();

    byte[] buffer =
        ConcurrentMessageDigest.digestMD5(ipTimeKey.getBytes(StandardCharsets.ISO_8859_1));
    String nonce = currentTime + ":" + MD5Encoder.encode(buffer);

    NonceInfo info = new NonceInfo(currentTime, getNonceCountWindowSize());
    synchronized (nonces) {
      nonces.put(nonce, info);
    }

    return nonce;
  }
    public Principal authenticate(Realm realm) {
      // Second MD5 digest used to calculate the digest :
      // MD5(Method + ":" + uri)
      String a2 = method + ":" + uri;

      byte[] buffer = ConcurrentMessageDigest.digestMD5(a2.getBytes(StandardCharsets.ISO_8859_1));
      String md5a2 = MD5Encoder.encode(buffer);

      return realm.authenticate(userName, response, nonce, nc, cnonce, qop, realmName, md5a2);
    }
    public boolean validate(Request request) {
      if ((userName == null)
          || (realmName == null)
          || (nonce == null)
          || (uri == null)
          || (response == null)) {
        return false;
      }

      // Validate the URI - should match the request line sent by client
      if (validateUri) {
        String uriQuery;
        String query = request.getQueryString();
        if (query == null) {
          uriQuery = request.getRequestURI();
        } else {
          uriQuery = request.getRequestURI() + "?" + query;
        }
        if (!uri.equals(uriQuery)) {
          // Some clients (older Android) use an absolute URI for
          // DIGEST but a relative URI in the request line.
          // request. 2.3.5 < fixed Android version <= 4.0.3
          String host = request.getHeader("host");
          String scheme = request.getScheme();
          if (host != null && !uriQuery.startsWith(scheme)) {
            StringBuilder absolute = new StringBuilder();
            absolute.append(scheme);
            absolute.append("://");
            absolute.append(host);
            absolute.append(uriQuery);
            if (!uri.equals(absolute.toString())) {
              return false;
            }
          } else {
            return false;
          }
        }
      }

      // Validate the Realm name
      String lcRealm = getRealmName(request.getContext());
      if (!lcRealm.equals(realmName)) {
        return false;
      }

      // Validate the opaque string
      if (!opaque.equals(opaqueReceived)) {
        return false;
      }

      // Validate nonce
      int i = nonce.indexOf(":");
      if (i < 0 || (i + 1) == nonce.length()) {
        return false;
      }
      long nonceTime;
      try {
        nonceTime = Long.parseLong(nonce.substring(0, i));
      } catch (NumberFormatException nfe) {
        return false;
      }
      String md5clientIpTimeKey = nonce.substring(i + 1);
      long currentTime = System.currentTimeMillis();
      if ((currentTime - nonceTime) > nonceValidity) {
        nonceStale = true;
        synchronized (nonces) {
          nonces.remove(nonce);
        }
      }
      String serverIpTimeKey = request.getRemoteAddr() + ":" + nonceTime + ":" + key;
      byte[] buffer =
          ConcurrentMessageDigest.digestMD5(serverIpTimeKey.getBytes(StandardCharsets.ISO_8859_1));
      String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
      if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
        return false;
      }

      // Validate qop
      if (qop != null && !QOP.equals(qop)) {
        return false;
      }

      // Validate cnonce and nc
      // Check if presence of nc and Cnonce is consistent with presence of qop
      if (qop == null) {
        if (cnonce != null || nc != null) {
          return false;
        }
      } else {
        if (cnonce == null || nc == null) {
          return false;
        }
        // RFC 2617 says nc must be 8 digits long. Older Android clients
        // use 6. 2.3.5 < fixed Android version <= 4.0.3
        if (nc.length() < 6 || nc.length() > 8) {
          return false;
        }
        long count;
        try {
          count = Long.parseLong(nc, 16);
        } catch (NumberFormatException nfe) {
          return false;
        }
        NonceInfo info;
        synchronized (nonces) {
          info = nonces.get(nonce);
        }
        if (info == null) {
          // Nonce is valid but not in cache. It must have dropped out
          // of the cache - force a re-authentication
          nonceStale = true;
        } else {
          if (!info.nonceCountValid(count)) {
            return false;
          }
        }
      }
      return true;
    }