Skip to content

Commit 9519cf0

Browse files
Introduce Bolt agent (#706)
* Introduce bolt agent for HELLO messages using bolt protocol 5.3+ * Introduce bolt protocol 5.3
1 parent aa3f98c commit 9519cf0

File tree

8 files changed

+165
-6
lines changed

8 files changed

+165
-6
lines changed

Neo4j.Driver/Neo4j.Driver.Tests.TestBackend/SupportedFeatures.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static SupportedFeatures()
5454
"Feature:Bolt:4.4",
5555
"Feature:Bolt:5.0",
5656
"Feature:Bolt:5.2",
57+
"Feature:Bolt:5.3",
5758
"Feature:Bolt:Patch:UTC",
5859
"Feature:Impersonation",
5960
//"Feature:TLS:1.1",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) "Neo4j"
2+
// Neo4j Sweden AB [http://neo4j.com]
3+
//
4+
// This file is part of Neo4j.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License").
7+
// You may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
using FluentAssertions;
19+
using Neo4j.Driver.Internal.Messaging.Utils;
20+
using Xunit;
21+
22+
namespace Neo4j.Driver.Internal.MessageHandling.Messages
23+
{
24+
public class BoltAgentBuilderTests
25+
{
26+
[Fact]
27+
public void ShouldReturnBoltAgent()
28+
{
29+
var agent = BoltAgentBuilder.Agent;
30+
agent.Should()
31+
.HaveCount(3)
32+
.And.ContainKey("platform")
33+
.And.ContainKey("language_details")
34+
.And.ContainKey("product")
35+
.WhichValue.Should()
36+
.MatchRegex(@"^neo4j-dotnet/\d\.\d+\.\d+$");
37+
}
38+
}
39+
}

Neo4j.Driver/Neo4j.Driver.Tests/Internal/MessageHandling/Messages/HelloMessageTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,19 @@ public void ShouldThrowIfPassingAuthToHelloMessageAbove51(int major, int minor)
148148

149149
exception.Should().BeOfType<ArgumentOutOfRangeException>();
150150
}
151+
152+
[Theory]
153+
[InlineData(5, 3)]
154+
public void ShouldContainBoltAgentAbove53(int major, int minor)
155+
{
156+
var message =
157+
new HelloMessage(
158+
new BoltProtocolVersion(major, minor),
159+
"User-Agent",
160+
null,
161+
default(INotificationsConfig));
162+
163+
message.Metadata.Should().ContainKey("bolt_agent");
164+
}
151165
}
152166
}

