/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.jeri.ssl;

import java.security.Principal;
import java.util.Iterator;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMaxPrincipal;
import net.jini.core.constraint.ClientMaxPrincipalType;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.core.constraint.ClientMinPrincipalType;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.ConnectionAbsoluteTime;
import net.jini.core.constraint.ConnectionRelativeTime;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.Delegation;
import net.jini.core.constraint.DelegationAbsoluteTime;
import net.jini.core.constraint.DelegationRelativeTime;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.core.constraint.ServerMinPrincipal;

/**
 * Records information about a connection used for a remote call and determines
 * whether the connection could support specific constraints. <p>
 *
 * Does not support heterogeneous constraint alternatives.  As a result,
 * callers can be assured that the choice of possible principals or of
 * Integrity.YES is independent of the suites, insuring that those items can be
 * picked before negotiating the cipher suite.
 *
 * @author Sun Microsystems, Inc.
 */
final class ConnectionContext extends Utilities {

    /** Constraints supported, without integrity or connection timeout */
    private static final long OK = Long.MAX_VALUE;

    /** Constraints supported with codebase integrity */
    private static final long INTEGRITY = -3;

    /** Constraints not supported */
    private static final long NOT_SUPPORTED = -4;

    /** The ClientMinPrincipalType supported by the provider. */
    private static final ClientMinPrincipalType clientMinPrincipalType =
	new ClientMinPrincipalType(X500Principal.class);

    /** The cipher suite */
    final String cipherSuite;

    /** The client principal, or null for an anonymous client */
    final Principal client;

    /** The server principal, or null for an anonymous server */
    final Principal server;

    /** Whether codebase integrity should be enforced */
    private final boolean integrity;

    /**
     * Whether the connection is being considered on the client side, which
     * does not support relative time constraints.
     */
    private final boolean clientSide;

    /** Set to true if the principals and cipher suite conflict */
    private boolean notSupported;

    /** Whether the requirements specify Integrity.YES */
    private boolean integrityRequired;

    /** Whether the preferences specify Integrity.YES */
    private boolean integrityPreferred;

    /** The absolute connection time, or Long.MAX_VALUE if not specified */
    private long connectionTime = Long.MAX_VALUE;

    /** The number of preferences satisfied */
    private int preferences;

    /**
     * Creates an instance that represents using the specified cipher suite,
     * client and server principals, whether to guarantee codebase integrity,
     * and constraints.  Null values for the principals mean they are
     * anonymous.  Non-X.500 principals are permitted to allow specifying a
     * dummy principal if the principal is unknown.  Returns null if the
     * constraints are not supported.
     */
    static ConnectionContext getInstance(String cipherSuite,
					 Principal client,
					 Principal server,
					 boolean integrity,
					 boolean clientSide,
					 InvocationConstraints constraints)
    {
	ConnectionContext context = new ConnectionContext(
	    cipherSuite, client, server, integrity, clientSide);
	return context.supported(constraints) ? context : null;
    }

    /** Creates an instance of this class. */
    private ConnectionContext(String cipherSuite,
			      Principal client,
			      Principal server,
			      boolean integrity,
			      boolean clientSide)
    {
	this.cipherSuite = cipherSuite;
	this.client = client;
	this.server = server;
	this.integrity = integrity;
	this.clientSide = clientSide;
	boolean serverAuth = doesServerAuthentication(cipherSuite);
	if (serverAuth != (server != null)
	    || (client != null && server == null))
	{
	    notSupported = true;
	}
    }

    public String toString() {
	StringBuffer sb = new StringBuffer("ConnectionContext[");
	fieldsToString(sb);
	sb.append("]");
	return sb.toString();
    }

    void fieldsToString(StringBuffer sb) {
	sb.append(cipherSuite);
	if (client != null) {
	    sb.append(", client: ").append(client);
	}
	if (server != null) {
	    sb.append(", server: ").append(server);
	}
	if (integrityRequired) {
	    sb.append(", integrity: required");
	} else if (integrityPreferred) {
	    sb.append(", integrity: preferred");
	}
	if (connectionTime != Long.MAX_VALUE) {
	    sb.append(", connectionTime = ").append(connectionTime);
	}
	sb.append(", preferences: ").append(preferences);
    }

    /** Returns whether integrity is required. */
    boolean getIntegrityRequired() {
	return integrityRequired;
    }

    /** Returns whether integrity is preferred. */
    boolean getIntegrityPreferred() {
	return integrityPreferred;
    }

    /**
     * Returns the absolute time when the connection should be completed, or
     * Long.MAX_VALUE for no limit.
     */
    long getConnectionTime() {
	return connectionTime;
    }

    /** Returns the number of preferences that can be satisfied. */
    int getPreferences() {
	return preferences;
    }

