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() throws SSLException {
295 try {
296 final Runnable r = this.sslEngine.getDelegatedTask();
297 if (r != null) {
298 r.run();
299 }
300 } catch (final RuntimeException ex) {
301 throw convert(ex);
302 }
303 }
304
305 private void doHandshake(final IOSession protocolSession) throws IOException {
306 boolean handshaking = true;
307
308 SSLEngineResult result = null;
309 while (handshaking) {
310 HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus();
311
312
313
314
315 if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) {
316 handshakeStatus = HandshakeStatus.NEED_WRAP;
317 }
318
319 switch (handshakeStatus) {
320 case NEED_WRAP:
321
322
323 this.session.getLock().lock();
324 try {
325
326 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
327
328
329 result = doWrap(EMPTY_BUFFER, outEncryptedBuf);
330
331 if (result.getStatus() != SSLEngineResult.Status.OK || result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
332 handshaking = false;
333 }
334 break;
335 } finally {
336 this.session.getLock().unlock();
337 }
338 case NEED_UNWRAP:
339
340
341
342 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
343 final ByteBuffer inPlainBuf = this.inPlain.acquire();
344
345
346 inEncryptedBuf.flip();
347 try {
348 result = doUnwrap(inEncryptedBuf, inPlainBuf);
349 } finally {
350 inEncryptedBuf.compact();
351 }
352
353 try {
354 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
355 throw new SSLException("Input buffer is full");
356 }
357 } finally {
358
359 if (inEncryptedBuf.position() == 0) {
360 this.inEncrypted.release();
361 }
362 }
363
364 if (this.status.compareTo(Status.CLOSING) >= 0) {
365 this.inPlain.release();
366 }
367 if (result.getStatus() != SSLEngineResult.Status.OK) {
368 handshaking = false;
369 }
370 break;
371 case NEED_TASK:
372 doRunTask();
373 break;
374 case NOT_HANDSHAKING:
375 handshaking = false;
376 break;
377 }
378 }
379
380
381
382
383 if (result != null && result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
384 this.session.setSocketTimeout(this.socketTimeout);
385 if (this.verifier != null) {
386 this.tlsDetails = this.verifier.verify(this.targetEndpoint, this.sslEngine);
387 }
388 if (this.tlsDetails == null) {
389 final SSLSession sslSession = this.sslEngine.getSession();
390 final String applicationProtocol = ReflectionUtils.callGetter(this.sslEngine, "ApplicationProtocol", String.class);
391 this.tlsDetails = new TlsDetails(sslSession, applicationProtocol);
392 }
393
394 ensureHandler().connected(protocolSession);
395
396 if (this.sessionStartCallback != null) {
397 this.sessionStartCallback.execute(this);
398 }
399 }
400 }
401
402 private void updateEventMask() {
403 this.session.getLock().lock();
404 try {
405
406 if (this.status == Status.ACTIVE
407 && (this.endOfStream || this.sslEngine.isInboundDone())) {
408 this.status = Status.CLOSING;
409 }
410 if (this.status == Status.CLOSING && !this.outEncrypted.hasData()) {
411 this.sslEngine.closeOutbound();
412 this.outboundClosedCount.incrementAndGet();
413 }
414 if (this.status == Status.CLOSING && this.sslEngine.isOutboundDone()
415 && (this.endOfStream || this.sslEngine.isInboundDone())) {
416 this.status = Status.CLOSED;
417 }
418
419 if (this.status.compareTo(Status.CLOSING) <= 0 && this.endOfStream
420 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
421 this.status = Status.CLOSED;
422 }
423 if (this.status == Status.CLOSED) {
424 this.session.close();
425 if (sessionEndCallback != null) {
426 sessionEndCallback.execute(this);
427 }
428 return;
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 case NEED_TASK:
444 break;
445 case FINISHED:
446 break;
447 }
448
449 if (this.endOfStream && !this.inPlain.hasData()) {
450 newMask = newMask & ~EventMask.READ;
451 } else if (this.status == Status.CLOSING) {
452 newMask = newMask | EventMask.READ;
453 }
454
455
456 if (this.outEncrypted.hasData()) {
457 newMask = newMask | EventMask.WRITE;
458 } else if (this.sslEngine.isOutboundDone()) {
459 newMask = newMask & ~EventMask.WRITE;
460 }
461
462
463 if (oldMask != newMask) {
464 this.session.setEventMask(newMask);
465 }
466 } finally {
467 this.session.getLock().unlock();
468 }
469 }
470
471 private int sendEncryptedData() throws IOException {
472 this.session.getLock().lock();
473 try {
474 if (!this.outEncrypted.hasData()) {
475
476
477
478
479 return this.session.write(EMPTY_BUFFER);
480 }
481
482
483 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
484
485
486
487 if (this.status == Status.CLOSED) {
488 outEncryptedBuf.clear();
489 }
490
491
492 int bytesWritten = 0;
493 if (outEncryptedBuf.position() > 0) {
494 outEncryptedBuf.flip();
495 try {
496 bytesWritten = this.session.write(outEncryptedBuf);
497 } finally {
498 outEncryptedBuf.compact();
499 }
500 }
501
502
503 if (outEncryptedBuf.position() == 0) {
504 this.outEncrypted.release();
505 }
506 return bytesWritten;
507 } finally {
508 this.session.getLock().unlock();
509 }
510 }
511
512 private int receiveEncryptedData() throws IOException {
513 if (this.endOfStream) {
514 return -1;
515 }
516
517
518 final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
519
520
521 final int bytesRead = this.session.read(inEncryptedBuf);
522
523
524 if (inEncryptedBuf.position() == 0) {
525 this.inEncrypted.release();
526 }
527 if (bytesRead == -1) {
528 this.endOfStream = true;
529 }
530 return bytesRead;
531 }
532
533 private void decryptData(final IOSession protocolSession) throws IOException {
534 final HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();
535 if ((handshakeStatus == HandshakeStatus.NOT_HANDSHAKING || handshakeStatus == HandshakeStatus.FINISHED)
536 && inEncrypted.hasData()) {
537 final ByteBuffer inEncryptedBuf = inEncrypted.acquire();
538 inEncryptedBuf.flip();
539 try {
540 while (inEncryptedBuf.hasRemaining()) {
541 final ByteBuffer inPlainBuf = inPlain.acquire();
542 try {
543 final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
544 if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
545 throw new SSLException("Unable to complete SSL handshake");
546 }
547 if (sslEngine.isInboundDone()) {
548 endOfStream = true;
549 }
550 if (inPlainBuf.hasRemaining()) {
551 inPlainBuf.flip();
552 try {
553 ensureHandler().inputReady(protocolSession, inPlainBuf.hasRemaining() ? inPlainBuf : null);
554 } finally {
555 inPlainBuf.clear();
556 }
557 }
558 if (result.getStatus() != SSLEngineResult.Status.OK) {
559 if (result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW && endOfStream) {
560 throw new SSLException("Unable to decrypt incoming data due to unexpected end of stream");
561 }
562 break;
563 }
564 } finally {
565 inPlain.release();
566 }
567 }
568 } finally {
569 inEncryptedBuf.compact();
570
571 if (inEncryptedBuf.position() == 0) {
572 inEncrypted.release();
573 }
574 }
575 }
576 }
577
578 private void encryptData(final IOSession protocolSession) throws IOException {
579 final boolean appReady;
580 this.session.getLock().lock();
581 try {
582 appReady = (this.appEventMask & SelectionKey.OP_WRITE) > 0
583 && this.status == Status.ACTIVE
584 && this.sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING;
585 } finally {
586 this.session.getLock().unlock();
587 }
588 if (appReady) {
589 ensureHandler().outputReady(protocolSession);
590 }
591 }
592
593 @Override
594 public int write(final ByteBuffer src) throws IOException {
595 Args.notNull(src, "Byte buffer");
596 this.session.getLock().lock();
597 try {
598 if (this.status != Status.ACTIVE) {
599 throw new ClosedChannelException();
600 }
601 if (this.handshakeStateRef.get() == TLSHandShakeState.READY) {
602 return 0;
603 }
604 final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
605 final SSLEngineResult result = doWrap(src, outEncryptedBuf);
606 return result.bytesConsumed();
607 } finally {
608 this.session.getLock().unlock();
609 }
610 }
611
612 @Override
613 public int read(final ByteBuffer dst) {
614 return endOfStream ? -1 : 0;
615 }
616
617 @Override
618 public String getId() {
619 return session.getId();
620 }
621
622 @Override
623 public Lock getLock() {
624 return this.session.getLock();
625 }
626
627 @Override
628 public void upgrade(final IOEventHandler handler) {
629 this.session.upgrade(handler);
630 }
631
632 public TlsDetails getTlsDetails() {
633 return tlsDetails;
634 }
635
636 @Override
637 public boolean isOpen() {
638 return this.status == Status.ACTIVE && this.session.isOpen();
639 }
640
641 @Override
642 public void close() {
643 close(CloseMode.GRACEFUL);
644 }
645
646 @Override
647 public void close(final CloseMode closeMode) {
648 this.session.getLock().lock();
649 try {
650 if (closeMode == CloseMode.GRACEFUL) {
651 if (this.status.compareTo(Status.CLOSING) >= 0) {
652 return;
653 }
654 this.status = Status.CLOSING;
655 if (this.session.getSocketTimeout().isDisabled()) {
656 this.session.setSocketTimeout(Timeout.ofMilliseconds(1000));
657 }
658 try {
659
660
661
662
663 updateEventMask();
664 } catch (final CancelledKeyException ex) {
665 this.session.close(CloseMode.GRACEFUL);
666 } catch (final Exception ex) {
667 this.session.close(CloseMode.IMMEDIATE);
668 }
669 } else {
670 if (this.status == Status.CLOSED) {
671 return;
672 }
673 this.inEncrypted.release();
674 this.outEncrypted.release();
675 this.inPlain.release();
676
677 this.status = Status.CLOSED;
678 this.session.close(closeMode);
679 }
680 } finally {
681 this.session.getLock().unlock();
682 }
683 }
684
685 @Override
686 public Status getStatus() {
687 return this.status;
688 }
689
690 @Override
691 public void enqueue(final Command command, final Command.Priority priority) {
692 this.session.getLock().lock();
693 try {
694 this.session.enqueue(command, priority);
695 setEvent(SelectionKey.OP_WRITE);
696 } finally {
697 this.session.getLock().unlock();
698 }
699 }
700
701 @Override
702 public boolean hasCommands() {
703 return this.session.hasCommands();
704 }
705
706 @Override
707 public Command poll() {
708 return this.session.poll();
709 }
710
711 @Override
712 public ByteChannel channel() {
713 return this.session.channel();
714 }
715
716 @Override
717 public SocketAddress getLocalAddress() {
718 return this.session.getLocalAddress();
719 }
720
721 @Override
722 public SocketAddress getRemoteAddress() {
723 return this.session.getRemoteAddress();
724 }
725
726 @Override
727 public int getEventMask() {
728 this.session.getLock().lock();
729 try {
730 return this.appEventMask;
731 } finally {
732 this.session.getLock().unlock();
733 }
734 }
735
736 @Override
737 public void setEventMask(final int ops) {
738 this.session.getLock().lock();
739 try {
740 this.appEventMask = ops;
741 updateEventMask();
742 } finally {
743 this.session.getLock().unlock();
744 }
745 }
746
747 @Override
748 public void setEvent(final int op) {
749 this.session.getLock().lock();
750 try {
751 this.appEventMask = this.appEventMask | op;
752 updateEventMask();
753 } finally {
754 this.session.getLock().unlock();
755 }
756 }
757
758 @Override
759 public void clearEvent(final int op) {
760 this.session.getLock().lock();
761 try {
762 this.appEventMask = this.appEventMask & ~op;
763 updateEventMask();
764 } finally {
765 this.session.getLock().unlock();
766 }
767 }
768
769 @Override
770 public Timeout getSocketTimeout() {
771 return this.session.getSocketTimeout();
772 }
773
774 @Override
775 public void setSocketTimeout(final Timeout timeout) {
776 this.socketTimeout = timeout;
777 if (this.sslEngine.getHandshakeStatus() == HandshakeStatus.FINISHED) {
778 this.session.setSocketTimeout(timeout);
779 }
780 }
781
782 @Override
783 public void updateReadTime() {
784 this.session.updateReadTime();
785 }
786
787 @Override
788 public void updateWriteTime() {
789 this.session.updateWriteTime();
790 }
791
792 @Override
793 public long getLastReadTime() {
794 return this.session.getLastReadTime();
795 }
796
797 @Override
798 public long getLastWriteTime() {
799 return this.session.getLastWriteTime();
800 }
801
802 @Override
803 public long getLastEventTime() {
804 return this.session.getLastEventTime();
805 }
806
807 private static void formatOps(final StringBuilder buffer, final int ops) {
808 if ((ops & SelectionKey.OP_READ) > 0) {
809 buffer.append('r');
810 }
811 if ((ops & SelectionKey.OP_WRITE) > 0) {
812 buffer.append('w');
813 }
814 }
815
816 @Override
817 public String toString() {
818 this.session.getLock().lock();
819 try {
820 final StringBuilder buffer = new StringBuilder();
821 buffer.append(this.session);
822 buffer.append("[");
823 buffer.append(this.status);
824 buffer.append("][");
825 formatOps(buffer, this.appEventMask);
826 buffer.append("][");
827 buffer.append(this.sslEngine.getHandshakeStatus());
828 if (this.sslEngine.isInboundDone()) {
829 buffer.append("][inbound done][");
830 }
831 if (this.sslEngine.isOutboundDone()) {
832 buffer.append("][outbound done][");
833 }
834 if (this.endOfStream) {
835 buffer.append("][EOF][");
836 }
837 buffer.append("][");
838 buffer.append(!this.inEncrypted.hasData() ? 0 : inEncrypted.acquire().position());
839 buffer.append("][");
840 buffer.append(!this.inPlain.hasData() ? 0 : inPlain.acquire().position());
841 buffer.append("][");
842 buffer.append(!this.outEncrypted.hasData() ? 0 : outEncrypted.acquire().position());
843 buffer.append("]");
844 return buffer.toString();
845 } finally {
846 this.session.getLock().unlock();
847 }
848 }
849
850 }