authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-27 20:18:20-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:29-07:00
log74c56376ee2dfb6100fd8da6cb03425b0e48a779
tree26849cc3a5360ad915ab21cdc616a383866d1381
parentda303bdaf1ae8717df2d4ede9e7dfb215636ae33

std: update http.WebSocket to new API


9 files changed, 449 insertions(+), 420 deletions(-)

lib/std/crypto/tls/Client.zig+1-23
......@@ -894,11 +894,7 @@ pub fn init(
894894pub fn reader(c: *Client) Reader {
895895 return .{
896896 .context = c,
897 .vtable = &.{
898 .read = read,
899 .readVec = readVec,
900 .discard = discard,
901 },
897 .vtable = &.{ .read = read },
902898 };
903899}
904900
......@@ -1225,24 +1221,6 @@ fn read(context: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) R
12251221 }
12261222}
12271223
1228fn readVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize {
1229 var bw: std.io.BufferedWriter = undefined;
1230 bw.initVec(data);
1231 return read(context, &bw, .countVec(data)) catch |err| switch (err) {
1232 error.WriteFailed => unreachable,
1233 else => |e| return e,
1234 };
1235}
1236
1237fn discard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize {
1238 var null_writer: Writer.Null = undefined;
1239 var bw = null_writer.writer().unbuffered();
1240 return read(context, &bw, limit) catch |err| switch (err) {
1241 error.WriteFailed => unreachable,
1242 else => |e| return e,
1243 };
1244}
1245
12461224fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
12471225 c.read_err = err;
12481226 return error.ReadFailed;
lib/std/fs/File.zig+37-2
......@@ -899,6 +899,15 @@ pub fn writeFileAll(self: File, in_file: File, options: BufferedWriter.WriteFile
899899 };
900900}
901901
902/// Memoizes key information about a file handle such as:
903/// * The size from calling stat, or the error that occurred therein.
904/// * The current seek position.
905/// * The error that occurred when trying to seek.
906/// * Whether reading should be done positionally or streaming.
907/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
908/// versus plain variants (e.g. `read`).
909///
910/// Fulfills the `std.io.Reader` interface.
902911pub const Reader = struct {
903912 file: File,
904913 err: ?ReadError = null,
......@@ -951,14 +960,40 @@ pub const Reader = struct {
951960 };
952961 }
953962
963 pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
964 switch (r.mode) {
965 .positional, .positional_reading => {
966 r.pos += offset;
967 },
968 .streaming, .streaming_reading => {
969 const seek_err = r.seek_err orelse e: {
970 if (posix.lseek_CUR(r.file.handle, offset)) |_| {
971 r.pos += offset;
972 return;
973 } else |err| {
974 r.seek_err = err;
975 break :e err;
976 }
977 };
978 if (offset < 0) return seek_err;
979 var remaining = offset;
980 while (remaining > 0) {
981 const n = discard(r, .limited(remaining)) catch |err| switch (err) {};
982 r.pos += n;
983 remaining -= n;
984 }
985 },
986 }
987 }
988
954989 pub fn seekTo(r: *Reader, offset: u64) SeekError!void {
955 // TODO if the offset is after the current offset, seek by discarding.
956 if (r.seek_err) |err| return err;
957990 switch (r.mode) {
958991 .positional, .positional_reading => {
959992 r.pos = offset;
960993 },
961994 .streaming, .streaming_reading => {
995 if (offset >= r.pos) return Reader.seekBy(r, offset - r.pos);
996 if (r.seek_err) |err| return err;
962997 posix.lseek_SET(r.file.handle, offset) catch |err| {
963998 r.seek_err = err;
964999 return err;
lib/std/http.zig+8-10
......@@ -7,7 +7,6 @@ pub const Server = @import("http/Server.zig");
77pub const HeadParser = @import("http/HeadParser.zig");
88pub const ChunkParser = @import("http/ChunkParser.zig");
99pub const HeaderIterator = @import("http/HeaderIterator.zig");
10pub const WebSocket = @import("http/WebSocket.zig");
1110
1211pub const Version = enum {
1312 @"HTTP/1.0",
......@@ -508,7 +507,7 @@ pub const Reader = struct {
508507 fn contentLengthRead(
509508 ctx: ?*anyopaque,
510509 bw: *std.io.BufferedWriter,
511 limit: std.io.Reader.Limit,
510 limit: std.io.Limit,
512511 ) std.io.Reader.RwError!usize {
513512 const reader: *Reader = @alignCast(@ptrCast(ctx));
514513 const remaining_content_length = &reader.state.body_remaining_content_length;
......@@ -535,7 +534,7 @@ pub const Reader = struct {
535534 return n;
536535 }
537536
538 fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {
537 fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
539538 const reader: *Reader = @alignCast(@ptrCast(ctx));
540539 const remaining_content_length = &reader.state.body_remaining_content_length;
541540 const remaining = remaining_content_length.*;
......@@ -551,7 +550,7 @@ pub const Reader = struct {
551550 fn chunkedRead(
552551 ctx: ?*anyopaque,
553552 bw: *std.io.BufferedWriter,
554 limit: std.io.Reader.Limit,
553 limit: std.io.Limit,
555554 ) std.io.Reader.RwError!usize {
556555 const reader: *Reader = @alignCast(@ptrCast(ctx));
557556 const chunk_len_ptr = switch (reader.state) {
......@@ -576,7 +575,7 @@ pub const Reader = struct {
576575 fn chunkedReadEndless(
577576 reader: *Reader,
578577 bw: *std.io.BufferedWriter,
579 limit: std.io.Reader.Limit,
578 limit: std.io.Limit,
580579 chunk_len_ptr: *RemainingChunkLen,
581580 ) (BodyError || std.io.Reader.RwError)!usize {
582581 const in = reader.in;
......@@ -712,7 +711,7 @@ pub const Reader = struct {
712711 return amt_read;
713712 }
714713
715 fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {
714 fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Limit) std.io.Reader.Error!usize {
716715 const reader: *Reader = @alignCast(@ptrCast(ctx));
717716 const chunk_len_ptr = switch (reader.state) {
718717 .ready => return error.EndOfStream,
......@@ -734,7 +733,7 @@ pub const Reader = struct {
734733
735734 fn chunkedDiscardEndless(
736735 reader: *Reader,
737 limit: std.io.Reader.Limit,
736 limit: std.io.Limit,
738737 chunk_len_ptr: *RemainingChunkLen,
739738 ) (BodyError || std.io.Reader.Error)!usize {
740739 const in = reader.in;
......@@ -812,8 +811,8 @@ pub const Decompressor = struct {
812811 buffered_reader: std.io.BufferedReader,
813812
814813 pub const Compression = union(enum) {
815 deflate: std.compress.zlib.Decompressor,
816 gzip: std.compress.gzip.Decompressor,
814 deflate: std.compress.flate.Decompressor,
815 gzip: std.compress.flate.Decompressor,
817816 zstd: std.compress.zstd.Decompress,
818817 none: void,
819818 };
......@@ -1238,7 +1237,6 @@ test {
12381237 _ = Method;
12391238 _ = ChunkParser;
12401239 _ = HeadParser;
1241 _ = WebSocket;
12421240
12431241 if (builtin.os.tag != .wasi) {
12441242 _ = Client;
lib/std/http/Server.zig+288-63
......@@ -57,6 +57,12 @@ pub const Request = struct {
5757 /// Pointers in this struct are invalidated with the next call to
5858 /// `receiveHead`.
5959 head: Head,
60 respond_err: ?RespondError,
61
62 pub const RespondError = error{
63 /// The request contained an `expect` header with an unrecognized value.
64 HttpExpectationFailed,
65 };
6066
6167 pub const Head = struct {
6268 method: http.Method,
......@@ -306,7 +312,7 @@ pub const Request = struct {
306312 request: *Request,
307313 content: []const u8,
308314 options: RespondOptions,
309 ) std.io.Writer.Error!void {
315 ) ExpectContinueError!void {
310316 try respondUnflushed(request, content, options);
311317 try request.server.out.flush();
312318 }
......@@ -315,7 +321,7 @@ pub const Request = struct {
315321 request: *Request,
316322 content: []const u8,
317323 options: RespondOptions,
318 ) std.io.Writer.Error!void {
324 ) ExpectContinueError!void {
319325 assert(options.status != .@"continue");
320326 if (std.debug.runtime_safety) {
321327 for (options.extra_headers) |header| {
......@@ -325,6 +331,7 @@ pub const Request = struct {
325331 assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
326332 }
327333 }
334 try writeExpectContinue(request);
328335
329336 const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none;
330337 const server_keep_alive = !transfer_encoding_none and options.keep_alive;
......@@ -333,17 +340,6 @@ pub const Request = struct {
333340 const phrase = options.reason orelse options.status.phrase() orelse "";
334341
335342 const out = request.server.out;
336 if (request.head.expect != null) {
337 // reader() and hence discardBody() above sets expect to null if it
338 // is handled. So the fact that it is not null here means unhandled.
339 var vecs: [3][]const u8 = .{
340 "HTTP/1.1 417 Expectation Failed\r\n",
341 if (keep_alive) "" else "connection: close\r\n",
342 "content-length: 0\r\n\r\n",
343 };
344 try out.writeVecAll(&vecs);
345 return;
346 }
347343 try out.print("{s} {d} {s}\r\n", .{
348344 @tagName(options.version), @intFromEnum(options.status), phrase,
349345 });
......@@ -402,6 +398,7 @@ pub const Request = struct {
402398 ///
403399 /// Asserts status is not `continue`.
404400 pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) std.io.Writer.Error!http.BodyWriter {
401 try writeExpectContinue(request);
405402 const o = options.respond_options;
406403 assert(o.status != .@"continue");
407404 const transfer_encoding_none = (o.transfer_encoding orelse .chunked) == .none;
......@@ -410,43 +407,34 @@ pub const Request = struct {
410407 const phrase = o.reason orelse o.status.phrase() orelse "";
411408 const out = request.server.out;
412409
413 const elide_body = if (request.head.expect != null) eb: {
414 // reader() and hence discardBody() above sets expect to null if it
415 // is handled. So the fact that it is not null here means unhandled.
416 try out.writeAll("HTTP/1.1 417 Expectation Failed\r\n");
417 if (!keep_alive) try out.writeAll("connection: close\r\n");
418 try out.writeAll("content-length: 0\r\n\r\n");
419 break :eb true;
420 } else eb: {
421 try out.print("{s} {d} {s}\r\n", .{
422 @tagName(o.version), @intFromEnum(o.status), phrase,
423 });
424
425 switch (o.version) {
426 .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"),
427 .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"),
428 }
410 try out.print("{s} {d} {s}\r\n", .{
411 @tagName(o.version), @intFromEnum(o.status), phrase,
412 });
429413
430 if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
431 .chunked => try out.writeAll("transfer-encoding: chunked\r\n"),
432 .none => {},
433 } else if (options.content_length) |len| {
434 try out.print("content-length: {d}\r\n", .{len});
435 } else {
436 try out.writeAll("transfer-encoding: chunked\r\n");
437 }
414 switch (o.version) {
415 .@"HTTP/1.0" => if (keep_alive) try out.writeAll("connection: keep-alive\r\n"),
416 .@"HTTP/1.1" => if (!keep_alive) try out.writeAll("connection: close\r\n"),
417 }
438418
439 for (o.extra_headers) |header| {
440 assert(header.name.len != 0);
441 try out.writeAll(header.name);
442 try out.writeAll(": ");
443 try out.writeAll(header.value);
444 try out.writeAll("\r\n");
445 }
419 if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
420 .chunked => try out.writeAll("transfer-encoding: chunked\r\n"),
421 .none => {},
422 } else if (options.content_length) |len| {
423 try out.print("content-length: {d}\r\n", .{len});
424 } else {
425 try out.writeAll("transfer-encoding: chunked\r\n");
426 }
446427
428 for (o.extra_headers) |header| {
429 assert(header.name.len != 0);
430 try out.writeAll(header.name);
431 try out.writeAll(": ");
432 try out.writeAll(header.value);
447433 try out.writeAll("\r\n");
448 break :eb request.head.method == .HEAD;
449 };
434 }
435
436 try out.writeAll("\r\n");
437 const elide_body = request.head.method == .HEAD;
450438
451439 return .{
452440 .http_protocol_output = request.server.out,
......@@ -460,36 +448,126 @@ pub const Request = struct {
460448 };
461449 }
462450
463 pub const ReaderError = error{
464 /// Failed to write "100-continue" to the stream.
465 WriteFailed,
466 /// Failed to write "100-continue" to the stream because it ended.
467 EndOfStream,
468 /// The client sent an expect HTTP header value other than
469 /// "100-continue".
470 HttpExpectationFailed,
451 pub const UpgradeRequest = union(enum) {
452 websocket: ?[]const u8,
453 other: []const u8,
454 none,
455 };
456
457 pub fn upgradeRequested(request: *const Request) UpgradeRequest {
458 switch (request.head.version) {
459 .@"HTTP/1.0" => return null,
460 .@"HTTP/1.1" => if (request.head.method != .GET) return null,
461 }
462
463 var sec_websocket_key: ?[]const u8 = null;
464 var upgrade_name: ?[]const u8 = null;
465 var it = request.iterateHeaders();
466 while (it.next()) |header| {
467 if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) {
468 sec_websocket_key = header.value;
469 } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) {
470 upgrade_name = header.value;
471 }
472 }
473
474 const name = upgrade_name orelse return .none;
475 if (std.ascii.eqlIgnoreCase(name, "websocket")) return .{ .websocket = sec_websocket_key };
476 return .{ .other = name };
477 }
478
479 pub const WebSocketOptions = struct {
480 /// The value from `UpgradeRequest.websocket` (sec-websocket-key header value).
481 key: []const u8,
482 reason: ?[]const u8 = null,
483 extra_headers: []const http.Header = &.{},
471484 };
472485
486 /// The header is not guaranteed to be sent until `WebSocket.flush` is
487 /// called on the returned struct.
488 pub fn respondWebSocket(request: *Request, options: WebSocketOptions) std.io.Writer.Error!WebSocket {
489 if (request.head.expect != null) return error.HttpExpectationFailed;
490
491 const out = request.server.out;
492 const version: http.Version = .@"HTTP/1.1";
493 const status: http.Status = .switching_protocols;
494 const phrase = options.reason orelse status.phrase() orelse "";
495
496 assert(request.head.version == version);
497 assert(request.head.method == .GET);
498
499 var sha1 = std.crypto.hash.Sha1.init(.{});
500 sha1.update(options.key);
501 sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
502 var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined;
503 sha1.final(&digest);
504 try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase });
505 try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: ");
506 const base64_digest = try out.writableArray(28);
507 assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);
508 out.advance(base64_digest.len);
509 try out.writeAll("\r\n");
510
511 for (options.extra_headers) |header| {
512 assert(header.name.len != 0);
513 try out.writeAll(header.name);
514 try out.writeAll(": ");
515 try out.writeAll(header.value);
516 try out.writeAll("\r\n");
517 }
518
519 try out.writeAll("\r\n");
520
521 return .{
522 .input = request.server.reader.in,
523 .output = request.server.out,
524 .key = options.key,
525 };
526 }
527
473528 /// In the case that the request contains "expect: 100-continue", this
474529 /// function writes the continuation header, which means it can fail with a
475530 /// write error. After sending the continuation header, it sets the
476531 /// request's expect field to `null`.
477532 ///
478533 /// Asserts that this function is only called once.
479 pub fn reader(request: *Request) ReaderError!std.io.Reader {
534 ///
535 /// See `readerExpectNone` for an infallible alternative that cannot write
536 /// to the server output stream.
537 pub fn readerExpectContinue(request: *Request) ExpectContinueError!std.io.Reader {
538 const flush = request.head.expect != null;
539 try writeExpectContinue(request);
540 if (flush) try request.server.out.flush();
541 return readerExpectNone(request);
542 }
543
544 /// Asserts the expect header is `null`. The caller must handle the
545 /// expectation manually and then set the value to `null` prior to calling
546 /// this function.
547 ///
548 /// Asserts that this function is only called once.
549 pub fn readerExpectNone(request: *Request) std.io.Reader {
480550 assert(request.server.reader.state == .received_head);
481 if (request.head.expect) |expect| {
482 if (mem.eql(u8, expect, "100-continue")) {
483 try request.server.out.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
484 request.head.expect = null;
485 } else {
486 return error.HttpExpectationFailed;
487 }
488 }
551 assert(request.head.expect == null);
489552 if (!request.head.method.requestHasBody()) return .ending;
490553 return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length);
491554 }
492555
556 pub const ExpectContinueError = error{
557 /// Failed to write "HTTP/1.1 100 Continue\r\n\r\n" to the stream.
558 WriteFailed,
559 /// The client sent an expect HTTP header value other than
560 /// "100-continue".
561 HttpExpectationFailed,
562 };
563
564 pub fn writeExpectContinue(request: *Request) ExpectContinueError!void {
565 const expect = request.head.expect orelse return;
566 if (!mem.eql(u8, expect, "100-continue")) return error.HttpExpectationFailed;
567 try request.server.out.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
568 request.head.expect = null;
569 }
570
493571 /// Returns whether the connection should remain persistent.
494572 ///
495573 /// If it would fail, it instead sets the Server state to receiving body
......@@ -528,3 +606,150 @@ pub const Request = struct {
528606 return false;
529607 }
530608};
609
610/// See https://tools.ietf.org/html/rfc6455
611pub const WebSocket = struct {
612 key: []const u8,
613 input: *std.io.BufferedReader,
614 output: *std.io.BufferedWriter,
615
616 pub const Header0 = packed struct(u8) {
617 opcode: Opcode,
618 rsv3: u1 = 0,
619 rsv2: u1 = 0,
620 rsv1: u1 = 0,
621 fin: bool,
622 };
623
624 pub const Header1 = packed struct(u8) {
625 payload_len: enum(u7) {
626 len16 = 126,
627 len64 = 127,
628 _,
629 },
630 mask: bool,
631 };
632
633 pub const Opcode = enum(u4) {
634 continuation = 0,
635 text = 1,
636 binary = 2,
637 connection_close = 8,
638 ping = 9,
639 /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional
640 /// heartbeat. A response to an unsolicited Pong frame is not expected."
641 pong = 10,
642 _,
643 };
644
645 pub const ReadSmallTextMessageError = error{
646 ConnectionClose,
647 UnexpectedOpCode,
648 MessageTooBig,
649 MissingMaskBit,
650 };
651
652 pub const SmallMessage = struct {
653 /// Can be text, binary, or ping.
654 opcode: Opcode,
655 data: []u8,
656 };
657
658 /// Reads the next message from the WebSocket stream, failing if the
659 /// message does not fit into the input buffer. The returned memory points
660 /// into the input buffer and is invalidated on the next read.
661 pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
662 const in = ws.input;
663 while (true) {
664 const h0 = in.takeStruct(Header0);
665 const h1 = in.takeStruct(Header1);
666
667 switch (h0.opcode) {
668 .text, .binary, .pong, .ping => {},
669 .connection_close => return error.ConnectionClose,
670 .continuation => return error.UnexpectedOpCode,
671 _ => return error.UnexpectedOpCode,
672 }
673
674 if (!h0.fin) return error.MessageTooBig;
675 if (!h1.mask) return error.MissingMaskBit;
676
677 const len: usize = switch (h1.payload_len) {
678 .len16 => try in.takeInt(u16, .big),
679 .len64 => std.math.cast(usize, try in.takeInt(u64, .big)) orelse return error.MessageTooBig,
680 else => @intFromEnum(h1.payload_len),
681 };
682 if (len > in.buffer.len) return error.MessageTooBig;
683 const mask: u32 = @bitCast((try in.takeArray(4)).*);
684 const payload = try in.take(len);
685
686 // Skip pongs.
687 if (h0.opcode == .pong) continue;
688
689 // The last item may contain a partial word of unused data.
690 const floored_len = (payload.len / 4) * 4;
691 const u32_payload: []align(1) u32 = @ptrCast(payload[0..floored_len]);
692 for (u32_payload) |*elem| elem.* ^= mask;
693 const mask_bytes: []const u8 = @ptrCast(&mask);
694 for (payload[floored_len..], mask_bytes[0 .. payload.len - floored_len]) |*leftover, m|
695 leftover.* ^= m;
696
697 return .{
698 .opcode = h0.opcode,
699 .data = payload,
700 };
701 }
702 }
703
704 pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) std.io.Writer.Error!void {
705 try writeMessageVecUnflushed(ws, &.{data}, op);
706 try ws.output.flush();
707 }
708
709 pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) std.io.Writer.Error!void {
710 try writeMessageVecUnflushed(ws, &.{data}, op);
711 }
712
713 pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) std.io.Writer.Error!void {
714 try writeMessageVecUnflushed(ws, data, op);
715 try ws.output.flush();
716 }
717
718 pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) std.io.Writer.Error!void {
719 const total_len = l: {
720 var total_len: u64 = 0;
721 for (data) |iovec| total_len += iovec.len;
722 break :l total_len;
723 };
724 const out = ws.output;
725 try out.writeStruct(@as(Header0, .{
726 .opcode = op,
727 .fin = true,
728 }));
729 switch (total_len) {
730 0...125 => try out.writeStruct(@as(Header1, .{
731 .payload_len = @enumFromInt(total_len),
732 .mask = false,
733 })),
734 126...0xffff => {
735 try out.writeStruct(@as(Header1, .{
736 .payload_len = .len16,
737 .mask = false,
738 }));
739 try out.writeInt(u16, @intCast(total_len), .big);
740 },
741 else => {
742 try out.writeStruct(@as(Header1, .{
743 .payload_len = .len64,
744 .mask = false,
745 }));
746 try out.writeInt(u64, total_len, .big);
747 },
748 }
749 try out.writeVecAll(data);
750 }
751
752 pub fn flush(ws: *WebSocket) std.io.Writer.Error!void {
753 try ws.output.flush();
754 }
755};
lib/std/http/WebSocket.zig deleted-243
......@@ -1,243 +0,0 @@
1//! See https://tools.ietf.org/html/rfc6455
2
3const builtin = @import("builtin");
4const std = @import("std");
5const WebSocket = @This();
6const assert = std.debug.assert;
7const native_endian = builtin.cpu.arch.endian();
8
9key: []const u8,
10request: *std.http.Server.Request,
11recv_fifo: std.fifo.LinearFifo(u8, .Slice),
12reader: std.io.BufferedReader,
13body_writer: std.http.BodyWriter,
14/// Number of bytes that have been peeked but not discarded yet.
15outstanding_len: usize,
16
17pub const InitError = error{WebSocketUpgradeMissingKey} ||
18 std.http.Server.Request.ReaderError;
19
20pub fn init(
21 ws: *WebSocket,
22 request: *std.http.Server.Request,
23 recv_buffer: []align(4) u8,
24) InitError!bool {
25 switch (request.head.version) {
26 .@"HTTP/1.0" => return false,
27 .@"HTTP/1.1" => if (request.head.method != .GET) return false,
28 }
29
30 var sec_websocket_key: ?[]const u8 = null;
31 var upgrade_websocket: bool = false;
32 var it = request.iterateHeaders();
33 while (it.next()) |header| {
34 if (std.ascii.eqlIgnoreCase(header.name, "sec-websocket-key")) {
35 sec_websocket_key = header.value;
36 } else if (std.ascii.eqlIgnoreCase(header.name, "upgrade")) {
37 if (!std.ascii.eqlIgnoreCase(header.value, "websocket"))
38 return false;
39 upgrade_websocket = true;
40 }
41 }
42 if (!upgrade_websocket)
43 return false;
44
45 const key = sec_websocket_key orelse return error.WebSocketUpgradeMissingKey;
46
47 var sha1 = std.crypto.hash.Sha1.init(.{});
48 sha1.update(key);
49 sha1.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
50 var digest: [std.crypto.hash.Sha1.digest_length]u8 = undefined;
51 sha1.final(&digest);
52 var base64_digest: [28]u8 = undefined;
53 assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len);
54
55 request.head.content_length = std.math.maxInt(u64);
56
57 ws.* = .{
58 .key = key,
59 .recv_fifo = .init(recv_buffer),
60 .reader = (try request.reader()).unbuffered(),
61 .body_writer = try request.respondStreaming(.{
62 .respond_options = .{
63 .status = .switching_protocols,
64 .extra_headers = &.{
65 .{ .name = "upgrade", .value = "websocket" },
66 .{ .name = "connection", .value = "upgrade" },
67 .{ .name = "sec-websocket-accept", .value = &base64_digest },
68 },
69 .transfer_encoding = .none,
70 },
71 }),
72 .request = request,
73 .outstanding_len = 0,
74 };
75 return true;
76}
77
78pub const Header0 = packed struct(u8) {
79 opcode: Opcode,
80 rsv3: u1 = 0,
81 rsv2: u1 = 0,
82 rsv1: u1 = 0,
83 fin: bool,
84};
85
86pub const Header1 = packed struct(u8) {
87 payload_len: enum(u7) {
88 len16 = 126,
89 len64 = 127,
90 _,
91 },
92 mask: bool,
93};
94
95pub const Opcode = enum(u4) {
96 continuation = 0,
97 text = 1,
98 binary = 2,
99 connection_close = 8,
100 ping = 9,
101 /// "A Pong frame MAY be sent unsolicited. This serves as a unidirectional
102 /// heartbeat. A response to an unsolicited Pong frame is not expected."
103 pong = 10,
104 _,
105};
106
107pub const ReadSmallTextMessageError = error{
108 ConnectionClose,
109 UnexpectedOpCode,
110 MessageTooBig,
111 MissingMaskBit,
112} || RecvError;
113
114pub const SmallMessage = struct {
115 /// Can be text, binary, or ping.
116 opcode: Opcode,
117 data: []u8,
118};
119
120/// Reads the next message from the WebSocket stream, failing if the message does not fit
121/// into `recv_buffer`.
122pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage {
123 while (true) {
124 const header_bytes = (try recv(ws, 2))[0..2];
125 const h0: Header0 = @bitCast(header_bytes[0]);
126 const h1: Header1 = @bitCast(header_bytes[1]);
127
128 switch (h0.opcode) {
129 .text, .binary, .pong, .ping => {},
130 .connection_close => return error.ConnectionClose,
131 .continuation => return error.UnexpectedOpCode,
132 _ => return error.UnexpectedOpCode,
133 }
134
135 if (!h0.fin) return error.MessageTooBig;
136 if (!h1.mask) return error.MissingMaskBit;
137
138 const len: usize = switch (h1.payload_len) {
139 .len16 => try recvReadInt(ws, u16),
140 .len64 => std.math.cast(usize, try recvReadInt(ws, u64)) orelse return error.MessageTooBig,
141 else => @intFromEnum(h1.payload_len),
142 };
143 if (len > ws.recv_fifo.buf.len) return error.MessageTooBig;
144
145 const mask: u32 = @bitCast((try recv(ws, 4))[0..4].*);
146 const payload = try recv(ws, len);
147
148 // Skip pongs.
149 if (h0.opcode == .pong) continue;
150
151 // The last item may contain a partial word of unused data.
152 const floored_len = (payload.len / 4) * 4;
153 const u32_payload: []align(1) u32 = @alignCast(std.mem.bytesAsSlice(u32, payload[0..floored_len]));
154 for (u32_payload) |*elem| elem.* ^= mask;
155 const mask_bytes = std.mem.asBytes(&mask)[0 .. payload.len - floored_len];
156 for (payload[floored_len..], mask_bytes) |*leftover, m| leftover.* ^= m;
157
158 return .{
159 .opcode = h0.opcode,
160 .data = payload,
161 };
162 }
163}
164
165const RecvError = std.http.Server.Request.ReadError || error{EndOfStream};
166
167fn recv(ws: *WebSocket, len: usize) RecvError![]u8 {
168 ws.recv_fifo.discard(ws.outstanding_len);
169 assert(len <= ws.recv_fifo.buf.len);
170 if (len > ws.recv_fifo.count) {
171 const small_buf = ws.recv_fifo.writableSlice(0);
172 const needed = len - ws.recv_fifo.count;
173 const buf = if (small_buf.len >= needed) small_buf else b: {
174 ws.recv_fifo.realign();
175 break :b ws.recv_fifo.writableSlice(0);
176 };
177 const n = try @as(RecvError!usize, @errorCast(ws.reader.readAtLeast(buf, needed)));
178 if (n < needed) return error.EndOfStream;
179 ws.recv_fifo.update(n);
180 }
181 ws.outstanding_len = len;
182 // TODO: improve the std lib API so this cast isn't necessary.
183 return @constCast(ws.recv_fifo.readableSliceOfLen(len));
184}
185
186fn recvReadInt(ws: *WebSocket, comptime I: type) !I {
187 const unswapped: I = @bitCast((try recv(ws, @sizeOf(I)))[0..@sizeOf(I)].*);
188 return switch (native_endian) {
189 .little => @byteSwap(unswapped),
190 .big => unswapped,
191 };
192}
193
194pub fn writeMessage(ws: *WebSocket, message: []const u8, opcode: Opcode) std.io.Writer.Error!void {
195 const iovecs: [1]std.posix.iovec_const = .{
196 .{ .base = message.ptr, .len = message.len },
197 };
198 return writeMessagev(ws, &iovecs, opcode);
199}
200
201pub fn writeMessagev(ws: *WebSocket, message: []const std.posix.iovec_const, opcode: Opcode) std.io.Writer.Error!void {
202 const total_len = l: {
203 var total_len: u64 = 0;
204 for (message) |iovec| total_len += iovec.len;
205 break :l total_len;
206 };
207
208 var header_buf: [2 + 8]u8 = undefined;
209 header_buf[0] = @bitCast(@as(Header0, .{
210 .opcode = opcode,
211 .fin = true,
212 }));
213 const header = switch (total_len) {
214 0...125 => blk: {
215 header_buf[1] = @bitCast(@as(Header1, .{
216 .payload_len = @enumFromInt(total_len),
217 .mask = false,
218 }));
219 break :blk header_buf[0..2];
220 },
221 126...0xffff => blk: {
222 header_buf[1] = @bitCast(@as(Header1, .{
223 .payload_len = .len16,
224 .mask = false,
225 }));
226 std.mem.writeInt(u16, header_buf[2..4], @intCast(total_len), .big);
227 break :blk header_buf[0..4];
228 },
229 else => blk: {
230 header_buf[1] = @bitCast(@as(Header1, .{
231 .payload_len = .len64,
232 .mask = false,
233 }));
234 std.mem.writeInt(u64, header_buf[2..10], total_len, .big);
235 break :blk header_buf[0..10];
236 },
237 };
238
239 var bw = ws.body_writer.writer().unbuffered();
240 try bw.writeAll(header);
241 for (message) |iovec| try bw.writeAll(iovec.base[0..iovec.len]);
242 try bw.flush();
243}
lib/std/io/BufferedReader.zig+84-19
......@@ -6,8 +6,10 @@ const assert = std.debug.assert;
66const testing = std.testing;
77const BufferedWriter = std.io.BufferedWriter;
88const Reader = std.io.Reader;
9const Writer = std.io.Writer;
910const Allocator = std.mem.Allocator;
1011const ArrayList = std.ArrayListUnmanaged;
12const Limit = std.io.Limit;
1113
1214const BufferedReader = @This();
1315
......@@ -63,12 +65,12 @@ pub fn readVec(br: *BufferedReader, data: []const []u8) Reader.Error!usize {
6365}
6466
6567/// Equivalent semantics to `std.io.Reader.VTable.read`.
66pub fn read(br: *BufferedReader, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
68pub fn read(br: *BufferedReader, bw: *BufferedWriter, limit: Limit) Reader.StreamError!usize {
6769 return passthruRead(br, bw, limit);
6870}
6971
7072/// Equivalent semantics to `std.io.Reader.VTable.discard`.
71pub fn discard(br: *BufferedReader, limit: Reader.Limit) Reader.Error!usize {
73pub fn discard(br: *BufferedReader, limit: Limit) Reader.Error!usize {
7274 return passthruDiscard(br, limit);
7375}
7476
......@@ -90,7 +92,7 @@ pub fn readVecAll(br: *BufferedReader, data: [][]u8) Reader.Error!void {
9092}
9193
9294/// "Pump" data from the reader to the writer.
93pub fn readAll(br: *BufferedReader, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!void {
95pub fn readAll(br: *BufferedReader, bw: *BufferedWriter, limit: Limit) Reader.StreamError!void {
9496 var remaining = limit;
9597 while (remaining.nonzero()) {
9698 const n = try br.read(bw, remaining);
......@@ -113,8 +115,8 @@ pub fn readRemaining(br: *BufferedReader, bw: *BufferedWriter) Reader.RwRemainin
113115}
114116
115117/// Equivalent to `readVec` but reads at most `limit` bytes.
116pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit) Reader.Error!usize {
117 assert(@intFromEnum(Reader.Limit.unlimited) == std.math.maxInt(usize));
118pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Limit) Reader.Error!usize {
119 assert(@intFromEnum(Limit.unlimited) == std.math.maxInt(usize));
118120 var remaining = @intFromEnum(limit);
119121 for (data, 0..) |buf, i| {
120122 const buffered = br.buffer[br.seek..br.end];
......@@ -165,7 +167,7 @@ pub fn readVecLimit(br: *BufferedReader, data: []const []u8, limit: Reader.Limit
165167 return @intFromEnum(limit) - remaining;
166168}
167169
168fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
170fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) Reader.StreamError!usize {
169171 const br: *BufferedReader = @alignCast(@ptrCast(context));
170172 const buffer = limit.slice(br.buffer[br.seek..br.end]);
171173 if (buffer.len > 0) {
......@@ -176,22 +178,19 @@ fn passthruRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit)
176178 return br.unbuffered_reader.read(bw, limit);
177179}
178180
179fn passthruDiscard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize {
181fn passthruDiscard(context: ?*anyopaque, limit: Limit) Reader.Error!usize {
180182 const br: *BufferedReader = @alignCast(@ptrCast(context));
181183 const buffered_len = br.end - br.seek;
182 if (limit.toInt()) |n| {
184 const remaining: Limit = if (limit.toInt()) |n| l: {
183185 if (buffered_len >= n) {
184186 br.seek += n;
185187 return n;
186188 }
187 br.seek = 0;
188 br.end = 0;
189 const additional = try br.unbuffered_reader.discard(.limited(n - buffered_len));
190 return n + additional;
191 }
192 const n = try br.unbuffered_reader.discard(.unlimited);
189 break :l .limited(n - buffered_len);
190 } else .unlimited;
193191 br.seek = 0;
194192 br.end = 0;
193 const n = if (br.unbuffered_reader.discard) |f| try f(remaining) else try br.defaultDiscard(remaining);
195194 return buffered_len + n;
196195}
197196
......@@ -200,6 +199,72 @@ fn passthruReadVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize
200199 return readVecLimit(br, data, .unlimited);
201200}
202201
202fn defaultDiscard(br: *BufferedReader, limit: Limit) Reader.Error!usize {
203 assert(br.seek == 0);
204 assert(br.end == 0);
205 var bw: BufferedWriter = .{
206 .unbuffered_writer = .{
207 .context = undefined,
208 .vtable = &.{
209 .writeSplat = defaultDiscardWriteSplat,
210 .writeFile = defaultDiscardWriteFile,
211 },
212 },
213 .buffer = br.buffer,
214 };
215 const n = br.read(&bw, limit) catch |err| switch (err) {
216 error.WriteFailed => unreachable,
217 error.ReadFailed => return error.ReadFailed,
218 error.EndOfStream => return error.EndOfStream,
219 };
220 if (n > @intFromEnum(limit)) {
221 const over_amt = n - @intFromEnum(limit);
222 assert(over_amt <= bw.buffer.end); // limit may be exceeded only by an amount within buffer capacity.
223 br.seek = bw.end - over_amt;
224 br.end = bw.end;
225 return @intFromEnum(limit);
226 }
227 return n;
228}
229
230fn defaultDiscardWriteSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Error!usize {
231 _ = context;
232 const headers = data[0 .. data.len - 1];
233 const pattern = data[headers.len..];
234 var written: usize = pattern.len * splat;
235 for (headers) |bytes| written += bytes.len;
236 return written;
237}
238
239fn defaultDiscardWriteFile(
240 context: ?*anyopaque,
241 file_reader: *std.fs.File.Reader,
242 limit: Limit,
243 headers_and_trailers: []const []const u8,
244 headers_len: usize,
245) Writer.FileError!usize {
246 _ = context;
247 if (file_reader.getSize()) |size| {
248 const remaining = size - file_reader.pos;
249 const seek_amt = limit.minInt(remaining);
250 // Error is observable on `file_reader` instance, and is safe to ignore
251 // depending on the caller's needs. Caller can make that decision.
252 file_reader.seekForward(seek_amt) catch {};
253 var n: usize = seek_amt;
254 for (headers_and_trailers[0..headers_len]) |bytes| n += bytes.len;
255 if (seek_amt == remaining) {
256 // Since we made it all the way through the file, the trailers are
257 // also included.
258 for (headers_and_trailers[headers_len..]) |bytes| n += bytes.len;
259 }
260 return n;
261 } else |_| {
262 // Error is observable on `file_reader` instance, and it is better to
263 // treat the file as a pipe.
264 return error.Unimplemented;
265 }
266}
267
203268/// Returns the next `len` bytes from `unbuffered_reader`, filling the buffer as
204269/// necessary.
205270///
......@@ -475,7 +540,7 @@ pub fn readSliceAlloc(br: *BufferedReader, allocator: Allocator, len: usize) Rea
475540///
476541/// See also:
477542/// * `readRemainingArrayList`
478pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Reader.Limit) Reader.LimitedAllocError![]u8 {
543pub fn readRemainingAlloc(r: Reader, gpa: Allocator, limit: Limit) Reader.LimitedAllocError![]u8 {
479544 var buffer: ArrayList(u8) = .empty;
480545 defer buffer.deinit(gpa);
481546 try readRemainingArrayList(r, gpa, null, &buffer, limit);
......@@ -499,7 +564,7 @@ pub fn readRemainingArrayList(
499564 gpa: Allocator,
500565 comptime alignment: ?std.mem.Alignment,
501566 list: *std.ArrayListAlignedUnmanaged(u8, alignment),
502 limit: Reader.Limit,
567 limit: Limit,
503568) Reader.LimitedAllocError!void {
504569 const buffer = br.buffer;
505570 const buffered = buffer[br.seek..br.end];
......@@ -680,7 +745,7 @@ pub fn peekDelimiterExclusive(br: *BufferedReader, delimiter: u8) DelimiterError
680745/// found. Does not write the delimiter itself.
681746///
682747/// Returns number of bytes streamed.
683pub fn readDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.RwError!usize {
748pub fn readDelimiter(br: *BufferedReader, bw: *BufferedWriter, delimiter: u8) Reader.StreamError!usize {
684749 const amount, const to = try br.readAny(bw, delimiter, .unlimited);
685750 return switch (to) {
686751 .delimiter => amount,
......@@ -722,7 +787,7 @@ pub fn readDelimiterLimit(
722787 br: *BufferedReader,
723788 bw: *BufferedWriter,
724789 delimiter: u8,
725 limit: Reader.Limit,
790 limit: Limit,
726791) StreamDelimiterLimitedError!usize {
727792 const amount, const to = try br.readAny(bw, delimiter, limit);
728793 return switch (to) {
......@@ -736,7 +801,7 @@ fn readAny(
736801 br: *BufferedReader,
737802 bw: *BufferedWriter,
738803 delimiter: ?u8,
739 limit: Reader.Limit,
804 limit: Limit,
740805) Reader.RwRemainingError!struct { usize, enum { delimiter, limit, end } } {
741806 var amount: usize = 0;
742807 var remaining = limit;
lib/std/io/BufferedWriter.zig+9-8
......@@ -5,6 +5,7 @@ const native_endian = @import("builtin").target.cpu.arch.endian();
55const Writer = std.io.Writer;
66const Allocator = std.mem.Allocator;
77const testing = std.testing;
8const Limit = std.io.Limit;
89
910/// Underlying stream to send bytes to.
1011///
......@@ -42,7 +43,7 @@ pub fn writer(bw: *BufferedWriter) Writer {
4243
4344const fixed_vtable: Writer.VTable = .{
4445 .writeSplat = fixedWriteSplat,
45 .writeFile = Writer.failingWriteFile,
46 .writeFile = Writer.unimplementedWriteFile,
4647};
4748
4849/// Replaces the `BufferedWriter` with one that writes to `buffer` and returns
......@@ -82,7 +83,7 @@ pub fn flush(bw: *BufferedWriter) Writer.Error!void {
8283 bw.end = 0;
8384}
8485
85pub fn flushLimit(bw: *BufferedWriter, limit: Writer.Limit) Writer.Error!void {
86pub fn flushLimit(bw: *BufferedWriter, limit: Limit) Writer.Error!void {
8687 const buffer = limit.slice(bw.buffer[0..bw.end]);
8788 var index: usize = 0;
8889 while (index < buffer.len) index += try bw.unbuffered_writer.writeVec(&.{buffer[index..]});
......@@ -228,7 +229,7 @@ pub fn writeSplatLimit(
228229 bw: *BufferedWriter,
229230 data: []const []const u8,
230231 splat: usize,
231 limit: Writer.Limit,
232 limit: Limit,
232233) Writer.Error!usize {
233234 _ = bw;
234235 _ = data;
......@@ -544,7 +545,7 @@ pub fn writeFile(
544545 bw: *BufferedWriter,
545546 file: std.fs.File,
546547 offset: Writer.Offset,
547 limit: Writer.Limit,
548 limit: Limit,
548549 headers_and_trailers: []const []const u8,
549550 headers_len: usize,
550551) Writer.FileError!usize {
......@@ -560,7 +561,7 @@ pub fn writeFileReading(
560561 bw: *BufferedWriter,
561562 file: std.fs.File,
562563 offset: Writer.Offset,
563 limit: Writer.Limit,
564 limit: Limit,
564565) WriteFileReadingError!usize {
565566 const dest = limit.slice(try bw.writableSliceGreedy(1));
566567 const n = if (offset.toInt()) |pos| try file.pread(dest, pos) else try file.read(dest);
......@@ -572,7 +573,7 @@ fn passthruWriteFile(
572573 context: ?*anyopaque,
573574 file: std.fs.File,
574575 offset: Writer.Offset,
575 limit: Writer.Limit,
576 limit: Limit,
576577 headers_and_trailers: []const []const u8,
577578 headers_len: usize,
578579) Writer.FileError!usize {
......@@ -653,7 +654,7 @@ pub const WriteFileOptions = struct {
653654 offset: Writer.Offset = .none,
654655 /// If the size of the source file is known, it is likely that passing the
655656 /// size here will save one syscall.
656 limit: Writer.Limit = .unlimited,
657 limit: Limit = .unlimited,
657658 /// Headers and trailers must be passed together so that in case `len` is
658659 /// zero, they can be forwarded directly to `Writer.VTable.writeSplat`.
659660 ///
......@@ -749,7 +750,7 @@ pub fn writeFileReadingAll(
749750 bw: *BufferedWriter,
750751 file: std.fs.File,
751752 offset: Writer.Offset,
752 limit: Writer.Limit,
753 limit: Limit,
753754) WriteFileReadingError!void {
754755 if (offset.toInt()) |start_pos| {
755756 var remaining = limit;
lib/std/io/Reader.zig+19-49
......@@ -5,6 +5,7 @@ const BufferedWriter = std.io.BufferedWriter;
55const BufferedReader = std.io.BufferedReader;
66const Allocator = std.mem.Allocator;
77const ArrayList = std.ArrayListUnmanaged;
8const Limit = std.io.Limit;
89
910pub const Limited = @import("Reader/Limited.zig");
1011
......@@ -25,21 +26,7 @@ pub const VTable = struct {
2526 /// Implementations are encouraged to utilize mandatory minimum buffer
2627 /// sizes combined with short reads (returning a value less than `limit`)
2728 /// in order to minimize complexity.
28 read: *const fn (context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize,
29
30 /// Writes bytes from the internally tracked stream position to `data`.
31 ///
32 /// Returns the number of bytes written, which will be at minimum `0` and
33 /// at most the sum of each data slice length. The number of bytes read,
34 /// including zero, does not indicate end of stream.
35 ///
36 /// The reader's internal logical seek position moves forward in accordance
37 /// with the number of bytes returned from this function.
38 ///
39 /// Implementations are encouraged to utilize mandatory minimum buffer
40 /// sizes combined with short reads (returning a value less than the total
41 /// buffer capacity inside `data`) in order to minimize complexity.
42 readVec: *const fn (context: ?*anyopaque, data: []const []u8) Error!usize,
29 read: *const fn (context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize,
4330
4431 /// Consumes bytes from the internally tracked stream position without
4532 /// providing access to them.
......@@ -54,10 +41,15 @@ pub const VTable = struct {
5441 /// Implementations are encouraged to utilize mandatory minimum buffer
5542 /// sizes combined with short reads (returning a value less than `limit`)
5643 /// in order to minimize complexity.
57 discard: *const fn (context: ?*anyopaque, limit: Limit) Error!usize,
44 ///
45 /// If an implementation sets this to `null`, a default implementation is
46 /// provided which is based on calling `read`, borrowing
47 /// `BufferedReader.buffer` to construct a temporary `BufferedWriter` and
48 /// ignoring the written data.
49 discard: *const fn (context: ?*anyopaque, limit: Limit) DiscardError!usize = null,
5850};
5951
60pub const RwError = error{
52pub const StreamError = error{
6153 /// See the `Reader` implementation for detailed diagnostics.
6254 ReadFailed,
6355 /// See the `Writer` implementation for detailed diagnostics.
......@@ -67,7 +59,7 @@ pub const RwError = error{
6759 EndOfStream,
6860};
6961
70pub const Error = error{
62pub const DiscardError = error{
7163 /// See the `Reader` implementation for detailed diagnostics.
7264 ReadFailed,
7365 EndOfStream,
......@@ -85,10 +77,7 @@ pub const ShortError = error{
8577 ReadFailed,
8678};
8779
88/// TODO: no pub
89pub const Limit = std.io.Limit;
90
91pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) RwError!usize {
80pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) StreamError!usize {
9281 const before = bw.count;
9382 const n = try r.vtable.read(r.context, bw, limit);
9483 assert(n <= @intFromEnum(limit));
......@@ -96,11 +85,7 @@ pub fn read(r: Reader, bw: *BufferedWriter, limit: Limit) RwError!usize {
9685 return n;
9786}
9887
99pub fn readVec(r: Reader, data: []const []u8) Error!usize {
100 return r.vtable.readVec(r.context, data);
101}
102
103pub fn discard(r: Reader, limit: Limit) Error!usize {
88pub fn discard(r: Reader, limit: Limit) DiscardError!usize {
10489 const n = try r.vtable.discard(r.context, limit);
10590 assert(n <= @intFromEnum(limit));
10691 return n;
......@@ -188,7 +173,6 @@ pub const failing: Reader = .{
188173 .context = undefined,
189174 .vtable = &.{
190175 .read = failingRead,
191 .readVec = failingReadVec,
192176 .discard = failingDiscard,
193177 },
194178};
......@@ -197,7 +181,6 @@ pub const ending: Reader = .{
197181 .context = undefined,
198182 .vtable = &.{
199183 .read = endingRead,
200 .readVec = endingReadVec,
201184 .discard = endingDiscard,
202185 },
203186};
......@@ -222,39 +205,27 @@ pub fn limited(r: Reader, limit: Limit) Limited {
222205 };
223206}
224207
225fn endingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize {
208fn endingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize {
226209 _ = context;
227210 _ = bw;
228211 _ = limit;
229212 return error.EndOfStream;
230213}
231214
232fn endingReadVec(context: ?*anyopaque, data: []const []u8) Error!usize {
233 _ = context;
234 _ = data;
235 return error.EndOfStream;
236}
237
238fn endingDiscard(context: ?*anyopaque, limit: Limit) Error!usize {
215fn endingDiscard(context: ?*anyopaque, limit: Limit) DiscardError!usize {
239216 _ = context;
240217 _ = limit;
241218 return error.EndOfStream;
242219}
243220
244fn failingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize {
221fn failingRead(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize {
245222 _ = context;
246223 _ = bw;
247224 _ = limit;
248225 return error.ReadFailed;
249226}
250227
251fn failingReadVec(context: ?*anyopaque, data: []const []u8) Error!usize {
252 _ = context;
253 _ = data;
254 return error.ReadFailed;
255}
256
257fn failingDiscard(context: ?*anyopaque, limit: Limit) Error!usize {
228fn failingDiscard(context: ?*anyopaque, limit: Limit) DiscardError!usize {
258229 _ = context;
259230 _ = limit;
260231 return error.ReadFailed;
......@@ -308,7 +279,6 @@ pub fn Hashed(comptime Hasher: type) type {
308279 .context = this,
309280 .vtable = &.{
310281 .read = @This().read,
311 .readVec = @This().readVec,
312282 .discard = @This().discard,
313283 },
314284 },
......@@ -318,7 +288,7 @@ pub fn Hashed(comptime Hasher: type) type {
318288 };
319289 }
320290
321 fn read(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) RwError!usize {
291 fn read(context: ?*anyopaque, bw: *BufferedWriter, limit: Limit) StreamError!usize {
322292 const this: *@This() = @alignCast(@ptrCast(context));
323293 const slice = limit.slice(try bw.writableSliceGreedy(1));
324294 const n = try this.in.readVec(&.{slice});
......@@ -327,7 +297,7 @@ pub fn Hashed(comptime Hasher: type) type {
327297 return n;
328298 }
329299
330 fn discard(context: ?*anyopaque, limit: Limit) Error!usize {
300 fn discard(context: ?*anyopaque, limit: Limit) DiscardError!usize {
331301 const this: *@This() = @alignCast(@ptrCast(context));
332302 var bw = this.hasher.writable(&.{});
333303 const n = this.in.read(&bw, limit) catch |err| switch (err) {
......@@ -337,7 +307,7 @@ pub fn Hashed(comptime Hasher: type) type {
337307 return n;
338308 }
339309
340 fn readVec(context: ?*anyopaque, data: []const []u8) Error!usize {
310 fn readVec(context: ?*anyopaque, data: []const []u8) DiscardError!usize {
341311 const this: *@This() = @alignCast(@ptrCast(context));
342312 const n = try this.in.readVec(data);
343313 var remaining: usize = n;
lib/std/io/Writer.zig+3-3
......@@ -1,6 +1,7 @@
11const std = @import("../std.zig");
22const assert = std.debug.assert;
33const Writer = @This();
4const Limit = std.io.Limit;
45
56pub const Null = @import("Writer/Null.zig");
67
......@@ -47,6 +48,8 @@ pub const VTable = struct {
4748 offset: Offset,
4849 /// Maximum amount of bytes to read from the file. Implementations may
4950 /// assume that the file size does not exceed this amount.
51 ///
52 /// `headers_and_trailers` do not count towards this limit.
5053 limit: Limit,
5154 /// Headers and trailers must be passed together so that in case `len` is
5255 /// zero, they can be forwarded directly to `VTable.writeVec`.
......@@ -68,9 +71,6 @@ pub const FileError = std.fs.File.PReadError || error{
6871 Unimplemented,
6972};
7073
71/// TODO: no pub
72pub const Limit = std.io.Limit;
73
7474pub const Offset = enum(u64) {
7575 zero = 0,
7676 /// Indicates to read the file as a stream.