docs: public static void handleHandshake(SSLEngine clientEngine, SSLEngine serverEngine) throws SSLException {
          final boolean bTrace = TraceComponent.isAnyTracingEnabled();
          if (bTrace && tc.isEntryEnabled()) {
              Tr.entry(tc, "handleHandshake");
          }
          if (clientEngine == null || serverEngine == null) {
              throw new SSLException("Null engine found: engine1=" + clientEngine + ", engine2=" + serverEngine);
          }
          SSLEngine currentEngine = clientEngine;
          SSLEngine otherEngine = serverEngine;
  
          if (bTrace && tc.isDebugEnabled()) {
              Tr.debug(tc, "Parameters: engine1=" + currentEngine.hashCode()
                           + ", engine2=" + otherEngine.hashCode());
          }
  
          WsByteBuffer netBuffer1 = allocateByteBuffer(
                                                       currentEngine.getSession().getPacketBufferSize(), false);
          WsByteBuffer netBuffer = netBuffer1;
          WsByteBuffer decryptedNetBuffer1 = allocateByteBuffer(
                                                                currentEngine.getSession().getApplicationBufferSize(), false);
          WsByteBuffer decryptedNetBuffer = decryptedNetBuffer1;
          WsByteBuffer encryptedAppBuffer1 = allocateByteBuffer(
                                                                currentEngine.getSession().getPacketBufferSize(), false);
          WsByteBuffer encryptedAppBuffer = encryptedAppBuffer1;
          SSLEngineResult result;
          Runnable task = null;
  
          SSLEngine tempEngine = null;
  
          HandshakeStatus tempStatus = null;
          HandshakeStatus currentStatus = HandshakeStatus.NEED_WRAP;
          HandshakeStatus otherStatus = HandshakeStatus.NEED_UNWRAP;
  
          if (bTrace && tc.isDebugEnabled()) {
              Tr.debug(tc, "current engine= " + currentEngine.hashCode() + ", status=" + currentStatus);
          }
  
          // Loop until both engines are finished handshaking.
          while (isHandshaking(currentEngine) || isHandshaking(otherEngine)
                 || (currentStatus != HandshakeStatus.FINISHED)
                 || (otherStatus != HandshakeStatus.FINISHED)) {
  
              // Check if data must be written to other engine.
              if ((currentStatus == HandshakeStatus.NEED_WRAP)
                  && (encryptedAppBuffer.limit() == encryptedAppBuffer.capacity())) {
                  if (bTrace && tc.isEventEnabled()) {
                      Tr.event(tc, "before wrap: encBuf: " + getBufferTraceInfo(encryptedAppBuffer));
                  }
                  result = currentEngine.wrap(emptyBuffer, encryptedAppBuffer.getWrappedByteBuffer());
                  if (0 < result.bytesProduced()) {
                      encryptedAppBuffer.flip();
                  }
                  currentStatus = result.getHandshakeStatus();
                  if (bTrace && tc.isEventEnabled()) {
                      Tr.event(tc, "after wrap: encBuf: " + getBufferTraceInfo(encryptedAppBuffer)
                                   + "\r\n\tstatus=" + result.getStatus()
                                   + " HSstatus=" + currentStatus
                                   + " consumed=" + result.bytesConsumed()
                                   + " produced=" + result.bytesProduced());
                  }
              }
  
              // Check if data must be read from other engine.
              else if ((currentStatus == HandshakeStatus.FINISHED
                        || currentStatus == HandshakeStatus.NEED_UNWRAP)
                       && (netBuffer.limit() != netBuffer.capacity())) {
                  if (bTrace && tc.isEventEnabled()) {
                      Tr.event(tc, "before unwrap: \r\n\tnetBuf: " + getBufferTraceInfo(netBuffer)
                                   + "\r\n\tdecBuf: " + getBufferTraceInfo(decryptedNetBuffer));
                  }
                  result = currentEngine.unwrap(
                                                netBuffer.getWrappedByteBuffer(),
                                                decryptedNetBuffer.getWrappedByteBuffer());
                  // handshakes shouldn't produce output so no need to flip the dec buffer
                  currentStatus = result.getHandshakeStatus();
                  if (bTrace && tc.isEventEnabled()) {
                      Tr.event(tc, "after unwrap: \r\n\tnetBuf: " + getBufferTraceInfo(netBuffer)
                                   + "\r\n\tdecBuf: " + getBufferTraceInfo(decryptedNetBuffer)
                                   + "\r\n\tstatus=" + result.getStatus()
                                   + " HSstatus=" + currentStatus
                                   + " consumed=" + result.bytesConsumed()
                                   + " produced=" + result.bytesProduced());
                  }
                  // Clear netBuffer for reuse if all data is drained.
                  if (netBuffer.remaining() == 0) {
                      netBuffer.clear();
                  }
              }
  
              // Handle anything extra that must be done within the engine.
              if (currentStatus == HandshakeStatus.NEED_TASK) {
                  while (currentStatus == HandshakeStatus.NEED_TASK) {
                      task = currentEngine.getDelegatedTask();
                      if (task != null) {
                          if (bTrace && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Run task");
                          }
                          // have a blocking task, go ahead and block this thread
                          task.run();
                          // then loop around and see if we have more to send to peer
                          currentStatus = currentEngine.getHandshakeStatus();
                          if (bTrace && tc.isDebugEnabled()) {
                              Tr.debug(tc, "After task, handshake status=" + currentStatus);
                          }
                      } else {
                          if (bTrace && tc.isDebugEnabled()) {
                              Tr.debug(tc, "No task, setting status to HS_NEED_WRAP");
                          }
                          // we were told there was something to do, but got no task
                          currentStatus = HandshakeStatus.NEED_WRAP;
                          // guess that there's some data to be sent now
                      }
                  }
  
                  // Check if more data needs to be sent from this engine.
                  if (currentStatus == HandshakeStatus.NEED_WRAP) {
                      // More data to send. Start again at the top of the loop without switching engines.
                      continue;
                  }
              } // end of NEED_TASK
  
              if (bTrace && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Switching engines");
              }
              // Encryped app output data from the current engine becomes network
              // input data for the other engine.
              netBuffer = encryptedAppBuffer;
  
              // Save aside the engine that was used in this loop.
              tempEngine = currentEngine;
              // Assign the engine to be used in the next loop.
              currentEngine = otherEngine;
              // Save the engine used in this loop for assignment next time around.
              otherEngine = tempEngine;
  
              // Save aside the status that was used in this loop
              tempStatus = currentStatus;
              // Assign the status to be used in the next loop.
              currentStatus = otherStatus;
              // Save the engine used in this loop for assignment next time around.
              otherStatus = tempStatus;
  
              if (bTrace && tc.isDebugEnabled()) {
                  Tr.debug(tc, "current engine= " + currentEngine.hashCode() + ", status=" + currentStatus);
              }
          }
  
          if (bTrace && tc.isEntryEnabled()) {
              Tr.exit(tc, "handleHandshake");
          }
      }