Example usage for io.netty.buffer ByteBuf retain

List of usage examples for io.netty.buffer ByteBuf retain

Introduction

In this page you can find the example usage for io.netty.buffer ByteBuf retain.

Prototype

@Override
    public abstract ByteBuf retain();

Source Link

Usage

From source file:org.beaconmc.network.socket.pipeline.PacketCompression.java

License:Open Source License

@Override
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> objects)
        throws Exception {

    ByteBuf prefixByteBuf = channelHandlerContext.alloc().buffer(5);
    PacketSerializer prefixPacketSerializer = new PacketSerializer(prefixByteBuf);
    ByteBuf contentByteBuf;/*from   w ww.  ja  v  a2s  .  com*/

    if (byteBuf.readableBytes() >= threshold) {

        int index = byteBuf.readerIndex();
        int length = byteBuf.readableBytes();

        byte[] source = new byte[length];
        byteBuf.readBytes(source);
        this.deflater.setInput(source);
        this.deflater.finish();

        byte[] compressed = new byte[length];
        int compressedLength = this.deflater.deflate(compressed);
        this.deflater.reset();

        if (compressedLength == 0) {
            throw new IllegalStateException("Failed to compress packet");
        } else if (compressedLength >= length) {
            prefixPacketSerializer.writeVarInt(0);
            byteBuf.readerIndex(index);
            byteBuf.retain();
            contentByteBuf = byteBuf;
        } else {
            prefixPacketSerializer.writeVarInt(length);
            contentByteBuf = Unpooled.wrappedBuffer(compressed, 0, compressedLength);
        }
    } else {
        prefixPacketSerializer.writeVarInt(0);
        byteBuf.retain();
        contentByteBuf = byteBuf;
    }
    objects.add(Unpooled.wrappedBuffer(prefixPacketSerializer.getByteBuf(), contentByteBuf));
}

From source file:org.beaconmc.network.socket.pipeline.PacketCompression.java

License:Open Source License

@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> objects)
        throws Exception {

    int index = byteBuf.readerIndex();

    PacketSerializer inPacketSerializer = new PacketSerializer(byteBuf);

    int uncompressedSize = inPacketSerializer.readVarInt();

    if (uncompressedSize == 0) {
        int length = byteBuf.readableBytes();
        if (length >= threshold) {
            throw new IllegalArgumentException("Received uncompressed packet");
        }/* w w w  .  ja va2 s . c  om*/

        ByteBuf outByteBuf = channelHandlerContext.alloc().buffer(length);
        byteBuf.readBytes(outByteBuf, length);
        objects.add(outByteBuf);
    } else {
        byte[] source = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(source);
        this.inflater.setInput(source);

        byte[] output = new byte[uncompressedSize];
        int resultLength = this.inflater.inflate(output);
        this.inflater.reset();

        if (resultLength == 0) {
            byteBuf.readerIndex(index);
            byteBuf.retain();
            objects.add(byteBuf);
        } else if (resultLength != uncompressedSize) {
            throw new IllegalStateException("Received compressed packet with invalid length");
        } else {
            objects.add(Unpooled.wrappedBuffer(output));
        }

    }

}

From source file:org.beaconmc.network.socket.pipeline.PacketLegacy.java

License:Open Source License

