authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-15 23:09:01-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
log20a784f7136143e4afa4d9d1d85fc0fa6d69d777
treeb6db814d9e577b96be4f4d102d9648c85833428e
parentc872a9fd49b090efc5b6132ec0ab959d7fe8e70f

std: start converting networking stuff to new reader/writer


5 files changed, 535 insertions(+), 591 deletions(-)

lib/std/crypto/ecdsa.zig+14-17
......@@ -155,38 +155,35 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
155155 }
156156
157157 // Read a DER-encoded integer.
158 fn readDerInt(out: []u8, reader: anytype) EncodingError!void {
159 var buf: [2]u8 = undefined;
160 _ = reader.readNoEof(&buf) catch return error.InvalidEncoding;
158 // Asserts `br` has storage capacity >= 2.
159 fn readDerInt(out: []u8, br: *std.io.BufferedReader) EncodingError!void {
160 const buf = br.take(2) catch return error.InvalidEncoding;
161161 if (buf[0] != 0x02) return error.InvalidEncoding;
162 var expected_len = @as(usize, buf[1]);
162 var expected_len: usize = buf[1];
163163 if (expected_len == 0 or expected_len > 1 + out.len) return error.InvalidEncoding;
164164 var has_top_bit = false;
165165 if (expected_len == 1 + out.len) {
166 if ((reader.readByte() catch return error.InvalidEncoding) != 0) return error.InvalidEncoding;
166 if ((br.takeByte() catch return error.InvalidEncoding) != 0) return error.InvalidEncoding;
167167 expected_len -= 1;
168168 has_top_bit = true;
169169 }
170170 const out_slice = out[out.len - expected_len ..];
171 reader.readNoEof(out_slice) catch return error.InvalidEncoding;
171 br.read(out_slice) catch return error.InvalidEncoding;
172172 if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding;
173173 }
174174
175175 /// Create a signature from a DER representation.
176176 /// Returns InvalidEncoding if the DER encoding is invalid.
177177 pub fn fromDer(der: []const u8) EncodingError!Signature {
178 if (der.len < 2) return error.InvalidEncoding;
179 var br: std.io.BufferedReader = undefined;
180 br.initFixed(der);
181 const buf = br.take(2) catch return error.InvalidEncoding;
182 if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) return error.InvalidEncoding;
178183 var sig: Signature = mem.zeroInit(Signature, .{});
179 var fb: std.io.FixedBufferStream = .{ .buffer = der };
180 const reader = fb.reader();
181 var buf: [2]u8 = undefined;
182 _ = reader.readNoEof(&buf) catch return error.InvalidEncoding;
183 if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) {
184 return error.InvalidEncoding;
185 }
186 try readDerInt(&sig.r, reader);
187 try readDerInt(&sig.s, reader);
188 if (fb.getPos() catch unreachable != der.len) return error.InvalidEncoding;
189
184 try readDerInt(&sig.r, &br);
185 try readDerInt(&sig.s, &br);
186 if (br.seek != der.len) return error.InvalidEncoding;
190187 return sig;
191188 }
192189 };
lib/std/crypto/tls/Client.zig+165-254
......@@ -1,3 +1,6 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
14const std = @import("../../std.zig");
25const tls = std.crypto.tls;
36const Client = @This();
......@@ -13,18 +16,44 @@ const hkdfExpandLabel = tls.hkdfExpandLabel;
1316const int = tls.int;
1417const array = tls.array;
1518
19/// The encrypted stream from the server to the client. Bytes are pulled from
20/// here via `reader`.
21///
22/// The buffer is asserted to have capacity at least `min_buffer_len`.
23///
24/// The size is enough to contain exactly one TLSCiphertext record.
25/// This buffer is segmented into four parts:
26/// 0. unused
27/// 1. cleartext
28/// 2. ciphertext
29/// 3. unused
30/// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and
31/// `partial_ciphertext_end` describe the span of the segments.
32input: *std.io.BufferedReader,
33/// The encrypted stream from the client to the server. Bytes are pushed here
34/// via `writer`.
35///
36/// The buffer is asserted to have capacity at least `min_buffer_len`.
37output: *std.io.BufferedWriter,
38/// Cleartext received from the server here.
39///
40/// Its buffer aliases the buffer of `input`.
41reader: std.io.BufferedReader,
42/// Populated under various error conditions.
43diagnostics: Diagnostics,
44
1645tls_version: tls.ProtocolVersion,
1746read_seq: u64,
1847write_seq: u64,
19/// The starting index of cleartext bytes inside `partially_read_buffer`.
48/// The starting index of cleartext bytes inside the input buffer.
2049partial_cleartext_idx: u15,
21/// The ending index of cleartext bytes inside `partially_read_buffer` as well
50/// The ending index of cleartext bytes inside the input buffer as well
2251/// as the starting index of ciphertext bytes.
2352partial_ciphertext_idx: u15,
24/// The ending index of ciphertext bytes inside `partially_read_buffer`.
53/// The ending index of ciphertext bytes inside the input buffer.
2554partial_ciphertext_end: u15,
2655/// When this is true, the stream may still not be at the end because there
27/// may be data in `partially_read_buffer`.
56/// may be data in the input buffer.
2857received_close_notify: bool,
2958/// By default, reaching the end-of-stream when reading from the server will
3059/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
......@@ -35,24 +64,40 @@ received_close_notify: bool,
3564/// the amount of data expected, such as HTTP with the Content-Length header.
3665allow_truncation_attacks: bool,
3766application_cipher: tls.ApplicationCipher,
38/// The size is enough to contain exactly one TLSCiphertext record.
39/// This buffer is segmented into four parts:
40/// 0. unused
41/// 1. cleartext
42/// 2. ciphertext
43/// 3. unused
44/// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and
45/// `partial_ciphertext_end` describe the span of the segments.
46partially_read_buffer: [tls.max_ciphertext_record_len]u8,
47/// Encrypted bytes sent to the server here.
48output: *std.io.BufferedWriter,
49/// If non-null, ssl secrets are logged to a file. Creating such a log file allows other
50/// programs with access to that file to decrypt all traffic over this connection.
51ssl_key_log: ?struct {
67/// If non-null, ssl secrets are logged to a stream. Creating such a log file
68/// allows other programs with access to that file to decrypt all traffic over
69/// this connection.
70ssl_key_log: ?*SslKeyLog,
71
72pub const Diagnostics = union {
73 /// Populated on `error.WriteFailure` and `error.ReadFailure`.
74 err: anyerror,
75 /// Populated on `error.TlsAlert`.
76 ///
77 /// If this isn't a error alert, then it's a closure alert, which makes
78 /// no sense in a handshake.
79 alert: tls.AlertDescription,
80
81 fn wrapWrite(d: *Diagnostics, returned: anyerror!void) error{WriteFailure}!void {
82 returned catch |err| {
83 d.* = .{ .err = err };
84 return error.WriteFailure;
85 };
86 }
87
88 fn wrapRead(d: *Diagnostics, returned: anyerror!void) error{ReadFailure}!void {
89 returned catch |err| {
90 d.* = .{ .err = err };
91 return error.ReadFailure;
92 };
93 }
94};
95
96pub const SslKeyLog = struct {
5297 client_key_seq: u64,
5398 server_key_seq: u64,
5499 client_random: [32]u8,
55 file: std.fs.File,
100 writer: *std.io.BufferedWriter,
56101
57102 fn clientCounter(key_log: *@This()) u64 {
58103 defer key_log.client_key_seq += 1;
......@@ -63,31 +108,12 @@ ssl_key_log: ?struct {
63108 defer key_log.server_key_seq += 1;
64109 return key_log.server_key_seq;
65110 }
66},
67
68/// This is an example of the type that is needed by the read and write
69/// functions. It can have any fields but it must at least have these
70/// functions.
71///
72/// Note that `std.net.Stream` conforms to this interface.
73///
74/// This declaration serves as documentation only.
75pub const StreamInterface = struct {
76 /// Can be any error set.
77 pub const ReadError = error{};
78
79 /// Returns the number of bytes read. The number read may be less than the
80 /// buffer space provided. End-of-stream is indicated by a return value of 0.
81 ///
82 /// The `iovecs` parameter is mutable because so that function may to
83 /// mutate the fields in order to handle partial reads from the underlying
84 /// stream layer.
85 pub fn readv(this: @This(), iovecs: []std.posix.iovec) ReadError!usize {
86 _ = .{ this, iovecs };
87 @panic("unimplemented");
88 }
89111};
90112
113/// The `std.io.BufferedReader` and `std.io.BufferedWriter` supplied to `init`
114/// each require a buffer capacity at least this amount.
115pub const min_buffer_len = tls.max_ciphertext_record_len;
116
91117pub const Options = struct {
92118 /// How to perform host verification of server certificates.
93119 host: union(enum) {
......@@ -109,39 +135,11 @@ pub const Options = struct {
109135 /// Verify that the server certificate is authorized by a given ca bundle.
110136 bundle: Certificate.Bundle,
111137 },
112 /// If non-null, ssl secrets are logged to this file. Creating such a log file allows
138 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
113139 /// other programs with access to that file to decrypt all traffic over this connection.
114 /// TODO `std.crypto` should have no dependencies on `std.fs`.
115 ssl_key_log_file: ?std.fs.File = null,
116 diagnostics: ?*Diagnostics = null,
117
118 pub const Diagnostics = union {
119 /// Populated on `error.WriteFailure` and `error.ReadFailure`.
120 err: anyerror,
121 /// Populated on `error.TlsAlert`.
122 ///
123 /// If this isn't a error alert, then it's a closure alert, which makes
124 /// no sense in a handshake.
125 alert: tls.AlertDescription,
126 };
140 ssl_key_log: ?*std.io.BufferedWriter = null,
127141};
128142
129/// TODO I wish this could be a method of Diagnostics
130fn wrapWrite(opt_diags: ?*Options.Diagnostics, returned: anyerror!void) error{WriteFailure}!void {
131 returned catch |err| {
132 if (opt_diags) |diags| diags.* = .{ .err = err };
133 return error.WriteFailure;
134 };
135}
136
137/// TODO I wish this could be a method of Diagnostics
138fn wrapRead(opt_diags: ?*Options.Diagnostics, returned: anyerror!void) error{ReadFailure}!void {
139 returned catch |err| {
140 if (opt_diags) |diags| diags.* = .{ .err = err };
141 return error.ReadFailure;
142 };
143}
144
145143const InitError = error{
146144 //OutOfMemory,
147145 WriteFailure,
......@@ -193,12 +191,21 @@ const InitError = error{
193191 WeakPublicKey,
194192};
195193
196/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session with `input`, which
197/// must conform to `StreamInterface`.
194/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session.
198195///
199196/// `host` is only borrowed during this function call.
200pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) InitError!Client {
201 const diags = options.diagnostics;
197///
198/// Both `input` and `output` are asserted to have buffer capacity at least
199/// `min_buffer_len`.
200pub fn init(
201 client: *Client,
202 input: *std.io.BufferedReader,
203 output: *std.io.BufferedWriter,
204 options: Options,
205) InitError!void {
206 assert(input.storage.buffer.len >= min_buffer_len);
207 assert(output.buffer.len >= min_buffer_len);
208 const diags = &client.diagnostics;
202209 const host = switch (options.host) {
203210 .no_verification => "",
204211 .explicit => |host| host,
......@@ -291,7 +298,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
291298
292299 {
293300 var iovecs: [2][]const u8 = .{ cleartext_header, host };
294 try wrapWrite(diags, output.writevAll(iovecs[0..if (host.len == 0) 1 else 2]));
301 try diags.wrapWrite(output.writevAll(iovecs[0..if (host.len == 0) 1 else 2]));
295302 }
296303
297304 var tls_version: tls.ProtocolVersion = undefined;
......@@ -343,12 +350,12 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
343350 var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined;
344351 var d: tls.Decoder = .{ .buf = &handshake_buffer };
345352 fragment: while (true) {
346 try wrapRead(diags, d.readAtLeastOurAmt(input, tls.record_header_len));
353 try diags.wrapRead(d.readAtLeastOurAmt(input, tls.record_header_len));
347354 const record_header = d.buf[d.idx..][0..tls.record_header_len];
348355 const record_ct = d.decode(tls.ContentType);
349356 d.skip(2); // legacy_version
350357 const record_len = d.decode(u16);
351 try wrapRead(diags, d.readAtLeast(input, record_len));
358 try diags.wrapRead(d.readAtLeast(input, record_len));
352359 var record_decoder = try d.sub(record_len);
353360 var ctd, const ct = content: switch (cipher_state) {
354361 .cleartext => .{ record_decoder, record_ct },
......@@ -426,7 +433,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
426433 const level = ctd.decode(tls.AlertLevel);
427434 const desc = ctd.decode(tls.AlertDescription);
428435 _ = level;
429 if (diags) |x| x.* = .{ .alert = desc };
436 diags.* = .{ .alert = desc };
430437 return error.TlsAlert;
431438 },
432439 .change_cipher_spec => {
......@@ -768,7 +775,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
768775 &client_change_cipher_spec_msg,
769776 &client_verify_msg,
770777 };
771 try wrapWrite(diags, output.writevAll(&all_msgs_vec));
778 try diags.wrapWrite(output.writevAll(&all_msgs_vec));
772779 },
773780 }
774781 write_seq += 1;
......@@ -833,7 +840,7 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
833840 &client_change_cipher_spec_msg,
834841 &finished_msg,
835842 };
836 try wrapWrite(diags, output.writevAll(&all_msgs_vec));
843 try diags.wrapWrite(output.writevAll(&all_msgs_vec));
837844
838845 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
839846 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);
......@@ -865,7 +872,10 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
865872 },
866873 };
867874 const leftover = d.rest();
868 var client: Client = .{
875 client.* = .{
876 .input = input,
877 .output = output,
878 .reader = undefined,
869879 .tls_version = tls_version,
870880 .read_seq = switch (tls_version) {
871881 .tls_1_3 => 0,
......@@ -883,7 +893,6 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
883893 .received_close_notify = false,
884894 .allow_truncation_attacks = false,
885895 .application_cipher = app_cipher,
886 .output = output,
887896 .partially_read_buffer = undefined,
888897 .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{
889898 .client_key_seq = key_seq,
......@@ -893,7 +902,14 @@ pub fn init(input: anytype, output: *std.io.BufferedWriter, options: Options) In
893902 } else null,
894903 };
895904 @memcpy(client.partially_read_buffer[0..leftover.len], leftover);
896 return client;
905 client.reader.init(.{
906 .context = client,
907 .vtable = &.{
908 .read = reader_read,
909 .readv = reader_readv,
910 },
911 }, input.storage.buffer[0..0]);
912 return;
897913 },
898914 else => return error.TlsUnexpectedMessage,
899915 }
......@@ -919,81 +935,45 @@ pub fn writer(c: *Client) std.io.Writer {
919935
920936fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
921937 const c: *Client = @alignCast(@ptrCast(context));
922 assert(data.len > 1 or splat > 0);
923 return writeEnd(c, data[0], false);
924}
925
926/// If `end` is true, then this function additionally sends a `close_notify`
927/// alert, which is necessary for the server to distinguish between a properly
928/// finished TLS session, or a truncation attack.
929pub fn writeAllEnd(c: *Client, bytes: []const u8, end: bool) anyerror!void {
930 var index: usize = 0;
931 while (index < bytes.len) {
932 index += try c.writeEnd(bytes[index..], end);
938 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
939 const output = &c.output;
940 const ciphertext_buf = try output.writableSlice(min_buffer_len);
941 var total_clear: usize = 0;
942 var ciphertext_end: usize = 0;
943 for (sliced_data) |buf| {
944 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
945 total_clear += prepared.cleartext_len;
946 ciphertext_end += prepared.ciphertext_end;
947 if (total_clear < buf.len) break;
933948 }
949 output.advance(ciphertext_end);
950 return total_clear;
934951}
935952
936/// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`.
937/// If `end` is true, then this function additionally sends a `close_notify` alert,
938/// which is necessary for the server to distinguish between a properly finished
939/// TLS session, or a truncation attack.
940pub fn writeEnd(c: *Client, bytes: []const u8, end: bool) anyerror!usize {
941 var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined;
942 var iovecs_buf: [6][]const u8 = undefined;
943 var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data);
944 if (end) {
945 prepared.iovec_end += prepareCiphertextRecord(
946 c,
947 iovecs_buf[prepared.iovec_end..],
948 ciphertext_buf[prepared.ciphertext_end..],
949 &tls.close_notify_alert,
950 .alert,
951 ).iovec_end;
952 }
953
954 const iovec_end = prepared.iovec_end;
955 const overhead_len = prepared.overhead_len;
956
957 // Ideally we would call writev exactly once here, however, we must ensure
958 // that we don't return with a record partially written.
959 var i: usize = 0;
960 var total_amt: usize = 0;
961 while (true) {
962 var amt = try c.output.writev(iovecs_buf[i..iovec_end]);
963 while (amt >= iovecs_buf[i].len) {
964 const encrypted_amt = iovecs_buf[i].len;
965 total_amt += encrypted_amt - overhead_len;
966 amt -= encrypted_amt;
967 i += 1;
968 // Rely on the property that iovecs delineate records, meaning that
969 // if amt equals zero here, we have fortunately found ourselves
970 // with a short read that aligns at the record boundary.
971 if (i >= iovec_end) return total_amt;
972 // We also cannot return on a vector boundary if the final close_notify is
973 // not sent; otherwise the caller would not know to retry the call.
974 if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt;
975 }
976 iovecs_buf[i] = iovecs_buf[i][amt..];
977 }
953/// Sends a `close_notify` alert, which is necessary for the server to
954/// distinguish between a properly finished TLS session, or a truncation
955/// attack.
956pub fn end(c: *Client) anyerror!void {
957 const output = &c.output;
958 const ciphertext_buf = try output.writableSlice(min_buffer_len);
959 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
960 output.advance(prepared.cleartext_len);
961 return prepared.ciphertext_end;
978962}
979963
980964fn prepareCiphertextRecord(
981965 c: *Client,
982 iovecs: [][]const u8,
983966 ciphertext_buf: []u8,
984967 bytes: []const u8,
985968 inner_content_type: tls.ContentType,
986969) struct {
987 iovec_end: usize,
988970 ciphertext_end: usize,
989 /// How many bytes are taken up by overhead per record.
990 overhead_len: usize,
971 cleartext_len: usize,
991972} {
992973 // Due to the trailing inner content type byte in the ciphertext, we need
993974 // an additional buffer for storing the cleartext into before encrypting.
994975 var cleartext_buf: [max_ciphertext_len]u8 = undefined;
995976 var ciphertext_end: usize = 0;
996 var iovec_end: usize = 0;
997977 var bytes_i: usize = 0;
998978 switch (c.application_cipher) {
999979 inline else => |*p| switch (c.tls_version) {
......@@ -1001,18 +981,15 @@ fn prepareCiphertextRecord(
1001981 const pv = &p.tls_1_3;
1002982 const P = @TypeOf(p.*);
1003983 const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1;
1004 const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len;
1005984 while (true) {
1006985 const encrypted_content_len: u16 = @min(
1007986 bytes.len - bytes_i,
1008987 tls.max_ciphertext_inner_record_len,
1009 ciphertext_buf.len -|
1010 (close_notify_alert_reserved + overhead_len + ciphertext_end),
988 ciphertext_buf.len -| (overhead_len + ciphertext_end),
1011989 );
1012990 if (encrypted_content_len == 0) return .{
1013 .iovec_end = iovec_end,
1014991 .ciphertext_end = ciphertext_end,
1015 .overhead_len = overhead_len,
992 .cleartext_len = bytes_i,
1016993 };
1017994
1018995 @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]);
......@@ -1021,7 +998,6 @@ fn prepareCiphertextRecord(
1021998 const ciphertext_len = encrypted_content_len + 1;
1022999 const cleartext = cleartext_buf[0..ciphertext_len];
10231000
1024 const record_start = ciphertext_end;
10251001 const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
10261002 ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++
10271003 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
......@@ -1039,35 +1015,27 @@ fn prepareCiphertextRecord(
10391015 };
10401016 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);
10411017 c.write_seq += 1; // TODO send key_update on overflow
1042
1043 const record = ciphertext_buf[record_start..ciphertext_end];
1044 iovecs[iovec_end] = record;
1045 iovec_end += 1;
10461018 }
10471019 },
10481020 .tls_1_2 => {
10491021 const pv = &p.tls_1_2;
10501022 const P = @TypeOf(p.*);
10511023 const overhead_len = tls.record_header_len + P.record_iv_length + P.mac_length;
1052 const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len;
10531024 while (true) {
10541025 const message_len: u16 = @min(
10551026 bytes.len - bytes_i,
10561027 tls.max_ciphertext_inner_record_len,
1057 ciphertext_buf.len -|
1058 (close_notify_alert_reserved + overhead_len + ciphertext_end),
1028 ciphertext_buf.len -| (overhead_len + ciphertext_end),
10591029 );
10601030 if (message_len == 0) return .{
1061 .iovec_end = iovec_end,
10621031 .ciphertext_end = ciphertext_end,
1063 .overhead_len = overhead_len,
1032 .cleartext_len = bytes_i,
10641033 };
10651034
10661035 @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]);
10671036 bytes_i += message_len;
10681037 const cleartext = cleartext_buf[0..message_len];
10691038
1070 const record_start = ciphertext_end;
10711039 const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
10721040 ciphertext_end += tls.record_header_len;
10731041 record_header.* = .{@intFromEnum(inner_content_type)} ++
......@@ -1089,10 +1057,6 @@ fn prepareCiphertextRecord(
10891057 ciphertext_end += P.mac_length;
10901058 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key);
10911059 c.write_seq += 1; // TODO send key_update on overflow
1092
1093 const record = ciphertext_buf[record_start..ciphertext_end];
1094 iovecs[iovec_end] = record;
1095 iovec_end += 1;
10961060 }
10971061 },
10981062 else => unreachable,
......@@ -1106,74 +1070,22 @@ pub fn eof(c: Client) bool {
11061070 c.partial_ciphertext_idx >= c.partial_ciphertext_end;
11071071}
11081072
1109/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1110/// Returns the number of bytes read, calling the underlying read function the
1111/// minimal number of times until the buffer has at least `len` bytes filled.
1112/// If the number read is less than `len` it means the stream reached the end.
1113/// Reaching the end of the stream is not an error condition.
1114pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize {
1115 var iovecs = [1]std.posix.iovec{.{ .base = buffer.ptr, .len = buffer.len }};
1116 return readvAtLeast(c, stream, &iovecs, len);
1073fn reader_read(
1074 context: ?*anyopaque,
1075 bw: *std.io.BufferedWriter,
1076 limit: std.io.Reader.Limit,
1077) anyerror!std.io.Reader.Status {
1078 const buf = limit.slice(try bw.writableSlice(1));
1079 const status = try reader_readv(context, &.{buf});
1080 bw.advance(status.len);
1081 return status;
11171082}
11181083
1119/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1120pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize {
1121 return readAtLeast(c, stream, buffer, 1);
1122}
1123
1124/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1125/// Returns the number of bytes read. If the number read is smaller than
1126/// `buffer.len`, it means the stream reached the end. Reaching the end of the
1127/// stream is not an error condition.
1128pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize {
1129 return readAtLeast(c, stream, buffer, buffer.len);
1130}
1084fn reader_readv(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
1085 const c: *Client = @ptrCast(@alignCast(context));
1086 if (c.eof()) return .{ .end = true };
11311087
1132/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1133/// Returns the number of bytes read. If the number read is less than the space
1134/// provided it means the stream reached the end. Reaching the end of the
1135/// stream is not an error condition.
1136/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1137/// order to handle partial reads from the underlying stream layer.
1138pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize {
1139 return readvAtLeast(c, stream, iovecs, 1);
1140}
1141
1142/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1143/// Returns the number of bytes read, calling the underlying read function the
1144/// minimal number of times until the iovecs have at least `len` bytes filled.
1145/// If the number read is less than `len` it means the stream reached the end.
1146/// Reaching the end of the stream is not an error condition.
1147/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1148/// order to handle partial reads from the underlying stream layer.
1149pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: usize) !usize {
1150 if (c.eof()) return 0;
1151
1152 var off_i: usize = 0;
1153 var vec_i: usize = 0;
1154 while (true) {
1155 var amt = try c.readvAdvanced(stream, iovecs[vec_i..]);
1156 off_i += amt;
1157 if (c.eof() or off_i >= len) return off_i;
1158 while (amt >= iovecs[vec_i].len) {
1159 amt -= iovecs[vec_i].len;
1160 vec_i += 1;
1161 }
1162 iovecs[vec_i].base += amt;
1163 iovecs[vec_i].len -= amt;
1164 }
1165}
1166
1167/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1168/// Returns number of bytes that have been read, populated inside `iovecs`. A
1169/// return value of zero bytes does not mean end of stream. Instead, check the `eof()`
1170/// for the end of stream. The `eof()` may be true after any call to
1171/// `read`, including when greater than zero bytes are returned, and this
1172/// function asserts that `eof()` is `false`.
1173/// See `readv` for a higher level function that has the same, familiar API as
1174/// other read functions, such as `std.fs.File.read`.
1175pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize {
1176 var vp: VecPut = .{ .iovecs = iovecs };
1088 var vp: VecPut = .{ .iovecs = data };
11771089
11781090 // Give away the buffered cleartext we have, if any.
11791091 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];
......@@ -1193,11 +1105,11 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
11931105 if (c.received_close_notify) {
11941106 c.partial_ciphertext_end = 0;
11951107 assert(vp.total == amt);
1196 return amt;
1108 return .{ .len = amt, .end = c.eof() };
11971109 } else if (amt > 0) {
11981110 // We don't need more data, so don't call read.
11991111 assert(vp.total == amt);
1200 return amt;
1112 return .{ .len = amt, .end = c.eof() };
12011113 }
12021114 }
12031115
......@@ -1241,7 +1153,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
12411153 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);
12421154 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;
12431155 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);
1244 const actual_read_len = try stream.readv(ask_iovecs);
1156 const actual_read_len = try c.input.readv(ask_iovecs);
12451157 if (actual_read_len == 0) {
12461158 // This is either a truncation attack, a bug in the server, or an
12471159 // intentional omission of the close_notify message due to truncation
......@@ -1268,7 +1180,7 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
12681180 // Perfect split.
12691181 if (frag.ptr == frag1.ptr) {
12701182 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1271 return vp.total;
1183 return .{ .len = vp.total, .end = c.eof() };
12721184 }
12731185 frag = frag1;
12741186 in = 0;
......@@ -1310,8 +1222,8 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
13101222 const record_len = mem.readInt(u16, frag[in..][0..2], .big);
13111223 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;
13121224 in += 2;
1313 const end = in + record_len;
1314 if (end > frag.len) {
1225 const the_end = in + record_len;
1226 if (the_end > frag.len) {
13151227 // We need the record header on the next iteration of the loop.
13161228 in -= tls.record_header_len;
13171229
......@@ -1398,17 +1310,23 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
13981310 .alert => {
13991311 if (cleartext.len != 2) return error.TlsDecodeError;
14001312 const level: tls.AlertLevel = @enumFromInt(cleartext[0]);
1313 _ = level;
14011314 const desc: tls.AlertDescription = @enumFromInt(cleartext[1]);
1402 if (desc == .close_notify) {
1403 c.received_close_notify = true;
1404 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1405 return vp.total;
1315 switch (desc) {
1316 .close_notify => {
1317 c.received_close_notify = true;
1318 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1319 return .{ .len = vp.total, .end = c.eof() };
1320 },
1321 .user_canceled => {
1322 // TODO: handle server-side closures
1323 return error.TlsUnexpectedMessage;
1324 },
1325 else => {
1326 c.diagnostics = .{ .alert = desc };
1327 return error.TlsAlert;
1328 },
14061329 }
1407 _ = level;
1408
1409 try desc.toError();
1410 // TODO: handle server-side closures
1411 return error.TlsUnexpectedMessage;
14121330 },
14131331 .handshake => {
14141332 var ct_i: usize = 0;
......@@ -1524,7 +1442,7 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi
15241442 }) catch {};
15251443}
15261444
1527fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
1445fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) std.io.Reader.Status {
15281446 const saved_buf = frag[in..];
15291447 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
15301448 // There is cleartext at the beginning already which we need to preserve.
......@@ -1536,11 +1454,11 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
15361454 c.partial_ciphertext_end = @intCast(saved_buf.len);
15371455 @memcpy(c.partially_read_buffer[0..saved_buf.len], saved_buf);
15381456 }
1539 return out;
1457 return .{ .len = out, .end = c.eof() };
15401458}
15411459
15421460/// Note that `first` usually overlaps with `c.partially_read_buffer`.
1543fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usize {
1461fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) std.io.Reader.Status {
15441462 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
15451463 // There is cleartext at the beginning already which we need to preserve.
15461464 c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + first.len + frag1.len);
......@@ -1555,7 +1473,7 @@ fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usi
15551473 std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first);
15561474 @memcpy(c.partially_read_buffer[first.len..][0..frag1.len], frag1);
15571475 }
1558 return out;
1476 return .{ .len = out, .end = c.eof() };
15591477}
15601478
15611479fn limitedOverlapCopy(frag: []u8, in: usize) void {
......@@ -1577,9 +1495,6 @@ fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 {
15771495 }
15781496}
15791497
1580const builtin = @import("builtin");
1581const native_endian = builtin.cpu.arch.endian();
1582
15831498inline fn big(x: anytype) @TypeOf(x) {
15841499 return switch (native_endian) {
15851500 .big => x,
......@@ -1958,7 +1873,3 @@ else
19581873 .AES_256_GCM_SHA384,
19591874 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
19601875 });
1961
1962test {
1963 _ = StreamInterface;
1964}
lib/std/http/Client.zig+243-225
......@@ -24,6 +24,12 @@ allocator: Allocator,
2424
2525ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
2626ca_bundle_mutex: std.Thread.Mutex = .{},
27/// Used both for the reader and writer buffers.
28tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.crypto.tls.Client.min_buffer_len,
29/// If non-null, ssl secrets are logged to a stream. Creating such a stream
30/// allows other processes with access to that stream to decrypt all
31/// traffic over connections created with this `Client`.
32ssl_key_logger: ?*std.io.BufferedWriter = null,
2733
2834/// When this is `true`, the next time this client performs an HTTPS request,
2935/// it will first rescan the system for root certificates.
......@@ -31,6 +37,10 @@ next_https_rescan_certs: bool = true,
3137
3238/// The pool of connections that can be reused (and currently in use).
3339connection_pool: ConnectionPool = .{},
40/// Each `Connection` allocates this amount for the reader buffer.
41read_buffer_size: usize,
42/// Each `Connection` allocates this amount for the writer buffer.
43write_buffer_size: usize,
3444
3545/// If populated, all http traffic travels through this third party.
3646/// This field cannot be modified while the client has active connections.
......@@ -41,7 +51,7 @@ http_proxy: ?*Proxy = null,
4151/// Pointer to externally-owned memory.
4252https_proxy: ?*Proxy = null,
4353
44/// A set of linked lists of connections that can be reused.
54/// A Least-Recently-Used cache of open connections to be reused.
4555pub const ConnectionPool = struct {
4656 mutex: std.Thread.Mutex = .{},
4757 /// Open connections that are currently in use.
......@@ -58,8 +68,10 @@ pub const ConnectionPool = struct {
5868 protocol: Connection.Protocol,
5969 };
6070
61 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
71 /// Finds and acquires a connection from the connection pool matching the criteria.
6272 /// If no connection is found, null is returned.
73 ///
74 /// Threadsafe.
6375 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {
6476 pool.mutex.lock();
6577 defer pool.mutex.unlock();
......@@ -96,21 +108,21 @@ pub const ConnectionPool = struct {
96108 return pool.acquireUnsafe(connection);
97109 }
98110
99 /// Tries to release a connection back to the connection pool. This function is threadsafe.
111 /// Tries to release a connection back to the connection pool.
100112 /// If the connection is marked as closing, it will be closed instead.
101113 ///
102 /// The allocator must be the owner of all nodes in this pool.
103 /// The allocator must be the owner of all resources associated with the connection.
114 /// `allocator` must be the same one used to create `connection`.
115 ///
116 /// Threadsafe.
104117 pub fn release(pool: *ConnectionPool, allocator: Allocator, connection: *Connection) void {
118 if (connection.closing) return connection.destroy(allocator);
119
105120 pool.mutex.lock();
106121 defer pool.mutex.unlock();
107122
108123 pool.used.remove(&connection.pool_node);
109124
110 if (connection.closing or pool.free_size == 0) {
111 connection.close(allocator);
112 return allocator.destroy(connection);
113 }
125 if (pool.free_size == 0) return connection.destroy(allocator);
114126
115127 if (pool.free_len >= pool.free_size) {
116128 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
......@@ -138,9 +150,11 @@ pub const ConnectionPool = struct {
138150 pool.used.append(&connection.pool_node);
139151 }
140152
141 /// Resizes the connection pool. This function is threadsafe.
153 /// Resizes the connection pool.
142154 ///
143155 /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size.
156 ///
157 /// Threadsafe.
144158 pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void {
145159 pool.mutex.lock();
146160 defer pool.mutex.unlock();
......@@ -158,9 +172,11 @@ pub const ConnectionPool = struct {
158172 pool.free_size = new_size;
159173 }
160174
161 /// Frees the connection pool and closes all connections within. This function is threadsafe.
175 /// Frees the connection pool and closes all connections within.
162176 ///
163177 /// All future operations on the connection pool will deadlock.
178 ///
179 /// Threadsafe.
164180 pub fn deinit(pool: *ConnectionPool, allocator: Allocator) void {
165181 pool.mutex.lock();
166182
......@@ -184,160 +200,212 @@ pub const ConnectionPool = struct {
184200 }
185201};
186202
187/// An interface to either a plain or TLS connection.
188203pub const Connection = struct {
204 client: *Client,
189205 stream: net.Stream,
190 /// Populated when protocol is TLS; this is the writer given to the TLS
191 /// client, which writes directly to `stream`, unbuffered.
192 stream_writer: std.io.BufferedWriter,
193 /// undefined unless protocol is tls.
194 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
195
206 /// HTTP protocol from client to server.
207 /// This either goes directly to `stream`, or to a TLS client.
208 writer: std.io.BufferedWriter,
196209 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
197210 pool_node: std.DoublyLinkedList.Node,
198
199 /// The protocol that this connection is using.
200 protocol: Protocol,
201
202 /// The host that this connection is connected to.
203 host: []u8,
204
205 /// The port that this connection is connected to.
206211 port: u16,
212 host_len: u8,
213 proxied: bool,
214 closing: bool,
215 protocol: Protocol,
207216
208 /// Whether this connection is proxied and is not directly connected.
209 proxied: bool = false,
217 pub const Protocol = enum { plain, tls };
210218
211 /// Whether this connection is closing when we're done with it.
212 closing: bool = false,
219 const Plain = struct {
220 /// Data from `Connection.stream`.
221 reader: std.io.BufferedReader,
222 connection: Connection,
223
224 fn create(
225 client: *Client,
226 remote_host: []const u8,
227 port: u16,
228 stream: net.Stream,
229 ) error{OutOfMemory}!*Connection {
230 const gpa = client.allocator;
231 const alloc_len = allocLen(client, remote_host.len);
232 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
233 errdefer gpa.free(base);
234 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len];
235 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
236 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
237 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
238 @memcpy(host_buffer, remote_host);
239 const plain: *Plain = @ptrCast(base);
240 plain.* = .{
241 .connection = .{
242 .client = client,
243 .stream = stream,
244 .writer = stream.writer().buffered(socket_write_buffer),
245 .pool_node = .{},
246 .port = port,
247 .proxied = false,
248 .closing = false,
249 .protocol = .plain,
250 },
251 .reader = undefined,
252 };
253 plain.reader.init(stream.reader(), socket_read_buffer);
254 }
213255
214 read_start: BufferSize = 0,
215 read_end: BufferSize = 0,
216 read_buf: [buffer_size]u8,
256 fn destroy(plain: *Plain) void {
257 const c = &plain.connection;
258 const gpa = c.client.allocator;
259 const base: [*]u8 = @ptrCast(plain);
260 gpa.free(base[0..allocLen(c.client, c.host_len)]);
261 }
217262
218 write_buffer: [buffer_size]u8,
219 writer: std.io.BufferedWriter,
263 fn allocLen(client: *Client, host_len: usize) usize {
264 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
265 }
220266
221 pub const buffer_size = std.crypto.tls.max_ciphertext_record_len;
222 const BufferSize = std.math.IntFittingRange(0, buffer_size);
267 fn host(plain: *Plain) []u8 {
268 const base: [*]u8 = @ptrCast(plain);
269 return base[@sizeOf(Plain)..][0..plain.connection.host_len];
270 }
271 };
223272
224 pub const Protocol = enum { plain, tls };
273 const Tls = struct {
274 /// Data from `client` to `Connection.stream`.
275 writer: std.io.BufferedWriter,
276 /// Data from `Connection.stream` to `client`.
277 reader: std.io.BufferedReader,
278 client: std.crypto.tls.Client,
279 connection: Connection,
280
281 fn create(
282 client: *Client,
283 remote_host: []const u8,
284 port: u16,
285 stream: net.Stream,
286 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
287 const gpa = client.allocator;
288 const alloc_len = allocLen(client, remote_host.len);
289 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
290 errdefer gpa.free(base);
291 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];
292 const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.tls_buffer_size];
293 const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size];
294 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
295 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
296 @memcpy(host_buffer, remote_host);
297 const tls: *Tls = @ptrCast(base);
298 tls.* = .{
299 .connection = .{
300 .client = client,
301 .stream = stream,
302 .writer = tls.client.writer().buffered(socket_write_buffer),
303 .pool_node = .{},
304 .port = port,
305 .proxied = false,
306 .closing = false,
307 .protocol = .tls,
308 },
309 .writer = stream.writer().buffered(tls_write_buffer),
310 .reader = undefined,
311 .client = undefined,
312 };
313 tls.reader.init(stream.reader(), tls_read_buffer);
314 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
315 tls.client.init(&tls.reader, &tls.writer, .{
316 .host = .{ .explicit = remote_host },
317 .ca = .{ .bundle = client.ca_bundle },
318 .ssl_key_logger = client.ssl_key_logger,
319 }) catch return error.TlsInitializationFailed;
320 // This is appropriate for HTTPS because the HTTP headers contain
321 // the content length which is used to detect truncation attacks.
322 tls.client.allow_truncation_attacks = true;
225323
226 pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
227 return conn.tls_client.readv(conn.stream, buffers) catch |err| {
228 // https://github.com/ziglang/zig/issues/2473
229 if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
324 return tls;
325 }
230326
231 switch (err) {
232 error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
233 error.ConnectionTimedOut => return error.ConnectionTimedOut,
234 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
235 else => return error.UnexpectedReadFailure,
236 }
237 };
238 }
327 fn destroy(tls: *Tls, gpa: Allocator) void {
328 const c = &tls.connection;
329 const base: [*]u8 = @ptrCast(tls);
330 gpa.free(base[0..allocLen(c.client, c.host_len)]);
331 }
239332
240 pub fn readvDirect(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
241 if (conn.protocol == .tls) {
242 if (disable_tls) unreachable;
333 fn allocLen(client: *Client, host_len: usize) usize {
334 return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + client.write_buffer_size;
335 }
243336
244 return conn.readvDirectTls(buffers);
337 fn host(tls: *Tls) []u8 {
338 const base: [*]u8 = @ptrCast(tls);
339 return base[@sizeOf(Tls)..][0..tls.connection.host_len];
245340 }
341 };
246342
247 return conn.stream.readv(buffers) catch |err| switch (err) {
248 error.ConnectionTimedOut => return error.ConnectionTimedOut,
249 error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
250 else => return error.UnexpectedReadFailure,
343 fn host(c: *Connection) []u8 {
344 return switch (c.protocol) {
345 .tls => {
346 if (disable_tls) unreachable;
347 const tls: *Tls = @fieldParentPtr("connection", c);
348 return tls.host();
349 },
350 .plain => {
351 const plain: *Plain = @fieldParentPtr("connection", c);
352 return plain.host();
353 },
251354 };
252355 }
253356
254 /// Refills the read buffer with data from the connection.
255 pub fn fill(conn: *Connection) ReadError!void {
256 if (conn.read_end != conn.read_start) return;
257
258 var iovecs = [1]std.posix.iovec{
259 .{ .base = &conn.read_buf, .len = conn.read_buf.len },
357 /// This is either data from `stream`, or `Tls.client`.
358 fn reader(c: *Connection) *std.io.BufferedReader {
359 return switch (c.protocol) {
360 .tls => {
361 if (disable_tls) unreachable;
362 const tls: *Tls = @fieldParentPtr("connection", c);
363 return &tls.client.reader;
364 },
365 .plain => {
366 const plain: *Plain = @fieldParentPtr("connection", c);
367 return &plain.reader;
368 },
260369 };
261 const nread = try conn.readvDirect(&iovecs);
262 if (nread == 0) return error.EndOfStream;
263 conn.read_start = 0;
264 conn.read_end = @intCast(nread);
265370 }
266371
267 /// Returns the current slice of buffered data.
268 pub fn peek(conn: *Connection) []const u8 {
269 return conn.read_buf[conn.read_start..conn.read_end];
270 }
271
272 /// Discards the given number of bytes from the read buffer.
273 pub fn drop(conn: *Connection, num: BufferSize) void {
274 conn.read_start += num;
275 }
276
277 /// Reads data from the connection into the given buffer.
278 pub fn read(conn: *Connection, buffer: []u8) ReadError!usize {
279 const available_read = conn.read_end - conn.read_start;
280 const available_buffer = buffer.len;
281
282 if (available_read > available_buffer) { // partially read buffered data
283 @memcpy(buffer[0..available_buffer], conn.read_buf[conn.read_start..conn.read_end][0..available_buffer]);
284 conn.read_start += @intCast(available_buffer);
285
286 return available_buffer;
287 } else if (available_read > 0) { // fully read buffered data
288 @memcpy(buffer[0..available_read], conn.read_buf[conn.read_start..conn.read_end]);
289 conn.read_start += available_read;
290
291 return available_read;
292 }
293
294 var iovecs = [2]std.posix.iovec{
295 .{ .base = buffer.ptr, .len = buffer.len },
296 .{ .base = &conn.read_buf, .len = conn.read_buf.len },
297 };
298 const nread = try conn.readvDirect(&iovecs);
299
300 if (nread > buffer.len) {
301 conn.read_start = 0;
302 conn.read_end = @intCast(nread - buffer.len);
303 return buffer.len;
372 /// If this is called without calling `flush` or `end`, data will be
373 /// dropped unsent.
374 pub fn destroy(c: *Connection) void {
375 c.stream.close();
376 switch (c.protocol) {
377 .tls => {
378 if (disable_tls) unreachable;
379 const tls: *Tls = @fieldParentPtr("connection", c);
380 tls.destroy();
381 },
382 .plain => {
383 const plain: *Plain = @fieldParentPtr("connection", c);
384 plain.destroy();
385 },
304386 }
305
306 return nread;
307387 }
308388
309 pub const ReadError = error{
310 TlsFailure,
311 TlsAlert,
312 ConnectionTimedOut,
313 ConnectionResetByPeer,
314 UnexpectedReadFailure,
315 EndOfStream,
316 };
317
318 pub const Reader = std.io.Reader(*Connection, ReadError, read);
319
320 pub fn reader(conn: *Connection) Reader {
321 return .{ .context = conn };
389 pub fn flush(c: *Connection) anyerror!void {
390 try c.writer.flush();
391 if (c.protocol == .tls) {
392 if (disable_tls) unreachable;
393 const tls: *Tls = @fieldParentPtr("connection", c);
394 try tls.writer.flush();
395 }
322396 }
323397
324 pub const WriteError = error{
325 ConnectionResetByPeer,
326 UnexpectedWriteFailure,
327 };
328
329 pub fn close(conn: *Connection, allocator: Allocator) void {
330 if (conn.protocol == .tls) {
398 /// If the connection is a TLS connection, sends the close_notify alert.
399 ///
400 /// Flushes all buffers.
401 pub fn end(c: *Connection) anyerror!void {
402 try c.writer.flush();
403 if (c.protocol == .tls) {
331404 if (disable_tls) unreachable;
332
333 // try to cleanly close the TLS connection, for any server that cares.
334 _ = conn.tls_client.writeEnd("", true) catch {};
335 if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();
336 allocator.destroy(conn.tls_client);
405 const tls: *Tls = @fieldParentPtr("connection", c);
406 try tls.client.end();
407 try tls.writer.flush();
337408 }
338
339 conn.stream.close();
340 allocator.free(conn.host);
341409 }
342410};
343411
......@@ -350,10 +418,10 @@ pub const RequestTransfer = union(enum) {
350418
351419/// The decompressor for response messages.
352420pub const Compression = union(enum) {
353 pub const DeflateDecompressor = std.compress.zlib.Decompressor(Request.TransferReader);
354 pub const GzipDecompressor = std.compress.gzip.Decompressor(Request.TransferReader);
421 pub const DeflateDecompressor = std.compress.zlib.Decompressor;
422 pub const GzipDecompressor = std.compress.gzip.Decompressor;
355423 // https://github.com/ziglang/zig/issues/18937
356 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Request.TransferReader, .{});
424 //pub const ZstdDecompressor = std.compress.zstd.DecompressStream(.{});
357425
358426 deflate: DeflateDecompressor,
359427 gzip: GzipDecompressor,
......@@ -617,9 +685,6 @@ pub const Response = struct {
617685 }
618686};
619687
620/// A HTTP request that has been sent.
621///
622/// Order of operations: open -> send[ -> write -> finish] -> wait -> read
623688pub const Request = struct {
624689 uri: Uri,
625690 client: *Client,
......@@ -1300,24 +1365,34 @@ pub const basic_authorization = struct {
13001365 }
13011366};
13021367
1303pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed };
1368pub const ConnectTcpError = Allocator.Error || error{
1369 ConnectionRefused,
1370 NetworkUnreachable,
1371 ConnectionTimedOut,
1372 ConnectionResetByPeer,
1373 TemporaryNameServerFailure,
1374 NameServerFailure,
1375 UnknownHostName,
1376 HostLacksNetworkAddresses,
1377 UnexpectedConnectFailure,
1378 TlsInitializationFailed,
1379};
13041380
1305/// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open.
1381/// Reuses a `Connection` if one matching `host` and `port` is already open.
13061382///
1307/// This function is threadsafe.
1308pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*Connection {
1383/// Threadsafe.
1384pub fn connectTcp(
1385 client: *Client,
1386 host: []const u8,
1387 port: u16,
1388 protocol: Connection.Protocol,
1389) ConnectTcpError!*Connection {
13091390 if (client.connection_pool.findConnection(.{
13101391 .host = host,
13111392 .port = port,
13121393 .protocol = protocol,
13131394 })) |conn| return conn;
13141395
1315 if (disable_tls and protocol == .tls)
1316 return error.TlsInitializationFailed;
1317
1318 const conn = try client.allocator.create(Connection);
1319 errdefer client.allocator.destroy(conn);
1320
13211396 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
13221397 error.ConnectionRefused => return error.ConnectionRefused,
13231398 error.NetworkUnreachable => return error.NetworkUnreachable,
......@@ -1331,77 +1406,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13311406 };
13321407 errdefer stream.close();
13331408
1334 conn.* = .{
1335 .stream = stream,
1336 .stream_writer = undefined,
1337 .tls_client = undefined,
1338 .read_buf = undefined,
1339
1340 .write_buffer = undefined,
1341 .writer = undefined, // populated below
1342
1343 .protocol = protocol,
1344 .host = try client.allocator.dupe(u8, host),
1345 .port = port,
1346
1347 .pool_node = .{},
1348 };
1349 errdefer client.allocator.free(conn.host);
1350
13511409 switch (protocol) {
13521410 .tls => {
1353 if (disable_tls) unreachable;
1354
1355 const tls_client = try client.allocator.create(std.crypto.tls.Client);
1356 errdefer client.allocator.destroy(tls_client);
1357
1358 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1359 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
1360 error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,
1361 error.OutOfMemory => return error.OutOfMemory,
1362 };
1363 defer client.allocator.free(ssl_key_log_path);
1364 break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{
1365 .truncate = false,
1366 .mode = switch (builtin.os.tag) {
1367 .windows, .wasi => 0,
1368 else => 0o600,
1369 },
1370 }) catch null;
1371 } else null;
1372 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
1373
1374 conn.stream_writer = .{
1375 .unbuffered_writer = stream.writer(),
1376 .buffer = &.{},
1377 };
1378
1379 tls_client.* = std.crypto.tls.Client.init(stream, &conn.stream_writer, .{
1380 .host = .{ .explicit = host },
1381 .ca = .{ .bundle = client.ca_bundle },
1382 .ssl_key_log_file = ssl_key_log_file,
1383 }) catch return error.TlsInitializationFailed;
1384 // This is appropriate for HTTPS because the HTTP headers contain
1385 // the content length which is used to detect truncation attacks.
1386 tls_client.allow_truncation_attacks = true;
1387
1388 conn.writer = .{
1389 .unbuffered_writer = tls_client.writer(),
1390 .buffer = &conn.write_buffer,
1391 };
1392 conn.tls_client = tls_client;
1411 if (disable_tls) return error.TlsInitializationFailed;
1412 const tc = try Connection.Tls.create(client, host, port, stream);
1413 client.connection_pool.addUsed(&tc.connection);
1414 return &tc.connection;
13931415 },
13941416 .plain => {
1395 conn.writer = .{
1396 .unbuffered_writer = stream.writer(),
1397 .buffer = &conn.write_buffer,
1398 };
1417 const pc = try Connection.Plain.create(client, host, port, stream);
1418 client.connection_pool.addUsed(&pc.connection);
1419 return &pc.connection;
13991420 },
14001421 }
1401
1402 client.connection_pool.addUsed(conn);
1403
1404 return conn;
14051422}
14061423
14071424pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;
......@@ -1662,16 +1679,17 @@ pub fn open(
16621679 var server_header: std.heap.FixedBufferAllocator = .init(options.server_header_buffer);
16631680 const protocol, const valid_uri = try validateUri(uri, server_header.allocator());
16641681
1665 if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
1682 if (protocol == .tls) {
16661683 if (disable_tls) unreachable;
1667
1668 client.ca_bundle_mutex.lock();
1669 defer client.ca_bundle_mutex.unlock();
1670
1671 if (client.next_https_rescan_certs) {
1672 client.ca_bundle.rescan(client.allocator) catch
1673 return error.CertificateBundleLoadFailure;
1674 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1684 if (@atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
1685 client.ca_bundle_mutex.lock();
1686 defer client.ca_bundle_mutex.unlock();
1687
1688 if (client.next_https_rescan_certs) {
1689 client.ca_bundle.rescan(client.allocator) catch
1690 return error.CertificateBundleLoadFailure;
1691 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1692 }
16751693 }
16761694 }
16771695
lib/std/http/Server.zig+52-78
......@@ -1,18 +1,25 @@
11//! Blocking HTTP server implementation.
22//! Handles a single connection's lifecycle.
33
4connection: net.Server.Connection,
4const std = @import("../std.zig");
5const http = std.http;
6const mem = std.mem;
7const net = std.net;
8const Uri = std.Uri;
9const assert = std.debug.assert;
10const testing = std.testing;
11
12const Server = @This();
13
14/// The reader's buffer must be large enough to store the client's entire HTTP
15/// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`.
16in: *std.io.BufferedReader,
17out: *std.io.BufferedWriter,
518/// Keeps track of whether the Server is ready to accept a new request on the
619/// same connection, and makes invalid API usage cause assertion failures
720/// rather than HTTP protocol violations.
821state: State,
9/// User-provided buffer that must outlive this Server.
10/// Used to store the client's entire HTTP header.
11read_buffer: []u8,
12/// Amount of available data inside read_buffer.
13read_buffer_len: usize,
14/// Index into `read_buffer` of the first byte of the next HTTP request.
15next_request_start: usize,
22in_err: anyerror,
1623
1724pub const State = enum {
1825 /// The connection is available to be used for the first time, or reused.
......@@ -31,14 +38,13 @@ pub const State = enum {
3138
3239/// Initialize an HTTP server that can respond to multiple requests on the same
3340/// connection.
41///
3442/// The returned `Server` is ready for `receiveHead` to be called.
35pub fn init(connection: net.Server.Connection, read_buffer: []u8) Server {
43pub fn init(in: *std.io.BufferedReader, out: *std.io.BufferedWriter) Server {
3644 return .{
37 .connection = connection,
45 .in = in,
46 .out = out,
3847 .state = .ready,
39 .read_buffer = read_buffer,
40 .read_buffer_len = 0,
41 .next_request_start = 0,
4248 };
4349}
4450
......@@ -48,78 +54,55 @@ pub const ReceiveHeadError = error{
4854 /// before closing the connection.
4955 HttpHeadersOversize,
5056 /// Client sent headers that did not conform to the HTTP protocol.
57 /// `in_err` is populated with a `Request.Head.ParseError`.
5158 HttpHeadersInvalid,
52 /// A low level I/O error occurred trying to read the headers.
53 HttpHeadersUnreadable,
5459 /// Partial HTTP request was received but the connection was closed before
5560 /// fully receiving the headers.
5661 HttpRequestTruncated,
5762 /// The client sent 0 bytes of headers before closing the stream.
5863 /// In other words, a keep-alive connection was finally closed.
5964 HttpConnectionClosing,
65 /// Error occurred reading from `in`; `in_err` is populated.
66 ReadFailure,
6067};
6168
62/// The header bytes reference the read buffer that Server was initialized with
63/// and remain alive until the next call to receiveHead.
69/// The header bytes reference the internal storage of `in`, which are
70/// invalidated with the next call to `receiveHead`.
6471pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
6572 assert(s.state == .ready);
6673 s.state = .received_head;
6774 errdefer s.state = .receiving_head;
6875
69 // In case of a reused connection, move the next request's bytes to the
70 // beginning of the buffer.
71 if (s.next_request_start > 0) {
72 if (s.read_buffer_len > s.next_request_start) {
73 rebase(s, 0);
74 } else {
75 s.read_buffer_len = 0;
76 }
77 }
78
76 const in = &s.in;
7977 var hp: http.HeadParser = .{};
80
81 if (s.read_buffer_len > 0) {
82 const bytes = s.read_buffer[0..s.read_buffer_len];
83 const end = hp.feed(bytes);
84 if (hp.state == .finished)
85 return finishReceivingHead(s, end);
86 }
78 var head_end: usize = 0;
8779
8880 while (true) {
89 const buf = s.read_buffer[s.read_buffer_len..];
90 if (buf.len == 0)
91 return error.HttpHeadersOversize;
92 const read_n = s.connection.stream.read(buf) catch
93 return error.HttpHeadersUnreadable;
94 if (read_n == 0) {
95 if (s.read_buffer_len > 0) {
96 return error.HttpRequestTruncated;
97 } else {
98 return error.HttpConnectionClosing;
99 }
100 }
101 s.read_buffer_len += read_n;
102 const bytes = buf[0..read_n];
103 const end = hp.feed(bytes);
104 if (hp.state == .finished)
105 return finishReceivingHead(s, s.read_buffer_len - bytes.len + end);
81 if (head_end >= in.bufferContents().len) return error.HttpHeadersOversize;
82 const buf = (in.peekGreedy(head_end + 1) catch |err| {
83 s.in_err = err;
84 return error.ReadFailure;
85 }) orelse switch (head_end) {
86 0 => return error.HttpConnectionClosing,
87 else => return error.HttpRequestTruncated,
88 };
89 head_end += hp.feed(buf[head_end..]);
90 if (hp.state == .finished) return .{
91 .server = s,
92 .head_end = head_end,
93 .head = Request.Head.parse(buf[0..head_end]) catch |err| {
94 s.in_err = err;
95 return error.HttpHeadersInvalid;
96 },
97 .reader_state = undefined,
98 .write_error = undefined,
99 };
106100 }
107101}
108102
109fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request {
110 return .{
111 .server = s,
112 .head_end = head_end,
113 .head = Request.Head.parse(s.read_buffer[0..head_end]) catch
114 return error.HttpHeadersInvalid,
115 .reader_state = undefined,
116 .write_error = undefined,
117 };
118}
119
120103pub const Request = struct {
121104 server: *Server,
122 /// Index into Server's read_buffer.
105 /// Index into `Server.in` internal buffer.
123106 head_end: usize,
124107 head: Head,
125108 reader_state: union {
......@@ -299,7 +282,7 @@ pub const Request = struct {
299282 };
300283
301284 pub fn iterateHeaders(r: *Request) http.HeaderIterator {
302 return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
285 return http.HeaderIterator.init(r.in.bufferContents()[0..r.head_end]);
303286 }
304287
305288 test iterateHeaders {
......@@ -312,13 +295,14 @@ pub const Request = struct {
312295
313296 var read_buffer: [500]u8 = undefined;
314297 @memcpy(read_buffer[0..request_bytes.len], request_bytes);
298 var br: std.io.BufferedReader = undefined;
299 br.initFixed(&read_buffer);
315300
316301 var server: Server = .{
317 .connection = undefined,
302 .in = &br,
303 .out = undefined,
318304 .state = .ready,
319 .read_buffer = &read_buffer,
320 .read_buffer_len = request_bytes.len,
321 .next_request_start = 0,
305 .in_err = undefined,
322306 };
323307
324308 var request: Request = .{
......@@ -1158,13 +1142,3 @@ fn rebase(s: *Server, index: usize) void {
11581142 }
11591143 s.read_buffer_len = index + leftover.len;
11601144}
1161
1162const std = @import("../std.zig");
1163const http = std.http;
1164const mem = std.mem;
1165const net = std.net;
1166const Uri = std.Uri;
1167const assert = std.debug.assert;
1168const testing = std.testing;
1169
1170const Server = @This();
lib/std/io/BufferedReader.zig+61-17
......@@ -94,6 +94,11 @@ pub fn storageBuffer(br: *BufferedReader) []u8 {
9494 return storage.buffer;
9595}
9696
97pub fn bufferContents(br: *BufferedReader) []u8 {
98 const storage = &br.storage;
99 return storage.buffer[br.seek..storage.end];
100}
101
97102/// Although `BufferedReader` can easily satisfy the `Reader` interface, it's
98103/// generally more practical to pass a `BufferedReader` instance itself around,
99104/// since it will result in fewer calls across vtable boundaries.
......@@ -159,31 +164,69 @@ pub fn seekForwardBy(br: *BufferedReader, seek_by: u64) anyerror!void {
159164/// is returned instead.
160165///
161166/// See also:
162/// * `peekAll`
167/// * `peekGreedy`
163168/// * `toss`
164169pub fn peek(br: *BufferedReader, n: usize) anyerror![]u8 {
165 return (try br.peekAll(n))[0..n];
170 return (try br.peekGreedy(n))[0..n];
166171}
167172
168/// Returns the next buffered bytes from `unbuffered_reader`, after filling the buffer
169/// with at least `n` bytes.
173/// Returns the next `n` bytes from `unbuffered_reader`, filling the buffer as
174/// necessary.
170175///
171176/// Invalidates previously returned values from `peek`.
172177///
173178/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
174179/// least as big as `n`.
175180///
181/// If there are fewer than `n` bytes left in the stream, `null` is returned
182/// instead.
183///
184/// See also:
185/// * `peekGreedy`
186/// * `toss`
187pub fn peek2(br: *BufferedReader, n: usize) anyerror!?[]u8 {
188 if (try br.peekGreedy(n)) |buf| return buf[0..n];
189 return null;
190}
191
192/// Returns all the next buffered bytes from `unbuffered_reader`, after filling
193/// the buffer to ensure it contains at least `n` bytes.
194///
195/// Invalidates previously returned values from `peek` and `peekGreedy`.
196///
197/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
198/// least as big as `n`.
199///
176200/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
177201/// is returned instead.
178202///
179203/// See also:
180204/// * `peek`
181205/// * `toss`
182pub fn peekAll(br: *BufferedReader, n: usize) anyerror![]u8 {
183 const storage = &br.storage;
184 assert(n <= storage.buffer.len);
185 try br.fill(n);
186 return storage.buffer[br.seek..storage.end];
206pub fn peekGreedy(br: *BufferedReader, n: usize) anyerror![]u8 {
207 assert(n <= br.storage.buffer.len);
208 if (try br.fill(n)) return br.bufferContents();
209 return error.EndOfStream;
210}
211
212/// Returns all the next buffered bytes from `unbuffered_reader`, after filling
213/// the buffer to ensure it contains at least `n` bytes.
214///
215/// Invalidates previously returned values from `peek` and `peekGreedy`.
216///
217/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
218/// least as big as `n`.
219///
220/// If there are fewer than `n` bytes left in the stream, `null` is returned
221/// instead.
222///
223/// See also:
224/// * `peek`
225/// * `toss`
226pub fn peekGreedy2(br: *BufferedReader, n: usize) anyerror!?[]u8 {
227 assert(n <= br.storage.buffer.len);
228 if (try br.fill(n)) return br.bufferContents();
229 return null;
187230}
188231
189232/// Skips the next `n` bytes from the stream, advancing the seek position. This
......@@ -505,17 +548,17 @@ pub fn discardDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror!vo
505548/// Fills the buffer such that it contains at least `n` bytes, without
506549/// advancing the seek position.
507550///
508/// Returns `error.EndOfStream` if there are fewer than `n` bytes remaining.
551/// Returns `false` if and only if there are fewer than `n` bytes remaining.
509552///
510553/// Asserts buffer capacity is at least `n`.
511pub fn fill(br: *BufferedReader, n: usize) anyerror!void {
554pub fn fill(br: *BufferedReader, n: usize) anyerror!bool {
512555 const storage = &br.storage;
513556 assert(n <= storage.buffer.len);
514557 const buffer = storage.buffer[0..storage.end];
515558 const seek = br.seek;
516559 if (seek + n <= buffer.len) {
517560 @branchHint(.likely);
518 return;
561 return true;
519562 }
520563 const remainder = buffer[seek..];
521564 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
......@@ -523,8 +566,8 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void {
523566 br.seek = 0;
524567 while (true) {
525568 const status = try br.unbuffered_reader.read(storage, .unlimited);
526 if (n <= storage.end) return;
527 if (status.end) return error.EndOfStream;
569 if (n <= storage.end) return true;
570 if (status.end) return false;
528571 }
529572}
530573
......@@ -535,7 +578,8 @@ pub fn takeByte(br: *BufferedReader) anyerror!u8 {
535578 const seek = br.seek;
536579 if (seek >= buffer.len) {
537580 @branchHint(.unlikely);
538 try br.fill(1);
581 const filled = try fill(br, 1);
582 if (!filled) return error.EndOfStream;
539583 }
540584 br.seek = seek + 1;
541585 return buffer[seek];
......@@ -603,7 +647,7 @@ fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) anyerror!Re
603647 var result: UnsignedResult = 0;
604648 var fits = true;
605649 while (true) {
606 const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try br.peekAll(1));
650 const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try br.peekGreedy(1));
607651 for (buffer, 1..) |byte, len| {
608652 if (remaining_bits > 0) {
609653 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
......@@ -639,7 +683,7 @@ test peek {
639683 return error.Unimplemented;
640684}
641685
642test peekAll {
686test peekGreedy {
643687 return error.Unimplemented;
644688}
645689