From d66537cba75746030658030971831a2e99445d8b Mon Sep 17 00:00:00 2001 From: Katya Sokolova Date: Thu, 8 Dec 2022 20:19:56 +0100 Subject: [PATCH 1/6] Fix compression --- .../System/Net/WebSockets/ClientWebSocket.cs | 3 ++ .../tests/DeflateTests.cs | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs index 487aab5594d813..4e55cd9575b8b4 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/ClientWebSocket.cs @@ -135,6 +135,9 @@ public override Task SendAsync(ArraySegment buffer, WebSocketMessageType m public override ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => ConnectedWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); + public override ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken) => + ConnectedWebSocket.SendAsync(buffer, messageType, messageFlags, cancellationToken); + public override Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) => ConnectedWebSocket.ReceiveAsync(buffer, cancellationToken); diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 9836f31df16cb7..6af22fbccd2452 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -87,6 +87,53 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => }), new LoopbackServer.Options { WebSocketEndpoint = true }); } + [ConditionalFact(nameof(WebSocketsSupported))] + public async Task ThrowWhenContinuationWithDifferentCompressionFlags() + { + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using (var cws = new ClientWebSocket()) + using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) + { + cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); + await ConnectAsync(cws, uri, cts.Token); + + await cws.SendAsync(Memory.Empty, WebSocketMessageType.Text, WebSocketMessageFlags.DisableCompression, default); + Assert.Throws("messageFlags", () => + cws.SendAsync(Memory.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default)); + } + }, server => server.AcceptConnectionAsync(async connection => + { + Dictionary headers = await LoopbackHelper.WebSocketHandshakeAsync(connection); + }), new LoopbackServer.Options { WebSocketEndpoint = true }); + } + + [ConditionalFact(nameof(WebSocketsSupported))] + public async Task SendHelloWithDisableCompression() + { + byte[] bytes = "Hello"u8.ToArray(); + await LoopbackServer.CreateClientAndServerAsync(async uri => + { + using (var cws = new ClientWebSocket()) + using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) + { + cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); + await ConnectAsync(cws, uri, cts.Token); + + WebSocketMessageFlags flags = WebSocketMessageFlags.DisableCompression | WebSocketMessageFlags.EndOfMessage; + await cws.SendAsync(bytes, WebSocketMessageType.Text, flags, cts.Token); + } + }, server => server.AcceptConnectionAsync(async connection => + { + var buffer = new byte[bytes.Length]; + Dictionary headers = await LoopbackHelper.WebSocketHandshakeAsync(connection); + using WebSocket websocket = WebSocket.CreateFromStream(connection.Stream, true, null, TimeSpan.FromSeconds(30)); + Assert.True(websocket.State == WebSocketState.Open || websocket.State == WebSocketState.CloseSent); + await websocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + Assert.Equal(bytes, buffer); + }), new LoopbackServer.Options { WebSocketEndpoint = true }); + } + private static string CreateDeflateOptionsHeader(WebSocketDeflateOptions options) { var builder = new StringBuilder(); From 84d3b4a0e64bdbf67f1b45d43462a160214bf3f6 Mon Sep 17 00:00:00 2001 From: Katya Sokolova Date: Fri, 9 Dec 2022 13:21:09 +0100 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Miha Zupan --- .../tests/DeflateTests.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 6af22fbccd2452..7ecde817db726e 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -88,23 +88,22 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => } [ConditionalFact(nameof(WebSocketsSupported))] - public async Task ThrowWhenContinuationWithDifferentCompressionFlags() + public async Task ThrowsWhenContinuationHasDifferentCompressionFlags() { await LoopbackServer.CreateClientAndServerAsync(async uri => { - using (var cws = new ClientWebSocket()) - using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) - { - cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); - await ConnectAsync(cws, uri, cts.Token); + using var cws = new ClientWebSocket(); + using var cts = new CancellationTokenSource(TimeOutMilliseconds); + + cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); + await ConnectAsync(cws, uri, cts.Token); - await cws.SendAsync(Memory.Empty, WebSocketMessageType.Text, WebSocketMessageFlags.DisableCompression, default); - Assert.Throws("messageFlags", () => - cws.SendAsync(Memory.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default)); - } + await cws.SendAsync(Memory.Empty, WebSocketMessageType.Text, WebSocketMessageFlags.DisableCompression, default); + Assert.Throws("messageFlags", () => + cws.SendAsync(Memory.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default)); }, server => server.AcceptConnectionAsync(async connection => { - Dictionary headers = await LoopbackHelper.WebSocketHandshakeAsync(connection); + await LoopbackHelper.WebSocketHandshakeAsync(connection); }), new LoopbackServer.Options { WebSocketEndpoint = true }); } @@ -114,19 +113,18 @@ public async Task SendHelloWithDisableCompression() byte[] bytes = "Hello"u8.ToArray(); await LoopbackServer.CreateClientAndServerAsync(async uri => { - using (var cws = new ClientWebSocket()) - using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) - { - cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); - await ConnectAsync(cws, uri, cts.Token); + using var cws = new ClientWebSocket(); + using var cts = new CancellationTokenSource(TimeOutMilliseconds); + + cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); + await ConnectAsync(cws, uri, cts.Token); - WebSocketMessageFlags flags = WebSocketMessageFlags.DisableCompression | WebSocketMessageFlags.EndOfMessage; - await cws.SendAsync(bytes, WebSocketMessageType.Text, flags, cts.Token); - } + WebSocketMessageFlags flags = WebSocketMessageFlags.DisableCompression | WebSocketMessageFlags.EndOfMessage; + await cws.SendAsync(bytes, WebSocketMessageType.Text, flags, cts.Token); }, server => server.AcceptConnectionAsync(async connection => { var buffer = new byte[bytes.Length]; - Dictionary headers = await LoopbackHelper.WebSocketHandshakeAsync(connection); + await LoopbackHelper.WebSocketHandshakeAsync(connection); using WebSocket websocket = WebSocket.CreateFromStream(connection.Stream, true, null, TimeSpan.FromSeconds(30)); Assert.True(websocket.State == WebSocketState.Open || websocket.State == WebSocketState.CloseSent); await websocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); From 12033d4cf773fc94c3913f474d6bdff46712a503 Mon Sep 17 00:00:00 2001 From: Katya Sokolova Date: Fri, 9 Dec 2022 13:24:30 +0100 Subject: [PATCH 3/6] Adding SendAsync to ref --- .../ref/System.Net.WebSockets.Client.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs b/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs index d7b7a97d724950..edb4eb043bcb0b 100644 --- a/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs +++ b/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs @@ -26,6 +26,7 @@ public override void Dispose() { } public override System.Threading.Tasks.ValueTask ReceiveAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.Task SendAsync(System.ArraySegment buffer, System.Net.WebSockets.WebSocketMessageType messageType, bool endOfMessage, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask SendAsync(System.ReadOnlyMemory buffer, System.Net.WebSockets.WebSocketMessageType messageType, bool endOfMessage, System.Threading.CancellationToken cancellationToken) { throw null; } + public override System.Threading.Tasks.ValueTask SendAsync(System.ReadOnlyMemory buffer, System.Net.WebSockets.WebSocketMessageType messageType, System.Net.WebSockets.WebSocketMessageFlags messageFlags, System.Threading.CancellationToken cancellationToken) { throw null; } } public sealed partial class ClientWebSocketOptions { From 39d4d4a7f469583ffe092f2baedba57c296cf1ac Mon Sep 17 00:00:00 2001 From: Katya Sokolova Date: Fri, 9 Dec 2022 15:02:07 +0100 Subject: [PATCH 4/6] fix ws deflate tests --- .../tests/DeflateTests.cs | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 7ecde817db726e..0e05169395ce72 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -90,20 +90,29 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => [ConditionalFact(nameof(WebSocketsSupported))] public async Task ThrowsWhenContinuationHasDifferentCompressionFlags() { + var deflateOpt = new WebSocketDeflateOptions + { + ClientMaxWindowBits = 14, + ClientContextTakeover = true, + ServerMaxWindowBits = 14, + ServerContextTakeover = true + }; await LoopbackServer.CreateClientAndServerAsync(async uri => { using var cws = new ClientWebSocket(); using var cts = new CancellationTokenSource(TimeOutMilliseconds); - cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); + cws.Options.DangerousDeflateOptions = deflateOpt; await ConnectAsync(cws, uri, cts.Token); + await cws.SendAsync(Memory.Empty, WebSocketMessageType.Text, WebSocketMessageFlags.DisableCompression, default); Assert.Throws("messageFlags", () => cws.SendAsync(Memory.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default)); }, server => server.AcceptConnectionAsync(async connection => { - await LoopbackHelper.WebSocketHandshakeAsync(connection); + var extensionsReply = CreateDeflateOptionsHeader(deflateOpt); + await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); }), new LoopbackServer.Options { WebSocketEndpoint = true }); } @@ -111,22 +120,43 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => public async Task SendHelloWithDisableCompression() { byte[] bytes = "Hello"u8.ToArray(); + byte[] compressed = new byte[] { 0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00 }; + var deflateOpt = new WebSocketDeflateOptions + { + ClientMaxWindowBits = 14, + ClientContextTakeover = true, + ServerMaxWindowBits = 14, + ServerContextTakeover = true + }; + await LoopbackServer.CreateClientAndServerAsync(async uri => { using var cws = new ClientWebSocket(); using var cts = new CancellationTokenSource(TimeOutMilliseconds); - cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions(); + cws.Options.DangerousDeflateOptions = deflateOpt; await ConnectAsync(cws, uri, cts.Token); + await cws.SendAsync(bytes, WebSocketMessageType.Text, true, cts.Token); + WebSocketMessageFlags flags = WebSocketMessageFlags.DisableCompression | WebSocketMessageFlags.EndOfMessage; await cws.SendAsync(bytes, WebSocketMessageType.Text, flags, cts.Token); }, server => server.AcceptConnectionAsync(async connection => { var buffer = new byte[bytes.Length]; - await LoopbackHelper.WebSocketHandshakeAsync(connection); - using WebSocket websocket = WebSocket.CreateFromStream(connection.Stream, true, null, TimeSpan.FromSeconds(30)); + var extensionsReply = CreateDeflateOptionsHeader(deflateOpt); + await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); + using WebSocket websocket = WebSocket.CreateFromStream(connection.Stream, new WebSocketCreationOptions + { + IsServer = true, + DangerousDeflateOptions = deflateOpt + }); + Assert.True(websocket.State == WebSocketState.Open || websocket.State == WebSocketState.CloseSent); + + await websocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + Assert.Equal(bytes, buffer); + await websocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); Assert.Equal(bytes, buffer); }), new LoopbackServer.Options { WebSocketEndpoint = true }); From e3608f6274e6780cbd60bbefca4f5205d6e04e73 Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Mon, 12 Dec 2022 13:26:10 +0000 Subject: [PATCH 5/6] Check bytes on server side --- .../tests/DeflateTests.cs | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 0e05169395ce72..203f3dfe07a39b 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -120,7 +120,13 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => public async Task SendHelloWithDisableCompression() { byte[] bytes = "Hello"u8.ToArray(); - byte[] compressed = new byte[] { 0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00 }; + + int prefixLength = 2; + byte[] rawPrefix = new byte[] { 0x81, 0x85 }; // fin=1, rsv=0, opcode=text; mask=1, len=5 + int rawRemainingBytes = 9; // mask bytes (4) + payload bytes (5) + byte[] compressedPrefix = new byte[] { 0xc1, 0x87 }; // fin=1, rsv=compressed, opcode=text; mask=1, len=7 + int compressedRemainingBytes = 11; // mask bytes (4) + payload bytes (7) + var deflateOpt = new WebSocketDeflateOptions { ClientMaxWindowBits = 14, @@ -143,22 +149,26 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => await cws.SendAsync(bytes, WebSocketMessageType.Text, flags, cts.Token); }, server => server.AcceptConnectionAsync(async connection => { - var buffer = new byte[bytes.Length]; + var buffer = new byte[compressedRemainingBytes]; var extensionsReply = CreateDeflateOptionsHeader(deflateOpt); await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); - using WebSocket websocket = WebSocket.CreateFromStream(connection.Stream, new WebSocketCreationOptions - { - IsServer = true, - DangerousDeflateOptions = deflateOpt - }); - Assert.True(websocket.State == WebSocketState.Open || websocket.State == WebSocketState.CloseSent); + // first message is compressed + await ReadExactAsync(buffer, prefixLength); + Assert.Equal(compressedPrefix, buffer[..prefixLength]); + // read rest of the frame + await ReadExactAsync(buffer, compressedRemainingBytes); - await websocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - Assert.Equal(bytes, buffer); + // second message is not compressed + await ReadExactAsync(buffer, prefixLength); + Assert.Equal(rawPrefix, buffer[..prefixLength]); + // read rest of the frame + await ReadExactAsync(buffer, rawRemainingBytes); - await websocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - Assert.Equal(bytes, buffer); + async Task ReadExactAsync(byte[] buf, int n) + { + await connection.Stream.ReadAtLeastAsync(buf.AsMemory(0, n), n); + } }), new LoopbackServer.Options { WebSocketEndpoint = true }); } From 04b917419553fe4793f298dc2e15e103d16ef75c Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Wed, 4 Jan 2023 19:26:58 +0000 Subject: [PATCH 6/6] Remove ref assembly change, pr feedback --- .../ref/System.Net.WebSockets.Client.cs | 1 - .../System.Net.WebSockets.Client/tests/DeflateTests.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs b/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs index edb4eb043bcb0b..d7b7a97d724950 100644 --- a/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs +++ b/src/libraries/System.Net.WebSockets.Client/ref/System.Net.WebSockets.Client.cs @@ -26,7 +26,6 @@ public override void Dispose() { } public override System.Threading.Tasks.ValueTask ReceiveAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.Task SendAsync(System.ArraySegment buffer, System.Net.WebSockets.WebSocketMessageType messageType, bool endOfMessage, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask SendAsync(System.ReadOnlyMemory buffer, System.Net.WebSockets.WebSocketMessageType messageType, bool endOfMessage, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask SendAsync(System.ReadOnlyMemory buffer, System.Net.WebSockets.WebSocketMessageType messageType, System.Net.WebSockets.WebSocketMessageFlags messageFlags, System.Threading.CancellationToken cancellationToken) { throw null; } } public sealed partial class ClientWebSocketOptions { diff --git a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs index 203f3dfe07a39b..158aa0983a01b1 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs @@ -111,7 +111,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => cws.SendAsync(Memory.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default)); }, server => server.AcceptConnectionAsync(async connection => { - var extensionsReply = CreateDeflateOptionsHeader(deflateOpt); + string extensionsReply = CreateDeflateOptionsHeader(deflateOpt); await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); }), new LoopbackServer.Options { WebSocketEndpoint = true }); } @@ -150,7 +150,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => }, server => server.AcceptConnectionAsync(async connection => { var buffer = new byte[compressedRemainingBytes]; - var extensionsReply = CreateDeflateOptionsHeader(deflateOpt); + string extensionsReply = CreateDeflateOptionsHeader(deflateOpt); await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply); // first message is compressed