//
//  ========================================================================
//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//


package org.eclipse.jetty.spdy.server;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLEngine;

import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;

public class ALPNSynReplyTest extends AbstractALPNTest
{
    @Test
    public void testGentleCloseDuringHandshake() throws Exception
    {
        InetSocketAddress address = prepare();
        SslContextFactory sslContextFactory = newSslContextFactory();
        sslContextFactory.start();
        SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
        sslEngine.setUseClientMode(true);
        ALPN.put(sslEngine, new ALPN.ClientProvider()
        {
            @Override
            public void unsupported()
            {
            }

            @Override
            public List<String> protocols()
            {
                return Arrays.asList("test");
            }

            @Override
            public void selected(String protocol)
            {
            }
        });
        sslEngine.beginHandshake();

        ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
        sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
        encrypted.flip();

        try (SocketChannel channel = SocketChannel.open(address))
        {
            // Send ClientHello, immediately followed by TLS Close Alert and then by FIN
            channel.write(encrypted);
            sslEngine.closeOutbound();
            encrypted.clear();
            sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
            encrypted.flip();
            channel.write(encrypted);
            channel.shutdownOutput();

            // Read ServerHello from server
            encrypted.clear();
            int read = channel.read(encrypted);
            encrypted.flip();
            Assert.assertTrue(read > 0);
            // Cannot decrypt, as the SSLEngine has been already closed

            // Now if we read more, we should either read the TLS Close Alert, or directly -1
            encrypted.clear();
            read = channel.read(encrypted);
            // Sending a TLS Close Alert during handshake results in an exception when
            // unwrapping that the server react to by closing the connection abruptly.
            Assert.assertTrue(read < 0);
        }
    }

    @Test
    public void testAbruptCloseDuringHandshake() throws Exception
    {
        InetSocketAddress address = prepare();
        SslContextFactory sslContextFactory = newSslContextFactory();
        sslContextFactory.start();
        SSLEngine sslEngine = sslContextFactory.newSSLEngine(address);
        sslEngine.setUseClientMode(true);
        ALPN.put(sslEngine, new ALPN.ClientProvider()
        {
            @Override
            public void unsupported()
            {
            }

            @Override
            public List<String> protocols()
            {
                return Arrays.asList("test");
            }

            @Override
            public void selected(String s)
            {
            }
        });
        sslEngine.beginHandshake();

        ByteBuffer encrypted = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
        sslEngine.wrap(BufferUtil.EMPTY_BUFFER, encrypted);
        encrypted.flip();

        try (SocketChannel channel = SocketChannel.open(address))
        {
            // Send ClientHello, immediately followed by FIN (no TLS Close Alert)
            channel.write(encrypted);
            channel.shutdownOutput();

            // Read ServerHello from server
            encrypted.clear();
            int read = channel.read(encrypted);
            encrypted.flip();
            Assert.assertTrue(read > 0);
            ByteBuffer decrypted = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
            sslEngine.unwrap(encrypted, decrypted);

            // Now if we read more, we should either read the TLS Close Alert, or directly -1
            encrypted.clear();
            read = channel.read(encrypted);
            // Since we have close the connection abruptly, the server also does so
            Assert.assertTrue(read < 0);
        }
    }
}
