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.io.nio;
15  
16  import java.io.IOException;
17  import java.nio.ByteBuffer;
18  import java.nio.channels.SelectionKey;
19  import java.nio.channels.SocketChannel;
20  import javax.net.ssl.SSLEngine;
21  import javax.net.ssl.SSLEngineResult;
22  import javax.net.ssl.SSLEngineResult.HandshakeStatus;
23  import javax.net.ssl.SSLException;
24  import javax.net.ssl.SSLSession;
25  
26  import org.eclipse.jetty.io.Buffer;
27  import org.eclipse.jetty.io.Buffers;
28  import org.eclipse.jetty.io.EofException;
29  import org.eclipse.jetty.util.log.Log;
30  import org.eclipse.jetty.util.log.Logger;
31  import org.eclipse.jetty.util.log.Logger;
32  
33  /* ------------------------------------------------------------ */
34  /**
35   * SslSelectChannelEndPoint
36   * <p>
37   * A SelectChannelEndPoint that uses an {@link SSLEngine} to handle an
38   * SSL connection.
39   * <p>
40   * There is a named logger "org.eclipse.jetty.http.ssl"
41   * </p>
42   */
43  public class SslSelectChannelEndPoint extends SelectChannelEndPoint
44  {
45      private static final Logger LOG = Log.getLogger(SslSelectChannelEndPoint.class);
46  
47      public static final Logger __log=Log.getLogger("org.eclipse.jetty.io.nio").getLogger("ssl");
48      
49      private static final ByteBuffer[] __NO_BUFFERS={};
50  
51      private final Buffers _buffers;
52  
53      private final SSLEngine _engine;
54      private final SSLSession _session;
55      private volatile NIOBuffer _inNIOBuffer;
56      private volatile NIOBuffer _outNIOBuffer;
57  
58      private final ByteBuffer[] _gather=new ByteBuffer[2];
59  
60      private boolean _closing=false;
61      private SSLEngineResult _result;
62  
63      private boolean _handshook=false;
64      private boolean _allowRenegotiate=false;
65  
66      private final boolean _debug = __log.isDebugEnabled(); // snapshot debug status for optimizer
67  
68      /* ------------------------------------------------------------ */
69      public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine, int maxIdleTime)
70              throws IOException
71      {
72          super(channel,selectSet,key, maxIdleTime);
73          _buffers=buffers;
74  
75          // ssl
76          _engine=engine;
77          _session=engine.getSession();
78  
79          if (_debug) __log.debug(_session+" channel="+channel);
80      }
81  
82      /* ------------------------------------------------------------ */
83      public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine)
84              throws IOException
85      {
86          super(channel,selectSet,key);
87          _buffers=buffers;
88  
89          // ssl
90          _engine=engine;
91          _session=engine.getSession();
92  
93          if (_debug) __log.debug(_session+" channel="+channel);
94      }
95  
96      int _outCount;
97  
98      /* ------------------------------------------------------------ */
99      private void needOutBuffer()
100     {
101         synchronized (this)
102         {
103             _outCount++;
104             if (_outNIOBuffer==null)
105                 _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
106         }
107     }
108 
109     /* ------------------------------------------------------------ */
110     private void freeOutBuffer()
111     {
112         synchronized (this)
113         {
114             if (--_outCount<=0 && _outNIOBuffer!=null && _outNIOBuffer.length()==0)
115             {
116                 _buffers.returnBuffer(_outNIOBuffer);
117                 _outNIOBuffer=null;
118                 _outCount=0;
119             }
120         }
121     }
122 
123     int _inCount;
124     /* ------------------------------------------------------------ */
125     private void needInBuffer()
126     {
127         synchronized (this)
128         {
129             _inCount++;
130             if(_inNIOBuffer==null)
131                 _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize());
132         }
133     }
134 
135     /* ------------------------------------------------------------ */
136     private void freeInBuffer()
137     {
138         synchronized (this)
139         {
140             if (--_inCount<=0 &&_inNIOBuffer!=null && _inNIOBuffer.length()==0)
141             {
142                 _buffers.returnBuffer(_inNIOBuffer);
143                 _inNIOBuffer=null;
144                 _inCount=0;
145             }
146         }
147     }
148 
149     /* ------------------------------------------------------------ */
150     /**
151      * @return True if SSL re-negotiation is allowed (default false)
152      */
153     public boolean isAllowRenegotiate()
154     {
155         return _allowRenegotiate;
156     }
157 
158     /* ------------------------------------------------------------ */
159     /**
160      * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
161      * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
162      * does not have CVE-2009-3555 fixed, then re-negotiation should
163      * not be allowed.
164      * @param allowRenegotiate true if re-negotiation is allowed (default false)
165      */
166     public void setAllowRenegotiate(boolean allowRenegotiate)
167     {
168         _allowRenegotiate = allowRenegotiate;
169     }
170 
171 
172     /* ------------------------------------------------------------ */
173     @Override
174     public boolean isOutputShutdown()
175     {
176         return _engine!=null && _engine.isOutboundDone();
177     }
178 
179     /* ------------------------------------------------------------ */
180     @Override
181     public boolean isInputShutdown()
182     {
183         return _engine!=null && _engine.isInboundDone();
184     }
185 
186     /* ------------------------------------------------------------ */
187     @Override
188     public void shutdownOutput() throws IOException
189     {
190         try
191         {
192             sslClose();
193         }
194         finally
195         {
196             super.shutdownOutput();
197         }
198     }
199 
200     /* ------------------------------------------------------------ */
201     protected void sslClose() throws IOException
202     {
203         if (_closing)
204             return;
205         _closing=true;
206 
207         // TODO - this really should not be done in a loop here - but with async callbacks.
208         long end=System.currentTimeMillis()+getMaxIdleTime();
209         try
210         {
211             while (isOpen() && isBufferingOutput()&& System.currentTimeMillis()<end)
212             {
213                 flush();
214                 if (isBufferingOutput())
215                 {
216                     Thread.sleep(50); // TODO non blocking
217                     flush();
218                 }
219             }
220 
221             _engine.closeOutbound();
222 
223             loop: while (isOpen() && !(_engine.isInboundDone() && _engine.isOutboundDone()) && System.currentTimeMillis()<end)
224             {
225                 while (isOpen() && isBufferingOutput() && System.currentTimeMillis()<end)
226                 {
227                     flush();
228                     if (isBufferingOutput())
229                         Thread.sleep(50);
230                 }
231 
232                 if (_debug) __log.debug(_session+" closing "+_engine.getHandshakeStatus());
233                 switch(_engine.getHandshakeStatus())
234                 {
235                     case FINISHED:
236                     case NOT_HANDSHAKING:
237                         _handshook=true;
238                         break loop;
239 
240                     case NEED_UNWRAP:
241                         Buffer buffer =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
242                         try
243                         {
244                             ByteBuffer bbuffer = ((NIOBuffer)buffer).getByteBuffer();
245                             if (!unwrap(bbuffer) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
246                             {
247                                 break loop;
248                             }
249                         }
250                         catch(SSLException e)
251                         {
252                             super.close();
253                             LOG.ignore(e);
254                         }
255                         finally
256                         {
257                             _buffers.returnBuffer(buffer);
258                         }
259                         break;
260 
261                     case NEED_TASK:
262                     {
263                         Runnable task;
264                         while ((task=_engine.getDelegatedTask())!=null)
265                         {
266                             task.run();
267                         }
268                         break;
269                     }
270 
271                     case NEED_WRAP:
272                     {
273                         needOutBuffer();
274                         ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer();
275                         try
276                         {
277                             if (_outNIOBuffer.length()>0)
278                                 flush();
279                             _outNIOBuffer.compact();
280                             int put=_outNIOBuffer.putIndex();
281                             out_buffer.position(put);
282                             _result=null;
283                             _result=_engine.wrap(__NO_BUFFERS,out_buffer);
284                             if (_debug) __log.debug(_session+" close wrap "+_result);
285                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
286                         }
287                         catch(SSLException e)
288                         {
289                             super.close();
290                             throw e;
291                         }
292                         finally
293                         {
294                             out_buffer.position(0);
295                             freeOutBuffer();
296                         }
297 
298                         break;
299                     }
300                 }
301             }
302         }
303         catch (Exception x)
304         {
305             LOG.debug(x);
306             super.close();
307         }
308     }
309 
310     /* ------------------------------------------------------------ */
311     @Override
312     public void close() throws IOException
313     {
314         try
315         {
316             sslClose();
317         }
318         finally
319         {
320             super.close();
321         }
322     }
323 
324     /* ------------------------------------------------------------ */
325     /** Fill the buffer with unencrypted bytes.
326      * Called by a Http Parser when more data is
327      * needed to continue parsing a request or a response.
328      */
329     @Override
330     public int fill(Buffer buffer) throws IOException
331     {   
332         // This end point only works on NIO buffer type (director
333         // or indirect), so extract the NIO buffer that is wrapped
334         // by the passed jetty Buffer.
335         final ByteBuffer bbuf=extractInputBuffer(buffer);
336 
337         // remember the original size of the unencrypted buffer
338         int size=buffer.length();
339 
340         HandshakeStatus initialStatus = _engine.getHandshakeStatus();
341         //noinspection SynchronizationOnLocalVariableOrMethodParameter
342         synchronized (bbuf)
343         {
344             try
345             {
346                 // Call the SSLEngine unwrap method to process data in
347                 // the inBuffer.  If there is no data in the inBuffer, then
348                 // super.fill is called to read encrypted bytes.
349                 unwrap(bbuf);
350 
351                 // Loop through the SSL engine state machine
352 
353                 int wraps=0;
354                 loop: while (true)
355                 {
356                     // If we have encrypted data in output buffer
357                     if (isBufferingOutput())
358                     {
359                         // we must flush it, as the other end might be
360                         // waiting for that outgoing data before sending
361                         // more incoming data
362                         flush();
363 
364                         // If we were unable to flush all the data, then
365                         // we should break the loop and wait for the call
366                         // back to handle when the SelectSet detects that
367                         // the channel is writable again.
368                         if (isBufferingOutput())
369                             break loop;
370                     }
371 
372                     // handle the current hand share status
373                     switch(_engine.getHandshakeStatus())
374                     {
375                         case FINISHED:
376                         case NOT_HANDSHAKING:
377                             // If we are closing, then unwrap must have CLOSED result,
378                             // so return -1 to signal upwards
379                             if (_closing)
380                                 return -1;
381 
382                             // otherwise we break loop with the data we have unwrapped.
383                             break loop;
384 
385                         case NEED_UNWRAP:
386                             checkRenegotiate();
387                             // Need more data to be unwrapped so try another call to unwrap
388                             if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
389                             {
390                                 // If the unwrap call did not make any progress and we are still in
391                                 // NEED_UNWRAP, then we should break the loop and wait for more data to
392                                 // arrive.
393                                 break loop;
394                             }
395                             // progress was made so continue the loop.
396                             break;
397 
398                         case NEED_TASK:
399                         {
400                             // A task needs to be run, so run it!
401 
402                             Runnable task;
403                             while ((task=_engine.getDelegatedTask())!=null)
404                             {
405                                 task.run();
406                             }
407 
408                             // Detect SUN JVM Bug!!!
409                             if(initialStatus==HandshakeStatus.NOT_HANDSHAKING &&
410                                _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0)
411                             {
412                                 // This should be NEED_WRAP
413                                 // 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.
414                                 // 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.
415                                 // See http://jira.codehaus.org/browse/JETTY-567 for more details
416                                 if (_debug) __log.warn(_session+" JETTY-567");
417                                 return -1;
418                             }
419                             break;
420                         }
421 
422                         case NEED_WRAP:
423                         {
424                             checkRenegotiate();
425                             // The SSL needs to send some handshake data to the other side,
426                             // so let fill become a flush for a little bit.
427                             wraps++;
428                             needOutBuffer();
429                             ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer();
430                             synchronized(out_buffer)
431                             {
432                                 try
433                                 {
434                                     // call wrap with empty application buffers, so it can
435                                     // generate required handshake messages into _outNIOBuffer
436                                     _outNIOBuffer.compact();
437                                     int put=_outNIOBuffer.putIndex();
438                                     out_buffer.position();
439                                     _result=null;
440                                     _result=_engine.wrap(__NO_BUFFERS,out_buffer);
441                                     if (_debug) __log.debug(_session+" fill wrap "+_result);
442                                     switch(_result.getStatus())
443                                     {
444                                         case BUFFER_OVERFLOW:
445                                         case BUFFER_UNDERFLOW:
446                                             LOG.warn("wrap {}",_result);
447                                         case CLOSED:
448                                             _closing=true;
449                                     }
450 
451                                     _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
452                                 }
453                                 catch(SSLException e)
454                                 {
455                                     super.close();
456                                     throw e;
457                                 }
458                                 finally
459                                 {
460                                     out_buffer.position(0);
461                                 }
462                             }
463 
464                             // flush the encrypted outNIOBuffer
465                             flush();
466                             freeOutBuffer();
467 
468                             break;
469                         }
470                     }
471                 }
472             }
473             finally
474             {
475                 // reset the Buffers
476                 buffer.setPutIndex(bbuf.position());
477                 bbuf.position(0);
478             }
479 
480             // return the number of unencrypted bytes filled.
481             int filled=buffer.length()-size;
482             if (filled>0)
483                 _handshook=true;
484             return filled;
485         }
486     }
487 
488     /* ------------------------------------------------------------ */
489     @Override
490     public int flush(Buffer buffer) throws IOException
491     {
492         return flush(buffer,null,null);
493     }
494 
495 
496     /* ------------------------------------------------------------ */
497     /*
498      */
499     @Override
500     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
501     {
502         int consumed=0;
503         int available=header.length();
504         if (buffer!=null)
505             available+=buffer.length();
506 
507         needOutBuffer();
508         ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer();
509         loop: while (true)
510         {
511             if (_outNIOBuffer.length()>0)
512             {
513                 flush();
514                 if (isBufferingOutput())
515                     break loop;
516             }
517 
518             switch(_engine.getHandshakeStatus())
519             {
520                 case FINISHED:
521                 case NOT_HANDSHAKING:
522                     if (_closing || available==0)
523                     {
524                         if (consumed==0)
525                             consumed= -1;
526                         break loop;
527                     }
528 
529                     int c;
530                     if (header!=null && header.length()>0)
531                     {
532                         if (buffer!=null && buffer.length()>0)
533                             c=wrap(header,buffer);
534                         else
535                             c=wrap(header);
536                     }
537                     else
538                         c=wrap(buffer);
539 
540 
541                     if (c>0)
542                     {
543                         _handshook=true;
544                         consumed+=c;
545                         available-=c;
546                     }
547                     else if (c<0)
548                     {
549                         if (consumed==0)
550                             consumed=-1;
551                         break loop;
552                     }
553 
554                     break;
555 
556                 case NEED_UNWRAP:
557                     checkRenegotiate();
558                     Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize());
559                     try
560                     {
561                         ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer();
562                         if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP)
563                         {
564                             break loop;
565                         }
566                     }
567                     finally
568                     {
569                         _buffers.returnBuffer(buf);
570                     }
571 
572                     break;
573 
574                 case NEED_TASK:
575                 {
576                     Runnable task;
577                     while ((task=_engine.getDelegatedTask())!=null)
578                     {
579                         task.run();
580                     }
581                     break;
582                 }
583 
584                 case NEED_WRAP:
585                 {
586                     checkRenegotiate();
587                     synchronized(out_buffer)
588                     {
589                         try
590                         {
591                             _outNIOBuffer.compact();
592                             int put=_outNIOBuffer.putIndex();
593                             out_buffer.position();
594                             _result=null;
595                             _result=_engine.wrap(__NO_BUFFERS,out_buffer);
596                             if (_debug) __log.debug(_session+" flush wrap "+_result);
597                             switch(_result.getStatus())
598                             {
599                                 case BUFFER_OVERFLOW:
600                                 case BUFFER_UNDERFLOW:
601                                     LOG.warn("unwrap {}",_result);
602                                 case CLOSED:
603                                     _closing=true;
604                             }
605                             _outNIOBuffer.setPutIndex(put+_result.bytesProduced());
606                         }
607                         catch(SSLException e)
608                         {
609                             super.close();
610                             throw e;
611                         }
612                         finally
613                         {
614                             out_buffer.position(0);
615                         }
616                     }
617 
618                     flush();
619                     if (isBufferingOutput())
620                         break loop;
621 
622                     break;
623                 }
624             }
625         }
626 
627         freeOutBuffer();
628         return consumed;
629     }
630 
631     /* ------------------------------------------------------------ */
632     @Override
633     public void flush() throws IOException
634     {
635         if (_outNIOBuffer==null)
636             return;
637 
638         int len=_outNIOBuffer.length();
639         if (isBufferingOutput())
640         {
641             int flushed=super.flush(_outNIOBuffer);
642             if (_debug) __log.debug(_session+" Flushed "+flushed+"/"+len);
643             if (isBufferingOutput())
644             {
645                 // Try again after yield.... cheaper than a reschedule.
646                 Thread.yield();
647                 flushed=super.flush(_outNIOBuffer);
648                 if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len);
649             }
650             else if (_closing && !_engine.isOutboundDone())
651             {
652                 _engine.closeOutbound();
653             }
654         }
655     }
656 
657     /* ------------------------------------------------------------ */
658     private void checkRenegotiate() throws IOException
659     {
660         if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
661         {
662             LOG.warn("SSL renegotiate denied: "+_channel);
663             super.close();
664         }
665     }
666 
667     /* ------------------------------------------------------------ */
668     private ByteBuffer extractInputBuffer(Buffer buffer)
669     {
670         assert buffer instanceof NIOBuffer;
671         NIOBuffer nbuf=(NIOBuffer)buffer;
672         ByteBuffer bbuf=nbuf.getByteBuffer();
673         bbuf.position(buffer.putIndex());
674         return bbuf;
675     }
676 
677     /* ------------------------------------------------------------ */
678     /**
679      * @return true if progress is made
680      */
681     private boolean unwrap(ByteBuffer buffer) throws IOException
682     {
683         needInBuffer();
684         ByteBuffer in_buffer=_inNIOBuffer.getByteBuffer();
685 
686         if (_inNIOBuffer.hasContent())
687             _inNIOBuffer.compact();
688         else
689             _inNIOBuffer.clear();
690 
691         int total_filled=0;
692         boolean remoteClosed = false;
693         // loop filling as much encrypted data as we can into the buffer
694         while (_inNIOBuffer.space()>0 && super.isOpen())
695         {
696             try
697             {
698                 int filled=super.fill(_inNIOBuffer);
699                 if (_debug) __log.debug(_session+" unwrap filled "+filled);
700                 if (filled < 0)
701                     remoteClosed = true;
702                 // break the loop if no progress is made (we have read everything there is to read)
703                 if (filled<=0)
704                     break;
705                 total_filled+=filled;
706             }
707             catch(IOException e)
708             {
709                 if (_inNIOBuffer.length()==0)
710                 {
711                     freeInBuffer();
712                     if (_outNIOBuffer!=null)
713                     {
714                         _outNIOBuffer.clear();
715                         freeOutBuffer();
716                     }
717                     throw e;
718                 }
719                 break;
720             }
721         }
722 
723         // If we have no progress and no data
724         if (total_filled==0 && _inNIOBuffer.length()==0)
725         {
726             if (isOpen() && remoteClosed)
727             {
728                 try
729                 {
730                     _engine.closeInbound();
731                 }
732                 catch (SSLException x)
733                 {
734                     // It may happen, for example, in case of truncation
735                     // attacks, we close so that we do not spin forever
736                     super.close();
737                 }
738             }
739 
740             freeInBuffer();
741             freeOutBuffer();
742 
743             if (!isOpen())
744                 throw new EofException();
745 
746             return false;
747         }
748 
749         // We have some in data, so try to unwrap it.
750         try
751         {
752             // inBuffer is the NIO buffer inside the _inNIOBuffer,
753             // so update its position and limit from the inNIOBuffer.
754             in_buffer.position(_inNIOBuffer.getIndex());
755             in_buffer.limit(_inNIOBuffer.putIndex());
756 
757             // Do the unwrap
758             _result=_engine.unwrap(in_buffer,buffer);
759             if (_debug) __log.debug(_session+" unwrap unwrap "+_result);
760 
761             // skip the bytes consumed
762             _inNIOBuffer.skip(_result.bytesConsumed());
763         }
764         catch(SSLException e)
765         {
766             LOG.warn(getRemoteAddr() + ":" + getRemotePort() + " " + e);
767             freeOutBuffer();
768             super.close();
769             throw e;
770         }
771         finally
772         {
773             // reset the buffer so it can be managed by the _inNIOBuffer again.
774             in_buffer.position(0);
775             in_buffer.limit(in_buffer.capacity());
776             freeInBuffer();
777         }
778 
779         // handle the unwrap results
780         switch(_result.getStatus())
781         {
782             case BUFFER_OVERFLOW:
783                 throw new IllegalStateException(_result.toString()+" "+buffer.position()+" "+buffer.limit());
784 
785             case BUFFER_UNDERFLOW:
786                 // Not enough data,
787                 // If we are closed, we will never get more, so EOF
788                 // else return and we will be tried again
789                 // later when more data arriving causes another dispatch.
790                 if (LOG.isDebugEnabled()) LOG.debug("unwrap {}",_result);
791                 if(!isOpen())
792                 {
793                     _inNIOBuffer.clear();
794                     if (_outNIOBuffer!=null)
795                         _outNIOBuffer.clear();
796                     throw new EofException();
797                 }
798                 return (total_filled > 0);
799 
800             case CLOSED:
801                 _closing=true;
802                 // return true is some bytes somewhere were moved about.
803                 return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
804             case OK:
805                 // return true is some bytes somewhere were moved about.
806                 return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0;
807             default:
808                 LOG.warn("unwrap "+_result);
809                 throw new IOException(_result.toString());
810         }
811     }
812 
813 
814     /* ------------------------------------------------------------ */
815     private ByteBuffer extractOutputBuffer(Buffer buffer)
816     {
817         if (buffer.buffer() instanceof NIOBuffer)
818             return ((NIOBuffer)buffer.buffer()).getByteBuffer();
819 
820         return ByteBuffer.wrap(buffer.array());
821     }
822 
823     /* ------------------------------------------------------------ */
824     private int wrap(final Buffer header, final Buffer buffer) throws IOException
825     {
826         _gather[0]=extractOutputBuffer(header);
827 
828         synchronized(_gather[0])
829         {
830             _gather[0].position(header.getIndex());
831             _gather[0].limit(header.putIndex());
832 
833             _gather[1]=extractOutputBuffer(buffer);
834 
835             synchronized(_gather[1])
836             {
837                 _gather[1].position(buffer.getIndex());
838                 _gather[1].limit(buffer.putIndex());
839 
840                 needOutBuffer();
841                 ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer();
842                 synchronized(out_buffer)
843                 {
844                     int consumed=0;
845                     try
846                     {
847                         _outNIOBuffer.clear();
848                         out_buffer.position(0);
849                         out_buffer.limit(out_buffer.capacity());
850 
851                         _result=null;
852                         _result=_engine.wrap(_gather,out_buffer);
853                         if (_debug) __log.debug(_session+" wrap wrap "+_result);
854                         _outNIOBuffer.setGetIndex(0);
855                         _outNIOBuffer.setPutIndex(_result.bytesProduced());
856                         consumed=_result.bytesConsumed();
857                     }
858                     catch(SSLException e)
859                     {
860                         LOG.warn(getRemoteAddr()+":"+getRemotePort()+" "+e);
861                         super.close();
862                         throw e;
863                     }
864                     finally
865                     {
866                         out_buffer.position(0);
867 
868                         if (consumed>0)
869                         {
870                             int len=consumed<header.length()?consumed:header.length();
871                             header.skip(len);
872                             consumed-=len;
873                             _gather[0].position(0);
874                             _gather[0].limit(_gather[0].capacity());
875                         }
876                         if (consumed>0)
877                         {
878                             int len=consumed<buffer.length()?consumed:buffer.length();
879                             buffer.skip(len);
880                             consumed-=len;
881                             _gather[1].position(0);
882                             _gather[1].limit(_gather[1].capacity());
883                         }
884                         assert consumed==0;
885 
886                         freeOutBuffer();
887                     }
888                 }
889             }
890         }
891 
892 
893         switch(_result.getStatus())
894         {
895             case BUFFER_OVERFLOW:
896             case BUFFER_UNDERFLOW:
897                 LOG.warn("unwrap {}",_result);
898 
899             case OK:
900                 return _result.bytesConsumed();
901             case CLOSED:
902                 _closing=true;
903                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
904 
905             default:
906                 LOG.warn("wrap "+_result);
907             throw new IOException(_result.toString());
908         }
909     }
910 
911     /* ------------------------------------------------------------ */
912     private int wrap(final Buffer buffer) throws IOException
913     {
914         _gather[0]=extractOutputBuffer(buffer);
915         synchronized(_gather[0])
916         {
917             ByteBuffer bb;
918 
919             _gather[0].position(buffer.getIndex());
920             _gather[0].limit(buffer.putIndex());
921 
922             int consumed=0;
923             needOutBuffer();
924             ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer();
925             synchronized(out_buffer)
926             {
927                 try
928                 {
929                     _outNIOBuffer.clear();
930                     out_buffer.position(0);
931                     out_buffer.limit(out_buffer.capacity());
932                     _result=null;
933                     _result=_engine.wrap(_gather[0],out_buffer);
934                     if (_debug) __log.debug(_session+" wrap wrap "+_result);
935                     _outNIOBuffer.setGetIndex(0);
936                     _outNIOBuffer.setPutIndex(_result.bytesProduced());
937                     consumed=_result.bytesConsumed();
938                 }
939                 catch(SSLException e)
940                 {
941                     LOG.warn(getRemoteAddr()+":"+getRemotePort()+" "+e);
942                     super.close();
943                     throw e;
944                 }
945                 finally
946                 {
947                     out_buffer.position(0);
948 
949                     if (consumed>0)
950                     {
951                         int len=consumed<buffer.length()?consumed:buffer.length();
952                         buffer.skip(len);
953                         consumed-=len;
954                         _gather[0].position(0);
955                         _gather[0].limit(_gather[0].capacity());
956                     }
957                     assert consumed==0;
958 
959                     freeOutBuffer();
960                 }
961             }
962         }
963         switch(_result.getStatus())
964         {
965             case BUFFER_OVERFLOW:
966             case BUFFER_UNDERFLOW:
967                 LOG.warn("unwrap {}",_result);
968 
969             case OK:
970                 return _result.bytesConsumed();
971             case CLOSED:
972                 _closing=true;
973                 return _result.bytesConsumed()>0?_result.bytesConsumed():-1;
974 
975             default:
976                 LOG.warn("wrap "+_result);
977             throw new IOException(_result.toString());
978         }
979     }
980 
981     /* ------------------------------------------------------------ */
982     @Override
983     public boolean isBufferingInput()
984     {
985         final Buffer in = _inNIOBuffer;
986         return in==null?false:_inNIOBuffer.hasContent();
987     }
988 
989     /* ------------------------------------------------------------ */
990     @Override
991     public boolean isBufferingOutput()
992     {
993         final NIOBuffer b=_outNIOBuffer;
994         return b==null?false:b.hasContent();
995     }
996 
997     /* ------------------------------------------------------------ */
998     @Override
999     public boolean isBufferred()
1000     {
1001         return true;
1002     }
1003 
1004     /* ------------------------------------------------------------ */
1005     public SSLEngine getSSLEngine()
1006     {
1007         return _engine;
1008     }
1009 
1010     /* ------------------------------------------------------------ */
1011     @Override
1012     public void scheduleWrite()
1013     {
1014         // only set !writable if we are not waiting for input
1015         if (!HandshakeStatus.NEED_UNWRAP.equals(_engine.getHandshakeStatus()) || super.isBufferingOutput())
1016         super.scheduleWrite();
1017     }
1018 
1019     /* ------------------------------------------------------------ */
1020     @Override
1021     public String toString()
1022     {
1023         final NIOBuffer i=_inNIOBuffer;
1024         final NIOBuffer o=_outNIOBuffer;
1025         return "SSL"+super.toString()+","+_engine.getHandshakeStatus()+", in/out="+
1026         (i==null?0:_inNIOBuffer.length())+"/"+(o==null?0:o.length())+
1027         " bi/o="+isBufferingInput()+"/"+isBufferingOutput()+
1028         " "+_result;
1029     }
1030 }