View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.io.nio;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.concurrent.atomic.AtomicBoolean;
24  import javax.net.ssl.SSLEngine;
25  import javax.net.ssl.SSLEngineResult;
26  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
27  import javax.net.ssl.SSLException;
28  import javax.net.ssl.SSLSession;
29  
30  import org.eclipse.jetty.io.AbstractConnection;
31  import org.eclipse.jetty.io.AsyncEndPoint;
32  import org.eclipse.jetty.io.Buffer;
33  import org.eclipse.jetty.io.Connection;
34  import org.eclipse.jetty.io.EndPoint;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  import org.eclipse.jetty.util.thread.Timeout.Task;
38  
39  /* ------------------------------------------------------------ */
40  /** SSL Connection.
41   * An AysyncConnection that acts as an interceptor between and EndPoint and another
42   * Connection, that implements TLS encryption using an {@link SSLEngine}.
43   * <p>
44   * The connector uses an {@link AsyncEndPoint} (like {@link SelectChannelEndPoint}) as
45   * it's source/sink of encrypted data.   It then provides {@link #getSslEndPoint()} to
46   * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
47   */
48  public class SslConnection extends AbstractConnection implements AsyncConnection
49  {
50      private final Logger _logger = Log.getLogger("org.eclipse.jetty.io.nio.ssl");
51  
52      private static final NIOBuffer __ZERO_BUFFER=new IndirectNIOBuffer(0);
53  
54      private static final ThreadLocal<SslBuffers> __buffers = new ThreadLocal<SslBuffers>();
55      private final SSLEngine _engine;
56      private final SSLSession _session;
57      private AsyncConnection _connection;
58      private final SslEndPoint _sslEndPoint;
59      private int _allocations;
60      private SslBuffers _buffers;
61      private NIOBuffer _inbound;
62      private NIOBuffer _unwrapBuf;
63      private NIOBuffer _outbound;
64      private AsyncEndPoint _aEndp;
65      private boolean _allowRenegotiate=true;
66      private boolean _handshook;
67      private boolean _ishut;
68      private boolean _oshut;
69      private final AtomicBoolean _progressed = new AtomicBoolean();
70  
71      /* ------------------------------------------------------------ */
72      /* this is a half baked buffer pool
73       */
74      private static class SslBuffers
75      {
76          final NIOBuffer _in;
77          final NIOBuffer _out;
78          final NIOBuffer _unwrap;
79  
80          SslBuffers(int packetSize, int appSize)
81          {
82              _in=new IndirectNIOBuffer(packetSize);
83              _out=new IndirectNIOBuffer(packetSize);
84              _unwrap=new IndirectNIOBuffer(appSize);
85          }
86      }
87  
88      /* ------------------------------------------------------------ */
89      public SslConnection(SSLEngine engine,EndPoint endp)
90      {
91          this(engine,endp,System.currentTimeMillis());
92      }
93  
94      /* ------------------------------------------------------------ */
95      public SslConnection(SSLEngine engine,EndPoint endp, long timeStamp)
96      {
97          super(endp,timeStamp);
98          _engine=engine;
99          _session=_engine.getSession();
100         _aEndp=(AsyncEndPoint)endp;
101         _sslEndPoint = newSslEndPoint();
102     }
103 
104     /* ------------------------------------------------------------ */
105     protected SslEndPoint newSslEndPoint()
106     {
107         return new SslEndPoint();
108     }
109 
110     /* ------------------------------------------------------------ */
111     /**
112      * @return True if SSL re-negotiation is allowed (default false)
113      */
114     public boolean isAllowRenegotiate()
115     {
116         return _allowRenegotiate;
117     }
118 
119     /* ------------------------------------------------------------ */
120     /**
121      * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
122      * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
123      * does not have CVE-2009-3555 fixed, then re-negotiation should
124      * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
125      * of renegotiates in u19 and with RFC5746 in u22.
126      *
127      * @param allowRenegotiate
128      *            true if re-negotiation is allowed (default false)
129      */
130     public void setAllowRenegotiate(boolean allowRenegotiate)
131     {
132         _allowRenegotiate = allowRenegotiate;
133     }
134 
135     /* ------------------------------------------------------------ */
136     private void allocateBuffers()
137     {
138         synchronized (this)
139         {
140             if (_allocations++==0)
141             {
142                 if (_buffers==null)
143                 {
144                     _buffers=__buffers.get();
145                     if (_buffers==null)
146                         _buffers=new SslBuffers(_session.getPacketBufferSize()*2,_session.getApplicationBufferSize()*2);
147                     _inbound=_buffers._in;
148                     _outbound=_buffers._out;
149                     _unwrapBuf=_buffers._unwrap;
150                     __buffers.set(null);
151                 }
152             }
153         }
154     }
155 
156     /* ------------------------------------------------------------ */
157     private void releaseBuffers()
158     {
159         synchronized (this)
160         {
161             if (--_allocations==0)
162             {
163                 if (_buffers!=null &&
164                     _inbound.length()==0 &&
165                     _outbound.length()==0 &&
166                     _unwrapBuf.length()==0)
167                 {
168                     _inbound=null;
169                     _outbound=null;
170                     _unwrapBuf=null;
171                     __buffers.set(_buffers);
172                     _buffers=null;
173                 }
174             }
175         }
176     }
177 
178     /* ------------------------------------------------------------ */
179     public Connection handle() throws IOException
180     {
181         try
182         {
183             allocateBuffers();
184 
185             boolean progress=true;
186 
187             while (progress)
188             {
189                 progress=false;
190 
191                 // If we are handshook let the delegate connection
192                 if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
193                     progress=process(null,null);
194 
195                 // handle the delegate connection
196                 AsyncConnection next = (AsyncConnection)_connection.handle();
197                 if (next!=_connection && next!=null)
198                 {
199                     _connection=next;
200                     progress=true;
201                 }
202 
203                 _logger.debug("{} handle {} progress={}", _session, this, progress);
204             }
205         }
206         finally
207         {
208             releaseBuffers();
209 
210             if (!_ishut && _sslEndPoint.isInputShutdown() && _sslEndPoint.isOpen())
211             {
212                 _ishut=true;
213                 try
214                 {
215                     _connection.onInputShutdown();
216                 }
217                 catch(Throwable x)
218                 {
219                     _logger.warn("onInputShutdown failed", x);
220                     try{_sslEndPoint.close();}
221                     catch(IOException e2){
222                         _logger.ignore(e2);}
223                 }
224             }
225         }
226 
227         return this;
228     }
229 
230     /* ------------------------------------------------------------ */
231     public boolean isIdle()
232     {
233         return false;
234     }
235 
236     /* ------------------------------------------------------------ */
237     public boolean isSuspended()
238     {
239         return false;
240     }
241 
242     /* ------------------------------------------------------------ */
243     public void onClose()
244     {
245         Connection connection = _sslEndPoint.getConnection();
246         if (connection != null && connection != this)
247             connection.onClose();
248     }
249 
250     /* ------------------------------------------------------------ */
251     @Override
252     public void onIdleExpired(long idleForMs)
253     {
254         try
255         {
256             _logger.debug("onIdleExpired {}ms on {}",idleForMs,this);
257             if (_endp.isOutputShutdown())
258                 _sslEndPoint.close();
259             else
260                 _sslEndPoint.shutdownOutput();
261         }
262         catch (IOException e)
263         {
264             _logger.warn(e);
265             super.onIdleExpired(idleForMs);
266         }
267     }
268 
269     /* ------------------------------------------------------------ */
270     public void onInputShutdown() throws IOException
271     {
272 
273     }
274 
275     /* ------------------------------------------------------------ */
276     private synchronized boolean process(Buffer toFill, Buffer toFlush) throws IOException
277     {
278         boolean some_progress=false;
279         try
280         {
281             // We need buffers to progress
282             allocateBuffers();
283 
284             // if we don't have a buffer to put received data into
285             if (toFill==null)
286             {
287                 // use the unwrapbuffer to hold received data.
288                 _unwrapBuf.compact();
289                 toFill=_unwrapBuf;
290             }
291             // Else if the fill buffer is too small for the SSL session
292             else if (toFill.capacity()<_session.getApplicationBufferSize())
293             {
294                 // fill to the temporary unwrapBuffer
295                 boolean progress=process(null,toFlush);
296 
297                 // if we received any data,
298                 if (_unwrapBuf!=null && _unwrapBuf.hasContent())
299                 {
300                     // transfer from temp buffer to fill buffer
301                     _unwrapBuf.skip(toFill.put(_unwrapBuf));
302                     return true;
303                 }
304                 else
305                     // return progress from recursive call
306                     return progress;
307             }
308             // Else if there is some temporary data
309             else if (_unwrapBuf!=null && _unwrapBuf.hasContent())
310             {
311                 // transfer from temp buffer to fill buffer
312                 _unwrapBuf.skip(toFill.put(_unwrapBuf));
313                 return true;
314             }
315 
316             // If we are here, we have a buffer ready into which we can put some read data.
317 
318             // If we have no data to flush, flush the empty buffer
319             if (toFlush==null)
320                 toFlush=__ZERO_BUFFER;
321 
322             // While we are making progress processing SSL engine
323             boolean progress=true;
324             while (progress)
325             {
326                 progress=false;
327 
328                 // Do any real IO
329                 int filled=0,flushed=0;
330                 try
331                 {
332                     // Read any available data
333                     if (_inbound.space()>0 && (filled=_endp.fill(_inbound))>0)
334                         progress = true;
335 
336                     // flush any output data
337                     if (_outbound.hasContent() && (flushed=_endp.flush(_outbound))>0)
338                         progress = true;
339                 }
340                 catch (IOException e)
341                 {
342                     _endp.close();
343                     throw e;
344                 }
345                 finally
346                 {
347                     _logger.debug("{} {} {} filled={}/{} flushed={}/{}",_session,this,_engine.getHandshakeStatus(),filled,_inbound.length(),flushed,_outbound.length());
348                 }
349 
350                 // handle the current hand share status
351                 switch(_engine.getHandshakeStatus())
352                 {
353                     case FINISHED:
354                         throw new IllegalStateException();
355 
356                     case NOT_HANDSHAKING:
357                     {
358                         // Try unwrapping some application data
359                         if (toFill.space()>0 && _inbound.hasContent() && unwrap(toFill))
360                             progress=true;
361 
362                         // Try wrapping some application data
363                         if (toFlush.hasContent() && _outbound.space()>0 && wrap(toFlush))
364                             progress=true;
365                     }
366                     break;
367 
368                     case NEED_TASK:
369                     {
370                         // A task needs to be run, so run it!
371                         Runnable task;
372                         while ((task=_engine.getDelegatedTask())!=null)
373                         {
374                             progress=true;
375                             task.run();
376                         }
377 
378                     }
379                     break;
380 
381                     case NEED_WRAP:
382                     {
383                         // The SSL needs to send some handshake data to the other side
384                         if (_handshook && !_allowRenegotiate)
385                             _endp.close();
386                         else if (wrap(toFlush))
387                             progress=true;
388                     }
389                     break;
390 
391                     case NEED_UNWRAP:
392                     {
393                         // The SSL needs to receive some handshake data from the other side
394                         if (_handshook && !_allowRenegotiate)
395                             _endp.close();
396                         else if (!_inbound.hasContent()&&filled==-1)
397                         {
398                             // No more input coming
399                             _endp.shutdownInput();
400                         }
401                         else if (unwrap(toFill))
402                             progress=true;
403                     }
404                     break;
405                 }
406 
407                 // pass on ishut/oshut state
408                 if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent())
409                     closeInbound();
410 
411                 if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent())
412                     _endp.shutdownOutput();
413 
414                 // remember if any progress has been made
415                 some_progress|=progress;
416             }
417 
418             // If we are reading into the temp buffer and it has some content, then we should be dispatched.
419             if (toFill==_unwrapBuf && _unwrapBuf.hasContent() && !_connection.isSuspended())
420                 _aEndp.dispatch();
421         }
422         finally
423         {
424             releaseBuffers();
425             if (some_progress)
426                 _progressed.set(true);
427         }
428         return some_progress;
429     }
430 
431     private void closeInbound()
432     {
433         try
434         {
435             _engine.closeInbound();
436         }
437         catch (SSLException x)
438         {
439             _logger.debug(x);
440         }
441     }
442 
443     private synchronized boolean wrap(final Buffer buffer) throws IOException
444     {
445         ByteBuffer bbuf=extractByteBuffer(buffer);
446         final SSLEngineResult result;
447 
448         synchronized(bbuf)
449         {
450             _outbound.compact();
451             ByteBuffer out_buffer=_outbound.getByteBuffer();
452             synchronized(out_buffer)
453             {
454                 try
455                 {
456                     bbuf.position(buffer.getIndex());
457                     bbuf.limit(buffer.putIndex());
458                     out_buffer.position(_outbound.putIndex());
459                     out_buffer.limit(out_buffer.capacity());
460                     result=_engine.wrap(bbuf,out_buffer);
461                     if (_logger.isDebugEnabled())
462                         _logger.debug("{} wrap {} {} consumed={} produced={}",
463                             _session,
464                             result.getStatus(),
465                             result.getHandshakeStatus(),
466                             result.bytesConsumed(),
467                             result.bytesProduced());
468 
469 
470                     buffer.skip(result.bytesConsumed());
471                     _outbound.setPutIndex(_outbound.putIndex()+result.bytesProduced());
472                 }
473                 catch(SSLException e)
474                 {
475                     _logger.debug(String.valueOf(_endp), e);
476                     _endp.close();
477                     throw e;
478                 }
479                 finally
480                 {
481                     out_buffer.position(0);
482                     out_buffer.limit(out_buffer.capacity());
483                     bbuf.position(0);
484                     bbuf.limit(bbuf.capacity());
485                 }
486             }
487         }
488 
489         switch(result.getStatus())
490         {
491             case BUFFER_UNDERFLOW:
492                 throw new IllegalStateException();
493 
494             case BUFFER_OVERFLOW:
495                 break;
496 
497             case OK:
498                 if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
499                     _handshook=true;
500                 break;
501 
502             case CLOSED:
503                 _logger.debug("wrap CLOSE {} {}",this,result);
504                 if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
505                     _endp.close();
506                 break;
507 
508             default:
509                 _logger.debug("{} wrap default {}",_session,result);
510             throw new IOException(result.toString());
511         }
512 
513         return result.bytesConsumed()>0 || result.bytesProduced()>0;
514     }
515 
516     private synchronized boolean unwrap(final Buffer buffer) throws IOException
517     {
518         if (!_inbound.hasContent())
519             return false;
520 
521         ByteBuffer bbuf=extractByteBuffer(buffer);
522         final SSLEngineResult result;
523 
524         synchronized(bbuf)
525         {
526             ByteBuffer in_buffer=_inbound.getByteBuffer();
527             synchronized(in_buffer)
528             {
529                 try
530                 {
531                     bbuf.position(buffer.putIndex());
532                     bbuf.limit(buffer.capacity());
533                     in_buffer.position(_inbound.getIndex());
534                     in_buffer.limit(_inbound.putIndex());
535 
536                     result=_engine.unwrap(in_buffer,bbuf);
537                     if (_logger.isDebugEnabled())
538                         _logger.debug("{} unwrap {} {} consumed={} produced={}",
539                             _session,
540                             result.getStatus(),
541                             result.getHandshakeStatus(),
542                             result.bytesConsumed(),
543                             result.bytesProduced());
544 
545                     _inbound.skip(result.bytesConsumed());
546                     _inbound.compact();
547                     buffer.setPutIndex(buffer.putIndex()+result.bytesProduced());
548                 }
549                 catch(SSLException e)
550                 {
551                     _logger.debug(String.valueOf(_endp), e);
552                     _endp.close();
553                     throw e;
554                 }
555                 finally
556                 {
557                     in_buffer.position(0);
558                     in_buffer.limit(in_buffer.capacity());
559                     bbuf.position(0);
560                     bbuf.limit(bbuf.capacity());
561                 }
562             }
563         }
564 
565         switch(result.getStatus())
566         {
567             case BUFFER_UNDERFLOW:
568                 if (_endp.isInputShutdown())
569                     _inbound.clear();
570                 break;
571 
572             case BUFFER_OVERFLOW:
573                 if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
574                 break;
575 
576             case OK:
577                 if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
578                     _handshook=true;
579                 break;
580 
581             case CLOSED:
582                 _logger.debug("unwrap CLOSE {} {}",this,result);
583                 if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
584                     _endp.close();
585                 break;
586 
587             default:
588                 _logger.debug("{} wrap default {}",_session,result);
589             throw new IOException(result.toString());
590         }
591 
592         //if (LOG.isDebugEnabled() && result.bytesProduced()>0)
593         //    LOG.debug("{} unwrapped '{}'",_session,buffer);
594 
595         return result.bytesConsumed()>0 || result.bytesProduced()>0;
596     }
597 
598 
599     /* ------------------------------------------------------------ */
600     private ByteBuffer extractByteBuffer(Buffer buffer)
601     {
602         if (buffer.buffer() instanceof NIOBuffer)
603             return ((NIOBuffer)buffer.buffer()).getByteBuffer();
604         return ByteBuffer.wrap(buffer.array());
605     }
606 
607     /* ------------------------------------------------------------ */
608     public AsyncEndPoint getSslEndPoint()
609     {
610         return _sslEndPoint;
611     }
612 
613     /* ------------------------------------------------------------ */
614     public String toString()
615     {
616         return String.format("%s %s", super.toString(), _sslEndPoint);
617     }
618 
619     /* ------------------------------------------------------------ */
620     /* ------------------------------------------------------------ */
621     public class SslEndPoint implements AsyncEndPoint
622     {
623         public SSLEngine getSslEngine()
624         {
625             return _engine;
626         }
627 
628         public AsyncEndPoint getEndpoint()
629         {
630             return _aEndp;
631         }
632 
633         public void shutdownOutput() throws IOException
634         {
635             synchronized (SslConnection.this)
636             {
637                 _logger.debug("{} ssl endp.oshut {}",_session,this);
638                 _engine.closeOutbound();
639                 _oshut=true;
640             }
641             flush();
642         }
643 
644         public boolean isOutputShutdown()
645         {
646             synchronized (SslConnection.this)
647             {
648                 return _oshut||!isOpen()||_engine.isOutboundDone();
649             }
650         }
651 
652         public void shutdownInput() throws IOException
653         {
654             _logger.debug("{} ssl endp.ishut!",_session);
655             // We do not do a closeInput here, as SSL does not support half close.
656             // isInputShutdown works it out itself from buffer state and underlying endpoint state.
657         }
658 
659         public boolean isInputShutdown()
660         {
661             synchronized (SslConnection.this)
662             {
663                 return _endp.isInputShutdown() &&
664                 !(_unwrapBuf!=null&&_unwrapBuf.hasContent()) &&
665                 !(_inbound!=null&&_inbound.hasContent());
666             }
667         }
668 
669         public void close() throws IOException
670         {
671             _logger.debug("{} ssl endp.close",_session);
672             _endp.close();
673         }
674 
675         public int fill(Buffer buffer) throws IOException
676         {
677             int size=buffer.length();
678             process(buffer, null);
679 
680             int filled=buffer.length()-size;
681 
682             if (filled==0 && isInputShutdown())
683                 return -1;
684             return filled;
685         }
686 
687         public int flush(Buffer buffer) throws IOException
688         {
689             int size = buffer.length();
690             process(null, buffer);
691             return size-buffer.length();
692         }
693 
694         public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
695         {
696             if (header!=null && header.hasContent())
697                 return flush(header);
698             if (buffer!=null && buffer.hasContent())
699                 return flush(buffer);
700             if (trailer!=null && trailer.hasContent())
701                 return flush(trailer);
702             return 0;
703         }
704 
705         public boolean blockReadable(long millisecs) throws IOException
706         {
707             long now = System.currentTimeMillis();
708             long end=millisecs>0?(now+millisecs):Long.MAX_VALUE;
709 
710             while (now<end)
711             {
712                 if (process(null,null))
713                     break;
714                 _endp.blockReadable(end-now);
715                 now = System.currentTimeMillis();
716             }
717 
718             return now<end;
719         }
720 
721         public boolean blockWritable(long millisecs) throws IOException
722         {
723             return _endp.blockWritable(millisecs);
724         }
725 
726         public boolean isOpen()
727         {
728             return _endp.isOpen();
729         }
730 
731         public Object getTransport()
732         {
733             return _endp;
734         }
735 
736         public void flush() throws IOException
737         {
738             process(null, null);
739         }
740 
741         public void dispatch()
742         {
743             _aEndp.dispatch();
744         }
745 
746         public void asyncDispatch()
747         {
748             _aEndp.asyncDispatch();
749         }
750 
751         public void scheduleWrite()
752         {
753             _aEndp.scheduleWrite();
754         }
755 
756         public void onIdleExpired(long idleForMs)
757         {
758             _aEndp.onIdleExpired(idleForMs);
759         }
760 
761         public void setCheckForIdle(boolean check)
762         {
763             _aEndp.setCheckForIdle(check);
764         }
765 
766         public boolean isCheckForIdle()
767         {
768             return _aEndp.isCheckForIdle();
769         }
770 
771         public void scheduleTimeout(Task task, long timeoutMs)
772         {
773             _aEndp.scheduleTimeout(task,timeoutMs);
774         }
775 
776         public void cancelTimeout(Task task)
777         {
778             _aEndp.cancelTimeout(task);
779         }
780 
781         public boolean isWritable()
782         {
783             return _aEndp.isWritable();
784         }
785 
786         public boolean hasProgressed()
787         {
788             return _progressed.getAndSet(false);
789         }
790 
791         public String getLocalAddr()
792         {
793             return _aEndp.getLocalAddr();
794         }
795 
796         public String getLocalHost()
797         {
798             return _aEndp.getLocalHost();
799         }
800 
801         public int getLocalPort()
802         {
803             return _aEndp.getLocalPort();
804         }
805 
806         public String getRemoteAddr()
807         {
808             return _aEndp.getRemoteAddr();
809         }
810 
811         public String getRemoteHost()
812         {
813             return _aEndp.getRemoteHost();
814         }
815 
816         public int getRemotePort()
817         {
818             return _aEndp.getRemotePort();
819         }
820 
821         public boolean isBlocking()
822         {
823             return false;
824         }
825 
826         public int getMaxIdleTime()
827         {
828             return _aEndp.getMaxIdleTime();
829         }
830 
831         public void setMaxIdleTime(int timeMs) throws IOException
832         {
833             _aEndp.setMaxIdleTime(timeMs);
834         }
835 
836         public Connection getConnection()
837         {
838             return _connection;
839         }
840 
841         public void setConnection(Connection connection)
842         {
843             _connection=(AsyncConnection)connection;
844         }
845 
846         public String toString()
847         {
848             // Do NOT use synchronized (SslConnection.this)
849             // because it's very easy to deadlock when debugging is enabled.
850             // We do a best effort to print the right toString() and that's it.
851             Buffer inbound = _inbound;
852             Buffer outbound = _outbound;
853             Buffer unwrap = _unwrapBuf;
854             int i = inbound == null? -1 : inbound.length();
855             int o = outbound == null ? -1 : outbound.length();
856             int u = unwrap == null ? -1 : unwrap.length();
857             return String.format("SSL %s i/o/u=%d/%d/%d ishut=%b oshut=%b {%s}",
858                     _engine.getHandshakeStatus(),
859                     i, o, u,
860                     _ishut, _oshut,
861                     _connection);
862         }
863 
864     }
865 }