All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.bolt.protocol.common.handler.AuthenticationProtocolLimiterHandler Maven / Gradle / Ivy

/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.bolt.protocol.common.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Stack;
import org.neo4j.bolt.protocol.error.ClientRequestComplexityExceeded;
import org.neo4j.memory.HeapEstimator;
import org.neo4j.packstream.error.reader.PackstreamReaderException;
import org.neo4j.packstream.io.PackstreamBuf;
import org.neo4j.packstream.io.Type;

public class AuthenticationProtocolLimiterHandler extends SimpleChannelInboundHandler {

    public static final long SHALLOW_SIZE =
            HeapEstimator.shallowSizeOfInstance(AuthenticationProtocolLimiterHandler.class);

    // TODO: Internal configuration parameters
    private final int maxElements;
    private final int maxMessageDepth;

    private final Stack levels = new Stack<>();

    public AuthenticationProtocolLimiterHandler(int maxElements, int maxMessageDepth) {
        this.maxElements = maxElements;
        this.maxMessageDepth = maxMessageDepth;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        if (!msg.isReadable()) {
            ctx.fireChannelRead(msg.retain());
            return;
        }

        msg.markReaderIndex();
        var buffer = PackstreamBuf.wrap(msg);

        var rootEncountered = false;
        while (msg.isReadable()) {
            var type = buffer.peekType();

            if (this.levels.isEmpty()) {
                if (rootEncountered) {
                    throw new PackstreamReaderException("Encountered illegal secondary root element within message");
                }

                rootEncountered = true;

                if (type != Type.STRUCT) {
                    throw new PackstreamReaderException("Encountered illegal root element: Expected struct");
                }
            }

            switch (type) {
                case LIST, MAP -> this.pushLevel(type, buffer.readLengthPrefixMarker(type));
                case STRUCT -> this.pushLevel(type, buffer.readStructHeader().length());
                default -> {
                    buffer.skip(type);

                    if (this.flipMapKey(type)) {
                        continue;
                    }

                    this.popLevel();
                }
            }
        }

        msg.resetReaderIndex();
        ctx.fireChannelRead(msg.retain());
    }

    private boolean flipMapKey(Type type) throws PackstreamReaderException {
        if (this.levels.isEmpty()) {
            return false;
        }

        // maps contain two values per element (a string key and a value) thus requiring
        // us to keep track of when to pop
        var currentLevel = this.levels.peek();
        if (currentLevel.type == Type.MAP) {
            currentLevel.expectingKey = !currentLevel.expectingKey;

            if (!currentLevel.expectingKey) {
                if (type != Type.STRING) {
                    throw new PackstreamReaderException("Encountered illegal map element: Expected string key");
                }

                return true;
            }
        }

        return false;
    }

    private void pushLevel(Type type, long remainingElements) throws PackstreamReaderException {
        if (this.levels.size() + 1 > maxMessageDepth) {
            throw new ClientRequestComplexityExceeded(
                    "Message has exceeded maximum permitted complexity of " + maxMessageDepth + " levels");
        }
        if (remainingElements > maxElements) {
            throw new ClientRequestComplexityExceeded(
                    "Message has exceeded maximum permitted complexity of " + maxElements + " elements");
        }

        this.flipMapKey(type);

        if (remainingElements == 0) {
            this.popLevel();
            return;
        }

        this.levels.push(new DecoderLevel(type, remainingElements));
    }

    private void popLevel() {
        while (!this.levels.isEmpty()) {
            var currentLevel = this.levels.peek();
            if (--currentLevel.remainingElements > 0) {
                return;
            }

            this.levels.pop();
        }
    }

    private static final class DecoderLevel {

        private final Type type;
        private long remainingElements;
        private boolean expectingKey = true;

        public DecoderLevel(Type type, long remainingElements) {
            this.type = type;
            this.remainingElements = remainingElements;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy