Search in sources :

Example 16 with SSLEngineResult

use of javax.net.ssl.SSLEngineResult in project jersey by jersey.

the class SslFilter method close.

@Override
synchronized void close() {
    if (state == State.NOT_STARTED) {
        downstreamFilter.close();
        return;
    }
    sslEngine.closeOutbound();
    try {
        LazyBuffer lazyBuffer = new LazyBuffer();
        while (sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
            ByteBuffer buffer = lazyBuffer.get();
            SSLEngineResult result = sslEngine.wrap(emptyBuffer, buffer);
            switch(result.getStatus()) {
                case BUFFER_OVERFLOW:
                    {
                        lazyBuffer.resize();
                        break;
                    }
                case BUFFER_UNDERFLOW:
                    {
                        /* This basically says that there is not enough data to create an SSL packet. Javadoc suggests that
                        BUFFER_UNDERFLOW can occur only after unwrap(), but to be 100% sure we handle all possible error
                        states: */
                        throw new IllegalStateException("SSL engine underflow while close operation \n" + getDebugState());
                    }
            }
        }
        if (lazyBuffer.isAllocated()) {
            ByteBuffer buffer = lazyBuffer.get();
            buffer.flip();
            writeQueue.write(buffer, new CompletionHandler<ByteBuffer>() {

                @Override
                public void completed(ByteBuffer result) {
                    downstreamFilter.close();
                }

                @Override
                public void failed(Throwable throwable) {
                    downstreamFilter.close();
                }
            });
        } else {
            // make sure we close even if SSL had nothing to send
            downstreamFilter.close();
        }
    } catch (Exception e) {
        handleSslError(e);
    }
}
Also used : SSLEngineResult(javax.net.ssl.SSLEngineResult) ByteBuffer(java.nio.ByteBuffer) SSLException(javax.net.ssl.SSLException)

Example 17 with SSLEngineResult

use of javax.net.ssl.SSLEngineResult in project jersey by jersey.

the class SslFilter method handleWrite.

private void handleWrite(final ByteBuffer applicationData, final CompletionHandler<ByteBuffer> completionHandler) {
    try {
        // transport buffer always writes all data, so there are not leftovers in the networkOutputBuffer
        networkOutputBuffer.clear();
        SSLEngineResult result = sslEngine.wrap(applicationData, networkOutputBuffer);
        switch(result.getStatus()) {
            case BUFFER_OVERFLOW:
                {
                    /* this means that the content of the ssl packet (max 16kB) did not fit into
                       networkOutputBuffer, we make sure to set networkOutputBuffer > max 16kB + SSL headers
                       when initializing this filter. This indicates a bug. */
                    throw new IllegalStateException("SSL packet does not fit into the network buffer: " + networkOutputBuffer + "\n" + getDebugState());
                }
            case BUFFER_UNDERFLOW:
                {
                    /* This basically says that there is not enough data to create an SSL packet. Javadoc suggests that
                    BUFFER_UNDERFLOW can occur only after unwrap(), but to be 100% sure we handle all possible error states: */
                    throw new IllegalStateException("SSL engine underflow with the following application input: " + applicationData + "\n" + getDebugState());
                }
            case CLOSED:
                {
                    state = State.CLOSED;
                    break;
                }
            case OK:
                {
                    // check if we started re-handshaking
                    if (result.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                        state = State.REHANDSHAKING;
                    }
                    networkOutputBuffer.flip();
                    // write only if something was written to the output buffer
                    if (networkOutputBuffer.hasRemaining()) {
                        writeQueue.write(networkOutputBuffer, new CompletionHandler<ByteBuffer>() {

                            @Override
                            public void completed(ByteBuffer result) {
                                handlePostWrite(applicationData, completionHandler);
                            }

                            @Override
                            public void failed(Throwable throwable) {
                                completionHandler.failed(throwable);
                            }
                        });
                    } else {
                        handlePostWrite(applicationData, completionHandler);
                    }
                    break;
                }
        }
    } catch (SSLException e) {
        handleSslError(e);
    }
}
Also used : SSLEngineResult(javax.net.ssl.SSLEngineResult) ByteBuffer(java.nio.ByteBuffer) SSLException(javax.net.ssl.SSLException)

Example 18 with SSLEngineResult

use of javax.net.ssl.SSLEngineResult in project netty by netty.

the class ReferenceCountedOpenSslEngine method wrap.

