Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c1aaf97
preliminary zlib and zstd bindings
akiraveliara Jul 10, 2024
0c1ab7d
Merge branch 'master' into aki/compression
akiraveliara Jul 10, 2024
88f961a
nuke the old PayloadDecompressor and add ways to configure compression
akiraveliara Jul 10, 2024
d2d0a48
the last remaining straws
akiraveliara Jul 11, 2024
d33e9cd
implement the decompressors
akiraveliara Jul 11, 2024
b0f73e0
use the pointer to access and check for prefixes
akiraveliara Jul 12, 2024
bde8b85
read the zlib suffix rather than headers
akiraveliara Jul 12, 2024
791ffed
Merge branch 'master' into aki/compression
akiraveliara Jul 27, 2024
93363f1
fill the throw helpers in
akiraveliara Jul 27, 2024
d676bb0
Merge branch 'master' into aki/compression
akiraveliara Aug 20, 2024
1b82cd3
discord doesn't send the suffix.
akiraveliara Aug 20, 2024
ffd672d
cope with unterminated payloads in zlib
akiraveliara Aug 21, 2024
af2cf41
alias consistency
akiraveliara Aug 23, 2024
f51ee2d
remove superseded GatewayCompressionLevel
akiraveliara Aug 23, 2024
3ea1463
remove GatewayCompressionLevel from DiscordConfiguration
akiraveliara Aug 23, 2024
52e4c3f
minuscule fix for gateway identifys
akiraveliara Sep 1, 2024
4e281ae
merge conflicts
akiraveliara Dec 9, 2024
08b5e37
avoid doubly infinite looping over buffer processing
akiraveliara Dec 9, 2024
a07c5ea
managed fallback + default decompression selector
akiraveliara Dec 9, 2024
2e9ae49
finish the managed backend
akiraveliara Dec 9, 2024
3e7ac6d
log b64'ed compressed payloads temporarily
akiraveliara Dec 9, 2024
07b3082
remove managed backend again
akiraveliara Dec 9, 2024
1528afb
make zstd decompression work, hopefully
akiraveliara Dec 9, 2024
79e95c4
make zlib work on top of ZLibStream, begrudgingly
akiraveliara Dec 10, 2024
318fba3
make extension method naming consistent
akiraveliara Dec 12, 2024
026f111
remove debugging code
akiraveliara Dec 14, 2024
0e82e6d
make sure to reset the compression context if the connection changes
akiraveliara Dec 15, 2024
ab04ea0
change the loglevel of the message telling us the active decompressor
akiraveliara Dec 16, 2024
de69b83
add doc
akiraveliara Dec 16, 2024
d49d846
implement the finalizer vam wanted
akiraveliara Dec 18, 2024
3d8fe83
fix a crash on shutdown
akiraveliara Dec 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions DSharpPlus/Clients/DiscordClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using DSharpPlus.Logging;
using DSharpPlus.Net;
using DSharpPlus.Net.Gateway;
using DSharpPlus.Net.Gateway.Compression;
using DSharpPlus.Net.Gateway.Compression.Zlib;
using DSharpPlus.Net.Gateway.Compression.Zstd;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -95,6 +98,36 @@ public static DiscordClientBuilder CreateSharded
return builder;
}

/// <summary>
/// Sets the gateway compression used to zstd. This requires zstd natives to be available to the application.
/// </summary>
/// <returns>The current instance for chaining.</returns>
public DiscordClientBuilder UseZstdCompression()
{
this.serviceCollection.Replace<IPayloadDecompressor, ZstdDecompressor>();
return this;
}

/// <summary>
/// Sets the gateway compression used to zlib. This is the default compression mode.
/// </summary>
/// <returns>The current instance for chaining.</returns>
public DiscordClientBuilder UseZlibCompression()
{
this.serviceCollection.Replace<IPayloadDecompressor, ZlibStreamDecompressor>();
return this;
}

