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