Skip to content

Commit

Permalink
WebSocket Compression (#49304)
Browse files Browse the repository at this point in the history
Adds support for RFC 7692 "compression extensions for websocket".

Closes #31088
  • Loading branch information
zlatanov authored Apr 28, 2021
1 parent 636988a commit 2c3eb64
Show file tree
Hide file tree
Showing 35 changed files with 2,309 additions and 187 deletions.
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" />
<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 { } }
[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>
[UnsupportedOSPlatform("browser")]
public WebSocketDeflateOptions? DangerousDeflateOptions { get; set; }

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

Expand Down
Loading

0 comments on commit 2c3eb64

Please sign in to comment.