@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {

    byteBuf.markReaderIndex();/*from   w  w w  .  j a v a 2  s . c o m*/
    boolean repliedPing = true;

    try {
        short packetId = byteBuf.readUnsignedByte();
        if (packetId == 254) {
            int length = byteBuf.readableBytes();
            AsyncServerPingEvent asyncServerPingEvent = EventFactory
                    .callAsyncServerPingEvent(this.server.createServerPing());
            if (asyncServerPingEvent.getPing() != null) {
                switch (length) {
                case 0:
                    this.sendPingAndClose(channelHandlerContext,
                            this.toArray(String.format("%s%d%d",
                                    asyncServerPingEvent.getPing().getDescription().getText(),
                                    asyncServerPingEvent.getPing().getPlayers().getOnline(),
                                    asyncServerPingEvent.getPing().getPlayers().getMax())));
                    break;
                case 1:
                    if (byteBuf.readUnsignedByte() != 1) {
                        return;
                    }
                    this.sendPingAndClose(channelHandlerContext,
                            this.toArray(String.format("1\0%d\0%s\0%s\0%d\0%d", 127,
                                    asyncServerPingEvent.getPing().getVersion().getName(),
                                    asyncServerPingEvent.getPing().getDescription().getText(),
                                    asyncServerPingEvent.getPing().getPlayers().getOnline(),
                                    asyncServerPingEvent.getPing().getPlayers().getMax())));
                    break;
                default:
                    boolean checkFlag = byteBuf.readUnsignedByte() == 1;
                    checkFlag &= byteBuf.readUnsignedByte() == 250;
                    checkFlag &= "MC|PingHost".equals(
                            new String(byteBuf.readBytes(byteBuf.readShort() * 2).array(), Charsets.UTF_16));

                    int checkShort = byteBuf.readShort();

                    checkFlag &= byteBuf.readUnsignedByte() >= 73;
                    checkFlag &= 3 + byteBuf.readBytes(byteBuf.readShort() * 2).array().length
                            + 4 == checkShort;
                    checkFlag &= byteBuf.readInt() <= '\uffff';
                    checkFlag &= byteBuf.readableBytes() == 0;

                    if (!checkFlag) {
                        return;
                    }

                    this.sendPingAndClose(channelHandlerContext,
                            this.toArray(String.format("1\0%d\0%s\0%s\0%d\0%d", 127,
                                    asyncServerPingEvent.getPing().getVersion().getName(),
                                    asyncServerPingEvent.getPing().getDescription().getText(),
                                    asyncServerPingEvent.getPing().getPlayers().getOnline(),
                                    asyncServerPingEvent.getPing().getPlayers().getMax())));
                    break;
                }
            } else {
                this.close(channelHandlerContext);
            }
            repliedPing = false;
        } else if (packetId == 0x02 && byteBuf.isReadable()) {
            this.sendPingAndClose(channelHandlerContext, this.toArray(ChatColor.RED + "Outdated Client"));
            repliedPing = false;
        }
    } catch (Exception e) {
        return;
    } finally {
        if (repliedPing) {
            byteBuf.resetReaderIndex();
            channelHandlerContext.channel().pipeline().remove("legacy_ping");
            channelHandlerContext.fireChannelRead(byteBuf.retain());
        }
    }
}

From source file:org.columbia.parikshan.duplicator.DuplicatorFrontendHandler.java

License:Apache License

@Override
public void channelRead(final ChannelHandlerContext ctx, Object buf) {
    // You need to reference count the message +1
    ByteBuf msg = (ByteBuf) buf;
    msg.retain();

    if (server2OutboundChannel.isActive()) {
        server2OutboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {
            @Override//w  w w .  j ava2  s . c  o m
            public void operationComplete(ChannelFuture future) {
                if (future.isSuccess()) {
                    // was able to flush out data, start to read the next chunk
                    ctx.channel().read();
                } else {
                    future.channel().close();
                }
            }
        });
    }

    if (server3OutboundChannel.isActive()) {
        //if(server3OutboundChannel.isWritable()) {
        //System.out.println("Writing and Flushing");
        server3OutboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                if (future.isSuccess()) {
                    // was able to flush out data, start to read the next chunk
                    //System.out.println(server3OutboundChannel.bytesBeforeUnwritable());
                    ctx.channel().read();
                } else {
                    future.channel().close();
                }
            }
        });
        //}else
        //System.out.println("Loop 1: Channel is no longer writeable");
    }

    // Optional to the above code instead channel writing automatically cares for reference counting for you
    //        channels.writeAndFlush(msg).addListeners(new ChannelFutureListener() {
    //
    //            @Override
    //            public void operationComplete(ChannelFuture future) throws Exception {
    //                if (future.isSuccess()) {
    //                    // was able to flush out data, start to read the next chunk
    //                    ctx.channel().read();
    //                } else {
    //                    future.channel().close();
    //                }
    //            }
    //        });
}