@Override
public final SSLEngineResult wrap(final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException {
    // Throw required runtime exceptions
    if (srcs == null) {
        throw new IllegalArgumentException("srcs is null");
    }
    if (dst == null) {
        throw new IllegalArgumentException("dst is null");
    }
    if (offset >= srcs.length || offset + length > srcs.length) {
        throw new IndexOutOfBoundsException("offset: " + offset + ", length: " + length + " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
    }
    if (dst.isReadOnly()) {
        throw new ReadOnlyBufferException();
    }
    synchronized (this) {
        if (isOutboundDone()) {
            // All drained in the outbound buffer
            return isInboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_UNWRAP_CLOSED;
        }
        int bytesProduced = 0;
        ByteBuf bioReadCopyBuf = null;
        try {
            // Setup the BIO buffer so that we directly write the encryption results into dst.
            if (dst.isDirect()) {
                SSL.bioSetByteBuffer(networkBIO, Buffer.address(dst) + dst.position(), dst.remaining(), true);
            } else {
                bioReadCopyBuf = alloc.directBuffer(dst.remaining());
                SSL.bioSetByteBuffer(networkBIO, memoryAddress(bioReadCopyBuf), bioReadCopyBuf.writableBytes(), true);
            }
            int bioLengthBefore = SSL.bioLengthByteBuffer(networkBIO);
            // Explicit use outboundClosed as we want to drain any bytes that are still present.
            if (outboundClosed) {
                // There is something left to drain.
                // See https://github.com/netty/netty/issues/6260
                bytesProduced = SSL.bioFlushByteBuffer(networkBIO);
                if (bytesProduced <= 0) {
                    return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, 0);
                }
                // OpenSSL can give us.
                if (!doSSLShutdown()) {
                    return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, bytesProduced);
                }
                bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO);
                return newResultMayFinishHandshake(NEED_WRAP, 0, bytesProduced);
            }
            // Flush any data that may be implicitly generated by OpenSSL (handshake, close, etc..).
            SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING;
            // Prepare OpenSSL to work in server mode and receive handshake
            if (handshakeState != HandshakeState.FINISHED) {
                if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
                    // Update accepted so we know we triggered the handshake via wrap
                    handshakeState = HandshakeState.STARTED_IMPLICITLY;
                }
                // Flush any data that may have been written implicitly during the handshake by OpenSSL.
                bytesProduced = SSL.bioFlushByteBuffer(networkBIO);
                if (bytesProduced > 0 && handshakeException != null) {
                    // we allow handshake() to throw the handshake exception.
                    return newResult(NEED_WRAP, 0, bytesProduced);
                }
                status = handshake();
                if (renegotiationPending && status == FINISHED) {
                    // If renegotiationPending is true that means when we attempted to start renegotiation
                    // the BIO buffer didn't have enough space to hold the HelloRequest which prompts the
                    // client to initiate a renegotiation. At this point the HelloRequest has been written
                    // so we can actually start the handshake process.
                    renegotiationPending = false;
                    SSL.setState(ssl, SSL.SSL_ST_ACCEPT);
                    handshakeState = HandshakeState.STARTED_EXPLICITLY;
                    status = handshake();
                }
                // Handshake may have generated more data, for example if the internal SSL buffer is small
                // we may have freed up space by flushing above.
                bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO);
                if (bytesProduced > 0) {
                    // It's important we call this before wrapStatus() as wrapStatus() may shutdown the engine.
                    return newResult(mayFinishHandshake(status != FINISHED ? getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED), 0, bytesProduced);
                }
                if (status == NEED_UNWRAP) {
                    // Signal if the outbound is done or not.
                    return isOutboundDone() ? NEED_UNWRAP_CLOSED : NEED_UNWRAP_OK;
                }
                // still present.
                if (outboundClosed) {
                    bytesProduced = SSL.bioFlushByteBuffer(networkBIO);
                    return newResultMayFinishHandshake(status, 0, bytesProduced);
                }
            }
            int srcsLen = 0;
            final int endOffset = offset + length;
            for (int i = offset; i < endOffset; ++i) {
                final ByteBuffer src = srcs[i];
                if (src == null) {
                    throw new IllegalArgumentException("srcs[" + i + "] is null");
                }
                if (srcsLen == MAX_PLAINTEXT_LENGTH) {
                    continue;
                }
                srcsLen += src.remaining();
                if (srcsLen > MAX_PLAINTEXT_LENGTH || srcsLen < 0) {
                    // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to MAX_PLAINTEXT_LENGTH.
                    // This also help us to guard against overflow.
                    // We not break out here as we still need to check for null entries in srcs[].
                    srcsLen = MAX_PLAINTEXT_LENGTH;
                }
            }
            if (dst.remaining() < calculateOutNetBufSize(srcsLen, endOffset - offset)) {
                // buffer.
                return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0);
            }
            // There was no pending data in the network BIO -- encrypt any application data
            int bytesConsumed = 0;
            // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs.
            bytesProduced = SSL.bioFlushByteBuffer(networkBIO);
            for (; offset < endOffset; ++offset) {
                final ByteBuffer src = srcs[offset];
                final int remaining = src.remaining();
                if (remaining == 0) {
                    continue;
                }
                // Write plaintext application data to the SSL engine
                int bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed));
                if (bytesWritten > 0) {
                    bytesConsumed += bytesWritten;
                    // Determine how much encrypted data was generated:
                    final int pendingNow = SSL.bioLengthByteBuffer(networkBIO);
                    bytesProduced += bioLengthBefore - pendingNow;
                    bioLengthBefore = pendingNow;
                    if (bytesConsumed == MAX_PLAINTEXT_LENGTH || bytesProduced == dst.remaining()) {
                        return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced);
                    }
                } else {
                    int sslError = SSL.getError(ssl, bytesWritten);
                    if (sslError == SSL.SSL_ERROR_ZERO_RETURN) {
                        // This means the connection was shutdown correctly, close inbound and outbound
                        if (!receivedShutdown) {
                            closeAll();
                            bytesProduced += bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO);
                            SSLEngineResult.HandshakeStatus hs = mayFinishHandshake(status != FINISHED ? getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED);
                            return newResult(hs, bytesConsumed, bytesProduced);
                        }
                        return newResult(NOT_HANDSHAKING, bytesConsumed, bytesProduced);
                    } else if (sslError == SSL.SSL_ERROR_WANT_READ) {
                        // been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
                        return newResult(NEED_UNWRAP, bytesConsumed, bytesProduced);
                    } else if (sslError == SSL.SSL_ERROR_WANT_WRITE) {
                        // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
                        return newResult(NEED_WRAP, bytesConsumed, bytesProduced);
                    } else {
                        // Everything else is considered as error
                        throw shutdownWithError("SSL_write");
                    }
                }
            }
            return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced);
        } finally {
            SSL.bioClearByteBuffer(networkBIO);
            if (bioReadCopyBuf == null) {
                dst.position(dst.position() + bytesProduced);
            } else {
                assert bioReadCopyBuf.readableBytes() <= dst.remaining() : "The destination buffer " + dst + " didn't have enough remaining space to hold the encrypted content in " + bioReadCopyBuf;
                dst.put(bioReadCopyBuf.internalNioBuffer(bioReadCopyBuf.readerIndex(), bytesProduced));
                bioReadCopyBuf.release();
            }
        }
    }
}
Also used : ReadOnlyBufferException(java.nio.ReadOnlyBufferException) SSLEngineResult(javax.net.ssl.SSLEngineResult) ByteBuf(io.netty.buffer.ByteBuf) ByteBuffer(java.nio.ByteBuffer)

