websocket
0

Configure Feed

Select the types of activity you want to include in your feed.

websocket.zig / src / t.zig
7.6 kB 248 lines
1const std = @import("std"); 2const proto = @import("proto.zig"); 3 4const Io = std.Io; 5const net = Io.net; 6const ArrayList = std.ArrayList; 7 8const Message = proto.Message; 9 10pub const allocator = std.testing.allocator; 11 12pub fn expectEqual(expected: anytype, actual: anytype) !void { 13 try std.testing.expectEqual(expected, actual); 14} 15 16pub const expectError = std.testing.expectError; 17pub const expectString = std.testing.expectEqualStrings; 18pub const expectSlice = std.testing.expectEqualSlices; 19 20pub fn getRandom() std.Random.DefaultPrng { 21 const io = std.Options.debug_io; 22 var seed_bytes: [8]u8 = undefined; 23 io.random(&seed_bytes); 24 const seed: u64 = @bitCast(seed_bytes); 25 return std.Random.DefaultPrng.init(seed); 26} 27 28pub var arena = std.heap.ArenaAllocator.init(allocator); 29pub fn reset() void { 30 _ = arena.reset(.free_all); 31} 32 33pub const Writer = struct { 34 pos: usize, 35 buf: std.ArrayList(u8), 36 random: std.Random.DefaultPrng, 37 38 pub fn init() Writer { 39 return .{ 40 .pos = 0, 41 .buf = .empty, 42 .random = getRandom(), 43 }; 44 } 45 46 pub fn deinit(self: *Writer) void { 47 self.buf.deinit(allocator); 48 } 49 50 pub fn ping(self: *Writer) void { 51 return self.pingPayload(""); 52 } 53 54 pub fn pong(self: *Writer) void { 55 return self.frame(true, 10, "", 0); 56 } 57 58 pub fn pingPayload(self: *Writer, payload: []const u8) void { 59 return self.frame(true, 9, payload, 0); 60 } 61 62 pub fn textFrame(self: *Writer, fin: bool, payload: []const u8) void { 63 return self.frame(fin, 1, payload, 0); 64 } 65 66 pub fn cont(self: *Writer, fin: bool, payload: []const u8) void { 67 return self.frame(fin, 0, payload, 0); 68 } 69 70 pub fn frame(self: *Writer, fin: bool, op_code: u8, payload: []const u8, reserved: u8) void { 71 var buf = &self.buf; 72 73 const l = payload.len; 74 var length_of_length: usize = 0; 75 76 if (l > 125) { 77 if (l < 65536) { 78 length_of_length = 2; 79 } else { 80 length_of_length = 8; 81 } 82 } 83 84 // 2 byte header + length_of_length + mask + payload_length 85 const needed = 2 + length_of_length + 4 + l; 86 buf.ensureUnusedCapacity(allocator, needed) catch unreachable; 87 88 if (fin) { 89 buf.appendAssumeCapacity(128 | op_code | reserved); 90 } else { 91 buf.appendAssumeCapacity(op_code | reserved); 92 } 93 94 if (length_of_length == 0) { 95 buf.appendAssumeCapacity(128 | @as(u8, @intCast(l))); 96 } else if (length_of_length == 2) { 97 buf.appendAssumeCapacity(128 | 126); 98 buf.appendAssumeCapacity(@intCast((l >> 8) & 0xFF)); 99 buf.appendAssumeCapacity(@intCast(l & 0xFF)); 100 } else { 101 buf.appendAssumeCapacity(128 | 127); 102 buf.appendAssumeCapacity(@intCast((l >> 56) & 0xFF)); 103 buf.appendAssumeCapacity(@intCast((l >> 48) & 0xFF)); 104 buf.appendAssumeCapacity(@intCast((l >> 40) & 0xFF)); 105 buf.appendAssumeCapacity(@intCast((l >> 32) & 0xFF)); 106 buf.appendAssumeCapacity(@intCast((l >> 24) & 0xFF)); 107 buf.appendAssumeCapacity(@intCast((l >> 16) & 0xFF)); 108 buf.appendAssumeCapacity(@intCast((l >> 8) & 0xFF)); 109 buf.appendAssumeCapacity(@intCast(l & 0xFF)); 110 } 111 112 var mask: [4]u8 = undefined; 113 self.random.random().bytes(&mask); 114 115 buf.appendSliceAssumeCapacity(&mask); 116 for (payload, 0..) |b, i| { 117 buf.appendAssumeCapacity(b ^ mask[i & 3]); 118 } 119 } 120 121 pub fn bytes(self: *const Writer) []const u8 { 122 return self.buf.items; 123 } 124 125 pub fn clear(self: *Writer) void { 126 self.pos = 0; 127 self.buf.clearRetainingCapacity(); 128 } 129 130 pub fn read( 131 self: *Writer, 132 buf: []u8, 133 ) !usize { 134 const data = self.buf.items[self.pos..]; 135 136 if (data.len == 0 or buf.len == 0) { 137 return 0; 138 } 139 140 // randomly fragment the data 141 const to_read = self.random.random().intRangeAtMost(usize, 1, @min(data.len, buf.len)); 142 @memcpy(buf[0..to_read], data[0..to_read]); 143 self.pos += to_read; 144 return to_read; 145 } 146}; 147 148/// Reads directly from a socket, preserving SO_RCVTIMEO as WouldBlock. 149pub const StreamReader = struct { 150 handle: net.Socket.Handle, 151 152 pub fn read(self: *StreamReader, buf: []u8) !usize { 153 return std.posix.read(self.handle, buf) catch |err| { 154 return switch (err) { 155 error.ConnectionResetByPeer => error.ConnectionResetByPeer, 156 error.WouldBlock => error.WouldBlock, 157 else => error.Unexpected, 158 }; 159 }; 160 } 161}; 162 163pub const SocketPair = struct { 164 writer: Writer, 165 io: Io, 166 client: net.Stream, 167 server: net.Stream, 168 server_taken: bool = false, 169 170 const Opts = struct { 171 port: ?u16 = null, 172 }; 173 174 pub fn init(opts: Opts) SocketPair { 175 const io = std.Options.debug_io; 176 const port: u16 = opts.port orelse 0; 177 178 // use Io.net to create a listener, connect a client, and accept 179 var listen_addr: net.IpAddress = .{ .ip4 = .loopback(port) }; 180 var server = net.IpAddress.listen(&listen_addr, io, .{ .reuse_address = true }) catch unreachable; 181 182 // get the actual bound address (for ephemeral port) 183 const bound_addr = server.socket.address; 184 185 // connect client 186 const client = net.IpAddress.connect(&bound_addr, io, .{ .mode = .stream }) catch unreachable; 187 188 // accept server-side connection 189 const accepted = server.accept(io) catch unreachable; 190 server.deinit(io); 191 192 return .{ 193 .io = io, 194 .client = client, 195 .server = accepted, 196 .writer = Writer.init(), 197 }; 198 } 199 200 pub fn deinit(self: *SocketPair) void { 201 self.writer.deinit(); 202 self.client.close(self.io); 203 if (!self.server_taken) self.server.close(self.io); 204 } 205 206 pub fn pingPayload(self: *SocketPair, payload: []const u8) void { 207 self.writer.pingPayload(payload); 208 } 209 210 pub fn textFrame(self: *SocketPair, fin: bool, payload: []const u8) void { 211 self.writer.textFrame(fin, payload); 212 } 213 214 pub fn cont(self: *SocketPair, fin: bool, payload: []const u8) void { 215 self.writer.cont(fin, payload); 216 } 217 218 pub fn sendBuf(self: *SocketPair) void { 219 self.ioWriteAll(self.writer.bytes()) catch unreachable; 220 self.writer.clear(); 221 } 222 223 pub fn clientWriteAll(self: *SocketPair, data: []const u8) !void { 224 try self.ioWriteAll(data); 225 } 226 227 fn ioWriteAll(self: *SocketPair, data: []const u8) !void { 228 var remaining = data; 229 while (remaining.len > 0) { 230 // netWrite: header is sent first, data array's last element is the splat pattern. 231 // Pass remaining as header, empty pattern with splat=0. 232 const empty = [_][]const u8{""}; 233 const n = self.io.vtable.netWrite(self.io.userdata, self.client.socket.handle, remaining, &empty, 0) catch { 234 return error.SendError; 235 }; 236 if (n == 0) return error.SendError; 237 remaining = remaining[n..]; 238 } 239 } 240 241 pub fn serverReader(self: *SocketPair) StreamReader { 242 return .{ .handle = self.server.socket.handle }; 243 } 244 245 pub fn clientReader(self: *SocketPair) StreamReader { 246 return .{ .handle = self.client.socket.handle }; 247 } 248};