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

org.coos.messaging.transport.SecureNioTCPTransport Maven / Gradle / Ivy

There is a newer version: 1.3.1
Show newest version
/**
 * COOS - Connected Objects Operating System (www.connectedobjects.org).
 *
 * Copyright (C) 2009 Telenor ASA and Tellu AS. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 * You may also contact one of the following for additional information:
 * Telenor ASA, Snaroyveien 30, N-1331 Fornebu, Norway (www.telenor.no)
 * Tellu AS, Hagalokkveien 13, N-1383 Asker, Norway (www.tellu.no)
 */
package org.coos.messaging.transport;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;

import org.coos.messaging.Channel;
import org.coos.messaging.Message;
import org.coos.messaging.Processor;
import org.coos.messaging.ProcessorException;
import org.coos.messaging.Service;
import org.coos.messaging.Transport;
import org.coos.messaging.impl.DefaultMessage;
import org.coos.messaging.impl.DefaultProcessor;

/**
 * Represents one non-blocking tcp connection. Decodes messages by reading the
 * length from the first 4 bytes.
 * 
 * @author Morten Versvik, Tellu AS
 * 
 */
public class SecureNioTCPTransport extends DefaultProcessor implements Transport, Service {
	protected List mailbox = Collections.synchronizedList(new LinkedList());
	protected Processor transportProcessor;
	private ByteBuffer netOutBuffer, netInBuffer, inBuffer;
	SocketChannel sc;
	SecureNioTCPTransportManager tm;

	/**
	 * Initial buffer size.
	 */
	private final static int BUFFER_SIZE = 20 * 1024;
	private byte[] buffer = new byte[BUFFER_SIZE];
	private int pos = 0;
	private int newmsgLength = Integer.MAX_VALUE;
	protected int MAX_LENGTH = 8 * 1024; // Max length for a coos msg

	/**
	 * 1 int is 4 bytes.
	 */
	private static final int SIZE_POSITION = 4 - 1;
	SSLEngine sslEngine;

	public SecureNioTCPTransport(SecureNioTCPTransportManager tm, Selector selector, SocketChannel sc,
			Hashtable properties) throws IOException, NoSuchAlgorithmException, KeyManagementException,
			KeyStoreException, CertificateException, UnrecoverableKeyException {
		this.sc = sc;
		netInBuffer = ByteBuffer.allocateDirect(sc.socket().getReceiveBufferSize());
		inBuffer = ByteBuffer.allocateDirect(sc.socket().getReceiveBufferSize());
		netOutBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
		netOutBuffer.flip(); // Start empty

		this.tm = tm;

		String keystore = ((String) properties.get("keystore"));
		char[] keystorepass = ((String) properties.get("keystorepass")).toCharArray();
		char[] keypassword = ((String) properties.get("keypassword")).toCharArray();

		// Fetch the keystore
		KeyStore ks = KeyStore.getInstance("JKS");
		ks.load(new FileInputStream(keystore), keystorepass);
		KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
		kmf.init(ks, keypassword);

		// set the context to SSL 3
		SSLContext sslcontext = SSLContext.getInstance("SSLv3");
		sslcontext.init(kmf.getKeyManagers(), null, null);

		sslEngine = sslcontext.createSSLEngine();

		sslEngine.setUseClientMode(false);
		sslEngine.setNeedClientAuth(false);
		sslEngine.setWantClientAuth(false);

	}

	@Override
	public void setChainedProcessor(Processor chainedProcessor) {
		this.transportProcessor = chainedProcessor;
	}

	public void receivedMessage(Message in) {
		try {
			transportProcessor.processMessage(in);
		} catch (ProcessorException e) {
			e.printStackTrace();
		}
	}

	public int getMailboxSize() {
		return mailbox.size();
	}

	public Message getMessage() {
		return mailbox.remove(0);
	}

	@Override
	public void setChannel(Channel channel) {
	}

	@Override
	public void processMessage(Message msg) throws ProcessorException {
		String priStr = msg.getHeader(Message.PRIORITY);
		if (priStr != null) {
			int pri = Integer.valueOf(priStr);
			int idx = 0;
			for (Message message : mailbox) {
				String pr = message.getHeader(Message.PRIORITY);
				if (pr != null) {
					int p = Integer.valueOf(pr);
					if (pri < p) {
						mailbox.add(idx, msg);
						synchronized (this) {
							this.notify();
						}
						break;
					}
				}
				idx++;
			}
		} else {
			mailbox.add(msg);
			synchronized (this) {
				this.notify();
			}
		}

		// A new message was added, notify the writer.
		if (sc != null)
			tm.readyWrite(sc);
	}

	@Override
	public void start() throws Exception {
	}

	@Override
	public void stop() throws Exception {
	}

