authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-02 17:27:47-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logbb7af21d6fbd056309d474b27f7f6639287e9e5d
treef5e18ce2d52313d490f1c231b51a772dcd87e31b
parente326d7e8ecd2c999faf0581e8e9c1517dcb5ab52

std.crypto.tls.Client: update to new reader/writer API


8 files changed, 314 insertions(+), 612 deletions(-)

lib/std/crypto/ecdsa.zig+2-2
...@@ -168,7 +168,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -168,7 +168,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
168 has_top_bit = true;168 has_top_bit = true;
169 }169 }
170 const out_slice = out[out.len - expected_len ..];170 const out_slice = out[out.len - expected_len ..];
171 br.read(out_slice) catch return error.InvalidEncoding;171 br.readSlice(out_slice) catch return error.InvalidEncoding;
172 if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding;172 if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding;
173 }173 }
174174
...@@ -177,7 +177,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {...@@ -177,7 +177,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
177 pub fn fromDer(der: []const u8) EncodingError!Signature {177 pub fn fromDer(der: []const u8) EncodingError!Signature {
178 if (der.len < 2) return error.InvalidEncoding;178 if (der.len < 2) return error.InvalidEncoding;
179 var br: std.io.BufferedReader = undefined;179 var br: std.io.BufferedReader = undefined;
180 br.initFixed(der);180 br.initFixed(@constCast(der));
181 const buf = br.take(2) catch return error.InvalidEncoding;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;182 if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) return error.InvalidEncoding;
183 var sig: Signature = mem.zeroInit(Signature, .{});183 var sig: Signature = mem.zeroInit(Signature, .{});
lib/std/crypto/tls.zig+7-5
...@@ -655,7 +655,7 @@ pub const Decoder = struct {...@@ -655,7 +655,7 @@ pub const Decoder = struct {
655 }655 }
656656
657 /// Use this function to increase `their_end`.657 /// Use this function to increase `their_end`.
658 pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void {658 pub fn readAtLeast(d: *Decoder, stream: *std.io.BufferedReader, their_amt: usize) !void {
659 assert(!d.disable_reads);659 assert(!d.disable_reads);
660 const existing_amt = d.cap - d.idx;660 const existing_amt = d.cap - d.idx;
661 d.their_end = d.idx + their_amt;661 d.their_end = d.idx + their_amt;
...@@ -663,14 +663,16 @@ pub const Decoder = struct {...@@ -663,14 +663,16 @@ pub const Decoder = struct {
663 const request_amt = their_amt - existing_amt;663 const request_amt = their_amt - existing_amt;
664 const dest = d.buf[d.cap..];664 const dest = d.buf[d.cap..];
665 if (request_amt > dest.len) return error.TlsRecordOverflow;665 if (request_amt > dest.len) return error.TlsRecordOverflow;
666 const actual_amt = try stream.readAtLeast(dest, request_amt);666 stream.readSlice(dest[0..request_amt]) catch |err| switch (err) {
667 if (actual_amt < request_amt) return error.TlsConnectionTruncated;667 error.EndOfStream => return error.TlsConnectionTruncated,
668 d.cap += actual_amt;668 error.ReadFailed => return error.ReadFailed,
669 };
670 d.cap += request_amt;
669 }671 }
670672
671 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.673 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.
672 /// Use when `our_amt` is calculated by us, not by them.674 /// Use when `our_amt` is calculated by us, not by them.
673 pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void {675 pub fn readAtLeastOurAmt(d: *Decoder, stream: *std.io.BufferedReader, our_amt: usize) !void {
674 assert(!d.disable_reads);676 assert(!d.disable_reads);
675 try readAtLeast(d, stream, our_amt);677 try readAtLeast(d, stream, our_amt);
676 d.our_end = d.idx + our_amt;678 d.our_end = d.idx + our_amt;
lib/std/crypto/tls/Client.zig+258-557
...@@ -4,11 +4,12 @@ const native_endian = builtin.cpu.arch.endian();...@@ -4,11 +4,12 @@ const native_endian = builtin.cpu.arch.endian();
4const std = @import("../../std.zig");4const std = @import("../../std.zig");
5const tls = std.crypto.tls;5const tls = std.crypto.tls;
6const Client = @This();6const Client = @This();
7const net = std.net;
8const mem = std.mem;7const mem = std.mem;
9const crypto = std.crypto;8const crypto = std.crypto;
10const assert = std.debug.assert;9const assert = std.debug.assert;
11const Certificate = std.crypto.Certificate;10const Certificate = std.crypto.Certificate;
11const Reader = std.io.Reader;
12const Writer = std.io.Writer;
1213
13const max_ciphertext_len = tls.max_ciphertext_len;14const max_ciphertext_len = tls.max_ciphertext_len;
14const hmacExpandLabel = tls.hmacExpandLabel;15const hmacExpandLabel = tls.hmacExpandLabel;
...@@ -21,38 +22,22 @@ const array = tls.array;...@@ -21,38 +22,22 @@ const array = tls.array;
21///22///
22/// The buffer is asserted to have capacity at least `min_buffer_len`.23/// The buffer is asserted to have capacity at least `min_buffer_len`.
23///24///
24/// The size is enough to contain exactly one TLSCiphertext record.25/// `remaining_cleartext_len` tells how many bytes inside this buffer have
25/// This buffer is segmented into four parts:26/// already been decrypted.
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,27input: *std.io.BufferedReader,
28/// Tells how many bytes inside `input` have already been decrypted.
29remaining_cleartext_len: u15,
30
33/// The encrypted stream from the client to the server. Bytes are pushed here31/// The encrypted stream from the client to the server. Bytes are pushed here
34/// via `writer`.32/// via `writer`.
35///
36/// The buffer is asserted to have capacity at least `min_buffer_len`.
37output: *std.io.BufferedWriter,33output: *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 when `error.TlsAlert` is returned.
43alert: ?tls.Alert,
44read_err: ?ReadError,
4534
35/// Populated when `error.TlsAlert` is returned.
36alert: ?tls.Alert = null,
37read_err: ?ReadError = null,
46tls_version: tls.ProtocolVersion,38tls_version: tls.ProtocolVersion,
47read_seq: u64,39read_seq: u64,
48write_seq: u64,40write_seq: u64,
49/// The starting index of cleartext bytes inside the input buffer.
50partial_cleartext_idx: u15,
51/// The ending index of cleartext bytes inside the input buffer as well
52/// as the starting index of ciphertext bytes.
53partial_ciphertext_idx: u15,
54/// The ending index of ciphertext bytes inside the input buffer.
55partial_ciphertext_end: u15,
56/// When this is true, the stream may still not be at the end because there41/// When this is true, the stream may still not be at the end because there
57/// may be data in the input buffer.42/// may be data in the input buffer.
58received_close_notify: bool,43received_close_notify: bool,
...@@ -60,11 +45,13 @@ received_close_notify: bool,...@@ -60,11 +45,13 @@ received_close_notify: bool,
60/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify45/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
61/// message has been received. By setting this flag to `true`, instead, the46/// message has been received. By setting this flag to `true`, instead, the
62/// end-of-stream will be forwarded to the application layer above TLS.47/// end-of-stream will be forwarded to the application layer above TLS.
48///
63/// This makes the application vulnerable to truncation attacks unless the49/// This makes the application vulnerable to truncation attacks unless the
64/// application layer itself verifies that the amount of data received equals50/// application layer itself verifies that the amount of data received equals
65/// the amount of data expected, such as HTTP with the Content-Length header.51/// the amount of data expected, such as HTTP with the Content-Length header.
66allow_truncation_attacks: bool,52allow_truncation_attacks: bool,
67application_cipher: tls.ApplicationCipher,53application_cipher: tls.ApplicationCipher,
54
68/// If non-null, ssl secrets are logged to a stream. Creating such a log file55/// If non-null, ssl secrets are logged to a stream. Creating such a log file
69/// allows other programs with access to that file to decrypt all traffic over56/// allows other programs with access to that file to decrypt all traffic over
70/// this connection.57/// this connection.
...@@ -80,6 +67,7 @@ pub const ReadError = error{...@@ -80,6 +67,7 @@ pub const ReadError = error{
80 TlsRecordOverflow,67 TlsRecordOverflow,
81 TlsUnexpectedMessage,68 TlsUnexpectedMessage,
82 TlsIllegalParameter,69 TlsIllegalParameter,
70 TlsSequenceOverflow,
83};71};
8472
85pub const SslKeyLog = struct {73pub const SslKeyLog = struct {
...@@ -99,8 +87,8 @@ pub const SslKeyLog = struct {...@@ -99,8 +87,8 @@ pub const SslKeyLog = struct {
99 }87 }
100};88};
10189
102/// The `std.io.BufferedReader` and `std.io.BufferedWriter` supplied to `init`90/// The `std.io.BufferedReader` supplied to `init` requires a buffer capacity
103/// each require a buffer capacity at least this amount.91/// at least this amount.
104pub const min_buffer_len = tls.max_ciphertext_record_len;92pub const min_buffer_len = tls.max_ciphertext_record_len;
10593
106pub const Options = struct {94pub const Options = struct {
...@@ -126,7 +114,10 @@ pub const Options = struct {...@@ -126,7 +114,10 @@ pub const Options = struct {
126 },114 },
127 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows115 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
128 /// other programs with access to that file to decrypt all traffic over this connection.116 /// other programs with access to that file to decrypt all traffic over this connection.
129 ssl_key_log: ?*std.io.BufferedWriter = null,117 ///
118 /// Only the `writer` field is observed during the handshake (`init`).
119 /// After that, the other fields are populated.
120 ssl_key_log: ?*SslKeyLog = null,
130};121};
131122
132const InitError = error{123const InitError = error{
...@@ -183,16 +174,14 @@ const InitError = error{...@@ -183,16 +174,14 @@ const InitError = error{
183///174///
184/// `host` is only borrowed during this function call.175/// `host` is only borrowed during this function call.
185///176///
186/// Both `input` and `output` are asserted to have buffer capacity at least177/// `input` is asserted to have buffer capacity at least `min_buffer_len`.
187/// `min_buffer_len`.
188pub fn init(178pub fn init(
189 client: *Client,179 client: *Client,
190 input: *std.io.BufferedReader,180 input: *std.io.BufferedReader,
191 output: *std.io.BufferedWriter,181 output: *std.io.BufferedWriter,
192 options: Options,182 options: Options,
193) InitError!void {183) InitError!void {
194 assert(input.storage.buffer.len >= min_buffer_len);184 assert(input.buffer.len >= min_buffer_len);
195 assert(output.buffer.len >= min_buffer_len);
196 client.alert = null;185 client.alert = null;
197 const host = switch (options.host) {186 const host = switch (options.host) {
198 .no_verification => "",187 .no_verification => "",
...@@ -286,7 +275,7 @@ pub fn init(...@@ -286,7 +275,7 @@ pub fn init(
286275
287 {276 {
288 var iovecs: [2][]const u8 = .{ cleartext_header, host };277 var iovecs: [2][]const u8 = .{ cleartext_header, host };
289 try output.writevAll(iovecs[0..if (host.len == 0) 1 else 2]);278 try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]);
290 }279 }
291280
292 var tls_version: tls.ProtocolVersion = undefined;281 var tls_version: tls.ProtocolVersion = undefined;
...@@ -335,20 +324,26 @@ pub fn init(...@@ -335,20 +324,26 @@ pub fn init(
335 var cleartext_fragment_start: usize = 0;324 var cleartext_fragment_start: usize = 0;
336 var cleartext_fragment_end: usize = 0;325 var cleartext_fragment_end: usize = 0;
337 var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined;326 var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined;
338 var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined;
339 var d: tls.Decoder = .{ .buf = &handshake_buffer };
340 fragment: while (true) {327 fragment: while (true) {
341 try d.readAtLeastOurAmt(input, tls.record_header_len);328 // Ensure the input buffer pointer is stable in this scope.
342 const record_header = d.buf[d.idx..][0..tls.record_header_len];329 input.rebaseCapacity(tls.max_ciphertext_record_len);
343 const record_ct = d.decode(tls.ContentType);330 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
344 d.skip(2); // legacy_version331 error.EndOfStream => return error.TlsConnectionTruncated,
345 const record_len = d.decode(u16);332 error.ReadFailed => return error.ReadFailed,
346 try d.readAtLeast(input, record_len);333 };
347 var record_decoder = try d.sub(record_len);334 const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked
335 input.toss(2); // legacy_version
336 const record_len = input.takeInt(u16, .big) catch unreachable; // already peeked
337 if (record_len > tls.max_ciphertext_len) return error.TlsRecordOverflow;
338 const record_buffer = input.take(record_len) catch |err| switch (err) {
339 error.EndOfStream => return error.TlsConnectionTruncated,
340 error.ReadFailed => return error.ReadFailed,
341 };
342 var record_decoder: tls.Decoder = .fromTheirSlice(record_buffer);
348 var ctd, const ct = content: switch (cipher_state) {343 var ctd, const ct = content: switch (cipher_state) {
349 .cleartext => .{ record_decoder, record_ct },344 .cleartext => .{ record_decoder, record_ct },
350 .handshake => {345 .handshake => {
351 std.debug.assert(tls_version == .tls_1_3);346 assert(tls_version == .tls_1_3);
352 if (record_ct != .application_data) return error.TlsUnexpectedMessage;347 if (record_ct != .application_data) return error.TlsUnexpectedMessage;
353 try record_decoder.ensure(record_len);348 try record_decoder.ensure(record_len);
354 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];349 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
...@@ -380,7 +375,7 @@ pub fn init(...@@ -380,7 +375,7 @@ pub fn init(
380 break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct };375 break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct };
381 },376 },
382 .application => {377 .application => {
383 std.debug.assert(tls_version == .tls_1_2);378 assert(tls_version == .tls_1_2);
384 if (record_ct != .handshake) return error.TlsUnexpectedMessage;379 if (record_ct != .handshake) return error.TlsUnexpectedMessage;
385 try record_decoder.ensure(record_len);380 try record_decoder.ensure(record_len);
386 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];381 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
...@@ -536,7 +531,7 @@ pub fn init(...@@ -536,7 +531,7 @@ pub fn init(
536 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);531 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
537 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);532 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
538 const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);533 const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);
539 if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{534 if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
540 .client_random = &client_hello_rand,535 .client_random = &client_hello_rand,
541 }, .{536 }, .{
542 .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret,537 .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret,
...@@ -710,7 +705,7 @@ pub fn init(...@@ -710,7 +705,7 @@ pub fn init(
710 &client_hello_rand,705 &client_hello_rand,
711 &server_hello_rand,706 &server_hello_rand,
712 }, 48);707 }, 48);
713 if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{708 if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
714 .client_random = &client_hello_rand,709 .client_random = &client_hello_rand,
715 }, .{710 }, .{
716 .CLIENT_RANDOM = &master_secret,711 .CLIENT_RANDOM = &master_secret,
...@@ -763,7 +758,7 @@ pub fn init(...@@ -763,7 +758,7 @@ pub fn init(
763 &client_change_cipher_spec_msg,758 &client_change_cipher_spec_msg,
764 &client_verify_msg,759 &client_verify_msg,
765 };760 };
766 try output.writevAll(&all_msgs_vec);761 try output.writeVecAll(&all_msgs_vec);
767 },762 },
768 }763 }
769 write_seq += 1;764 write_seq += 1;
...@@ -828,11 +823,11 @@ pub fn init(...@@ -828,11 +823,11 @@ pub fn init(
828 &client_change_cipher_spec_msg,823 &client_change_cipher_spec_msg,
829 &finished_msg,824 &finished_msg,
830 };825 };
831 try output.writevAll(&all_msgs_vec);826 try output.writeVecAll(&all_msgs_vec);
832827
833 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);828 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
834 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);829 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);
835 if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{830 if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
836 .counter = key_seq,831 .counter = key_seq,
837 .client_random = &client_hello_rand,832 .client_random = &client_hello_rand,
838 }, .{833 }, .{
...@@ -859,11 +854,9 @@ pub fn init(...@@ -859,11 +854,9 @@ pub fn init(
859 else => unreachable,854 else => unreachable,
860 },855 },
861 };856 };
862 const leftover = d.rest();
863 client.* = .{857 client.* = .{
864 .input = input,858 .input = input,
865 .output = output,859 .output = output,
866 .reader = undefined,
867 .tls_version = tls_version,860 .tls_version = tls_version,
868 .read_seq = switch (tls_version) {861 .read_seq = switch (tls_version) {
869 .tls_1_3 => 0,862 .tls_1_3 => 0,
...@@ -875,29 +868,18 @@ pub fn init(...@@ -875,29 +868,18 @@ pub fn init(
875 .tls_1_2 => write_seq,868 .tls_1_2 => write_seq,
876 else => unreachable,869 else => unreachable,
877 },870 },
878 .partial_cleartext_idx = 0,871 .remaining_cleartext_len = 0,
879 .partial_ciphertext_idx = 0,
880 .partial_ciphertext_end = @intCast(leftover.len),
881 .received_close_notify = false,872 .received_close_notify = false,
882 .allow_truncation_attacks = false,873 .allow_truncation_attacks = false,
883 .application_cipher = app_cipher,874 .application_cipher = app_cipher,
884 .partially_read_buffer = undefined,875 .ssl_key_log = options.ssl_key_log,
885 .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{876 };
886 .client_key_seq = key_seq,877 if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{
887 .server_key_seq = key_seq,878 .client_key_seq = key_seq,
888 .client_random = client_hello_rand,879 .server_key_seq = key_seq,
889 .file = key_log_file,880 .client_random = client_hello_rand,
890 } else null,881 .writer = ssl_key_log.writer,
891 };882 };
892 @memcpy(client.partially_read_buffer[0..leftover.len], leftover);
893 client.reader.init(.{
894 .context = client,
895 .vtable = &.{
896 .read = read,
897 .readVec = readVec,
898 .discard = discard,
899 },
900 }, input.storage.buffer[0..0]);
901 return;883 return;
902 },884 },
903 else => return error.TlsUnexpectedMessage,885 else => return error.TlsUnexpectedMessage,
...@@ -912,17 +894,28 @@ pub fn init(...@@ -912,17 +894,28 @@ pub fn init(
912 }894 }
913}895}
914896
915pub fn writer(c: *Client) std.io.Writer {897pub fn reader(c: *Client) Reader {
898 return .{
899 .context = c,
900 .vtable = &.{
901 .read = read,
902 .readVec = readVec,
903 .discard = discard,
904 },
905 };
906}
907
908pub fn writer(c: *Client) Writer {
916 return .{909 return .{
917 .context = c,910 .context = c,
918 .vtable = &.{911 .vtable = &.{
919 .writeSplat = writeSplat,912 .writeSplat = writeSplat,
920 .writeFile = std.io.Writer.unimplementedWriteFile,913 .writeFile = Writer.unimplementedWriteFile,
921 },914 },
922 };915 };
923}916}
924917
925fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {918fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Error!usize {
926 const c: *Client = @alignCast(@ptrCast(context));919 const c: *Client = @alignCast(@ptrCast(context));
927 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;920 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
928 const output = c.output;921 const output = c.output;
...@@ -942,7 +935,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i...@@ -942,7 +935,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i
942/// Sends a `close_notify` alert, which is necessary for the server to935/// Sends a `close_notify` alert, which is necessary for the server to
943/// distinguish between a properly finished TLS session, or a truncation936/// distinguish between a properly finished TLS session, or a truncation
944/// attack.937/// attack.
945pub fn end(c: *Client) std.io.Writer.Error!void {938pub fn end(c: *Client) Writer.Error!void {
946 const output = c.output;939 const output = c.output;
947 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);940 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
948 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);941 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
...@@ -1054,372 +1047,212 @@ fn prepareCiphertextRecord(...@@ -1054,372 +1047,212 @@ fn prepareCiphertextRecord(
1054}1047}
10551048
1056pub fn eof(c: Client) bool {1049pub fn eof(c: Client) bool {
1057 return c.received_close_notify and1050 return c.received_close_notify and c.remaining_cleartext_len == 0;
1058 c.partial_cleartext_idx >= c.partial_ciphertext_idx and
1059 c.partial_ciphertext_idx >= c.partial_ciphertext_end;
1060}
1061
1062fn read(
1063 context: ?*anyopaque,
1064 bw: *std.io.BufferedWriter,
1065 limit: std.io.Reader.Limit,
1066) std.io.Reader.RwError!usize {
1067 const buf = limit.slice(try bw.writableSliceGreedy(1));
1068 const n = try readVec(context, &.{buf});
1069 bw.advance(n);
1070 return n;
1071}1051}
10721052
1073fn readVec(context: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {1053fn read(context: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
1074 const c: *Client = @ptrCast(@alignCast(context));1054 const c: *Client = @ptrCast(@alignCast(context));
1075 if (c.eof()) return error.EndOfStream;1055 if (c.eof()) return error.EndOfStream;
10761056 const input = c.input;
1077 var vp: VecPut = .{ .iovecs = data };1057 if (c.remaining_cleartext_len > 0) {
10781058 const n = try bw.write(input.bufferContents()[0..c.remaining_cleartext_len]);
1079 // Give away the buffered cleartext we have, if any.1059 c.remaining_cleartext_len = @intCast(c.remaining_cleartext_len - n);
1080 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];1060 return n;
1081 if (partial_cleartext.len > 0) {
1082 const amt: u15 = @intCast(vp.put(partial_cleartext));
1083 c.partial_cleartext_idx += amt;
1084
1085 if (c.partial_cleartext_idx == c.partial_ciphertext_idx and
1086 c.partial_ciphertext_end == c.partial_ciphertext_idx)
1087 {
1088 // The buffer is now empty.
1089 c.partial_cleartext_idx = 0;
1090 c.partial_ciphertext_idx = 0;
1091 c.partial_ciphertext_end = 0;
1092 }
1093
1094 if (c.received_close_notify) {
1095 c.partial_ciphertext_end = 0;
1096 assert(vp.total == amt);
1097 return amt;
1098 } else if (amt > 0) {
1099 // We don't need more data, so don't call read.
1100 assert(vp.total == amt);
1101 return amt;
1102 }
1103 }1061 }
11041062 // If at least one full encrypted record is not buffered, read once.
1105 assert(!c.received_close_notify);1063 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
11061064 error.EndOfStream => {
1107 // Ideally, this buffer would never be used. It is needed when `iovecs` are1065 // This is either a truncation attack, a bug in the server, or an
1108 // too small to fit the cleartext, which may be as large as `max_ciphertext_len`.1066 // intentional omission of the close_notify message due to truncation
1109 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;1067 // detection handled above the TLS layer.
1110 // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`.1068 if (c.allow_truncation_attacks) {
1111 var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined;1069 c.received_close_notify = true;
1112 // How many bytes left in the user's buffer.1070 return error.EndOfStream;
1113 const free_size = vp.freeSize();1071 } else {
1114 // The amount of the user's buffer that we need to repurpose for storing1072 return failRead(c, error.TlsConnectionTruncated);
1115 // ciphertext. The end of the buffer will be used for such purposes.1073 }
1116 const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len;
1117 // The amount of the user's buffer that will be used to give cleartext. The
1118 // beginning of the buffer will be used for such purposes.
1119 const cleartext_buf_len = free_size - ciphertext_buf_len;
1120
1121 // Recoup `partially_read_buffer` space. This is necessary because it is assumed
1122 // below that `frag0` is big enough to hold at least one record.
1123 limitedOverlapCopy(c.partially_read_buffer[0..c.partial_ciphertext_end], c.partial_ciphertext_idx);
1124 c.partial_ciphertext_end -= c.partial_ciphertext_idx;
1125 c.partial_ciphertext_idx = 0;
1126 c.partial_cleartext_idx = 0;
1127 const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..];
1128
1129 var ask_iovecs_buf: [2]std.posix.iovec = .{
1130 .{
1131 .base = first_iov.ptr,
1132 .len = first_iov.len,
1133 },
1134 .{
1135 .base = &in_stack_buffer,
1136 .len = in_stack_buffer.len,
1137 },1074 },
1075 error.ReadFailed => return error.ReadFailed,
1138 };1076 };
11391077 const ct: tls.ContentType = @enumFromInt(record_header[0]);
1140 // Cleartext capacity of output buffer, in records. Minimum one full record.1078 const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big);
1141 const buf_cap = @max(cleartext_buf_len / max_ciphertext_len, 1);1079 _ = legacy_version;
1142 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);1080 const record_len = mem.readInt(u16, record_header[3..][0..2], .big);
1143 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;1081 if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
1144 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);1082 const record_end = 5 + record_len;
1145 const actual_read_len = try c.input.readv(ask_iovecs);1083 if (record_end > input.bufferContents().len) {
1146 if (actual_read_len == 0) {1084 input.fillMore() catch |err| switch (err) {
1147 // This is either a truncation attack, a bug in the server, or an1085 error.EndOfStream => return failRead(c, error.TlsConnectionTruncated),
1148 // intentional omission of the close_notify message due to truncation1086 error.ReadFailed => return error.ReadFailed,
1149 // detection handled above the TLS layer.1087 };
1150 if (c.allow_truncation_attacks) {1088 if (record_end > input.bufferContents().len) return 0;
1151 c.received_close_notify = true;
1152 } else {
1153 return failRead(c, error.TlsConnectionTruncated);
1154 }
1155 }1089 }
11561090
1157 // There might be more bytes inside `in_stack_buffer` that need to be processed,1091 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;
1158 // but at least frag0 will have one complete ciphertext record.1092 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1159 const frag0_end = @min(c.partially_read_buffer.len, c.partial_ciphertext_end + actual_read_len);1093 inline else => |*p| switch (c.tls_version) {
1160 const frag0 = c.partially_read_buffer[c.partial_ciphertext_idx..frag0_end];1094 .tls_1_3 => {
1161 var frag1 = in_stack_buffer[0..actual_read_len -| first_iov.len];1095 const pv = &p.tls_1_3;
1162 // We need to decipher frag0 and frag1 but there may be a ciphertext record1096 const P = @TypeOf(p.*);
1163 // straddling the boundary. We can handle this with two memcpy() calls to1097 const ad = input.take(tls.record_header_len) catch unreachable; // already peeked
1164 // assemble the straddling record in between handling the two sides.1098 const ciphertext_len = record_len - P.AEAD.tag_length;
1165 var frag = frag0;1099 const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked
1166 var in: usize = 0;1100 const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked
1167 while (true) {1101 const nonce = nonce: {
1168 if (in == frag.len) {1102 const V = @Vector(P.AEAD.nonce_length, u8);
1169 // Perfect split.1103 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1170 if (frag.ptr == frag1.ptr) {1104 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));
1171 c.partial_ciphertext_end = c.partial_ciphertext_idx;1105 break :nonce @as(V, pv.server_iv) ^ operand;
1172 return vp.total;
1173 }
1174 frag = frag1;
1175 in = 0;
1176 continue;
1177 }
1178
1179 if (in + tls.record_header_len > frag.len) {
1180 if (frag.ptr == frag1.ptr)
1181 return finishRead(c, frag, in, vp.total);
1182
1183 const first = frag[in..];
1184
1185 if (frag1.len < tls.record_header_len)
1186 return finishRead2(c, first, frag1, vp.total);
1187
1188 // A record straddles the two fragments. Copy into the now-empty first fragment.
1189 const record_len_byte_0: u16 = straddleByte(frag, frag1, in + 3);
1190 const record_len_byte_1: u16 = straddleByte(frag, frag1, in + 4);
1191 const record_len = (record_len_byte_0 << 8) | record_len_byte_1;
1192 if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
1193
1194 const full_record_len = record_len + tls.record_header_len;
1195 const second_len = full_record_len - first.len;
1196 if (frag1.len < second_len)
1197 return finishRead2(c, first, frag1, vp.total);
1198
1199 limitedOverlapCopy(frag, in);
1200 @memcpy(frag[first.len..][0..second_len], frag1[0..second_len]);
1201 frag = frag[0..full_record_len];
1202 frag1 = frag1[second_len..];
1203 in = 0;
1204 continue;
1205 }
1206 const ct: tls.ContentType = @enumFromInt(frag[in]);
1207 in += 1;
1208 const legacy_version = mem.readInt(u16, frag[in..][0..2], .big);
1209 in += 2;
1210 _ = legacy_version;
1211 const record_len = mem.readInt(u16, frag[in..][0..2], .big);
1212 if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
1213 in += 2;
1214 const the_end = in + record_len;
1215 if (the_end > frag.len) {
1216 // We need the record header on the next iteration of the loop.
1217 in -= tls.record_header_len;
1218
1219 if (frag.ptr == frag1.ptr)
1220 return finishRead(c, frag, in, vp.total);
1221
1222 // A record straddles the two fragments. Copy into the now-empty first fragment.
1223 const first = frag[in..];
1224 const full_record_len = record_len + tls.record_header_len;
1225 const second_len = full_record_len - first.len;
1226 if (frag1.len < second_len)
1227 return finishRead2(c, first, frag1, vp.total);
1228
1229 limitedOverlapCopy(frag, in);
1230 @memcpy(frag[first.len..][0..second_len], frag1[0..second_len]);
1231 frag = frag[0..full_record_len];
1232 frag1 = frag1[second_len..];
1233 in = 0;
1234 continue;
1235 }
1236 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1237 inline else => |*p| switch (c.tls_version) {
1238 .tls_1_3 => {
1239 const pv = &p.tls_1_3;
1240 const P = @TypeOf(p.*);
1241 const ad = frag[in - tls.record_header_len ..][0..tls.record_header_len];
1242 const ciphertext_len = record_len - P.AEAD.tag_length;
1243 const ciphertext = frag[in..][0..ciphertext_len];
1244 in += ciphertext_len;
1245 const auth_tag = frag[in..][0..P.AEAD.tag_length].*;
1246 const nonce = nonce: {
1247 const V = @Vector(P.AEAD.nonce_length, u8);
1248 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1249 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));
1250 break :nonce @as(V, pv.server_iv) ^ operand;
1251 };
1252 const out_buf = vp.peek();
1253 const cleartext_buf = if (ciphertext.len <= out_buf.len)
1254 out_buf
1255 else
1256 &cleartext_stack_buffer;
1257 const cleartext = cleartext_buf[0..ciphertext.len];
1258 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1259 return failRead(c, error.TlsBadRecordMac);
1260 const msg = mem.trimEnd(u8, cleartext, "\x00");
1261 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };
1262 },
1263 .tls_1_2 => {
1264 const pv = &p.tls_1_2;
1265 const P = @TypeOf(p.*);
1266 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1267 const ad = std.mem.toBytes(big(c.read_seq)) ++
1268 frag[in - tls.record_header_len ..][0 .. 1 + 2] ++
1269 std.mem.toBytes(big(message_len));
1270 const record_iv = frag[in..][0..P.record_iv_length].*;
1271 in += P.record_iv_length;
1272 const masked_read_seq = c.read_seq &
1273 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
1274 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
1275 const V = @Vector(P.AEAD.nonce_length, u8);
1276 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1277 const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq)));
1278 break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand;
1279 };
1280 const ciphertext = frag[in..][0..message_len];
1281 in += message_len;
1282 const auth_tag = frag[in..][0..P.mac_length].*;
1283 in += P.mac_length;
1284 const out_buf = vp.peek();
1285 const cleartext_buf = if (message_len <= out_buf.len)
1286 out_buf
1287 else
1288 &cleartext_stack_buffer;
1289 const cleartext = cleartext_buf[0..ciphertext.len];
1290 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1291 return failRead(c, error.TlsBadRecordMac);
1292 break :cleartext .{ cleartext, ct };
1293 },
1294 else => unreachable,
1295 },
1296 };
1297 c.read_seq = try std.math.add(u64, c.read_seq, 1);
1298 switch (inner_ct) {
1299 .alert => {
1300 if (cleartext.len != 2) return failRead(c, error.TlsDecodeError);
1301 const alert: tls.Alert = .{
1302 .level = @enumFromInt(cleartext[0]),
1303 .description = @enumFromInt(cleartext[1]),
1304 };1106 };
1305 switch (alert.description) {1107 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1306 .close_notify => {1108 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1307 c.received_close_notify = true;1109 return failRead(c, error.TlsBadRecordMac);
1308 c.partial_ciphertext_end = c.partial_ciphertext_idx;1110 const msg = mem.trimRight(u8, cleartext, "\x00");
1309 return vp.total;1111 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };
1310 },
1311 .user_canceled => {
1312 // TODO: handle server-side closures
1313 return failRead(c, error.TlsUnexpectedMessage);
1314 },
1315 else => {
1316 c.alert = alert;
1317 return failRead(c, error.TlsAlert);
1318 },
1319 }
1320 },1112 },
1321 .handshake => {1113 .tls_1_2 => {
1322 var ct_i: usize = 0;1114 const pv = &p.tls_1_2;
1323 while (true) {1115 const P = @TypeOf(p.*);
1324 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);1116 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1325 ct_i += 1;1117 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
1326 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);1118 const ad = std.mem.toBytes(big(c.read_seq)) ++
1327 ct_i += 3;1119 ad_header[0 .. 1 + 2] ++
1328 const next_handshake_i = ct_i + handshake_len;1120 std.mem.toBytes(big(message_len));
1329 if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength);1121 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked
1330 const handshake = cleartext[ct_i..next_handshake_i];1122 const masked_read_seq = c.read_seq &
1331 switch (handshake_type) {1123 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
1332 .new_session_ticket => {1124 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
1333 // This client implementation ignores new session tickets.1125 const V = @Vector(P.AEAD.nonce_length, u8);
1334 },1126 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1335 .key_update => {1127 const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq)));
1336 switch (c.application_cipher) {1128 break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand;
1337 inline else => |*p| {1129 };
1338 const pv = &p.tls_1_3;1130 const ciphertext = input.take(message_len) catch unreachable; // already peeked
1339 const P = @TypeOf(p.*);1131 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked
1340 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);1132 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1341 if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{1133 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1342 .counter = key_log.serverCounter(),1134 return failRead(c, error.TlsBadRecordMac);
1343 .client_random = &key_log.client_random,1135 break :cleartext .{ cleartext, ct };
1344 }, .{
1345 .SERVER_TRAFFIC_SECRET = &server_secret,
1346 });
1347 pv.server_secret = server_secret;
1348 pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
1349 pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
1350 },
1351 }
1352 c.read_seq = 0;
1353
1354 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
1355 .update_requested => {
1356 switch (c.application_cipher) {
1357 inline else => |*p| {
1358 const pv = &p.tls_1_3;
1359 const P = @TypeOf(p.*);
1360 const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);
1361 if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{
1362 .counter = key_log.clientCounter(),
1363 .client_random = &key_log.client_random,
1364 }, .{
1365 .CLIENT_TRAFFIC_SECRET = &client_secret,
1366 });
1367 pv.client_secret = client_secret;
1368 pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
1369 pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
1370 },
1371 }
1372 c.write_seq = 0;
1373 },
1374 .update_not_requested => {},
1375 _ => return failRead(c, error.TlsIllegalParameter),
1376 }
1377 },
1378 else => return failRead(c, error.TlsUnexpectedMessage),
1379 }
1380 ct_i = next_handshake_i;
1381 if (ct_i >= cleartext.len) break;
1382 }
1383 },1136 },
1384 .application_data => {1137 else => unreachable,
1385 // Determine whether the output buffer or a stack1138 },
1386 // buffer was used for storing the cleartext.1139 };
1387 if (cleartext.ptr == &cleartext_stack_buffer) {1140 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
1388 // Stack buffer was used, so we must copy to the output buffer.1141 switch (inner_ct) {
1389 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {1142 .alert => {
1390 // We have already run out of room in iovecs. Continue1143 if (cleartext.len != 2) return failRead(c, error.TlsDecodeError);
1391 // appending to `partially_read_buffer`.1144 const alert: tls.Alert = .{
1392 @memcpy(1145 .level = @enumFromInt(cleartext[0]),
1393 c.partially_read_buffer[c.partial_ciphertext_idx..][0..cleartext.len],1146 .description = @enumFromInt(cleartext[1]),
1394 cleartext,1147 };
1395 );1148 switch (alert.description) {
1396 c.partial_ciphertext_idx = @intCast(c.partial_ciphertext_idx + cleartext.len);1149 .close_notify => {
1397 } else {1150 c.received_close_notify = true;
1398 const amt = vp.put(cleartext);1151 return 0;
1399 if (amt < cleartext.len) {1152 },
1400 const rest = cleartext[amt..];1153 .user_canceled => {
1401 c.partial_cleartext_idx = 0;1154 // TODO: handle server-side closures
1402 c.partial_ciphertext_idx = @intCast(rest.len);1155 return failRead(c, error.TlsUnexpectedMessage);
1403 @memcpy(c.partially_read_buffer[0..rest.len], rest);1156 },
1157 else => {
1158 c.alert = alert;
1159 return failRead(c, error.TlsAlert);
1160 },
1161 }
1162 },
1163 .handshake => {
1164 var ct_i: usize = 0;
1165 while (true) {
1166 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);
1167 ct_i += 1;
1168 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);
1169 ct_i += 3;
1170 const next_handshake_i = ct_i + handshake_len;
1171 if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength);
1172 const handshake = cleartext[ct_i..next_handshake_i];
1173 switch (handshake_type) {
1174 .new_session_ticket => {
1175 // This client implementation ignores new session tickets.
1176 },
1177 .key_update => {
1178 switch (c.application_cipher) {
1179 inline else => |*p| {
1180 const pv = &p.tls_1_3;
1181 const P = @TypeOf(p.*);
1182 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);
1183 if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
1184 .counter = key_log.serverCounter(),
1185 .client_random = &key_log.client_random,
1186 }, .{
1187 .SERVER_TRAFFIC_SECRET = &server_secret,
1188 });
1189 pv.server_secret = server_secret;
1190 pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
1191 pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
1192 },
1404 }1193 }
1405 }1194 c.read_seq = 0;
1406 } else {1195
1407 // Output buffer was used directly which means no1196 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
1408 // memory copying needs to occur, and we can move1197 .update_requested => {
1409 // on to the next ciphertext record.1198 switch (c.application_cipher) {
1410 vp.next(cleartext.len);1199 inline else => |*p| {
1200 const pv = &p.tls_1_3;
1201 const P = @TypeOf(p.*);
1202 const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);
1203 if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
1204 .counter = key_log.clientCounter(),
1205 .client_random = &key_log.client_random,
1206 }, .{
1207 .CLIENT_TRAFFIC_SECRET = &client_secret,
1208 });
1209 pv.client_secret = client_secret;
1210 pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
1211 pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
1212 },
1213 }
1214 c.write_seq = 0;
1215 },
1216 .update_not_requested => {},
1217 _ => return failRead(c, error.TlsIllegalParameter),
1218 }
1219 },
1220 else => return failRead(c, error.TlsUnexpectedMessage),
1411 }1221 }
1412 },1222 ct_i = next_handshake_i;
1413 else => return failRead(c, error.TlsUnexpectedMessage),1223 if (ct_i >= cleartext.len) break;
1414 }1224 }
1415 in = end;1225 return 0;
1226 },
1227 .application_data => {
1228 const n = try bw.write(limit.sliceConst(cleartext));
1229 if (n < cleartext.len) {
1230 const remainder = cleartext[n..];
1231 input.unread(remainder);
1232 c.remaining_cleartext_len = @intCast(remainder.len);
1233 }
1234 return n;
1235 },
1236 else => return failRead(c, error.TlsUnexpectedMessage),
1416 }1237 }
1417}1238}
14181239
1419fn discard(context: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {1240fn readVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize {
1420 _ = context;1241 var bw: std.io.BufferedWriter = undefined;
1421 _ = limit;1242 bw.initFixed(data[0]);
1422 @panic("TODO");1243 return read(context, &bw, .limited(data[0].len)) catch |err| switch (err) {
1244 error.WriteFailed => unreachable,
1245 else => |e| return e,
1246 };
1247}
1248
1249fn discard(context: ?*anyopaque, limit: Reader.Limit) Reader.Error!usize {
1250 var null_writer: Writer.Null = undefined;
1251 var bw = null_writer.writer().unbuffered();
1252 return read(context, &bw, limit) catch |err| switch (err) {
1253 error.WriteFailed => unreachable,
1254 else => |e| return e,
1255 };
1423}1256}
14241257
1425fn failRead(c: *Client, err: ReadError) error{ReadFailed} {1258fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
...@@ -1427,12 +1260,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} {...@@ -1427,12 +1260,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
1427 return error.ReadFailed;1260 return error.ReadFailed;
1428}1261}
14291262
1430fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) void {1263fn logSecrets(bw: *std.io.BufferedWriter, context: anytype, secrets: anytype) void {
1431 const locked = if (key_log_file.lock(.exclusive)) |_| true else |_| false;1264 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| bw.print("{s}" ++
1432 defer if (locked) key_log_file.unlock();
1433 key_log_file.seekFromEnd(0) catch {};
1434 var w = key_log_file.writer().unbuffered();
1435 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| w.print("{s}" ++
1436 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++1265 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++
1437 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{1266 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{
1438 context.client_random,1267 context.client_random,
...@@ -1440,59 +1269,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi...@@ -1440,59 +1269,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi
1440 }) catch {};1269 }) catch {};
1441}1270}
14421271
1443fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) std.io.Reader.Status {
1444 const saved_buf = frag[in..];
1445 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1446 // There is cleartext at the beginning already which we need to preserve.
1447 c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + saved_buf.len);
1448 @memcpy(c.partially_read_buffer[c.partial_ciphertext_idx..][0..saved_buf.len], saved_buf);
1449 } else {
1450 c.partial_cleartext_idx = 0;
1451 c.partial_ciphertext_idx = 0;
1452 c.partial_ciphertext_end = @intCast(saved_buf.len);
1453 @memcpy(c.partially_read_buffer[0..saved_buf.len], saved_buf);
1454 }
1455 return .{ .len = out, .end = c.eof() };
1456}
1457
1458/// Note that `first` usually overlaps with `c.partially_read_buffer`.
1459fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) std.io.Reader.Status {
1460 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1461 // There is cleartext at the beginning already which we need to preserve.
1462 c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + first.len + frag1.len);
1463 // TODO: eliminate this call to copyForwards
1464 std.mem.copyForwards(u8, c.partially_read_buffer[c.partial_ciphertext_idx..][0..first.len], first);
1465 @memcpy(c.partially_read_buffer[c.partial_ciphertext_idx + first.len ..][0..frag1.len], frag1);
1466 } else {
1467 c.partial_cleartext_idx = 0;
1468 c.partial_ciphertext_idx = 0;
1469 c.partial_ciphertext_end = @intCast(first.len + frag1.len);
1470 // TODO: eliminate this call to copyForwards
1471 std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first);
1472 @memcpy(c.partially_read_buffer[first.len..][0..frag1.len], frag1);
1473 }
1474 return .{ .len = out, .end = c.eof() };
1475}
1476
1477fn limitedOverlapCopy(frag: []u8, in: usize) void {
1478 const first = frag[in..];
1479 if (first.len <= in) {
1480 // A single, non-overlapping memcpy suffices.
1481 @memcpy(frag[0..first.len], first);
1482 } else {
1483 // One memcpy call would overlap, so just do this instead.
1484 std.mem.copyForwards(u8, frag, first);
1485 }
1486}
1487
1488fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 {
1489 if (index < s1.len) {
1490 return s1[index];
1491 } else {
1492 return s2[index - s1.len];
1493 }
1494}
1495
1496inline fn big(x: anytype) @TypeOf(x) {1272inline fn big(x: anytype) @TypeOf(x) {
1497 return switch (native_endian) {1273 return switch (native_endian) {
1498 .big => x,1274 .big => x,
...@@ -1753,81 +1529,6 @@ const CertificatePublicKey = struct {...@@ -1753,81 +1529,6 @@ const CertificatePublicKey = struct {
1753 }1529 }
1754};1530};
17551531
1756/// Abstraction for sending multiple byte buffers to a slice of iovecs.
1757const VecPut = struct {
1758 iovecs: []const std.posix.iovec,
1759 idx: usize = 0,
1760 off: usize = 0,
1761 total: usize = 0,
1762
1763 /// Returns the amount actually put which is always equal to bytes.len
1764 /// unless the vectors ran out of space.
1765 fn put(vp: *VecPut, bytes: []const u8) usize {
1766 if (vp.idx >= vp.iovecs.len) return 0;
1767 var bytes_i: usize = 0;
1768 while (true) {
1769 const v = vp.iovecs[vp.idx];
1770 const dest = v.base[vp.off..v.len];
1771 const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)];
1772 @memcpy(dest[0..src.len], src);
1773 bytes_i += src.len;
1774 vp.off += src.len;
1775 if (vp.off >= v.len) {
1776 vp.off = 0;
1777 vp.idx += 1;
1778 if (vp.idx >= vp.iovecs.len) {
1779 vp.total += bytes_i;
1780 return bytes_i;
1781 }
1782 }
1783 if (bytes_i >= bytes.len) {
1784 vp.total += bytes_i;
1785 return bytes_i;
1786 }
1787 }
1788 }
1789
1790 /// Returns the next buffer that consecutive bytes can go into.
1791 fn peek(vp: VecPut) []u8 {
1792 if (vp.idx >= vp.iovecs.len) return &.{};
1793 const v = vp.iovecs[vp.idx];
1794 return v.base[vp.off..v.len];
1795 }
1796
1797 // After writing to the result of peek(), one can call next() to
1798 // advance the cursor.
1799 fn next(vp: *VecPut, len: usize) void {
1800 vp.total += len;
1801 vp.off += len;
1802 if (vp.off >= vp.iovecs[vp.idx].len) {
1803 vp.off = 0;
1804 vp.idx += 1;
1805 }
1806 }
1807
1808 fn freeSize(vp: VecPut) usize {
1809 if (vp.idx >= vp.iovecs.len) return 0;
1810 var total: usize = 0;
1811 total += vp.iovecs[vp.idx].len - vp.off;
1812 if (vp.idx + 1 >= vp.iovecs.len) return total;
1813 for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.len;
1814 return total;
1815 }
1816};
1817
1818/// Limit iovecs to a specific byte size.
1819fn limitVecs(iovecs: []std.posix.iovec, len: usize) []std.posix.iovec {
1820 var bytes_left: usize = len;
1821 for (iovecs, 0..) |*iovec, vec_i| {
1822 if (bytes_left <= iovec.len) {
1823 iovec.len = bytes_left;
1824 return iovecs[0 .. vec_i + 1];
1825 }
1826 bytes_left -= iovec.len;
1827 }
1828 return iovecs;
1829}
1830
1831/// The priority order here is chosen based on what crypto algorithms Zig has1532/// The priority order here is chosen based on what crypto algorithms Zig has
1832/// available in the standard library as well as what is faster. Following are1533/// available in the standard library as well as what is faster. Following are
1833/// a few data points on the relative performance of these algorithms.1534/// a few data points on the relative performance of these algorithms.
lib/std/http.zig+4-10
...@@ -487,9 +487,7 @@ pub const Reader = struct {...@@ -487,9 +487,7 @@ pub const Reader = struct {
487 return decompressor.compression.gzip.reader();487 return decompressor.compression.gzip.reader();
488 },488 },
489 .zstd => {489 .zstd => {
490 decompressor.compression = .{ .zstd = .init(reader.in, .{490 decompressor.compression = .{ .zstd = .init(reader.in, .{ .verify_checksum = false }) };
491 .window_buffer = decompression_buffer,
492 }) };
493 return decompressor.compression.zstd.reader();491 return decompressor.compression.zstd.reader();
494 },492 },
495 .compress => unreachable,493 .compress => unreachable,
...@@ -742,7 +740,7 @@ pub const Decompressor = struct {...@@ -742,7 +740,7 @@ pub const Decompressor = struct {
742 pub const Compression = union(enum) {740 pub const Compression = union(enum) {
743 deflate: std.compress.zlib.Decompressor,741 deflate: std.compress.zlib.Decompressor,
744 gzip: std.compress.gzip.Decompressor,742 gzip: std.compress.gzip.Decompressor,
745 zstd: std.compress.zstd.Decompressor,743 zstd: std.compress.zstd.Decompress,
746 none: void,744 none: void,
747 };745 };
748746
...@@ -768,12 +766,8 @@ pub const Decompressor = struct {...@@ -768,12 +766,8 @@ pub const Decompressor = struct {
768 return decompressor.compression.gzip.reader();766 return decompressor.compression.gzip.reader();
769 },767 },
770 .zstd => {768 .zstd => {
771 const first_half = buffer[0 .. buffer.len / 2];769 decompressor.buffered_reader = transfer_reader.buffered(buffer);
772 const second_half = buffer[buffer.len / 2 ..];770 decompressor.compression = .{ .zstd = .init(&decompressor.buffered_reader, .{}) };
773 decompressor.buffered_reader = transfer_reader.buffered(first_half);
774 decompressor.compression = .{ .zstd = .init(&decompressor.buffered_reader, .{
775 .window_buffer = second_half,
776 }) };
777 return decompressor.compression.gzip.reader();771 return decompressor.compression.gzip.reader();
778 },772 },
779 .compress => unreachable,773 .compress => unreachable,
lib/std/http/Client.zig+10-24
...@@ -28,7 +28,7 @@ tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.cr...@@ -28,7 +28,7 @@ tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.cr
28/// If non-null, ssl secrets are logged to a stream. Creating such a stream28/// If non-null, ssl secrets are logged to a stream. Creating such a stream
29/// allows other processes with access to that stream to decrypt all29/// allows other processes with access to that stream to decrypt all
30/// traffic over connections created with this `Client`.30/// traffic over connections created with this `Client`.
31ssl_key_log: ?*std.io.BufferedWriter = null,31ssl_key_log: ?*std.crypto.tls.Client.SslKeyLog = null,
3232
33/// When this is `true`, the next time this client performs an HTTPS request,33/// When this is `true`, the next time this client performs an HTTPS request,
34/// it will first rescan the system for root certificates.34/// it will first rescan the system for root certificates.
...@@ -230,8 +230,11 @@ pub const Connection = struct {...@@ -230,8 +230,11 @@ pub const Connection = struct {
230 stream_writer: net.Stream.Writer,230 stream_writer: net.Stream.Writer,
231 stream_reader: net.Stream.Reader,231 stream_reader: net.Stream.Reader,
232 /// HTTP protocol from client to server.232 /// HTTP protocol from client to server.
233 /// This either goes directly to `stream`, or to a TLS client.233 /// This either goes directly to `stream_writer`, or to a TLS client.
234 writer: std.io.BufferedWriter,234 writer: std.io.BufferedWriter,
235 /// HTTP protocol from server to client.
236 /// This either comes directly from `stream_reader`, or from a TLS client.
237 reader: std.io.BufferedReader,
235 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.238 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
236 pool_node: std.DoublyLinkedList.Node,239 pool_node: std.DoublyLinkedList.Node,
237 port: u16,240 port: u16,
...@@ -241,8 +244,6 @@ pub const Connection = struct {...@@ -241,8 +244,6 @@ pub const Connection = struct {
241 protocol: Protocol,244 protocol: Protocol,
242245
243 const Plain = struct {246 const Plain = struct {
244 /// Data from `Connection.stream`.
245 reader: std.io.BufferedReader,
246 connection: Connection,247 connection: Connection,
247248
248 fn create(249 fn create(
...@@ -267,6 +268,7 @@ pub const Connection = struct {...@@ -267,6 +268,7 @@ pub const Connection = struct {
267 .stream_writer = stream.writer(),268 .stream_writer = stream.writer(),
268 .stream_reader = stream.reader(),269 .stream_reader = stream.reader(),
269 .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer),270 .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer),
271 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
270 .pool_node = .{},272 .pool_node = .{},
271 .port = port,273 .port = port,
272 .host_len = @intCast(remote_host.len),274 .host_len = @intCast(remote_host.len),
...@@ -274,7 +276,6 @@ pub const Connection = struct {...@@ -274,7 +276,6 @@ pub const Connection = struct {
274 .closing = false,276 .closing = false,
275 .protocol = .plain,277 .protocol = .plain,
276 },278 },
277 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
278 };279 };
279 return plain;280 return plain;
280 }281 }
...@@ -327,6 +328,7 @@ pub const Connection = struct {...@@ -327,6 +328,7 @@ pub const Connection = struct {
327 .stream_writer = stream.writer(),328 .stream_writer = stream.writer(),
328 .stream_reader = stream.reader(),329 .stream_reader = stream.reader(),
329 .writer = tls.client.writer().buffered(socket_write_buffer),330 .writer = tls.client.writer().buffered(socket_write_buffer),
331 .reader = tls.client.reader().unbuffered(),
330 .pool_node = .{},332 .pool_node = .{},
331 .port = port,333 .port = port,
332 .host_len = @intCast(remote_host.len),334 .host_len = @intCast(remote_host.len),
...@@ -386,21 +388,6 @@ pub const Connection = struct {...@@ -386,21 +388,6 @@ pub const Connection = struct {
386 };388 };
387 }389 }
388390
389 /// This is either data from `stream`, or `Tls.client`.
390 fn reader(c: *Connection) *std.io.BufferedReader {
391 return switch (c.protocol) {
392 .tls => {
393 if (disable_tls) unreachable;
394 const tls: *Tls = @fieldParentPtr("connection", c);
395 return &tls.client.reader;
396 },
397 .plain => {
398 const plain: *Plain = @fieldParentPtr("connection", c);
399 return &plain.reader;
400 },
401 };
402 }
403
404 /// If this is called without calling `flush` or `end`, data will be391 /// If this is called without calling `flush` or `end`, data will be
405 /// dropped unsent.392 /// dropped unsent.
406 pub fn destroy(c: *Connection) void {393 pub fn destroy(c: *Connection) void {
...@@ -1556,7 +1543,7 @@ pub fn request(...@@ -1556,7 +1543,7 @@ pub fn request(
1556 .client = client,1543 .client = client,
1557 .connection = connection,1544 .connection = connection,
1558 .reader = .{1545 .reader = .{
1559 .in = connection.reader(),1546 .in = &connection.reader,
1560 .state = .ready,1547 .state = .ready,
1561 .body_state = undefined,1548 .body_state = undefined,
1562 },1549 },
...@@ -1670,8 +1657,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1670,8 +1657,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
16701657
1671 const decompress_buffer: []u8 = switch (response.head.content_encoding) {1658 const decompress_buffer: []u8 = switch (response.head.content_encoding) {
1672 .identity => &.{},1659 .identity => &.{},
1673 .zstd => options.decompress_buffer orelse1660 .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len),
1674 try client.allocator.alloc(u8, std.compress.zstd.default_window_len * 2),
1675 else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024),1661 else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024),
1676 };1662 };
1677 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);1663 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);
...@@ -1681,7 +1667,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1681,7 +1667,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
1681 const list = storage.list;1667 const list = storage.list;
16821668
1683 if (storage.allocator) |allocator| {1669 if (storage.allocator) |allocator| {
1684 reader.readRemainingArrayList(allocator, null, list, storage.append_limit) catch |err| switch (err) {1670 reader.readRemainingArrayList(allocator, null, list, storage.append_limit, 128) catch |err| switch (err) {
1685 error.ReadFailed => return response.bodyErr().?,1671 error.ReadFailed => return response.bodyErr().?,
1686 else => |e| return e,1672 else => |e| return e,
1687 };1673 };
lib/std/io/BufferedReader.zig+27-12
...@@ -252,6 +252,12 @@ pub fn toss(br: *BufferedReader, n: usize) void {...@@ -252,6 +252,12 @@ pub fn toss(br: *BufferedReader, n: usize) void {
252 assert(br.seek <= br.end);252 assert(br.seek <= br.end);
253}253}
254254
255pub fn unread(noalias br: *BufferedReader, noalias data: []const u8) void {
256 _ = br;
257 _ = data;
258 @panic("TODO");
259}
260
255/// Equivalent to `peek` followed by `toss`.261/// Equivalent to `peek` followed by `toss`.
256///262///
257/// The data returned is invalidated by the next call to `take`, `peek`,263/// The data returned is invalidated by the next call to `take`, `peek`,
...@@ -736,24 +742,25 @@ pub fn discardDelimiterExclusive(br: *BufferedReader, delimiter: u8) Reader.Shor...@@ -736,24 +742,25 @@ pub fn discardDelimiterExclusive(br: *BufferedReader, delimiter: u8) Reader.Shor
736/// Asserts buffer capacity is at least `n`.742/// Asserts buffer capacity is at least `n`.
737pub fn fill(br: *BufferedReader, n: usize) Reader.Error!void {743pub fn fill(br: *BufferedReader, n: usize) Reader.Error!void {
738 assert(n <= br.buffer.len);744 assert(n <= br.buffer.len);
739 const buffer = br.buffer[0..br.end];745 if (br.seek + n <= br.end) {
740 const seek = br.seek;
741 if (seek + n <= buffer.len) {
742 @branchHint(.likely);746 @branchHint(.likely);
743 return;747 return;
744 }748 }
745 if (seek > 0) {749 rebaseCapacity(br, n);
746 const remainder = buffer[seek..];750 while (br.end < br.seek + n) {
747 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
748 br.end = remainder.len;
749 br.seek = 0;
750 }
751 while (true) {
752 br.end += try br.unbuffered_reader.readVec(&.{br.buffer[br.end..]});751 br.end += try br.unbuffered_reader.readVec(&.{br.buffer[br.end..]});
753 if (n <= br.end) return;
754 }752 }
755}753}
756754
755/// Fills the buffer with at least one more byte of data, without advancing the
756/// seek position, doing exactly one underlying read.
757///
758/// Asserts buffer capacity is at least 1.
759pub fn fillMore(br: *BufferedReader) Reader.Error!void {
760 rebaseCapacity(br, 1);
761 br.end += try br.unbuffered_reader.readVec(&.{br.buffer[br.end..]});
762}
763
757/// Returns the next byte from the stream or returns `error.EndOfStream`.764/// Returns the next byte from the stream or returns `error.EndOfStream`.
758///765///
759/// Does not advance the seek position.766/// Does not advance the seek position.
...@@ -783,7 +790,7 @@ pub fn takeByteSigned(br: *BufferedReader) Reader.Error!i8 {...@@ -783,7 +790,7 @@ pub fn takeByteSigned(br: *BufferedReader) Reader.Error!i8 {
783 return @bitCast(try br.takeByte());790 return @bitCast(try br.takeByte());
784}791}
785792
786/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.793/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
787pub inline fn takeInt(br: *BufferedReader, comptime T: type, endian: std.builtin.Endian) Reader.Error!T {794pub inline fn takeInt(br: *BufferedReader, comptime T: type, endian: std.builtin.Endian) Reader.Error!T {
788 const n = @divExact(@typeInfo(T).int.bits, 8);795 const n = @divExact(@typeInfo(T).int.bits, 8);
789 return std.mem.readInt(T, try br.takeArray(n), endian);796 return std.mem.readInt(T, try br.takeArray(n), endian);
...@@ -957,6 +964,14 @@ pub fn rebase(br: *BufferedReader) void {...@@ -957,6 +964,14 @@ pub fn rebase(br: *BufferedReader) void {
957 br.end = data.len;964 br.end = data.len;
958}965}
959966
967/// Ensures `capacity` more data can be buffered without rebasing, by rebasing
968/// if necessary.
969///
970/// Asserts `capacity` is within the buffer capacity.
971pub fn rebaseCapacity(br: *BufferedReader, capacity: usize) void {
972 if (br.end > br.buffer.len - capacity) rebase(br);
973}
974
960/// Advances the stream and decreases the size of the storage buffer by `n`,975/// Advances the stream and decreases the size of the storage buffer by `n`,
961/// returning the range of bytes no longer accessible by `br`.976/// returning the range of bytes no longer accessible by `br`.
962///977///
lib/std/io/Reader.zig+4
...@@ -100,6 +100,10 @@ pub const Limit = enum(usize) {...@@ -100,6 +100,10 @@ pub const Limit = enum(usize) {
100 return s[0..l.minInt(s.len)];100 return s[0..l.minInt(s.len)];
101 }101 }
102102
103 pub fn sliceConst(l: Limit, s: []const u8) []const u8 {
104 return s[0..l.minInt(s.len)];
105 }
106
103 pub fn toInt(l: Limit) ?usize {107 pub fn toInt(l: Limit) ?usize {
104 return switch (l) {108 return switch (l) {
105 else => @intFromEnum(l),109 else => @intFromEnum(l),
lib/std/io/Writer.zig+2-2
...@@ -140,7 +140,7 @@ pub fn failingWriteFile(...@@ -140,7 +140,7 @@ pub fn failingWriteFile(
140 limit: std.io.Writer.Limit,140 limit: std.io.Writer.Limit,
141 headers_and_trailers: []const []const u8,141 headers_and_trailers: []const []const u8,
142 headers_len: usize,142 headers_len: usize,
143) Error!usize {143) FileError!usize {
144 _ = context;144 _ = context;
145 _ = file;145 _ = file;
146 _ = offset;146 _ = offset;
...@@ -165,7 +165,7 @@ pub fn unimplementedWriteFile(...@@ -165,7 +165,7 @@ pub fn unimplementedWriteFile(
165 limit: std.io.Writer.Limit,165 limit: std.io.Writer.Limit,
166 headers_and_trailers: []const []const u8,166 headers_and_trailers: []const []const u8,
167 headers_len: usize,167 headers_len: usize,
168) Error!usize {168) FileError!usize {
169 _ = context;169 _ = context;
170 _ = file;170 _ = file;
171 _ = offset;171 _ = offset;