/*
 * Copyright (c) 2012 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package eu.emi.security.authn.x509.helpers.ocsp;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.cert.X509Certificate;

import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;

import eu.emi.security.authn.x509.X509Credential;

/**
 * OCSP client which adds a cache layer on top of {@link OCSPClientImpl}.
 * There are two caches (all of them are configurable) consulted in the given order:
 * unresponsive responders cache (per responder); OCSP responses cache (per responder and checked certificate tuple).
 * <p>
 * This class is thread safe.
 * @author K. Benedyczak
 */
public class OCSPCachingClient
{
	private final long maxTtl;
	private OCSPRespondersCache respondersCache;
	private OCSPResponsesCache responsesCache;
	
	/**
	 * 
	 * @param maxTtl maximum time after each cached response expires. Negative for no cache at all, 0 for no limit
	 * (i.e. caching time will be only controlled by the OCSP response validity period). In ms.
	 * @param diskPath if not null, cached responses will be stored on disk.
	 * @param prefix used if disk cache is enabled, as a common prefix for all files created in the cache directory. 
	 */
	public OCSPCachingClient(long maxTtl, File diskPath, String prefix)
	{
		this.maxTtl = maxTtl;
		responsesCache = new OCSPResponsesCache(maxTtl, diskPath, prefix);
		respondersCache = new OCSPRespondersCache(maxTtl, diskPath, prefix);
	}

	/**
	 * Returns the checked certificate status.
	 * @param responder mandatory - URL of the responder. HTTP or HTTPs, however in https mode the 
	 * @param toCheckCert mandatory certificate to be checked
	 * @param issuerCert mandatory certificate of the toCheckCert issuer
	 * @param requester if not null, then it is assumed that request must be signed by the requester.
	 * @param addNonce if true nonce will be added to the request and required in response
	 * @param timeout timeout
	 * @return raw result of the query
	 * @throws IOException IO exception
	 * @throws OCSPException OCSP exception
	 */
	public OCSPResult queryForCertificate(URL responder, X509Certificate toCheckCert,
			X509Certificate issuerCert, X509Credential requester, boolean addNonce,
			int timeout) throws IOException, OCSPException
	{
		return queryForCertificate(responder, toCheckCert, issuerCert, requester, addNonce, timeout, 
				new OCSPClientImpl());
	}
	
	/**
	 * Returns the checked certificate status, using a custom client.  
	 * @param responder mandatory - URL of the responder. HTTP or HTTPs, however in https mode the 
	 * @param toCheckCert mandatory certificate to be checked
	 * @param issuerCert mandatory certificate of the toCheckCert issuer
	 * @param requester if not null, then it is assumed that request must be signed by the requester.
	 * @param addNonce if true nonce will be added to the request and required in response
	 * @param timeout timeout
	 * @param client client to be used for network calls
	 * @return raw result of the query
	 * @throws IOException IO exception
	 * @throws OCSPException OCSP exception
	 */
	public OCSPResult queryForCertificate(URL responder, X509Certificate toCheckCert,
			X509Certificate issuerCert, X509Credential requester, boolean addNonce,
			int timeout, OCSPClientImpl client) throws IOException, OCSPException
	{
		if (maxTtl < 0)
		{
			return client.queryForCertificate(responder, toCheckCert, issuerCert, 
					requester, addNonce, timeout);
		}
		
		String responderKey = respondersCache.createResponderKey(issuerCert);
		respondersCache.checkCachedError(responderKey);
		
		String responseKey = responsesCache.createResponseKey(toCheckCert, issuerCert);		
		SingleResp cachedResp = responsesCache.getCachedResp(responseKey, client, 
				toCheckCert, issuerCert);
		if (cachedResp != null)
			return new OCSPResult(cachedResp);
		
		OCSPReq request = client.createRequest(toCheckCert, issuerCert, requester, addNonce);
		OCSPResponseStructure responseWithMeta;
		try
		{
			responseWithMeta = client.send(responder, request, timeout);
		} catch (IOException e)
		{
			respondersCache.addToCache(responderKey, e);
			throw e;
		}
		OCSPResp fullResponse = responseWithMeta.getResponse();
		
		byte[] nonce = OCSPClientImpl.extractNonce(request);
		SingleResp singleResp = client.verifyResponse(fullResponse, toCheckCert, issuerCert, nonce);
		responsesCache.addToCache(responseKey, responseWithMeta, singleResp);
		return new OCSPResult(singleResp);
	}

	
	public void clearMemoryCache()
	{
		responsesCache.clearMemoryCache();
		respondersCache.clearMemoryCache();
	}
}