Example 19 with SSLEngineResult

use of javax.net.ssl.SSLEngineResult in project netty by netty.

the class ReferenceCountedOpenSslEngine method unwrap.

public final SSLEngineResult unwrap(final ByteBuffer[] srcs, int srcsOffset, final int srcsLength, final ByteBuffer[] dsts, int dstsOffset, final int dstsLength) throws SSLException {
    // Throw required runtime exceptions
    if (srcs == null) {
        throw new NullPointerException("srcs");
    }
    if (srcsOffset >= srcs.length || srcsOffset + srcsLength > srcs.length) {
        throw new IndexOutOfBoundsException("offset: " + srcsOffset + ", length: " + srcsLength + " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
    }
    if (dsts == null) {
        throw new IllegalArgumentException("dsts is null");
    }
    if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) {
        throw new IndexOutOfBoundsException("offset: " + dstsOffset + ", length: " + dstsLength + " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))");
    }
    long capacity = 0;
    final int dstsEndOffset = dstsOffset + dstsLength;
    for (int i = dstsOffset; i < dstsEndOffset; i++) {
        ByteBuffer dst = dsts[i];
        if (dst == null) {
            throw new IllegalArgumentException("dsts[" + i + "] is null");
        }
        if (dst.isReadOnly()) {
            throw new ReadOnlyBufferException();
        }
        capacity += dst.remaining();
    }
    final int srcsEndOffset = srcsOffset + srcsLength;
    long len = 0;
    for (int i = srcsOffset; i < srcsEndOffset; i++) {
        ByteBuffer src = srcs[i];
        if (src == null) {
            throw new IllegalArgumentException("srcs[" + i + "] is null");
        }
        len += src.remaining();
    }
    synchronized (this) {
        if (isInboundDone()) {
            return isOutboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_WRAP_CLOSED;
        }
        SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING;
        // Prepare OpenSSL to work in server mode and receive handshake
        if (handshakeState != HandshakeState.FINISHED) {
            if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
                // Update accepted so we know we triggered the handshake via wrap
                handshakeState = HandshakeState.STARTED_IMPLICITLY;
            }
            status = handshake();
            if (status == NEED_WRAP) {
                return NEED_WRAP_OK;
            }
            // Check if the inbound is considered to be closed if so let us try to wrap again.
            if (isInboundDone) {
                return NEED_WRAP_CLOSED;
            }
        }
        if (len < SSL_RECORD_HEADER_LENGTH) {
            return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0);
        }
        int packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset);
        if (packetLength == SslUtils.NOT_ENCRYPTED) {
            throw new NotSslRecordException("not an SSL/TLS record");
        }
        if (packetLength - SSL_RECORD_HEADER_LENGTH > capacity) {
            // that the buffer needs to be increased.
            return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0);
        }
        if (len < packetLength) {
            // the whole packet.
            return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0);
        }
        // This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW.
        assert srcsOffset < srcsEndOffset;
        // This must always be the case if we reached here.
        assert capacity > 0;
        // Number of produced bytes
        int bytesProduced = 0;
        int bytesConsumed = 0;
        try {
            for (; srcsOffset < srcsEndOffset; ++srcsOffset) {
                ByteBuffer src = srcs[srcsOffset];
                int remaining = src.remaining();
                if (remaining == 0) {
                    // with length 0.
                    continue;
                }
                // Write more encrypted data into the BIO. Ensure we only read one packet at a time as
                // stated in the SSLEngine javadocs.
                int pendingEncryptedBytes = min(packetLength, remaining);
                ByteBuf bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes);
                try {
                    readLoop: for (; dstsOffset < dstsEndOffset; ++dstsOffset) {
                        ByteBuffer dst = dsts[dstsOffset];
                        if (!dst.hasRemaining()) {
                            // No space left in the destination buffer, skip it.
                            continue;
                        }
                        int bytesRead = readPlaintextData(dst);
                        // We are directly using the ByteBuffer memory for the write, and so we only know what
                        // has been consumed after we let SSL decrypt the data. At this point we should update
                        // the number of bytes consumed, update the ByteBuffer position, and release temp
                        // ByteBuf.
                        int localBytesConsumed = pendingEncryptedBytes - SSL.bioLengthByteBuffer(networkBIO);
                        bytesConsumed += localBytesConsumed;
                        packetLength -= localBytesConsumed;
                        pendingEncryptedBytes -= localBytesConsumed;
                        src.position(src.position() + localBytesConsumed);
                        if (bytesRead > 0) {
                            bytesProduced += bytesRead;
                            if (!dst.hasRemaining()) {
                                // Move to the next dst buffer as this one is full.
                                continue;
                            }
                            if (packetLength == 0) {
                                // We read everything return now.
                                return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced);
                            }
                            // try to write again to the BIO. stop reading from it by break out of the readLoop.
                            break;
                        } else {
                            int sslError = SSL.getError(ssl, bytesRead);
                            if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) {
                                // write more to the BIO.
                                break readLoop;
                            } else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) {
                                // This means the connection was shutdown correctly, close inbound and outbound
                                if (!receivedShutdown) {
                                    closeAll();
                                }
                                return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced);
                            } else {
                                return sslReadErrorResult(SSL.getLastErrorNumber(), bytesConsumed, bytesProduced);
                            }
                        }
                    }
                    // Either we have no more dst buffers to put the data, or no more data to generate; we are done.
                    if (dstsOffset >= dstsEndOffset || packetLength == 0) {
                        break;
                    }
                } finally {
                    if (bioWriteCopyBuf != null) {
                        bioWriteCopyBuf.release();
                    }
                }
            }
        } finally {
            SSL.bioClearByteBuffer(networkBIO);
            rejectRemoteInitiatedRenegation();
        }
        // Check to see if we received a close_notify message from the peer.
        if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
            closeAll();
        }
        return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced);
    }
}
Also used : ReadOnlyBufferException(java.nio.ReadOnlyBufferException) SSLEngineResult(javax.net.ssl.SSLEngineResult) ByteBuf(io.netty.buffer.ByteBuf) ByteBuffer(java.nio.ByteBuffer)