/// <summary>
/// Disables gateway compression entirely.
/// </summary>
/// <returns>The current instance for chaining.</returns>
public DiscordClientBuilder DisableGatewayCompression()
{
this.serviceCollection.Replace<IPayloadDecompressor, NullDecompressor>();
return this;
}

/// <summary>
/// Disables the DSharpPlus default logger for this DiscordClientBuilder.
/// </summary>
Expand Down
10 changes: 5 additions & 5 deletions DSharpPlus/Clients/MultiShardOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using DSharpPlus.Net;
using DSharpPlus.Net.Abstractions;
using DSharpPlus.Net.Gateway;
using DSharpPlus.Net.WebSocket;
using DSharpPlus.Net.Gateway.Compression;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
Expand All @@ -22,7 +22,7 @@ public sealed class MultiShardOrchestrator : IShardOrchestrator
private readonly DiscordApiClient apiClient;
private readonly ShardingOptions options;
private readonly IServiceProvider serviceProvider;
private readonly PayloadDecompressor decompressor;
private readonly IPayloadDecompressor decompressor;

private uint shardCount;
private uint stride;
Expand Down Expand Up @@ -54,7 +54,7 @@ public MultiShardOrchestrator
IServiceProvider serviceProvider,
IOptions<ShardingOptions> options,
DiscordApiClient apiClient,
PayloadDecompressor decompressor
IPayloadDecompressor decompressor
)
{
this.apiClient = apiClient;
Expand Down Expand Up @@ -99,9 +99,9 @@ public async ValueTask StartAsync(DiscordActivity? activity, DiscordUserStatus?
gwuri.AddParameter("v", "10")
.AddParameter("encoding", "json");

if (this.decompressor.CompressionLevel == GatewayCompressionLevel.Stream)
if (this.decompressor.IsTransportCompression)
{
gwuri.AddParameter("compress", "zlib-stream");
gwuri.AddParameter("compress", this.decompressor.Name);
}

this.shards = new IGatewayClient[startShards];
Expand Down
10 changes: 5 additions & 5 deletions DSharpPlus/Clients/SingleShardOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using DSharpPlus.Entities;
using DSharpPlus.Net;
using DSharpPlus.Net.Gateway;
using DSharpPlus.Net.WebSocket;
using DSharpPlus.Net.Gateway.Compression;

namespace DSharpPlus.Clients;

Expand All @@ -15,7 +15,7 @@ public sealed class SingleShardOrchestrator : IShardOrchestrator
{
private readonly IGatewayClient gatewayClient;
private readonly DiscordApiClient apiClient;
private readonly PayloadDecompressor decompressor;
private readonly IPayloadDecompressor decompressor;

/// <summary>
/// Creates a new instance of this type.
Expand All @@ -24,7 +24,7 @@ public SingleShardOrchestrator
(
IGatewayClient gatewayClient,
DiscordApiClient apiClient,
PayloadDecompressor decompressor
IPayloadDecompressor decompressor
)
{
this.gatewayClient = gatewayClient;
Expand Down Expand Up @@ -77,9 +77,9 @@ public async ValueTask StartAsync(DiscordActivity? activity, DiscordUserStatus?
gwuri.AddParameter("v", "10")
.AddParameter("encoding", "json");

if (this.decompressor.CompressionLevel == GatewayCompressionLevel.Stream)
if (this.decompressor.IsTransportCompression)
{
gwuri.AddParameter("compress", "zlib-stream");
gwuri.AddParameter("compress", this.decompressor.Name);
}

await this.gatewayClient.ConnectAsync(gwuri.Build(), activity, status, idleSince);
Expand Down
1 change: 1 addition & 0 deletions DSharpPlus/DSharpPlus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PackageId>DSharpPlus</PackageId>
<Description>A C# API for Discord based off DiscordSharp, but rewritten to fit the API standards.</Description>
<PackageTags>$(PackageTags), webhooks</PackageTags>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>true</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>Preview</LangVersion>
Expand Down
9 changes: 0 additions & 9 deletions DSharpPlus/DiscordConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ namespace DSharpPlus;
/// </summary>
public sealed class DiscordConfiguration
{
/// <summary>
/// <para>Sets the level of compression for WebSocket traffic.</para>
/// <para>Disabling this option will increase the amount of traffic sent via WebSocket. Setting <see cref="GatewayCompressionLevel.Payload"/> will enable compression for READY and GUILD_CREATE payloads. Setting <see cref="Stream"/> will enable compression for the entire WebSocket stream, drastically reducing amount of traffic.</para>
/// <para>Defaults to <see cref="GatewayCompressionLevel.None"/>.</para>
/// </summary>
/// <remarks>This property's default has been changed from <see cref="GatewayCompressionLevel.Stream"/> due to a bug in the client wherein rapid reconnections causes payloads to be decompressed into a mangled string.</remarks>
// Here be dragons, ye who use compression.
public GatewayCompressionLevel GatewayCompressionLevel { internal get; set; } = GatewayCompressionLevel.None;

/// <summary>
/// Sets whether the client should attempt to cache members if exclusively using unprivileged intents.
Expand Down Expand Up @@ -74,7 +66,6 @@ public DiscordConfiguration()
/// <param name="other">Client configuration to clone.</param>
public DiscordConfiguration(DiscordConfiguration other)
{
this.GatewayCompressionLevel = other.GatewayCompressionLevel;
this.UdpClientFactory = other.UdpClientFactory;
this.LogUnknownEvents = other.LogUnknownEvents;
this.LogUnknownAuditlogs = other.LogUnknownAuditlogs;
Expand Down
23 changes: 20 additions & 3 deletions DSharpPlus/Extensions/ServiceCollectionExtensions.InternalSetup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Channels;

using DSharpPlus.Clients;
Expand All @@ -7,7 +8,9 @@
using DSharpPlus.Net.Gateway;
using DSharpPlus.Net.InboundWebhooks;
using DSharpPlus.Net.InboundWebhooks.Transport;
using DSharpPlus.Net.WebSocket;
using DSharpPlus.Net.Gateway.Compression;
using DSharpPlus.Net.Gateway.Compression.Zlib;
using DSharpPlus.Net.Gateway.Compression.Zstd;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -56,7 +59,7 @@ DiscordIntents intents
))
.AddTransient<ITransportService, TransportService>()
.AddTransient<IGatewayClient, GatewayClient>()
.AddTransient<PayloadDecompressor>()
.RegisterBestDecompressor()
.AddSingleton<IShardOrchestrator, SingleShardOrchestrator>()
.AddSingleton<IEventDispatcher, DefaultEventDispatcher>()
.AddSingleton<DiscordClient>();
Expand Down Expand Up @@ -115,7 +118,7 @@ DiscordIntents intents
.AddKeyedSingleton("DSharpPlus.Gateway.EventChannel", Channel.CreateUnbounded<GatewayPayload>(new UnboundedChannelOptions { SingleReader = true }))
.AddTransient<ITransportService, TransportService>()
.AddTransient<IGatewayClient, GatewayClient>()
.AddTransient<PayloadDecompressor>()
.RegisterBestDecompressor()
.AddSingleton<IShardOrchestrator, MultiShardOrchestrator>()
.AddSingleton<IEventDispatcher, DefaultEventDispatcher>()
.AddSingleton<DiscordClient>();
Expand All @@ -140,4 +143,18 @@ DiscordIntents intents

return serviceCollection;
}

private static IServiceCollection RegisterBestDecompressor(this IServiceCollection services)
{
if (NativeLibrary.TryLoad("libzstd", out _))
{
services.AddTransient<IPayloadDecompressor, ZstdDecompressor>();
}
else
{
services.AddTransient<IPayloadDecompressor, ZlibStreamDecompressor>();
}

return services;
}
}
28 changes: 28 additions & 0 deletions DSharpPlus/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Linq;

using DSharpPlus.Net.Gateway.Compression;
using DSharpPlus.Net.Gateway.Compression.Zlib;
using DSharpPlus.Net.Gateway.Compression.Zstd;

using Microsoft.Extensions.DependencyInjection;

namespace DSharpPlus.Extensions;
Expand Down Expand Up @@ -52,6 +56,30 @@ DiscordIntents intents
return services;
}

/// <summary>
/// Forces DSharpPlus to use zlib compression for the gateway.
/// </summary>
/// <param name="services">The service collection to configure this for.</param>
/// <returns>The current instance for chaining.</returns>
public static IServiceCollection UseZlibCompression(this IServiceCollection services)
=> services.Replace<IPayloadDecompressor, ZlibStreamDecompressor>();

/// <summary>
/// Forces DSharpPlus to use zstd compression for the gateway.
/// </summary>
/// <param name="services">The service collection to configure this for.</param>
/// <returns>The current instance for chaining.</returns>
public static IServiceCollection UseZstdCompression(this IServiceCollection services)
=> services.Replace<IPayloadDecompressor, ZstdDecompressor>();

/// <summary>
/// Forces DSharpPlus to disable gateway compression entirely.
/// </summary>
/// <param name="services">The service collection to configure this for.</param>
/// <returns>The current instance for chaining.</returns>
public static IServiceCollection DisableGatewayCompression(this IServiceCollection services)
=> services.Replace<IPayloadDecompressor, NullDecompressor>();

/// <summary>
/// Decorates a given <typeparamref name="TInterface"/> with a decorator of type <typeparamref name="TDecorator"/>.
/// </summary>
Expand Down
23 changes: 0 additions & 23 deletions DSharpPlus/GatewayCompressionLevel.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal sealed class GatewayIdentify
/// <summary>
/// Gets or sets whether to encrypt websocket traffic.
/// </summary>
[JsonProperty("compress")]
[JsonProperty("compress", DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Compress { get; set; }

/// <summary>
Expand Down
39 changes: 39 additions & 0 deletions DSharpPlus/Net/Gateway/Compression/IPayloadDecompressor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;

using CommunityToolkit.HighPerformance.Buffers;

namespace DSharpPlus.Net.Gateway.Compression;

/// <summary>
/// Contains functionality for decompressing inbound gateway payloads.
/// </summary>
public interface IPayloadDecompressor : IDisposable
{
/// <summary>
/// Gets the name of the decompressor.
/// </summary>
public string? Name { get; }

/// <summary>
/// Indicates whether the present compression format is connection-wide.
/// </summary>
public bool IsTransportCompression { get; }

/// <summary>
/// Attempts to decompress the provided payload.
/// </summary>
/// <param name="compressed">The raw, compressed data.</param>
/// <param name="decompressed">A buffer writer for writing decompressed data.</param>
/// <returns>A value indicating whether the operation was successful.</returns>
public bool TryDecompress(ReadOnlySpan<byte> compressed, ArrayPoolBufferWriter<byte> decompressed);

/// <summary>
/// Initializes the decompressor for a new connection.
/// </summary>
public void Initialize();

/// <summary>
/// Frees and destroys all internal state when a connection has terminated.
/// </summary>
public void Reset();
}
44 changes: 44 additions & 0 deletions DSharpPlus/Net/Gateway/Compression/NullDecompressor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;

using CommunityToolkit.HighPerformance;
using CommunityToolkit.HighPerformance.Buffers;

namespace DSharpPlus.Net.Gateway.Compression;

/// <summary>
/// Represents a decompressor that doesn't decompress at all.
/// </summary>
public sealed class NullDecompressor : IPayloadDecompressor
{
/// <inheritdoc/>
public string? Name => null;

// this decompressor *technically* applies transport-wide, and this simplifies composing the IDENTIFY payload.
/// <inheritdoc/>
public bool IsTransportCompression => true;

/// <inheritdoc/>
public bool TryDecompress(ReadOnlySpan<byte> compressed, ArrayPoolBufferWriter<byte> decompressed)
{
decompressed.Write(compressed);
return true;
}

/// <inheritdoc/>
public void Dispose()
{

}

/// <inheritdoc/>
public void Reset()
{

}

/// <inheritdoc/>
public void Initialize()
{

}
}
Loading
Loading