dev@grizzly.java.net

SSL client code review

From: Oleksiy Stashok <Oleksiy.Stashok_at_Sun.COM>
Date: Mon, 02 Jul 2007 14:27:24 +0300

Hello,

please review client SSL implementation[attached ssl.new] and code
change I propose additionally [attached ssl.diff].

List of changes:
+ add SSLCallbackHandler, where method onHandshake(IOEvent) added ->
generic <P extends CallbackHandler> was added to interface ConnectorHandler
+ SSLUtils changed, where possible pass SocketChannel instead of
SelectionKey. As there are situations, where SelectionKey is unknown.
May be we can do this change for other classes (where it makes sense).
+ IOEvent.DefaultIOEvent implementation added to reduce the number of
anonym. inner classes.
+ Removed TCP/UDP ConnectorInstanceHandler classes, as they have the
same implementation. Created
ConnectorInstanceHandler.ConcurrentQueueDelegateCIH class, which
implements the common logic and delegates creating of new
ConnectorHandlers to the provided Callable factory.

+ SSL client implementation added
+ SSL client unit test

WBR,
Alexey.


# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: C:\Projects\Grizzly\trunk\modules\grizzly\src
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: main/java/com/sun/grizzly/SSLCallbackHandler.java
*** C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly\SSLCallbackHandler.java Locally New
--- C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly\SSLCallbackHandler.java Locally New
***************
*** 1,0 ****
--- 1,37 ----
+ /*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the License). You may not use this file except in
+ * compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://glassfish.dev.java.net/public/CDDLv1.0.html or
+ * glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * Header Notice in each file and include the License file
+ * at glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * If applicable, add the following below the CDDL Header,
+ * with the fields enclosed by brackets [] replaced by
+ * you own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ */
+
+ package com.sun.grizzly;
+
+ /**
+ * @author Alexey Stashok
+ */
+ public interface SSLCallbackHandler<E> extends CallbackHandler<E> {
+ /**
+ * This method is called when an non blocking OP_CONNECT is ready
+ * to get processed.
+ * @param ioEvent an object containing information about the current
+ * non blocking connection.
+ */
+ public void onHandshake(IOEvent<E> ioEvent);
+ }
Index: main/java/com/sun/grizzly/SSLConfig.java
*** C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly\SSLConfig.java Locally New
--- C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly\SSLConfig.java Locally New
***************
*** 1,0 ****
--- 1,183 ----
+ /*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the License). You may not use this file except in
+ * compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://glassfish.dev.java.net/public/CDDLv1.0.html or
+ * glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * Header Notice in each file and include the License file
+ * at glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * If applicable, add the following below the CDDL Header,
+ * with the fields enclosed by brackets [] replaced by
+ * you own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ */
+
+ package com.sun.grizzly;
+
+ import java.io.FileInputStream;
+ import java.security.KeyStore;
+ import javax.net.ssl.KeyManagerFactory;
+ import javax.net.ssl.SSLContext;
+ import javax.net.ssl.TrustManagerFactory;
+
+ /**
+ * @author Alexey Stashok
+ */
+ public class SSLConfig {
+ public static SSLConfig DEFAULT_CONFIG = new SSLConfig();
+
+ private String trustStoreType = "JKS";
+ private String keyStoreType = "JKS";
+
+ private char[] trustStorePass = "changeit".toCharArray();
+ private char[] keyStorePass = "changeit".toCharArray();
+
+ private String trustStoreFile = System.getProperty("javax.net.ssl.trustStore");
+ private String keyStoreFile = System.getProperty("javax.net.ssl.keyStore");
+
+ private String trustStoreAlgorithm = "SunX509";
+ private String keyStoreAlgorithm = "SunX509";
+
+ private String securityProtocol = "TLS";
+
+ private boolean needClientAuth = false;
+
+ private boolean wantClientAuth = false;
+
+ public String getTrustStoreType() {
+ return trustStoreType;
+ }
+
+ public void setTrustStoreType(String trustStoreType) {
+ this.trustStoreType = trustStoreType;
+ }
+
+ public String getKeyStoreType() {
+ return keyStoreType;
+ }
+
+ public void setKeyStoreType(String keyStoreType) {
+ this.keyStoreType = keyStoreType;
+ }
+
+ public String getTrustStorePass() {
+ return new String(trustStorePass);
+ }
+
+ public void setTrustStorePass(String trustStorePass) {
+ this.trustStorePass = trustStorePass.toCharArray();
+ }
+
+ public String getKeyStorePass() {
+ return new String(keyStorePass);
+ }
+
+ public void setKeyStorePass(String keyStorePass) {
+ this.keyStorePass = keyStorePass.toCharArray();
+ }
+
+ public String getTrustStoreFile() {
+ return trustStoreFile;
+ }
+
+ public void setTrustStoreFile(String trustStoreFile) {
+ this.trustStoreFile = trustStoreFile;
+ }
+
+ public String getKeyStoreFile() {
+ return keyStoreFile;
+ }
+
+ public void setKeyStoreFile(String keyStoreFile) {
+ this.keyStoreFile = keyStoreFile;
+ }
+
+ public String getTrustStoreAlgorithm() {
+ return trustStoreAlgorithm;
+ }
+
+ public void setTrustStoreAlgorithm(String trustStoreAlgorithm) {
+ this.trustStoreAlgorithm = trustStoreAlgorithm;
+ }
+
+ public String getKeyStoreAlgorithm() {
+ return keyStoreAlgorithm;
+ }
+
+ public void setKeyStoreAlgorithm(String keyStoreAlgorithm) {
+ this.keyStoreAlgorithm = keyStoreAlgorithm;
+ }
+
+ public String getSecurityProtocol() {
+ return securityProtocol;
+ }
+
+ public void setSecurityProtocol(String securityProtocol) {
+ this.securityProtocol = securityProtocol;
+ }
+
+ public boolean isNeedClientAuth() {
+ return needClientAuth;
+ }
+
+ public void setNeedClientAuth(boolean needClientAuth) {
+ this.needClientAuth = needClientAuth;
+ }
+
+ public boolean isWantClientAuth() {
+ return wantClientAuth;
+ }
+
+ public void setWantClientAuth(boolean wantClientAuth) {
+ this.wantClientAuth = wantClientAuth;
+ }
+
+ public SSLContext createSSLContext() {
+ SSLContext sslContext = null;
+
+ try{
+ TrustManagerFactory trustManagerFactory = null;
+ KeyManagerFactory keyManagerFactory = null;
+
+ try {
+ KeyStore trustStore = KeyStore.getInstance(keyStoreType);
+ trustStore.load(new FileInputStream(trustStoreFile),
+ trustStorePass);
+
+ trustManagerFactory =
+ TrustManagerFactory.getInstance(trustStoreAlgorithm);
+ trustManagerFactory.init(trustStore);
+ } catch (Exception e) {
+ }
+
+ try {
+ KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+ keyStore.load(new FileInputStream(keyStoreFile),
+ keyStorePass);
+
+
+ keyManagerFactory =
+ KeyManagerFactory.getInstance(keyStoreAlgorithm);
+ keyManagerFactory.init(keyStore, keyStorePass);
+ } catch (Exception e) {
+ }
+
+ sslContext = SSLContext.getInstance(securityProtocol);
+ sslContext.init(keyManagerFactory != null ? keyManagerFactory.getKeyManagers() : null,
+ trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null,
+ null);
+ } catch(Exception ex){
+ }
+
+ return sslContext;
+ }
+ }
Index: test/java/com/sun/grizzly/SSLConnectionTest.java
*** C:\Projects\Grizzly\trunk\modules\grizzly\src\test\java\com\sun\grizzly\SSLConnectionTest.java Locally New
--- C:\Projects\Grizzly\trunk\modules\grizzly\src\test\java\com\sun\grizzly\SSLConnectionTest.java Locally New
***************
*** 1,0 ****
--- 1,315 ----
+ /*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the License). You may not use this file except in
+ * compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://glassfish.dev.java.net/public/CDDLv1.0.html or
+ * glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * Header Notice in each file and include the License file
+ * at glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * If applicable, add the following below the CDDL Header,
+ * with the fields enclosed by brackets [] replaced by
+ * you own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ */
+
+ package com.sun.grizzly;
+
+ import com.sun.grizzly.filter.SSLReadFilter;
+ import com.sun.grizzly.utils.ControllerUtils;
+ import com.sun.grizzly.utils.SSLEchoFilter;
+ import java.io.IOException;
+ import java.net.InetSocketAddress;
+ import java.net.URL;
+ import java.nio.ByteBuffer;
+ import java.nio.channels.SelectionKey;
+ import java.util.Arrays;
+ import java.util.Enumeration;
+ import java.util.concurrent.CountDownLatch;
+ import java.util.concurrent.TimeUnit;
+ import javax.net.ssl.SSLContext;
+ import junit.framework.TestCase;
+
+ /**
+ * @author Alexey Stashok
+ */
+ public class SSLConnectionTest extends TestCase {
+
+ private static final String TRUST_STORE_PROP = "javax.net.ssl.trustStore";
+ private static final String KEY_STORE_PROP = "javax.net.ssl.keyStore";
+
+ public static final int PORT = 18888;
+ public static final int PACKETS_COUNT = 10;
+ public static final int CLIENTS_COUNT = 99;
+
+ /**
+ * A <code>SSLCallbackHandler</code> handler invoked by the TCPSelectorHandler
+ * when a non blocking operation is ready to be processed.
+ */
+ private SSLCallbackHandler callbackHandler;
+
+ private SSLConfig sslConfig;
+
+
+ @Override
+ public void setUp() {
+ sslConfig = new SSLConfig();
+ ClassLoader cl = getClass().getClassLoader();
+ // override system properties
+ URL cacertsUrl = cl.getResource("ssltest-cacerts.jks");
+ if (cacertsUrl != null) {
+ sslConfig.setTrustStoreFile(cacertsUrl.getFile());
+ }
+
+ // override system properties
+ URL keystoreUrl = cl.getResource("ssltest-keystore.jks");
+ if (keystoreUrl != null) {
+ sslConfig.setKeyStoreFile(keystoreUrl.getFile());
+ }
+
+ SSLConfig.DEFAULT_CONFIG = sslConfig;
+ }
+
+ public void testSimplePacket() throws IOException {
+
+ final Controller controller = createSSLController(SSLConfig.DEFAULT_CONFIG.createSSLContext());
+ ControllerUtils.startController(controller);
+ final SSLConnectorHandler sslConnector = (SSLConnectorHandler) controller.
+ acquireConnectorHandler(Controller.Protocol.TLS);
+
+ try {
+ final byte[] testData = "Hello".getBytes();
+ final byte[] response = new byte[sslConnector.getApplicationBufferSize()];
+
+ final ByteBuffer writeBB = ByteBuffer.wrap(testData);
+ final ByteBuffer readBB = ByteBuffer.wrap(response);
+ final CountDownLatch handshakeDoneLatch = new CountDownLatch(1);
+ final CountDownLatch responseArrivedLatch = new CountDownLatch(1);
+
+ callbackHandler = createCallbackHandler(controller, sslConnector, responseArrivedLatch, handshakeDoneLatch, writeBB, readBB);
+
+ try {
+ sslConnector.connect(new InetSocketAddress("localhost", PORT), callbackHandler);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+
+ waitOnLatch(handshakeDoneLatch, 10, TimeUnit.SECONDS);
+ assertTrue(sslConnector.isHandshakeDone());
+
+ long nWrite = sslConnector.write(writeBB, false);
+
+ long nRead = -1;
+
+ // All bytes written
+ if (nWrite == testData.length) {
+ nRead = sslConnector.read(readBB, false);
+ }
+
+ if (nRead != nWrite) {
+ waitOnLatch(responseArrivedLatch, 5, TimeUnit.SECONDS);
+ }
+
+ assertTrue(Arrays.equals(testData, toArray(readBB)));
+ } finally {
+ try {
+ sslConnector.close();
+ controller.releaseConnectorHandler(sslConnector);
+ controller.stop();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+
+ public void testSeveralPackets() throws IOException {
+ final Controller controller = createSSLController(SSLConfig.DEFAULT_CONFIG.createSSLContext());
+ ControllerUtils.startController(controller);
+ try {
+
+ for(int i=0; i<CLIENTS_COUNT; i++) {
+ //System.out.println("Client#" + i);
+ final SSLConnectorHandler sslConnector =
+ (SSLConnectorHandler) controller.acquireConnectorHandler(Controller.Protocol.TLS);
+ sslConnector.setController(controller);
+ final byte[] testData = new String("Hello. Client#" + i + " Packet#000").getBytes();
+ final byte[] response = new byte[sslConnector.getApplicationBufferSize()];
+
+ final ByteBuffer writeBB = ByteBuffer.wrap(testData);
+ final ByteBuffer readBB = ByteBuffer.wrap(response);
+
+ final CountDownLatch[] responseArrivedLatchHolder = new CountDownLatch[1];
+ final CountDownLatch[] handshakeDoneLatchHolder = new CountDownLatch[1];
+ CountDownLatch handshakeDoneLatch = new CountDownLatch(1);
+ handshakeDoneLatchHolder[0] = handshakeDoneLatch;
+ callbackHandler = createCallbackHandler(controller, sslConnector,
+ responseArrivedLatchHolder, handshakeDoneLatchHolder,
+ writeBB, readBB);
+
+ try {
+ sslConnector.connect(new InetSocketAddress("localhost", PORT),
+ callbackHandler);
+
+ waitOnLatch(handshakeDoneLatch, 10, TimeUnit.SECONDS);
+ assertTrue(sslConnector.isHandshakeDone());
+
+ for(int j=0; j<PACKETS_COUNT; j++) {
+ //System.out.println("Packet#" + j);
+ CountDownLatch responseArrivedLatch = new CountDownLatch(1);
+ responseArrivedLatchHolder[0] = responseArrivedLatch;
+ readBB.clear();
+ writeBB.position(writeBB.limit() - 3);
+ byte[] packetNum = Integer.toString(j).getBytes();
+ writeBB.put(packetNum);
+ writeBB.position(0);
+ long nWrite = sslConnector.write(writeBB, false);
+ long nRead = -1;
+
+ // All bytes written
+ if (nWrite == testData.length){
+ nRead = sslConnector.read(readBB, false);
+ }
+
+ if (nRead != nWrite){
+ waitOnLatch(responseArrivedLatch, 15, TimeUnit.SECONDS);
+ }
+ String val1 = new String(testData);
+ String val2 = new String(toArray(readBB));
+ //System.out.println("Assert. client#" + i + " packet#" + j + " Pattern: " + val1 + " Came: " + val2);
+ assertEquals(val1, val2);
+ }
+ } finally {
+ sslConnector.close();
+ controller.releaseConnectorHandler(sslConnector);
+ }
+ }
+ } finally {
+ try{
+ controller.stop();
+ } catch (Throwable t){
+ t.printStackTrace();
+ }
+ }
+ }
+
+ private Controller createSSLController(SSLContext sslContext) {
+ final SSLReadFilter readFilter = new SSLReadFilter();
+ readFilter.setSSLContext(sslContext);
+
+ final ProtocolFilter echoFilter = new SSLEchoFilter();
+
+ SSLSelectorHandler selectorHandler = new SSLSelectorHandler();
+ selectorHandler.setPort(PORT);
+
+ final Controller controller = new Controller();
+
+ controller.setSelectorHandler(selectorHandler);
+
+ controller.setProtocolChainInstanceHandler(new DefaultProtocolChainInstanceHandler() {
+
+ public ProtocolChain poll() {
+ ProtocolChain protocolChain = protocolChains.poll();
+ if (protocolChain == null) {
+ protocolChain = new DefaultProtocolChain();
+ protocolChain.addFilter(readFilter);
+ protocolChain.addFilter(echoFilter);
+ }
+ return protocolChain;
+ }
+ });
+
+ return controller;
+ }
+
+ private SSLCallbackHandler createCallbackHandler(final Controller controller,
+ final SSLConnectorHandler sslConnector,
+ final CountDownLatch responseArrivedLatch,
+ final CountDownLatch handshakeDoneLatch,
+ final ByteBuffer writeBB, final ByteBuffer readBB) {
+
+ return createCallbackHandler(controller, sslConnector,
+ new CountDownLatch[] {responseArrivedLatch},
+ new CountDownLatch[] {handshakeDoneLatch},
+ writeBB, readBB);
+ }
+
+ private SSLCallbackHandler createCallbackHandler(final Controller controller,
+ final SSLConnectorHandler sslConnector,
+ final CountDownLatch[] responseArrivedLatchHolder,
+ final CountDownLatch[] handshakeDoneLatchHolder,
+ final ByteBuffer writeBB, final ByteBuffer readBB) {
+
+ return new SSLCallbackHandler<Context>() {
+
+ private int readTry;
+
+ public void onConnect(IOEvent<Context> ioEvent) {
+ SelectionKey key = ioEvent.attachment().getSelectionKey();
+ sslConnector.finishConnect(key);
+ try {
+ if (sslConnector.handshake(readBB, false)) {
+ onHandshake(ioEvent);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void onRead(IOEvent<Context> ioEvent) {
+ try {
+ long nRead = sslConnector.read(readBB, false);
+
+ if (nRead > 0 || readTry++ > 2) {
+ responseArrivedLatchHolder[0].countDown();
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ controller.cancelKey(ioEvent.attachment().getSelectionKey());
+ }
+ }
+
+ public void onWrite(IOEvent<Context> ioEvent) {
+ SelectionKey key = ioEvent.attachment().getSelectionKey();
+ try {
+ while (writeBB.hasRemaining()) {
+ long nWrite = sslConnector.write(writeBB, false);
+
+ if (nWrite == 0) {
+ return;
+ }
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ controller.cancelKey(key);
+ }
+ }
+
+ public void onHandshake(IOEvent<Context> ioEvent) {
+ readBB.clear();
+ handshakeDoneLatchHolder[0].countDown();
+ }
+ };
+ }
+
+ public void waitOnLatch(CountDownLatch latch, int timeout, TimeUnit timeUnit) {
+ try {
+ latch.await(timeout, timeUnit);
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ private byte[] toArray(ByteBuffer bb) {
+ byte[] buf = new byte[bb.remaining()];
+ bb.get(buf);
+ return buf;
+ }
+ }
Index: main/java/com/sun/grizzly/SSLConnectorHandler.java
*** C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly\SSLConnectorHandler.java Locally New
--- C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly\SSLConnectorHandler.java Locally New
***************
*** 1,0 ****
--- 1,905 ----
+ /*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the License). You may not use this file except in
+ * compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://glassfish.dev.java.net/public/CDDLv1.0.html or
+ * glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * Header Notice in each file and include the License file
+ * at glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * If applicable, add the following below the CDDL Header,
+ * with the fields enclosed by brackets [] replaced by
+ * you own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ */
+ package com.sun.grizzly;
+
+ import com.sun.grizzly.Controller.Protocol;
+ import com.sun.grizzly.util.ByteBufferInputStream;
+ import com.sun.grizzly.util.OutputWriter;
+ import com.sun.grizzly.util.SSLOutputWriter;
+ import com.sun.grizzly.util.SSLUtils;
+ import java.io.EOFException;
+ import java.io.IOException;
+ import java.net.SocketAddress;
+ import java.nio.ByteBuffer;
+ import java.nio.channels.AlreadyConnectedException;
+ import java.nio.channels.NotYetConnectedException;
+ import java.nio.channels.SelectableChannel;
+ import java.nio.channels.SelectionKey;
+ import java.nio.channels.SocketChannel;
+ import java.util.concurrent.CountDownLatch;
+ import java.util.concurrent.TimeUnit;
+ 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.SSLException;
+
+ /**
+ * Non blocking SSL Connector Handler. The recommended way to use this class
+ * is by creating an external Controller and share the same SelectorHandler
+ * instance.
+ *
+ * Recommended
+ * -----------
+ * Controller controller = new Controller();
+ * // new SSLSelectorHandler(true) means the Selector will be used only
+ * // for client operation (OP_READ, OP_WRITE, OP_CONNECT).
+ * SSLSelectorHandler sslSelectorHandler = new SSLSelectorHandler(true);
+ * controller.setSelectorHandler(sslSelectorHandler);
+ * SSLConnectorHandler sslConnectorHandler = new SSLConnectorHandler();
+ * sslConnectorHandler.connect(localhost,port, new SSLCallbackHandler(){...},
+ * sslSelectorHandler);
+ * SSLConnectorHandler sslConnectorHandler2 = new SSLConnectorHandler();
+ * sslConnectorHandler2.connect(localhost,port, new SSLCallbackHandler(){...},
+ * sslSelectorHandler);
+ *
+ * Not recommended (but still works)
+ * ---------------------------------
+ * SSLConnectorHandler sslConnectorHandler = new SSLConnectorHandler();
+ * sslConnectorHandler.connect(localhost,port);
+ *
+ * Internally, an new Controller will be created everytime connect(localhost,port)
+ * is invoked, which has an impact on performance.
+ *
+ * As common comment: developer should be very careful if dealing directly with
+ * <code>SSLConnectorHandler</code>'s underlying socket channel! In most cases
+ * there is no need to do this, but use read, write methods provided
+ * by <code>SSLConnectorHandler</code>
+ *
+ * @author Alexey Stashok
+ * @author Jeanfrancois Arcand
+ */
+ public class SSLConnectorHandler implements ConnectorHandler<SSLSelectorHandler, SSLCallbackHandler>, CallbackHandler {
+
+ /**
+ * The underlying SSLSelectorHandler used to mange SelectionKeys.
+ */
+ private SSLSelectorHandler selectorHandler;
+
+
+ /**
+ * A blocking <code>InputStream</code> that use a pool of Selector
+ * to execute a blocking read operation.
+ */
+ private ByteBufferInputStream inputStream;
+
+ /**
+ * A <code>SSLCallbackHandler</code> handler invoked by the TCPSelectorHandler
+ * when a non blocking operation is ready to be processed.
+ */
+ private SSLCallbackHandler callbackHandler;
+
+ /*
+ * An empty ByteBuffer used for handshaking
+ */
+ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
+
+ /**
+ * Input buffer for reading encrypted data from channel
+ */
+ private ByteBuffer securedInputBuffer;
+
+ /**
+ * Output buffer, which contains encrypted data ready for writing to channel
+ */
+ private ByteBuffer securedOutputBuffer;
+
+ /**
+ * Buffer, where application data could be written during a asynchronous handshaking.
+ * It is set when user application calls: SSLConnectorHandler.handshake(appDataBuffer)
+ * and references appDataBuffer.
+ */
+ private ByteBuffer asyncHandshakeBuffer;
+
+ /**
+ * The connection's SocketChannel.
+ */
+ private SocketChannel socketChannel;
+
+ /**
+ * Default <code>SSLContext</code>, created on
+ * top of default <code>SSLConfiguration</code>
+ */
+ private static volatile SSLContext defaultSSLContext;
+
+ /**
+ * Is the connection established.
+ */
+ private volatile boolean isConnected;
+
+ /**
+ * Is the handshake phase completed
+ */
+ private volatile boolean isHandshakeDone;
+
+ /**
+ * The internal Controller used (in case not specified).
+ */
+ private Controller controller;
+
+ /**
+ * IsConnected Latch related
+ */
+ private CountDownLatch isConnectedLatch;
+
+ /**
+ * Are we creating a controller every run.
+ */
+ private boolean isStandalone = false;
+
+ /**
+ * Is async handshake in progress
+ */
+ private boolean isProcessingAsyncHandshake;
+
+ /**
+ * Result of last <code>SSLEngine</code> operation
+ */
+ private SSLEngineResult sslLastOperationResult;
+
+ /**
+ * Current handshake status
+ */
+ private SSLEngineResult.HandshakeStatus handshakeStatus;
+
+ /**
+ * Current <code>SSLEngine</code> status
+ */
+ private SSLEngineResult.Status sslEngineStatus = null;
+
+
+ /**
+ * Are we creating a controller every run.
+ */
+ private boolean delegateSSLTasks;
+
+ /**
+ * Connector's <code>SSLEngine</code>
+ */
+ private SSLEngine sslEngine;
+
+ /**
+ * Connector's <code>SSLContext</code>
+ */
+ private SSLContext sslContext;
+
+ public SSLConnectorHandler() {
+ this(defaultSSLContext);
+ }
+
+ public SSLConnectorHandler(SSLConfig sslConfig) {
+ this(sslConfig.createSSLContext());
+ }
+
+ public SSLConnectorHandler(SSLContext sslContext) {
+ if (sslContext == null) {
+ if (defaultSSLContext == null) {
+ synchronized (SSLConnectorHandler.class) {
+ if (defaultSSLContext == null) {
+ defaultSSLContext = SSLConfig.DEFAULT_CONFIG.createSSLContext();
+ }
+ }
+ }
+
+ sslContext = defaultSSLContext;
+ }
+
+ this.sslContext = sslContext;
+ }
+
+ public boolean getDelegateSSLTasks() {
+ return delegateSSLTasks;
+ }
+
+ public void setDelegateSSLTasks(boolean delegateSSLTasks) {
+ this.delegateSSLTasks = delegateSSLTasks;
+ }
+
+ /**
+ * Connect to hostname:port. When an aysnchronous event happens (e.g
+ * OP_READ or OP_WRITE), the <code>Controller</code> will invoke
+ * the CallBackHandler.
+ * @param remoteAddress remote address to connect
+ * @param callbackHandler the handler invoked by the Controller when
+ * an non blocking operation is ready to be handled.
+ * @throws java.io.IOException
+ */
+ public void connect(SocketAddress remoteAddress, SSLCallbackHandler callbackHandler) throws IOException {
+ connect(remoteAddress, null, callbackHandler);
+ }
+
+ /**
+ * Connect to hostname:port. When an aysnchronous event happens (e.g
+ * OP_READ or OP_WRITE), the <code>Controller</code> will invoke
+ * the CallBackHandler.
+ * @param remoteAddress remote address to connect
+ * @param localAddress local address to bind
+ * @param callbackHandler the handler invoked by the Controller when
+ * an non blocking operation is ready to be handled.
+ * @throws java.io.IOException
+ */
+ public void connect(SocketAddress remoteAddress, SocketAddress localAddress, SSLCallbackHandler callbackHandler) throws IOException {
+ if (controller == null) {
+ throw new IllegalStateException("Controller cannot be null");
+ }
+
+ connect(remoteAddress, localAddress, callbackHandler, (SSLSelectorHandler) controller.getSelectorHandler(protocol()));
+ }
+
+ /**
+ * Connect to hostname:port. When an aysnchronous event happens (e.g
+ * OP_READ or OP_WRITE), the <code>Controller</code> will invoke
+ * the CallBackHandler.
+ * @param remoteAddress remote address to connect
+ * @param callbackHandler the handler invoked by the Controller when
+ * an non blocking operation is ready to be handled.
+ * @param selectorHandler an instance of SelectorHandler.
+ * @throws java.io.IOException
+ */
+ public void connect(SocketAddress remoteAddress, SSLCallbackHandler callbackHandler, SSLSelectorHandler selectorHandler) throws IOException {
+ connect(remoteAddress, null, callbackHandler, selectorHandler);
+ }
+
+ /**
+ * Connect to hostname:port. When an aysnchronous event happens (e.g
+ * OP_READ or OP_WRITE), the <code>Controller</code> will invoke
+ * the CallBackHandler.
+ * @param remoteAddress remote address to connect
+ * @param localAddress local address to bin
+ * @param callbackHandler the handler invoked by the Controller when
+ * an non blocking operation is ready to be handled.
+ * @param selectorHandler an instance of SelectorHandler.
+ * @throws java.io.IOException
+ */
+ public void connect(SocketAddress remoteAddress, SocketAddress localAddress, SSLCallbackHandler callbackHandler, SSLSelectorHandler selectorHandler) throws IOException {
+ if (isConnected) {
+ throw new AlreadyConnectedException();
+ }
+
+ if (controller == null) {
+ throw new IllegalStateException("Controller cannot be null");
+ }
+
+ if (selectorHandler == null) {
+ throw new IllegalStateException("Controller cannot be null");
+ }
+
+ this.selectorHandler = selectorHandler;
+ this.callbackHandler = callbackHandler;
+
+ // Wait for the onConnect to be invoked.
+ isConnectedLatch = new CountDownLatch(1);
+
+ selectorHandler.connect(remoteAddress, localAddress, this);
+ inputStream = new ByteBufferInputStream();
+ inputStream.setSecure(true);
+
+ try {
+ isConnectedLatch.await(30, TimeUnit.SECONDS);
+ } catch (InterruptedException ex) {
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ /**
+ * Connect to hostname:port. Internally an instance of Controller and
+ * its default SelectorHandler will be created everytime this method is
+ * called. This method should be used only and only if no external
+ * Controller has been initialized.
+ * @param remoteAddress remote address to connect
+ * @throws java.io.IOException
+ */
+ public void connect(SocketAddress remoteAddress) throws IOException {
+ connect(remoteAddress, (SocketAddress) null);
+ }
+
+ /**
+ * Connect to hostname:port. Internally an instance of Controller and
+ * its default SelectorHandler will be created everytime this method is
+ * called. This method should be used only and only if no external
+ * Controller has been initialized.
+ * @param remoteAddress remote address to connect
+ * @throws java.io.IOException
+ * @param localAddress local address to bin
+ */
+ public void connect(SocketAddress remoteAddress, SocketAddress localAddress) throws IOException {
+ if (isConnected) {
+ throw new AlreadyConnectedException();
+ }
+
+ if (controller == null) {
+ isStandalone = true;
+ controller = new Controller();
+ controller.setSelectorHandler(new TCPSelectorHandler(true));
+ DefaultPipeline pipeline = new DefaultPipeline();
+ pipeline.initPipeline();
+ pipeline.startPipeline();
+ controller.setPipeline(pipeline);
+
+ callbackHandler = new SSLCallbackHandler<Context>() {
+
+ public void onConnect(IOEvent<Context> ioEvent) {
+ SelectionKey key = ioEvent.attachment().getSelectionKey();
+ socketChannel = (SocketChannel) key.channel();
+ finishConnect(key);
+ getController().registerKey(key, SelectionKey.OP_WRITE, Protocol.TCP);
+ }
+
+ public void onRead(IOEvent<Context> ioEvent) {
+ }
+
+ public void onWrite(IOEvent<Context> ioEvent) {
+ }
+
+ public void onHandshake(IOEvent<Context> ioEvent) {
+ }
+ };
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ try {
+ pipeline.execute(new Context() {
+ @Override
+ public Object call() throws Exception {
+ latch.countDown();
+ controller.start();
+ return null;
+ }
+ });
+ } catch (PipelineFullException ex) {
+ throw new IOException(ex.getMessage());
+ }
+
+ try {
+ latch.await();
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ }
+ }
+
+ connect(remoteAddress, localAddress, callbackHandler, (SSLSelectorHandler) controller.getSelectorHandler(protocol()));
+ }
+
+ /**
+ * Initiate SSL handshake phase.
+ * Handshake is required to be done after connection established.
+ *
+ * @param byteBuffer Application <code>ByteBuffer</code>, where application data
+ * will be stored
+ * @param blocking true, if handshake should be done in blocking mode, for non-blocking false
+ * @return If blocking parameter is true - method should always return true if handshake is done,
+ * or throw IOException otherwise. For non-blocking mode method returns true if handshake is done, or false
+ * if handshake will be completed in non-blocking manner.
+ * @throws java.io.IOException if some error occurs during processing I/O operations/
+ */
+ public boolean handshake(ByteBuffer byteBuffer, boolean blocking) throws IOException {
+ sslEngine.beginHandshake();
+ handshakeStatus = sslEngine.getHandshakeStatus();
+
+ if (blocking) {
+ SSLUtils.doHandshake(socketChannel, byteBuffer, securedInputBuffer, securedOutputBuffer, sslEngine, handshakeStatus);
+ finishHandshake();
+
+ // Sync should be always done
+ return true;
+ } else {
+ doAsyncHandshake(byteBuffer);
+
+ // is async handshake completed
+ return isHandshakeDone();
+ }
+ }
+
+ /**
+ * Read bytes. If blocking is set to <tt>true</tt>, a pool of temporary
+ * <code>Selector</code> will be used to read bytes.
+ * @param byteBuffer The byteBuffer to store bytes.
+ * @param blocking <tt>true</tt> if a a pool of temporary Selector
+ * is required to handle a blocking read.
+ * @return number of bytes read
+ * @throws java.io.IOException
+ */
+ public long read(ByteBuffer byteBuffer, boolean blocking) throws IOException {
+ if (!isConnected) {
+ throw new NotYetConnectedException();
+ }
+
+ if (blocking) {
+ inputStream.setSelectionKey(getSelectionKey());
+ return inputStream.read(byteBuffer);
+ } else {
+ int nRead = doReadAsync(byteBuffer);
+
+ if (!blocking && nRead == 0) {
+ registerSelectionKeyFor(SelectionKey.OP_READ);
+ return 0;
+ }
+ return nRead;
+ }
+ }
+
+
+ /**
+ * Writes bytes. If blocking is set to <tt>true</tt>, a pool of temporary
+ * <code>Selector</code> will be used to writes bytes.
+ * @param byteBuffer The byteBuffer to write.
+ * @param blocking <tt>true</tt> if a a pool of temporary Selector
+ * is required to handle a blocking write.
+ * @return number of bytes written. As on socket encrypted data is written -
+ * it's impossible to determine correspondent source data, which was written.
+ * That's why method either returns 0, if not all the source data was written,
+ * or byteBuffer.remaining() - if all the data was written
+ * @throws java.io.IOException
+ */
+ public long write(ByteBuffer byteBuffer, boolean blocking) throws IOException {
+ if (!isConnected) {
+ throw new NotYetConnectedException();
+ }
+
+ if (blocking) {
+ return SSLOutputWriter.flushChannel(socketChannel, byteBuffer, securedOutputBuffer, sslEngine);
+ } else {
+ if (callbackHandler == null) {
+ throw new IllegalStateException("Non blocking write needs a CallbackHandler");
+ }
+
+ int nWrite = 1;
+ int totalWrite = 0;
+
+ while (nWrite > 0 && byteBuffer.hasRemaining()) {
+ nWrite = doWriteAsync(byteBuffer);
+ totalWrite += nWrite;
+ }
+
+ if (totalWrite == 0 && byteBuffer.hasRemaining()) {
+ registerSelectionKeyFor(SelectionKey.OP_WRITE);
+ }
+ return nWrite;
+ }
+ }
+
+
+ /**
+ * Close the underlying connection.
+ */
+ public void close() throws IOException {
+ if (socketChannel != null) {
+ try {
+ if (securedOutputBuffer.hasRemaining()) {
+ // if there is something is securedOutputBuffer - flush it
+ OutputWriter.flushChannel(socketChannel, securedOutputBuffer);
+ }
+
+ // Close secure outbound channel and flush data
+ sslEngine.closeOutbound();
+ SSLUtils.wrap(EMPTY_BUFFER, securedOutputBuffer, sslEngine);
+ OutputWriter.flushChannel(socketChannel, securedOutputBuffer);
+ } catch (IOException e) {
+ }
+
+ if (selectorHandler != null) {
+ SelectionKey key = socketChannel.keyFor(selectorHandler.getSelector());
+
+ if (key == null) {
+ return;
+ }
+ key.cancel();
+ key.attach(null);
+ }
+
+ socketChannel.close();
+ }
+
+ if (controller != null && isStandalone) {
+ controller.stop();
+ controller = null;
+ }
+
+ sslEngine = null;
+ asyncHandshakeBuffer = null;
+ isStandalone = false;
+ isConnected = false;
+ isHandshakeDone = false;
+ }
+
+
+ /**
+ * Finish handling the OP_CONNECT interest ops.
+ * @param key - a <code>SelectionKey</code>
+ */
+ public void finishConnect(SelectionKey key) {
+ try {
+ socketChannel = (SocketChannel) key.channel();
+ socketChannel.finishConnect();
+ isConnected = socketChannel.isConnected();
+ initSSLEngineIfRequired();
+ } catch (IOException ex) {
+ // XXX LOG ME
+ ex.printStackTrace();
+ } finally {
+ isConnectedLatch.countDown();
+ }
+ }
+
+ /**
+ * Changes SSLConnectorHandler state, after handshake operation is done.
+ * Normally should not be called by outside Grizzly, just in case when custom
+ * handshake code was used.
+ */
+ public void finishHandshake() {
+ isProcessingAsyncHandshake = false;
+ isHandshakeDone = true;
+ }
+
+ /**
+ * A token decribing the protocol supported by an implementation of this
+ * interface
+ * @return this <code>ConnectorHandler</code>'s protocol
+ */
+ public Controller.Protocol protocol() {
+ return Controller.Protocol.TLS;
+ }
+
+
+ /**
+ * Is the underlying SocketChannel connected.
+ * @return <tt>true</tt> if connected, otherwise <tt>false</tt>
+ */
+ public boolean isConnected() {
+ return isConnected;
+ }
+
+ /**
+ * Is the underlying SocketChannel connected.
+ * @return <tt>true</tt> if connected, otherwise <tt>false</tt>
+ */
+ public boolean isHandshakeDone() {
+ return isHandshakeDone && !isProcessingAsyncHandshake;
+ }
+
+ public Controller getController() {
+ return controller;
+ }
+
+
+ public void setController(Controller controller) {
+ this.controller = controller;
+ }
+
+ public SelectableChannel getUnderlyingChannel() {
+ return socketChannel;
+ }
+
+ public SSLCallbackHandler getCallbackHandler() {
+ return callbackHandler;
+ }
+
+ public void setCallbackHandler(SSLCallbackHandler callbackHandler) {
+ this.callbackHandler = callbackHandler;
+ }
+
+ public SSLSelectorHandler getSelectorHandler() {
+ return selectorHandler;
+ }
+
+ /**
+ * Gets the size of the largest application buffer that may occur when
+ * using this session.
+ * SSLEngine application data buffers must be large enough to hold the
+ * application data from any inbound network application data packet
+ * received. Typically, outbound application data buffers can be of any size.
+ *
+ * (javadoc is taken from SSLSession.getApplicationBufferSize())
+ * @return largets application buffer size, which may occur
+ */
+ public int getApplicationBufferSize() {
+ initSSLEngineIfRequired();
+ return sslEngine.getSession().getApplicationBufferSize();
+ }
+
+ public void onConnect(IOEvent ioEvent) {
+ callbackHandler.onConnect(ioEvent);
+ }
+
+ public void onRead(IOEvent ioEvent) {
+ try {
+ // if processing handshake - pass the data to handshake related code
+ if (isProcessingAsyncHandshake) {
+ doAsyncHandshake(asyncHandshakeBuffer);
+ if (isHandshakeDone()) {
+ callbackHandler.onHandshake(ioEvent);
+ } else {
+ return;
+ }
+ }
+
+ callbackHandler.onRead(ioEvent);
+ } catch (IOException ex) {
+ //log
+ }
+ }
+
+ public void onWrite(IOEvent ioEvent) {
+ try {
+ // check if all the secured data was written, if not -
+ // flush as much as possible.
+ if (!securedOutputBuffer.hasRemaining() || flushSecuredOutputBuffer()) {
+ // if no encrypted data left in buffer - continue processing
+ if (isProcessingAsyncHandshake) {
+ doAsyncHandshake(asyncHandshakeBuffer);
+ if (isHandshakeDone()) {
+ callbackHandler.onHandshake(ioEvent);
+ } else {
+ return;
+ }
+ }
+
+ callbackHandler.onWrite(ioEvent);
+ }
+ } catch (IOException ex) {
+ // log
+ }
+ }
+
+ /**
+ * Read a data from channel in async mode and decrypt
+ * @param byteBuffer buffer for decrypted data
+ * @return number of bytes read to byteBuffer
+ * @throws java.io.IOException
+ */
+ private int doReadAsync(ByteBuffer byteBuffer) throws IOException {
+ // Clear or compact secured input buffer
+ clearOrCompactBuffer(securedInputBuffer);
+
+ // Read data to secured buffer
+ int bytesRead = socketChannel.read(securedInputBuffer);
+
+ if (bytesRead == -1) {
+ try {
+ sslEngine.closeInbound();
+ // check if there is some secured data still available
+ if (securedInputBuffer.position() == 0 ||
+ sslEngineStatus == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
+ return -1;
+ }
+ } catch (SSLException e) {
+ return -1;
+ }
+ }
+
+ securedInputBuffer.flip();
+
+ if (bytesRead == 0 && !securedInputBuffer.hasRemaining()) {
+ return 0;
+ }
+
+ clearOrCompactBuffer(byteBuffer);
+
+ SSLEngineResult result = null;
+ do {
+ result = sslEngine.unwrap(securedInputBuffer, byteBuffer);
+ // During handshake phase several unwrap actions could be executed on read data
+ } while (result.getStatus() == SSLEngineResult.Status.OK && result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP && result.bytesProduced() == 0);
+
+ updateSSLEngineStatus(result);
+
+ if (sslEngineStatus == SSLEngineResult.Status.CLOSED) {
+ return -1;
+ } else if (sslEngineStatus == SSLEngineResult.Status.BUFFER_OVERFLOW) {
+ throw new IllegalStateException("Application buffer is overflowed");
+ }
+
+ byteBuffer.flip();
+
+ return result.bytesProduced();
+ }
+
+ /**
+ * Write secured data to channel in async mode
+ *
+ * @param byteBuffer non-crypted data buffer
+ * @return number of bytes written. As non-crypted data is passed, but crypted
+ * data is written on channel - it's difficult to determine correspondent size
+ * of non-crypted data which was written. That's why method returns 0, if not all
+ * the data was written, or byteBuffer.remaining() if all the source data was written.
+ * @throws java.io.IOException
+ */
+ private int doWriteAsync(ByteBuffer byteBuffer) throws IOException {
+ if (securedOutputBuffer.hasRemaining() && !flushSecuredOutputBuffer()) {
+ return 0;
+ }
+
+ securedOutputBuffer.clear();
+ SSLEngineResult result = SSLUtils.wrap(byteBuffer, securedOutputBuffer, sslEngine);
+
+ updateSSLEngineStatus(result);
+
+ int bytesWritten = socketChannel.write(securedOutputBuffer);
+ if (securedOutputBuffer.hasRemaining()) {
+ return 0;
+ }
+
+ return result.bytesConsumed();
+ }
+
+ /**
+ * Perform an SSL handshake in async mode.
+ * @param byteBuffer The application <code>ByteBuffer</code>
+ *
+ * @throws IOException if the handshake fail.
+ */
+ private void doAsyncHandshake(ByteBuffer byteBuffer) throws IOException {
+ SSLEngineResult result;
+ isProcessingAsyncHandshake = true;
+ asyncHandshakeBuffer = byteBuffer;
+ while (handshakeStatus != HandshakeStatus.FINISHED) {
+ switch (handshakeStatus) {
+ case NEED_WRAP:
+ result = SSLUtils.wrap(EMPTY_BUFFER, securedOutputBuffer, sslEngine);
+ updateSSLEngineStatus(result);
+ switch (result.getStatus()) {
+ case OK:
+ if (!flushSecuredOutputBuffer()) {
+ return;
+ }
+ break;
+ default:
+ throw new IOException("Handshaking error: " + result.getStatus());
+ }
+
+ if (handshakeStatus != HandshakeStatus.NEED_UNWRAP) {
+ break;
+ }
+ case NEED_UNWRAP:
+ int bytesRead = doReadAsync(byteBuffer);
+ if (bytesRead == -1) {
+ try {
+ sslEngine.closeInbound();
+ } catch (IOException ex) {
+ }
+
+ throw new EOFException("Connection closed");
+ } else if (bytesRead == 0 && sslLastOperationResult.bytesConsumed() == 0) {
+ registerSelectionKeyFor(SelectionKey.OP_READ);
+ return;
+ }
+
+ if (handshakeStatus != HandshakeStatus.NEED_TASK) {
+ break;
+ }
+ case NEED_TASK:
+ handshakeStatus = executeDelegatedTask();
+ break;
+ default:
+ throw new RuntimeException("Invalid Handshaking State" + handshakeStatus);
+ }
+ }
+
+ if (isProcessingAsyncHandshake) {
+ finishHandshake();
+ }
+
+ asyncHandshakeBuffer = null;
+ }
+
+ /**
+ * Complete hanshakes operations.
+ * @return SSLEngineResult.HandshakeStatus
+ */
+ private SSLEngineResult.HandshakeStatus executeDelegatedTask() {
+ Runnable runnable;
+ while ((runnable = sslEngine.getDelegatedTask()) != null) {
+ runnable.run();
+ }
+
+ return sslEngine.getHandshakeStatus();
+ }
+
+ /**
+ * Update <code>SSLConnectorHandler</code> internal status with
+ * last <code>SSLEngine</code> operation result.
+ *
+ * @param result last <code>SSLEngine</code> operation result
+ */
+ private void updateSSLEngineStatus(SSLEngineResult result) {
+ sslLastOperationResult = result;
+ sslEngineStatus = result.getStatus();
+ handshakeStatus = result.getHandshakeStatus();
+ }
+
+ /**
+ * Clears buffer if there is no info available, or compact buffer otherwise.
+ * @param buffer byte byffer
+ */
+ private static void clearOrCompactBuffer(ByteBuffer buffer) {
+ if (!buffer.hasRemaining()) {
+ buffer.clear();
+ } else if (buffer.position() > 0) {
+ buffer.compact();
+ }
+ }
+
+ /**
+ * Gets <code>SSLConnectorHandler</code> <code>SelectionKey</code>
+ * @return <code>SelectionKey</code>
+ */
+ private SelectionKey getSelectionKey() {
+ return socketChannel.keyFor(selectorHandler.getSelector());
+ }
+
+ /**
+ * Registers <code>SSLConnectorHandler<code>'s <code>SelectionKey</code>
+ * to listen channel operations.
+ * @param ops interested channel operations
+ */
+ private void registerSelectionKeyFor(int ops) {
+ SelectionKey key = getSelectionKey();
+ key.attach(this);
+ selectorHandler.register(key, ops);
+ }
+
+ /**
+ * Flushes as much as possible bytes from the secured output buffer
+ * @return true if secured buffer was completely flushed, false otherwise
+ */
+ private boolean flushSecuredOutputBuffer() throws IOException {
+ int nWrite = 1;
+
+ while (nWrite > 0 && securedOutputBuffer.hasRemaining()) {
+ nWrite = socketChannel.write(securedOutputBuffer);
+ }
+
+ if (securedOutputBuffer.hasRemaining()) {
+ SelectionKey key = socketChannel.keyFor(selectorHandler.getSelector());
+ key.attach(callbackHandler);
+ selectorHandler.register(key, SelectionKey.OP_WRITE);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Initiate <code>SSLEngine</code> and related secure buffers
+ */
+ private void initSSLEngineIfRequired() {
+ if (sslEngine == null) {
+ sslEngine = sslContext.createSSLEngine();
+ sslEngine.setUseClientMode(true);
+
+ int bbSize = sslEngine.getSession().getPacketBufferSize();
+ securedInputBuffer = ByteBuffer.allocate(bbSize * 2);
+ securedOutputBuffer = ByteBuffer.allocate(bbSize * 2);
+ }
+ }
+ }
Index: test/java/com/sun/grizzly/utils/SSLEchoFilter.java
*** C:\Projects\Grizzly\trunk\modules\grizzly\src\test\java\com\sun\grizzly\utils\SSLEchoFilter.java Locally New
--- C:\Projects\Grizzly\trunk\modules\grizzly\src\test\java\com\sun\grizzly\utils\SSLEchoFilter.java Locally New
***************
*** 1,0 ****
--- 1,66 ----
+ /*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the License). You may not use this file except in
+ * compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://glassfish.dev.java.net/public/CDDLv1.0.html or
+ * glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * Header Notice in each file and include the License file
+ * at glassfish/bootstrap/legal/CDDLv1.0.txt.
+ * If applicable, add the following below the CDDL Header,
+ * with the fields enclosed by brackets [] replaced by
+ * you own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ */
+
+ package com.sun.grizzly.utils;
+
+ import com.sun.grizzly.*;
+ import com.sun.grizzly.util.SSLOutputWriter;
+ import com.sun.grizzly.util.WorkerThread;
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
+ import java.nio.channels.SocketChannel;
+
+ /**
+ * Simple echo filter
+ *
+ * @author Alexey Stashok
+ */
+ public class SSLEchoFilter implements ProtocolFilter {
+ public boolean execute(Context ctx) throws IOException {
+ final WorkerThread workerThread = ((WorkerThread)Thread.currentThread());
+ ByteBuffer buffer = workerThread.getByteBuffer();
+ buffer.flip();
+ if (buffer.hasRemaining()) {
+ // Store incoming data in byte[]
+ byte[] data = new byte[buffer.remaining()];
+ int position = buffer.position();
+ buffer.get(data);
+ buffer.position(position);
+
+ SocketChannel channel = (SocketChannel) ctx.getSelectionKey().channel();
+ try {
+ SSLOutputWriter.flushChannel(channel, buffer);
+ } catch (IOException ex) {
+ System.out.println("Exception. Echo \"" + new String(data) + "\"");
+ throw ex;
+ }
+ }
+
+ buffer.clear();
+ return false;
+ }
+
+ public boolean postExecute(Context ctx) throws IOException {
+ return true;
+ }
+ }