parent
0dd7477501
commit
9f0edefd16
|
|
@ -14,157 +14,154 @@ namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
{
|
{
|
||||||
public partial class WebSocketConnectionTests
|
public partial class WebSocketConnectionTests
|
||||||
{
|
{
|
||||||
public class ConnectionLifecycle
|
[Fact]
|
||||||
|
public async Task SendReceiveFrames()
|
||||||
{
|
{
|
||||||
[Fact]
|
using (var pair = WebSocketPair.Create())
|
||||||
public async Task SendReceiveFrames()
|
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
var client = pair.ClientSocket.ExecuteAsync(_ =>
|
||||||
{
|
{
|
||||||
var client = pair.ClientSocket.ExecuteAsync(_ =>
|
Assert.False(true, "did not expect the client to receive any frames!");
|
||||||
{
|
return TaskCache.CompletedTask;
|
||||||
Assert.False(true, "did not expect the client to receive any frames!");
|
});
|
||||||
return TaskCache.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send Frames
|
// Send Frames
|
||||||
await pair.ClientSocket.SendAsync(CreateTextFrame("Hello")).OrTimeout();
|
await pair.ClientSocket.SendAsync(CreateTextFrame("Hello")).OrTimeout();
|
||||||
await pair.ClientSocket.SendAsync(CreateTextFrame("World")).OrTimeout();
|
await pair.ClientSocket.SendAsync(CreateTextFrame("World")).OrTimeout();
|
||||||
await pair.ClientSocket.SendAsync(CreateBinaryFrame(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF })).OrTimeout();
|
await pair.ClientSocket.SendAsync(CreateBinaryFrame(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF })).OrTimeout();
|
||||||
await pair.ClientSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
await pair.ClientSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
||||||
|
|
||||||
var summary = await pair.ServerSocket.ExecuteAndCaptureFramesAsync().OrTimeout();
|
var summary = await pair.ServerSocket.ExecuteAndCaptureFramesAsync().OrTimeout();
|
||||||
Assert.Equal(3, summary.Received.Count);
|
Assert.Equal(3, summary.Received.Count);
|
||||||
Assert.Equal("Hello", Encoding.UTF8.GetString(summary.Received[0].Payload.ToArray()));
|
Assert.Equal("Hello", Encoding.UTF8.GetString(summary.Received[0].Payload.ToArray()));
|
||||||
Assert.Equal("World", Encoding.UTF8.GetString(summary.Received[1].Payload.ToArray()));
|
Assert.Equal("World", Encoding.UTF8.GetString(summary.Received[1].Payload.ToArray()));
|
||||||
Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }, summary.Received[2].Payload.ToArray());
|
Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }, summary.Received[2].Payload.ToArray());
|
||||||
|
|
||||||
await pair.ServerSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
await pair.ServerSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
||||||
await client.OrTimeout();
|
await client.OrTimeout();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteReturnsWhenCloseFrameReceived()
|
public async Task ExecuteReturnsWhenCloseFrameReceived()
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
{
|
await pair.ClientSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.InvalidMessageType, "Abc")).OrTimeout();
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
var serverSummary = await pair.ServerSocket.ExecuteAndCaptureFramesAsync().OrTimeout();
|
||||||
await pair.ClientSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.InvalidMessageType, "Abc")).OrTimeout();
|
await pair.ServerSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure, "Ok")).OrTimeout();
|
||||||
var serverSummary = await pair.ServerSocket.ExecuteAndCaptureFramesAsync().OrTimeout();
|
var clientSummary = await client.OrTimeout();
|
||||||
await pair.ServerSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure, "Ok")).OrTimeout();
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(0, serverSummary.Received.Count);
|
Assert.Equal(0, serverSummary.Received.Count);
|
||||||
Assert.Equal(WebSocketCloseStatus.InvalidMessageType, serverSummary.CloseResult.Status);
|
Assert.Equal(WebSocketCloseStatus.InvalidMessageType, serverSummary.CloseResult.Status);
|
||||||
Assert.Equal("Abc", serverSummary.CloseResult.Description);
|
Assert.Equal("Abc", serverSummary.CloseResult.Description);
|
||||||
|
|
||||||
Assert.Equal(0, clientSummary.Received.Count);
|
Assert.Equal(0, clientSummary.Received.Count);
|
||||||
Assert.Equal(WebSocketCloseStatus.NormalClosure, clientSummary.CloseResult.Status);
|
Assert.Equal(WebSocketCloseStatus.NormalClosure, clientSummary.CloseResult.Status);
|
||||||
Assert.Equal("Ok", clientSummary.CloseResult.Description);
|
Assert.Equal("Ok", clientSummary.CloseResult.Description);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AbnormalTerminationOfInboundChannelCausesExecuteToThrow()
|
public async Task AbnormalTerminationOfInboundChannelCausesExecuteToThrow()
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
{
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
pair.TerminateFromClient(new InvalidOperationException("It broke!"));
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
pair.TerminateFromClient(new InvalidOperationException("It broke!"));
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(() => server);
|
await Assert.ThrowsAsync<InvalidOperationException>(() => server);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StateTransitions()
|
public async Task StateTransitions()
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
// Initial State
|
||||||
{
|
Assert.Equal(WebSocketConnectionState.Created, pair.ServerSocket.State);
|
||||||
// Initial State
|
Assert.Equal(WebSocketConnectionState.Created, pair.ClientSocket.State);
|
||||||
Assert.Equal(WebSocketConnectionState.Created, pair.ServerSocket.State);
|
|
||||||
Assert.Equal(WebSocketConnectionState.Created, pair.ClientSocket.State);
|
|
||||||
|
|
||||||
// Start the sockets
|
// Start the sockets
|
||||||
var serverReceiving = new TaskCompletionSource<object>();
|
var serverReceiving = new TaskCompletionSource<object>();
|
||||||
var clientReceiving = new TaskCompletionSource<object>();
|
var clientReceiving = new TaskCompletionSource<object>();
|
||||||
var server = pair.ServerSocket.ExecuteAsync(frame => serverReceiving.TrySetResult(null));
|
var server = pair.ServerSocket.ExecuteAsync(frame => serverReceiving.TrySetResult(null));
|
||||||
var client = pair.ClientSocket.ExecuteAsync(frame => clientReceiving.TrySetResult(null));
|
var client = pair.ClientSocket.ExecuteAsync(frame => clientReceiving.TrySetResult(null));
|
||||||
|
|
||||||
// Send a frame from each and verify that the state transitioned.
|
// Send a frame from each and verify that the state transitioned.
|
||||||
// We need to do this because it's the only way to correctly wait for the state transition (which happens asynchronously in ExecuteAsync)
|
// We need to do this because it's the only way to correctly wait for the state transition (which happens asynchronously in ExecuteAsync)
|
||||||
await pair.ClientSocket.SendAsync(CreateTextFrame("Hello")).OrTimeout();
|
await pair.ClientSocket.SendAsync(CreateTextFrame("Hello")).OrTimeout();
|
||||||
await pair.ServerSocket.SendAsync(CreateTextFrame("Hello")).OrTimeout();
|
await pair.ServerSocket.SendAsync(CreateTextFrame("Hello")).OrTimeout();
|
||||||
|
|
||||||
await Task.WhenAll(serverReceiving.Task, clientReceiving.Task).OrTimeout();
|
await Task.WhenAll(serverReceiving.Task, clientReceiving.Task).OrTimeout();
|
||||||
|
|
||||||
// Check state
|
// Check state
|
||||||
Assert.Equal(WebSocketConnectionState.Connected, pair.ServerSocket.State);
|
Assert.Equal(WebSocketConnectionState.Connected, pair.ServerSocket.State);
|
||||||
Assert.Equal(WebSocketConnectionState.Connected, pair.ClientSocket.State);
|
Assert.Equal(WebSocketConnectionState.Connected, pair.ClientSocket.State);
|
||||||
|
|
||||||
// Close the server socket
|
// Close the server socket
|
||||||
await pair.ServerSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
await pair.ServerSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
||||||
await client.OrTimeout();
|
await client.OrTimeout();
|
||||||
|
|
||||||
// Check state
|
// Check state
|
||||||
Assert.Equal(WebSocketConnectionState.CloseSent, pair.ServerSocket.State);
|
Assert.Equal(WebSocketConnectionState.CloseSent, pair.ServerSocket.State);
|
||||||
Assert.Equal(WebSocketConnectionState.CloseReceived, pair.ClientSocket.State);
|
Assert.Equal(WebSocketConnectionState.CloseReceived, pair.ClientSocket.State);
|
||||||
|
|
||||||
// Close the client socket
|
// Close the client socket
|
||||||
await pair.ClientSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
await pair.ClientSocket.CloseAsync(new WebSocketCloseResult(WebSocketCloseStatus.NormalClosure)).OrTimeout();
|
||||||
await server.OrTimeout();
|
await server.OrTimeout();
|
||||||
|
|
||||||
// Check state
|
// Check state
|
||||||
Assert.Equal(WebSocketConnectionState.Closed, pair.ServerSocket.State);
|
Assert.Equal(WebSocketConnectionState.Closed, pair.ServerSocket.State);
|
||||||
Assert.Equal(WebSocketConnectionState.Closed, pair.ClientSocket.State);
|
Assert.Equal(WebSocketConnectionState.Closed, pair.ClientSocket.State);
|
||||||
|
|
||||||
// Verify we can't restart the connection or send a message
|
// Verify we can't restart the connection or send a message
|
||||||
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await pair.ServerSocket.ExecuteAsync(f => { }));
|
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await pair.ServerSocket.ExecuteAsync(f => { }));
|
||||||
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await pair.ClientSocket.SendAsync(CreateTextFrame("Nope")));
|
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await pair.ClientSocket.SendAsync(CreateTextFrame("Nope")));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanReceiveControlFrameInTheMiddleOfFragmentedMessage()
|
public async Task CanReceiveControlFrameInTheMiddleOfFragmentedMessage()
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
// Start the sockets
|
||||||
{
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
// Start the sockets
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
// Send (Fin=false, "Hello"), (Ping), (Fin=true, "World")
|
// Send (Fin=false, "Hello"), (Ping), (Fin=true, "World")
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
endOfMessage: false,
|
endOfMessage: false,
|
||||||
opcode: WebSocketOpcode.Text,
|
opcode: WebSocketOpcode.Text,
|
||||||
payload: ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello"))));
|
payload: ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello"))));
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
endOfMessage: true,
|
endOfMessage: true,
|
||||||
opcode: WebSocketOpcode.Ping,
|
opcode: WebSocketOpcode.Ping,
|
||||||
payload: ReadableBuffer.Create(Encoding.UTF8.GetBytes("ping"))));
|
payload: ReadableBuffer.Create(Encoding.UTF8.GetBytes("ping"))));
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
endOfMessage: true,
|
endOfMessage: true,
|
||||||
opcode: WebSocketOpcode.Continuation,
|
opcode: WebSocketOpcode.Continuation,
|
||||||
payload: ReadableBuffer.Create(Encoding.UTF8.GetBytes("World"))));
|
payload: ReadableBuffer.Create(Encoding.UTF8.GetBytes("World"))));
|
||||||
|
|
||||||
// Close the socket
|
// Close the socket
|
||||||
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure);
|
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure);
|
||||||
var serverSummary = await server;
|
var serverSummary = await server;
|
||||||
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure);
|
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure);
|
||||||
var clientSummary = await client;
|
var clientSummary = await client;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var nonControlFrames = serverSummary.Received.Where(f => f.Opcode < WebSocketOpcode.Close).ToList();
|
var nonControlFrames = serverSummary.Received.Where(f => f.Opcode < WebSocketOpcode.Close).ToList();
|
||||||
Assert.Equal(2, nonControlFrames.Count);
|
Assert.Equal(2, nonControlFrames.Count);
|
||||||
Assert.False(nonControlFrames[0].EndOfMessage);
|
Assert.False(nonControlFrames[0].EndOfMessage);
|
||||||
Assert.True(nonControlFrames[1].EndOfMessage);
|
Assert.True(nonControlFrames[1].EndOfMessage);
|
||||||
Assert.Equal(WebSocketOpcode.Text, nonControlFrames[0].Opcode);
|
Assert.Equal(WebSocketOpcode.Text, nonControlFrames[0].Opcode);
|
||||||
Assert.Equal(WebSocketOpcode.Continuation, nonControlFrames[1].Opcode);
|
Assert.Equal(WebSocketOpcode.Continuation, nonControlFrames[1].Opcode);
|
||||||
Assert.Equal("Hello", Encoding.UTF8.GetString(nonControlFrames[0].Payload.ToArray()));
|
Assert.Equal("Hello", Encoding.UTF8.GetString(nonControlFrames[0].Payload.ToArray()));
|
||||||
Assert.Equal("World", Encoding.UTF8.GetString(nonControlFrames[1].Payload.ToArray()));
|
Assert.Equal("World", Encoding.UTF8.GetString(nonControlFrames[1].Payload.ToArray()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,101 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.SignalR.Tests.Common;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Tests.Common;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
{
|
{
|
||||||
public partial class WebSocketConnectionTests
|
public partial class WebSocketConnectionTests
|
||||||
{
|
{
|
||||||
public class PingPongBehavior
|
[Fact]
|
||||||
|
public async Task AutomaticPingTransmission()
|
||||||
{
|
{
|
||||||
[ConditionalFact]
|
var startTime = DateTime.UtcNow;
|
||||||
[SkipIfEnvVarPresent("TRAVIS", "This test times out a lot on Travis because of slow performance/threading issues")]
|
// Arrange
|
||||||
public async Task AutomaticPingTransmission()
|
using (var pair = WebSocketPair.Create(
|
||||||
|
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough().WithPingInterval(TimeSpan.FromMilliseconds(10)),
|
||||||
|
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
||||||
{
|
{
|
||||||
var startTime = DateTime.UtcNow;
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
// Arrange
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
using (var pair = WebSocketPair.Create(
|
|
||||||
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough().WithPingInterval(TimeSpan.FromMilliseconds(10)),
|
// Act
|
||||||
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
// Wait for pings to be sent
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
|
var clientSummary = await client.OrTimeout();
|
||||||
|
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
|
var serverSummary = await server.OrTimeout();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEqual(0, clientSummary.Received.Count);
|
||||||
|
|
||||||
|
Assert.True(clientSummary.Received.All(f => f.EndOfMessage));
|
||||||
|
Assert.True(clientSummary.Received.All(f => f.Opcode == WebSocketOpcode.Ping));
|
||||||
|
Assert.True(clientSummary.Received.All(f =>
|
||||||
{
|
{
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
var str = Encoding.UTF8.GetString(f.Payload.ToArray());
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
// Act
|
// We can't verify the exact timestamp, but we can verify that it is a timestamp created after we started.
|
||||||
// Wait for pings to be sent
|
if (DateTime.TryParseExact(str, "O", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dt))
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
|
||||||
var serverSummary = await server.OrTimeout();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.NotEqual(0, clientSummary.Received.Count);
|
|
||||||
|
|
||||||
Assert.True(clientSummary.Received.All(f => f.EndOfMessage));
|
|
||||||
Assert.True(clientSummary.Received.All(f => f.Opcode == WebSocketOpcode.Ping));
|
|
||||||
Assert.True(clientSummary.Received.All(f =>
|
|
||||||
{
|
{
|
||||||
var str = Encoding.UTF8.GetString(f.Payload.ToArray());
|
return dt >= startTime;
|
||||||
|
}
|
||||||
// We can't verify the exact timestamp, but we can verify that it is a timestamp created after we started.
|
return false;
|
||||||
if (DateTime.TryParseExact(str, "O", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dt))
|
}));
|
||||||
{
|
|
||||||
return dt >= startTime;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AutomaticPingResponse()
|
public async Task AutomaticPingResponse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using (var pair = WebSocketPair.Create(
|
||||||
|
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
||||||
|
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
||||||
{
|
{
|
||||||
// Arrange
|
var payload = Encoding.UTF8.GetBytes("ping payload");
|
||||||
using (var pair = WebSocketPair.Create(
|
|
||||||
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
var pongTcs = new TaskCompletionSource<WebSocketFrame>();
|
||||||
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
|
||||||
|
var client = pair.ClientSocket.ExecuteAsync(f =>
|
||||||
{
|
{
|
||||||
var payload = Encoding.UTF8.GetBytes("ping payload");
|
if (f.Opcode == WebSocketOpcode.Pong)
|
||||||
|
|
||||||
var pongTcs = new TaskCompletionSource<WebSocketFrame>();
|
|
||||||
|
|
||||||
var client = pair.ClientSocket.ExecuteAsync(f =>
|
|
||||||
{
|
{
|
||||||
if (f.Opcode == WebSocketOpcode.Pong)
|
pongTcs.TrySetResult(f.Copy());
|
||||||
{
|
}
|
||||||
pongTcs.TrySetResult(f.Copy());
|
else
|
||||||
}
|
{
|
||||||
else
|
Assert.False(true, "Received non-pong frame from server!");
|
||||||
{
|
}
|
||||||
Assert.False(true, "Received non-pong frame from server!");
|
});
|
||||||
}
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
});
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
endOfMessage: true,
|
endOfMessage: true,
|
||||||
opcode: WebSocketOpcode.Ping,
|
opcode: WebSocketOpcode.Ping,
|
||||||
payload: ReadableBuffer.Create(payload)));
|
payload: ReadableBuffer.Create(payload)));
|
||||||
|
|
||||||
var pongFrame = await pongTcs.Task.OrTimeout();
|
var pongFrame = await pongTcs.Task.OrTimeout();
|
||||||
|
|
||||||
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
await server.OrTimeout();
|
await server.OrTimeout();
|
||||||
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
await client.OrTimeout();
|
await client.OrTimeout();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(pongFrame.EndOfMessage);
|
Assert.True(pongFrame.EndOfMessage);
|
||||||
Assert.Equal(WebSocketOpcode.Pong, pongFrame.Opcode);
|
Assert.Equal(WebSocketOpcode.Pong, pongFrame.Opcode);
|
||||||
Assert.Equal(payload, pongFrame.Payload.ToArray());
|
Assert.Equal(payload, pongFrame.Payload.ToArray());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,237 +12,234 @@ namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
{
|
{
|
||||||
public partial class WebSocketConnectionTests
|
public partial class WebSocketConnectionTests
|
||||||
{
|
{
|
||||||
public class ProtocolErrors
|
[Theory]
|
||||||
|
[InlineData(new byte[] { 0x11, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x21, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x31, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x41, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x51, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x61, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x71, 0x00 })]
|
||||||
|
public Task TerminatesConnectionOnReservedBitSet(byte[] rawFrame)
|
||||||
{
|
{
|
||||||
[Theory]
|
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Reserved bits, which are required to be zero, were set.");
|
||||||
[InlineData(new byte[] { 0x11, 0x00 })]
|
}
|
||||||
[InlineData(new byte[] { 0x21, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0x31, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0x41, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0x51, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0x61, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0x71, 0x00 })]
|
|
||||||
public Task TerminatesConnectionOnReservedBitSet(byte[] rawFrame)
|
|
||||||
{
|
|
||||||
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Reserved bits, which are required to be zero, were set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(0x03)]
|
[InlineData(0x03)]
|
||||||
[InlineData(0x04)]
|
[InlineData(0x04)]
|
||||||
[InlineData(0x05)]
|
[InlineData(0x05)]
|
||||||
[InlineData(0x06)]
|
[InlineData(0x06)]
|
||||||
[InlineData(0x07)]
|
[InlineData(0x07)]
|
||||||
[InlineData(0x0B)]
|
[InlineData(0x0B)]
|
||||||
[InlineData(0x0C)]
|
[InlineData(0x0C)]
|
||||||
[InlineData(0x0D)]
|
[InlineData(0x0D)]
|
||||||
[InlineData(0x0E)]
|
[InlineData(0x0E)]
|
||||||
[InlineData(0x0F)]
|
[InlineData(0x0F)]
|
||||||
public Task ReservedOpcodes(byte opcode)
|
public Task ReservedOpcodes(byte opcode)
|
||||||
|
{
|
||||||
|
var payload = Encoding.UTF8.GetBytes("hello");
|
||||||
|
var frame = new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: (WebSocketOpcode)opcode,
|
||||||
|
payload: ReadableBuffer.Create(payload));
|
||||||
|
return SendFrameAndExpectClose(frame, WebSocketCloseStatus.ProtocolError, $"Received frame using reserved opcode: 0x{opcode:X}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x01, 0xAB })]
|
||||||
|
|
||||||
|
// Invalid UTF-8 reason
|
||||||
|
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0x80, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0x99, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0xAB, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0xB0, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x03, 0x03, 0xE8, 0xC2 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x03, 0x03, 0xE8, 0xE0 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x04, 0x03, 0xE8, 0xE0, 0xA0 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x04, 0x03, 0xE8, 0xE0, 0xA4 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x05, 0x03, 0xE8, 0xF0, 0x90, 0x80 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x04, 0x03, 0xE8, 0xC1, 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x05, 0x03, 0xE8, 0xE0, 0x81, 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x06, 0x03, 0xE8, 0xF0, 0x80, 0x81, 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x05, 0x03, 0xE8, 0xE0, 0x82, 0xA7 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x06, 0x03, 0xE8, 0xF0, 0x80, 0x82, 0xA7 })]
|
||||||
|
[InlineData(new byte[] { 0x88, 0x06, 0x03, 0xE8, 0xF0, 0x80, 0xA0, 0x80 })]
|
||||||
|
public Task InvalidCloseFrames(byte[] rawFrame)
|
||||||
|
{
|
||||||
|
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Close frame payload invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task CloseFrameTooLong()
|
||||||
|
{
|
||||||
|
var rawFrame = new byte[256];
|
||||||
|
new Random().NextBytes(rawFrame);
|
||||||
|
|
||||||
|
// Put a WebSocket frame header in front
|
||||||
|
rawFrame[0] = 0x88; // Close frame, FIN=true
|
||||||
|
rawFrame[1] = 0x7E; // Mask=false, LEN=126
|
||||||
|
rawFrame[2] = 0x00; // Extended Len = 252 (256 - 4 bytes for header)
|
||||||
|
rawFrame[3] = 0xFC;
|
||||||
|
|
||||||
|
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Close frame payload too long. Maximum size is 125 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// 0-999 reserved
|
||||||
|
[InlineData(0)]
|
||||||
|
[InlineData(999)]
|
||||||
|
// Specifically reserved status codes, or codes that should not be sent in frames.
|
||||||
|
[InlineData(1004)]
|
||||||
|
[InlineData(1005)]
|
||||||
|
[InlineData(1006)]
|
||||||
|
[InlineData(1012)]
|
||||||
|
[InlineData(1013)]
|
||||||
|
[InlineData(1014)]
|
||||||
|
[InlineData(1015)]
|
||||||
|
// Undefined status codes
|
||||||
|
[InlineData(1016)]
|
||||||
|
[InlineData(1100)]
|
||||||
|
[InlineData(2000)]
|
||||||
|
[InlineData(2999)]
|
||||||
|
public Task InvalidCloseStatuses(ushort status)
|
||||||
|
{
|
||||||
|
var rawFrame = new byte[] { 0x88, 0x02, (byte)(status >> 8), (byte)(status) };
|
||||||
|
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, $"Invalid close status: {status}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new byte[] { 0x08, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x09, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0x0A, 0x00 })]
|
||||||
|
public Task TerminatesConnectionOnFragmentedControlFrame(byte[] rawFrame)
|
||||||
|
{
|
||||||
|
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Control frames may not be fragmented");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TerminatesConnectionOnNonContinuationFrameFollowingFragmentedMessageStart()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using (var pair = WebSocketPair.Create(
|
||||||
|
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
||||||
|
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
||||||
{
|
{
|
||||||
var payload = Encoding.UTF8.GetBytes("hello");
|
var payload = Encoding.UTF8.GetBytes("hello");
|
||||||
var frame = new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
opcode: (WebSocketOpcode)opcode,
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
payload: ReadableBuffer.Create(payload));
|
|
||||||
return SendFrameAndExpectClose(frame, WebSocketCloseStatus.ProtocolError, $"Received frame using reserved opcode: 0x{opcode:X}");
|
// Act
|
||||||
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
|
endOfMessage: false,
|
||||||
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: ReadableBuffer.Create(payload)));
|
||||||
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: ReadableBuffer.Create(payload)));
|
||||||
|
|
||||||
|
// Server should terminate
|
||||||
|
var clientSummary = await client.OrTimeout();
|
||||||
|
|
||||||
|
Assert.Equal(WebSocketCloseStatus.ProtocolError, clientSummary.CloseResult.Status);
|
||||||
|
Assert.Equal("Received non-continuation frame during a fragmented message", clientSummary.CloseResult.Description);
|
||||||
|
|
||||||
|
await server.OrTimeout();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData(new byte[] { 0x88, 0x01, 0xAB })]
|
public async Task TerminatesConnectionOnUnsolicitedContinuationFrame()
|
||||||
|
{
|
||||||
// Invalid UTF-8 reason
|
// Arrange
|
||||||
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0x80, 0x6C, 0x6F })]
|
using (var pair = WebSocketPair.Create(
|
||||||
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0x99, 0x6C, 0x6F })]
|
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
||||||
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0xAB, 0x6C, 0x6F })]
|
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
||||||
[InlineData(new byte[] { 0x88, 0x07, 0x03, 0xE8, 0x48, 0x65, 0xB0, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x03, 0x03, 0xE8, 0xC2 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x03, 0x03, 0xE8, 0xE0 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x04, 0x03, 0xE8, 0xE0, 0xA0 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x04, 0x03, 0xE8, 0xE0, 0xA4 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x05, 0x03, 0xE8, 0xF0, 0x90, 0x80 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x04, 0x03, 0xE8, 0xC1, 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x05, 0x03, 0xE8, 0xE0, 0x81, 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x06, 0x03, 0xE8, 0xF0, 0x80, 0x81, 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x05, 0x03, 0xE8, 0xE0, 0x82, 0xA7 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x06, 0x03, 0xE8, 0xF0, 0x80, 0x82, 0xA7 })]
|
|
||||||
[InlineData(new byte[] { 0x88, 0x06, 0x03, 0xE8, 0xF0, 0x80, 0xA0, 0x80 })]
|
|
||||||
public Task InvalidCloseFrames(byte[] rawFrame)
|
|
||||||
{
|
{
|
||||||
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Close frame payload invalid");
|
var payload = Encoding.UTF8.GetBytes("hello");
|
||||||
|
|
||||||
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: ReadableBuffer.Create(payload)));
|
||||||
|
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Continuation,
|
||||||
|
payload: ReadableBuffer.Create(payload)));
|
||||||
|
|
||||||
|
// Server should terminate
|
||||||
|
var clientSummary = await client.OrTimeout();
|
||||||
|
|
||||||
|
Assert.Equal(WebSocketCloseStatus.ProtocolError, clientSummary.CloseResult.Status);
|
||||||
|
Assert.Equal("Continuation Frame was received when expecting a new message", clientSummary.CloseResult.Description);
|
||||||
|
|
||||||
|
await server.OrTimeout();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task CloseFrameTooLong()
|
public Task TerminatesConnectionOnPingFrameLargerThan125Bytes()
|
||||||
|
{
|
||||||
|
var payload = new byte[126];
|
||||||
|
new Random().NextBytes(payload);
|
||||||
|
return SendFrameAndExpectClose(
|
||||||
|
new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Ping,
|
||||||
|
payload: ReadableBuffer.Create(payload)),
|
||||||
|
WebSocketCloseStatus.ProtocolError,
|
||||||
|
"Ping frame exceeded maximum size of 125 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SendFrameAndExpectClose(WebSocketFrame frame, WebSocketCloseStatus closeStatus, string closeReason)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using (var pair = WebSocketPair.Create(
|
||||||
|
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
||||||
|
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
||||||
{
|
{
|
||||||
var rawFrame = new byte[256];
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
new Random().NextBytes(rawFrame);
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
|
||||||
// Put a WebSocket frame header in front
|
// Act
|
||||||
rawFrame[0] = 0x88; // Close frame, FIN=true
|
await pair.ClientSocket.SendAsync(frame);
|
||||||
rawFrame[1] = 0x7E; // Mask=false, LEN=126
|
|
||||||
rawFrame[2] = 0x00; // Extended Len = 252 (256 - 4 bytes for header)
|
|
||||||
rawFrame[3] = 0xFC;
|
|
||||||
|
|
||||||
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Close frame payload too long. Maximum size is 125 bytes");
|
// Server should terminate
|
||||||
|
var clientSummary = await client.OrTimeout();
|
||||||
|
|
||||||
|
Assert.Equal(closeStatus, clientSummary.CloseResult.Status);
|
||||||
|
Assert.Equal(closeReason, clientSummary.CloseResult.Description);
|
||||||
|
|
||||||
|
await server.OrTimeout();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
private static async Task WriteFrameAndExpectClose(byte[] rawFrame, WebSocketCloseStatus closeStatus, string closeReason)
|
||||||
// 0-999 reserved
|
{
|
||||||
[InlineData(0)]
|
// Arrange
|
||||||
[InlineData(999)]
|
using (var pair = WebSocketPair.Create(
|
||||||
// Specifically reserved status codes, or codes that should not be sent in frames.
|
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
||||||
[InlineData(1004)]
|
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
||||||
[InlineData(1005)]
|
|
||||||
[InlineData(1006)]
|
|
||||||
[InlineData(1012)]
|
|
||||||
[InlineData(1013)]
|
|
||||||
[InlineData(1014)]
|
|
||||||
[InlineData(1015)]
|
|
||||||
// Undefined status codes
|
|
||||||
[InlineData(1016)]
|
|
||||||
[InlineData(1100)]
|
|
||||||
[InlineData(2000)]
|
|
||||||
[InlineData(2999)]
|
|
||||||
public Task InvalidCloseStatuses(ushort status)
|
|
||||||
{
|
{
|
||||||
var rawFrame = new byte[] { 0x88, 0x02, (byte)(status >> 8), (byte)(status) };
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, $"Invalid close status: {status}.");
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
// Act
|
||||||
[InlineData(new byte[] { 0x08, 0x00 })]
|
await pair.ClientToServer.Writer.WriteAsync(rawFrame);
|
||||||
[InlineData(new byte[] { 0x09, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0x0A, 0x00 })]
|
|
||||||
public Task TerminatesConnectionOnFragmentedControlFrame(byte[] rawFrame)
|
|
||||||
{
|
|
||||||
return WriteFrameAndExpectClose(rawFrame, WebSocketCloseStatus.ProtocolError, "Control frames may not be fragmented");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
// Server should terminate
|
||||||
public async Task TerminatesConnectionOnNonContinuationFrameFollowingFragmentedMessageStart()
|
var clientSummary = await client.OrTimeout();
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var pair = WebSocketPair.Create(
|
|
||||||
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
|
||||||
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
|
||||||
{
|
|
||||||
var payload = Encoding.UTF8.GetBytes("hello");
|
|
||||||
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
Assert.Equal(closeStatus, clientSummary.CloseResult.Status);
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
Assert.Equal(closeReason, clientSummary.CloseResult.Description);
|
||||||
|
|
||||||
// Act
|
await server.OrTimeout();
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
|
||||||
endOfMessage: false,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: ReadableBuffer.Create(payload)));
|
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: ReadableBuffer.Create(payload)));
|
|
||||||
|
|
||||||
// Server should terminate
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(WebSocketCloseStatus.ProtocolError, clientSummary.CloseResult.Status);
|
|
||||||
Assert.Equal("Received non-continuation frame during a fragmented message", clientSummary.CloseResult.Description);
|
|
||||||
|
|
||||||
await server.OrTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task TerminatesConnectionOnUnsolicitedContinuationFrame()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var pair = WebSocketPair.Create(
|
|
||||||
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
|
||||||
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
|
||||||
{
|
|
||||||
var payload = Encoding.UTF8.GetBytes("hello");
|
|
||||||
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: ReadableBuffer.Create(payload)));
|
|
||||||
await pair.ClientSocket.SendAsync(new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Continuation,
|
|
||||||
payload: ReadableBuffer.Create(payload)));
|
|
||||||
|
|
||||||
// Server should terminate
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(WebSocketCloseStatus.ProtocolError, clientSummary.CloseResult.Status);
|
|
||||||
Assert.Equal("Continuation Frame was received when expecting a new message", clientSummary.CloseResult.Description);
|
|
||||||
|
|
||||||
await server.OrTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public Task TerminatesConnectionOnPingFrameLargerThan125Bytes()
|
|
||||||
{
|
|
||||||
var payload = new byte[126];
|
|
||||||
new Random().NextBytes(payload);
|
|
||||||
return SendFrameAndExpectClose(
|
|
||||||
new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Ping,
|
|
||||||
payload: ReadableBuffer.Create(payload)),
|
|
||||||
WebSocketCloseStatus.ProtocolError,
|
|
||||||
"Ping frame exceeded maximum size of 125 bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task SendFrameAndExpectClose(WebSocketFrame frame, WebSocketCloseStatus closeStatus, string closeReason)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var pair = WebSocketPair.Create(
|
|
||||||
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
|
||||||
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
|
||||||
{
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await pair.ClientSocket.SendAsync(frame);
|
|
||||||
|
|
||||||
// Server should terminate
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(closeStatus, clientSummary.CloseResult.Status);
|
|
||||||
Assert.Equal(closeReason, clientSummary.CloseResult.Description);
|
|
||||||
|
|
||||||
await server.OrTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task WriteFrameAndExpectClose(byte[] rawFrame, WebSocketCloseStatus closeStatus, string closeReason)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
using (var pair = WebSocketPair.Create(
|
|
||||||
serverOptions: new WebSocketOptions().WithAllFramesPassedThrough(),
|
|
||||||
clientOptions: new WebSocketOptions().WithAllFramesPassedThrough()))
|
|
||||||
{
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await pair.ClientToServer.Writer.WriteAsync(rawFrame);
|
|
||||||
|
|
||||||
// Server should terminate
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(closeStatus, clientSummary.CloseResult.Status);
|
|
||||||
Assert.Equal(closeReason, clientSummary.CloseResult.Description);
|
|
||||||
|
|
||||||
await server.OrTimeout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,184 +13,181 @@ namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
{
|
{
|
||||||
public partial class WebSocketConnectionTests
|
public partial class WebSocketConnectionTests
|
||||||
{
|
{
|
||||||
public class TheReceiveAsyncMethod
|
[Theory]
|
||||||
|
[InlineData(new byte[] { 0x81, 0x00 }, "", true)]
|
||||||
|
[InlineData(new byte[] { 0x81, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F }, "Hello", true)]
|
||||||
|
[InlineData(new byte[] { 0x81, 0x85, 0x1, 0x2, 0x3, 0x4, 0x48 ^ 0x1, 0x65 ^ 0x2, 0x6C ^ 0x3, 0x6C ^ 0x4, 0x6F ^ 0x1 }, "Hello", true)]
|
||||||
|
[InlineData(new byte[] { 0x01, 0x00 }, "", false)]
|
||||||
|
[InlineData(new byte[] { 0x01, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F }, "Hello", false)]
|
||||||
|
[InlineData(new byte[] { 0x01, 0x85, 0x1, 0x2, 0x3, 0x4, 0x48 ^ 0x1, 0x65 ^ 0x2, 0x6C ^ 0x3, 0x6C ^ 0x4, 0x6F ^ 0x1 }, "Hello", false)]
|
||||||
|
public Task ReadTextFrames(byte[] rawFrame, string message, bool endOfMessage)
|
||||||
{
|
{
|
||||||
[Theory]
|
return RunSingleFrameTest(
|
||||||
[InlineData(new byte[] { 0x81, 0x00 }, "", true)]
|
rawFrame,
|
||||||
[InlineData(new byte[] { 0x81, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F }, "Hello", true)]
|
endOfMessage,
|
||||||
[InlineData(new byte[] { 0x81, 0x85, 0x1, 0x2, 0x3, 0x4, 0x48 ^ 0x1, 0x65 ^ 0x2, 0x6C ^ 0x3, 0x6C ^ 0x4, 0x6F ^ 0x1 }, "Hello", true)]
|
WebSocketOpcode.Text,
|
||||||
[InlineData(new byte[] { 0x01, 0x00 }, "", false)]
|
b => Assert.Equal(message, Encoding.UTF8.GetString(b)));
|
||||||
[InlineData(new byte[] { 0x01, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F }, "Hello", false)]
|
}
|
||||||
[InlineData(new byte[] { 0x01, 0x85, 0x1, 0x2, 0x3, 0x4, 0x48 ^ 0x1, 0x65 ^ 0x2, 0x6C ^ 0x3, 0x6C ^ 0x4, 0x6F ^ 0x1 }, "Hello", false)]
|
|
||||||
public Task ReadTextFrames(byte[] rawFrame, string message, bool endOfMessage)
|
|
||||||
{
|
|
||||||
return RunSingleFrameTest(
|
|
||||||
rawFrame,
|
|
||||||
endOfMessage,
|
|
||||||
WebSocketOpcode.Text,
|
|
||||||
b => Assert.Equal(message, Encoding.UTF8.GetString(b)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
// Opcode = Binary
|
// Opcode = Binary
|
||||||
[InlineData(new byte[] { 0x82, 0x00 }, new byte[0], WebSocketOpcode.Binary, true)]
|
[InlineData(new byte[] { 0x82, 0x00 }, new byte[0], WebSocketOpcode.Binary, true)]
|
||||||
[InlineData(new byte[] { 0x82, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, true)]
|
[InlineData(new byte[] { 0x82, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, true)]
|
||||||
[InlineData(new byte[] { 0x82, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, true)]
|
[InlineData(new byte[] { 0x82, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, true)]
|
||||||
[InlineData(new byte[] { 0x02, 0x00 }, new byte[0], WebSocketOpcode.Binary, false)]
|
[InlineData(new byte[] { 0x02, 0x00 }, new byte[0], WebSocketOpcode.Binary, false)]
|
||||||
[InlineData(new byte[] { 0x02, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, false)]
|
[InlineData(new byte[] { 0x02, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, false)]
|
||||||
[InlineData(new byte[] { 0x02, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, false)]
|
[InlineData(new byte[] { 0x02, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Binary, false)]
|
||||||
|
|
||||||
// Opcode = Ping
|
// Opcode = Ping
|
||||||
[InlineData(new byte[] { 0x89, 0x00 }, new byte[0], WebSocketOpcode.Ping, true)]
|
[InlineData(new byte[] { 0x89, 0x00 }, new byte[0], WebSocketOpcode.Ping, true)]
|
||||||
[InlineData(new byte[] { 0x89, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Ping, true)]
|
[InlineData(new byte[] { 0x89, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Ping, true)]
|
||||||
[InlineData(new byte[] { 0x89, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Ping, true)]
|
[InlineData(new byte[] { 0x89, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Ping, true)]
|
||||||
// Control frames can't have fin=false
|
// Control frames can't have fin=false
|
||||||
|
|
||||||
// Opcode = Pong
|
// Opcode = Pong
|
||||||
[InlineData(new byte[] { 0x8A, 0x00 }, new byte[0], WebSocketOpcode.Pong, true)]
|
[InlineData(new byte[] { 0x8A, 0x00 }, new byte[0], WebSocketOpcode.Pong, true)]
|
||||||
[InlineData(new byte[] { 0x8A, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Pong, true)]
|
[InlineData(new byte[] { 0x8A, 0x05, 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Pong, true)]
|
||||||
[InlineData(new byte[] { 0x8A, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Pong, true)]
|
[InlineData(new byte[] { 0x8A, 0x85, 0x1, 0x2, 0x3, 0x4, 0xDE ^ 0x1, 0xAD ^ 0x2, 0xBE ^ 0x3, 0xEF ^ 0x4, 0xAB ^ 0x1 }, new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, WebSocketOpcode.Pong, true)]
|
||||||
// Control frames can't have fin=false
|
// Control frames can't have fin=false
|
||||||
public Task ReadBinaryFormattedFrames(byte[] rawFrame, byte[] payload, WebSocketOpcode opcode, bool endOfMessage)
|
public Task ReadBinaryFormattedFrames(byte[] rawFrame, byte[] payload, WebSocketOpcode opcode, bool endOfMessage)
|
||||||
{
|
{
|
||||||
return RunSingleFrameTest(
|
return RunSingleFrameTest(
|
||||||
rawFrame,
|
rawFrame,
|
||||||
endOfMessage,
|
endOfMessage,
|
||||||
opcode,
|
opcode,
|
||||||
b => Assert.Equal(payload, b));
|
b => Assert.Equal(payload, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ReadMultipleFramesAcrossMultipleBuffers()
|
public async Task ReadMultipleFramesAcrossMultipleBuffers()
|
||||||
{
|
{
|
||||||
var result = await RunReceiveTest(
|
var result = await RunReceiveTest(
|
||||||
producer: async (channel, cancellationToken) =>
|
producer: async (channel, cancellationToken) =>
|
||||||
{
|
|
||||||
await channel.WriteAsync(new byte[] { 0x02, 0x05 }.Slice()).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
await channel.WriteAsync(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB, 0x80, 0x05 }.Slice()).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
await channel.WriteAsync(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }.Slice()).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
await channel.WriteAsync(new byte[] { 0xAB }.Slice()).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.Equal(2, result.Received.Count);
|
|
||||||
|
|
||||||
Assert.False(result.Received[0].EndOfMessage);
|
|
||||||
Assert.Equal(WebSocketOpcode.Binary, result.Received[0].Opcode);
|
|
||||||
Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, result.Received[0].Payload.ToArray());
|
|
||||||
|
|
||||||
Assert.True(result.Received[1].EndOfMessage);
|
|
||||||
Assert.Equal(WebSocketOpcode.Continuation, result.Received[1].Opcode);
|
|
||||||
Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, result.Received[1].Payload.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ReadLargeMaskedPayload()
|
|
||||||
{
|
|
||||||
// This test was added to ensure we don't break a behavior discovered while running the Autobahn test suite.
|
|
||||||
|
|
||||||
// Larger than one page, which means it will span blocks in the memory pool.
|
|
||||||
var expectedPayload = new byte[4192];
|
|
||||||
for (int i = 0; i < expectedPayload.Length; i++)
|
|
||||||
{
|
{
|
||||||
expectedPayload[i] = (byte)(i % byte.MaxValue);
|
await channel.WriteAsync(new byte[] { 0x02, 0x05 }.Slice()).OrTimeout();
|
||||||
}
|
await Task.Yield();
|
||||||
var maskingKey = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
await channel.WriteAsync(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB, 0x80, 0x05 }.Slice()).OrTimeout();
|
||||||
var sendPayload = new byte[4192];
|
await Task.Yield();
|
||||||
for (int i = 0; i < expectedPayload.Length; i++)
|
await channel.WriteAsync(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }.Slice()).OrTimeout();
|
||||||
|
await Task.Yield();
|
||||||
|
await channel.WriteAsync(new byte[] { 0xAB }.Slice()).OrTimeout();
|
||||||
|
await Task.Yield();
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Received.Count);
|
||||||
|
|
||||||
|
Assert.False(result.Received[0].EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Binary, result.Received[0].Opcode);
|
||||||
|
Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, result.Received[0].Payload.ToArray());
|
||||||
|
|
||||||
|
Assert.True(result.Received[1].EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Continuation, result.Received[1].Opcode);
|
||||||
|
Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF, 0xAB }, result.Received[1].Payload.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ReadLargeMaskedPayload()
|
||||||
|
{
|
||||||
|
// This test was added to ensure we don't break a behavior discovered while running the Autobahn test suite.
|
||||||
|
|
||||||
|
// Larger than one page, which means it will span blocks in the memory pool.
|
||||||
|
var expectedPayload = new byte[4192];
|
||||||
|
for (int i = 0; i < expectedPayload.Length; i++)
|
||||||
|
{
|
||||||
|
expectedPayload[i] = (byte)(i % byte.MaxValue);
|
||||||
|
}
|
||||||
|
var maskingKey = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
||||||
|
var sendPayload = new byte[4192];
|
||||||
|
for (int i = 0; i < expectedPayload.Length; i++)
|
||||||
|
{
|
||||||
|
sendPayload[i] = (byte)(expectedPayload[i] ^ maskingKey[i % 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await RunReceiveTest(
|
||||||
|
producer: async (channel, cancellationToken) =>
|
||||||
{
|
{
|
||||||
sendPayload[i] = (byte)(expectedPayload[i] ^ maskingKey[i % 4]);
|
// We use a 64-bit length because we want to ensure that the first page of data ends at an
|
||||||
}
|
// offset within the frame that is NOT divisible by 4. This ensures that when the unmasking
|
||||||
|
// moves from one buffer to the other, we are at a non-zero position within the masking key.
|
||||||
|
// This ensures that we're tracking the masking key offset properly.
|
||||||
|
|
||||||
var result = await RunReceiveTest(
|
// Header: (Opcode=Binary, Fin=true), (Mask=false, Len=126), (64-bit big endian length)
|
||||||
producer: async (channel, cancellationToken) =>
|
await channel.WriteAsync(new byte[] { 0x82, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x60 }).OrTimeout();
|
||||||
{
|
await channel.WriteAsync(maskingKey).OrTimeout();
|
||||||
// We use a 64-bit length because we want to ensure that the first page of data ends at an
|
await Task.Yield();
|
||||||
// offset within the frame that is NOT divisible by 4. This ensures that when the unmasking
|
await channel.WriteAsync(sendPayload).OrTimeout();
|
||||||
// moves from one buffer to the other, we are at a non-zero position within the masking key.
|
});
|
||||||
// This ensures that we're tracking the masking key offset properly.
|
|
||||||
|
|
||||||
// Header: (Opcode=Binary, Fin=true), (Mask=false, Len=126), (64-bit big endian length)
|
Assert.Equal(1, result.Received.Count);
|
||||||
await channel.WriteAsync(new byte[] { 0x82, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x60 }).OrTimeout();
|
|
||||||
await channel.WriteAsync(maskingKey).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
await channel.WriteAsync(sendPayload).OrTimeout();
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.Equal(1, result.Received.Count);
|
var frame = result.Received[0];
|
||||||
|
Assert.True(frame.EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Binary, frame.Opcode);
|
||||||
|
Assert.Equal(expectedPayload, frame.Payload.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
var frame = result.Received[0];
|
[Fact]
|
||||||
Assert.True(frame.EndOfMessage);
|
public async Task Read16BitPayloadLength()
|
||||||
Assert.Equal(WebSocketOpcode.Binary, frame.Opcode);
|
{
|
||||||
Assert.Equal(expectedPayload, frame.Payload.ToArray());
|
var expectedPayload = new byte[1024];
|
||||||
}
|
new Random().NextBytes(expectedPayload);
|
||||||
|
|
||||||
[Fact]
|
var result = await RunReceiveTest(
|
||||||
public async Task Read16BitPayloadLength()
|
producer: async (channel, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var expectedPayload = new byte[1024];
|
// Header: (Opcode=Binary, Fin=true), (Mask=false, Len=126), (16-bit big endian length)
|
||||||
new Random().NextBytes(expectedPayload);
|
await channel.WriteAsync(new byte[] { 0x82, 0x7E, 0x04, 0x00 }).OrTimeout();
|
||||||
|
await Task.Yield();
|
||||||
|
await channel.WriteAsync(expectedPayload).OrTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
var result = await RunReceiveTest(
|
Assert.Equal(1, result.Received.Count);
|
||||||
producer: async (channel, cancellationToken) =>
|
|
||||||
{
|
|
||||||
// Header: (Opcode=Binary, Fin=true), (Mask=false, Len=126), (16-bit big endian length)
|
|
||||||
await channel.WriteAsync(new byte[] { 0x82, 0x7E, 0x04, 0x00 }).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
await channel.WriteAsync(expectedPayload).OrTimeout();
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.Equal(1, result.Received.Count);
|
var frame = result.Received[0];
|
||||||
|
Assert.True(frame.EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Binary, frame.Opcode);
|
||||||
|
Assert.Equal(expectedPayload, frame.Payload.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
var frame = result.Received[0];
|
[Fact]
|
||||||
Assert.True(frame.EndOfMessage);
|
public async Task Read64bitPayloadLength()
|
||||||
Assert.Equal(WebSocketOpcode.Binary, frame.Opcode);
|
{
|
||||||
Assert.Equal(expectedPayload, frame.Payload.ToArray());
|
// Allocating an actual (2^32 + 1) byte payload is crazy for this test. We just need to test that we can USE a 64-bit length
|
||||||
}
|
var expectedPayload = new byte[1024];
|
||||||
|
new Random().NextBytes(expectedPayload);
|
||||||
|
|
||||||
[Fact]
|
var result = await RunReceiveTest(
|
||||||
public async Task Read64bitPayloadLength()
|
producer: async (channel, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// Allocating an actual (2^32 + 1) byte payload is crazy for this test. We just need to test that we can USE a 64-bit length
|
// Header: (Opcode=Binary, Fin=true), (Mask=false, Len=127), (64-bit big endian length)
|
||||||
var expectedPayload = new byte[1024];
|
await channel.WriteAsync(new byte[] { 0x82, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00 }).OrTimeout();
|
||||||
new Random().NextBytes(expectedPayload);
|
await Task.Yield();
|
||||||
|
await channel.WriteAsync(expectedPayload).OrTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
var result = await RunReceiveTest(
|
Assert.Equal(1, result.Received.Count);
|
||||||
producer: async (channel, cancellationToken) =>
|
|
||||||
{
|
|
||||||
// Header: (Opcode=Binary, Fin=true), (Mask=false, Len=127), (64-bit big endian length)
|
|
||||||
await channel.WriteAsync(new byte[] { 0x82, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00 }).OrTimeout();
|
|
||||||
await Task.Yield();
|
|
||||||
await channel.WriteAsync(expectedPayload).OrTimeout();
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.Equal(1, result.Received.Count);
|
var frame = result.Received[0];
|
||||||
|
Assert.True(frame.EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Binary, frame.Opcode);
|
||||||
|
Assert.Equal(expectedPayload, frame.Payload.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
var frame = result.Received[0];
|
private static async Task RunSingleFrameTest(byte[] rawFrame, bool endOfMessage, WebSocketOpcode expectedOpcode, Action<byte[]> payloadAssert)
|
||||||
Assert.True(frame.EndOfMessage);
|
{
|
||||||
Assert.Equal(WebSocketOpcode.Binary, frame.Opcode);
|
var result = await RunReceiveTest(
|
||||||
Assert.Equal(expectedPayload, frame.Payload.ToArray());
|
producer: async (channel, cancellationToken) =>
|
||||||
}
|
{
|
||||||
|
await channel.WriteAsync(rawFrame.Slice()).OrTimeout();
|
||||||
|
});
|
||||||
|
var frames = result.Received;
|
||||||
|
Assert.Equal(1, frames.Count);
|
||||||
|
|
||||||
private static async Task RunSingleFrameTest(byte[] rawFrame, bool endOfMessage, WebSocketOpcode expectedOpcode, Action<byte[]> payloadAssert)
|
var frame = frames[0];
|
||||||
{
|
|
||||||
var result = await RunReceiveTest(
|
|
||||||
producer: async (channel, cancellationToken) =>
|
|
||||||
{
|
|
||||||
await channel.WriteAsync(rawFrame.Slice()).OrTimeout();
|
|
||||||
});
|
|
||||||
var frames = result.Received;
|
|
||||||
Assert.Equal(1, frames.Count);
|
|
||||||
|
|
||||||
var frame = frames[0];
|
Assert.Equal(endOfMessage, frame.EndOfMessage);
|
||||||
|
Assert.Equal(expectedOpcode, frame.Opcode);
|
||||||
Assert.Equal(endOfMessage, frame.EndOfMessage);
|
payloadAssert(frame.Payload.ToArray());
|
||||||
Assert.Equal(expectedOpcode, frame.Opcode);
|
|
||||||
payloadAssert(frame.Payload.ToArray());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<WebSocketConnectionSummary> RunReceiveTest(Func<IPipeWriter, CancellationToken, Task> producer)
|
private static async Task<WebSocketConnectionSummary> RunReceiveTest(Func<IPipeWriter, CancellationToken, Task> producer)
|
||||||
|
|
|
||||||
|
|
@ -13,196 +13,193 @@ namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
{
|
{
|
||||||
public partial class WebSocketConnectionTests
|
public partial class WebSocketConnectionTests
|
||||||
{
|
{
|
||||||
public class TheSendAsyncMethod
|
// No auto-pinging for us!
|
||||||
|
private readonly static WebSocketOptions DefaultTestOptions = new WebSocketOptions().WithAllFramesPassedThrough();
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", true, new byte[] { 0x81, 0x00 })]
|
||||||
|
[InlineData("Hello", true, new byte[] { 0x81, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F })]
|
||||||
|
[InlineData("", false, new byte[] { 0x01, 0x00 })]
|
||||||
|
[InlineData("Hello", false, new byte[] { 0x01, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F })]
|
||||||
|
public async Task WriteTextFrames(string message, bool endOfMessage, byte[] expectedRawFrame)
|
||||||
{
|
{
|
||||||
// No auto-pinging for us!
|
var data = await RunSendTest(
|
||||||
private readonly static WebSocketOptions DefaultTestOptions = new WebSocketOptions().WithAllFramesPassedThrough();
|
producer: async (socket) =>
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("", true, new byte[] { 0x81, 0x00 })]
|
|
||||||
[InlineData("Hello", true, new byte[] { 0x81, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F })]
|
|
||||||
[InlineData("", false, new byte[] { 0x01, 0x00 })]
|
|
||||||
[InlineData("Hello", false, new byte[] { 0x01, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F })]
|
|
||||||
public async Task WriteTextFrames(string message, bool endOfMessage, byte[] expectedRawFrame)
|
|
||||||
{
|
|
||||||
var data = await RunSendTest(
|
|
||||||
producer: async (socket) =>
|
|
||||||
{
|
|
||||||
var payload = Encoding.UTF8.GetBytes(message);
|
|
||||||
await socket.SendAsync(CreateFrame(
|
|
||||||
endOfMessage,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: payload)).OrTimeout();
|
|
||||||
}, options: DefaultTestOptions);
|
|
||||||
Assert.Equal(expectedRawFrame, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// Opcode = Binary
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Binary, true, new byte[] { 0x82, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, true, new byte[] { 0x82, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Binary, false, new byte[] { 0x02, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, false, new byte[] { 0x02, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
|
|
||||||
// Opcode = Continuation
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Continuation, true, new byte[] { 0x80, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, true, new byte[] { 0x80, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Continuation, false, new byte[] { 0x00, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, false, new byte[] { 0x00, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
|
|
||||||
// Opcode = Ping
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Ping, true, new byte[] { 0x89, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, true, new byte[] { 0x89, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Ping, false, new byte[] { 0x09, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, false, new byte[] { 0x09, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
|
|
||||||
// Opcode = Pong
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Pong, true, new byte[] { 0x8A, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, true, new byte[] { 0x8A, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Pong, false, new byte[] { 0x0A, 0x00 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, false, new byte[] { 0x0A, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
|
||||||
public async Task WriteBinaryFormattedFrames(byte[] payload, WebSocketOpcode opcode, bool endOfMessage, byte[] expectedRawFrame)
|
|
||||||
{
|
|
||||||
var data = await RunSendTest(
|
|
||||||
producer: async (socket) =>
|
|
||||||
{
|
|
||||||
await socket.SendAsync(CreateFrame(
|
|
||||||
endOfMessage,
|
|
||||||
opcode,
|
|
||||||
payload: payload)).OrTimeout();
|
|
||||||
}, options: DefaultTestOptions);
|
|
||||||
Assert.Equal(expectedRawFrame, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x81, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData("Hello", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x81, 0x85, 0x01, 0x02, 0x03, 0x04, 0x48 ^ 0x01, 0x65 ^ 0x02, 0x6C ^ 0x03, 0x6C ^ 0x04, 0x6F ^ 0x01 })]
|
|
||||||
public async Task WriteMaskedTextFrames(string message, byte[] maskingKey, byte[] expectedRawFrame)
|
|
||||||
{
|
|
||||||
var data = await RunSendTest(
|
|
||||||
producer: async (socket) =>
|
|
||||||
{
|
|
||||||
var payload = Encoding.UTF8.GetBytes(message);
|
|
||||||
await socket.SendAsync(CreateFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: payload)).OrTimeout();
|
|
||||||
}, options: DefaultTestOptions.WithFixedMaskingKey(maskingKey));
|
|
||||||
Assert.Equal(expectedRawFrame, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
// Opcode = Binary
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Binary, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x82, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x82, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Binary, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x02, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x02, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
|
|
||||||
// Opcode = Continuation
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Continuation, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x80, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x80, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Continuation, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x00, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x00, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
|
|
||||||
// Opcode = Ping
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Ping, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x89, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x89, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Ping, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x09, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x09, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
|
|
||||||
// Opcode = Pong
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Pong, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x8A, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x8A, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
[InlineData(new byte[0], WebSocketOpcode.Pong, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x0A, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
|
||||||
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x0A, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
|
||||||
public async Task WriteMaskedBinaryFormattedFrames(byte[] payload, WebSocketOpcode opcode, bool endOfMessage, byte[] maskingKey, byte[] expectedRawFrame)
|
|
||||||
{
|
|
||||||
var data = await RunSendTest(
|
|
||||||
producer: async (socket) =>
|
|
||||||
{
|
|
||||||
await socket.SendAsync(CreateFrame(
|
|
||||||
endOfMessage,
|
|
||||||
opcode,
|
|
||||||
payload: payload)).OrTimeout();
|
|
||||||
}, options: DefaultTestOptions.WithFixedMaskingKey(maskingKey));
|
|
||||||
Assert.Equal(expectedRawFrame, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task WriteRandomMaskedFrame()
|
|
||||||
{
|
|
||||||
var data = await RunSendTest(
|
|
||||||
producer: async (socket) =>
|
|
||||||
{
|
|
||||||
await socket.SendAsync(CreateFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Binary,
|
|
||||||
payload: new byte[] { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E })).OrTimeout();
|
|
||||||
}, options: DefaultTestOptions.WithRandomMasking());
|
|
||||||
|
|
||||||
// Verify the header
|
|
||||||
Assert.Equal(0x82, data[0]);
|
|
||||||
Assert.Equal(0x85, data[1]);
|
|
||||||
|
|
||||||
// We don't know the mask, so we have to read it in order to verify this frame
|
|
||||||
var mask = data.Slice(2, 4);
|
|
||||||
var actualPayload = data.Slice(6);
|
|
||||||
|
|
||||||
// Unmask the payload
|
|
||||||
for (int i = 0; i < actualPayload.Length; i++)
|
|
||||||
{
|
{
|
||||||
actualPayload[i] = (byte)(mask[i % 4] ^ actualPayload[i]);
|
var payload = Encoding.UTF8.GetBytes(message);
|
||||||
}
|
await socket.SendAsync(CreateFrame(
|
||||||
Assert.Equal(new byte[] { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E }, actualPayload.ToArray());
|
endOfMessage,
|
||||||
}
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: payload)).OrTimeout();
|
||||||
|
}, options: DefaultTestOptions);
|
||||||
|
Assert.Equal(expectedRawFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(WebSocketCloseStatus.MandatoryExtension, "Hi", null, new byte[] { 0x88, 0x04, 0x03, 0xF2, (byte)'H', (byte)'i' })]
|
// Opcode = Binary
|
||||||
[InlineData(WebSocketCloseStatus.PolicyViolation, "", null, new byte[] { 0x88, 0x02, 0x03, 0xF0 })]
|
[InlineData(new byte[0], WebSocketOpcode.Binary, true, new byte[] { 0x82, 0x00 })]
|
||||||
[InlineData(WebSocketCloseStatus.MandatoryExtension, "Hi", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x88, 0x84, 0x01, 0x02, 0x03, 0x04, 0x03 ^ 0x01, 0xF2 ^ 0x02, (byte)'H' ^ 0x03, (byte)'i' ^ 0x04 })]
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, true, new byte[] { 0x82, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
[InlineData(WebSocketCloseStatus.PolicyViolation, "", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x88, 0x82, 0x01, 0x02, 0x03, 0x04, 0x03 ^ 0x01, 0xF0 ^ 0x02 })]
|
[InlineData(new byte[0], WebSocketOpcode.Binary, false, new byte[] { 0x02, 0x00 })]
|
||||||
public async Task WriteCloseFrames(WebSocketCloseStatus status, string description, byte[] maskingKey, byte[] expectedRawFrame)
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, false, new byte[] { 0x02, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
{
|
|
||||||
var data = await RunSendTest(
|
|
||||||
producer: async (socket) =>
|
|
||||||
{
|
|
||||||
await socket.CloseAsync(new WebSocketCloseResult(status, description)).OrTimeout();
|
|
||||||
}, options: maskingKey == null ? DefaultTestOptions : DefaultTestOptions.WithFixedMaskingKey(maskingKey));
|
|
||||||
Assert.Equal(expectedRawFrame, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<byte[]> RunSendTest(Func<WebSocketConnection, Task> producer, WebSocketOptions options)
|
// Opcode = Continuation
|
||||||
{
|
[InlineData(new byte[0], WebSocketOpcode.Continuation, true, new byte[] { 0x80, 0x00 })]
|
||||||
using (var factory = new PipeFactory())
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, true, new byte[] { 0x80, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Continuation, false, new byte[] { 0x00, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, false, new byte[] { 0x00, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
|
|
||||||
|
// Opcode = Ping
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Ping, true, new byte[] { 0x89, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, true, new byte[] { 0x89, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Ping, false, new byte[] { 0x09, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, false, new byte[] { 0x09, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
|
|
||||||
|
// Opcode = Pong
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Pong, true, new byte[] { 0x8A, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, true, new byte[] { 0x8A, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Pong, false, new byte[] { 0x0A, 0x00 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, false, new byte[] { 0x0A, 0x05, 0xA, 0xB, 0xC, 0xD, 0xE })]
|
||||||
|
public async Task WriteBinaryFormattedFrames(byte[] payload, WebSocketOpcode opcode, bool endOfMessage, byte[] expectedRawFrame)
|
||||||
|
{
|
||||||
|
var data = await RunSendTest(
|
||||||
|
producer: async (socket) =>
|
||||||
{
|
{
|
||||||
var outbound = factory.Create();
|
await socket.SendAsync(CreateFrame(
|
||||||
var inbound = factory.Create();
|
endOfMessage,
|
||||||
|
opcode,
|
||||||
|
payload: payload)).OrTimeout();
|
||||||
|
}, options: DefaultTestOptions);
|
||||||
|
Assert.Equal(expectedRawFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
using (var connection = new WebSocketConnection(inbound.Reader, outbound.Writer, options))
|
[Theory]
|
||||||
{
|
[InlineData("", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x81, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
var executeTask = connection.ExecuteAndCaptureFramesAsync();
|
[InlineData("Hello", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x81, 0x85, 0x01, 0x02, 0x03, 0x04, 0x48 ^ 0x01, 0x65 ^ 0x02, 0x6C ^ 0x03, 0x6C ^ 0x04, 0x6F ^ 0x01 })]
|
||||||
await producer(connection).OrTimeout();
|
public async Task WriteMaskedTextFrames(string message, byte[] maskingKey, byte[] expectedRawFrame)
|
||||||
connection.Abort();
|
{
|
||||||
inbound.Writer.Complete();
|
var data = await RunSendTest(
|
||||||
await executeTask.OrTimeout();
|
producer: async (socket) =>
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = await outbound.Reader.ReadToEndAsync();
|
|
||||||
var data = buffer.ToArray();
|
|
||||||
outbound.Reader.Advance(buffer.End);
|
|
||||||
inbound.Reader.Complete();
|
|
||||||
CompleteChannels(outbound);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CompleteChannels(params IPipe[] readerWriters)
|
|
||||||
{
|
|
||||||
foreach (var readerWriter in readerWriters)
|
|
||||||
{
|
{
|
||||||
readerWriter.Reader.Complete();
|
var payload = Encoding.UTF8.GetBytes(message);
|
||||||
readerWriter.Writer.Complete();
|
await socket.SendAsync(CreateFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: payload)).OrTimeout();
|
||||||
|
}, options: DefaultTestOptions.WithFixedMaskingKey(maskingKey));
|
||||||
|
Assert.Equal(expectedRawFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// Opcode = Binary
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Binary, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x82, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x82, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Binary, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x02, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Binary, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x02, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
|
||||||
|
// Opcode = Continuation
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Continuation, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x80, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x80, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Continuation, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x00, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Continuation, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x00, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
|
||||||
|
// Opcode = Ping
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Ping, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x89, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x89, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Ping, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x09, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Ping, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x09, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
|
||||||
|
// Opcode = Pong
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Pong, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x8A, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, true, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x8A, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
[InlineData(new byte[0], WebSocketOpcode.Pong, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x0A, 0x80, 0x01, 0x02, 0x03, 0x04 })]
|
||||||
|
[InlineData(new byte[] { 0xA, 0xB, 0xC, 0xD, 0xE }, WebSocketOpcode.Pong, false, new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x0A, 0x85, 0x01, 0x02, 0x03, 0x04, 0x0A ^ 0x01, 0x0B ^ 0x02, 0x0C ^ 0x03, 0x0D ^ 0x04, 0x0E ^ 0x01 })]
|
||||||
|
public async Task WriteMaskedBinaryFormattedFrames(byte[] payload, WebSocketOpcode opcode, bool endOfMessage, byte[] maskingKey, byte[] expectedRawFrame)
|
||||||
|
{
|
||||||
|
var data = await RunSendTest(
|
||||||
|
producer: async (socket) =>
|
||||||
|
{
|
||||||
|
await socket.SendAsync(CreateFrame(
|
||||||
|
endOfMessage,
|
||||||
|
opcode,
|
||||||
|
payload: payload)).OrTimeout();
|
||||||
|
}, options: DefaultTestOptions.WithFixedMaskingKey(maskingKey));
|
||||||
|
Assert.Equal(expectedRawFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteRandomMaskedFrame()
|
||||||
|
{
|
||||||
|
var data = await RunSendTest(
|
||||||
|
producer: async (socket) =>
|
||||||
|
{
|
||||||
|
await socket.SendAsync(CreateFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Binary,
|
||||||
|
payload: new byte[] { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E })).OrTimeout();
|
||||||
|
}, options: DefaultTestOptions.WithRandomMasking());
|
||||||
|
|
||||||
|
// Verify the header
|
||||||
|
Assert.Equal(0x82, data[0]);
|
||||||
|
Assert.Equal(0x85, data[1]);
|
||||||
|
|
||||||
|
// We don't know the mask, so we have to read it in order to verify this frame
|
||||||
|
var mask = data.Slice(2, 4);
|
||||||
|
var actualPayload = data.Slice(6);
|
||||||
|
|
||||||
|
// Unmask the payload
|
||||||
|
for (int i = 0; i < actualPayload.Length; i++)
|
||||||
|
{
|
||||||
|
actualPayload[i] = (byte)(mask[i % 4] ^ actualPayload[i]);
|
||||||
|
}
|
||||||
|
Assert.Equal(new byte[] { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E }, actualPayload.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(WebSocketCloseStatus.MandatoryExtension, "Hi", null, new byte[] { 0x88, 0x04, 0x03, 0xF2, (byte)'H', (byte)'i' })]
|
||||||
|
[InlineData(WebSocketCloseStatus.PolicyViolation, "", null, new byte[] { 0x88, 0x02, 0x03, 0xF0 })]
|
||||||
|
[InlineData(WebSocketCloseStatus.MandatoryExtension, "Hi", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x88, 0x84, 0x01, 0x02, 0x03, 0x04, 0x03 ^ 0x01, 0xF2 ^ 0x02, (byte)'H' ^ 0x03, (byte)'i' ^ 0x04 })]
|
||||||
|
[InlineData(WebSocketCloseStatus.PolicyViolation, "", new byte[] { 0x01, 0x02, 0x03, 0x04 }, new byte[] { 0x88, 0x82, 0x01, 0x02, 0x03, 0x04, 0x03 ^ 0x01, 0xF0 ^ 0x02 })]
|
||||||
|
public async Task WriteCloseFrames(WebSocketCloseStatus status, string description, byte[] maskingKey, byte[] expectedRawFrame)
|
||||||
|
{
|
||||||
|
var data = await RunSendTest(
|
||||||
|
producer: async (socket) =>
|
||||||
|
{
|
||||||
|
await socket.CloseAsync(new WebSocketCloseResult(status, description)).OrTimeout();
|
||||||
|
}, options: maskingKey == null ? DefaultTestOptions : DefaultTestOptions.WithFixedMaskingKey(maskingKey));
|
||||||
|
Assert.Equal(expectedRawFrame, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<byte[]> RunSendTest(Func<WebSocketConnection, Task> producer, WebSocketOptions options)
|
||||||
|
{
|
||||||
|
using (var factory = new PipeFactory())
|
||||||
|
{
|
||||||
|
var outbound = factory.Create();
|
||||||
|
var inbound = factory.Create();
|
||||||
|
|
||||||
|
using (var connection = new WebSocketConnection(inbound.Reader, outbound.Writer, options))
|
||||||
|
{
|
||||||
|
var executeTask = connection.ExecuteAndCaptureFramesAsync();
|
||||||
|
await producer(connection).OrTimeout();
|
||||||
|
connection.Abort();
|
||||||
|
inbound.Writer.Complete();
|
||||||
|
await executeTask.OrTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buffer = await outbound.Reader.ReadToEndAsync();
|
||||||
|
var data = buffer.ToArray();
|
||||||
|
outbound.Reader.Advance(buffer.End);
|
||||||
|
inbound.Reader.Complete();
|
||||||
|
CompleteChannels(outbound);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CompleteChannels(params IPipe[] readerWriters)
|
||||||
|
{
|
||||||
|
foreach (var readerWriter in readerWriters)
|
||||||
|
{
|
||||||
|
readerWriter.Reader.Complete();
|
||||||
|
readerWriter.Writer.Complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,72 +12,186 @@ namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
{
|
{
|
||||||
public partial class WebSocketConnectionTests
|
public partial class WebSocketConnectionTests
|
||||||
{
|
{
|
||||||
public class Utf8Validation
|
[Theory]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F }, "Hello")]
|
||||||
|
[InlineData(new byte[] { 0xC2, 0xA7, 0x31, 0x2C, 0x20, 0x39, 0x35, 0xC2, 0xA2 }, "§1, 95¢")]
|
||||||
|
[InlineData(new byte[] { 0xE0, 0xA0, 0x80, 0xE0, 0xA4, 0x80 }, "\u0800\u0900")]
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x90, 0x80, 0x80 }, "\U00010000")]
|
||||||
|
public async Task ValidSingleFramePayloads(byte[] payload, string decoded)
|
||||||
{
|
{
|
||||||
[Theory]
|
using (var pair = WebSocketPair.Create())
|
||||||
[InlineData(new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F }, "Hello")]
|
|
||||||
[InlineData(new byte[] { 0xC2, 0xA7, 0x31, 0x2C, 0x20, 0x39, 0x35, 0xC2, 0xA2 }, "§1, 95¢")]
|
|
||||||
[InlineData(new byte[] { 0xE0, 0xA0, 0x80, 0xE0, 0xA4, 0x80 }, "\u0800\u0900")]
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x90, 0x80, 0x80 }, "\U00010000")]
|
|
||||||
public async Task ValidSingleFramePayloads(byte[] payload, string decoded)
|
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
var timeoutToken = TestUtil.CreateTimeoutToken();
|
||||||
|
using (timeoutToken.Register(() => pair.Dispose()))
|
||||||
{
|
{
|
||||||
var timeoutToken = TestUtil.CreateTimeoutToken();
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
using (timeoutToken.Register(() => pair.Dispose()))
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
{
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
var frame = new WebSocketFrame(
|
var frame = new WebSocketFrame(
|
||||||
endOfMessage: true,
|
endOfMessage: true,
|
||||||
opcode: WebSocketOpcode.Text,
|
opcode: WebSocketOpcode.Text,
|
||||||
payload: ReadableBuffer.Create(payload));
|
payload: ReadableBuffer.Create(payload));
|
||||||
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
||||||
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
var serverSummary = await server.OrTimeout();
|
var serverSummary = await server.OrTimeout();
|
||||||
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
var clientSummary = await client.OrTimeout();
|
var clientSummary = await client.OrTimeout();
|
||||||
|
|
||||||
Assert.Equal(0, clientSummary.Received.Count);
|
Assert.Equal(0, clientSummary.Received.Count);
|
||||||
|
|
||||||
Assert.Equal(1, serverSummary.Received.Count);
|
Assert.Equal(1, serverSummary.Received.Count);
|
||||||
Assert.True(serverSummary.Received[0].EndOfMessage);
|
Assert.True(serverSummary.Received[0].EndOfMessage);
|
||||||
Assert.Equal(WebSocketOpcode.Text, serverSummary.Received[0].Opcode);
|
Assert.Equal(WebSocketOpcode.Text, serverSummary.Received[0].Opcode);
|
||||||
Assert.Equal(decoded, Encoding.UTF8.GetString(serverSummary.Received[0].Payload.ToArray()));
|
Assert.Equal(decoded, Encoding.UTF8.GetString(serverSummary.Received[0].Payload.ToArray()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0x6C, 0x6C, 0x6F }, "Hello")]
|
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0x6C, 0x6C, 0x6F }, "Hello")]
|
||||||
|
|
||||||
[InlineData(new byte[0], new byte[] { 0xC2, 0xA7 }, "§")]
|
[InlineData(new byte[0], new byte[] { 0xC2, 0xA7 }, "§")]
|
||||||
[InlineData(new byte[] { 0xC2 }, new byte[] { 0xA7 }, "§")]
|
[InlineData(new byte[] { 0xC2 }, new byte[] { 0xA7 }, "§")]
|
||||||
[InlineData(new byte[] { 0xC2, 0xA7 }, new byte[0], "§")]
|
[InlineData(new byte[] { 0xC2, 0xA7 }, new byte[0], "§")]
|
||||||
|
|
||||||
[InlineData(new byte[0], new byte[] { 0xC2, 0xA2 }, "¢")]
|
[InlineData(new byte[0], new byte[] { 0xC2, 0xA2 }, "¢")]
|
||||||
[InlineData(new byte[] { 0xC2 }, new byte[] { 0xA2 }, "¢")]
|
[InlineData(new byte[] { 0xC2 }, new byte[] { 0xA2 }, "¢")]
|
||||||
[InlineData(new byte[] { 0xC2, 0xA2 }, new byte[0], "¢")]
|
[InlineData(new byte[] { 0xC2, 0xA2 }, new byte[0], "¢")]
|
||||||
|
|
||||||
[InlineData(new byte[0], new byte[] { 0xE0, 0xA0, 0x80 }, "\u0800")]
|
[InlineData(new byte[0], new byte[] { 0xE0, 0xA0, 0x80 }, "\u0800")]
|
||||||
[InlineData(new byte[] { 0xE0 }, new byte[] { 0xA0, 0x80 }, "\u0800")]
|
[InlineData(new byte[] { 0xE0 }, new byte[] { 0xA0, 0x80 }, "\u0800")]
|
||||||
[InlineData(new byte[] { 0xE0, 0xA0 }, new byte[] { 0x80 }, "\u0800")]
|
[InlineData(new byte[] { 0xE0, 0xA0 }, new byte[] { 0x80 }, "\u0800")]
|
||||||
[InlineData(new byte[] { 0xE0, 0xA0, 0x80 }, new byte[0], "\u0800")]
|
[InlineData(new byte[] { 0xE0, 0xA0, 0x80 }, new byte[0], "\u0800")]
|
||||||
|
|
||||||
[InlineData(new byte[0], new byte[] { 0xE0, 0xA4, 0x80 }, "\u0900")]
|
[InlineData(new byte[0], new byte[] { 0xE0, 0xA4, 0x80 }, "\u0900")]
|
||||||
[InlineData(new byte[] { 0xE0 }, new byte[] { 0xA4, 0x80 }, "\u0900")]
|
[InlineData(new byte[] { 0xE0 }, new byte[] { 0xA4, 0x80 }, "\u0900")]
|
||||||
[InlineData(new byte[] { 0xE0, 0xA4 }, new byte[] { 0x80 }, "\u0900")]
|
[InlineData(new byte[] { 0xE0, 0xA4 }, new byte[] { 0x80 }, "\u0900")]
|
||||||
[InlineData(new byte[] { 0xE0, 0xA4, 0x80 }, new byte[0], "\u0900")]
|
[InlineData(new byte[] { 0xE0, 0xA4, 0x80 }, new byte[0], "\u0900")]
|
||||||
|
|
||||||
[InlineData(new byte[0], new byte[] { 0xF0, 0x90, 0x80, 0x80 }, "\U00010000")]
|
[InlineData(new byte[0], new byte[] { 0xF0, 0x90, 0x80, 0x80 }, "\U00010000")]
|
||||||
[InlineData(new byte[] { 0xF0 }, new byte[] { 0x90, 0x80, 0x80 }, "\U00010000")]
|
[InlineData(new byte[] { 0xF0 }, new byte[] { 0x90, 0x80, 0x80 }, "\U00010000")]
|
||||||
[InlineData(new byte[] { 0xF0, 0x90 }, new byte[] { 0x80, 0x80 }, "\U00010000")]
|
[InlineData(new byte[] { 0xF0, 0x90 }, new byte[] { 0x80, 0x80 }, "\U00010000")]
|
||||||
[InlineData(new byte[] { 0xF0, 0x90, 0x80 }, new byte[] { 0x80 }, "\U00010000")]
|
[InlineData(new byte[] { 0xF0, 0x90, 0x80 }, new byte[] { 0x80 }, "\U00010000")]
|
||||||
[InlineData(new byte[] { 0xF0, 0x90, 0x80, 0x80 }, new byte[0], "\U00010000")]
|
[InlineData(new byte[] { 0xF0, 0x90, 0x80, 0x80 }, new byte[0], "\U00010000")]
|
||||||
public async Task ValidMultiFramePayloads(byte[] payload1, byte[] payload2, string decoded)
|
public async Task ValidMultiFramePayloads(byte[] payload1, byte[] payload2, string decoded)
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
{
|
{
|
||||||
using (var pair = WebSocketPair.Create())
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
|
||||||
|
var frame = new WebSocketFrame(
|
||||||
|
endOfMessage: false,
|
||||||
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: ReadableBuffer.Create(payload1));
|
||||||
|
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
||||||
|
frame = new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Continuation,
|
||||||
|
payload: ReadableBuffer.Create(payload2));
|
||||||
|
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
||||||
|
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
|
var serverSummary = await server.OrTimeout();
|
||||||
|
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
||||||
|
var clientSummary = await client.OrTimeout();
|
||||||
|
|
||||||
|
Assert.Equal(0, clientSummary.Received.Count);
|
||||||
|
|
||||||
|
Assert.Equal(2, serverSummary.Received.Count);
|
||||||
|
Assert.False(serverSummary.Received[0].EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Text, serverSummary.Received[0].Opcode);
|
||||||
|
Assert.True(serverSummary.Received[1].EndOfMessage);
|
||||||
|
Assert.Equal(WebSocketOpcode.Continuation, serverSummary.Received[1].Opcode);
|
||||||
|
|
||||||
|
var finalPayload = serverSummary.Received.SelectMany(f => f.Payload.ToArray()).ToArray();
|
||||||
|
Assert.Equal(decoded, Encoding.UTF8.GetString(finalPayload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
|
||||||
|
// Continuation byte as first byte of code point
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65, 0x80, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65, 0x99, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65, 0xAB, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65, 0xB0, 0x6C, 0x6F })]
|
||||||
|
|
||||||
|
// Incomplete Code Point
|
||||||
|
[InlineData(new byte[] { 0xC2 })]
|
||||||
|
[InlineData(new byte[] { 0xE0 })]
|
||||||
|
[InlineData(new byte[] { 0xE0, 0xA0 })]
|
||||||
|
[InlineData(new byte[] { 0xE0, 0xA4 })]
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x90, 0x80 })]
|
||||||
|
|
||||||
|
// Overlong Encoding
|
||||||
|
|
||||||
|
// 'H' (1 byte char) encoded with 2, 3 and 4 bytes
|
||||||
|
[InlineData(new byte[] { 0xC1, 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0xE0, 0x81, 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x80, 0x81, 0x88 })]
|
||||||
|
|
||||||
|
// '§' (2 byte char) encoded with 3 and 4 bytes
|
||||||
|
[InlineData(new byte[] { 0xE0, 0x82, 0xA7 })]
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x80, 0x82, 0xA7 })]
|
||||||
|
|
||||||
|
// '\u0800' (3 byte char) encoded with 4 bytes
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x80, 0xA0, 0x80 })]
|
||||||
|
public async Task InvalidSingleFramePayloads(byte[] payload)
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
|
{
|
||||||
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
|
||||||
|
var frame = new WebSocketFrame(
|
||||||
|
endOfMessage: true,
|
||||||
|
opcode: WebSocketOpcode.Text,
|
||||||
|
payload: ReadableBuffer.Create(payload));
|
||||||
|
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
||||||
|
var clientSummary = await client.OrTimeout();
|
||||||
|
var serverSummary = await server.OrTimeout();
|
||||||
|
|
||||||
|
Assert.Equal(0, serverSummary.Received.Count);
|
||||||
|
Assert.Equal(0, clientSummary.Received.Count);
|
||||||
|
Assert.Equal(WebSocketCloseStatus.InvalidPayloadData, clientSummary.CloseResult.Status);
|
||||||
|
Assert.Equal("An invalid Text frame payload was received", clientSummary.CloseResult.Description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
|
||||||
|
// Continuation byte as first byte of code point
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0x80, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0x99, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0xAB, 0x6C, 0x6F })]
|
||||||
|
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0xB0, 0x6C, 0x6F })]
|
||||||
|
|
||||||
|
// Incomplete Code Point
|
||||||
|
[InlineData(new byte[] { 0xC2 }, new byte[0])]
|
||||||
|
[InlineData(new byte[] { 0xE0 }, new byte[0])]
|
||||||
|
[InlineData(new byte[] { 0xE0, 0xA0 }, new byte[0])]
|
||||||
|
[InlineData(new byte[] { 0xE0, 0xA4 }, new byte[0])]
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x90, 0x80 }, new byte[0])]
|
||||||
|
|
||||||
|
// Overlong Encoding
|
||||||
|
|
||||||
|
// 'H' (1 byte char) encoded with 2, 3 and 4 bytes
|
||||||
|
[InlineData(new byte[] { 0xC1 }, new byte[] { 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0xE0 }, new byte[] { 0x81, 0x88 })]
|
||||||
|
[InlineData(new byte[] { 0xF0 }, new byte[] { 0x80, 0x81, 0x88 })]
|
||||||
|
|
||||||
|
// '§' (2 byte char) encoded with 3 and 4 bytes
|
||||||
|
[InlineData(new byte[] { 0xE0, 0x82 }, new byte[] { 0xA7 })]
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x80 }, new byte[] { 0x82, 0xA7 })]
|
||||||
|
|
||||||
|
// '\u0800' (3 byte char) encoded with 4 bytes
|
||||||
|
[InlineData(new byte[] { 0xF0, 0x80 }, new byte[] { 0xA0, 0x80 })]
|
||||||
|
public async Task InvalidMultiFramePayloads(byte[] payload1, byte[] payload2)
|
||||||
|
{
|
||||||
|
using (var pair = WebSocketPair.Create())
|
||||||
|
{
|
||||||
|
var timeoutToken = TestUtil.CreateTimeoutToken();
|
||||||
|
using (timeoutToken.Register(() => pair.Dispose()))
|
||||||
{
|
{
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
||||||
|
|
@ -92,136 +206,19 @@ namespace Microsoft.Extensions.WebSockets.Internal.Tests
|
||||||
opcode: WebSocketOpcode.Continuation,
|
opcode: WebSocketOpcode.Continuation,
|
||||||
payload: ReadableBuffer.Create(payload2));
|
payload: ReadableBuffer.Create(payload2));
|
||||||
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
||||||
await pair.ClientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
|
||||||
var serverSummary = await server.OrTimeout();
|
|
||||||
await pair.ServerSocket.CloseAsync(WebSocketCloseStatus.NormalClosure).OrTimeout();
|
|
||||||
var clientSummary = await client.OrTimeout();
|
var clientSummary = await client.OrTimeout();
|
||||||
|
var serverSummary = await server.OrTimeout();
|
||||||
|
|
||||||
Assert.Equal(0, clientSummary.Received.Count);
|
Assert.Equal(1, serverSummary.Received.Count);
|
||||||
|
|
||||||
Assert.Equal(2, serverSummary.Received.Count);
|
|
||||||
Assert.False(serverSummary.Received[0].EndOfMessage);
|
Assert.False(serverSummary.Received[0].EndOfMessage);
|
||||||
Assert.Equal(WebSocketOpcode.Text, serverSummary.Received[0].Opcode);
|
Assert.Equal(WebSocketOpcode.Text, serverSummary.Received[0].Opcode);
|
||||||
Assert.True(serverSummary.Received[1].EndOfMessage);
|
Assert.Equal(payload1, serverSummary.Received[0].Payload.ToArray());
|
||||||
Assert.Equal(WebSocketOpcode.Continuation, serverSummary.Received[1].Opcode);
|
|
||||||
|
|
||||||
var finalPayload = serverSummary.Received.SelectMany(f => f.Payload.ToArray()).ToArray();
|
|
||||||
Assert.Equal(decoded, Encoding.UTF8.GetString(finalPayload));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
|
|
||||||
// Continuation byte as first byte of code point
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65, 0x80, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65, 0x99, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65, 0xAB, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65, 0xB0, 0x6C, 0x6F })]
|
|
||||||
|
|
||||||
// Incomplete Code Point
|
|
||||||
[InlineData(new byte[] { 0xC2 })]
|
|
||||||
[InlineData(new byte[] { 0xE0 })]
|
|
||||||
[InlineData(new byte[] { 0xE0, 0xA0 })]
|
|
||||||
[InlineData(new byte[] { 0xE0, 0xA4 })]
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x90, 0x80 })]
|
|
||||||
|
|
||||||
// Overlong Encoding
|
|
||||||
|
|
||||||
// 'H' (1 byte char) encoded with 2, 3 and 4 bytes
|
|
||||||
[InlineData(new byte[] { 0xC1, 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0xE0, 0x81, 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x80, 0x81, 0x88 })]
|
|
||||||
|
|
||||||
// '§' (2 byte char) encoded with 3 and 4 bytes
|
|
||||||
[InlineData(new byte[] { 0xE0, 0x82, 0xA7 })]
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x80, 0x82, 0xA7 })]
|
|
||||||
|
|
||||||
// '\u0800' (3 byte char) encoded with 4 bytes
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x80, 0xA0, 0x80 })]
|
|
||||||
public async Task InvalidSingleFramePayloads(byte[] payload)
|
|
||||||
{
|
|
||||||
using (var pair = WebSocketPair.Create())
|
|
||||||
{
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
var frame = new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: ReadableBuffer.Create(payload));
|
|
||||||
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
var serverSummary = await server.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(0, serverSummary.Received.Count);
|
|
||||||
Assert.Equal(0, clientSummary.Received.Count);
|
Assert.Equal(0, clientSummary.Received.Count);
|
||||||
Assert.Equal(WebSocketCloseStatus.InvalidPayloadData, clientSummary.CloseResult.Status);
|
Assert.Equal(WebSocketCloseStatus.InvalidPayloadData, clientSummary.CloseResult.Status);
|
||||||
Assert.Equal("An invalid Text frame payload was received", clientSummary.CloseResult.Description);
|
Assert.Equal("An invalid Text frame payload was received", clientSummary.CloseResult.Description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
|
|
||||||
// Continuation byte as first byte of code point
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0x80, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0x99, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0xAB, 0x6C, 0x6F })]
|
|
||||||
[InlineData(new byte[] { 0x48, 0x65 }, new byte[] { 0xB0, 0x6C, 0x6F })]
|
|
||||||
|
|
||||||
// Incomplete Code Point
|
|
||||||
[InlineData(new byte[] { 0xC2 }, new byte[0])]
|
|
||||||
[InlineData(new byte[] { 0xE0 }, new byte[0])]
|
|
||||||
[InlineData(new byte[] { 0xE0, 0xA0 }, new byte[0])]
|
|
||||||
[InlineData(new byte[] { 0xE0, 0xA4 }, new byte[0])]
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x90, 0x80 }, new byte[0])]
|
|
||||||
|
|
||||||
// Overlong Encoding
|
|
||||||
|
|
||||||
// 'H' (1 byte char) encoded with 2, 3 and 4 bytes
|
|
||||||
[InlineData(new byte[] { 0xC1 }, new byte[] { 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0xE0 }, new byte[] { 0x81, 0x88 })]
|
|
||||||
[InlineData(new byte[] { 0xF0 }, new byte[] { 0x80, 0x81, 0x88 })]
|
|
||||||
|
|
||||||
// '§' (2 byte char) encoded with 3 and 4 bytes
|
|
||||||
[InlineData(new byte[] { 0xE0, 0x82 }, new byte[] { 0xA7 })]
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x80 }, new byte[] { 0x82, 0xA7 })]
|
|
||||||
|
|
||||||
// '\u0800' (3 byte char) encoded with 4 bytes
|
|
||||||
[InlineData(new byte[] { 0xF0, 0x80 }, new byte[] { 0xA0, 0x80 })]
|
|
||||||
public async Task InvalidMultiFramePayloads(byte[] payload1, byte[] payload2)
|
|
||||||
{
|
|
||||||
using (var pair = WebSocketPair.Create())
|
|
||||||
{
|
|
||||||
var timeoutToken = TestUtil.CreateTimeoutToken();
|
|
||||||
using (timeoutToken.Register(() => pair.Dispose()))
|
|
||||||
{
|
|
||||||
var server = pair.ServerSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
var client = pair.ClientSocket.ExecuteAndCaptureFramesAsync();
|
|
||||||
|
|
||||||
var frame = new WebSocketFrame(
|
|
||||||
endOfMessage: false,
|
|
||||||
opcode: WebSocketOpcode.Text,
|
|
||||||
payload: ReadableBuffer.Create(payload1));
|
|
||||||
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
|
||||||
frame = new WebSocketFrame(
|
|
||||||
endOfMessage: true,
|
|
||||||
opcode: WebSocketOpcode.Continuation,
|
|
||||||
payload: ReadableBuffer.Create(payload2));
|
|
||||||
await pair.ClientSocket.SendAsync(frame).OrTimeout();
|
|
||||||
var clientSummary = await client.OrTimeout();
|
|
||||||
var serverSummary = await server.OrTimeout();
|
|
||||||
|
|
||||||
Assert.Equal(1, serverSummary.Received.Count);
|
|
||||||
Assert.False(serverSummary.Received[0].EndOfMessage);
|
|
||||||
Assert.Equal(WebSocketOpcode.Text, serverSummary.Received[0].Opcode);
|
|
||||||
Assert.Equal(payload1, serverSummary.Received[0].Payload.ToArray());
|
|
||||||
|
|
||||||
Assert.Equal(0, clientSummary.Received.Count);
|
|
||||||
Assert.Equal(WebSocketCloseStatus.InvalidPayloadData, clientSummary.CloseResult.Status);
|
|
||||||
Assert.Equal("An invalid Text frame payload was received", clientSummary.CloseResult.Description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue