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