You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by fl...@apache.org on 2022/01/26 17:13:06 UTC

[tinkerpop] 01/01: TINKERPOP-2682 Enable WS compression in .NET

This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a commit to branch TINKERPOP-2682
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 2ae1dbb5d9553f8a2b40d981ef8879e87c0f8771
Author: Florian Hockmann <fh...@florian-hockmann.de>
AuthorDate: Wed Jan 26 17:50:06 2022 +0100

    TINKERPOP-2682 Enable WS compression in .NET
    
    WebSocket support was only added in .NET 6 which made it necessary to
    explicitly add .NET 6 as an additional target framework.
    
    I also enabled package validation [1] so we a) notice if we introduce
    breaking by accident and b) ensure that our code compiled against
    .NET Standard 2.0 can also be run against .NET 6.
    This already helped me to keep the breaking change here to a minimum.
    It's just an added optional parameter.
    We could refactor the GremlinClient constructors in the future in a way
    to avoid this problem of having to add optional parameters for new
    config option which is always a breaking change.
    The validation is executed on `dotnet pack` which gets already executed
    via `mvn verify` and is therefore also part of our GH actions.
    
    Compatibility errors that we know about and that we accept should be
    added to the `CompatibilitySuppressions.xml` file which can also be
    generated via
    
    ```
    dotnet pack Gremlin.Net.csproj /p:GenerateCompatibilitySuppressionFile=true
    ```
    
    WebSocket compression can be problematic due to attacks like
    CRIME/BREACH as noted in TINKERPOP-2682. So, I added an option to easily
    disable it for applications that might be vulnerable and added docs to
    make users aware of this. We might want to do the same maybe also for
    Java and Python.
    
    Lastly, some quick benchmarks I ran on my machine:
    
    GraphBinary:
    
    Without compression:
    Packets transmitted: 3594
    Bytes transmitted: 7MB
    Runtime: 1min 1.5sec
    
    With compression:
    Packets transmitted: 62 (1.7%)
    Bytes transmitted: 34KB (0.5%)
    Runtime: 1.3sec
    
    GraphSON 3:
    
    Without compression:
    Packets transmitted: 12029
    Bytes transmitted: 25MB
    Runtime: 1min 40sec
    
    With compression:
    Packets transmitted: 116 (1.0%)
    Bytes transmitted: 103KB (0.4%)
    Runtime: 1min 39sec
    
    So, both for GraphSON as well as for GraphBinary the network traffic
    shrinks by a factor of ~100 while the total runtime is not affected.
    
    [1]: https://docs.microsoft.com/en-us/dotnet/fundamentals/package-validation/overview
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/reference/gremlin-variants.asciidoc       | 12 ++++
 docs/src/upgrade/release-3.5.x.asciidoc            | 20 +++++++
 .../src/Gremlin.Net/CompatibilitySuppressions.xml  | 11 ++++
 .../src/Gremlin.Net/Driver/Connection.cs           | 12 +++-
 .../src/Gremlin.Net/Driver/ConnectionFactory.cs    | 13 ++--
 .../src/Gremlin.Net/Driver/GremlinClient.cs        | 39 ++++++++----
 .../src/Gremlin.Net/Driver/WebSocketConnection.cs  | 44 +++++++++-----
 .../src/Gremlin.Net/Driver/WebSocketSettings.cs    | 55 +++++++++++++++++
 gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj  |  9 ++-
 .../CompressionBenchmarks.cs                       | 70 ++++++++++++++++++++++
 .../Gremlin.Net.Benchmarks.csproj                  |  2 +-
 .../test/Gremlin.Net.Benchmarks/Program.cs         | 10 ++++
 13 files changed, 259 insertions(+), 39 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index abeff7d..91aaf6a 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -23,6 +23,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 [[release-3-5-3]]
 === TinkerPop 3.5.3 (Release Date: NOT OFFICIALLY RELEASED YET)
 
+* Added support for WebSocket compression in the .NET driver. (Only available in .NET 6.)
 
 [[release-3-5-2]]
 === TinkerPop 3.5.2 (Release Date: January 10, 2022)
diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc
index 8010d4b..36f27e2 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -1276,6 +1276,18 @@ when a new request comes in.
 A `ServerUnavailableException` is thrown if no connection is available to the server to submit a request after
 `ReconnectionAttempts` retries.
 
