001: /*
002: * Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
019: * The latest copy of this software may be found on http://www.xsocket.org/
020: */
021: package org.xsocket.connection.spi;
022:
023: import java.io.IOException;
024: import java.nio.ByteBuffer;
025: import java.nio.channels.ClosedChannelException;
026: import java.util.ArrayList;
027: import java.util.IdentityHashMap;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.logging.Level;
031: import java.util.logging.Logger;
032:
033: import javax.net.ssl.SSLContext;
034:
035: /**
036: * SSL io handler
037: *
038: * @author grro@xsocket.org
039: */
040: final class IoSSLHandler extends ChainableIoHandler implements
041: SSLProcessor.EventHandler {
042:
043: private static final Logger LOG = Logger
044: .getLogger(IoSSLHandler.class.getName());
045:
046: // receive & send queue
047: private final IoQueue outAppDataQueue = new IoQueue();
048:
049: // sync write support
050: private final PendingWriteMap pendingWriteMap = new PendingWriteMap();
051:
052: private final IOEventHandler ioEventHandler = new IOEventHandler();
053:
054: // ssl stuff
055: private SSLProcessor sslProcessor = null;
056: private boolean isClientMode = false;
057: private boolean isSSLConnected = false;
058: private final Object initGuard = new Object();
059:
060: private IOException readException = null;
061:
062: /**
063: * constructor
064: *
065: * @param successor the successor
066: * @param sslContext the ssl context to use
067: * @param isClientMode true, if is in client mode
068: * @param memoryManager the memory manager to use
069: * @throws IOException If some other I/O error occurs
070: */
071: IoSSLHandler(ChainableIoHandler successor, SSLContext sslContext,
072: boolean isClientMode, IMemoryManager memoryManager)
073: throws IOException {
074: super (successor);
075:
076: this .isClientMode = isClientMode;
077: sslProcessor = new SSLProcessor(sslContext, isClientMode,
078: memoryManager, this );
079: }
080:
081: public void init(IIoHandlerCallback callbackHandler)
082: throws IOException {
083: setPreviousCallback(callbackHandler);
084: getSuccessor().init(ioEventHandler);
085:
086: startSSL();
087: }
088:
089: /**
090: * {@inheritDoc}
091: */
092: public boolean reset() {
093: outAppDataQueue.drain();
094: pendingWriteMap.clear();
095:
096: return super .reset();
097: }
098:
099: /**
100: * {@inheritDoc}
101: */
102: public void setPreviousCallback(IIoHandlerCallback callbackHandler) {
103: super .setPreviousCallback(callbackHandler);
104: getSuccessor().setPreviousCallback(ioEventHandler);
105: }
106:
107: /**
108: * {@inheritDoc}
109: */
110: @Override
111: public int getPendingWriteDataSize() {
112: return outAppDataQueue.getSize()
113: + super .getPendingWriteDataSize();
114: }
115:
116: /**
117: * {@inheritDoc}
118: */
119: @Override
120: public boolean hasDataToSend() {
121: return (!outAppDataQueue.isEmpty() || super .hasDataToSend());
122: }
123:
124: /**
125: * start the ssl mode
126: *
127: * @throws IOException If some other I/O error occurs
128: */
129: void startSSL() throws IOException {
130: if (!isSSLConnected) {
131: sslProcessor.start();
132: }
133:
134: if (isClientMode) {
135: synchronized (initGuard) {
136:
137: while (!isSSLConnected) {
138: if (readException != null) {
139: IOException ex = readException;
140: readException = null;
141: throw ex;
142: }
143:
144: try {
145: if (DefaultIoProvider.isDispatcherThread()) {
146: LOG
147: .warning("try to initialize ssl client within xSocket I/O thread ("
148: + Thread.currentThread()
149: .getName()
150: + "). This will cause a deadlock");
151: }
152: initGuard.wait();
153: } catch (InterruptedException ignore) {
154: }
155: }
156: }
157: }
158: }
159:
160: /**
161: * {@inheritDoc}
162: */
163: public final void close(boolean immediate) throws IOException {
164:
165: if (!immediate) {
166: flushOutgoing();
167: }
168:
169: getSuccessor().close(immediate);
170: }
171:
172: /**
173: * {@inheritDoc}
174: */
175: public final void write(ByteBuffer[] buffers)
176: throws ClosedChannelException, IOException {
177: outAppDataQueue.append(buffers);
178: flush();
179: }
180:
181: private void flush() throws ClosedChannelException, IOException {
182: synchronized (sslProcessor) {
183: if (!sslProcessor.isHandshaking()) {
184: if (!outAppDataQueue.isEmpty()) {
185: sslProcessor.encrypt(outAppDataQueue.drain());
186: }
187: } else {
188: sslProcessor.encrypt();
189: }
190: }
191: }
192:
193: /**
194: * {@inheritDoc}
195: */
196: public void flushOutgoing() throws IOException {
197:
198: }
199:
200: private synchronized void readIncomingEncryptedData(
201: ByteBuffer[] inNetDataList) throws ClosedChannelException,
202: IOException {
203: if (inNetDataList != null) {
204: if (LOG.isLoggable(Level.FINE)) {
205: int size = 0;
206: for (ByteBuffer buffer : inNetDataList) {
207: size += buffer.remaining();
208: }
209:
210: LOG.fine("received " + size + " bytes encrypted data");
211: }
212:
213: sslProcessor.decrypt(inNetDataList);
214: }
215: }
216:
217: public void onHandshakeFinished() throws IOException {
218: if (!isSSLConnected) {
219: if (LOG.isLoggable(Level.FINE)) {
220: if (isClientMode) {
221: LOG
222: .fine("["
223: + getId()
224: + "] handshake has been finished (clientMode)");
225: } else {
226: LOG
227: .fine("["
228: + getId()
229: + "] handshake has been finished (serverMode)");
230: }
231: }
232:
233: isSSLConnected = true;
234: synchronized (initGuard) {
235: initGuard.notifyAll();
236: }
237:
238: getPreviousCallback().onConnect();
239: }
240:
241: flush();
242: }
243:
244: public void onSSLProcessorClosed() throws IOException {
245: close(true);
246: }
247:
248: public void onDataDecrypted(ByteBuffer[] decryptedBufferList) {
249: if (decryptedBufferList == null) {
250: return;
251: }
252:
253: if (decryptedBufferList.length == 0) {
254: return;
255: }
256:
257: if (LOG.isLoggable(Level.FINE)) {
258: LOG.fine("reading decryted data");
259: }
260:
261: getPreviousCallback().onData(decryptedBufferList);
262: }
263:
264: public void onDataEncrypted(ByteBuffer plainData,
265: ByteBuffer encryptedData) throws IOException {
266:
267: if (encryptedData.hasRemaining()) {
268: if (LOG.isLoggable(Level.FINE)) {
269: LOG.fine("writing encryted data");
270: }
271:
272: pendingWriteMap.add(plainData, encryptedData);
273: ByteBuffer[] buffers = new ByteBuffer[1];
274: buffers[0] = encryptedData;
275: getSuccessor().write(buffers);
276: }
277: }
278:
279: private final class IOEventHandler implements IIoHandlerCallback {
280:
281: public void onData(ByteBuffer[] data) {
282: try {
283: readIncomingEncryptedData(data);
284: } catch (IOException ioe) {
285: if (LOG.isLoggable(Level.FINE)) {
286: LOG
287: .fine("["
288: + getId()
289: + "] error occured while receiving data. Reason: "
290: + ioe.toString());
291: }
292:
293: synchronized (initGuard) {
294: readException = ioe;
295: initGuard.notifyAll();
296: }
297: }
298: }
299:
300: public void onConnect() {
301:
302: }
303:
304: public void onWriteException(IOException ioException,
305: ByteBuffer data) {
306: getPreviousCallback().onWriteException(ioException, data);
307: }
308:
309: public void onWritten(ByteBuffer data) {
310: ByteBuffer plainData = pendingWriteMap
311: .getPlainIfWritten(data);
312: if (plainData != null) {
313: getPreviousCallback().onWritten(plainData);
314: } else {
315: // else case shouldn't occur, handle it nevertheless
316: getPreviousCallback().onWritten(data);
317: }
318: }
319:
320: public void onDisconnect() {
321: sslProcessor.destroy();
322: getPreviousCallback().onDisconnect();
323: }
324:
325: public void onConnectionAbnormalTerminated() {
326: getPreviousCallback().onConnectionAbnormalTerminated();
327: }
328:
329: public void onConnectionTimeout() {
330: getPreviousCallback().onConnectionTimeout();
331: }
332:
333: public void onIdleTimeout() {
334: getPreviousCallback().onIdleTimeout();
335: }
336: }
337:
338: private static final class PendingWriteMap {
339:
340: private Map<ByteBuffer, List<ByteBuffer>> plainEncryptedMapping = new IdentityHashMap<ByteBuffer, List<ByteBuffer>>();
341: private Map<ByteBuffer, ByteBuffer> encryptedPlainMapping = new IdentityHashMap<ByteBuffer, ByteBuffer>();
342:
343: public synchronized void add(ByteBuffer plain,
344: ByteBuffer encrypted) {
345: // ignore system data (plain is empty)
346: if (plain.limit() > 0) {
347: List<ByteBuffer> encryptedList = plainEncryptedMapping
348: .get(plain);
349: if (encryptedList == null) {
350: encryptedList = new ArrayList<ByteBuffer>();
351: plainEncryptedMapping.put(plain, encryptedList);
352: }
353:
354: encryptedList.add(encrypted);
355: encryptedPlainMapping.put(encrypted, plain);
356: }
357: }
358:
359: public synchronized ByteBuffer getPlainIfWritten(
360: ByteBuffer encrypted) {
361: ByteBuffer plain = encryptedPlainMapping.remove(encrypted);
362: if (plain != null) {
363: List<ByteBuffer> encryptedList = plainEncryptedMapping
364: .get(plain);
365: encryptedList.remove(encrypted);
366: if (encryptedList.isEmpty()) {
367: plainEncryptedMapping.remove(plain);
368: return plain;
369: }
370: }
371: return null;
372: }
373:
374: public synchronized void clear() {
375: plainEncryptedMapping.clear();
376: encryptedPlainMapping.clear();
377: }
378: }
379: }
|