docs: protected Exception decryptMessage(boolean async) {
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "decryptMessage");
          }
  
          Exception exception = null;
          // Storage for the result of calling the SSL engine
          SSLEngineResult result = null;
          // Status of the SSL engine.
          Status status = null;
          // Access output buffer to give to SSL engine. Attempt to use buffers from app channel.
          getDecryptedNetworkBuffers();
          final int packetSize = getConnLink().getPacketBufferSize();
          final int appBufferSize = getConnLink().getAppBufferSize();
  
          try {
              while (true) {
                  // JSSE (as of JDK 6 11/2010) requires at least the packet-size
                  // of available space in the output buffers or it blindly returns
                  // the BUFFER_OVERFLOW result
                  if (appBufferSize > availableDecryptionSpace()) {
                      expandDecryptedNetBuffer();
                  }
                  // Protect JSSE from potential SSL packet sizes that are too big.
                  int savedLimit = SSLUtils.adjustBufferForJSSE(netBuffer, packetSize);
                  // These limits will be reset only if the result of the unwrap is
                  // "OK". Otherwise, they will not be changed. Try to reuse this array.
                  if ((decryptedNetLimitInfo == null)
                      || (decryptedNetLimitInfo.length != decryptedNetBuffers.length)) {
                      decryptedNetLimitInfo = new int[decryptedNetBuffers.length];
                  }
                  SSLUtils.getBufferLimits(decryptedNetBuffers, decryptedNetLimitInfo);
                  if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                      Tr.event(tc, "before unwrap:\r\n\tnetBuf: "
                                   + SSLUtils.getBufferTraceInfo(netBuffer)
                                   + "\r\n\tdecNetBuffers: " + SSLUtils.getBufferTraceInfo(decryptedNetBuffers));
                  }
                  // Call the SSL engine to decrypt the request.
                  result = getConnLink().getSSLEngine().unwrap(
                                                               this.netBuffer.getWrappedByteBuffer(),
                                                               SSLUtils.getWrappedByteBuffers(decryptedNetBuffers));
                  if (0 < result.bytesProduced()) {
                      SSLUtils.flipBuffers(decryptedNetBuffers, result.bytesProduced());
                  }
                  status = result.getStatus();
                  if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                      Tr.event(tc, "after unwrap:\r\n\tnetBuf: "
                                   + SSLUtils.getBufferTraceInfo(netBuffer)
                                   + "\r\n\tdecNetBuffers: " + SSLUtils.getBufferTraceInfo(decryptedNetBuffers)
                                   + "\r\n\tstatus=" + status
                                   + " HSstatus=" + result.getHandshakeStatus()
                                   + " consumed=" + result.bytesConsumed()
                                   + " produced=" + result.bytesProduced());
                  }
  
                  // If a limit modification was saved, restore it.
                  if (-1 != savedLimit) {
                      this.netBuffer.limit(savedLimit);
                  }
  
                  // Record the number of bytes produced.
                  bytesProduced += result.bytesProduced();
  
                  // check CLOSED status first, recent JDKs are showing the status=CLOSED
                  // and hsstatus=NEED_WRAP when receiving the connection closed message
                  // from the other end. Our call to flushCloseDown() later will handle
                  // the write of our own 23 bytes of connection closure
                  if (status == Status.CLOSED) {
                      exception = sessionClosedException;
                      break;
                  }
  
                  // Handle the SSL engine result
                  // Note: check handshake status first as renegotations could be requested
                  // at any point and the Status is secondary to the handshake status
                  if ((result.getHandshakeStatus() == HandshakeStatus.NEED_TASK)
                      || (result.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)
                      || (result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)) {
                      try {
                          result = doHandshake(async);
                      } catch (IOException e) {
                          // no FFDC required
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Caught exception during SSL handshake, " + e);
                          }
                          exception = e;
                          break;
                      }
                      // Check to see if handshake was done synchronously.
                      if (result != null) {
                          // Handshake was done synchronously. Verify results.
                          status = result.getStatus();
                          if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) {
                              // Handshake finished. Need to read more per original request.
                              exception = readNeededInternalException;
                              // No error here. Just inform caller.
                              break;
                          } else if (status == Status.OK) {
                              // Handshake complete and initial request already read and decrypted.
                              prepareDataForNextChannel();
                              break;
                          } else {
                              // Unknown result from handshake. All other results should have thrown exceptions.
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "Unhandled result from SSL engine: " + status);
                              }
                              exception = new SSLException("Unhandled result from SSL engine: " + status);
                              break;
                          }
                      }
                      // Handshake is being handled asynchronously.
                      break;
                  }
  
                  else if (status == Status.OK) {
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "OK result from the SSL engine,"
                                       + " callerReqAlloc=" + getJITAllocateAction()
                                       + " decNetBuffRelReq=" + decryptedNetBufferReleaseRequired);
                      }
                      if (bytesRequested > bytesProduced) {
                          // More data needs to be decrypted.
                          // 431992 - only change pos/lim if something was produced
                          if (0 < result.bytesProduced()) {
                              // Prepare the output buffer, preventing overwrites and
                              // opening up space closed by JSSE.
                              SSLUtils.positionToLimit(decryptedNetBuffers);
                              // Reset the limits saved for the decryptedNetBuffers.
                              SSLUtils.setBufferLimits(decryptedNetBuffers, decryptedNetLimitInfo);
                          }
                          // Check if all the data has been read from the netBuffers.
                          if (this.netBuffer.remaining() == 0) {
                              // No more data available. More must be read. Reused
                              // exception instance (save new object).
                              exception = readNeededInternalException;
                              break;
                          }
                          // More data is available. Loop around and decrypt again.
                          continue;
                      }
                      // Data has been decrypted.
                      break;
                  }
  
                  else if (status == Status.BUFFER_OVERFLOW) {
                      // The output buffers provided to the SSL engine were not big
                      // enough. A bigger buffer must be supplied. If we can build
                      // a bigger buffer and call again, build it. Prepare the output
                      // buffer, preventing overwrites and opening up space closed by JSSE.
                      expandDecryptedNetBuffer();
                      // Try again with the bigger buffer array.
                      continue;
                  }
  
                  else if (status == Status.BUFFER_UNDERFLOW) {
                      // The engine was not able to unwrap the incoming data because there were not
                      // source bytes available to make a complete packet.
                      // More data needs to be read into the input buffer. Alert
                      // calling method to keep reading.
                      exception = readNeededInternalException;
                      // No error here. Just inform caller.
                      break;
                  }
  
                  else {
                      exception = new SSLException("Unknown result from ssl engine not handled yet: " + status);
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(tc, "Unknown result from ssl engine not handled yet: " + status);
                      }
                      break;
                  }
              } // end while
  
          } catch (SSLException ssle) {
              // no FFDC required
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Caught exception during decryption, " + ssle);
              }
              exception = ssle;
              cleanupDecBuffers();
          } catch (Exception up) {
              synchronized (closeSync) {
                  // if close has been called then assume this exception was due to a race condition
                  // with the close logic and consume the exception without FFDC. Otherwise rethrow
                  // as the logic did before this change.
                  if (!closeCalled) {
                      throw up;
                  }
              }
          }
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "decryptMessage: " + exception);
          }
          return exception;
      }