@Override
    public SSLEngineResult wrap(ByteBuffer[] srcs, int srcsOffset, int srcsLength, ByteBuffer dst)
            throws SSLException {
        checkArgument(srcs != null, "srcs is null");
        checkArgument(dst != null, "dst is null");
        checkPositionIndexes(srcsOffset, srcsOffset + srcsLength, srcs.length);
        if (dst.isReadOnly()) {
            throw new ReadOnlyBufferException();
        }

        if ((srcsOffset != 0) || (srcsLength != srcs.length)) {
            srcs = Arrays.copyOfRange(srcs, srcsOffset, srcsOffset + srcsLength);
        }
        BufferUtils.checkNotNull(srcs);

        synchronized (ssl) {
            switch (state) {
                case STATE_MODE_SET:
                    // Begin the handshake implicitly.
                    beginHandshakeInternal();
                    break;
                case STATE_CLOSED_OUTBOUND:
                case STATE_CLOSED:
                    // We may have pending encrypted bytes from a close_notify alert, so
                    // try to read them out
                    SSLEngineResult pendingNetResult =
                            readPendingBytesFromBIO(dst, 0, 0, HandshakeStatus.NOT_HANDSHAKING);
                    if (pendingNetResult != null) {
                        freeIfDone();
                        return pendingNetResult;
                    }
                    return new SSLEngineResult(Status.CLOSED, getHandshakeStatusInternal(), 0, 0);
                case STATE_NEW:
                    throw new IllegalStateException(
                            "Client/server mode must be set before calling wrap");
                default:
                    break;
            }

            // If we haven't completed the handshake yet, just let the caller know.
            HandshakeStatus handshakeStatus = HandshakeStatus.NOT_HANDSHAKING;
            // Prepare OpenSSL to work in server mode and receive handshake
            if (!handshakeFinished) {
                handshakeStatus = handshake();
                if (handshakeStatus == NEED_UNWRAP) {
                    return NEED_UNWRAP_OK;
                }

                if (state == STATE_CLOSED) {
                    return NEED_UNWRAP_CLOSED;
                }
                // NEED_WRAP - just fall through to perform the wrap.
            }

            int dataLength = (int) min(BufferUtils.remaining(srcs), SSL3_RT_MAX_PLAIN_LENGTH);
            if (dst.remaining() < calculateOutNetBufSize(dataLength)) {
                return new SSLEngineResult(
                    Status.BUFFER_OVERFLOW, getHandshakeStatusInternal(), 0, 0);
            }

            int bytesProduced = 0;
            int bytesConsumed = 0;
            if (dataLength > 0) {
                // Try and find a single buffer to send, e.g. the first non-empty buffer has
                // more than enough data remaining to fill a TLS record. Otherwise copy as much
                // data as possible from the source buffers to fill a record. Note the we can't
                // mark the data as consumed until we see how much the TLS layer actually consumes.
                boolean isCopy = false;
                ByteBuffer outputBuffer
                        = BufferUtils.getBufferLargerThan(srcs, SSL3_RT_MAX_PLAIN_LENGTH);
                if (outputBuffer == null) {
                    // The buffer by getOrCreateLazyDirectBuffer() is also used by
                    // writePlainTextDataHeap(), but by filling it here the write path will go via
                    // writePlainTextDataDirect() and the cost will be approximately the same,
                    // especially if compacting multiple non-direct buffers into a single
                    // direct one.
                    // TODO(): use bufferAllocator if set.
                    // https://github.com/google/conscrypt/issues/974
                    outputBuffer = BufferUtils.copyNoConsume(
                            srcs, getOrCreateLazyDirectBuffer(), SSL3_RT_MAX_PLAIN_LENGTH);
                    isCopy = true;
                }
                final SSLEngineResult pendingNetResult;
                // Write plaintext application data to the SSL engine
                int result = writePlaintextData(outputBuffer,
                        min(SSL3_RT_MAX_PLAIN_LENGTH, outputBuffer.remaining()));
                if (result > 0) {
                    bytesConsumed = result;
                    if (isCopy) {
                        // Data was a copy, so mark it as consumed in the original buffers.
                        BufferUtils.consume(srcs, bytesConsumed);
                    }

                    pendingNetResult = readPendingBytesFromBIO(
                            dst, bytesConsumed, bytesProduced, handshakeStatus);
                    if (pendingNetResult != null) {
                        if (pendingNetResult.getStatus() != OK) {
                            return pendingNetResult;
                        }
                        bytesProduced = pendingNetResult.bytesProduced();
                    }
                } else {
                    int sslError = ssl.getError(result);
                    switch (sslError) {
                        case SSL_ERROR_ZERO_RETURN:
                            // This means the connection was shutdown correctly, close inbound
                            // and outbound
                            closeAll();
                            pendingNetResult = readPendingBytesFromBIO(
                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
                            return pendingNetResult != null ? pendingNetResult
                                    : CLOSED_NOT_HANDSHAKING;
                        case SSL_ERROR_WANT_READ:
                            // If there is no pending data to read from BIO we should go back to
                            // event loop and try
                            // to read more data [1]. It is also possible that event loop will
                            // detect the socket
                            // has been closed. [1]
                            // https://www.openssl.org/docs/manmaster/man3/SSL_write.html
                            pendingNetResult = readPendingBytesFromBIO(
                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
                            return pendingNetResult != null
                                    ? pendingNetResult
                                    : new SSLEngineResult(getEngineStatus(), NEED_UNWRAP,
                                    bytesConsumed, bytesProduced);
                        case SSL_ERROR_WANT_WRITE:
                            // SSL_ERROR_WANT_WRITE typically means that the underlying
                            // transport is not writable
                            // and we should set the "want write" flag on the selector and try
                            // again when the
                            // underlying transport is writable [1]. However we are not directly
                            // writing to the
                            // underlying transport and instead writing to a BIO buffer. The
                            // OpenSsl documentation
                            // says we should do the following [1]:
                            //
                            // "When using a buffering BIO, like a BIO pair, data must be
                            // written into or retrieved
                            // out of the BIO before being able to continue."
                            //
                            // So we attempt to drain the BIO buffer below, but if there is no
                            // data this condition
                            // is undefined and we assume their is a fatal error with the
                            // openssl engine and close.
                            // [1] https://www.openssl.org/docs/manmaster/man3/SSL_write.html
                            pendingNetResult = readPendingBytesFromBIO(
                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
                            return pendingNetResult != null ? pendingNetResult
                                    : NEED_WRAP_CLOSED;
                        default:
                            // Everything else is considered as error
                            closeAll();
                            throw newSslExceptionWithMessage("SSL_write: error " + sslError);
                    }
                }
            }

            // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not have
            // checked if the srcs was empty, or only contained empty buffers.
            if (bytesConsumed == 0) {
                SSLEngineResult pendingNetResult =
                        readPendingBytesFromBIO(dst, 0, bytesProduced, handshakeStatus);
                if (pendingNetResult != null) {
                    return pendingNetResult;
                }
            }
            return newResult(bytesConsumed, bytesProduced, handshakeStatus);
        }
    }
Read encrypted data from the OpenSSL network BIO
common/src/main/java/org/conscrypt/ConscryptEngine.java