From source file:org.curioswitch.curiostack.gcloud.storage.FileWriter.java

License:Open Source License

private CompletableFuture<Void> doUploadChunk(ByteBuf chunk, boolean endOfFile) {

    int length = chunk.readableBytes();
    long limit = filePosition + length;

    StringBuilder range = new StringBuilder("bytes ");
    if (length == 0) {
        range.append('*');
    } else {// w w w . java2s  .  c om
        range.append(filePosition).append('-').append(limit - 1);
    }
    range.append('/');
    if (endOfFile) {
        range.append(limit);
    } else {
        range.append('*');
    }

    RequestHeaders headers = RequestHeaders.of(HttpMethod.PUT, uploadUrl, HttpHeaderNames.CONTENT_RANGE,
            range.toString());

    HttpData data = new ByteBufHttpData(chunk, true);
    chunk.retain();

    return httpClient.execute(headers, data).aggregate(eventLoop).thenComposeAsync(msg -> {
        ResponseHeaders responseHeaders = msg.headers();
        if (!responseHeaders.status().codeClass().equals(HttpStatusClass.SUCCESS)
                && responseHeaders.status().code() != 308) {
            chunk.release();
            throw new RuntimeException("Unsuccessful response uploading chunk: endOfFile: " + endOfFile
                    + " Request headers: " + headers + "\n" + " Response headers: " + responseHeaders + "\n"
                    + msg.content().toStringUtf8());
        }

        String responseRange = responseHeaders.get(HttpHeaderNames.RANGE);
        if (responseRange == null) {
            chunk.release();
            return completedFuture(null);
        }

        long responseLimit = rangeHeaderLimit(responseHeaders.get(HttpHeaderNames.RANGE));
        filePosition = responseLimit + 1;
        int notUploaded = (int) (limit - 1 - responseLimit);
        if (notUploaded > 0) {
            chunk.readerIndex(chunk.writerIndex() - notUploaded);

            if (endOfFile) {
                return doUploadChunk(chunk, true);
            }

            if (unfinishedChunk == null) {
                copyUnfinishedBuffer(chunk);
            } else {
                ByteBuf newUnfinished = alloc.buffer(chunk.readableBytes() + unfinishedChunk.readableBytes());
                newUnfinished.writeBytes(chunk).writeBytes(unfinishedChunk);
                unfinishedChunk.release();
                unfinishedChunk = newUnfinished;
            }
        }
        chunk.release();
        return completedFuture(null);
    }, eventLoop);
}

From source file:org.eclipse.milo.opcua.stack.client.transport.uasc.UascClientMessageHandler.java

License:Open Source License

private boolean accumulateChunk(ByteBuf buffer) throws UaException {
    int chunkSize = buffer.readerIndex(0).readableBytes();

    if (chunkSize > maxChunkSize) {
        throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                String.format("max chunk size exceeded (%s)", maxChunkSize));
    }/*from   w  w w. j ava  2 s  .c o m*/

    chunkBuffers.add(buffer.retain());

    if (maxChunkCount > 0 && chunkBuffers.size() > maxChunkCount) {
        throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                String.format("max chunk count exceeded (%s)", maxChunkCount));
    }

    char chunkType = (char) buffer.getByte(3);

    return (chunkType == 'A' || chunkType == 'F');
}

From source file:org.eclipse.milo.opcua.stack.client.transport.websocket.OpcClientWebSocketBinaryFrameCodec.java

License:Open Source License

@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {
    out.add(new BinaryWebSocketFrame(msg.retain()));
}

From source file:org.eclipse.milo.opcua.stack.server.handlers.UaTcpServerAsymmetricHandler.java