Example 20 with SSLEngineResult

use of javax.net.ssl.SSLEngineResult in project netty by netty.

the class SslHandler method wrapNonAppData.

/**
     * This method will not call
     * {@link #setHandshakeFailure(ChannelHandlerContext, Throwable, boolean)} or
     * {@link #setHandshakeFailure(ChannelHandlerContext, Throwable)}.
     * @return {@code true} if this method ends on {@link SSLEngineResult.HandshakeStatus#NOT_HANDSHAKING}.
     */
private boolean wrapNonAppData(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
    ByteBuf out = null;
    ByteBufAllocator alloc = ctx.alloc();
    try {
        // See https://github.com/netty/netty/issues/5860
        while (!ctx.isRemoved()) {
            if (out == null) {
                // As this is called for the handshake we have no real idea how big the buffer needs to be.
                // That said 2048 should give us enough room to include everything like ALPN / NPN data.
                // If this is not enough we will increase the buffer in wrap(...).
                out = allocateOutNetBuf(ctx, 2048, 1);
            }
            SSLEngineResult result = wrap(alloc, engine, Unpooled.EMPTY_BUFFER, out);
            if (result.bytesProduced() > 0) {
                ctx.write(out);
                if (inUnwrap) {
                    needsFlush = true;
                }
                out = null;
            }
            switch(result.getHandshakeStatus()) {
                case FINISHED:
                    setHandshakeSuccess();
                    return false;
                case NEED_TASK:
                    runDelegatedTasks();
                    break;
                case NEED_UNWRAP:
                    if (!inUnwrap) {
                        unwrapNonAppData(ctx);
                    }
                    break;
                case NEED_WRAP:
                    break;
                case NOT_HANDSHAKING:
                    setHandshakeSuccessIfStillHandshaking();
                    // https://github.com/netty/netty/issues/1108#issuecomment-14266970
                    if (!inUnwrap) {
                        unwrapNonAppData(ctx);
                    }
                    return true;
                default:
                    throw new IllegalStateException("Unknown handshake status: " + result.getHandshakeStatus());
            }
            if (result.bytesProduced() == 0) {
                break;
            }
            // Fix for Android, where it was encrypting empty buffers even when not handshaking
            if (result.bytesConsumed() == 0 && result.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
                break;
            }
        }
    } finally {
        if (out != null) {
            out.release();
        }
    }
    return false;
}
Also used : ByteBufAllocator(io.netty.buffer.ByteBufAllocator) SSLEngineResult(javax.net.ssl.SSLEngineResult) CompositeByteBuf(io.netty.buffer.CompositeByteBuf) ByteBuf(io.netty.buffer.ByteBuf)