	public byte[] decode(ByteBuffer socketBuffer) throws IOException {
		// Reads until the buffer is empty or until a packet
		// is fully reassembled.
		while (socketBuffer.hasRemaining()) {
			// Copies into the temporary buffer
			byte data = socketBuffer.get();
			try {
				buffer[pos] = data;
				if (pos == SIZE_POSITION) { // Got size parameter
					ByteArrayInputStream bin = new ByteArrayInputStream(buffer);
					DataInputStream din = new DataInputStream(bin);

					newmsgLength = din.readInt() + SIZE_POSITION + 1;

					if (newmsgLength > MAX_LENGTH)
						throw new IOException("Packet too big");

					if (buffer.length < newmsgLength)
						buffer = new byte[newmsgLength];
				}
			} catch (IndexOutOfBoundsException e) {
				// We resize the buffer, shouldn't happen.
				e.printStackTrace();
				throw new IOException("Packet too big. Maximum size allowed: " + BUFFER_SIZE + " bytes.");
			}
			pos++;

			// Check if it is the final byte of a packet.
			if (pos == newmsgLength) {
				newmsgLength = Integer.MAX_VALUE;

				// The current packet is fully reassembled. Return it
				byte[] newBuffer = new byte[pos];
				System.arraycopy(buffer, 0, newBuffer, 0, pos);
				pos = 0;

				return newBuffer;
			}
		}
		// No packet was reassembled. There is not enough data. Wait
		// for more data to arrive.
		return null;
	}

	public void decodeFromSocket() throws Exception {
		int readBytes = sc.read(netInBuffer);
		if (readBytes == -1) {
			sc.close();
			tm.socketDisconnected(sc);
			return;
		}
		/*
		 * if (readBytes == 0 && !netInBuffer.hasRemaining()) { return; }
		 */

		netInBuffer.flip();

		SSLEngineResult ser = sslEngine.unwrap(netInBuffer, inBuffer);

		// System.out.println("read: " + ser + " in: " + inBuffer.position() +
		// " - " + inBuffer.limit());

		if (ser.getHandshakeStatus() == HandshakeStatus.FINISHED) {
			initDone = true;
		}
		if (ser.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
			Executor exec = Executors.newSingleThreadExecutor();
			Runnable task;
			while ((task = sslEngine.getDelegatedTask()) != null) {
				exec.execute(task);
			}
		}

		if (ser.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
			tm.readyWrite(sc);
			netInBuffer.compact();
			return;
		}

		inBuffer.flip();
		byte[] bb = decode(inBuffer);
		// System.out.println("decode: " + " in: " + inBuffer.position() + " - "
		// + inBuffer.limit());

		if (bb == null) {
			// Partial packet received. Must wait for more data. All the
			// contents
			// of inBuffer were processed by the protocol decoder. We can
			// delete it and prepare for more data.
			inBuffer.clear();
			netInBuffer.compact();
			// netInBuffer.flip();
			// netInBuffer.clear();

		} else {
			// A packet was reassembled.

			Message msg;
			try {
				// System.out.println(bb.length);
				msg = new DefaultMessage(new DataInputStream(new ByteArrayInputStream(bb)));
				// System.out.println(msg);
				receivedMessage(msg);
				inBuffer.compact();
				netInBuffer.compact();
				// netInBuffer.flip();
			} catch (Exception e) {
				e.printStackTrace();
			}

			// The netInBuffer might still have some data left. Perhaps
			// the beginning of another packet. So don't clear it. Next
			// time reading is activated, we start by processing the netInBuffer
			// again.
		}
	}

	boolean initDone = false;

	public void handleWrite() throws Exception {
		netOutBuffer.flip();

		if (!netOutBuffer.hasRemaining()) {
			netOutBuffer.clear();
			SSLEngineResult ser = null;
			// netOutBuffer = ByteBuffer.allocate(50000);

			if (initDone) {
				if (mailbox.isEmpty()) {
					tm.doneWrite(sc);
					return;
				} else {
					Message msg = getMessage();

					// netOutBuffer.flip();

					ser = sslEngine.wrap(ByteBuffer.wrap(msg.serialize()), netOutBuffer);

					if (ser.getStatus() == Status.BUFFER_OVERFLOW) {
						mailbox.add(0, msg); // Insert first again
						netOutBuffer = ByteBuffer.allocateDirect(netOutBuffer.capacity() * 2);

						handleWrite(); // Retry encryption
						return;
					}
				}
			} else {
				// SslEngine wants to send more headers?
				ser = sslEngine.wrap(ByteBuffer.allocate(0), netOutBuffer);
			}

			if (ser.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
				Executor exec = Executors.newSingleThreadExecutor();
				Runnable task;
				while ((task = sslEngine.getDelegatedTask()) != null) {
					exec.execute(task);
				}
			}

			if (ser.getHandshakeStatus() == HandshakeStatus.FINISHED) {
				initDone = true;
			}
			if (ser.getStatus() == Status.BUFFER_OVERFLOW) {
				netOutBuffer = ByteBuffer.allocateDirect(netOutBuffer.capacity() * 2);

				handleWrite(); // Retry encryption
				return;
			}

			netOutBuffer.flip();

			sc.write(netOutBuffer);
			netOutBuffer.compact();
			netOutBuffer.flip();

			if (ser.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP && !initDone) {
				decodeFromSocket();
				return;
			}
		}

		sc.write(netOutBuffer);

		// Check if there are more to be written.
		if (!netOutBuffer.hasRemaining()) {
			// netOutBuffer was completely written, deactivate write intent.

			// tm.doneWrite(sc);
			netOutBuffer.clear();
			netOutBuffer.flip();
		} else {
			netOutBuffer.compact();
			netOutBuffer.flip();
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy