Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket Compression #49304

Merged
merged 47 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
705c171
Prototyped how deflate/inflate should be plugged in.
zlatanov Mar 4, 2021
740798c
Replaced WebSocketReceiveResultGetter abstraction for the receive wit…
zlatanov Mar 4, 2021
72a34fd
Moved ZLibNative and ZLibNative.ZStream to Common so they can be used…
zlatanov Mar 4, 2021
5128c9f
Added compression related implementation.
zlatanov Mar 4, 2021
2825973
Added api changes and implementation to ClientWebSocket to light up d…
zlatanov Mar 5, 2021
67a5d72
Added tests and fixed a few deflate bugs.
zlatanov Mar 5, 2021
9cb024e
Forgot to dispose inflater & deflater.
zlatanov Mar 6, 2021
92cf8f4
Addressing pr feedback.
zlatanov Mar 9, 2021
4045305
Moved deflate state into deflater.
zlatanov Mar 9, 2021
862e163
Moved inflater buffer into the inflater.
zlatanov Mar 9, 2021
92520b6
Using Math.Clamp instead of Math.Min/Max.
zlatanov Mar 14, 2021
4d024fa
Releasing deflater buffer as soon as we're done using it.
zlatanov Mar 14, 2021
b8bdfae
Using 2 constructors in ManagedWebSocket to avoid allocations for Web…
zlatanov Mar 14, 2021
7e8d3c4
Added missing assert.
zlatanov Mar 14, 2021
3869367
Removed unnecessary comments that visual studio inserted from resx fi…
zlatanov Mar 14, 2021
b7f58b1
Better buffer handling in inflater.
zlatanov Mar 15, 2021
c6c3326
Renaming clientBufferLength to userBufferLength.
zlatanov Mar 15, 2021
7ec3151
Fixed an issue in inflater / deflater where with context takeover, th…
zlatanov Mar 26, 2021
271c9cd
Revert removal of BOM.
zlatanov Mar 26, 2021
61c7e5b
PR feedback for ClientWebSocket.
zlatanov Mar 26, 2021
4fa65f4
PR feedback for WebSocket.
zlatanov Mar 26, 2021
9031be3
Prefer Length over IsEmpty for spans.
zlatanov Mar 26, 2021
4e70073
Fixed wrong check.
zlatanov Mar 29, 2021
b7743ef
New API that allows disabling compression per message basis and renam…
zlatanov Mar 31, 2021
49d00eb
Added comment explaining why GetReceiveResult<TResult> will not resul…
zlatanov Mar 31, 2021
58a3338
Exposed DeflateReset and InflateReset methods from zlib.
zlatanov Mar 31, 2021
56fd0ef
Pooling zlib streams.
zlatanov Mar 31, 2021
fdf41d6
Moved min/max deflate window bits consants to WebSocketValidate so th…
zlatanov Apr 8, 2021
4fd18f6
Testing negotiation of deflate options with reflection.
zlatanov Apr 8, 2021
29c539a
Removed the default value for messageFlags parameter.
zlatanov Apr 8, 2021
0739640
pr feedback
zlatanov Apr 8, 2021
224f7ab
Removed cache capacity limit for deflate streams. Now the cache is on…
zlatanov Apr 8, 2021
4a7feb4
Created a test with active issue to track a failing case for the defl…
zlatanov Apr 8, 2021
222c782
Disabled client websocket deflate tests for browser platform.
zlatanov Apr 9, 2021
0b09b5b
Created tests for ZLibStream pooling.
zlatanov Apr 12, 2021
99869bf
Reusing constants in deflate options.
zlatanov Apr 14, 2021
c46a0f3
Fixing tests for browser environment.
zlatanov Apr 14, 2021
3ec748d
Added Block flush code for zlib, because it's needed for websockets.
zlatanov Apr 16, 2021
fb45f2c
Bug fixes after running Autobahn WebSocket Testsuite.
zlatanov Apr 16, 2021
00283e8
Addressing PR feedback.
zlatanov Apr 16, 2021
5a0e435
Fixed a test.
zlatanov Apr 16, 2021
935969a
Addressing flakiness of a couple of tests.
zlatanov Apr 19, 2021
437b7e7
Disallowing the usage of different compression options for continuati…
zlatanov Apr 20, 2021
5edadd9
Removed [ActiveIssue] from websocket deflate tests. Created a new tes…
zlatanov Apr 24, 2021
f6a4b32
Addressing PR feedback.
zlatanov Apr 28, 2021
f0f09f3
Removed custom deflate pool.
zlatanov Apr 28, 2021
ea049ea
Added missing dispose when creating deflater.
zlatanov Apr 28, 2021
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
6 changes: 6 additions & 0 deletions src/libraries/Common/src/Interop/Interop.zlib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ internal static extern ZLibNative.ErrorCode DeflateInit2_(
[DllImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_Deflate")]
internal static extern ZLibNative.ErrorCode Deflate(ref ZLibNative.ZStream stream, ZLibNative.FlushCode flush);

[DllImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_DeflateReset")]
internal static extern ZLibNative.ErrorCode DeflateReset(ref ZLibNative.ZStream stream);

[DllImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_DeflateEnd")]
internal static extern ZLibNative.ErrorCode DeflateEnd(ref ZLibNative.ZStream stream);

Expand All @@ -29,6 +32,9 @@ internal static extern ZLibNative.ErrorCode DeflateInit2_(
[DllImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_Inflate")]
internal static extern ZLibNative.ErrorCode Inflate(ref ZLibNative.ZStream stream, ZLibNative.FlushCode flush);

[DllImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_InflateReset")]
internal static extern ZLibNative.ErrorCode InflateReset(ref ZLibNative.ZStream stream);

[DllImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_InflateEnd")]
internal static extern ZLibNative.ErrorCode InflateEnd(ref ZLibNative.ZStream stream);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public enum FlushCode : int
NoFlush = 0,
SyncFlush = 2,
Finish = 4,
Block = 5
}

public enum ErrorCode : int
Expand Down Expand Up @@ -281,6 +282,13 @@ public ErrorCode Deflate(FlushCode flush)
}


public ErrorCode DeflateReset()
{
EnsureNotDisposed();
EnsureState(State.InitializedForDeflate);
return Interop.zlib.DeflateReset(ref _zStream);
}

public ErrorCode DeflateEnd()
{
EnsureNotDisposed();
Expand Down Expand Up @@ -313,6 +321,13 @@ public ErrorCode Inflate(FlushCode flush)
}


public ErrorCode InflateReset()
{
EnsureNotDisposed();
EnsureState(State.InitializedForInflate);
return Interop.zlib.InflateReset(ref _zStream);
}

public ErrorCode InflateEnd()
{
EnsureNotDisposed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ namespace System.Net.WebSockets
{
internal static partial class WebSocketValidate
{
/// <summary>
/// The minimum value for window bits that the websocket per-message-deflate extension can support.<para />
/// For the current implementation of deflate(), a windowBits value of 8 (a window size of 256 bytes) is not supported.
/// We cannot use silently 9 instead of 8, because the websocket produces raw deflate stream
/// and thus it needs to know the window bits in advance.
/// </summary>
internal const int MinDeflateWindowBits = 9;

/// <summary>
/// The maximum value for window bits that the websocket per-message-deflate extension can support.
/// </summary>
internal const int MaxDeflateWindowBits = 15;

internal const int MaxControlFramePayloadLength = 123;
private const int CloseStatusCodeAbort = 1006;
private const int CloseStatusCodeFailedTLSHandshake = 1015;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ static const Entry s_compressionNative[] =
DllImportEntry(CompressionNative_Crc32)
DllImportEntry(CompressionNative_Deflate)
DllImportEntry(CompressionNative_DeflateEnd)
DllImportEntry(CompressionNative_DeflateReset)
DllImportEntry(CompressionNative_DeflateInit2_)
DllImportEntry(CompressionNative_Inflate)
DllImportEntry(CompressionNative_InflateEnd)
DllImportEntry(CompressionNative_InflateReset)
DllImportEntry(CompressionNative_InflateInit2_)
};

Expand Down
22 changes: 22 additions & 0 deletions src/libraries/Native/AnyOS/zlib/pal_zlib.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ int32_t CompressionNative_Deflate(PAL_ZStream* stream, int32_t flush)
return result;
}

int32_t CompressionNative_DeflateReset(PAL_ZStream* stream)
{
assert(stream != NULL);

z_stream* zStream = GetCurrentZStream(stream);
int32_t result = deflateReset(zStream);
TransferStateToPalZStream(zStream, stream);

return result;
}

int32_t CompressionNative_DeflateEnd(PAL_ZStream* stream)
{
assert(stream != NULL);
Expand Down Expand Up @@ -172,6 +183,17 @@ int32_t CompressionNative_Inflate(PAL_ZStream* stream, int32_t flush)
return result;
}

int32_t CompressionNative_InflateReset(PAL_ZStream* stream)
{
assert(stream != NULL);

z_stream* zStream = GetCurrentZStream(stream);
int32_t result = inflateReset(zStream);
TransferStateToPalZStream(zStream, stream);

return result;
}

int32_t CompressionNative_InflateEnd(PAL_ZStream* stream)
{
assert(stream != NULL);
Expand Down
16 changes: 16 additions & 0 deletions src/libraries/Native/AnyOS/zlib/pal_zlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ Returns a PAL_ErrorCode indicating success or an error number on failure.
*/
FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENCTION CompressionNative_Deflate(PAL_ZStream* stream, int32_t flush);

/*
This function is equivalent to DeflateEnd followed by DeflateInit, but does not free and reallocate
the internal compression state. The stream will leave the compression level and any other attributes that may have been set unchanged.

Returns a PAL_ErrorCode indicating success or an error number on failure.
*/
FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENCTION CompressionNative_DeflateReset(PAL_ZStream* stream);

/*
All dynamically allocated data structures for this stream are freed.

Expand All @@ -117,6 +125,14 @@ Returns a PAL_ErrorCode indicating success or an error number on failure.
*/
FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENCTION CompressionNative_Inflate(PAL_ZStream* stream, int32_t flush);

/*
This function is equivalent to InflateEnd followed by InflateInit, but does not free and reallocate
the internal decompression state. The The stream will keep attributes that may have been set by InflateInit.

Returns a PAL_ErrorCode indicating success or an error number on failure.
*/
FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENCTION CompressionNative_InflateReset(PAL_ZStream* stream);

/*
All dynamically allocated data structures for this stream are freed.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ BrotliEncoderSetParameter
CompressionNative_Crc32
CompressionNative_Deflate
CompressionNative_DeflateEnd
CompressionNative_DeflateReset
CompressionNative_DeflateInit2_
CompressionNative_Inflate
CompressionNative_InflateEnd
CompressionNative_InflateReset
CompressionNative_InflateInit2_
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ EXPORTS
CompressionNative_Crc32
CompressionNative_Deflate
CompressionNative_DeflateEnd
CompressionNative_DeflateReset
CompressionNative_DeflateInit2_
CompressionNative_Inflate
CompressionNative_InflateEnd
CompressionNative_InflateReset
CompressionNative_InflateInit2_
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
<Compile Include="System\IO\Compression\DeflateZLib\DeflateStream.cs" />
<Compile Include="System\IO\Compression\DeflateZLib\Inflater.cs" />
<Compile Include="System\IO\Compression\DeflateZLib\ZLibException.cs" />
<Compile Include="System\IO\Compression\DeflateZLib\ZLibNative.cs" />
<Compile Include="System\IO\Compression\DeflateZLib\ZLibNative.ZStream.cs" />
<Compile Include="$(CommonPath)System\IO\Compression\ZLibNative.cs"
Link="Common\System\IO\Compression\ZLibNative.cs" />
<Compile Include="$(CommonPath)System\IO\Compression\ZLibNative.ZStream.cs"
Link="Common\System\IO\Compression\ZLibNative.ZStream.cs" />
zlatanov marked this conversation as resolved.
Show resolved Hide resolved
<Compile Include="System\IO\Compression\CompressionLevel.cs" />
<Compile Include="System\IO\Compression\CompressionMode.cs" />
<Compile Include="System\IO\Compression\Crc32Helper.ZLib.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ internal ClientWebSocketOptions() { }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public System.TimeSpan KeepAliveInterval { get { throw null; } set { } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public System.Net.WebSockets.WebSocketDeflateOptions? DangerousDeflateOptions { get { throw null; } set { } }
zlatanov marked this conversation as resolved.
Show resolved Hide resolved
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public System.Net.IWebProxy? Proxy { get { throw null; } set { } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public System.Net.Security.RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get { throw null; } set { } }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,4 @@
<root>
<!--
Microsoft ResX Schema

Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
Expand Down Expand Up @@ -193,8 +134,14 @@
</data>
<data name="net_WebSockets_Connection_Aborted" xml:space="preserve">
<value>Connection was aborted.</value>
</data>
</data>
<data name="net_WebSockets_Invalid_Binary_Type" xml:space="preserve">
<value>WebSocket binary type '{0}' not supported.</value>
</data>
</root>
</data>
<data name="net_WebSockets_ServerWindowBitsNegotiationFailure" xml:space="preserve">
<value>The WebSocket failed to negotiate max server window bits. The client requested {0} but the server responded with {1}.</value>
</data>
<data name="net_WebSockets_ClientWindowBitsNegotiationFailure" xml:space="preserve">
<value>The WebSocket failed to negotiate max client window bits. The client requested {0} but the server responded with {1}.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="System\Net\WebSockets\ClientWebSocket.cs" />
<Compile Include="System\Net\WebSockets\ClientWebSocketDeflateConstants.cs" />
<Compile Include="System\Net\WebSockets\ClientWebSocketOptions.cs" Condition="'$(TargetsBrowser)' != 'true'" />
<Compile Include="$(CommonPath)System\Net\UriScheme.cs" Link="Common\System\Net\UriScheme.cs" />
<Compile Include="$(CommonPath)System\Net\WebSockets\WebSocketValidate.cs" Link="Common\System\Net\WebSockets\WebSocketValidate.cs" />
Expand Down Expand Up @@ -37,6 +38,7 @@
<Reference Include="System.Security.Cryptography.Algorithms" />
<Reference Include="System.Security.Cryptography.Primitives" />
<Reference Include="System.Threading.Channels" Condition="'$(TargetsBrowser)' == 'true'" />
<Reference Include="System.Memory" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)System.Private.Runtime.InteropServices.JavaScript\src\System.Private.Runtime.InteropServices.JavaScript.csproj" Condition="'$(TargetsBrowser)' == 'true'" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ public TimeSpan KeepAliveInterval
set => throw new PlatformNotSupportedException();
}

[UnsupportedOSPlatform("browser")]
public WebSocketDeflateOptions? DangerousDeflateOptions
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

[UnsupportedOSPlatform("browser")]
public void SetBuffer(int receiveBufferSize, int sendBufferSize)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Net.WebSockets
{
internal static class ClientWebSocketDeflateConstants
{
/// <summary>
/// The maximum length that this extension can have, assuming that we're not abusing white space.
/// <para />
/// "permessage-deflate; client_max_window_bits=15; client_no_context_takeover; server_max_window_bits=15; server_no_context_takeover"
/// </summary>
public const int MaxExtensionLength = 128;

public const string Extension = "permessage-deflate";

public const string ClientMaxWindowBits = "client_max_window_bits";
public const string ClientNoContextTakeover = "client_no_context_takeover";

public const string ServerMaxWindowBits = "server_max_window_bits";
public const string ServerNoContextTakeover = "server_no_context_takeover";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ public TimeSpan KeepAliveInterval
}
}

/// <summary>
/// Gets or sets the options for the per-message-deflate extension.
/// When present, the options are sent to the server during the handshake phase. If the server
/// supports per-message-deflate and the options are accepted, the <see cref="WebSocket"/> instance
/// will be created with compression enabled by default for all messages.<para />
/// Be aware that enabling compression makes the application subject to CRIME/BREACH type of attacks.
/// It is strongly advised to turn off compression when sending data containing secrets by
/// specifying <see cref="WebSocketMessageFlags.DisableCompression" /> flag for such messages.
/// </summary>
zlatanov marked this conversation as resolved.
Show resolved Hide resolved
[UnsupportedOSPlatform("browser")]
public WebSocketDeflateOptions? DangerousDeflateOptions { get; set; }

internal int ReceiveBufferSize => _receiveBufferSize;
internal ArraySegment<byte>? Buffer => _buffer;

Expand Down
Loading