License:Open Source License

private void onOpenSecureChannel(ChannelHandlerContext ctx, ByteBuf buffer) throws UaException {
    buffer.skipBytes(3); // Skip messageType

    char chunkType = (char) buffer.readByte();

    if (chunkType == 'A') {
        chunkBuffers.forEach(ByteBuf::release);
        chunkBuffers.clear();//from  w w w .ja  v a  2  s. com
        headerRef.set(null);
    } else {
        buffer.skipBytes(4); // Skip messageSize

        long secureChannelId = buffer.readUnsignedInt();
        AsymmetricSecurityHeader securityHeader = AsymmetricSecurityHeader.decode(buffer);

        if (secureChannelId == 0) {
            // Okay, this is the first OpenSecureChannelRequest... carry on.
            String endpointUrl = ctx.channel().attr(UaTcpServerHelloHandler.ENDPOINT_URL_KEY).get();
            String securityPolicyUri = securityHeader.getSecurityPolicyUri();

            EndpointDescription endpointDescription = Arrays.stream(server.getEndpointDescriptions())
                    .filter(e -> {
                        String s1 = pathOrUrl(endpointUrl);
                        String s2 = pathOrUrl(e.getEndpointUrl());
                        boolean uriMatch = s1.equals(s2);
                        boolean policyMatch = e.getSecurityPolicyUri().equals(securityPolicyUri);
                        return uriMatch && policyMatch;
                    }).findFirst().orElse(null);

            if (endpointDescription == null && !server.getConfig().isStrictEndpointUrlsEnabled()) {
                endpointDescription = Arrays.stream(server.getEndpointDescriptions())
                        .filter(e -> e.getSecurityPolicyUri().equals(securityPolicyUri)).findFirst()
                        .orElse(null);
            }

            if (endpointDescription == null) {
                throw new UaException(StatusCodes.Bad_SecurityChecksFailed, "SecurityPolicy URI did not match");
            }

            secureChannel = server.openSecureChannel();
            secureChannel.setEndpointDescription(endpointDescription);
        } else {
            secureChannel = server.getSecureChannel(secureChannelId);

            if (secureChannel == null) {
                throw new UaException(StatusCodes.Bad_TcpSecureChannelUnknown,
                        "unknown secure channel id: " + secureChannelId);
            }

            if (!secureChannel.getRemoteCertificateBytes().equals(securityHeader.getSenderCertificate())) {
                throw new UaException(StatusCodes.Bad_SecurityChecksFailed,
                        "certificate requesting renewal did not match existing certificate.");
            }

            Channel boundChannel = secureChannel.attr(UaTcpStackServer.BoundChannelKey).get();
            if (boundChannel != null && boundChannel != ctx.channel()) {
                throw new UaException(StatusCodes.Bad_SecurityChecksFailed,
                        "received a renewal request from channel other than the bound channel.");
            }
        }

        if (!headerRef.compareAndSet(null, securityHeader)) {
            if (!securityHeader.equals(headerRef.get())) {
                throw new UaException(StatusCodes.Bad_SecurityChecksFailed,
                        "subsequent AsymmetricSecurityHeader did not match");
            }
        }

        SecurityPolicy securityPolicy = SecurityPolicy.fromUri(securityHeader.getSecurityPolicyUri());
        secureChannel.setSecurityPolicy(securityPolicy);

        if (!securityHeader.getSenderCertificate().isNull() && securityPolicy != SecurityPolicy.None) {
            secureChannel.setRemoteCertificate(securityHeader.getSenderCertificate().bytes());

            try {
                CertificateValidator certificateValidator = server.getCertificateValidator();

                certificateValidator.validate(secureChannel.getRemoteCertificate());

                certificateValidator.verifyTrustChain(secureChannel.getRemoteCertificate(),
                        secureChannel.getRemoteCertificateChain());
            } catch (UaException e) {
                try {
                    UaException cause = new UaException(e.getStatusCode(), "security checks failed");
                    ErrorMessage errorMessage = ExceptionHandler.sendErrorMessage(ctx, cause);

                    logger.debug("[remote={}] {}.", ctx.channel().remoteAddress(), errorMessage.getReason(),
                            cause);
                } catch (Exception inner) {
                    logger.error("Error sending ErrorMessage: {}", inner.getMessage(), inner);
                }
            }
        }

        if (!securityHeader.getReceiverThumbprint().isNull()) {
            CertificateManager certificateManager = server.getCertificateManager();

            Optional<X509Certificate> localCertificate = certificateManager
                    .getCertificate(securityHeader.getReceiverThumbprint());

            Optional<KeyPair> keyPair = certificateManager.getKeyPair(securityHeader.getReceiverThumbprint());

            if (localCertificate.isPresent() && keyPair.isPresent()) {
                secureChannel.setLocalCertificate(localCertificate.get());
                secureChannel.setKeyPair(keyPair.get());
            } else {
                throw new UaException(StatusCodes.Bad_SecurityChecksFailed,
                        "no certificate for provided thumbprint");
            }
        }

        int chunkSize = buffer.readerIndex(0).readableBytes();

        if (chunkSize > maxChunkSize) {
            throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                    String.format("max chunk size exceeded (%s)", maxChunkSize));
        }

        chunkBuffers.add(buffer.retain());

        if (chunkBuffers.size() > maxChunkCount) {
            throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                    String.format("max chunk count exceeded (%s)", maxChunkCount));
        }

        if (chunkType == 'F') {
            final List<ByteBuf> buffersToDecode = chunkBuffers;

            chunkBuffers = new ArrayList<>(maxChunkCount);
            headerRef.set(null);

            serializationQueue.decode((binaryDecoder, chunkDecoder) -> {
                ByteBuf messageBuffer = null;

                try {
                    messageBuffer = chunkDecoder.decodeAsymmetric(secureChannel, buffersToDecode);

                    OpenSecureChannelRequest request = binaryDecoder.setBuffer(messageBuffer)
                            .decodeMessage(null);

                    logger.debug("Received OpenSecureChannelRequest ({}, id={}).", request.getRequestType(),
                            secureChannelId);

                    long requestId = chunkDecoder.getLastRequestId();
                    installSecurityToken(ctx, request, requestId);
                } catch (UaException e) {
                    logger.error("Error decoding asymmetric message: {}", e.getMessage(), e);
                    ctx.close();
                } finally {
                    if (messageBuffer != null) {
                        messageBuffer.release();
                    }
                    buffersToDecode.clear();
                }
            });
        }
    }
}

