1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.reactor.ssl;
29
30 import java.io.IOException;
31 import java.net.SocketAddress;
32 import java.nio.ByteBuffer;
33 import java.nio.channels.ByteChannel;
34 import java.nio.channels.CancelledKeyException;
35 import java.nio.channels.ClosedChannelException;
36 import java.nio.channels.SelectionKey;
37 import java.util.concurrent.atomic.AtomicInteger;
38 import java.util.concurrent.atomic.AtomicReference;
39 import java.util.concurrent.locks.Lock;
40
41 import javax.net.ssl.SSLContext;
42 import javax.net.ssl.SSLEngine;
43 import javax.net.ssl.SSLEngineResult;
44 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
45 import javax.net.ssl.SSLException;
46 import javax.net.ssl.SSLSession;
47
48 import org.apache.hc.core5.annotation.Contract;
49 import org.apache.hc.core5.annotation.Internal;
50 import org.apache.hc.core5.annotation.ThreadingBehavior;
51 import org.apache.hc.core5.function.Callback;
52 import org.apache.hc.core5.io.CloseMode;
53 import org.apache.hc.core5.net.NamedEndpoint;
54 import org.apache.hc.core5.reactor.Command;
55 import org.apache.hc.core5.reactor.EventMask;
56 import org.apache.hc.core5.reactor.IOEventHandler;
57 import org.apache.hc.core5.reactor.IOSession;
58 import org.apache.hc.core5.util.Args;
59 import org.apache.hc.core5.util.Asserts;
60 import org.apache.hc.core5.util.ReflectionUtils;
61 import org.apache.hc.core5.util.Timeout;
62
63
64
65
66
67
68
69
70 @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
71 @Internal
72 public class SSLIOSession implements IOSession {
73
74 enum TLSHandShakeState { READY, INITIALIZED, HANDSHAKING, COMPLETE }
75
76 private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
77
78 private final NamedEndpoint targetEndpoint;
79 private final IOSession session;
80 private final SSLEngine sslEngine;
81 private final SSLManagedBuffer inEncrypted;
82 private final SSLManagedBuffer outEncrypted;
83 private final SSLManagedBuffer inPlain;
84 private final SSLSessionInitializer initializer;
85 private final SSLSessionVerifier verifier;
86 private final Callback<SSLIOSession> sessionStartCallback;
87 private final Callback<SSLIOSession> sessionEndCallback;
88 private final Timeout connectTimeout;
89 private final SSLMode sslMode;
90 private final AtomicInteger outboundClosedCount;
91 private final AtomicReference<TLSHandShakeState> handshakeStateRef;
92 private final IOEventHandler internalEventHandler;
93
94 private int appEventMask;
95
96 private volatile boolean endOfStream;
97 private volatile Status status = Status.ACTIVE;
98 private volatile Timeout socketTimeout;
99 private volatile TlsDetails tlsDetails;
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 public SSLIOSession(
116 final NamedEndpoint targetEndpoint,
117 final IOSession session,
118 final SSLMode sslMode,
119 final SSLContext sslContext,
120 final SSLBufferMode sslBufferMode,
121 final SSLSessionInitializer initializer,
122 final SSLSessionVerifier verifier,
123 final Callback<SSLIOSession> sessionStartCallback,
124 final Callback<SSLIOSession> sessionEndCallback,
125 final Timeout connectTimeout) {
126 super();
127 Args.notNull(session, "IO session");
128 Args.notNull(sslContext, "SSL context");
129 this.targetEndpoint = targetEndpoint;
130 this.session = session;
131 this.sslMode = sslMode;
132 this.initializer = initializer;
133 this.verifier = verifier;
134 this.sessionStartCallback = sessionStartCallback;
135 this.sessionEndCallback = sessionEndCallback;
136
137 this.appEventMask = session.getEventMask();
138 if (this.sslMode == SSLMode.CLIENT && targetEndpoint != null) {
139 this.sslEngine = sslContext.createSSLEngine(targetEndpoint.getHostName(), targetEndpoint.getPort());
140 } else {
141 this.sslEngine = sslContext.createSSLEngine();
142 }
143
144 final SSLSession sslSession = this.sslEngine.getSession();
145
146 final int netBufferSize = sslSession.getPacketBufferSize();
147 this.inEncrypted = SSLManagedBuffer.create(sslBufferMode, netBufferSize);
148 this.outEncrypted = SSLManagedBuffer.create(sslBufferMode, netBufferSize);
149
150
151 final int appBufferSize = sslSession.getApplicationBufferSize();
152 this.inPlain = SSLManagedBuffer.create(sslBufferMode, appBufferSize);
153 this.outboundClosedCount = new AtomicInteger(0);
154 this.handshakeStateRef = new AtomicReference<>(TLSHandShakeState.READY);
155 this.connectTimeout = connectTimeout;
156 this.internalEventHandler = new IOEventHandler() {
157
158 @Override
159 public void connected(final IOSession protocolSession) throws IOException {
160 if (handshakeStateRef.compareAndSet(TLSHandShakeState.READY, TLSHandShakeState.INITIALIZED)) {
161 initialize(protocolSession);
162 }
163 }
164
165 @Override
166 public void inputReady(final IOSession protocolSession, final ByteBuffer src) throws IOException {
167 if (handshakeStateRef.compareAndSet(TLSHandShakeState.READY, TLSHandShakeState.INITIALIZED)) {
168 initialize(protocolSession);
169 }
170 receiveEncryptedData();
171 doHandshake(protocolSession);
172 decryptData(protocolSession);
173 updateEventMask();
174 }
175
176 @Override
177 public void outputReady(final IOSession protocolSession) throws IOException {
178 if (handshakeStateRef.compareAndSet(TLSHandShakeState.READY, TLSHandShakeState.INITIALIZED)) {
179 initialize(protocolSession);
180 }
181 encryptData(protocolSession);
182 sendEncryptedData();
183 doHandshake(protocolSession);
184 updateEventMask();
185 }
186
187 @Override
188 public void timeout(final IOSession protocolSession, final Timeout timeout) throws IOException {
189 if (sslEngine.isInboundDone() && !sslEngine.isInboundDone()) {
190
191 close(CloseMode.IMMEDIATE);
192 }
193 ensureHandler().timeout(protocolSession, timeout);
194 }
195
196 @Override
197 public void exception(final IOSession protocolSession, final Exception cause) {
198 final IOEventHandler handler = session.getHandler();
199 if (handshakeStateRef.get() != TLSHandShakeState.COMPLETE) {
200 session.close(CloseMode.GRACEFUL);
201 close(CloseMode.IMMEDIATE);
202 }
203 if (handler != null) {
204 handler.exception(protocolSession, cause);
205 }
206 }
207
208 @Override
209 public void disconnected(final IOSession protocolSession) {
210 final IOEventHandler handler = session.getHandler();
211 if (handler != null) {
212 handler.disconnected(protocolSession);
213 }
214 }
215
216 };
217
218 }
219
220 private IOEventHandler ensureHandler() {
221 final IOEventHandler handler = session.getHandler();
222 Asserts.notNull(handler, "IO event handler");
223 return handler;
224 }
225
226 @Override
227 public IOEventHandler getHandler() {
228 return internalEventHandler;
229 }
230
231 private void initialize(final IOSession protocolSession) throws IOException {
232
233 this.socketTimeout = this.session.getSocketTimeout();
234 if (connectTimeout != null) {
235 this.session.setSocketTimeout(connectTimeout);
236 }
237
238 this.session.getLock().lock();
239 try {
240 if (this.status.compareTo(Status.CLOSING) >= 0) {
241 return;
242 }
243 switch (this.sslMode) {
244 case CLIENT:
245 this.sslEngine.setUseClientMode(true);
246 break;
247 case SERVER:
248 this.sslEngine.setUseClientMode(false);
249 break;
250 }
251 if (this.initializer != null) {
252 this.initializer.initialize(this.targetEndpoint, this.sslEngine);
253 }
254 this.handshakeStateRef.set(TLSHandShakeState.HANDSHAKING);
255 this.sslEngine.beginHandshake();
256
257 this.inEncrypted.release();
258 this.outEncrypted.release();
259 doHandshake(protocolSession);
260 } finally {
261 this.session.getLock().unlock();
262 }
263 }
264
265
266
267
268
269
270 private SSLException convert(final RuntimeException ex) {
271 Throwable cause = ex.getCause();
272 if (cause == null) {
273 cause = ex;
274 }
275 return new SSLException(cause);
276 }
277
278 private SSLEngineResult doWrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
279 try {
280 return this.sslEngine.wrap(src, dst);
281 } catch (final RuntimeException ex) {
282 throw convert(ex);
283 }
284 }
285
286 private SSLEngineResult doUnwrap(final ByteBuffer src, final ByteBuffer dst) throws SSLException {
287 try {
288 return this.sslEngine.unwrap(src, dst);
289 } catch (final RuntimeException ex) {
290 throw convert(ex);
291 }
292 }
293
294 private void doRunTask() {
295 final Runnable r = this.sslEngine.getDelegatedTask();
296 if (r != null) {
297 r.run();
298 }
299 }
300
301 private void doHandshake(final IOSession protocolSession) throws IOException {
302 boolean handshaking = true;
303
304 SSLEngineResult result = null;
305 while (handshaking) {
306 HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus();
307
308
309
310
311 if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) {
312 handshakeStatus = HandshakeStatus.NEED_WRAP;
313 }
314
315 switch (handshakeStatus) {
316 case NEED_WRAP:
317
318
319 this.session.getLock().lock();
320 try {
321
322 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
323
324
325 result = doWrap(EMPTY_BUFFER, outEncryptedBuf);
326
327 if (result.getStatus() != SSLEngineResult.Status.OK || result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
328 handshaking = false;
329 }
330 break;
331 } finally {
332 this.session.getLock().unlock();
333 }
334 case NEED_UNWRAP:
335
336
337
338 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
339 final ByteBuffer inPlainBuf = this.inPlain.acquire();
340
341
342 inEncryptedBuf.flip();
343 try {
344 result = doUnwrap(inEncryptedBuf, inPlainBuf);
345 } finally {
346 inEncryptedBuf.compact();
347 }
348
349 try {
350 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
351 throw new SSLException("Input buffer is full");
352 }
353 } finally {
354
355 if (inEncryptedBuf.position() == 0) {
356 this.inEncrypted.release();
357 }
358 }
359
360 if (this.status.compareTo(Status.CLOSING) >= 0) {
361 this.inPlain.release();
362 }
363 if (result.getStatus() != SSLEngineResult.Status.OK) {
364 handshaking = false;
365 }
366 break;
367 case NEED_TASK:
368 doRunTask();
369 break;
370 case NOT_HANDSHAKING:
371 handshaking = false;
372 break;
373 }
374 }
375
376
377
378
379 if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
380 this.session.setSocketTimeout(this.socketTimeout);
381 if (this.verifier != null) {
382 this.tlsDetails = this.verifier.verify(this.targetEndpoint, this.sslEngine);
383 }
384 if (this.tlsDetails == null) {
385 final SSLSession sslSession = this.sslEngine.getSession();
386 final String applicationProtocol = ReflectionUtils.callGetter(this.sslEngine, "ApplicationProtocol", String.class);
387 this.tlsDetails = new TlsDetails(sslSession, applicationProtocol);
388 }
389
390 ensureHandler().connected(protocolSession);
391
392 if (this.sessionStartCallback != null) {
393 this.sessionStartCallback.execute(this);
394 }
395 }
396 }
397
398 private void updateEventMask() {
399 this.session.getLock().lock();
400 try {
401
402 if (this.status == Status.ACTIVE
403 && (this.endOfStream || this.sslEngine.isInboundDone())) {
404 this.status = Status.CLOSING;
405 }
406 if (this.status == Status.CLOSING && !this.outEncrypted.hasData()) {
407 this.sslEngine.closeOutbound();
408 this.outboundClosedCount.incrementAndGet();
409 }
410 if (this.status == Status.CLOSING && this.sslEngine.isOutboundDone()
411 && (this.endOfStream || this.sslEngine.isInboundDone())) {
412 this.status = Status.CLOSED;
413 }
414
415 if (this.status.compareTo(Status.CLOSING) <= 0 && this.endOfStream
416 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
417 this.status = Status.CLOSED;
418 }
419 if (this.status == Status.CLOSED) {
420 this.session.close();
421 if (sessionEndCallback != null) {
422 sessionEndCallback.execute(this);
423 }
424 return;
425 }
426
427 if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
428 doRunTask();
429 }
430
431 final int oldMask = this.session.getEventMask();
432 int newMask = oldMask;
433 switch (this.sslEngine.getHandshakeStatus()) {
434 case NEED_WRAP:
435 newMask = EventMask.READ_WRITE;
436 break;
437 case NEED_UNWRAP:
438 newMask = EventMask.READ;
439 break;
440 case NOT_HANDSHAKING:
441 newMask = this.appEventMask;
442 break;
443 }
444
445 if (this.endOfStream && !this.inPlain.hasData()) {
446 newMask = newMask & ~EventMask.READ;
447 } else if (this.status == Status.CLOSING) {
448 newMask = newMask | EventMask.READ;
449 }
450
451
452 if (this.outEncrypted.hasData()) {
453 newMask = newMask | EventMask.WRITE;
454 } else if (this.sslEngine.isOutboundDone()) {
455 newMask = newMask & ~EventMask.WRITE;
456 }
457
458
459 if (oldMask != newMask) {
460 this.session.setEventMask(newMask);
461 }
462 } finally {
463 this.session.getLock().unlock();
464 }
465 }
466
467 private int sendEncryptedData() throws IOException {
468 this.session.getLock().lock();
469 try {
470 if (!this.outEncrypted.hasData()) {
471
472
473
474
475 return this.session.write(EMPTY_BUFFER);
476 }
477
478
479 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
480
481
482
483 if (this.status == Status.CLOSED) {
484 outEncryptedBuf.clear();
485 }
486
487
488 int bytesWritten = 0;
489 if (outEncryptedBuf.position() > 0) {
490 outEncryptedBuf.flip();
491 try {
492 bytesWritten = this.session.write(outEncryptedBuf);
493 } finally {
494 outEncryptedBuf.compact();
495 }
496 }
497
498
499 if (outEncryptedBuf.position() == 0) {
500 this.outEncrypted.release();
501 }
502 return bytesWritten;
503 } finally {
504 this.session.getLock().unlock();
505 }
506 }
507
508 private int receiveEncryptedData() throws IOException {
509 if (this.endOfStream) {
510 return -1;
511 }
512
513
514 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
515
516
517 final int bytesRead = this.session.read(inEncryptedBuf);
518
519
520 if (inEncryptedBuf.position() == 0) {
521 this.inEncrypted.release();
522 }
523 if (bytesRead == -1) {
524 this.endOfStream = true;
525 }
526 return bytesRead;
527 }
528
529 private void decryptData(final IOSession protocolSession) throws IOException {
530 final HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
531 if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING || handshakeStatus == HandshakeStatus.FINISHED)
532 && inEncrypted.hasData()) {
533 final ByteBuffer inEncryptedBuf = inEncrypted.acquire();
534 inEncryptedBuf.flip();
535 try {
536 while (inEncryptedBuf.hasRemaining()) {
537 final ByteBuffer inPlainBuf = inPlain.acquire();
538 try {
539 final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
540 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
541 throw new SSLException("Unable to complete SSL handshake");
542 }
543 if (sslEngine.isInboundDone()) {
544 endOfStream = true;
545 }
546 if (inPlainBuf.hasRemaining()) {
547 inPlainBuf.flip();
548 try {
549 ensureHandler().inputReady(protocolSession, inPlainBuf.hasRemaining() ? inPlainBuf : null);
550 } finally {
551 inPlainBuf.clear();
552 }
553 }
554 if (result.getStatus() != SSLEngineResult.Status.OK) {
555 if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW && endOfStream) {
556 throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
557 }
558 break;
559 }
560 } finally {
561 inPlain.release();
562 }
563 }
564 } finally {
565 inEncryptedBuf.compact();
566
567 if (inEncryptedBuf.position() == 0) {
568 inEncrypted.release();
569 }
570 }
571 }
572 }
573
574 private void encryptData(final IOSession protocolSession) throws IOException {
575 final boolean appReady;
576 this.session.getLock().lock();
577 try {
578 appReady = (this.appEventMask & SelectionKey.OP_WRITE) > 0
579 && this.status == Status.ACTIVE
580 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
581 } finally {
582 this.session.getLock().unlock();
583 }
584 if (appReady) {
585 ensureHandler().outputReady(protocolSession);
586 }
587 }
588
589 @Override
590 public int write(final ByteBuffer src) throws IOException {
591 Args.notNull(src, "Byte buffer");
592 this.session.getLock().lock();
593 try {
594 if (this.status != Status.ACTIVE) {
595 throw new ClosedChannelException();
596 }
597 if (this.handshakeStateRef.get() == TLSHandShakeState.READY) {
598 return 0;
599 }
600 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
601 final SSLEngineResult result = doWrap(src, outEncryptedBuf);
602 return result.bytesConsumed();
603 } finally {
604 this.session.getLock().unlock();
605 }
606 }
607
608 @Override
609 public int read(final ByteBuffer dst) {
610 return endOfStream ? -1 : 0;
611 }
612
613 @Override
614 public String getId() {
615 return session.getId();
616 }
617
618 @Override
619 public Lock getLock() {
620 return this.session.getLock();
621 }
622
623 @Override
624 public void upgrade(final IOEventHandler handler) {
625 this.session.upgrade(handler);
626 }
627
628 public TlsDetails getTlsDetails() {
629 return tlsDetails;
630 }
631
632 @Override
633 public boolean isOpen() {
634 return this.status == Status.ACTIVE && this.session.isOpen();
635 }
636
637 @Override
638 public void close() {
639 close(CloseMode.GRACEFUL);
640 }
641
642 @Override
643 public void close(final CloseMode closeMode) {
644 this.session.getLock().lock();
645 try {
646 if (closeMode == CloseMode.GRACEFUL) {
647 if (this.status.compareTo(Status.CLOSING) >= 0) {
648 return;
649 }
650 this.status = Status.CLOSING;
651 if (this.session.getSocketTimeout().isDisabled()) {
652 this.session.setSocketTimeout(Timeout.ofMilliseconds(1000));
653 }
654 try {
655
656
657
658
659 updateEventMask();
660 } catch (final CancelledKeyException ex) {
661 this.session.close(CloseMode.GRACEFUL);
662 } catch (final Exception ex) {
663 this.session.close(CloseMode.IMMEDIATE);
664 }
665 } else {
666 if (this.status == Status.CLOSED) {
667 return;
668 }
669 this.inEncrypted.release();
670 this.outEncrypted.release();
671 this.inPlain.release();
672
673 this.status = Status.CLOSED;
674 this.session.close(closeMode);
675 }
676 } finally {
677 this.session.getLock().unlock();
678 }
679 }
680
681 @Override
682 public Status getStatus() {
683 return this.status;
684 }
685
686 @Override
687 public void enqueue(final Command command, final Command.Priority priority) {
688 this.session.getLock().lock();
689 try {
690 this.session.enqueue(command, priority);
691 setEvent(SelectionKey.OP_WRITE);
692 } finally {
693 this.session.getLock().unlock();
694 }
695 }
696
697 @Override
698 public boolean hasCommands() {
699 return this.session.hasCommands();
700 }
701
702 @Override
703 public Command poll() {
704 return this.session.poll();
705 }
706
707 @Override
708 public ByteChannel channel() {
709 return this.session.channel();
710 }
711
712 @Override
713 public SocketAddress getLocalAddress() {
714 return this.session.getLocalAddress();
715 }
716
717 @Override
718 public SocketAddress getRemoteAddress() {
719 return this.session.getRemoteAddress();
720 }
721
722 @Override
723 public int getEventMask() {
724 this.session.getLock().lock();
725 try {
726 return this.appEventMask;
727 } finally {
728 this.session.getLock().unlock();
729 }
730 }
731
732 @Override
733 public void setEventMask(final int ops) {
734 this.session.getLock().lock();
735 try {
736 this.appEventMask = ops;
737 updateEventMask();
738 } finally {
739 this.session.getLock().unlock();
740 }
741 }
742
743 @Override
744 public void setEvent(final int op) {
745 this.session.getLock().lock();
746 try {
747 this.appEventMask = this.appEventMask | op;
748 updateEventMask();
749 } finally {
750 this.session.getLock().unlock();
751 }
752 }
753
754 @Override
755 public void clearEvent(final int op) {
756 this.session.getLock().lock();
757 try {
758 this.appEventMask = this.appEventMask & ~op;
759 updateEventMask();
760 } finally {
761 this.session.getLock().unlock();
762 }
763 }
764
765 @Override
766 public Timeout getSocketTimeout() {
767 return this.session.getSocketTimeout();
768 }
769
770 @Override
771 public void setSocketTimeout(final Timeout timeout) {
772 this.socketTimeout = timeout;
773 if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED) {
774 this.session.setSocketTimeout(timeout);
775 }
776 }
777
778 @Override
779 public void updateReadTime() {
780 this.session.updateReadTime();
781 }
782
783 @Override
784 public void updateWriteTime() {
785 this.session.updateWriteTime();
786 }
787
788 @Override
789 public long getLastReadTime() {
790 return this.session.getLastReadTime();
791 }
792
793 @Override
794 public long getLastWriteTime() {
795 return this.session.getLastWriteTime();
796 }
797
798 @Override
799 public long getLastEventTime() {
800 return this.session.getLastEventTime();
801 }
802
803 private static void formatOps(final StringBuilder buffer, final int ops) {
804 if ((ops & SelectionKey.OP_READ) > 0) {
805 buffer.append('r');
806 }
807 if ((ops & SelectionKey.OP_WRITE) > 0) {
808 buffer.append('w');
809 }
810 }
811
812 @Override
813 public String toString() {
814 this.session.getLock().lock();
815 try {
816 final StringBuilder buffer = new StringBuilder();
817 buffer.append(this.session);
818 buffer.append("[");
819 buffer.append(this.status);
820 buffer.append("][");
821 formatOps(buffer, this.appEventMask);
822 buffer.append("][");
823 buffer.append(this.sslEngine.getHandshakeStatus());
824 if (this.sslEngine.isInboundDone()) {
825 buffer.append("][inbound done][");
826 }
827 if (this.sslEngine.isOutboundDone()) {
828 buffer.append("][outbound done][");
829 }
830 if (this.endOfStream) {
831 buffer.append("][EOF][");
832 }
833 buffer.append("][");
834 buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
835 buffer.append("][");
836 buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
837 buffer.append("][");
838 buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
839 buffer.append("]");
840 return buffer.toString();
841 } finally {
842 this.session.getLock().unlock();
843 }
844 }
845
846 }