1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
const std = @import("std");
const c9 = @import("cloud9");
const testing = std.testing;
const http = c9.http;
test "client and server preserve 9P frames at WebSocket length boundaries" {
var bytes: [70000]u8 = undefined;
var data: [66000]u8 = @splat(0xa5);
var receive_buffer: [70000]u8 = undefined;
for ([_]usize{ 0, 114, 115, 65524, 65525 }) |length| {
const frame = try c9.encode(.{ .rread = .{ .data = data[0..length] } }, 7, &bytes);
for ([_]http.Role{ .client, .server }) |role| {
var writer: std.Io.Writer.Allocating = .init(testing.allocator);
defer writer.deinit();
var empty: std.Io.Reader = .fixed("");
var sender: http.WebSocket = .{ .input = &empty, .output = &writer.writer, .role = role };
try sender.send(frame, .binary, if (role == .client) .{ 1, 2, 3, 4 } else null);
var reader: testing.Reader = .init(&.{}, &.{.{ .buffer = writer.written() }});
reader.artificial_limit = .limited(1);
var receiver: http.WebSocket = .{ .input = &reader.interface, .output = &writer.writer, .role = if (role == .client) .server else .client };
const message = try receiver.receive(&receive_buffer);
try testing.expectEqual(.binary, message.opcode);
try testing.expectEqualSlices(u8, frame, message.data);
}
}
}
test "fragmented message survives an interleaved ping" {
// Rflush tag 1 split after its size field. Ping between the fragments.
var reader: std.Io.Reader = .fixed(&.{ 0x02, 4, 7, 0, 0, 0, 0x89, 1, 'x', 0x80, 3, 109, 1, 0 });
var writer: std.Io.Writer = .fixed(&.{});
var socket: http.WebSocket = .{ .input = &reader, .output = &writer, .role = .client };
var buffer: [64]u8 = undefined;
const ping = try socket.receive(&buffer);
try testing.expectEqual(.ping, ping.opcode);
try testing.expectEqualStrings("x", ping.data);
const frame = try socket.receive(&buffer);
const decoded = try c9.decode(frame.data);
try testing.expectEqual(c9.Type.rflush, decoded.msg.msgType());
try testing.expectEqual(@as(u16, 1), decoded.tag);
}
test "framing rejects invalid masks, reserved bits, lengths and controls" {
const cases = .{
.{ &[_]u8{ 0x82, 0x80 }, error.InvalidMask },
.{ &[_]u8{ 0xc2, 0 }, error.ReservedBits },
.{ &[_]u8{ 0x82, 126, 0, 7 }, error.NonCanonicalLength },
.{ &[_]u8{ 0x89, 126, 0, 126 }, error.InvalidControl },
.{ &[_]u8{ 0x09, 0 }, error.InvalidControl },
.{ &[_]u8{ 0x80, 0 }, error.UnexpectedContinuation },
.{ &[_]u8{ 0x81, 0 }, error.ExpectedBinary },
.{ &[_]u8{ 0x88, 1, 0 }, error.InvalidClose },
.{ &[_]u8{ 0x88, 2, 3, 237 }, error.InvalidClose },
.{ &[_]u8{ 0x88, 3, 3, 232, 255 }, error.InvalidClose },
.{ &[_]u8{ 0x82, 100 }, error.MessageTooLarge },
};
inline for (cases) |case| {
var reader: std.Io.Reader = .fixed(case[0]);
var writer: std.Io.Writer = .fixed(&.{});
var socket: http.WebSocket = .{ .input = &reader, .output = &writer, .role = .client };
var buffer: [64]u8 = undefined;
try testing.expectError(case[1], socket.receive(&buffer));
}
}
test "binary messages must contain exactly one complete 9P frame" {
var reader: std.Io.Reader = .fixed(&.{ 0x82, 7, 8, 0, 0, 0, 109, 1, 0 });
var writer: std.Io.Writer = .fixed(&.{});
var socket: http.WebSocket = .{ .input = &reader, .output = &writer, .role = .client };
var buffer: [64]u8 = undefined;
try testing.expectError(error.Truncated, socket.receive(&buffer));
}
test "upgrade validates nonce and uses RFC 6455 accept value" {
var reader: std.Io.Reader = .fixed("GET /9p HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive, Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n");
var writer: std.Io.Writer.Allocating = .init(testing.allocator);
defer writer.deinit();
var server: std.http.Server = .init(&reader, &writer.writer);
var request = try server.receiveHead();
_ = try http.accept(&request);
try testing.expect(std.mem.find(u8, writer.written(), "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") != null);
}
test "upgrade rejects invalid nonce before responding" {
inline for (.{ "not-a-nonce", "AAAAAAAAAAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAAAAAAAAA=", "!!!!!!!!!!!!!!!!!!!!!!==" }) |nonce| {
var reader: std.Io.Reader = .fixed("GET /9p HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: " ++ nonce ++ "\r\n\r\n");
var writer: std.Io.Writer = .fixed(&.{});
var server: std.http.Server = .init(&reader, &writer);
var request = try server.receiveHead();
try testing.expectError(error.InvalidUpgrade, http.accept(&request));
}
}
|