Skip to content

Commit 6a9bff4

Browse files
committed
Introduce bolt_agent
This update introduces support for the new `HELLO` message `bolt_agent` field that is mandatory in the Bolt protocol version 5.3. The `bolt_agent` value is non-configurable and follows a common format among the official drivers. Java driver `bolt_agent` sample: ```json {"product": "neo4j-java/5.7.0-bf6c5444e19cef8b6cb7aa7e0cecf80b7f8a19a9", "platform": "Linux; 5.15.49-linuxkit; aarch64", "language": "Java/17.0.6", "language_details": "Eclipse Adoptium; OpenJDK 64-Bit Server VM; 17.0.6+10"} ``` This update also brings support for the Bolt protocol version 5.3.
1 parent b19edb9 commit 6a9bff4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1470
-84
lines changed

driver/src/main/java/org/neo4j/driver/Config.java

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
*/
1919
package org.neo4j.driver;
2020

21-
import static java.lang.String.format;
2221
import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING;
22+
import static org.neo4j.driver.internal.util.DriverInfoUtil.driverVersion;
2323

2424
import java.io.File;
2525
import java.io.Serial;
@@ -345,7 +345,7 @@ public static final class ConfigBuilder {
345345
private long idleTimeBeforeConnectionTest = PoolSettings.DEFAULT_IDLE_TIME_BEFORE_CONNECTION_TEST;
346346
private long maxConnectionLifetimeMillis = PoolSettings.DEFAULT_MAX_CONNECTION_LIFETIME;
347347
private long connectionAcquisitionTimeoutMillis = PoolSettings.DEFAULT_CONNECTION_ACQUISITION_TIMEOUT;
348-
private String userAgent = format("neo4j-java/%s", driverVersion());
348+
private String userAgent = driverVersion();
349349
private final SecuritySettings.SecuritySettingsBuilder securitySettingsBuilder =
350350
new SecuritySettings.SecuritySettingsBuilder();
351351
private long routingTablePurgeDelayMillis = RoutingSettings.STALE_ROUTING_TABLE_PURGE_DELAY_MS;
@@ -748,23 +748,6 @@ public ConfigBuilder withNotificationConfig(NotificationConfig notificationConfi
748748
return this;
749749
}
750750

751-
/**
752-
* Extracts the driver version from the driver jar MANIFEST.MF file.
753-
*/
754-
private static String driverVersion() {
755-
// "Session" is arbitrary - the only thing that matters is that the class we use here is in the
756-
// 'org.neo4j.driver' package, because that is where the jar manifest specifies the version.
757-
// This is done as part of the build, adding a MANIFEST.MF file to the generated jarfile.
758-
Package pkg = Session.class.getPackage();
759-
if (pkg != null && pkg.getImplementationVersion() != null) {
760-
return pkg.getImplementationVersion();
761-
}
762-
763-
// If there is no version, we're not running from a jar file, but from raw compiled class files.
764-
// This should only happen during development, so call the version 'dev'.
765-
return "dev";
766-
}
767-
768751
/**
769752
* Create a config instance from this builder.
770753
*
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal;
20+
21+
public record BoltAgent(String product, String platform, String language, String languageDetails) {}

driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.neo4j.driver.internal.security.StaticAuthTokenManager;
6262
import org.neo4j.driver.internal.spi.ConnectionPool;
6363
import org.neo4j.driver.internal.spi.ConnectionProvider;
64+
import org.neo4j.driver.internal.util.DriverInfoUtil;
6465
import org.neo4j.driver.internal.util.Futures;
6566
import org.neo4j.driver.net.ServerAddressResolver;
6667

@@ -141,7 +142,8 @@ protected ConnectionPool createConnectionPool(
141142
Clock clock = createClock();
142143
ConnectionSettings settings =
143144
new ConnectionSettings(authTokenManager, config.userAgent(), config.connectionTimeoutMillis());
144-
ChannelConnector connector = createConnector(settings, securityPlan, config, clock, routingContext);
145+
var boltAgent = DriverInfoUtil.boltAgent();
146+
ChannelConnector connector = createConnector(settings, securityPlan, config, clock, routingContext, boltAgent);
145147
PoolSettings poolSettings = new PoolSettings(
146148
config.maxConnectionPoolSize(),
147149
config.connectionAcquisitionTimeoutMillis(),
@@ -179,15 +181,17 @@ protected ChannelConnector createConnector(
179181
SecurityPlan securityPlan,
180182
Config config,
181183
Clock clock,
182-
RoutingContext routingContext) {
184+
RoutingContext routingContext,
185+
BoltAgent boltAgent) {
183186
return new ChannelConnectorImpl(
184187
settings,
185188
securityPlan,
186189
config.logging(),
187190
clock,
188191
routingContext,
189192
getDomainNameResolver(),
190-
config.notificationConfig());
193+
config.notificationConfig(),
194+
boltAgent);
191195
}
192196

193197
private InternalDriver createDriver(

driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42;
3030
import org.neo4j.driver.internal.messaging.v44.BoltProtocolV44;
3131
import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5;
32-
import org.neo4j.driver.internal.messaging.v52.BoltProtocolV52;
32+
import org.neo4j.driver.internal.messaging.v53.BoltProtocolV53;
3333

3434
public final class BoltProtocolUtil {
3535
public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017;
@@ -41,7 +41,7 @@ public final class BoltProtocolUtil {
4141

4242
private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer(copyInt(
4343
BOLT_MAGIC_PREAMBLE,
44-
BoltProtocolV52.VERSION.toIntRange(BoltProtocolV5.VERSION),
44+
BoltProtocolV53.VERSION.toIntRange(BoltProtocolV5.VERSION),
4545
BoltProtocolV44.VERSION.toIntRange(BoltProtocolV42.VERSION),
4646
BoltProtocolV41.VERSION.toInt(),
4747
BoltProtocolV3.VERSION.toInt()))

driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.neo4j.driver.AuthTokenManager;
3434
import org.neo4j.driver.Logging;
3535
import org.neo4j.driver.NotificationConfig;
36+
import org.neo4j.driver.internal.BoltAgent;
3637
import org.neo4j.driver.internal.BoltServerAddress;
3738
import org.neo4j.driver.internal.ConnectionSettings;
3839
import org.neo4j.driver.internal.DomainNameResolver;
@@ -42,6 +43,7 @@
4243

4344
public class ChannelConnectorImpl implements ChannelConnector {
4445
private final String userAgent;
46+
private final BoltAgent boltAgent;
4547
private final AuthTokenManager authTokenManager;
4648
private final RoutingContext routingContext;
4749
private final SecurityPlan securityPlan;
@@ -60,7 +62,8 @@ public ChannelConnectorImpl(
6062
Clock clock,
6163
RoutingContext routingContext,
6264
DomainNameResolver domainNameResolver,
63-
NotificationConfig notificationConfig) {
65+
NotificationConfig notificationConfig,
66+
BoltAgent boltAgent) {
6467
this(
6568
connectionSettings,
6669
securityPlan,
@@ -69,7 +72,8 @@ public ChannelConnectorImpl(
6972
clock,
7073
routingContext,
7174
domainNameResolver,
72-
notificationConfig);
75+
notificationConfig,
76+
boltAgent);
7377
}
7478

7579
public ChannelConnectorImpl(
@@ -80,8 +84,10 @@ public ChannelConnectorImpl(
8084
Clock clock,
8185
RoutingContext routingContext,
8286
DomainNameResolver domainNameResolver,
83-
NotificationConfig notificationConfig) {
87+
NotificationConfig notificationConfig,
88+
BoltAgent boltAgent) {
8489
this.userAgent = connectionSettings.userAgent();
90+
this.boltAgent = requireNonNull(boltAgent);
8591
this.authTokenManager = connectionSettings.authTokenProvider();
8692
this.routingContext = routingContext;
8793
this.connectTimeoutMillis = connectionSettings.connectTimeoutMillis();
@@ -145,6 +151,6 @@ private void installHandshakeCompletedListeners(
145151
// add listener that sends an INIT message. connection is now fully established. channel pipeline if fully
146152
// set to send/receive messages for a selected protocol version
147153
handshakeCompleted.addListener(new HandshakeCompletedListener(
148-
userAgent, routingContext, connectionInitialized, notificationConfig, clock));
154+
userAgent, boltAgent, routingContext, connectionInitialized, notificationConfig, clock));
149155
}
150156
}

driver/src/main/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListener.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,29 @@
2626
import io.netty.channel.ChannelPromise;
2727
import java.time.Clock;
2828
import org.neo4j.driver.NotificationConfig;
29+
import org.neo4j.driver.internal.BoltAgent;
2930
import org.neo4j.driver.internal.cluster.RoutingContext;
3031
import org.neo4j.driver.internal.messaging.BoltProtocol;
3132
import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51;
3233

3334
public class HandshakeCompletedListener implements ChannelFutureListener {
3435
private final String userAgent;
36+
private final BoltAgent boltAgent;
3537
private final RoutingContext routingContext;
3638
private final ChannelPromise connectionInitializedPromise;
3739
private final NotificationConfig notificationConfig;
3840
private final Clock clock;
3941

4042
public HandshakeCompletedListener(
4143
String userAgent,
44+
BoltAgent boltAgent,
4245
RoutingContext routingContext,
4346
ChannelPromise connectionInitializedPromise,
4447
NotificationConfig notificationConfig,
4548
Clock clock) {
4649
requireNonNull(clock, "clock must not be null");
4750
this.userAgent = requireNonNull(userAgent);
51+
this.boltAgent = requireNonNull(boltAgent);
4852
this.routingContext = routingContext;
4953
this.connectionInitializedPromise = requireNonNull(connectionInitializedPromise);
5054
this.notificationConfig = notificationConfig;
@@ -71,6 +75,7 @@ public void operationComplete(ChannelFuture future) {
7175
authContext.setValidToken(authToken);
7276
protocol.initializeChannel(
7377
userAgent,
78+
boltAgent,
7479
authToken,
7580
routingContext,
7681
connectionInitializedPromise,
@@ -81,7 +86,13 @@ public void operationComplete(ChannelFuture future) {
8186
channel.eventLoop());
8287
} else {
8388
protocol.initializeChannel(
84-
userAgent, null, routingContext, connectionInitializedPromise, notificationConfig, clock);
89+
userAgent,
90+
boltAgent,
91+
null,
92+
routingContext,
93+
connectionInitializedPromise,
94+
notificationConfig,
95+
clock);
8596
}
8697
} else {
8798
connectionInitializedPromise.setFailure(future.cause());

driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.neo4j.driver.Transaction;
3535
import org.neo4j.driver.TransactionConfig;
3636
import org.neo4j.driver.exceptions.ClientException;
37+
import org.neo4j.driver.internal.BoltAgent;
3738
import org.neo4j.driver.internal.DatabaseBookmark;
3839
import org.neo4j.driver.internal.async.UnmanagedTransaction;
3940
import org.neo4j.driver.internal.cluster.RoutingContext;
@@ -47,6 +48,7 @@
4748
import org.neo4j.driver.internal.messaging.v5.BoltProtocolV5;
4849
import org.neo4j.driver.internal.messaging.v51.BoltProtocolV51;
4950
import org.neo4j.driver.internal.messaging.v52.BoltProtocolV52;
51+
import org.neo4j.driver.internal.messaging.v53.BoltProtocolV53;
5052
import org.neo4j.driver.internal.spi.Connection;
5153

5254
public interface BoltProtocol {
@@ -61,14 +63,16 @@ public interface BoltProtocol {
6163
* Initialize channel after it is connected and handshake selected this protocol version.
6264
*
6365
* @param userAgent the user agent string.
66+
* @param boltAgent the bolt agent
6467
* @param authToken the authentication token.
6568
* @param routingContext the configured routing context
6669
* @param channelInitializedPromise the promise to be notified when initialization is completed.
67-
* @param notificationConfig the notification configuration
68-
* @param clock the clock to use
70+
* @param notificationConfig the notification configuration
71+
* @param clock the clock to use
6972
*/
7073
void initializeChannel(
7174
String userAgent,
75+
BoltAgent boltAgent,
7276
AuthToken authToken,
7377
RoutingContext routingContext,
7478
ChannelPromise channelInitializedPromise,
@@ -189,6 +193,8 @@ static BoltProtocol forVersion(BoltProtocolVersion version) {
189193
return BoltProtocolV51.INSTANCE;
190194
} else if (BoltProtocolV52.VERSION.equals(version)) {
191195
return BoltProtocolV52.INSTANCE;
196+
} else if (BoltProtocolV53.VERSION.equals(version)) {
197+
return BoltProtocolV53.INSTANCE;
192198
}
193199
throw new ClientException("Unknown protocol version: " + version);
194200
}

driver/src/main/java/org/neo4j/driver/internal/messaging/request/HelloMessage.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,30 @@
2727
import java.util.Objects;
2828
import org.neo4j.driver.NotificationConfig;
2929
import org.neo4j.driver.Value;
30+
import org.neo4j.driver.internal.BoltAgent;
3031

3132
public class HelloMessage extends MessageWithMetadata {
3233
public static final byte SIGNATURE = 0x01;
3334

3435
private static final String USER_AGENT_METADATA_KEY = "user_agent";
36+
private static final String BOLT_AGENT_METADATA_KEY = "bolt_agent";
37+
private static final String BOLT_AGENT_PRODUCT_KEY = "product";
38+
private static final String BOLT_AGENT_PLATFORM_KEY = "platform";
39+
private static final String BOLT_AGENT_LANGUAGE_KEY = "language";
40+
private static final String BOLT_AGENT_LANGUAGE_DETAIL_KEY = "language_details";
3541
private static final String ROUTING_CONTEXT_METADATA_KEY = "routing";
3642
private static final String PATCH_BOLT_METADATA_KEY = "patch_bolt";
3743

3844
private static final String DATE_TIME_UTC_PATCH_VALUE = "utc";
3945

4046
public HelloMessage(
4147
String userAgent,
48+
BoltAgent boltAgent,
4249
Map<String, Value> authToken,
4350
Map<String, String> routingContext,
4451
boolean includeDateTimeUtc,
4552
NotificationConfig notificationConfig) {
46-
super(buildMetadata(userAgent, authToken, routingContext, includeDateTimeUtc, notificationConfig));
53+
super(buildMetadata(userAgent, boltAgent, authToken, routingContext, includeDateTimeUtc, notificationConfig));
4754
}
4855

4956
@Override
@@ -77,12 +84,29 @@ public String toString() {
7784

7885
private static Map<String, Value> buildMetadata(
7986
String userAgent,
87+
BoltAgent boltAgent,
8088
Map<String, Value> authToken,
8189
Map<String, String> routingContext,
8290
boolean includeDateTimeUtc,
8391
NotificationConfig notificationConfig) {
8492
Map<String, Value> result = new HashMap<>(authToken);
85-
result.put(USER_AGENT_METADATA_KEY, value(userAgent));
93+
if (userAgent != null) {
94+
result.put(USER_AGENT_METADATA_KEY, value(userAgent));
95+
}
96+
if (boltAgent != null) {
97+
var boltAgentMap = new HashMap<String, String>();
98+
boltAgentMap.put(BOLT_AGENT_PRODUCT_KEY, boltAgent.product());
99+
if (boltAgent.platform() != null) {
100+
boltAgentMap.put(BOLT_AGENT_PLATFORM_KEY, boltAgent.platform());
101+
}
102+
if (boltAgent.language() != null) {
103+
boltAgentMap.put(BOLT_AGENT_LANGUAGE_KEY, boltAgent.language());
104+
}
105+
if (boltAgent.languageDetails() != null) {
106+
boltAgentMap.put(BOLT_AGENT_LANGUAGE_DETAIL_KEY, boltAgent.languageDetails());
107+
}
108+
result.put(BOLT_AGENT_METADATA_KEY, value(boltAgentMap));
109+
}
86110
if (routingContext != null) {
87111
result.put(ROUTING_CONTEXT_METADATA_KEY, value(routingContext));
88112
}

driver/src/main/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.neo4j.driver.TransactionConfig;
4141
import org.neo4j.driver.exceptions.Neo4jException;
4242
import org.neo4j.driver.exceptions.UnsupportedFeatureException;
43+
import org.neo4j.driver.internal.BoltAgent;
4344
import org.neo4j.driver.internal.DatabaseBookmark;
4445
import org.neo4j.driver.internal.DatabaseName;
4546
import org.neo4j.driver.internal.async.UnmanagedTransaction;
@@ -81,6 +82,7 @@ public MessageFormat createMessageFormat() {
8182
@Override
8283
public void initializeChannel(
8384
String userAgent,
85+
BoltAgent boltAgent,
8486
AuthToken authToken,
8587
RoutingContext routingContext,
8688
ChannelPromise channelInitializedPromise,
@@ -97,13 +99,15 @@ public void initializeChannel(
9799
if (routingContext.isServerRoutingEnabled()) {
98100
message = new HelloMessage(
99101
userAgent,
102+
null,
100103
((InternalAuthToken) authToken).toMap(),
101104
routingContext.toMap(),
102105
includeDateTimeUtcPatchInHello(),
103106
notificationConfig);
104107
} else {
105108
message = new HelloMessage(
106109
userAgent,
110+
null,
107111
((InternalAuthToken) authToken).toMap(),
108112
null,
109113
includeDateTimeUtcPatchInHello(),

driver/src/main/java/org/neo4j/driver/internal/messaging/v51/BoltProtocolV51.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.concurrent.CompletableFuture;
2828
import org.neo4j.driver.AuthToken;
2929
import org.neo4j.driver.NotificationConfig;
30+
import org.neo4j.driver.internal.BoltAgent;
3031
import org.neo4j.driver.internal.cluster.RoutingContext;
3132
import org.neo4j.driver.internal.handlers.HelloV51ResponseHandler;
3233
import org.neo4j.driver.internal.messaging.BoltProtocol;
@@ -42,6 +43,7 @@ public class BoltProtocolV51 extends BoltProtocolV5 {
4243
@Override
4344
public void initializeChannel(
4445
String userAgent,
46+
BoltAgent boltAgent,
4547
AuthToken authToken,
4648
RoutingContext routingContext,
4749
ChannelPromise channelInitializedPromise,
@@ -57,9 +59,9 @@ public void initializeChannel(
5759

5860
if (routingContext.isServerRoutingEnabled()) {
5961
message = new HelloMessage(
60-
userAgent, Collections.emptyMap(), routingContext.toMap(), false, notificationConfig);
62+
userAgent, null, Collections.emptyMap(), routingContext.toMap(), false, notificationConfig);
6163
} else {
62-
message = new HelloMessage(userAgent, Collections.emptyMap(), null, false, notificationConfig);
64+
message = new HelloMessage(userAgent, null, Collections.emptyMap(), null, false, notificationConfig);
6365
}
6466

6567
var helloFuture = new CompletableFuture<Void>();

0 commit comments

Comments
 (0)