    /**
     * Checks if the specified constraints are supported, computing
     * integrityRequired, integrityPreferred, connectionTime and preferences as
     * a side effect.
     */
    private boolean supported(InvocationConstraints constraints) {
	if (notSupported) {
	    return false;
	}
	for (Iterator i = constraints.requirements().iterator(); i.hasNext(); )
	{
	    long r = supported((InvocationConstraint) i.next());
	    if (r == NOT_SUPPORTED) {
		return false;
	    }
	    if (r == INTEGRITY) {
		integrityRequired = true;
	    } else if (connectionTime > r) {
		connectionTime = r;
	    }
	}
	for (Iterator i = constraints.preferences().iterator(); i.hasNext(); ) {
	    long r = supported((InvocationConstraint) i.next());
	    if (r == NOT_SUPPORTED) {
	      continue;
	    }
	    preferences++;
	    if (r == INTEGRITY) {
		if (!integrityRequired) {
		    integrityPreferred = true;
		}
	    } else if (connectionTime > r) {
		connectionTime = r;
	    }
	}
	if (integrity && !integrityRequired && !integrityPreferred) {
	    return false;
	} else {
	    return true;
	}
    }

    /**
     * Checks if the constraint is supported, returning NOT_SUPPORTED if it is
     * not supported, INTEGRITY if the constraint is Integrity.YES or
     * constraint alternatives with elements of type Integrity, the connection
     * time if the constraint is an instance of ConnectionAbsoluteTime or
     * constraint alternatives of them, and otherwise OK.
     */
    private long supported(InvocationConstraint constraint) {
	if (constraint instanceof ConstraintAlternatives) {
	    return supported((ConstraintAlternatives) constraint);
	} else if (constraint instanceof Integrity) {
	    return integrity && constraint == Integrity.YES
		? INTEGRITY : NOT_SUPPORTED;
	} else if (constraint instanceof Confidentiality) {
	    return ok(doesEncryption(cipherSuite) ==
		      (constraint == Confidentiality.YES));
	} else if (constraint instanceof ConfidentialityStrength) {
	    return ok(!doesEncryption(cipherSuite) ||
		      (hasStrongCipherAlgorithm(cipherSuite)
		       == (constraint == ConfidentialityStrength.STRONG)));
	} else if (constraint instanceof ClientAuthentication) {
	    return ok((client == null) ==
		      (constraint == ClientAuthentication.NO));
	} else if (constraint instanceof ClientMinPrincipalType) {
	    return ok(client == null ||
		      constraint.equals(clientMinPrincipalType));
	} else if (constraint instanceof ClientMaxPrincipalType) {
	    return ok(client == null ||
		      ((ClientMaxPrincipalType) constraint).elements().contains(
			  X500Principal.class));
	} else if (constraint instanceof ClientMinPrincipal) {
	    if (client == null) {
		return OK;
	    } 
	    Set elements = ((ClientMinPrincipal) constraint).elements();
	    return ok(elements.size() == 1 && elements.contains(client));
	} else if (constraint instanceof ClientMaxPrincipal) {
	    return ok(client == null ||
		      ((ClientMaxPrincipal) constraint).elements().contains(
			  client));
	} else if (constraint instanceof Delegation) {
	    return ok(client == null || (constraint == Delegation.NO));
	} else if (constraint instanceof DelegationAbsoluteTime) {
	    return OK;
	} else if (constraint instanceof DelegationRelativeTime) {
	    return ok(!clientSide);
	} else if (constraint instanceof ServerAuthentication) {
	    return ok((server == null) ==
		      (constraint == ServerAuthentication.NO));
	} else if (constraint instanceof ServerMinPrincipal) {
	    if (server == null) {
		return OK;
	    }
	    Set elements = ((ServerMinPrincipal) constraint).elements();
	    return ok(elements.size() == 1 && elements.contains(server));
	} else if (constraint instanceof ConnectionAbsoluteTime) {
	    return Math.max(((ConnectionAbsoluteTime) constraint).getTime(), 0);
	} else if (constraint instanceof ConnectionRelativeTime) {
	    return ok(!clientSide);
	} else {
	    return NOT_SUPPORTED;
	}
    }

    /** Returns OK if the argument is true, else NOT_SUPPORTED. */
    private static long ok(boolean ok) {
	return ok ? OK : NOT_SUPPORTED;
    }

    /**
     * Checks if the constraint alternatives are supported, returning
     * NOT_SUPPORTED if the elements have different types or none are
     * supported, INTEGRITY if the elements are instances of Integrity, the
     * largest connection time if the elements are instances of
     * ConnectionAbsoluteTime, and otherwise OK.
     */
    private long supported(ConstraintAlternatives constraint) {
	Set alts = constraint.elements();
	long connectionTime = -1;
	Class type = null;
	boolean supported = false;
	boolean integrity = false;
	for (Iterator i = alts.iterator(); i.hasNext(); ) {
	    InvocationConstraint alt = (InvocationConstraint) i.next();
	    if (type == null) {
		type = alt.getClass();
	    } else if (type != alt.getClass()) {
		return NOT_SUPPORTED;
	    }
	    long r = supported(alt);
	    if (r != NOT_SUPPORTED) {
		supported = true;
		if (r == INTEGRITY) {
		    integrity = true;
		} else if (r > connectionTime) {
		    connectionTime = r;
		}
	    }
	}
	if (!supported) {
	    return NOT_SUPPORTED;
	} else if (integrity) {
	    return INTEGRITY;
	} else if (connectionTime >= 0) {
	    return connectionTime;
	} else {
	    return OK;
	}
    }
}
