View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.support;
21  
22  import java.nio.ByteBuffer;
23  import java.util.LinkedList;
24  import java.util.Queue;
25  import java.util.concurrent.ConcurrentLinkedQueue;
26  
27  import javax.net.ssl.SSLContext;
28  import javax.net.ssl.SSLEngine;
29  import javax.net.ssl.SSLEngineResult;
30  import javax.net.ssl.SSLException;
31  import javax.net.ssl.SSLHandshakeException;
32  import javax.net.ssl.SSLSession;
33  
34  import org.apache.mina.common.IoSession;
35  import org.apache.mina.common.WriteFuture;
36  import org.apache.mina.common.IoFilter.NextFilter;
37  import org.apache.mina.common.IoFilter.WriteRequest;
38  import org.apache.mina.common.support.DefaultWriteFuture;
39  import org.apache.mina.filter.SSLFilter;
40  import org.apache.mina.util.SessionLog;
41  
42  /**
43   * A helper class using the SSLEngine API to decrypt/encrypt data.
44   * <p>
45   * Each connection has a SSLEngine that is used through the lifetime of the connection.
46   * We allocate byte buffers for use as the outbound and inbound network buffers.
47   * These buffers handle all of the intermediary data for the SSL connection. To make things easy,
48   * we'll require outNetBuffer be completely flushed before trying to wrap any more data.
49   *
50   * @author The Apache Directory Project (mina-dev@directory.apache.org)
51   * @version $Rev: 561232 $, $Date: 2007-07-31 07:19:37 +0200 (Tue, 31 Jul 2007) $
52   */
53  public class SSLHandler {
54      private final SSLFilter parent;
55  
56      private final SSLContext ctx;
57  
58      private final IoSession session;
59  
60      private final Queue<Event> preHandshakeEventQueue = new LinkedList<Event>();
61  
62      private final Queue<Event> filterWriteEventQueue = new ConcurrentLinkedQueue<Event>();
63  
64      private final Queue<Event> messageReceivedEventQueue = new ConcurrentLinkedQueue<Event>();
65  
66      private SSLEngine sslEngine;
67  
68      /**
69       * Encrypted data from the net
70       */
71      private ByteBuffer inNetBuffer;
72  
73      /**
74       * Encrypted data to be written to the net
75       */
76      private ByteBuffer outNetBuffer;
77  
78      /**
79       * Applicaton cleartext data to be read by application
80       */
81      private ByteBuffer appBuffer;
82  
83      /**
84       * Empty buffer used during initial handshake and close operations
85       */
86      private final ByteBuffer hsBB = ByteBuffer.allocate(0);
87  
88      /**
89       * Handshake status
90       */
91      private SSLEngineResult.HandshakeStatus handshakeStatus;
92  
93      private boolean initialHandshakeComplete;
94      
95      /**
96       * Handshake complete?
97       */
98      private boolean handshakeComplete;
99  
100     private boolean writingEncryptedData;
101 
102     /**
103      * Constuctor.
104      *
105      * @param sslc
106      * @throws SSLException
107      */
108     public SSLHandler(SSLFilter parent, SSLContext sslc, IoSession session)
109             throws SSLException {
110         this.parent = parent;
111         this.session = session;
112         this.ctx = sslc;
113         init();
114     }
115 
116     public void init() throws SSLException {
117         if (sslEngine != null) {
118             return;
119         }
120 
121         sslEngine = ctx.createSSLEngine();
122         sslEngine.setUseClientMode(parent.isUseClientMode());
123 
124         if (parent.isWantClientAuth()) {
125             sslEngine.setWantClientAuth(true);
126         }
127 
128         if (parent.isNeedClientAuth()) {
129             sslEngine.setNeedClientAuth(true);
130         }
131 
132         if (parent.getEnabledCipherSuites() != null) {
133             sslEngine.setEnabledCipherSuites(parent.getEnabledCipherSuites());
134         }
135 
136         if (parent.getEnabledProtocols() != null) {
137             sslEngine.setEnabledProtocols(parent.getEnabledProtocols());
138         }
139 
140         sslEngine.beginHandshake();
141         handshakeStatus = sslEngine.getHandshakeStatus();//SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
142         handshakeComplete = false;
143         initialHandshakeComplete = false;
144 
145         SSLByteBufferPool.initiate(sslEngine);
146 
147         appBuffer = SSLByteBufferPool.getApplicationBuffer();
148 
149         inNetBuffer = SSLByteBufferPool.getPacketBuffer();
150         outNetBuffer = SSLByteBufferPool.getPacketBuffer();
151         outNetBuffer.position(0);
152         outNetBuffer.limit(0);
153 
154         writingEncryptedData = false;
155     }
156 
157     /**
158      * Release allocated ByteBuffers.
159      */
160     public void destroy() {
161         if (sslEngine == null) {
162             return;
163         }
164 
165         // Close inbound and flush all remaining data if available.
166         try {
167             sslEngine.closeInbound();
168         } catch (SSLException e) {
169             SessionLog.debug(session,
170                     "Unexpected exception from SSLEngine.closeInbound().", e);
171         }
172 
173         try {
174             do {
175                 outNetBuffer.clear();
176             } while (sslEngine.wrap(hsBB, outNetBuffer).bytesProduced() > 0);
177         } catch (SSLException e) {
178             SessionLog.debug(session,
179                     "Unexpected exception from SSLEngine.wrap().", e);
180         }
181         sslEngine.closeOutbound();
182         sslEngine = null;
183 
184         SSLByteBufferPool.release(appBuffer);
185         SSLByteBufferPool.release(inNetBuffer);
186         SSLByteBufferPool.release(outNetBuffer);
187         preHandshakeEventQueue.clear();
188     }
189 
190     public SSLFilter getParent() {
191         return parent;
192     }
193 
194     public IoSession getSession() {
195         return session;
196     }
197 
198     /**
199      * Check we are writing encrypted data.
200      */
201     public boolean isWritingEncryptedData() {
202         return writingEncryptedData;
203     }
204 
205     /**
206      * Check if handshake is completed.
207      */
208     public boolean isHandshakeComplete() {
209         return handshakeComplete;
210     }
211 
212     public boolean isInboundDone() {
213         return sslEngine == null || sslEngine.isInboundDone();
214     }
215 
216     public boolean isOutboundDone() {
217         return sslEngine == null || sslEngine.isOutboundDone();
218     }
219 
220     /**
221      * Check if there is any need to complete handshake.
222      */
223     public boolean needToCompleteHandshake() {
224         return (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP && !isInboundDone());
225     }
226 
227     public void schedulePreHandshakeWriteRequest(NextFilter nextFilter,
228             WriteRequest writeRequest) {
229         preHandshakeEventQueue.offer(new Event(EventType.FILTER_WRITE,
230                 nextFilter, writeRequest));
231     }
232 
233     public void flushPreHandshakeEvents() throws SSLException {
234         Event scheduledWrite;
235 
236         while ((scheduledWrite = preHandshakeEventQueue.poll()) != null) {
237             if (SessionLog.isDebugEnabled(session)) {
238                 SessionLog.debug(session, " Flushing buffered write request: "
239                         + scheduledWrite.data);
240             }
241             parent.filterWrite(scheduledWrite.nextFilter, session,
242                     (WriteRequest) scheduledWrite.data);
243         }
244     }
245 
246     public void scheduleFilterWrite(NextFilter nextFilter,
247             WriteRequest writeRequest) {
248         filterWriteEventQueue.offer(new Event(EventType.FILTER_WRITE,
249                 nextFilter, writeRequest));
250     }
251 
252     public void scheduleMessageReceived(NextFilter nextFilter,
253             Object message) {
254         messageReceivedEventQueue.offer(new Event(EventType.RECEIVED, nextFilter,
255                 message));
256     }
257     
258     public void flushScheduledEvents() {
259         // Fire events only when no lock is hold for this handler.
260         if (Thread.holdsLock(this)) {
261             return;
262         }
263 
264         Event e;
265         
266         // We need synchronization here inevitably because filterWrite can be
267         // called simultaneously and cause 'bad record MAC' integrity error.
268         synchronized (this) {
269             while ((e = filterWriteEventQueue.poll()) != null) {
270                 e.nextFilter.filterWrite(session, (WriteRequest) e.data);
271             }
272         }
273 
274         while ((e = messageReceivedEventQueue.poll()) != null) {
275             e.nextFilter.messageReceived(session, e.data);
276         }
277     }
278 
279     /**
280      * Call when data read from net. Will perform inial hanshake or decrypt provided
281      * Buffer.
282      * Decrytpted data reurned by getAppBuffer(), if any.
283      *
284      * @param buf buffer to decrypt
285      * @throws SSLException on errors
286      */
287     public void messageReceived(NextFilter nextFilter, ByteBuffer buf)
288             throws SSLException {
289         if (buf.limit() > inNetBuffer.remaining()) {
290             // We have to expand inNetBuffer
291             inNetBuffer = SSLByteBufferPool.expandBuffer(inNetBuffer,
292                     inNetBuffer.capacity() + (buf.limit() * 2));
293             // We also expand app. buffer (twice the size of in net. buffer)
294             appBuffer = SSLByteBufferPool.expandBuffer(appBuffer, inNetBuffer
295                     .capacity() * 2);
296             if (SessionLog.isDebugEnabled(session)) {
297                 SessionLog.debug(session, " expanded inNetBuffer:"
298                         + inNetBuffer);
299                 SessionLog.debug(session, " expanded appBuffer:" + appBuffer);
300             }
301         }
302 
303         // append buf to inNetBuffer
304         inNetBuffer.put(buf);
305         if (!handshakeComplete) {
306             handshake(nextFilter);
307         } else {
308             decrypt(nextFilter);
309         }
310 
311         if (isInboundDone()) {
312             // Rewind the MINA buffer if not all data is processed and inbound is finished.
313             buf.position(buf.position() - inNetBuffer.position());
314             inNetBuffer.clear();
315         }
316     }
317 
318     /**
319      * Get decrypted application data.
320      *
321      * @return buffer with data
322      */
323     public ByteBuffer getAppBuffer() {
324         return appBuffer;
325     }
326 
327     /**
328      * Get encrypted data to be sent.
329      *
330      * @return buffer with data
331      */
332     public ByteBuffer getOutNetBuffer() {
333         return outNetBuffer;
334     }
335 
336     /**
337      * Encrypt provided buffer. Encytpted data reurned by getOutNetBuffer().
338      *
339      * @param src data to encrypt
340      * @throws SSLException on errors
341      */
342     public void encrypt(ByteBuffer src) throws SSLException {
343         if (!handshakeComplete) {
344             throw new IllegalStateException();
345         }
346 
347         // The data buffer is (must be) empty, we can reuse the entire
348         // buffer.
349         outNetBuffer.clear();
350 
351         // Loop until there is no more data in src
352         while (src.hasRemaining()) {
353 
354             if (src.remaining() > ((outNetBuffer.capacity() - outNetBuffer
355                     .position()) / 2)) {
356                 // We have to expand outNetBuffer
357                 // Note: there is no way to know the exact size required, but enrypted data
358                 // shouln't need to be larger than twice the source data size?
359                 outNetBuffer = SSLByteBufferPool.expandBuffer(outNetBuffer, src
360                         .capacity() * 2);
361                 if (SessionLog.isDebugEnabled(session)) {
362                     SessionLog.debug(session, " expanded outNetBuffer:"
363                             + outNetBuffer);
364                 }
365             }
366 
367             SSLEngineResult result = sslEngine.wrap(src, outNetBuffer);
368             if (SessionLog.isDebugEnabled(session)) {
369                 SessionLog.debug(session, " Wrap res:" + result);
370             }
371 
372             if (result.getStatus() == SSLEngineResult.Status.OK) {
373                 if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
374                     doTasks();
375                 }
376             } else {
377                 throw new SSLException("SSLEngine error during encrypt: "
378                         + result.getStatus() + " src: " + src
379                         + "outNetBuffer: " + outNetBuffer);
380             }
381         }
382 
383         outNetBuffer.flip();
384     }
385 
386     /**
387      * Start SSL shutdown process.
388      *
389      * @return <tt>true</tt> if shutdown process is started.
390      *         <tt>false</tt> if shutdown process is already finished.
391      *
392      * @throws SSLException on errors
393      */
394     public boolean closeOutbound() throws SSLException {
395         if (sslEngine == null || sslEngine.isOutboundDone()) {
396             return false;
397         }
398 
399         sslEngine.closeOutbound();
400 
401         // By RFC 2616, we can "fire and forget" our close_notify
402         // message, so that's what we'll do here.
403         outNetBuffer.clear();
404         SSLEngineResult result = sslEngine.wrap(hsBB, outNetBuffer);
405         if (result.getStatus() != SSLEngineResult.Status.CLOSED) {
406             throw new SSLException("Improper close state: " + result);
407         }
408         outNetBuffer.flip();
409         return true;
410     }
411 
412     /**
413      * Decrypt in net buffer. Result is stored in app buffer.
414      *
415      * @throws SSLException
416      */
417     private void decrypt(NextFilter nextFilter) throws SSLException {
418 
419         if (!handshakeComplete) {
420             throw new IllegalStateException();
421         }
422 
423         unwrap(nextFilter);
424     }
425 
426     /**
427      * @param status
428      * @throws SSLException
429      */
430     private void checkStatus(SSLEngineResult res)
431             throws SSLException {
432         
433         SSLEngineResult.Status status = res.getStatus();
434         
435         /*
436          * The status may be:
437          * OK - Normal operation
438          * OVERFLOW - Should never happen since the application buffer is
439          *      sized to hold the maximum packet size.
440          * UNDERFLOW - Need to read more data from the socket. It's normal.
441          * CLOSED - The other peer closed the socket. Also normal.
442          */
443         if (status != SSLEngineResult.Status.OK
444                 && status != SSLEngineResult.Status.CLOSED
445                 && status != SSLEngineResult.Status.BUFFER_UNDERFLOW) {
446             throw new SSLException("SSLEngine error during decrypt: " + status
447                     + " inNetBuffer: " + inNetBuffer + "appBuffer: "
448                     + appBuffer);
449         }
450     }
451 
452     /**
453      * Perform any handshaking processing.
454      */
455     public void handshake(NextFilter nextFilter) throws SSLException {
456         if (SessionLog.isDebugEnabled(session)) {
457             SessionLog.debug(session, " doHandshake()");
458         }
459 
460         for (;;) {
461             if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED) {
462                 session.setAttribute(SSLFilter.SSL_SESSION, sslEngine
463                         .getSession());
464                 if (SessionLog.isDebugEnabled(session)) {
465                     SSLSession sslSession = sslEngine.getSession();
466                     SessionLog.debug(session,
467                             "  handshakeStatus=FINISHED");
468                     SessionLog.debug(session, "  sslSession CipherSuite used "
469                             + sslSession.getCipherSuite());
470                 }
471                 handshakeComplete = true;
472                 if (!initialHandshakeComplete
473                         && session.containsAttribute(SSLFilter.USE_NOTIFICATION)) {
474                     // SESSION_SECURED is fired only when it's the first handshake.
475                     // (i.e. renegotiation shouldn't trigger SESSION_SECURED.)
476                     initialHandshakeComplete = true;
477                     scheduleMessageReceived(nextFilter,
478                             SSLFilter.SESSION_SECURED);
479                 }
480                 break;
481             } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) {
482                 if (SessionLog.isDebugEnabled(session)) {
483                     SessionLog.debug(session,
484                             "  handshakeStatus=NEED_TASK");
485                 }
486                 handshakeStatus = doTasks();
487             } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
488                 // we need more data read
489                 if (SessionLog.isDebugEnabled(session)) {
490                     SessionLog.debug(session,
491                             "  handshakeStatus=NEED_UNWRAP");
492                 }
493                 SSLEngineResult.Status status = unwrapHandshake(nextFilter);
494                 if (status == SSLEngineResult.Status.BUFFER_UNDERFLOW
495                         || isInboundDone()) {
496                     // We need more data or the session is closed
497                     break;
498                 }
499             } else if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
500                 if (SessionLog.isDebugEnabled(session)) {
501                     SessionLog.debug(session,
502                             "  handshakeStatus=NEED_WRAP");
503                 }
504                 // First make sure that the out buffer is completely empty. Since we
505                 // cannot call wrap with data left on the buffer
506                 if (outNetBuffer.hasRemaining()) {
507                     if (SessionLog.isDebugEnabled(session)) {
508                         SessionLog
509                                 .debug(session, "  Still data in out buffer!");
510                     }
511                     break;
512                 }
513                 outNetBuffer.clear();
514                 SSLEngineResult result = sslEngine.wrap(hsBB, outNetBuffer);
515                 if (SessionLog.isDebugEnabled(session)) {
516                     SessionLog.debug(session, " Wrap res:" + result);
517                 }
518 
519                 outNetBuffer.flip();
520                 handshakeStatus = result.getHandshakeStatus();
521                 writeNetBuffer(nextFilter);
522             } else {
523                 throw new IllegalStateException("Invalid Handshaking State"
524                         + handshakeStatus);
525             }
526         }
527     }
528 
529     public WriteFuture writeNetBuffer(NextFilter nextFilter)
530             throws SSLException {
531         // Check if any net data needed to be writen
532         if (!getOutNetBuffer().hasRemaining()) {
533             // no; bail out
534             return DefaultWriteFuture.newNotWrittenFuture(session);
535         }
536 
537         // set flag that we are writing encrypted data
538         // (used in SSLFilter.filterWrite())
539         writingEncryptedData = true;
540 
541         // write net data
542         WriteFuture writeFuture = null;
543 
544         try {
545             if (SessionLog.isDebugEnabled(session)) {
546                 SessionLog.debug(session, " write outNetBuffer: "
547                         + getOutNetBuffer());
548             }
549             org.apache.mina.common.ByteBuffer writeBuffer = copy(getOutNetBuffer());
550             if (SessionLog.isDebugEnabled(session)) {
551                 SessionLog.debug(session, " session write: " + writeBuffer);
552             }
553             //debug("outNetBuffer (after copy): {0}", sslHandler.getOutNetBuffer());
554 
555             writeFuture = new DefaultWriteFuture(session);
556             parent.filterWrite(nextFilter, session, new WriteRequest(
557                     writeBuffer, writeFuture));
558 
559             // loop while more writes required to complete handshake
560             while (needToCompleteHandshake()) {
561                 try {
562                     handshake(nextFilter);
563                 } catch (SSLException ssle) {
564                     SSLException newSSLE = new SSLHandshakeException(
565                             "SSL handshake failed.");
566                     newSSLE.initCause(ssle);
567                     throw newSSLE;
568                 }
569                 if (getOutNetBuffer().hasRemaining()) {
570                     if (SessionLog.isDebugEnabled(session)) {
571                         SessionLog.debug(session, " write outNetBuffer2: "
572                                 + getOutNetBuffer());
573                     }
574                     org.apache.mina.common.ByteBuffer writeBuffer2 = copy(getOutNetBuffer());
575                     writeFuture = new DefaultWriteFuture(session);
576                     parent.filterWrite(nextFilter, session, new WriteRequest(
577                             writeBuffer2, writeFuture));
578                 }
579             }
580         } finally {
581             writingEncryptedData = false;
582         }
583 
584         return writeFuture;
585     }
586 
587     private void unwrap(NextFilter nextFilter) throws SSLException {
588         if (SessionLog.isDebugEnabled(session)) {
589             SessionLog.debug(session, " unwrap()");
590         }
591 
592         // Prepare the net data for reading.
593         inNetBuffer.flip();
594 
595         SSLEngineResult res = unwrap0();
596 
597         // prepare to be written again
598         inNetBuffer.compact();
599         
600         checkStatus(res);
601         
602         renegotiateIfNeeded(nextFilter, res);
603     }
604 
605     private SSLEngineResult.Status unwrapHandshake(NextFilter nextFilter) throws SSLException {
606         if (SessionLog.isDebugEnabled(session)) {
607             SessionLog.debug(session, " unwrapHandshake()");
608         }
609 
610         // Prepare the net data for reading.
611         inNetBuffer.flip();
612 
613         SSLEngineResult res = unwrap0();
614         handshakeStatus = res.getHandshakeStatus();
615 
616         checkStatus(res);
617 
618         // If handshake finished, no data was produced, and the status is still ok,
619         // try to unwrap more
620         if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED
621                 && res.getStatus() == SSLEngineResult.Status.OK
622                 && inNetBuffer.hasRemaining()) {
623             res = unwrap0();
624             
625             // prepare to be written again
626             inNetBuffer.compact();
627 
628             renegotiateIfNeeded(nextFilter, res);
629         } else {
630             // prepare to be written again
631             inNetBuffer.compact();
632         }
633 
634         return res.getStatus();
635     }
636 
637     private void renegotiateIfNeeded(NextFilter nextFilter, SSLEngineResult res)
638             throws SSLException {
639         if (res.getStatus() != SSLEngineResult.Status.CLOSED
640                 && res.getStatus() != SSLEngineResult.Status.BUFFER_UNDERFLOW
641                 && res.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
642             // Renegotiation required.
643             SessionLog.debug(session, " Renegotiating...");
644             handshakeComplete = false;
645             handshakeStatus = res.getHandshakeStatus();
646             handshake(nextFilter);
647         }
648     }
649 
650     private SSLEngineResult unwrap0() throws SSLException {
651         SSLEngineResult res;
652         do {
653             if (SessionLog.isDebugEnabled(session)) {
654                 SessionLog.debug(session, "   inNetBuffer: " + inNetBuffer);
655                 SessionLog.debug(session, "   appBuffer: " + appBuffer);
656             }
657             res = sslEngine.unwrap(inNetBuffer, appBuffer);
658             if (SessionLog.isDebugEnabled(session)) {
659                 SessionLog.debug(session, " Unwrap res:" + res);
660             }
661         } while (res.getStatus() == SSLEngineResult.Status.OK
662                 && (handshakeComplete && res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING
663                         || res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP));
664         
665         return res;
666     }
667 
668     /**
669      * Do all the outstanding handshake tasks in the current Thread.
670      */
671     private SSLEngineResult.HandshakeStatus doTasks() {
672         if (SessionLog.isDebugEnabled(session)) {
673             SessionLog.debug(session, "   doTasks()");
674         }
675 
676         /*
677          * We could run this in a separate thread, but I don't see the need
678          * for this when used from SSLFilter. Use thread filters in MINA instead?
679          */
680         Runnable runnable;
681         while ((runnable = sslEngine.getDelegatedTask()) != null) {
682             if (SessionLog.isDebugEnabled(session)) {
683                 SessionLog.debug(session, "    doTask: " + runnable);
684             }
685             runnable.run();
686         }
687         if (SessionLog.isDebugEnabled(session)) {
688             SessionLog.debug(session, "   doTasks(): "
689                     + sslEngine.getHandshakeStatus());
690         }
691         return sslEngine.getHandshakeStatus();
692     }
693 
694     /**
695      * Creates a new Mina byte buffer that is a deep copy of the remaining bytes
696      * in the given buffer (between index buf.position() and buf.limit())
697      *
698      * @param src the buffer to copy
699      * @return the new buffer, ready to read from
700      */
701     public static org.apache.mina.common.ByteBuffer copy(java.nio.ByteBuffer src) {
702         org.apache.mina.common.ByteBuffer copy = org.apache.mina.common.ByteBuffer
703                 .allocate(src.remaining());
704         copy.put(src);
705         copy.flip();
706         return copy;
707     }
708 
709     private static class EventType {
710         public static final EventType RECEIVED = new EventType("RECEIVED");
711 
712         public static final EventType FILTER_WRITE = new EventType(
713                 "FILTER_WRITE");
714 
715         private final String value;
716 
717         private EventType(String value) {
718             this.value = value;
719         }
720 
721         public String toString() {
722             return value;
723         }
724     }
725 
726     private static class Event {
727         private final EventType type;
728 
729         private final NextFilter nextFilter;
730 
731         private final Object data;
732 
733         Event(EventType type, NextFilter nextFilter, Object data) {
734             this.type = type;
735             this.nextFilter = nextFilter;
736             this.data = data;
737         }
738 
739         public Object getData() {
740             return data;
741         }
742 
743         public NextFilter getNextFilter() {
744             return nextFilter;
745         }
746 
747         public EventType getType() {
748             return type;
749         }
750     }
751 }