+==== WebSocket Configuration
+
+The WebSocket connections can also be configured, directly as parameters of the `GremlinClient` constructor. It takes
+an optional delegate `webSocketConfiguration` that will be invoked for each connection. This makes it possible to
+configure more advanced options like the `KeepAliveInterval` or client certificates.
+
+Starting with .NET 6, it is also possible to use compression for WebSockets. This is enabled by default starting with
+TinkerPop 3.5.3 (again, only on .NET 6 or higher). Note that compression might make an application susceptible to
+attacks like CRIME/BREACH. Compression should therefore be turned off if the application sends sensitive data to the
+server as well as data that could potentially be controlled by an untrusted user. Compression can be disabled via the
+`disableCompression` parameter.
+
 [[gremlin-dotnet-serialization]]
 === Serialization
 
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 9a53e06..211675e 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -21,6 +21,26 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 
 *The Sleeping Gremlin: No. 18 Entr'acte Symphonique*
 
+== TinkerPop 3.5.3
+
+*Release Date: NOT OFFICIALLY RELEASED YET*
+
+Please see the link:https://github.com/apache/tinkerpop/blob/3.5.3/CHANGELOG.asciidoc#release-3-5-3[changelog] for a
+complete list of all the modifications that are part of this release.
+
+=== Upgrading for Users
+
+==== .NET WebSocket Compression Support
+
+.NET 6 added support for WebSocket compression. This is now also enabled by default in Gremlin.NET.
+
+It should be noted however that compression might make an application susceptible to attacks like CRIME/BREACH.
+Compression should therefore be turned off if the application sends sensitive data to the server as well as data that
+could potentially be controlled by an untrusted user. Compression can be disabled via the `disableCompression`
+parameter on the `GremlinClient` constructor.
+
+See:link:https://issues.apache.org/jira/browse/TINKERPOP-2682[TINKERPOP-2682]
+
 == TinkerPop 3.5.2
 
 *Release Date: January 10, 2022*
diff --git a/gremlin-dotnet/src/Gremlin.Net/CompatibilitySuppressions.xml b/gremlin-dotnet/src/Gremlin.Net/CompatibilitySuppressions.xml
new file mode 100644
index 0000000..d1abba6
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/CompatibilitySuppressions.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <Suppression>
+    <!-- Added an optional parameter to the constructor. -->
+    <DiagnosticId>CP0002</DiagnosticId>
+    <Target>M:Gremlin.Net.Driver.GremlinClient.#ctor(Gremlin.Net.Driver.GremlinServer,Gremlin.Net.Driver.IMessageSerializer,Gremlin.Net.Driver.ConnectionPoolSettings,System.Action{System.Net.WebSockets.ClientWebSocketOptions},System.String)</Target>
+    <Left>lib/netstandard2.0/Gremlin.Net.dll</Left>
+    <Right>lib/netstandard2.0/Gremlin.Net.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
+</Suppressions>
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs
index ea9e485..16bf155 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Connection.cs
@@ -58,7 +58,7 @@ namespace Gremlin.Net.Driver
         private const int Closed = 1;
 
         public Connection(Uri uri, string username, string password, IMessageSerializer messageSerializer,
-            Action<ClientWebSocketOptions> webSocketConfiguration, string sessionId)
+            WebSocketSettings webSocketSettings, string sessionId)
         {
             _uri = uri;
             _username = username;
@@ -69,7 +69,7 @@ namespace Gremlin.Net.Driver
                 _sessionEnabled = true;
             }
             _messageSerializer = messageSerializer;
-            _webSocketConnection = new WebSocketConnection(webSocketConfiguration);
+            _webSocketConnection = new WebSocketConnection(webSocketSettings);
         }
 
         public async Task ConnectAsync(CancellationToken cancellationToken)
@@ -244,6 +244,14 @@ namespace Gremlin.Net.Driver
                 message = RebuildSessionMessage(message);
             }
             var serializedMsg = await _messageSerializer.SerializeMessageAsync(message).ConfigureAwait(false);
+#if NET6_0_OR_GREATER
+            if (message.Processor == Tokens.OpsAuthentication)
+            {
+                // Don't compress a message that contains credentials to prevent attacks like CRIME or BREACH
+                await _webSocketConnection.SendMessageUncompressedAsync(serializedMsg).ConfigureAwait(false);
+                return;
+            }
+#endif
             await _webSocketConnection.SendMessageAsync(serializedMsg).ConfigureAwait(false);
         }
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionFactory.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionFactory.cs
index 59807a1..428c819 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionFactory.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionFactory.cs
@@ -21,31 +21,28 @@
 
 #endregion
 
