private void handshake() throws IOException {
    boolean read = key.isReadable();
    boolean write = key.isWritable();
    handshakeComplete = false;
    handshakeStatus = sslEngine.getHandshakeStatus();
    if (!flush(netWriteBuffer)) {
      key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
      return;
    }
    try {
      switch (handshakeStatus) {
        case NEED_TASK:
          logger.trace(
              "SSLHandshake NEED_TASK channelId {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {}",
              getConnectionId(), appReadBuffer.position(), netReadBuffer.position(), netWriteBuffer.position());
          handshakeStatus = runDelegatedTasks();
          break;
        case NEED_WRAP:
          logger.trace(
              "SSLHandshake NEED_WRAP channelId {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {}",
              getConnectionId(), appReadBuffer.position(), netReadBuffer.position(), netWriteBuffer.position());
          do {
            handshakeResult = handshakeWrap(write);
            if (handshakeResult.getStatus() == Status.BUFFER_OVERFLOW) {
              handleWrapOverflow();
            }
          } while (handshakeResult.getStatus() == Status.BUFFER_OVERFLOW);
          if (handshakeResult.getStatus() == Status.BUFFER_UNDERFLOW) {
            throw new IllegalStateException("Should not have received BUFFER_UNDERFLOW during handshake WRAP.");
          } else if (handshakeResult.getStatus() == Status.CLOSED) {
            throw new EOFException("SSL handshake status CLOSED during handshake WRAP");
          }
          logger.trace(
              "SSLHandshake NEED_WRAP channelId {}, handshakeResult {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {}",
              getConnectionId(), handshakeResult, appReadBuffer.position(), netReadBuffer.position(),
              netWriteBuffer.position());
          //if handshake status is not NEED_UNWRAP or unable to flush netWriteBuffer contents
          //we will break here otherwise we can do need_unwrap in the same call.
          if (handshakeStatus != HandshakeStatus.NEED_UNWRAP || !flush(netWriteBuffer)) {
            key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
            break;
          }
        case NEED_UNWRAP:
          logger.trace(
              "SSLHandshake NEED_UNWRAP channelId {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {}",
              getConnectionId(), appReadBuffer.position(), netReadBuffer.position(), netWriteBuffer.position());
          do {
            handshakeResult = handshakeUnwrap(read);
            if (handshakeResult.getStatus() == Status.BUFFER_OVERFLOW) {
              handleUnwrapOverflow();
            }
          } while (handshakeResult.getStatus() == Status.BUFFER_OVERFLOW);
          if (handshakeResult.getStatus() == Status.BUFFER_UNDERFLOW) {
            handleUnwrapUnderflow();
          } else if (handshakeResult.getStatus() == Status.CLOSED) {
            throw new EOFException("SSL handshake status CLOSED during handshake UNWRAP");
          }
          logger.trace(
              "SSLHandshake NEED_UNWRAP channelId {}, handshakeResult {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {}",
              getConnectionId(), handshakeResult, appReadBuffer.position(), netReadBuffer.position(),
              netWriteBuffer.position());

          //if handshakeStatus completed than fall-through to finished status.
          //after handshake is finished there is no data left to read/write in socketChannel.
          //so the selector won't invoke this channel if we don't go through the handshakeFinished here.
          if (handshakeStatus != HandshakeStatus.FINISHED) {
            if (handshakeStatus == HandshakeStatus.NEED_WRAP) {
              key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
            } else if (handshakeStatus == HandshakeStatus.NEED_UNWRAP) {
              key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
            }
            break;
          }
        case FINISHED:
          handshakeFinished();
          break;
        case NOT_HANDSHAKING:
          handshakeFinished();
          break;
        default:
          throw new IllegalStateException(String.format("Unexpected status [%s]", handshakeStatus));
      }
    } catch (SSLException e) {
      handshakeFailure();
      throw e;
    }
  }
Performs SSL handshake, non blocking.
Before application data (ambry protocols) can be sent client & ambry server must
perform ssl handshake.
During the handshake SSLEngine generates encrypted data  that will be transported over socketChannel.
Each SSLEngine operation generates SSLEngineResult, of which SSLEngineResult.handshakeStatus field is used to
determine what operation needs to occur to move handshake along.
A typical handshake might look like this.
+-------------+----------------------------------+-------------+
|  client     |  SSL/TLS message                 | HSStatus    |
+-------------+----------------------------------+-------------+
| wrap()      | ClientHello                      | NEED_UNWRAP |
| unwrap()    | ServerHello/Cert/ServerHelloDone | NEED_WRAP   |
| wrap()      | ClientKeyExchange                | NEED_WRAP   |
| wrap()      | ChangeCipherSpec                 | NEED_WRAP   |
| wrap()      | Finished                         | NEED_UNWRAP |
| unwrap()    | ChangeCipherSpec                 | NEED_UNWRAP |
| unwrap()    | Finished                         | FINISHED    |
+-------------+----------------------------------+-------------+

@throws IOException
ambry-network/src/main/java/com/github/ambry/network/SSLTransmission.java