Aggregations

SSLEngineResult (javax.net.ssl.SSLEngineResult)131 ByteBuffer (java.nio.ByteBuffer)53 IOException (java.io.IOException)31 SSLException (javax.net.ssl.SSLException)29 SSLEngine (javax.net.ssl.SSLEngine)23 Test (org.junit.Test)13 ReadOnlyBufferException (java.nio.ReadOnlyBufferException)12 SelfSignedCertificate (io.netty.handler.ssl.util.SelfSignedCertificate)10 EOFException (java.io.EOFException)7 HandshakeStatus (javax.net.ssl.SSLEngineResult.HandshakeStatus)7 ByteBuf (io.netty.buffer.ByteBuf)6 SSLSession (javax.net.ssl.SSLSession)6 WritePendingException (java.nio.channels.WritePendingException)5 KeyManagementException (java.security.KeyManagementException)5 NoSuchAlgorithmException (java.security.NoSuchAlgorithmException)5 ExecutionException (java.util.concurrent.ExecutionException)5 TimeoutException (java.util.concurrent.TimeoutException)5 CompositeByteBuf (io.netty.buffer.CompositeByteBuf)4 Status (javax.net.ssl.SSLEngineResult.Status)4 BufferUnderflowException (java.nio.BufferUnderflowException)3