Neo4j.Driver/Neo4j.Driver.Tests/Internal/Protocol/BoltProtocolFactoryTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public void ShouldCreateLegacyBoltProtocol()
3838
[InlineData(4, 3)]
3939
[InlineData(4, 4)]
4040
[InlineData(5, 0)]
41+
[InlineData(5, 1)]
42+
[InlineData(5, 2)]
43+
[InlineData(5, 3)]
4144
public void ShouldCreateBoltProtocol(int major, int minor)
4245
{
4346
var boltProtocol = BoltProtocolFactory.Default.ForVersion(new BoltProtocolVersion(major, minor));

Neo4j.Driver/Neo4j.Driver/Internal/Messaging/HelloMessage.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ internal sealed class HelloMessage : IRequestMessage
2727
{
2828
private const string UserAgentMetadataKey = "user_agent";
2929
private const string RoutingMetadataKey = "routing";
30+
private const string BoltAgentMetadataKey = "bolt_agent";
31+
private const string PatchBoltMetadataKey = "patch_bolt";
3032

3133
public HelloMessage(
3234
BoltProtocolVersion version,
@@ -41,7 +43,10 @@ public HelloMessage(
4143

4244
if (authToken?.Count > 0)
4345
{
44-
Metadata = new Dictionary<string, object>(authToken) { [UserAgentMetadataKey] = userAgent };
46+
Metadata = new Dictionary<string, object>(authToken)
47+
{
48+
[UserAgentMetadataKey] = userAgent
49+
};
4550
}
4651
else
4752
{
@@ -55,7 +60,7 @@ public HelloMessage(
5560

5661
if (version >= BoltProtocolVersion.V4_3 && version < BoltProtocolVersion.V5_0)
5762
{
58-
Metadata.Add("patch_bolt", new[] { "utc" });
63+
Metadata.Add(PatchBoltMetadataKey, new[] { "utc" });
5964
}
6065
}
6166

@@ -72,14 +77,19 @@ public HelloMessage(
7277

7378
Metadata = new Dictionary<string, object>
7479
{
75-
[UserAgentMetadataKey] = userAgent,
76-
[RoutingMetadataKey] = routingContext
80+
[RoutingMetadataKey] = routingContext,
81+
[UserAgentMetadataKey] = userAgent
7782
};
7883

7984
if (version >= BoltProtocolVersion.V5_2)
8085
{
8186
NotificationsMetadataWriter.AddNotificationsConfigToMetadata(Metadata, notificationsConfig);
8287
}
88+
89+
if (version >= BoltProtocolVersion.V5_3)
90+
{
91+
Metadata.Add(BoltAgentMetadataKey, BoltAgentBuilder.Agent);
92+
}
8393
}
8494

8595
public IDictionary<string, object> Metadata { get; }
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) "Neo4j"
2+
// Neo4j Sweden AB [http://neo4j.com]
3+
//
4+
// This file is part of Neo4j.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License").
7+
// You may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
using System;
19+
using System.Collections.Generic;
20+
using System.Runtime.InteropServices;
21+
using System.Threading;
22+
23+
namespace Neo4j.Driver.Internal.Messaging.Utils;
24+
25+
internal static class BoltAgentBuilder
26+
{
27+
private static readonly Lazy<Dictionary<string, string>> LazyAgent = new(
28+
GetBoltAgent,
29+
LazyThreadSafetyMode.PublicationOnly);
30+
31+
public static Dictionary<string, string> Agent => LazyAgent.Value;
32+
33+
/// <summary>
34+
/// This Dictionary follows a common format and other teams across neo4j rely on it.
35+
/// Changes need to be in accordance with company policy.
36+
/// </summary>
37+
private static Dictionary<string, string> GetBoltAgent()
38+
{
39+
var version = typeof(BoltAgentBuilder).Assembly.GetName().Version;
40+
if (version == null)
41+
{
42+
throw new ClientException("Could not collect assembly version of driver required for handshake.");
43+
}
44+
45+
var os = OsString();
46+
var env = DotnetString();
47+
48+
var boltAgent = new Dictionary<string, string>(3)
49+
{
50+
["product"] = $"neo4j-dotnet/{version.Major}.{version.Minor}.{version.Build}"
51+
};
52+
53+
if (!string.IsNullOrEmpty(os))
54+
{
55+
boltAgent["platform"] = os;
56+
}
57+
58+
if (!string.IsNullOrEmpty(env))
59+
{
60+
boltAgent["language_details"] = env;
61+
}
62+
63+
return boltAgent;
64+
}
65+
66+
private static string DotnetString()
67+
{
68+
try
69+
{
70+
return RuntimeInformation.FrameworkDescription;
71+
}
72+
catch (Exception)
73+
{
74+
return string.Empty;
75+
}
76+
}
77+
78+
private static string OsString()
79+
{
80+
try
81+
{
82+
var arch = RuntimeInformation.ProcessArchitecture;
83+
var os = RuntimeInformation.OSDescription;
84+
return $"{os};{arch}";
85+
}
86+
catch (Exception)
87+
{
88+
return string.Empty;
89+
}
90+
}
91+
}

Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal class BoltProtocolFactory : IBoltProtocolFactory
5252
//This is a 'magic' handshake identifier to indicate we're using 'BOLT' ('GOGOBOLT')
5353
goGoBolt,
5454
// 4 versions max.
55-
BoltProtocolVersion.V5_2.PackToIntRange(BoltProtocolVersion.V5_0),
55+
BoltProtocolVersion.V5_3.PackToIntRange(BoltProtocolVersion.V5_0),
5656
BoltProtocolVersion.V4_4.PackToIntRange(BoltProtocolVersion.V4_2),
5757
BoltProtocolVersion.V4_1.PackToInt(),
5858
BoltProtocolVersion.V3_0.PackToInt()
@@ -79,7 +79,7 @@ public IBoltProtocol ForVersion(BoltProtocolVersion version)
7979
{ MajorVersion: 0, MinorVersion: 0 } => throw new NotSupportedException(NoAgreedVersion),
8080
{ MajorVersion: 3, MinorVersion: 0 } => BoltProtocolV3.Instance,
8181
{ MajorVersion: 4, MinorVersion: <= 4, MinorVersion: >= 1 } => BoltProtocol.Instance,
82-
{ MajorVersion: 5, MinorVersion: <= 2, MinorVersion: >= 0 } => BoltProtocol.Instance,
82+
{ MajorVersion: 5, MinorVersion: <= 3, MinorVersion: >= 0 } => BoltProtocol.Instance,
8383
_ => throw new NotSupportedException(
8484
$"Protocol error, server suggested unexpected protocol version: {version}")
8585
};

Neo4j.Driver/Neo4j.Driver/Internal/Protocol/BoltProtocolVersion.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ internal sealed class BoltProtocolVersion : IEquatable<BoltProtocolVersion>
3737
public static readonly BoltProtocolVersion V5_0 = new(5, 0);
3838
public static readonly BoltProtocolVersion V5_1 = new(5, 1);
3939
public static readonly BoltProtocolVersion V5_2 = new(5, 2);
40+
public static readonly BoltProtocolVersion V5_3 = new(5, 3);
4041

4142
private readonly int _compValue;
4243

0 commit comments

Comments
 (0)