@Override
  public int read(ByteBuffer dst) throws IOException {
    if (closing) {
      return -1;
    } else if (!handshakeComplete) {
      return 0;
    }

    //if we have unread decrypted data in appReadBuffer read that into dst buffer.
    int read = 0;
    if (appReadBuffer.position() > 0) {
      read = readFromAppBuffer(dst);
    }
    boolean isClosed = false;
    // Each loop reads at most once from the socket.
    while (dst.remaining() > 0) {
      int netread = 0;
      netReadBuffer = Utils.ensureCapacity(netReadBuffer, netReadBufferSize());
      if (netReadBuffer.remaining() > 0) {
        netread = readFromSocketChannel();
      }

      while (netReadBuffer.position() > 0) {
        netReadBuffer.flip();
        long startTimeMs = System.currentTimeMillis();
        SSLEngineResult unwrapResult = sslEngine.unwrap(netReadBuffer, appReadBuffer);
        long decryptionTimeMs = System.currentTimeMillis() - startTimeMs;
        logger.trace("SSL decryption time: {} ms for {} bytes", decryptionTimeMs, unwrapResult.bytesProduced());
        if (unwrapResult.bytesProduced() > 0) {
          metrics.sslDecryptionTimeInUsPerKB.mark(
              TimeUnit.MILLISECONDS.toMicros(decryptionTimeMs) * 1024 / unwrapResult.bytesProduced());
        }
        netReadBuffer.compact();
        // handle ssl renegotiation.
        if (unwrapResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING
            && unwrapResult.getStatus() == Status.OK) {
          logger.trace(
              "SSLChannel Read begin renegotiation getConnectionId() {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {}",
              getConnectionId(), appReadBuffer.position(), netReadBuffer.position(), netWriteBuffer.position());
          handshake();
          metrics.sslRenegotiationCount.inc();
          break;
        }

        if (unwrapResult.getStatus() == Status.OK) {
          read += readFromAppBuffer(dst);
        } else if (unwrapResult.getStatus() == Status.BUFFER_OVERFLOW) {
          handleUnwrapOverflow();

          // appReadBuffer will extended up to currentApplicationBufferSize.
          // We need to read the existing content into dst before we can do unwrap again.
          // If there is no space in dst we can break here.
          if (dst.hasRemaining()) {
            read += readFromAppBuffer(dst);
          } else {
            break;
          }
        } else if (unwrapResult.getStatus() == Status.BUFFER_UNDERFLOW) {
          handleUnwrapUnderflow();
          break;
        } else if (unwrapResult.getStatus() == Status.CLOSED) {
          // If data has been read and unwrapped, return the data. Close will be handled on the next poll.
          if (appReadBuffer.position() == 0 && read == 0) {
            throw new EOFException();
          } else {
            isClosed = true;
            break;
          }
        }
      }
      if (read == 0 && netread < 0) {
        throw new EOFException("EOF during read");
      }
      if (netread <= 0 || isClosed) {
        break;
      }
    }
    // If data has been read and unwrapped, return the data even if end-of-stream, channel will be closed
    // on a subsequent poll.
    return read;
  }
Reads a sequence of bytes from this channel into the given buffer. Reads as much as possible
until either the dst buffer is full or there is no more data in the socket.

@param dst The buffer into which bytes are to be transferred
@return The number of bytes read, possible zero or -1 if the channel has reached end-of-stream
        and no more data is available
@throws IOException if some other I/O error occurs
ambry-network/src/main/java/com/github/ambry/network/SSLTransmission.java