View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.http.ssl;
15  
16  import java.io.IOException;
17  import java.nio.ByteBuffer;
18  import java.nio.channels.SelectionKey;
19  import java.nio.channels.SocketChannel;
20  
21  import javax.net.ssl.SSLEngine;
22  import javax.net.ssl.SSLEngineResult;
23  import javax.net.ssl.SSLException;
24  import javax.net.ssl.SSLSession;
25  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
26  
27  import org.eclipse.jetty.http.Parser;
28  import org.eclipse.jetty.io.Buffer;
29  import org.eclipse.jetty.io.Buffers;
30  import org.eclipse.jetty.io.EofException;
31  import org.eclipse.jetty.io.nio.NIOBuffer;
32  import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
33  import org.eclipse.jetty.io.nio.SelectorManager;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  
37  /* ------------------------------------------------------------ */
38  /**
39   * SslSelectChannelEndPoint
40   * <p>
41   * A SelectChannelEndPoint that uses an {@link SSLEngine} to handle an
42   * SSL connection.
43   * <p>
44   * There is a named logger "org.eclipse.jetty.http.ssl"
45   * </p>
46   */
47  public class SslSelectChannelEndPoint extends SelectChannelEndPoint
48  {
49      static Logger __log = Log.getLogger("org.eclipse.jetty.http.ssl");
50      
51      private static final ByteBuffer[] __NO_BUFFERS={};
52  
53      private final Buffers _buffers;
54      
55      private final SSLEngine _engine;
56      private final SSLSession _session;
57      private final ByteBuffer _inBuffer;
58      private final NIOBuffer _inNIOBuffer;
59      private final ByteBuffer _outBuffer;
60      private final NIOBuffer _outNIOBuffer;
61  
62      private final NIOBuffer[] _reuseBuffer=new NIOBuffer[2];
63      private final ByteBuffer[] _gather=new ByteBuffer[2];
64  
65      private boolean _closing=false;
66      private SSLEngineResult _result;
67  
68      private final boolean _debug = __log.isDebugEnabled(); // snapshot debug status for optimizer 
69  
70      
71      /* ------------------------------------------------------------ */
72      public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
73              throws IOException
74      {
75          super(channel,selectSet,key);
76          _buffers=buffers;
77          
78          // ssl
79          _engine=engine;
80          _session=engine.getSession();
81  
82          // TODO pool buffers and use only when needed.
83          _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
84          _outBuffer=_outNIOBuffer.getByteBuffer();
85          _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
86          _inBuffer=_inNIOBuffer.getByteBuffer();
87          
88          if (_debug) __log.debug(_session+" channel="+channel);
89      }
90  
91      // TODO get rid of these dumps
92      public void dump()
93      {
94          Log.info(""+_result);
95          // System.err.println(h.toString());
96          // System.err.println("--");
97      }
98      
99      /* ------------------------------------------------------------ */
100     /* (non-Javadoc)
101      * @see org.eclipse.io.nio.SelectChannelEndPoint#idleExpired()
102      */
103     protected void idleExpired()
104     {
105         try
106         {
107             getSelectManager().dispatch(new Runnable()
108             {
109                 public void run() 
110                 { 
111                     doIdleExpired();
112                 }
113             });
114         }
115         catch(Exception e)
116         {
117             Log.ignore(e);
118         }
119     }
120     
121     /* ------------------------------------------------------------ */
122     protected void doIdleExpired()
123     {
124         super.idleExpired();
125     }
126 
127     /* ------------------------------------------------------------ */
128     public void close() throws IOException
129     {
130         // TODO - this really should not be done in a loop here - but with async callbacks.
131 
132         _closing=true;
133         try
134         {   
135             if (isBufferingOutput())
136             {
137                 flush();
138                 while (isOpen() && isBufferingOutput())
139                 {
140                     Thread.sleep(100); // TODO non blocking
141                     flush();
142                 }
143             }
144 
145             _engine.closeOutbound();
146 
147             loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()))
148             {   
149                 if (isBufferingOutput())
150                 {
151                     flush();
152                     while (isOpen() && isBufferingOutput())
153                     {
154                         Thread.sleep(100); // TODO non blocking
155                         flush();
156                     }
157                 }
158 
159                 if (_debug) __log.debug(_session+" closing "+_engine.getHandshakeStatus());
160                 switch(_engine.getHandshakeStatus())
161                 {
162                     case FINISHED:
163                     case NOT_HANDSHAKING:
164                         break loop;
165                         
166                     case NEED_UNWRAP:
167                         Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
168                         try
169                         {
170                             ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer();
171                             if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
172                             {
173                                 break loop;
174                             }
175                         }
176                         catch(SSLException e)
177                         {
178                             Log.ignore(e);
179                         }
180                         finally
181                         {
182                             _buffers.returnBuffer(buffer);
183                         }
184                         break;
185                         
186                     case NEED_TASK:
187                     {
188                         Runnable task;
189                         while ((task=_engine.getDelegatedTask())!=null)
190                         {
191                             task.run();
192                         }
193                         break;
194                     }
195                         
196                     case NEED_WRAP:
197                     {
198                         try
199                         {
200                             _outNIOBuffer.compact();
201                             int put=_outNIOBuffer.putIndex();
202                             _outBuffer.position(put);
203                             _result=null;
204                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
205                             if (_debug) __log.debug(_session+" close wrap "+_result);
206                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
207                         }
208                         finally
209                         {
210                             _outBuffer.position(0);
211                         }
212                         
213                         break;
214                     }
215                 }
216             }
217         }
218         catch(IOException e)
219         {
220             Log.ignore(e);
221         }
222         catch (InterruptedException e)
223         {
224             Log.ignore(e);
225         }
226         finally
227         {
228             super.close();
229             
230             if (_inNIOBuffer!=null)
231                 _buffers.returnBuffer(_inNIOBuffer);
232             if (_outNIOBuffer!=null)
233                 _buffers.returnBuffer(_outNIOBuffer);
234             if (_reuseBuffer[0]!=null)
235                 _buffers.returnBuffer(_reuseBuffer[0]);
236             if (_reuseBuffer[1]!=null)
237                 _buffers.returnBuffer(_reuseBuffer[1]);
238         }   
239     }
240 
241     /* ------------------------------------------------------------ */
242     /** Fill the buffer with unencrypted bytes.
243      * Called by a {@link Parser} instance when more data is 
244      * needed to continue parsing a request or a response.
245      */
246     @Override
247     public int fill(Buffer buffer) throws IOException
248     {
249         // This end point only works on NIO buffer type (director
250         // or indirect), so extract the NIO buffer that is wrapped
251         // by the passed jetty Buffer.
252         final ByteBuffer bbuf=extractInputBuffer(buffer);
253         
254         // remember the original size of the unencrypted buffer
255         int size=buffer.length();
256         
257         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
258         //noinspection SynchronizationOnLocalVariableOrMethodParameter
259         synchronized (bbuf)
260         {
261             try
262             {
263                 // Call the SSLEngine unwrap method to process data in
264                 // the inBuffer.  If there is no data in the inBuffer, then
265                 // super.fill is called to read encrypted bytes.
266                 unwrap(bbuf);
267 
268                 // Loop through the SSL engine state machine
269                 
270                 int wraps=0;
271                 loop: while (true)
272                 {
273                     // If we have encrypted data in output buffer
274                     if (isBufferingOutput())
275                     {
276                         // we must flush it, as the other end might be
277                         // waiting for that outgoing data before sending 
278                         // more incoming data
279                         flush();
280                         
281                         // If we were unable to flush all the data, then 
282                         // we should break the loop and wait for the call
283                         // back to handle when the SelectSet detects that
284                         // the channel is writable again.
285                         if (isBufferingOutput())
286                             break loop;
287                     }
288 
289                     // handle the current hand share status
290                     switch(_engine.getHandshakeStatus())
291                     {
292                         case FINISHED:
293                         case NOT_HANDSHAKING:
294                             // If we are closing, then unwrap must have CLOSED result,
295                             // so return -1 to signal upwards
296                             if (_closing)
297                                 return -1;
298                             
299                             // otherwise we break loop with the data we have unwrapped.
300                             break loop;
301 
302                         case NEED_UNWRAP:
303                             // Need more data to be unwrapped so try another call to unwrap
304                             if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
305                             {
306                                 // If the unwrap call did not make any progress and we are still in
307                                 // NEED_UNWRAP, then we should break the loop and wait for more data to
308                                 // arrive.
309                                 break loop;
310                             }
311                             // progress was made so continue the loop.
312                             break;
313 
314                         case NEED_TASK:
315                         {
316                             // A task needs to be run, so run it!
317                             
318                             Runnable task;
319                             while ((task=_engine.getDelegatedTask())!=null)
320                             {
321                                 task.run();
322                             }
323                             
324                             // Detect SUN JVM Bug!!!
325                             if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && 
326                                _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
327                             {
328                                 // This should be NEED_WRAP
329                                 // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS.
330                                 // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it.
331                                 // See http://jira.codehaus.org/browse/JETTY-567 for more details
332                                 if (_debug) __log.warn(_session+" JETTY-567");
333                                 return -1;
334                             }
335                             break;
336                         }
337 
338                         case NEED_WRAP:
339                         {
340                             // The SSL needs to send some handshake data to the other side,
341                             // so let fill become a flush for a little bit.
342                             wraps++;
343                             synchronized(_outBuffer)
344                             {
345                                 try
346                                 {
347                                     // call wrap with empty application buffers, so it can
348                                     // generate required handshake messages into _outNIOBuffer
349                                     _outNIOBuffer.compact();
350                                     int put=_outNIOBuffer.putIndex();
351                                     _outBuffer.position();
352                                     _result=null;
353                                     _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
354                                     if (_debug) __log.debug(_session+" fill wrap "+_result);
355                                     switch(_result.getStatus())
356                                     {
357                                         case BUFFER_OVERFLOW:
358                                         case BUFFER_UNDERFLOW:
359                                             Log.warn("wrap {}",_result);
360                                         case CLOSED:
361                                             _closing=true;
362                                     }
363                                     
364                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
365                                 }
366                                 finally
367                                 {
368                                     _outBuffer.position(0);
369                                 }
370                             }
371 
372                             // flush the encrypted outNIOBuffer
373                             flush();
374 
375                             break;
376                         }
377                     }
378                 }
379             }
380             catch(SSLException e)
381             {
382                 Log.warn(e.toString());
383                 Log.debug(e);
384                 throw e;
385             }
386             finally
387             {
388                 // reset the Buffers
389                 buffer.setPutIndex(bbuf.position());
390                 bbuf.position(0);
391             }
392         }
393         
394         // return the number of unencrypted bytes filled.
395         return buffer.length()-size; 
396 
397     }
398 
399     /* ------------------------------------------------------------ */
400     @Override
401     public int flush(Buffer buffer) throws IOException
402     {
403         return flush(buffer,null,null);
404     }
405 
406 
407     /* ------------------------------------------------------------ */
408     /*     
409      */
410     @Override
411     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
412     {   
413         int consumed=0;
414         int available=header.length();
415         if (buffer!=null)
416             available+=buffer.length();
417         
418         int tries=0;
419         loop: while (true)
420         {   
421             if (_outNIOBuffer.length()>0)
422             {
423                 flush();
424                 if (isBufferingOutput())
425                     break loop;
426             }
427             
428             switch(_engine.getHandshakeStatus())
429             {
430                 case FINISHED:
431                 case NOT_HANDSHAKING:
432 
433                     if (_closing || available==0)
434                     {
435                         if (consumed==0)
436                             consumed= -1;
437                         break loop;
438                     }
439                         
440                     int c;
441                     if (header!=null && header.length()>0)
442                     {
443                         if (buffer!=null && buffer.length()>0)
444                             c=wrap(header,buffer);
445                         else
446                             c=wrap(header);
447                     }
448                     else 
449                         c=wrap(buffer);
450                     
451                     if (c>0)
452                     {
453                         consumed+=c;
454                         available-=c;
455                     }
456                     else if (c<0)
457                     {
458                         if (consumed==0)
459                             consumed=-1;
460                         break loop;
461                     }
462                     
463                     break;
464 
465                 case NEED_UNWRAP:
466                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
467                     try
468                     {
469                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
470                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
471                         {
472                             break loop;
473                         }
474                     }
475                     finally
476                     {
477                         _buffers.returnBuffer(buf);
478                     }
479                     
480                     break;
481 
482                 case NEED_TASK:
483                 {
484                     Runnable task;
485                     while ((task=_engine.getDelegatedTask())!=null)
486                     {
487                         task.run();
488                     }
489                     break;
490                 }
491 
492                 case NEED_WRAP:
493                 {
494                     synchronized(_outBuffer)
495                     {
496                         try
497                         {
498                             _outNIOBuffer.compact();
499                             int put=_outNIOBuffer.putIndex();
500                             _outBuffer.position();
501                             _result=null;
502                             _result=_engine.wrap(__NO_BUFFERS,_outBuffer);
503                             if (_debug) __log.debug(_session+" flush wrap "+_result);
504                             switch(_result.getStatus())
505                             {
506                                 case BUFFER_OVERFLOW:
507                                 case BUFFER_UNDERFLOW:
508                                     Log.warn("unwrap {}",_result);
509                                 case CLOSED:
510                                     _closing=true;
511                             }
512                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
513                         }
514                         finally
515                         {
516                             _outBuffer.position(0);
517                         }
518                     }
519 
520                     flush();
521                     if (isBufferingOutput())
522                         break loop;
523 
524                     break;
525                 }
526             }
527         }
528         
529         return consumed;
530     }
531     
532     /* ------------------------------------------------------------ */
533     @Override
534     public void flush() throws IOException
535     {
536         int len=_outNIOBuffer.length();
537         if (isBufferingOutput())
538         {
539             int flushed=super.flush(_outNIOBuffer);
540             if (_debug) __log.debug(_session+" Flushed "+flushed+"/"+len);
541             if (isBufferingOutput())
542             {
543                 // Try again after yield.... cheaper than a reschedule.
544                 Thread.yield();
545                 flushed=super.flush(_outNIOBuffer);
546                 if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len);
547             }
548         }
549     }
550 
551     /* ------------------------------------------------------------ */
552     private ByteBuffer extractInputBuffer(Buffer buffer)
553     {
554         assert buffer instanceof NIOBuffer;
555         NIOBuffer nbuf=(NIOBuffer)buffer;
556         ByteBuffer bbuf=nbuf.getByteBuffer();
557         bbuf.position(buffer.putIndex());
558         return bbuf;
559     }
560 
561     /* ------------------------------------------------------------ */
562     /**
563      * @return true if progress is made
564      */
565     private boolean unwrap(ByteBuffer buffer) throws IOException
566     {
567         if (_inNIOBuffer.hasContent())
568             _inNIOBuffer.compact();
569         else 
570             _inNIOBuffer.clear();
571 
572         int total_filled=0;
573         
574         // loop filling as much encrypted data as we can into the buffer
575         while (_inNIOBuffer.space()>0 && super.isOpen())
576         {
577             try
578             {
579                 int filled=super.fill(_inNIOBuffer);
580                 if (_debug) __log.debug(_session+" unwrap filled "+filled);
581                 // break the loop if no progress is made (we have read everything
582                 // there is to read).
583                 if (filled<=0)
584                     break;
585                 total_filled+=filled;
586             }
587             catch(IOException e)
588             {
589                 if (_inNIOBuffer.length()==0)
590                 {
591                     _outNIOBuffer.clear();
592                     throw e;
593                 }
594                 break;
595             }
596         }
597         
598         // If we have no progress and no data
599         if (total_filled==0 && _inNIOBuffer.length()==0)
600         {
601             if(!isOpen())
602             {
603                 _outNIOBuffer.clear();
604                 throw new EofException();
605             }
606             return false;
607         }
608 
609         // We have some in data, so try to unwrap it.
610         try
611         {
612             // inBuffer is the NIO buffer inside the _inNIOBuffer,
613             // so update its position and limit from the inNIOBuffer.
614             _inBuffer.position(_inNIOBuffer.getIndex());
615             _inBuffer.limit(_inNIOBuffer.putIndex());
616             _result=null;
617             
618             // Do the unwrap
619             _result=_engine.unwrap(_inBuffer,buffer);
620             if (_debug) __log.debug(_session+" unwrap unwrap "+_result);
621             
622             // skip the bytes consumed
623             _inNIOBuffer.skip(_result.bytesConsumed());
624         }
625         finally
626         {
627             // reset the buffer so it can be managed by the _inNIOBuffer again.
628             _inBuffer.position(0);
629             _inBuffer.limit(_inBuffer.capacity());
630         }
631 
632         // handle the unwrap results
633         switch(_result.getStatus())
634         {
635             case BUFFER_OVERFLOW:
636                 throw new IllegalStateException(_result.toString());
637                 
638             case BUFFER_UNDERFLOW:
639                 // Not enough data, 
640                 // If we are closed, we will never get more, so EOF
641                 // else return and we will be tried again
642                 // later when more data arriving causes another dispatch.
643                 if (Log.isDebugEnabled()) Log.debug("unwrap {}",_result);
644                 if(!isOpen())
645                 {
646                     _inNIOBuffer.clear();
647                     _outNIOBuffer.clear();
648                     throw new EofException();
649                 }
650                 return (total_filled > 0);
651                 
652             case CLOSED:
653                 _closing=true;
654             case OK:
655                 // return true is some bytes somewhere were moved about.
656                 return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
657             default:
658                 Log.warn("unwrap "+_result);
659                 throw new IOException(_result.toString());
660         }
661     }
662 
663     
664     /* ------------------------------------------------------------ */
665     private ByteBuffer extractOutputBuffer(Buffer buffer,int n)
666     {
667         if (buffer.buffer() instanceof NIOBuffer)
668         {
669             NIOBuffer nBuf=(NIOBuffer)buffer.buffer();
670             return nBuf.getByteBuffer();
671         }
672         else
673         {
674             if (_reuseBuffer[n]==null)
675                 _reuseBuffer[n] = (NIOBuffer)_buffers.getBuffer(_session.getApplicationBufferSize());
676             NIOBuffer buf = _reuseBuffer[n];
677             buf.clear();
678             buf.put(buffer);
679             return buf.getByteBuffer();
680         }
681     }
682 
683     /* ------------------------------------------------------------ */
684     private int wrap(final Buffer header, final Buffer buffer) throws IOException
685     {
686         _gather[0]=extractOutputBuffer(header,0);
687         synchronized(_gather[0])
688         {
689             _gather[0].position(header.getIndex());
690             _gather[0].limit(header.putIndex());
691 
692             _gather[1]=extractOutputBuffer(buffer,1);
693 
694             synchronized(_gather[1])
695             {
696                 _gather[1].position(buffer.getIndex());
697                 _gather[1].limit(buffer.putIndex());
698 
699                 synchronized(_outBuffer)
700                 {
701                     int consumed=0;
702                     try
703                     {
704                         _outNIOBuffer.clear();
705                         _outBuffer.position(0);
706                         _outBuffer.limit(_outBuffer.capacity());
707 
708                         _result=null;
709                         _result=_engine.wrap(_gather,_outBuffer);
710                         if (_debug) __log.debug(_session+" wrap wrap "+_result);
711                         _outNIOBuffer.setGetIndex(0);
712                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
713                         consumed=_result.bytesConsumed();
714                     }
715                     finally
716                     {
717                         _outBuffer.position(0);
718 
719                         if (consumed>0)
720                         {
721                             int len=consumed<header.length()?consumed:header.length();
722                             header.skip(len);
723                             consumed-=len;
724                             _gather[0].position(0);
725                             _gather[0].limit(_gather[0].capacity());
726                         }
727                         if (consumed>0)
728                         {
729                             int len=consumed<buffer.length()?consumed:buffer.length();
730                             buffer.skip(len);
731                             consumed-=len;
732                             _gather[1].position(0);
733                             _gather[1].limit(_gather[1].capacity());
734                         }
735                         assert consumed==0;
736                     }
737                 }
738             }
739         }
740         
741 
742         switch(_result.getStatus())
743         {
744             case BUFFER_OVERFLOW:
745             case BUFFER_UNDERFLOW:
746                 Log.warn("unwrap {}",_result);
747                 
748             case OK:
749                 return _result.bytesConsumed();
750             case CLOSED:
751                 _closing=true;
752                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
753 
754             default:
755                 Log.warn("wrap "+_result);
756             throw new IOException(_result.toString());
757         }
758     }
759 
760     /* ------------------------------------------------------------ */
761     private int wrap(final Buffer buffer) throws IOException
762     {
763         _gather[0]=extractOutputBuffer(buffer,0);
764         synchronized(_gather[0])
765         {
766             _gather[0].position(buffer.getIndex());
767             _gather[0].limit(buffer.putIndex());
768 
769             int consumed=0;
770             synchronized(_outBuffer)
771             {
772                 try
773                 {
774                     _outNIOBuffer.clear();
775                     _outBuffer.position(0);
776                     _outBuffer.limit(_outBuffer.capacity());
777                     _result=null;
778                     _result=_engine.wrap(_gather[0],_outBuffer);
779                     if (_debug) __log.debug(_session+" wrap wrap "+_result);
780                     _outNIOBuffer.setGetIndex(0);
781                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
782                     consumed=_result.bytesConsumed();
783                 }
784                 finally
785                 {
786                     _outBuffer.position(0);
787 
788                     if (consumed>0)
789                     {
790                         int len=consumed<buffer.length()?consumed:buffer.length();
791                         buffer.skip(len);
792                         consumed-=len;
793                         _gather[0].position(0);
794                         _gather[0].limit(_gather[0].capacity());
795                     }
796                     assert consumed==0;
797                 }
798             }
799         }
800         switch(_result.getStatus())
801         {
802             case BUFFER_OVERFLOW:
803             case BUFFER_UNDERFLOW:
804                 Log.warn("unwrap {}",_result);
805                 
806             case OK:
807                 return _result.bytesConsumed();
808             case CLOSED:
809                 _closing=true;
810                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
811 
812             default:
813                 Log.warn("wrap "+_result);
814             throw new IOException(_result.toString());
815         }
816     }
817 
818     /* ------------------------------------------------------------ */
819     public boolean isBufferingInput()
820     {
821         return _inNIOBuffer.hasContent();
822     }
823 
824     /* ------------------------------------------------------------ */
825     public boolean isBufferingOutput()
826     {
827         return _outNIOBuffer.hasContent();
828     }
829 
830     /* ------------------------------------------------------------ */
831     public boolean isBufferred()
832     {
833         return true;
834     }
835 
836     /* ------------------------------------------------------------ */
837     public SSLEngine getSSLEngine()
838     {
839         return _engine;
840     }
841     
842     /* ------------------------------------------------------------ */
843     public String toString()
844     {
845         return super.toString()+","+_engine.getHandshakeStatus()+", in/out="+_inNIOBuffer.length()+"/"+_outNIOBuffer.length()+" "+_result;
846     }
847 }