From 9505210fd01e14bef3c0822efdeddb6f14ae36f6 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Fri, 17 Jan 2025 19:44:49 +0000 Subject: [PATCH] Add support for Bolt Protocol Handshake Manifest v1 This update brings support for a new Bolt handshake and also removes support for Bolt protocol versions 4 and 4.1. --- .../BoltProtocolMinorVersionRange.java | 29 ++++ .../async/connection/BoltProtocolUtil.java | 38 ++++- .../async/connection/HandshakeHandler.java | 50 ++++-- .../async/connection/ManifestHandler.java | 26 ++++ .../async/connection/ManifestHandlerV1.java | 142 ++++++++++++++++++ .../impl/async/connection/VarLongBuilder.java | 35 +++++ .../impl/messaging/BoltProtocol.java | 52 +------ .../connection/BoltProtocolUtilTest.java | 5 +- .../connection/HandshakeHandlerTest.java | 34 ++++- .../connection/ManifestHandlerV1Test.java | 97 ++++++++++++ .../async/connection/VarLongBuilderTest.java | 66 ++++++++ .../impl/messaging/BoltProtocolTest.java | 4 - .../impl/cluster/RoutingTableHandler.java | 2 +- .../impl/cluster/RoutingTableHandlerImpl.java | 2 +- .../cluster/RoutingTableRegistryImpl.java | 2 +- .../messages/requests/GetFeatures.java | 4 +- 16 files changed, 513 insertions(+), 75 deletions(-) create mode 100644 bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolMinorVersionRange.java create mode 100644 bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandler.java create mode 100644 bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1.java create mode 100644 bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilder.java create mode 100644 bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1Test.java create mode 100644 bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilderTest.java diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolMinorVersionRange.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolMinorVersionRange.java new file mode 100644 index 000000000..7037028ef --- /dev/null +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolMinorVersionRange.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection; + +import org.neo4j.driver.internal.bolt.api.BoltProtocolVersion; + +record BoltProtocolMinorVersionRange(int majorVersion, int minorVersion, int minorVersionNum) { + public boolean contains(BoltProtocolVersion version) { + if (majorVersion != version.getMajorVersion()) { + return false; + } + + return version.getMinorVersion() <= minorVersion && version.getMinorVersion() >= minorVersion - minorVersionNum; + } +} diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java index 6ad5d02ec..08d973032 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtil.java @@ -21,12 +21,25 @@ import static java.lang.Integer.toHexString; import io.netty.buffer.ByteBuf; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; import org.neo4j.driver.internal.bolt.api.BoltProtocolVersion; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.BoltProtocol; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v42.BoltProtocolV42; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v51.BoltProtocolV51; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v52.BoltProtocolV52; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v53.BoltProtocolV53; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v54.BoltProtocolV54; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v55.BoltProtocolV55; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v56.BoltProtocolV56; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; public final class BoltProtocolUtil { @@ -37,16 +50,37 @@ public final class BoltProtocolUtil { public static final int DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES = Short.MAX_VALUE / 2; + public static final SortedMap versionToProtocol; + private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer(copyInt( BOLT_MAGIC_PREAMBLE, + 0x000001FF, BoltProtocolV58.VERSION.toIntRange(BoltProtocolV5.VERSION), BoltProtocolV44.VERSION.toIntRange(BoltProtocolV42.VERSION), - BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt())) .asReadOnly(); private static final String HANDSHAKE_STRING = createHandshakeString(); + static { + var map = new TreeMap(Comparator.reverseOrder()); + map.putAll(Map.ofEntries( + Map.entry(BoltProtocolV58.VERSION, BoltProtocolV58.INSTANCE), + Map.entry(BoltProtocolV57.VERSION, BoltProtocolV57.INSTANCE), + Map.entry(BoltProtocolV56.VERSION, BoltProtocolV56.INSTANCE), + Map.entry(BoltProtocolV55.VERSION, BoltProtocolV55.INSTANCE), + Map.entry(BoltProtocolV54.VERSION, BoltProtocolV54.INSTANCE), + Map.entry(BoltProtocolV53.VERSION, BoltProtocolV53.INSTANCE), + Map.entry(BoltProtocolV52.VERSION, BoltProtocolV52.INSTANCE), + Map.entry(BoltProtocolV51.VERSION, BoltProtocolV51.INSTANCE), + Map.entry(BoltProtocolV5.VERSION, BoltProtocolV5.INSTANCE), + Map.entry(BoltProtocolV44.VERSION, BoltProtocolV44.INSTANCE), + Map.entry(BoltProtocolV43.VERSION, BoltProtocolV43.INSTANCE), + Map.entry(BoltProtocolV42.VERSION, BoltProtocolV42.INSTANCE), + Map.entry(BoltProtocolV3.VERSION, BoltProtocolV3.INSTANCE))); + versionToProtocol = Collections.unmodifiableSortedMap(map); + } + private BoltProtocolUtil() {} public static ByteBuf handshakeBuf() { diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandler.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandler.java index 77c5c2814..a2fa5d421 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandler.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandler.java @@ -46,6 +46,7 @@ public class HandshakeHandler extends ReplayingDecoder { private boolean failed; private ChannelActivityLogger log; private ChannelErrorLogger errorLog; + private ManifestHandler manifestHandler; public HandshakeHandler( ChannelPipelineBuilder pipelineBuilder, @@ -100,18 +101,47 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable error) { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - var serverSuggestedVersion = BoltProtocolVersion.fromRawBytes(in.readInt()); - log.log(System.Logger.Level.DEBUG, "S: [Bolt Handshake] %s", serverSuggestedVersion); - - // this is a one-time handler, remove it when protocol version has been read - ctx.pipeline().remove(this); - - var protocol = protocolForVersion(serverSuggestedVersion); - if (protocol != null) { - protocolSelected(serverSuggestedVersion, protocol.createMessageFormat(), ctx); + if (manifestHandler != null) { + try { + manifestHandler.decode(in); + } catch (Throwable e) { + fail(ctx, e); + } } else { - handleUnknownSuggestedProtocolVersion(serverSuggestedVersion, ctx); + var serverSuggestedVersion = BoltProtocolVersion.fromRawBytes(in.readInt()); + + if (new BoltProtocolVersion(255, 1).equals(serverSuggestedVersion)) { + log.log(System.Logger.Level.DEBUG, "S: [Bolt Handshake Manifest] v1", serverSuggestedVersion); + manifestHandler = new ManifestHandlerV1(ctx.channel(), logging); + } else { + log.log(System.Logger.Level.DEBUG, "S: [Bolt Handshake] %s", serverSuggestedVersion); + + // this is a one-time handler, remove it when protocol version has been read + ctx.pipeline().remove(this); + + var protocol = protocolForVersion(serverSuggestedVersion); + if (protocol != null) { + protocolSelected(serverSuggestedVersion, protocol.createMessageFormat(), ctx); + } else { + handleUnknownSuggestedProtocolVersion(serverSuggestedVersion, ctx); + } + } + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (manifestHandler != null) { + // this is a one-time handler, remove it when protocol version has been read + ctx.pipeline().remove(this); + try { + var protocol = manifestHandler.complete(); + protocolSelected(protocol.version(), protocol.createMessageFormat(), ctx); + } catch (Throwable e) { + fail(ctx, e); + } } + super.channelReadComplete(ctx); } private BoltProtocol protocolForVersion(BoltProtocolVersion version) { diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandler.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandler.java new file mode 100644 index 000000000..6a69baf2c --- /dev/null +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandler.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection; + +import io.netty.buffer.ByteBuf; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.BoltProtocol; + +interface ManifestHandler { + void decode(ByteBuf in); + + BoltProtocol complete(); +} diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1.java new file mode 100644 index 000000000..9fa2d0bc4 --- /dev/null +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection; + +import static io.netty.buffer.Unpooled.unreleasableBuffer; +import static java.lang.Integer.toHexString; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import org.neo4j.driver.internal.bolt.api.LoggingProvider; +import org.neo4j.driver.internal.bolt.api.exception.BoltClientException; +import org.neo4j.driver.internal.bolt.basicimpl.impl.logging.ChannelActivityLogger; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.BoltProtocol; + +final class ManifestHandlerV1 implements ManifestHandler { + private final ChannelActivityLogger log; + private final Channel channel; + private final VarLongBuilder expectedVersionRangesBuilder = new VarLongBuilder(); + + private long expectedVersionRanges = -1L; + private Set serverSupportedVersionRanges; + + public ManifestHandlerV1(Channel channel, LoggingProvider logging) { + this.channel = Objects.requireNonNull(channel); + log = new ChannelActivityLogger(channel, logging, getClass()); + } + + @Override + public void decode(ByteBuf byteBuf) { + if (expectedVersionRanges < 0) { + decodeExpectedVersionsSegment(byteBuf); + } else if (expectedVersionRanges > 0) { + decodeServerSupportedBoltVersionRange(byteBuf); + } else { + byteBuf.readByte(); + } + } + + @Override + public BoltProtocol complete() { + return findSupportedBoltProtocol(); + } + + private void decodeExpectedVersionsSegment(ByteBuf byteBuf) { + var segment = byteBuf.readByte(); + var value = (byte) (0b01111111 & segment); + + try { + expectedVersionRangesBuilder.add(value); + } catch (IllegalStateException e) { + throw new BoltClientException( + "The driver does not support the number of Bolt Protocol version ranges that the server wants to send", + e); + } + + var finished = (segment >> 7) == 0; + if (finished) { + expectedVersionRanges = expectedVersionRangesBuilder.build(); + var size = (int) expectedVersionRanges; + if (expectedVersionRanges != size) { + throw new BoltClientException( + "The driver does not support the number of Bolt Protocol version ranges that the server wants to send"); + } else { + log.log( + System.Logger.Level.DEBUG, + "S: [Bolt Handshake Manifest] [expected version ranges %d]", + expectedVersionRanges); + serverSupportedVersionRanges = new HashSet<>(size); + } + } + } + + private void decodeServerSupportedBoltVersionRange(ByteBuf byteBuf) { + var value = byteBuf.readInt(); + var major = value & 0x000000FF; + var minor = (value >> 8) & 0x000000FF; + var minorNum = (value >> 16) & 0x000000FF; + serverSupportedVersionRanges.add(new BoltProtocolMinorVersionRange(major, minor, minorNum)); + expectedVersionRanges--; + + if (expectedVersionRanges == 0) { + log.log( + System.Logger.Level.DEBUG, + "S: [Bolt Handshake Manifest] [server supported version ranges %s]", + serverSupportedVersionRanges); + } + } + + private BoltProtocol findSupportedBoltProtocol() { + for (var entry : BoltProtocolUtil.versionToProtocol.entrySet()) { + var version = entry.getKey(); + for (var range : serverSupportedVersionRanges) { + if (range.contains(version)) { + var protocol = entry.getValue(); + write(protocol.version().toInt()); + write((byte) 0); + return protocol; + } + } + } + write(0); + write((byte) 0); + channel.flush(); + throw new BoltClientException("No supported Bolt Protocol version was found"); + } + + private void write(int value) { + log.log( + System.Logger.Level.DEBUG, + "C: [Bolt Handshake Manifest] %s", + String.format("[%s]", toHexString(value))); + channel.write(Unpooled.copyInt(value).asReadOnly()); + } + + @SuppressWarnings("SameParameterValue") + private void write(byte value) { + log.log( + System.Logger.Level.DEBUG, + "C: [Bolt Handshake Manifest] %s", + String.format("[%s]", toHexString(value))); + channel.write( + unreleasableBuffer(Unpooled.copiedBuffer(new byte[] {value})).asReadOnly()); + } +} diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilder.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilder.java new file mode 100644 index 000000000..1c7061c09 --- /dev/null +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection; + +final class VarLongBuilder { + private long value; + private byte position; + + public void add(long segment) { + if (position > 8) { + throw new IllegalStateException("Segment overflow"); + } + segment = segment << (position * 7); + value |= segment; + position++; + } + + public long build() { + return value; + } +} diff --git a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java index 8da0890ee..19bb66cd0 100644 --- a/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java +++ b/bolt-api-netty/src/main/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocol.java @@ -40,21 +40,7 @@ import org.neo4j.driver.internal.bolt.api.summary.RunSummary; import org.neo4j.driver.internal.bolt.api.values.Value; import org.neo4j.driver.internal.bolt.api.values.ValueFactory; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v4.BoltProtocolV4; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v41.BoltProtocolV41; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v42.BoltProtocolV42; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v43.BoltProtocolV43; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v5.BoltProtocolV5; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v51.BoltProtocolV51; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v52.BoltProtocolV52; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v53.BoltProtocolV53; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v54.BoltProtocolV54; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v55.BoltProtocolV55; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v56.BoltProtocolV56; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; +import org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection.BoltProtocolUtil; import org.neo4j.driver.internal.bolt.basicimpl.impl.spi.Connection; public interface BoltProtocol { @@ -156,37 +142,11 @@ static BoltProtocol forChannel(Channel channel) { } static BoltProtocol forVersion(BoltProtocolVersion version) { - if (BoltProtocolV3.VERSION.equals(version)) { - return BoltProtocolV3.INSTANCE; - } else if (BoltProtocolV4.VERSION.equals(version)) { - return BoltProtocolV4.INSTANCE; - } else if (BoltProtocolV41.VERSION.equals(version)) { - return BoltProtocolV41.INSTANCE; - } else if (BoltProtocolV42.VERSION.equals(version)) { - return BoltProtocolV42.INSTANCE; - } else if (BoltProtocolV43.VERSION.equals(version)) { - return BoltProtocolV43.INSTANCE; - } else if (BoltProtocolV44.VERSION.equals(version)) { - return BoltProtocolV44.INSTANCE; - } else if (BoltProtocolV5.VERSION.equals(version)) { - return BoltProtocolV5.INSTANCE; - } else if (BoltProtocolV51.VERSION.equals(version)) { - return BoltProtocolV51.INSTANCE; - } else if (BoltProtocolV52.VERSION.equals(version)) { - return BoltProtocolV52.INSTANCE; - } else if (BoltProtocolV53.VERSION.equals(version)) { - return BoltProtocolV53.INSTANCE; - } else if (BoltProtocolV54.VERSION.equals(version)) { - return BoltProtocolV54.INSTANCE; - } else if (BoltProtocolV55.VERSION.equals(version)) { - return BoltProtocolV55.INSTANCE; - } else if (BoltProtocolV56.VERSION.equals(version)) { - return BoltProtocolV56.INSTANCE; - } else if (BoltProtocolV57.VERSION.equals(version)) { - return BoltProtocolV57.INSTANCE; - } else if (BoltProtocolV58.VERSION.equals(version)) { - return BoltProtocolV58.INSTANCE; + var protocol = BoltProtocolUtil.versionToProtocol.get(version); + if (protocol != null) { + return protocol; + } else { + throw new BoltClientException("Unknown protocol version: " + version); } - throw new BoltClientException("Unknown protocol version: " + version); } } diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java index 533faf618..f1eeec65d 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/BoltProtocolUtilTest.java @@ -28,7 +28,6 @@ import io.netty.buffer.Unpooled; import org.junit.jupiter.api.Test; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; @@ -38,15 +37,15 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), BOLT_MAGIC_PREAMBLE, + 0x000001FF, (8 << 16) | BoltProtocolV58.VERSION.toInt(), (2 << 16) | BoltProtocolV44.VERSION.toInt(), - BoltProtocolV41.VERSION.toInt(), BoltProtocolV3.VERSION.toInt()); } @Test void shouldReturnHandshakeString() { - assertEquals("[0x6060b017, 526341, 132100, 260, 3]", handshakeString()); + assertEquals("[0x6060b017, 511, 526341, 132100, 3]", handshakeString()); } @Test diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandlerTest.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandlerTest.java index c039937c1..dac229283 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandlerTest.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/HandshakeHandlerTest.java @@ -57,10 +57,25 @@ import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.MessageFormat; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.MessageFormatV3; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v4.MessageFormatV4; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v42.BoltProtocolV42; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v43.BoltProtocolV43; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v43.MessageFormatV43; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.MessageFormatV44; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v5.BoltProtocolV5; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v5.MessageFormatV5; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v51.BoltProtocolV51; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v51.MessageFormatV51; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v52.BoltProtocolV52; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v53.BoltProtocolV53; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v54.BoltProtocolV54; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v54.MessageFormatV54; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v55.BoltProtocolV55; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v56.BoltProtocolV56; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.BoltProtocolV57; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v57.MessageFormatV57; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v58.BoltProtocolV58; class HandshakeHandlerTest { private final EmbeddedChannel channel = new EmbeddedChannel(); @@ -265,9 +280,18 @@ private void testFailure(BoltProtocolVersion serverSuggestedVersion, String expe private static Stream protocolVersions() { return Stream.of( arguments(BoltProtocolV3.VERSION, MessageFormatV3.class), - arguments(BoltProtocolV4.VERSION, MessageFormatV4.class), - arguments(BoltProtocolV41.VERSION, MessageFormatV4.class), - arguments(BoltProtocolV42.VERSION, MessageFormatV4.class)); + arguments(BoltProtocolV42.VERSION, MessageFormatV4.class), + arguments(BoltProtocolV43.VERSION, MessageFormatV43.class), + arguments(BoltProtocolV44.VERSION, MessageFormatV44.class), + arguments(BoltProtocolV5.VERSION, MessageFormatV5.class), + arguments(BoltProtocolV51.VERSION, MessageFormatV51.class), + arguments(BoltProtocolV52.VERSION, MessageFormatV51.class), + arguments(BoltProtocolV53.VERSION, MessageFormatV51.class), + arguments(BoltProtocolV54.VERSION, MessageFormatV54.class), + arguments(BoltProtocolV55.VERSION, MessageFormatV54.class), + arguments(BoltProtocolV56.VERSION, MessageFormatV54.class), + arguments(BoltProtocolV57.VERSION, MessageFormatV57.class), + arguments(BoltProtocolV58.VERSION, MessageFormatV57.class)); } private static HandshakeHandler newHandler(CompletableFuture handshakeCompletedFuture) { diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1Test.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1Test.java new file mode 100644 index 000000000..e65a370f7 --- /dev/null +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/ManifestHandlerV1Test.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.MockitoAnnotations.openMocks; + +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.neo4j.driver.internal.bolt.api.LoggingProvider; +import org.neo4j.driver.internal.bolt.api.exception.BoltClientException; +import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.BoltProtocol; + +class ManifestHandlerV1Test { + ManifestHandlerV1 handler; + + EmbeddedChannel embeddedChannel = new EmbeddedChannel(); + + @Mock + LoggingProvider loggingProvider; + + System.Logger logger; + + @BeforeEach + void beforeEach() { + openMocks(this); + given(loggingProvider.getLog(any(Class.class))).willReturn(mock(System.Logger.class)); + handler = new ManifestHandlerV1(embeddedChannel, loggingProvider); + } + + @Test + void shouldThrowOnRangeOverflow() { + for (var val : new byte[] {-1, -1, -1, -1, -1, -1, -1, -1, -1}) { + handler.decode(Unpooled.copiedBuffer(new byte[] {val})); + } + + assertThrows(BoltClientException.class, () -> handler.decode(Unpooled.copiedBuffer(new byte[] {-1}))); + } + + @Test + void shouldThrowOnRangeLargerThanMaxInteger() { + for (var val : new byte[] {-1, -1, -1, -1}) { + handler.decode(Unpooled.copiedBuffer(new byte[] {val})); + } + + assertThrows(BoltClientException.class, () -> handler.decode(Unpooled.copiedBuffer(new byte[] {0b00001111}))); + } + + @ParameterizedTest + @MethodSource("shouldSelectProtocolArgs") + void shouldSelectProtocol(BoltProtocol protocol) { + handler.decode(Unpooled.copiedBuffer(new byte[] {1})); + handler.decode(Unpooled.copyInt(protocol.version().toInt())); + assertEquals(protocol, handler.complete()); + } + + static Stream shouldSelectProtocolArgs() { + return BoltProtocolUtil.versionToProtocol.values().stream().map(Arguments::of); + } + + @Test + void shouldThrowOnUnsupportedVersion() { + handler.decode(Unpooled.copiedBuffer(new byte[] {1})); + handler.decode(Unpooled.copyInt(0x00000104)); + + assertThrows(BoltClientException.class, () -> handler.complete()); + var outboundMessages = embeddedChannel.outboundMessages(); + assertEquals(2, outboundMessages.size()); + assertEquals(Unpooled.copyInt(0), outboundMessages.poll()); + assertEquals(Unpooled.copiedBuffer(new byte[] {0}), outboundMessages.poll()); + } +} diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilderTest.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilderTest.java new file mode 100644 index 000000000..7779c9327 --- /dev/null +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/async/connection/VarLongBuilderTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [https://neo4j.com] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.bolt.basicimpl.impl.async.connection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class VarLongBuilderTest { + VarLongBuilder builder; + + @BeforeEach + void beforeEach() { + builder = new VarLongBuilder(); + } + + @ParameterizedTest + @MethodSource("shouldBuildArgs") + void shouldBuild(byte[] segments, long value) { + for (var segment : segments) { + builder.add(segment); + } + + assertEquals(value, builder.build()); + } + + @Test + void shouldThrowOnOverflow() { + var segments = new byte[] {127, 127, 127, 127, 127, 127, 127, 127, 127}; + for (var segment : segments) { + builder.add(segment); + } + + assertThrows(IllegalStateException.class, () -> builder.add(0)); + } + + private static Stream shouldBuildArgs() { + return Stream.of( + arguments(new byte[] {0}, 0L), + arguments(new byte[] {127}, 127L), + arguments(new byte[] {(byte) 0b00010110, 1}, 150L), + arguments(new byte[] {127, 127, 127, 127, (byte) 0b00000111}, Integer.MAX_VALUE), + arguments(new byte[] {127, 127, 127, 127, 127, 127, 127, 127, 127}, Long.MAX_VALUE)); + } +} diff --git a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocolTest.java b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocolTest.java index 79509ea1c..a3d0204f3 100644 --- a/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocolTest.java +++ b/bolt-api-netty/src/test/java/org/neo4j/driver/internal/bolt/basicimpl/impl/messaging/BoltProtocolTest.java @@ -26,8 +26,6 @@ import org.neo4j.driver.internal.bolt.api.BoltProtocolVersion; import org.neo4j.driver.internal.bolt.api.exception.BoltClientException; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v3.BoltProtocolV3; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v4.BoltProtocolV4; -import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v43.BoltProtocolV43; import org.neo4j.driver.internal.bolt.basicimpl.impl.messaging.v44.BoltProtocolV44; @@ -45,8 +43,6 @@ class BoltProtocolTest { void shouldCreateProtocolForKnownVersions() { assertAll( () -> assertInstanceOf(BoltProtocolV3.class, BoltProtocol.forVersion(BoltProtocolV3.VERSION)), - () -> assertInstanceOf(BoltProtocolV4.class, BoltProtocol.forVersion(BoltProtocolV4.VERSION)), - () -> assertInstanceOf(BoltProtocolV41.class, BoltProtocol.forVersion(BoltProtocolV41.VERSION)), () -> assertInstanceOf(BoltProtocolV42.class, BoltProtocol.forVersion(BoltProtocolV42.VERSION)), () -> assertInstanceOf(BoltProtocolV43.class, BoltProtocol.forVersion(BoltProtocolV43.VERSION)), () -> assertInstanceOf(BoltProtocolV44.class, BoltProtocol.forVersion(BoltProtocolV44.VERSION)), diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java index de3832b7b..dfa58460e 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandler.java @@ -44,5 +44,5 @@ CompletionStage ensureRoutingTable( RoutingTable routingTable(); - boolean staleRoutingTable(AccessMode mode); + boolean isStaleFor(AccessMode mode); } diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java index 487cffae9..34d37f065 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableHandlerImpl.java @@ -210,7 +210,7 @@ public RoutingTable routingTable() { } @Override - public synchronized boolean staleRoutingTable(AccessMode mode) { + public synchronized boolean isStaleFor(AccessMode mode) { if (refreshRoutingTableFuture != null) { return true; } diff --git a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java index 7bafffdfa..f65c4c30e 100644 --- a/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java +++ b/bolt-api-routed/src/main/java/org/neo4j/driver/internal/bolt/routedimpl/impl/cluster/RoutingTableRegistryImpl.java @@ -108,7 +108,7 @@ public CompletionStage ensureRoutingTable( if (!databaseNameFuture.isDone()) { if (homeDatabaseHint != null) { var handler = routingTableHandlers.get(DatabaseNameUtil.database(homeDatabaseHint)); - if (handler != null && !handler.staleRoutingTable(mode)) { + if (handler != null && !handler.isStaleFor(mode)) { return CompletableFuture.completedFuture(handler); } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java index ed66161e6..5dff7f537 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/GetFeatures.java @@ -32,7 +32,6 @@ @Getter public class GetFeatures implements TestkitRequest { private static final Set COMMON_FEATURES = new HashSet<>(Arrays.asList( - "Feature:Bolt:4.1", "Feature:Bolt:4.2", "Feature:Bolt:4.3", "Feature:Bolt:4.4", @@ -76,7 +75,8 @@ public class GetFeatures implements TestkitRequest { "Feature:API:SSLClientCertificate", "Feature:API:Summary:GqlStatusObjects", "Feature:API:Driver:MaxConnectionLifetime", - "Optimization:HomeDatabaseCache")); + "Optimization:HomeDatabaseCache", + "Feature:Bolt:HandshakeManifestV1")); private static final Set SYNC_FEATURES = new HashSet<>(Arrays.asList( "Feature:Bolt:3.0",