-using System;
-using System.Net.WebSockets;
-
 namespace Gremlin.Net.Driver
 {
     internal class ConnectionFactory : IConnectionFactory
     {
-        private readonly Action<ClientWebSocketOptions> _webSocketConfiguration;
+        private readonly WebSocketSettings _webSocketSettings;
         private readonly GremlinServer _gremlinServer;
         private readonly string _sessionId;
-        private IMessageSerializer _messageSerializer;
+        private readonly IMessageSerializer _messageSerializer;
 
         public ConnectionFactory(GremlinServer gremlinServer, IMessageSerializer messageSerializer,
-            Action<ClientWebSocketOptions> webSocketConfiguration, string sessionId)
+            WebSocketSettings webSocketSettings, string sessionId)
         {
             _gremlinServer = gremlinServer;
             _messageSerializer = messageSerializer;
             _sessionId = sessionId;
-            _webSocketConfiguration = webSocketConfiguration;
+            _webSocketSettings = webSocketSettings;
         }
 
         public IConnection CreateConnection()
         {
             return new Connection(_gremlinServer.Uri, _gremlinServer.Username, _gremlinServer.Password,
-                _messageSerializer, _webSocketConfiguration, _sessionId);
+                _messageSerializer, _webSocketSettings, _sessionId);
         }
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs
index 745a3cc..4119c7d 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs
@@ -100,9 +100,10 @@ namespace Gremlin.Net.Driver
                 default:
                     throw new ArgumentException(nameof(mimeType), $"{mimeType} not supported");
             }
-            
+
             var connectionFactory =
-                new ConnectionFactory(gremlinServer, messageSerializer, webSocketConfiguration, sessionId);
+                new ConnectionFactory(gremlinServer, messageSerializer,
+                    new WebSocketSettings { WebSocketConfigurationCallback = webSocketConfiguration }, sessionId);
 
             // make sure one connection in pool as session mode
             if (!string.IsNullOrEmpty(sessionId))
@@ -145,21 +146,39 @@ namespace Gremlin.Net.Driver
         ///     object used to configure WebSocket connections.
         /// </param>
         /// <param name="sessionId">The session Id if Gremlin Client in session mode, defaults to null as session-less Client.</param>