From source file:org.eclipse.milo.opcua.stack.server.transport.uasc.UascServerAsymmetricHandler.java

License:Open Source License

private void onOpenSecureChannel(ChannelHandlerContext ctx, ByteBuf buffer) throws UaException {
    buffer.skipBytes(3); // Skip messageType

    char chunkType = (char) buffer.readByte();

    if (chunkType == 'A') {
        chunkBuffers.forEach(ByteBuf::release);
        chunkBuffers.clear();//  ww w . j a v  a  2s . c o  m
        headerRef.set(null);
    } else {
        buffer.skipBytes(4); // Skip messageSize

        final long secureChannelId = buffer.readUnsignedIntLE();

        final AsymmetricSecurityHeader header = AsymmetricSecurityHeader.decode(buffer, maxArrayLength,
                maxStringLength);

        if (!headerRef.compareAndSet(null, header)) {
            if (!header.equals(headerRef.get())) {
                throw new UaException(StatusCodes.Bad_SecurityChecksFailed,
                        "subsequent AsymmetricSecurityHeader did not match");
            }
        }

        if (secureChannelId != 0) {
            if (secureChannel == null) {
                throw new UaException(StatusCodes.Bad_TcpSecureChannelUnknown,
                        "unknown secure channel id: " + secureChannelId);
            }

            if (secureChannelId != secureChannel.getChannelId()) {
                throw new UaException(StatusCodes.Bad_TcpSecureChannelUnknown,
                        "unknown secure channel id: " + secureChannelId);
            }
        }

        if (secureChannel == null) {
            secureChannel = new ServerSecureChannel();
            secureChannel.setChannelId(stackServer.getNextChannelId());

            String securityPolicyUri = header.getSecurityPolicyUri();
            SecurityPolicy securityPolicy = SecurityPolicy.fromUri(securityPolicyUri);

            secureChannel.setSecurityPolicy(securityPolicy);

            if (securityPolicy != SecurityPolicy.None) {
                secureChannel.setRemoteCertificate(header.getSenderCertificate().bytesOrEmpty());

                CertificateValidator certificateValidator = stackServer.getConfig().getCertificateValidator();

                certificateValidator.validate(secureChannel.getRemoteCertificate());
                certificateValidator.verifyTrustChain(secureChannel.getRemoteCertificateChain());

                CertificateManager certificateManager = stackServer.getConfig().getCertificateManager();

                Optional<X509Certificate[]> localCertificateChain = certificateManager
                        .getCertificateChain(header.getReceiverThumbprint());

                Optional<KeyPair> keyPair = certificateManager.getKeyPair(header.getReceiverThumbprint());

                if (localCertificateChain.isPresent() && keyPair.isPresent()) {
                    X509Certificate[] chain = localCertificateChain.get();

                    secureChannel.setLocalCertificate(chain[0]);
                    secureChannel.setLocalCertificateChain(chain);
                    secureChannel.setKeyPair(keyPair.get());
                } else {
                    throw new UaException(StatusCodes.Bad_SecurityChecksFailed,
                            "no certificate for provided thumbprint");
                }
            }
        }

        int chunkSize = buffer.readerIndex(0).readableBytes();

        if (chunkSize > maxChunkSize) {
            throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                    String.format("max chunk size exceeded (%s)", maxChunkSize));
        }

        chunkBuffers.add(buffer.retain());

        if (maxChunkCount > 0 && chunkBuffers.size() > maxChunkCount) {
            throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                    String.format("max chunk count exceeded (%s)", maxChunkCount));
        }

        if (chunkType == 'F') {
            final List<ByteBuf> buffersToDecode = chunkBuffers;

            chunkBuffers = new ArrayList<>();
            headerRef.set(null);

            serializationQueue.decode((binaryDecoder, chunkDecoder) -> chunkDecoder
                    .decodeAsymmetric(secureChannel, buffersToDecode, new ChunkDecoder.Callback() {
                        @Override
                        public void onDecodingError(UaException ex) {
                            logger.error("Error decoding asymmetric message: {}", ex.getMessage(), ex);

                            ctx.close();
                        }

                        @Override
                        public void onMessageAborted(MessageAbortedException ex) {
                            logger.warn("Asymmetric message aborted. error={} reason={}", ex.getStatusCode(),
                                    ex.getMessage());
                        }

                        @Override
                        public void onMessageDecoded(ByteBuf message, long requestId) {
                            try {
                                OpenSecureChannelRequest request = (OpenSecureChannelRequest) binaryDecoder
                                        .setBuffer(message).readMessage(null);

                                logger.debug("Received OpenSecureChannelRequest ({}, id={}).",
                                        request.getRequestType(), secureChannelId);

                                sendOpenSecureChannelResponse(ctx, requestId, request);
                            } catch (Throwable t) {
                                logger.error("Error decoding OpenSecureChannelRequest", t);
                                ctx.close();
                            } finally {
                                message.release();
                                buffersToDecode.clear();
                            }
                        }
                    }));
        }
    }
}

