/*
 * 
 * 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 com.sun.jini.discovery.internal;

import com.sun.jini.discovery.DatagramBufferFactory;
import com.sun.jini.discovery.DiscoveryProtocolException;
import com.sun.jini.discovery.MulticastAnnouncement;
import com.sun.jini.discovery.MulticastAnnouncementDecoder;
import com.sun.jini.discovery.MulticastRequest;
import com.sun.jini.discovery.MulticastRequestEncoder;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.io.UnsupportedConstraintException;

/**
 * Superclass for client-side providers for the net.jini.discovery.x500.*
 * discovery formats.
 */
public class X500Client
    extends X500Provider
    implements MulticastRequestEncoder, MulticastAnnouncementDecoder
{
    /**
     * Creates an instance with the given attributes.  Throws
     * NoSuchAlgorithmException if a provider for the specified signature
     * algorithm cannot be found.
     */
    protected X500Client(String formatName,
			 String signatureAlgorithm,
			 int maxSignatureLength,
			 String keyAlgorithm,
			 String keyAlgorithmOID)
	throws NoSuchAlgorithmException
    {
	super(formatName,
	      signatureAlgorithm,
	      maxSignatureLength,
	      keyAlgorithm,
	      keyAlgorithmOID);
    }

    // documentation inherited from MulticastRequestEncoder
    public void encodeMulticastRequest(MulticastRequest request,
				       DatagramBufferFactory bufs,
				       InvocationConstraints constraints)
	throws IOException
    {
	if (request == null || bufs == null) {
	    throw new NullPointerException();
	}
	try {
	    X500Constraints cons = X500Constraints.process(constraints, true);

	    // REMIND: instead iterate through constraint-designated principals
	    X500PrivateCredential[] creds = getPrivateCredentials();
	    X500PrivateCredential chosen = null;
	    int best = -1;
	    for (int i = 0; i < creds.length; i++) {
		X500PrivateCredential c = creds[i];
		X500Principal p = c.getCertificate().getSubjectX500Principal();
		int score = cons.checkClientPrincipal(p);
		if (score < 0) {
		    if (logger.isLoggable(Level.FINEST)) {
			logger.log(Level.FINEST,
				   "skipping disallowed principal {0}",
				   new Object[]{ p });
		    }
		    continue;
		}
		try {
		    checkAuthenticationPermission(p, "connect");
		} catch (SecurityException e) {
		    if (logger.isLoggable(Level.FINE)) {
			logger.log(Level.FINE,
				   "not authorized to use principal {0}",
				   new Object[]{ p });
		    }
		    continue;
		}
		if (score > best) {
		    chosen = c;
		    best = score;
		}
	    }
	    if (chosen == null) {
		throw new UnsupportedConstraintException(
		    "unsupported constraints: " + constraints);
	    }
	    if (logger.isLoggable(Level.FINEST)) {
		logger.log(Level.FINEST, "using principal {0}",
			   new Object[]{ chosen });
	    }
	    SigningBufferFactory sbf = new SigningBufferFactory(bufs, chosen);
	    Plaintext.encodeMulticastRequest(request, sbf);
	    sbf.sign();

	} catch (IOException e) {
	    throw e;
	} catch (SecurityException e) {
	    throw e;
	} catch (Exception e) {
	    throw new DiscoveryProtocolException(null, e);
	}
    }

    // documentation inherited from MulticastAnnouncementDecoder
    public MulticastAnnouncement decodeMulticastAnnouncement(
					    ByteBuffer buf,
					    InvocationConstraints constraints)
	throws IOException
    {
	try {
	    X500Constraints cons = X500Constraints.process(constraints, false);

	    int len = buf.getInt();
	    ByteBuffer data = buf.duplicate();
	    data.limit(data.position() + len);
	    buf.position(data.limit());

	    X500Principal p = new X500Principal(Plaintext.getUtf(buf));
	    if (cons.checkServerPrincipal(p) < 0) {
		throw new UnsupportedConstraintException(
		    "principal not allowed: " + p);
	    }
	    Certificate cert = getCertificate(p);
	    if (cert == null) {
		throw new DiscoveryProtocolException(
		    "unknown principal: " + p);
	    }
	    if (logger.isLoggable(Level.FINEST)) {
		logger.log(Level.FINEST, "mapped principal {0} to {1}",
			   new Object[]{ p, cert });
	    }
	    ByteBuffer signed = (ByteBuffer) data.duplicate().position(0);
	    if (!verify(signed, buf, cert.getPublicKey())) {
		throw new DiscoveryProtocolException(
		    "signature verification failed: " + p);
	    }
	    return Plaintext.decodeMulticastAnnouncement(data);

	} catch (IOException e) {
	    throw e;
	} catch (SecurityException e) {
	    throw e;
	} catch (Exception e) {
	    throw new DiscoveryProtocolException(null, e);
	}
    }
}