+        /// <param name="disableCompression">
+        ///     Whether to disable compression. Compression is only supported since .NET 6.
+        ///     There it is also enabled by default.
+        ///
+        ///     Note that compression might make your application susceptible to attacks like CRIME/BREACH. Compression
+        ///     should therefore be turned off if your application sends sensitive data to the server as well as data
+        ///     that could potentially be controlled by an untrusted user.
+        /// </param>
         public GremlinClient(GremlinServer gremlinServer, IMessageSerializer messageSerializer = null,
             ConnectionPoolSettings connectionPoolSettings = null,
-            Action<ClientWebSocketOptions> webSocketConfiguration = null, string sessionId = null)
+            Action<ClientWebSocketOptions> webSocketConfiguration = null, string sessionId = null
+            , bool disableCompression = false
+        )
         {
-            messageSerializer = messageSerializer ?? new GraphSON3MessageSerializer();
+            messageSerializer ??= new GraphSON3MessageSerializer();
+            var webSocketSettings = new WebSocketSettings
+            {
+                WebSocketConfigurationCallback = webSocketConfiguration
+#if NET6_0_OR_GREATER
+                , UseCompression = !disableCompression
+#endif
+            };
             var connectionFactory =
-                new ConnectionFactory(gremlinServer, messageSerializer, webSocketConfiguration, sessionId);
-
+                new ConnectionFactory(gremlinServer, messageSerializer, webSocketSettings, sessionId);
+            
             // make sure one connection in pool as session mode
             if (!string.IsNullOrEmpty(sessionId))
             {
                 if (connectionPoolSettings != null)
                 {
                     if (connectionPoolSettings.PoolSize != 1)
-                        throw new ArgumentOutOfRangeException(nameof(connectionPoolSettings), "PoolSize must be 1 in session mode!");
+                        throw new ArgumentOutOfRangeException(nameof(connectionPoolSettings),
+                            "PoolSize must be 1 in session mode!");
                 }
                 else
                 {
@@ -178,10 +197,8 @@ namespace Gremlin.Net.Driver
         /// <inheritdoc />
         public async Task<ResultSet<T>> SubmitAsync<T>(RequestMessage requestMessage)
         {
-            using (var connection = _connectionPool.GetAvailableConnection())
-            {
-                return await connection.SubmitAsync<T>(requestMessage).ConfigureAwait(false);
-            }
+            using var connection = _connectionPool.GetAvailableConnection();
+            return await connection.SubmitAsync<T>(requestMessage).ConfigureAwait(false);
         }
 
         #region IDisposable Support
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketConnection.cs
index 936de12..2069998 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketConnection.cs
@@ -35,10 +35,16 @@ namespace Gremlin.Net.Driver
         private const WebSocketMessageType MessageType = WebSocketMessageType.Binary;
         private readonly ClientWebSocket _client;
 
-        public WebSocketConnection(Action<ClientWebSocketOptions> webSocketConfiguration)
+        public WebSocketConnection(WebSocketSettings settings)
         {
             _client = new ClientWebSocket();
-            webSocketConfiguration?.Invoke(_client.Options);
+#if NET6_0_OR_GREATER
+            if (settings.UseCompression)
+            {
+                _client.Options.DangerousDeflateOptions = settings.CompressionOptions;
+            }
+#endif
+            settings.WebSocketConfigurationCallback?.Invoke(_client.Options);
         }
 
         public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
@@ -65,7 +71,17 @@ namespace Gremlin.Net.Driver
         private bool CloseAlreadyInitiated => _client.State == WebSocketState.Closed ||
                                             _client.State == WebSocketState.Aborted ||
                                             _client.State == WebSocketState.CloseSent;
-
+        
+#if NET6_0_OR_GREATER
+        public async Task SendMessageUncompressedAsync(byte[] message)
+        {
+            await _client.SendAsync(new ArraySegment<byte>(message), MessageType,
+                    WebSocketMessageFlags.EndOfMessage | WebSocketMessageFlags.DisableCompression,
+                    CancellationToken.None)
+                .ConfigureAwait(false);
+        }
+#endif
+        
         public async Task SendMessageAsync(byte[] message)
         {
             await
@@ -75,20 +91,18 @@ namespace Gremlin.Net.Driver
 
         public async Task<byte[]> ReceiveMessageAsync()
         {
-            using (var ms = new MemoryStream())
-            {
-                WebSocketReceiveResult received;
-                var buffer = new byte[ReceiveBufferSize];
+            using var ms = new MemoryStream();
+            WebSocketReceiveResult received;
+            var buffer = new byte[ReceiveBufferSize];
 
-                do
-                {
-                    var receiveBuffer = new ArraySegment<byte>(buffer);
-                    received = await _client.ReceiveAsync(receiveBuffer, CancellationToken.None).ConfigureAwait(false);
-                    ms.Write(receiveBuffer.Array, receiveBuffer.Offset, received.Count);
-                } while (!received.EndOfMessage);
+            do
+            {
+                var receiveBuffer = new ArraySegment<byte>(buffer);
+                received = await _client.ReceiveAsync(receiveBuffer, CancellationToken.None).ConfigureAwait(false);
+                ms.Write(receiveBuffer.Array, receiveBuffer.Offset, received.Count);
+            } while (!received.EndOfMessage);
 
-                return ms.ToArray();
-            }
+            return ms.ToArray();
         }
 
         public bool IsOpen => _client.State == WebSocketState.Open;
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketSettings.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketSettings.cs
new file mode 100644
index 0000000..a34b226
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/WebSocketSettings.cs
@@ -0,0 +1,55 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#endregion
+
+using System;
+using System.Net.WebSockets;
+
+namespace Gremlin.Net.Driver
+{
+    /// <summary>
+    ///     Holds settings for WebSocket connections.
+    /// </summary>
+    internal class WebSocketSettings
+    {
+        // We currently only use this internally so we don't have to pass these values through the different layers
+        // all the way down to the WebSocketConnection class. We can make this public however when we refactor the
+        // configuration in general.
+        
+        /// <summary>
+        ///     Gets or sets the delegate that will be invoked with the <see cref="ClientWebSocketOptions" />
+        ///     object used to configure WebSocket connections.
+        /// </summary>
+        public Action<ClientWebSocketOptions> WebSocketConfigurationCallback { get; set; }
+#if NET6_0_OR_GREATER
+        /// <summary>
+        ///     Gets or sets whether compressions will be used. The default is true. (Only available since .NET 6.)
+        /// </summary>
+        public bool UseCompression { get; set; } = true;
+
+        /// <summary>
+        ///     Gets or sets compression options. (Only available since .SET 6.)
+        /// </summary>
+        public WebSocketDeflateOptions CompressionOptions { get; set; } = new WebSocketDeflateOptions();
+#endif
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
index 9a90b91..57afe61 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
+++ b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
@@ -18,7 +18,7 @@ limitations under the License.
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup Label="Build">
-    <TargetFramework>netstandard2.0</TargetFramework>
+    <TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <LangVersion>8</LangVersion>
@@ -67,14 +67,19 @@ NOTE that versions suffixed with "-rc" are considered release candidates (i.e. p
     <RepositoryUrl>https://github.com/apache/tinkerpop</RepositoryUrl>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>    
     <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
+    <EnablePackageValidation>true</EnablePackageValidation>
+    <PackageValidationBaselineVersion>3.5.2</PackageValidationBaselineVersion>
   </PropertyGroup>
 
   <ItemGroup>
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
     <PackageReference Include="System.Text.Json" Version="5.0.2" />
-    <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
     <PackageReference Include="Polly" Version="7.2.2" />
   </ItemGroup>
+    
+  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
+    <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
+  </ItemGroup>
 
   <ItemGroup>
     <None Include="../../LICENSE" Pack="true" PackagePath="" />
diff --git a/gremlin-dotnet/test/Gremlin.Net.Benchmarks/CompressionBenchmarks.cs b/gremlin-dotnet/test/Gremlin.Net.Benchmarks/CompressionBenchmarks.cs
new file mode 100644
index 0000000..ae933bf
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.Benchmarks/CompressionBenchmarks.cs
@@ -0,0 +1,70 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#endregion
+
+using System.Threading.Tasks;
+using Gremlin.Net.Driver;
+using Gremlin.Net.Driver.Remote;
+using Gremlin.Net.Structure.IO.GraphBinary;
+using Gremlin.Net.Structure.IO.GraphSON;
+using static Gremlin.Net.Process.Traversal.AnonymousTraversalSource;
+using static Gremlin.Net.Process.Traversal.__;
+
+namespace Gremlin.Net.Benchmarks;
+
+public class CompressionBenchmarks
+{
+    public static async Task GraphSONWithoutCompression()
+    {
+        var client = new GremlinClient(new GremlinServer("localhost", 45940), new GraphSON3MessageSerializer(),
+            disableCompression: true);
+        await PerformBenchmarkWithClient(client);
+    }
+    
+    public static async Task GraphSONWithCompression()
+    {
+        var client = new GremlinClient(new GremlinServer("localhost", 45940), new GraphSON3MessageSerializer());
+        await PerformBenchmarkWithClient(client);
+    }
+    
+    public static async Task GraphBinaryWithoutCompression()
+    {
+        var client = new GremlinClient(new GremlinServer("localhost", 45940), new GraphBinaryMessageSerializer(),
+            disableCompression: true);
+        await PerformBenchmarkWithClient(client);
+    }
+    
+    public static async Task GraphBinaryWithCompression()
+    {
+        var client = new GremlinClient(new GremlinServer("localhost", 45940), new GraphBinaryMessageSerializer());
+        await PerformBenchmarkWithClient(client);
+    }
+
+    private static async Task PerformBenchmarkWithClient(GremlinClient client)
+    {
+        var g = Traversal().WithRemote(new DriverRemoteConnection(client));
+        for (var i = 0; i < 5; i++)
+        {
+            await g.V().Repeat(Both()).Times(10).Emit().Fold().Promise(t => t.ToList());
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Gremlin.Net.Benchmarks.csproj b/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Gremlin.Net.Benchmarks.csproj
index 51a1b71..d886eb7 100644
--- a/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Gremlin.Net.Benchmarks.csproj
+++ b/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Gremlin.Net.Benchmarks.csproj
@@ -2,7 +2,7 @@
 
     <PropertyGroup>
         <OutputType>Exe</OutputType>
-        <TargetFramework>netcoreapp3.1</TargetFramework>
+        <TargetFramework>net6.0</TargetFramework>
     </PropertyGroup>
 
     <ItemGroup>
diff --git a/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Program.cs b/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Program.cs
index 070d4d6..73ce83a 100644
--- a/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Program.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.Benchmarks/Program.cs
@@ -21,6 +21,9 @@
 
 #endregion
 
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
 using BenchmarkDotNet.Running;
 
 namespace Gremlin.Net.Benchmarks
@@ -31,5 +34,12 @@ namespace Gremlin.Net.Benchmarks
         {
             BenchmarkRunner.Run<MessageSerializerBenchmarks>();
         }
+        
+        // public static async Task Main()
+        // {
+        //     var watch = Stopwatch.StartNew();
+        //     await CompressionBenchmarks.GraphBinaryWithCompression();
+        //     Console.WriteLine(watch.Elapsed);
+        // }
     }
 }
\ No newline at end of file