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 {
168168 has_top_bit = true;
169169 }
170170 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;
172172 if (@intFromBool(has_top_bit) != out[0] >> 7) return error.InvalidEncoding;
173173 }
174174
......@@ -177,7 +177,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
177177 pub fn fromDer(der: []const u8) EncodingError!Signature {
178178 if (der.len < 2) return error.InvalidEncoding;
179179 var br: std.io.BufferedReader = undefined;
180 br.initFixed(der);
180 br.initFixed(@constCast(der));
181181 const buf = br.take(2) catch return error.InvalidEncoding;
182182 if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) return error.InvalidEncoding;
183183 var sig: Signature = mem.zeroInit(Signature, .{});
lib/std/crypto/tls.zig+7-5
......@@ -655,7 +655,7 @@ pub const Decoder = struct {
655655 }
656656
657657 /// 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 {
659659 assert(!d.disable_reads);
660660 const existing_amt = d.cap - d.idx;
661661 d.their_end = d.idx + their_amt;
......@@ -663,14 +663,16 @@ pub const Decoder = struct {
663663 const request_amt = their_amt - existing_amt;
664664 const dest = d.buf[d.cap..];
665665 if (request_amt > dest.len) return error.TlsRecordOverflow;
666 const actual_amt = try stream.readAtLeast(dest, request_amt);
667 if (actual_amt < request_amt) return error.TlsConnectionTruncated;
668 d.cap += actual_amt;
666 stream.readSlice(dest[0..request_amt]) catch |err| switch (err) {
667 error.EndOfStream => return error.TlsConnectionTruncated,
668 error.ReadFailed => return error.ReadFailed,
669 };
670 d.cap += request_amt;
669671 }
670672
671673 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.
672674 /// 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 {
674676 assert(!d.disable_reads);
675677 try readAtLeast(d, stream, our_amt);
676678 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();
44const std = @import("../../std.zig");
55const tls = std.crypto.tls;
66const Client = @This();
7const net = std.net;
87const mem = std.mem;
98const crypto = std.crypto;
109const assert = std.debug.assert;
1110const Certificate = std.crypto.Certificate;
11const Reader = std.io.Reader;
12const Writer = std.io.Writer;
1213
1314const max_ciphertext_len = tls.max_ciphertext_len;
1415const hmacExpandLabel = tls.hmacExpandLabel;
......@@ -21,38 +22,22 @@ const array = tls.array;
2122///
2223/// The buffer is asserted to have capacity at least `min_buffer_len`.
2324///
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.
25/// `remaining_cleartext_len` tells how many bytes inside this buffer have
26/// already been decrypted.
3227input: *std.io.BufferedReader,
28/// Tells how many bytes inside `input` have already been decrypted.
29remaining_cleartext_len: u15,
30
3331/// The encrypted stream from the client to the server. Bytes are pushed here
3432/// via `writer`.
35///
36/// The buffer is asserted to have capacity at least `min_buffer_len`.
3733output: *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,
4638tls_version: tls.ProtocolVersion,
4739read_seq: u64,
4840write_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,
5641/// When this is true, the stream may still not be at the end because there
5742/// may be data in the input buffer.
5843received_close_notify: bool,
......@@ -60,11 +45,13 @@ received_close_notify: bool,
6045/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
6146/// message has been received. By setting this flag to `true`, instead, the
6247/// end-of-stream will be forwarded to the application layer above TLS.
48///
6349/// This makes the application vulnerable to truncation attacks unless the
6450/// application layer itself verifies that the amount of data received equals
6551/// the amount of data expected, such as HTTP with the Content-Length header.
6652allow_truncation_attacks: bool,
6753application_cipher: tls.ApplicationCipher,
54
6855/// If non-null, ssl secrets are logged to a stream. Creating such a log file
6956/// allows other programs with access to that file to decrypt all traffic over
7057/// this connection.
......@@ -80,6 +67,7 @@ pub const ReadError = error{
8067 TlsRecordOverflow,
8168 TlsUnexpectedMessage,
8269 TlsIllegalParameter,
70 TlsSequenceOverflow,
8371};
8472
8573pub const SslKeyLog = struct {
......@@ -99,8 +87,8 @@ pub const SslKeyLog = struct {
9987 }
10088};
10189
102/// The `std.io.BufferedReader` and `std.io.BufferedWriter` supplied to `init`
103/// each require a buffer capacity at least this amount.
90/// The `std.io.BufferedReader` supplied to `init` requires a buffer capacity
91/// at least this amount.
10492pub const min_buffer_len = tls.max_ciphertext_record_len;
10593
10694pub const Options = struct {
......@@ -126,7 +114,10 @@ pub const Options = struct {
126114 },
127115 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
128116 /// 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,
130121};
131122
132123const InitError = error{
......@@ -183,16 +174,14 @@ const InitError = error{
183174///
184175/// `host` is only borrowed during this function call.
185176///
186/// Both `input` and `output` are asserted to have buffer capacity at least
187/// `min_buffer_len`.
177/// `input` is asserted to have buffer capacity at least `min_buffer_len`.
188178pub fn init(
189179 client: *Client,
190180 input: *std.io.BufferedReader,
191181 output: *std.io.BufferedWriter,
192182 options: Options,
193183) InitError!void {
194 assert(input.storage.buffer.len >= min_buffer_len);
195 assert(output.buffer.len >= min_buffer_len);
184 assert(input.buffer.len >= min_buffer_len);
196185 client.alert = null;
197186 const host = switch (options.host) {
198187 .no_verification => "",
......@@ -286,7 +275,7 @@ pub fn init(
286275
287276 {
288277 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]);
290279 }
291280
292281 var tls_version: tls.ProtocolVersion = undefined;
......@@ -335,20 +324,26 @@ pub fn init(
335324 var cleartext_fragment_start: usize = 0;
336325 var cleartext_fragment_end: usize = 0;
337326 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 };
340327 fragment: while (true) {
341 try d.readAtLeastOurAmt(input, tls.record_header_len);
342 const record_header = d.buf[d.idx..][0..tls.record_header_len];
343 const record_ct = d.decode(tls.ContentType);
344 d.skip(2); // legacy_version
345 const record_len = d.decode(u16);
346 try d.readAtLeast(input, record_len);
347 var record_decoder = try d.sub(record_len);
328 // Ensure the input buffer pointer is stable in this scope.
329 input.rebaseCapacity(tls.max_ciphertext_record_len);
330 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
331 error.EndOfStream => return error.TlsConnectionTruncated,
332 error.ReadFailed => return error.ReadFailed,
333 };
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);
348343 var ctd, const ct = content: switch (cipher_state) {
349344 .cleartext => .{ record_decoder, record_ct },
350345 .handshake => {
351 std.debug.assert(tls_version == .tls_1_3);
346 assert(tls_version == .tls_1_3);
352347 if (record_ct != .application_data) return error.TlsUnexpectedMessage;
353348 try record_decoder.ensure(record_len);
354349 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
......@@ -380,7 +375,7 @@ pub fn init(
380375 break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct };
381376 },
382377 .application => {
383 std.debug.assert(tls_version == .tls_1_2);
378 assert(tls_version == .tls_1_2);
384379 if (record_ct != .handshake) return error.TlsUnexpectedMessage;
385380 try record_decoder.ensure(record_len);
386381 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
......@@ -536,7 +531,7 @@ pub fn init(
536531 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
537532 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
538533 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, .{
540535 .client_random = &client_hello_rand,
541536 }, .{
542537 .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret,
......@@ -710,7 +705,7 @@ pub fn init(
710705 &client_hello_rand,
711706 &server_hello_rand,
712707 }, 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, .{
714709 .client_random = &client_hello_rand,
715710 }, .{
716711 .CLIENT_RANDOM = &master_secret,
......@@ -763,7 +758,7 @@ pub fn init(
763758 &client_change_cipher_spec_msg,
764759 &client_verify_msg,
765760 };
766 try output.writevAll(&all_msgs_vec);
761 try output.writeVecAll(&all_msgs_vec);
767762 },
768763 }
769764 write_seq += 1;
......@@ -828,11 +823,11 @@ pub fn init(
828823 &client_change_cipher_spec_msg,
829824 &finished_msg,
830825 };
831 try output.writevAll(&all_msgs_vec);
826 try output.writeVecAll(&all_msgs_vec);
832827
833828 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
834829 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, .{
836831 .counter = key_seq,
837832 .client_random = &client_hello_rand,
838833 }, .{
......@@ -859,11 +854,9 @@ pub fn init(
859854 else => unreachable,
860855 },
861856 };
862 const leftover = d.rest();
863857 client.* = .{
864858 .input = input,
865859 .output = output,
866 .reader = undefined,
867860 .tls_version = tls_version,
868861 .read_seq = switch (tls_version) {
869862 .tls_1_3 => 0,
......@@ -875,29 +868,18 @@ pub fn init(
875868 .tls_1_2 => write_seq,
876869 else => unreachable,
877870 },
878 .partial_cleartext_idx = 0,
879 .partial_ciphertext_idx = 0,
880 .partial_ciphertext_end = @intCast(leftover.len),
871 .remaining_cleartext_len = 0,
881872 .received_close_notify = false,
882873 .allow_truncation_attacks = false,
883874 .application_cipher = app_cipher,
884 .partially_read_buffer = undefined,
885 .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{
886 .client_key_seq = key_seq,
887 .server_key_seq = key_seq,
888 .client_random = client_hello_rand,
889 .file = key_log_file,
890 } else null,
875 .ssl_key_log = options.ssl_key_log,
876 };
877 if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{
878 .client_key_seq = key_seq,
879 .server_key_seq = key_seq,
880 .client_random = client_hello_rand,
881 .writer = ssl_key_log.writer,
891882 };
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]);
901883 return;
902884 },
903885 else => return error.TlsUnexpectedMessage,
......@@ -912,17 +894,28 @@ pub fn init(
912894 }
913895}
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 {
916909 return .{
917910 .context = c,
918911 .vtable = &.{
919912 .writeSplat = writeSplat,
920 .writeFile = std.io.Writer.unimplementedWriteFile,
913 .writeFile = Writer.unimplementedWriteFile,
921914 },
922915 };
923916}
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 {
926919 const c: *Client = @alignCast(@ptrCast(context));
927920 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
928921 const output = c.output;
......@@ -942,7 +935,7 @@ fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) std.i
942935/// Sends a `close_notify` alert, which is necessary for the server to
943936/// distinguish between a properly finished TLS session, or a truncation
944937/// attack.
945pub fn end(c: *Client) std.io.Writer.Error!void {
938pub fn end(c: *Client) Writer.Error!void {
946939 const output = c.output;
947940 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
948941 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
......@@ -1054,372 +1047,212 @@ fn prepareCiphertextRecord(
10541047}
10551048
10561049pub fn eof(c: Client) bool {
1057 return c.received_close_notify and
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;
1050 return c.received_close_notify and c.remaining_cleartext_len == 0;
10711051}
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 {
10741054 const c: *Client = @ptrCast(@alignCast(context));
10751055 if (c.eof()) return error.EndOfStream;
1076
1077 var vp: VecPut = .{ .iovecs = data };
1078
1079 // Give away the buffered cleartext we have, if any.
1080 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];
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 }
1056 const input = c.input;
1057 if (c.remaining_cleartext_len > 0) {
1058 const n = try bw.write(input.bufferContents()[0..c.remaining_cleartext_len]);
1059 c.remaining_cleartext_len = @intCast(c.remaining_cleartext_len - n);
1060 return n;
11031061 }
1104
1105 assert(!c.received_close_notify);
1106
1107 // Ideally, this buffer would never be used. It is needed when `iovecs` are
1108 // too small to fit the cleartext, which may be as large as `max_ciphertext_len`.
1109 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;
1110 // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`.
1111 var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined;
1112 // How many bytes left in the user's buffer.
1113 const free_size = vp.freeSize();
1114 // The amount of the user's buffer that we need to repurpose for storing
1115 // ciphertext. The end of the buffer will be used for such purposes.
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,
1062 // If at least one full encrypted record is not buffered, read once.
1063 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
1064 error.EndOfStream => {
1065 // This is either a truncation attack, a bug in the server, or an
1066 // intentional omission of the close_notify message due to truncation
1067 // detection handled above the TLS layer.
1068 if (c.allow_truncation_attacks) {
1069 c.received_close_notify = true;
1070 return error.EndOfStream;
1071 } else {
1072 return failRead(c, error.TlsConnectionTruncated);
1073 }
11371074 },
1075 error.ReadFailed => return error.ReadFailed,
11381076 };
1139
1140 // Cleartext capacity of output buffer, in records. Minimum one full record.
1141 const buf_cap = @max(cleartext_buf_len / max_ciphertext_len, 1);
1142 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);
1143 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;
1144 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);
1145 const actual_read_len = try c.input.readv(ask_iovecs);
1146 if (actual_read_len == 0) {
1147 // This is either a truncation attack, a bug in the server, or an
1148 // intentional omission of the close_notify message due to truncation
1149 // detection handled above the TLS layer.
1150 if (c.allow_truncation_attacks) {
1151 c.received_close_notify = true;
1152 } else {
1153 return failRead(c, error.TlsConnectionTruncated);
1154 }
1077 const ct: tls.ContentType = @enumFromInt(record_header[0]);
1078 const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big);
1079 _ = legacy_version;
1080 const record_len = mem.readInt(u16, record_header[3..][0..2], .big);
1081 if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
1082 const record_end = 5 + record_len;
1083 if (record_end > input.bufferContents().len) {
1084 input.fillMore() catch |err| switch (err) {
1085 error.EndOfStream => return failRead(c, error.TlsConnectionTruncated),
1086 error.ReadFailed => return error.ReadFailed,
1087 };
1088 if (record_end > input.bufferContents().len) return 0;
11551089 }
11561090
1157 // There might be more bytes inside `in_stack_buffer` that need to be processed,
1158 // but at least frag0 will have one complete ciphertext record.
1159 const frag0_end = @min(c.partially_read_buffer.len, c.partial_ciphertext_end + actual_read_len);
1160 const frag0 = c.partially_read_buffer[c.partial_ciphertext_idx..frag0_end];
1161 var frag1 = in_stack_buffer[0..actual_read_len -| first_iov.len];
1162 // We need to decipher frag0 and frag1 but there may be a ciphertext record
1163 // straddling the boundary. We can handle this with two memcpy() calls to
1164 // assemble the straddling record in between handling the two sides.
1165 var frag = frag0;
1166 var in: usize = 0;
1167 while (true) {
1168 if (in == frag.len) {
1169 // Perfect split.
1170 if (frag.ptr == frag1.ptr) {
1171 c.partial_ciphertext_end = c.partial_ciphertext_idx;
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]),
1091 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;
1092 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1093 inline else => |*p| switch (c.tls_version) {
1094 .tls_1_3 => {
1095 const pv = &p.tls_1_3;
1096 const P = @TypeOf(p.*);
1097 const ad = input.take(tls.record_header_len) catch unreachable; // already peeked
1098 const ciphertext_len = record_len - P.AEAD.tag_length;
1099 const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked
1100 const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked
1101 const nonce = nonce: {
1102 const V = @Vector(P.AEAD.nonce_length, u8);
1103 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1104 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));
1105 break :nonce @as(V, pv.server_iv) ^ operand;
13041106 };
1305 switch (alert.description) {
1306 .close_notify => {
1307 c.received_close_notify = true;
1308 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1309 return vp.total;
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 }
1107 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1108 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1109 return failRead(c, error.TlsBadRecordMac);
1110 const msg = mem.trimRight(u8, cleartext, "\x00");
1111 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };
13201112 },
1321 .handshake => {
1322 var ct_i: usize = 0;
1323 while (true) {
1324 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);
1325 ct_i += 1;
1326 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);
1327 ct_i += 3;
1328 const next_handshake_i = ct_i + handshake_len;
1329 if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength);
1330 const handshake = cleartext[ct_i..next_handshake_i];
1331 switch (handshake_type) {
1332 .new_session_ticket => {
1333 // This client implementation ignores new session tickets.
1334 },
1335 .key_update => {
1336 switch (c.application_cipher) {
1337 inline else => |*p| {
1338 const pv = &p.tls_1_3;
1339 const P = @TypeOf(p.*);
1340 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);
1341 if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{
1342 .counter = key_log.serverCounter(),
1343 .client_random = &key_log.client_random,
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 }
1113 .tls_1_2 => {
1114 const pv = &p.tls_1_2;
1115 const P = @TypeOf(p.*);
1116 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1117 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
1118 const ad = std.mem.toBytes(big(c.read_seq)) ++
1119 ad_header[0 .. 1 + 2] ++
1120 std.mem.toBytes(big(message_len));
1121 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked
1122 const masked_read_seq = c.read_seq &
1123 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
1124 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
1125 const V = @Vector(P.AEAD.nonce_length, u8);
1126 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1127 const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq)));
1128 break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand;
1129 };
1130 const ciphertext = input.take(message_len) catch unreachable; // already peeked
1131 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked
1132 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1133 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1134 return failRead(c, error.TlsBadRecordMac);
1135 break :cleartext .{ cleartext, ct };
13831136 },
1384 .application_data => {
1385 // Determine whether the output buffer or a stack
1386 // buffer was used for storing the cleartext.
1387 if (cleartext.ptr == &cleartext_stack_buffer) {
1388 // Stack buffer was used, so we must copy to the output buffer.
1389 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1390 // We have already run out of room in iovecs. Continue
1391 // appending to `partially_read_buffer`.
1392 @memcpy(
1393 c.partially_read_buffer[c.partial_ciphertext_idx..][0..cleartext.len],
1394 cleartext,
1395 );
1396 c.partial_ciphertext_idx = @intCast(c.partial_ciphertext_idx + cleartext.len);
1397 } else {
1398 const amt = vp.put(cleartext);
1399 if (amt < cleartext.len) {
1400 const rest = cleartext[amt..];
1401 c.partial_cleartext_idx = 0;
1402 c.partial_ciphertext_idx = @intCast(rest.len);
1403 @memcpy(c.partially_read_buffer[0..rest.len], rest);
1137 else => unreachable,
1138 },
1139 };
1140 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
1141 switch (inner_ct) {
1142 .alert => {
1143 if (cleartext.len != 2) return failRead(c, error.TlsDecodeError);
1144 const alert: tls.Alert = .{
1145 .level = @enumFromInt(cleartext[0]),
1146 .description = @enumFromInt(cleartext[1]),
1147 };
1148 switch (alert.description) {
1149 .close_notify => {
1150 c.received_close_notify = true;
1151 return 0;
1152 },
1153 .user_canceled => {
1154 // TODO: handle server-side closures
1155 return failRead(c, error.TlsUnexpectedMessage);
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 },
14041193 }
1405 }
1406 } else {
1407 // Output buffer was used directly which means no
1408 // memory copying needs to occur, and we can move
1409 // on to the next ciphertext record.
1410 vp.next(cleartext.len);
1194 c.read_seq = 0;
1195
1196 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
1197 .update_requested => {
1198 switch (c.application_cipher) {
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),
14111221 }
1412 },
1413 else => return failRead(c, error.TlsUnexpectedMessage),
1414 }
1415 in = end;
1222 ct_i = next_handshake_i;
1223 if (ct_i >= cleartext.len) break;
1224 }
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),
14161237 }
14171238}
14181239
1419fn discard(context: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {
1420 _ = context;
1421 _ = limit;
1422 @panic("TODO");
1240fn readVec(context: ?*anyopaque, data: []const []u8) Reader.Error!usize {
1241 var bw: std.io.BufferedWriter = undefined;
1242 bw.initFixed(data[0]);
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 };
14231256}
14241257
14251258fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
......@@ -1427,12 +1260,8 @@ fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
14271260 return error.ReadFailed;
14281261}
14291262
1430fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) void {
1431 const locked = if (key_log_file.lock(.exclusive)) |_| true else |_| false;
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}" ++
1263fn logSecrets(bw: *std.io.BufferedWriter, context: anytype, secrets: anytype) void {
1264 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| bw.print("{s}" ++
14361265 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++
14371266 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{
14381267 context.client_random,
......@@ -1440,59 +1269,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi
14401269 }) catch {};
14411270}
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
14961272inline fn big(x: anytype) @TypeOf(x) {
14971273 return switch (native_endian) {
14981274 .big => x,
......@@ -1753,81 +1529,6 @@ const CertificatePublicKey = struct {
17531529 }
17541530};
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
18311532/// The priority order here is chosen based on what crypto algorithms Zig has
18321533/// available in the standard library as well as what is faster. Following are
18331534/// 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 {
487487 return decompressor.compression.gzip.reader();
488488 },
489489 .zstd => {
490 decompressor.compression = .{ .zstd = .init(reader.in, .{
491 .window_buffer = decompression_buffer,
492 }) };
490 decompressor.compression = .{ .zstd = .init(reader.in, .{ .verify_checksum = false }) };
493491 return decompressor.compression.zstd.reader();
494492 },
495493 .compress => unreachable,
......@@ -742,7 +740,7 @@ pub const Decompressor = struct {
742740 pub const Compression = union(enum) {
743741 deflate: std.compress.zlib.Decompressor,
744742 gzip: std.compress.gzip.Decompressor,
745 zstd: std.compress.zstd.Decompressor,
743 zstd: std.compress.zstd.Decompress,
746744 none: void,
747745 };
748746
......@@ -768,12 +766,8 @@ pub const Decompressor = struct {
768766 return decompressor.compression.gzip.reader();
769767 },
770768 .zstd => {
771 const first_half = buffer[0 .. buffer.len / 2];
772 const second_half = buffer[buffer.len / 2 ..];
773 decompressor.buffered_reader = transfer_reader.buffered(first_half);
774 decompressor.compression = .{ .zstd = .init(&decompressor.buffered_reader, .{
775 .window_buffer = second_half,
776 }) };
769 decompressor.buffered_reader = transfer_reader.buffered(buffer);
770 decompressor.compression = .{ .zstd = .init(&decompressor.buffered_reader, .{}) };
777771 return decompressor.compression.gzip.reader();
778772 },
779773 .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
2828/// If non-null, ssl secrets are logged to a stream. Creating such a stream
2929/// allows other processes with access to that stream to decrypt all
3030/// traffic over connections created with this `Client`.
31ssl_key_log: ?*std.io.BufferedWriter = null,
31ssl_key_log: ?*std.crypto.tls.Client.SslKeyLog = null,
3232
3333/// When this is `true`, the next time this client performs an HTTPS request,
3434/// it will first rescan the system for root certificates.
......@@ -230,8 +230,11 @@ pub const Connection = struct {
230230 stream_writer: net.Stream.Writer,
231231 stream_reader: net.Stream.Reader,
232232 /// 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.
234234 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,
235238 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
236239 pool_node: std.DoublyLinkedList.Node,
237240 port: u16,
......@@ -241,8 +244,6 @@ pub const Connection = struct {
241244 protocol: Protocol,
242245
243246 const Plain = struct {
244 /// Data from `Connection.stream`.
245 reader: std.io.BufferedReader,
246247 connection: Connection,
247248
248249 fn create(
......@@ -267,6 +268,7 @@ pub const Connection = struct {
267268 .stream_writer = stream.writer(),
268269 .stream_reader = stream.reader(),
269270 .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer),
271 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
270272 .pool_node = .{},
271273 .port = port,
272274 .host_len = @intCast(remote_host.len),
......@@ -274,7 +276,6 @@ pub const Connection = struct {
274276 .closing = false,
275277 .protocol = .plain,
276278 },
277 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
278279 };
279280 return plain;
280281 }
......@@ -327,6 +328,7 @@ pub const Connection = struct {
327328 .stream_writer = stream.writer(),
328329 .stream_reader = stream.reader(),
329330 .writer = tls.client.writer().buffered(socket_write_buffer),
331 .reader = tls.client.reader().unbuffered(),
330332 .pool_node = .{},
331333 .port = port,
332334 .host_len = @intCast(remote_host.len),
......@@ -386,21 +388,6 @@ pub const Connection = struct {
386388 };
387389 }
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
404391 /// If this is called without calling `flush` or `end`, data will be
405392 /// dropped unsent.
406393 pub fn destroy(c: *Connection) void {
......@@ -1556,7 +1543,7 @@ pub fn request(
15561543 .client = client,
15571544 .connection = connection,
15581545 .reader = .{
1559 .in = connection.reader(),
1546 .in = &connection.reader,
15601547 .state = .ready,
15611548 .body_state = undefined,
15621549 },
......@@ -1670,8 +1657,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
16701657
16711658 const decompress_buffer: []u8 = switch (response.head.content_encoding) {
16721659 .identity => &.{},
1673 .zstd => options.decompress_buffer orelse
1674 try client.allocator.alloc(u8, std.compress.zstd.default_window_len * 2),
1660 .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len),
16751661 else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024),
16761662 };
16771663 defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer);
......@@ -1681,7 +1667,7 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
16811667 const list = storage.list;
16821668
16831669 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) {
16851671 error.ReadFailed => return response.bodyErr().?,
16861672 else => |e| return e,
16871673 };
lib/std/io/BufferedReader.zig+27-12
......@@ -252,6 +252,12 @@ pub fn toss(br: *BufferedReader, n: usize) void {
252252 assert(br.seek <= br.end);
253253}
254254
255pub fn unread(noalias br: *BufferedReader, noalias data: []const u8) void {
256 _ = br;
257 _ = data;
258 @panic("TODO");
259}
260
255261/// Equivalent to `peek` followed by `toss`.
256262///
257263/// 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
736742/// Asserts buffer capacity is at least `n`.
737743pub fn fill(br: *BufferedReader, n: usize) Reader.Error!void {
738744 assert(n <= br.buffer.len);
739 const buffer = br.buffer[0..br.end];
740 const seek = br.seek;
741 if (seek + n <= buffer.len) {
745 if (br.seek + n <= br.end) {
742746 @branchHint(.likely);
743747 return;
744748 }
745 if (seek > 0) {
746 const remainder = buffer[seek..];
747 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
748 br.end = remainder.len;
749 br.seek = 0;
750 }
751 while (true) {
749 rebaseCapacity(br, n);
750 while (br.end < br.seek + n) {
752751 br.end += try br.unbuffered_reader.readVec(&.{br.buffer[br.end..]});
753 if (n <= br.end) return;
754752 }
755753}
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
757764/// Returns the next byte from the stream or returns `error.EndOfStream`.
758765///
759766/// Does not advance the seek position.
......@@ -783,7 +790,7 @@ pub fn takeByteSigned(br: *BufferedReader) Reader.Error!i8 {
783790 return @bitCast(try br.takeByte());
784791}
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`.
787794pub inline fn takeInt(br: *BufferedReader, comptime T: type, endian: std.builtin.Endian) Reader.Error!T {
788795 const n = @divExact(@typeInfo(T).int.bits, 8);
789796 return std.mem.readInt(T, try br.takeArray(n), endian);
......@@ -957,6 +964,14 @@ pub fn rebase(br: *BufferedReader) void {
957964 br.end = data.len;
958965}
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
960975/// Advances the stream and decreases the size of the storage buffer by `n`,
961976/// returning the range of bytes no longer accessible by `br`.
962977///
lib/std/io/Reader.zig+4
......@@ -100,6 +100,10 @@ pub const Limit = enum(usize) {
100100 return s[0..l.minInt(s.len)];
101101 }
102102
103 pub fn sliceConst(l: Limit, s: []const u8) []const u8 {
104 return s[0..l.minInt(s.len)];
105 }
106
103107 pub fn toInt(l: Limit) ?usize {
104108 return switch (l) {
105109 else => @intFromEnum(l),
lib/std/io/Writer.zig+2-2
......@@ -140,7 +140,7 @@ pub fn failingWriteFile(
140140 limit: std.io.Writer.Limit,
141141 headers_and_trailers: []const []const u8,
142142 headers_len: usize,
143) Error!usize {
143) FileError!usize {
144144 _ = context;
145145 _ = file;
146146 _ = offset;
......@@ -165,7 +165,7 @@ pub fn unimplementedWriteFile(
165165 limit: std.io.Writer.Limit,
166166 headers_and_trailers: []const []const u8,
167167 headers_len: usize,
168) Error!usize {
168) FileError!usize {
169169 _ = context;
170170 _ = file;
171171 _ = offset;