From source file:org.eclipse.milo.opcua.stack.server.transport.uasc.UascServerSymmetricHandler.java

License:Open Source License

private void onSecureMessage(ChannelHandlerContext ctx, ByteBuf buffer) throws UaException {
    buffer.skipBytes(3); // Skip messageType

    char chunkType = (char) buffer.readByte();

    if (chunkType == 'A') {
        chunkBuffers.forEach(ByteBuf::release);
        chunkBuffers.clear();/*from  w  w w  .  ja  v a2s  .co m*/
    } else {
        buffer.skipBytes(4); // Skip messageSize

        long secureChannelId = buffer.readUnsignedIntLE();
        if (secureChannelId != secureChannel.getChannelId()) {
            throw new UaException(StatusCodes.Bad_SecureChannelIdInvalid,
                    "invalid secure channel id: " + secureChannelId);
        }

        int chunkSize = buffer.readerIndex(0).readableBytes();
        if (chunkSize > maxChunkSize) {
            throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                    String.format("max chunk size exceeded (%s)", maxChunkSize));
        }

        chunkBuffers.add(buffer.retain());

        if (maxChunkCount > 0 && chunkBuffers.size() > maxChunkCount) {
            throw new UaException(StatusCodes.Bad_TcpMessageTooLarge,
                    String.format("max chunk count exceeded (%s)", maxChunkCount));
        }

        if (chunkType == 'F') {
            final List<ByteBuf> buffersToDecode = chunkBuffers;
            chunkBuffers = new ArrayList<>();

            serializationQueue.decode((binaryDecoder, chunkDecoder) -> {
                try {
                    validateChunkHeaders(buffersToDecode);
                } catch (UaException e) {
                    logger.error("Error validating chunk headers: {}", e.getMessage(), e);
                    buffersToDecode.forEach(ReferenceCountUtil::safeRelease);
                    ctx.fireExceptionCaught(e);
                    return;
                }

                chunkDecoder.decodeSymmetric(secureChannel, buffersToDecode, new ChunkDecoder.Callback() {
                    @Override
                    public void onDecodingError(UaException ex) {
                        logger.error("Error decoding symmetric message: {}", ex.getMessage(), ex);

                        ctx.close();
                    }

                    @Override
                    public void onMessageAborted(MessageAbortedException ex) {
                        logger.warn("Received message abort chunk; error={}, reason={}", ex.getStatusCode(),
                                ex.getMessage());
                    }

                    @Override
                    public void onMessageDecoded(ByteBuf message, long requestId) {
                        UaRequestMessage request = (UaRequestMessage) binaryDecoder.setBuffer(message)
                                .readMessage(null);

                        stackServer.getConfig().getExecutor().execute(() -> {
                            try {
                                String endpointUrl = ctx.channel().attr(UascServerHelloHandler.ENDPOINT_URL_KEY)
                                        .get();

                                EndpointDescription endpoint = ctx.channel()
                                        .attr(UascServerAsymmetricHandler.ENDPOINT_KEY).get();

                                String path = EndpointUtil.getPath(endpointUrl);

                                InetSocketAddress remoteSocketAddress = (InetSocketAddress) ctx.channel()
                                        .remoteAddress();

                                ServiceRequest serviceRequest = new ServiceRequest(stackServer, request,
                                        endpoint, secureChannel.getChannelId(),
                                        remoteSocketAddress.getAddress(),
                                        secureChannel.getRemoteCertificateBytes());

                                serviceRequest.getFuture().whenComplete((response, fault) -> {
                                    if (response != null) {
                                        sendServiceResponse(ctx, requestId, request, response);
                                    } else {
                                        UInteger requestHandle = request.getRequestHeader().getRequestHandle();

                                        sendServiceFault(ctx, requestId, requestHandle, fault);
                                    }
                                });

                                stackServer.onServiceRequest(path, serviceRequest);
                            } catch (Throwable t) {
                                logger.error("Error decoding UaRequestMessage", t);

                                sendServiceFault(ctx, requestId, uint(0), t);
                            } finally {
                                message.release();
                                buffersToDecode.clear();
                            }
                        });
                    }
                });
            });
        }
    }
}