All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.java_websocket.SSLSocketChannel2 Maven / Gradle / Ivy

There is a newer version: 1.6.0
Show newest version
/**
 * Copyright (C) 2003 Alexander Kout
 * Originally from the jFxp project (http://jfxp.sourceforge.net/).
 * Copied with permission June 11, 2012 by Femi Omojola ([email protected]).
 */
package org.java_websocket;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;

/**
 * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper.
 */
public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel {
	protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 );

	protected ExecutorService exec;

	protected List> tasks;

	/** raw payload incomming */
	protected ByteBuffer inData;
	/** encrypted data outgoing */
	protected ByteBuffer outCrypt;
	/** encrypted data incoming */
	protected ByteBuffer inCrypt;

	/** the underlying channel */
	protected SocketChannel socketChannel;
	/** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */
	protected SelectionKey selectionKey;


	protected SSLEngineResult engineResult;
	protected SSLEngine sslEngine;


	private Status engineStatus = Status.BUFFER_UNDERFLOW;

	public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException {
		if( channel == null || sslEngine == null || exec == null )
			throw new IllegalArgumentException( "parameter must not be null" );

		this.socketChannel = channel;
		this.sslEngine = sslEngine;
		this.exec = exec;

		tasks = new ArrayList>( 3 );
		if( key != null ) {
			key.interestOps( key.interestOps() | SelectionKey.OP_WRITE );
			this.selectionKey = key;
		}
		createBuffers( sslEngine.getSession() );
		// kick off handshake
		socketChannel.write( wrap( emptybuffer ) );// initializes res
		processHandshake();
	}

	private void consumeFutureUninterruptible( Future f ) {
		try {
			boolean interrupted = false;
			while ( true ) {
				try {
					f.get();
					break;
				} catch ( InterruptedException e ) {
					interrupted = true;
				}
			}
			if( interrupted )
				Thread.currentThread().interrupt();
		} catch ( ExecutionException e ) {
			throw new RuntimeException( e );
		}
	}

	private synchronized void processHandshake() throws IOException {
		if( engineResult.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING )
			return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking.
		if( !tasks.isEmpty() ) {
			Iterator> it = tasks.iterator();
			while ( it.hasNext() ) {
				Future f = it.next();
				if( f.isDone() ) {
					it.remove();
				} else {
					if( isBlocking() )
						consumeFutureUninterruptible( f );
					return;
				}
			}
		}

		if( engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) {
			if( !isBlocking() || engineStatus == Status.BUFFER_UNDERFLOW ) {
				inCrypt.compact();
				int read = socketChannel.read( inCrypt );
				if( read == -1 ) {
					throw new IOException( "connection closed unexpectedly by peer" );
				}
				inCrypt.flip();
			}
			inData.compact();
			unwrap();
			if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) {
				createBuffers( sslEngine.getSession() );
				return;
			}
		}
		consumeDelegatedTasks();
		assert ( engineResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );
		if( tasks.isEmpty() || engineResult.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) {
			socketChannel.write( wrap( emptybuffer ) );
			if( engineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) {
				createBuffers( sslEngine.getSession() );
			}
		}
	}

	private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException {
		outCrypt.compact();
		engineResult = sslEngine.wrap( b, outCrypt );
		outCrypt.flip();
		return outCrypt;
	}

	private synchronized ByteBuffer unwrap() throws SSLException {
		int rem;
		do {
			rem = inData.remaining();
			engineResult = sslEngine.unwrap( inCrypt, inData );
			engineStatus = engineResult.getStatus();
		} while ( engineStatus == SSLEngineResult.Status.OK && ( rem != inData.remaining() || engineResult.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) );

		inData.flip();
		return inData;
	}

	protected void consumeDelegatedTasks() {
		Runnable task;
		while ( ( task = sslEngine.getDelegatedTask() ) != null ) {
			tasks.add( exec.submit( task ) );
			// task.run();
		}
	}

	protected void createBuffers( SSLSession session ) {
		int appBufferMax = session.getApplicationBufferSize();
		int netBufferMax = session.getPacketBufferSize();

		if( inData == null ) {
			inData = ByteBuffer.allocate( appBufferMax );
			outCrypt = ByteBuffer.allocate( netBufferMax );
			inCrypt = ByteBuffer.allocate( netBufferMax );
		} else {
			if( inData.capacity() != appBufferMax )
				inData = ByteBuffer.allocate( appBufferMax );
			if( outCrypt.capacity() != netBufferMax )
				outCrypt = ByteBuffer.allocate( netBufferMax );
			if( inCrypt.capacity() != netBufferMax )
				inCrypt = ByteBuffer.allocate( netBufferMax );
		}
		inData.rewind();
		inData.flip();
		inCrypt.rewind();
		inCrypt.flip();
		outCrypt.rewind();
		outCrypt.flip();
	}

	public int write( ByteBuffer src ) throws IOException {
		if( !isHandShakeComplete() ) {
			processHandshake();
			return 0;
		}
		int num = socketChannel.write( wrap( src ) );
		return num;

	}

	/**
	 * Blocks when in blocking mode until at least one byte has been decoded.
* When not in blocking mode 0 may be returned. **/ public int read( ByteBuffer dst ) throws IOException { if( !dst.hasRemaining() ) return 0; if( !isHandShakeComplete() ) { if( isBlocking() ) { while ( !isHandShakeComplete() ) { processHandshake(); } } else { processHandshake(); if( !isHandShakeComplete() ) { return 0; } } } int purged = readRemaining( dst ); if( purged != 0 ) return purged; assert ( inData.position() == 0 ); inData.clear(); if( !inCrypt.hasRemaining() ) inCrypt.clear(); else inCrypt.compact(); if( ( isBlocking() && inCrypt.position() == 0 ) || engineStatus == Status.BUFFER_UNDERFLOW ) if( socketChannel.read( inCrypt ) == -1 ) { return -1; } inCrypt.flip(); unwrap(); int transfered = transfereTo( inData, dst ); if( transfered == 0 && isBlocking() ) { return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking } return transfered; } private int readRemaining( ByteBuffer dst ) throws SSLException { if( inData.hasRemaining() ) { return transfereTo( inData, dst ); } if( !inData.hasRemaining() ) inData.clear(); // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) if( inCrypt.hasRemaining() ) { unwrap(); int amount = transfereTo( inData, dst ); if( amount > 0 ) return amount; } return 0; } public boolean isConnected() { return socketChannel.isConnected(); } public void close() throws IOException { sslEngine.closeOutbound(); sslEngine.getSession().invalidate(); if( socketChannel.isOpen() ) socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written socketChannel.close(); } private boolean isHandShakeComplete() { HandshakeStatus status = engineResult.getHandshakeStatus(); return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; } public SelectableChannel configureBlocking( boolean b ) throws IOException { return socketChannel.configureBlocking( b ); } public boolean connect( SocketAddress remote ) throws IOException { return socketChannel.connect( remote ); } public boolean finishConnect() throws IOException { return socketChannel.finishConnect(); } public Socket socket() { return socketChannel.socket(); } public boolean isInboundDone() { return sslEngine.isInboundDone(); } @Override public boolean isOpen() { return socketChannel.isOpen(); } @Override public boolean isNeedWrite() { return outCrypt.hasRemaining() || !isHandShakeComplete(); } @Override public void writeMore() throws IOException { write( outCrypt ); } @Override public boolean isNeedRead() { return inData.hasRemaining() || ( inCrypt.hasRemaining() && engineResult.getStatus() != Status.BUFFER_UNDERFLOW ); } @Override public int readMore( ByteBuffer dst ) throws SSLException { return readRemaining( dst ); } private int transfereTo( ByteBuffer from, ByteBuffer to ) { int fremain = from.remaining(); int toremain = to.remaining(); if( fremain > toremain ) { // FIXME there should be a more efficient transfer method int limit = Math.min( fremain, toremain ); for( int i = 0 ; i < limit ; i++ ) { to.put( from.get() ); } return limit; } else { to.put( from ); return fremain; } } @Override public boolean isBlocking() { return socketChannel.isBlocking(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy