package org.jacorb.test.orb.iiop;
/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 2000-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Properties;
import org.jacorb.config.ConfigurationException;
import org.jacorb.config.JacORBConfiguration;
import org.jacorb.orb.CDROutputStream;
import org.jacorb.orb.ORB;
import org.jacorb.orb.iiop.ClientIIOPConnection;
import org.jacorb.orb.iiop.IIOPAddress;
import org.jacorb.orb.iiop.IIOPProfile;
import org.jacorb.test.harness.ORBTestCase;
import org.jacorb.test.harness.TestUtils;
import org.junit.Before;
import org.junit.Test;
import org.omg.CORBA.NO_PERMISSION;
import org.omg.CSIIOP.AS_ContextSec;
import org.omg.CSIIOP.CompoundSecMech;
import org.omg.CSIIOP.CompoundSecMechList;
import org.omg.CSIIOP.CompoundSecMechListHelper;
import org.omg.CSIIOP.SAS_ContextSec;
import org.omg.CSIIOP.ServiceConfiguration;
import org.omg.CSIIOP.TAG_TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANSHelper;
import org.omg.CSIIOP.TransportAddress;
import org.omg.IOP.TAG_CSI_SEC_MECH_LIST;
import org.omg.IOP.TAG_NULL_TAG;
import org.omg.IOP.TaggedComponent;
import org.omg.SSLIOP.SSL;
import org.omg.SSLIOP.SSLHelper;
import org.omg.SSLIOP.TAG_SSL_SEC_TRANS;

/**
 * @author <a href="mailto:russell.gold@oracle.com">Russell Gold</a>
 */
public class ClientIIOPConnectionTest extends ORBTestCase
{

    private TestConnection testConnection = new TestConnection();
    private IIOPProfile iiopProfile;

    private static final short SSL_FEATURE = (short) 16;
    private static final short NOT_REQUIRED = (short) 0;
    private static final short NOT_SUPPORTED = (short) 0;


    @Before
    public void setUp() throws Exception
    {
        iiopProfile = new IIOPProfile( new IIOPAddress( "localhost", 4000 ),
                                       null,
                                       getORB().getGIOPMinorVersion());
    }


    class TestConnection extends ClientIIOPConnection
    {
        void setProfile( ORB orb, IIOPProfile profile ) throws ConfigurationException
        {
            this.profile = profile;
            profile.configure( orb.getConfiguration() );
            checkSSL();
        }

    }


    private void configureConnection( boolean enableSsl, int clientSupports, int clientRequires ) throws Exception
    {
        configureObjectUnderTest( new String[][]
                {
                        { "jacorb.log.default.verbosity", TestUtils.verbose ? "4" : "0" },
                        { "jacorb.security.support_ssl", enableSsl ? "on" : "off" },
                        { "jacorb.security.ssl.client.supported_options", Integer.toString( clientSupports ) },
                        { "jacorb.security.ssl.client.required_options", Integer.toString( clientRequires ) },
                }
        );
    }


    private void configureObjectUnderTest( String[][] settings ) throws ConfigurationException
    {
        Properties properties = new Properties();
        for (int i = 0; i < settings.length; i++)
        {
            properties.setProperty( settings[i][0], settings[i][1] );
        }
        testConnection.configure( JacORBConfiguration.getConfiguration( properties, orb, false ));
    }


