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

[release/7.0] Fix compression #79547

Merged
merged 6 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType m
public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) =>
ConnectedWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken);

public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken) =>
ConnectedWebSocket.SendAsync(buffer, messageType, messageFlags, cancellationToken);

public override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken) =>
ConnectedWebSocket.ReceiveAsync(buffer, cancellationToken);

Expand Down
85 changes: 85 additions & 0 deletions src/libraries/System.Net.WebSockets.Client/tests/DeflateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,91 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
}), new LoopbackServer.Options { WebSocketEndpoint = true });
}

[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 = deflateOpt;
await ConnectAsync(cws, uri, cts.Token);


await cws.SendAsync(Memory<byte>.Empty, WebSocketMessageType.Text, WebSocketMessageFlags.DisableCompression, default);
Assert.Throws<ArgumentException>("messageFlags", () =>
cws.SendAsync(Memory<byte>.Empty, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, default));
}, server => server.AcceptConnectionAsync(async connection =>
{
string extensionsReply = CreateDeflateOptionsHeader(deflateOpt);
await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply);
}), new LoopbackServer.Options { WebSocketEndpoint = true });
}

[ConditionalFact(nameof(WebSocketsSupported))]
public async Task SendHelloWithDisableCompression()
{
byte[] bytes = "Hello"u8.ToArray();

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,
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 = 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[compressedRemainingBytes];
string extensionsReply = CreateDeflateOptionsHeader(deflateOpt);
await LoopbackHelper.WebSocketHandshakeAsync(connection, extensionsReply);

// first message is compressed
await ReadExactAsync(buffer, prefixLength);
Assert.Equal(compressedPrefix, buffer[..prefixLength]);
// read rest of the frame
await ReadExactAsync(buffer, compressedRemainingBytes);

// second message is not compressed
await ReadExactAsync(buffer, prefixLength);
Assert.Equal(rawPrefix, buffer[..prefixLength]);
// read rest of the frame
await ReadExactAsync(buffer, rawRemainingBytes);

async Task ReadExactAsync(byte[] buf, int n)
{
await connection.Stream.ReadAtLeastAsync(buf.AsMemory(0, n), n);
}
}), new LoopbackServer.Options { WebSocketEndpoint = true });
}

private static string CreateDeflateOptionsHeader(WebSocketDeflateOptions options)
{
var builder = new StringBuilder();
Expand Down