    /**
     * Verifies that if no profiles are available, the connection does not use ssl.
     */
    @Test
    public void testNoProfiles() throws Exception
    {
        assertFalse( "Should not report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", -1, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_SSL_SEC_TRANS profile (now obsolete) is present,
     * the connection will use it as a specification of SSL.
     */
    @Test
    public void testSslSecTransProfile() throws Exception
    {
        int port = 5000;

        configureConnection( true, SSL_FEATURE, SSL_FEATURE );
        addSslSecTransComponent( SSL_FEATURE, SSL_FEATURE, port );
        testConnection.setProfile( getORB(), iiopProfile );

        assertTrue( "Should report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", port, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_SSL_SEC_TRANS profile is present,
     * but the client is not supporting ssl, the connection does not use it.
     */
    @Test
    public void testSslSecTransWithSslNotSupported() throws Exception
    {
        configureConnection( false, NOT_SUPPORTED, NOT_REQUIRED );
        addSslSecTransComponent( SSL_FEATURE, SSL_FEATURE, (short) 80 );
        testConnection.setProfile( getORB(), iiopProfile );

        assertFalse( "Should not report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", -1, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_SSL_SEC_TRANS profile does not enable ssl if neither client nor server requires it.
     */
    @Test
    public void testSslSecTransProfileNotRequired() throws Exception
    {
        configureConnection( true, SSL_FEATURE, NOT_REQUIRED );
        addSslSecTransComponent( SSL_FEATURE, NOT_REQUIRED, 50 );
        testConnection.setProfile( getORB(), iiopProfile );

        assertFalse( "Should not report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", -1, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_COMPOUND_SEC_MECH profile with a TLS transport mechanism is present,
     * the connection will use it as a specification of SSL.
     */
    @Test
    public void testTlsTransProfile() throws Exception
    {
        int port = 5000;

        configureConnection( true, SSL_FEATURE, SSL_FEATURE );
        addCompoundSecMechComponentWithTls( SSL_FEATURE, SSL_FEATURE, port );
        testConnection.setProfile( getORB(), iiopProfile );

        assertTrue( "Should report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", port, testConnection.getSsl_port() );
    }


    /**
     * Verifies that port numbers &gt; 32767 are supported.
     */
    @Test
    public void testTlsHighPortNum() throws Exception
    {
        int port = 40000;

        configureConnection( true, SSL_FEATURE, SSL_FEATURE );
        addCompoundSecMechComponentWithTls( SSL_FEATURE, SSL_FEATURE, port );
        testConnection.setProfile( getORB(), iiopProfile );

        assertTrue( "Should report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", port, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_COMPOUND_SEC_MECH is present with no transport mechanism,
     * and the client requires SSL, an exception will be thrown.
     */
    @Test
    public void testCompoundSecMechNoTls() throws Exception
    {
        try
        {
            configureConnection( true, SSL_FEATURE, SSL_FEATURE );
            addCompoundSecMechComponentWithoutTls( SSL_FEATURE, SSL_FEATURE );
            testConnection.setProfile( getORB(), iiopProfile );
            fail( "Should have thrown a NO_PERMISSION exception" );
        } catch (NO_PERMISSION e)
        {
        }
        assertFalse( "Should not report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", -1, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_COMPOUND_SEC_MECH profile with a TLS transport mechanism is present,
     * but the client is not supporting ssl, the connection does not use it.
     */
    @Test
    public void testTlsTransProfileWithSslNotSupported() throws Exception
    {
        configureConnection( false, SSL_FEATURE, SSL_FEATURE );
        addCompoundSecMechComponentWithTls( SSL_FEATURE, SSL_FEATURE, 60 );
        testConnection.setProfile( getORB(), iiopProfile );

        assertFalse( "Should not report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", -1, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if a TAG_COMPOUND_SEC_MECH profile with a TLS transport mechanism is present,
     * ssl is not enabled if neither client nor server requires it.
     */
    @Test
    public void testTlsTransProfileNotRequired() throws Exception
    {
        int port = 5000;

        configureConnection( true, SSL_FEATURE, NOT_REQUIRED );
        addCompoundSecMechComponentWithTls( SSL_FEATURE, NOT_REQUIRED, port );
        testConnection.setProfile( getORB(), iiopProfile );

        assertFalse( "Should not report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", -1, testConnection.getSsl_port() );
    }


    /**
     * Verifies that if both a TAG_SSL_SEC_TRANS and a TAG_COMPOUND_SEC_MECH profile without a TLS transport mechanism are present,
     * the SSL_SEC_TRANS will be used.
     */
    @Test
    public void testSslTransProfileFavoredOverNonTlsSecMech() throws Exception
    {
        int port = 5000;

        configureConnection( true, SSL_FEATURE, SSL_FEATURE );
        addSslSecTransComponent( SSL_FEATURE, SSL_FEATURE, port );
        addCompoundSecMechComponentWithoutTls( SSL_FEATURE, SSL_FEATURE );
        testConnection.setProfile( getORB(), iiopProfile );

        assertTrue( "Should report ssl enabled", testConnection.isSSL() );
        assertEquals( "ssl port", port, testConnection.getSsl_port() );
    }


    private void addSslSecTransComponent( short target_supports, short target_requires, int port )
    {
        iiopProfile.addComponent( TAG_SSL_SEC_TRANS.value, new SSL( target_supports, target_requires, (short) (port & 0x0ffff)), SSLHelper.class);
    }


    private void addCompoundSecMechComponentWithTls( short target_supports, short target_requires, int port )
    {
        iiopProfile.addComponent( TAG_CSI_SEC_MECH_LIST.value, createCompoundSecMechListWithTls( target_supports, target_requires, port), CompoundSecMechListHelper.class);
    }


    private CompoundSecMechList createCompoundSecMechListWithTls( short targetSupports, short targetRequires, int port )
    {
        SAS_ContextSec sasContextSec =
            new SAS_ContextSec( NOT_REQUIRED, NOT_REQUIRED, new ServiceConfiguration[0], new byte[0][0], 0 );

        TaggedComponent transportMech = createTlsTransportMech( targetSupports, targetRequires, port );

        AS_ContextSec asContextSec =
            new AS_ContextSec( targetSupports, targetRequires, new byte[0], new byte[0] );
        CompoundSecMech[] compoundSecMech =
            new CompoundSecMech[] { new CompoundSecMech( targetRequires, transportMech, asContextSec, sasContextSec) };

        return new CompoundSecMechList(false, compoundSecMech);
    }


    private void addCompoundSecMechComponentWithoutTls( short target_supports, short target_requires )
    {
        iiopProfile.addComponent( TAG_CSI_SEC_MECH_LIST.value, createCompoundSecMechListWithoutTls( target_supports, target_requires ), CompoundSecMechListHelper.class);
    }


    private CompoundSecMechList createCompoundSecMechListWithoutTls( short targetSupports, short targetRequires )
    {
        SAS_ContextSec sasContextSec = new SAS_ContextSec( NOT_REQUIRED, NOT_REQUIRED, new ServiceConfiguration[0], new byte[0][0], 0 );
        TaggedComponent transportMech = new TaggedComponent( TAG_NULL_TAG.value, new byte[0] );
        AS_ContextSec asContextSec = new AS_ContextSec( targetSupports, targetRequires, new byte[0], new byte[0] );

        CompoundSecMech[] compoundSecMech =
            new CompoundSecMech[] { new CompoundSecMech( targetRequires, transportMech, asContextSec, sasContextSec) };

        return new CompoundSecMechList(false, compoundSecMech);
    }


    private static TaggedComponent createTlsTransportMech( short targetSupports, short targetRequires, int port )
    {
        CDROutputStream out = new CDROutputStream();
        try
        {
            out.beginEncapsulatedArray();
            TLS_SEC_TRANS tls = new TLS_SEC_TRANS( targetSupports, targetRequires, new TransportAddress[] { new TransportAddress( "local", (short) (port & 0x0ffff) )} );
            TLS_SEC_TRANSHelper.write( out, tls );
            return new TaggedComponent( TAG_TLS_SEC_TRANS.value, out.getBufferCopy() );
        }
        finally
        {
            out.close();
        }
    }

}
