authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-03 02:43:50-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-01-03 02:43:50-05:00
logc9ef277fa7e43f119a7f2896635b4fdf9c97edbe
treedd0fa2ef288eec970e973476a4f6809723a04bd0
parent8bd734d60cd55d65ea52a051ccdc35939edeb99c
parent7178451d6258d9d04fdf03269478948643c39f02
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #13980 from ziglang/std.net

networking: delete std.x; add std.crypto.tls and std.http.Client

38 files changed, 3917 insertions(+), 3707 deletions(-)

lib/std/Url.zig created+98
...@@ -0,0 +1,98 @@
1scheme: []const u8,
2host: []const u8,
3path: []const u8,
4port: ?u16,
5
6/// TODO: redo this implementation according to RFC 1738. This code is only a
7/// placeholder for now.
8pub fn parse(s: []const u8) !Url {
9 var scheme_end: usize = 0;
10 var host_start: usize = 0;
11 var host_end: usize = 0;
12 var path_start: usize = 0;
13 var port_start: usize = 0;
14 var port_end: usize = 0;
15 var state: enum {
16 scheme,
17 scheme_slash1,
18 scheme_slash2,
19 host,
20 port,
21 path,
22 } = .scheme;
23
24 for (s) |b, i| switch (state) {
25 .scheme => switch (b) {
26 ':' => {
27 state = .scheme_slash1;
28 scheme_end = i;
29 },
30 else => {},
31 },
32 .scheme_slash1 => switch (b) {
33 '/' => {
34 state = .scheme_slash2;
35 },
36 else => return error.InvalidUrl,
37 },
38 .scheme_slash2 => switch (b) {
39 '/' => {
40 state = .host;
41 host_start = i + 1;
42 },
43 else => return error.InvalidUrl,
44 },
45 .host => switch (b) {
46 ':' => {
47 state = .port;
48 host_end = i;
49 port_start = i + 1;
50 },
51 '/' => {
52 state = .path;
53 host_end = i;
54 path_start = i;
55 },
56 else => {},
57 },
58 .port => switch (b) {
59 '/' => {
60 port_end = i;
61 state = .path;
62 path_start = i;
63 },
64 else => {},
65 },
66 .path => {},
67 };
68
69 const port_slice = s[port_start..port_end];
70 const port = if (port_slice.len == 0) null else try std.fmt.parseInt(u16, port_slice, 10);
71
72 return .{
73 .scheme = s[0..scheme_end],
74 .host = s[host_start..host_end],
75 .path = s[path_start..],
76 .port = port,
77 };
78}
79
80const Url = @This();
81const std = @import("std.zig");
82const testing = std.testing;
83
84test "basic" {
85 const parsed = try parse("https://ziglang.org/download");
86 try testing.expectEqualStrings("https", parsed.scheme);
87 try testing.expectEqualStrings("ziglang.org", parsed.host);
88 try testing.expectEqualStrings("/download", parsed.path);
89 try testing.expectEqual(@as(?u16, null), parsed.port);
90}
91
92test "with port" {
93 const parsed = try parse("http://example:1337/");
94 try testing.expectEqualStrings("http", parsed.scheme);
95 try testing.expectEqualStrings("example", parsed.host);
96 try testing.expectEqualStrings("/", parsed.path);
97 try testing.expectEqual(@as(?u16, 1337), parsed.port);
98}
lib/std/c.zig+2-2
...@@ -206,7 +206,7 @@ pub extern "c" fn sendto(...@@ -206,7 +206,7 @@ pub extern "c" fn sendto(
206 dest_addr: ?*const c.sockaddr,206 dest_addr: ?*const c.sockaddr,
207 addrlen: c.socklen_t,207 addrlen: c.socklen_t,
208) isize;208) isize;
209pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const std.x.os.Socket.Message, flags: c_int) isize;209pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const c.msghdr_const, flags: u32) isize;
210210
211pub extern "c" fn recv(sockfd: c.fd_t, arg1: ?*anyopaque, arg2: usize, arg3: c_int) isize;211pub extern "c" fn recv(sockfd: c.fd_t, arg1: ?*anyopaque, arg2: usize, arg3: c_int) isize;
212pub extern "c" fn recvfrom(212pub extern "c" fn recvfrom(
...@@ -217,7 +217,7 @@ pub extern "c" fn recvfrom(...@@ -217,7 +217,7 @@ pub extern "c" fn recvfrom(
217 noalias src_addr: ?*c.sockaddr,217 noalias src_addr: ?*c.sockaddr,
218 noalias addrlen: ?*c.socklen_t,218 noalias addrlen: ?*c.socklen_t,
219) isize;219) isize;
220pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *std.x.os.Socket.Message, flags: c_int) isize;220pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *c.msghdr, flags: u32) isize;
221221
222pub extern "c" fn kill(pid: c.pid_t, sig: c_int) c_int;222pub extern "c" fn kill(pid: c.pid_t, sig: c_int) c_int;
223pub extern "c" fn getdirentries(fd: c.fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize;223pub extern "c" fn getdirentries(fd: c.fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize;
lib/std/c/darwin.zig+10-1
...@@ -1007,7 +1007,16 @@ pub const sockaddr = extern struct {...@@ -1007,7 +1007,16 @@ pub const sockaddr = extern struct {
1007 data: [14]u8,1007 data: [14]u8,
10081008
1009 pub const SS_MAXSIZE = 128;1009 pub const SS_MAXSIZE = 128;
1010 pub const storage = std.x.os.Socket.Address.Native.Storage;1010 pub const storage = extern struct {
1011 len: u8 align(8),
1012 family: sa_family_t,
1013 padding: [126]u8 = undefined,
1014
1015 comptime {
1016 assert(@sizeOf(storage) == SS_MAXSIZE);
1017 assert(@alignOf(storage) == 8);
1018 }
1019 };
1011 pub const in = extern struct {1020 pub const in = extern struct {
1012 len: u8 = @sizeOf(in),1021 len: u8 = @sizeOf(in),
1013 family: sa_family_t = AF.INET,1022 family: sa_family_t = AF.INET,
lib/std/c/dragonfly.zig+12-2
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("../std.zig");2const std = @import("../std.zig");
3const assert = std.debug.assert;
3const maxInt = std.math.maxInt;4const maxInt = std.math.maxInt;
4const iovec = std.os.iovec;5const iovec = std.os.iovec;
56
...@@ -478,11 +479,20 @@ pub const CLOCK = struct {...@@ -478,11 +479,20 @@ pub const CLOCK = struct {
478479
479pub const sockaddr = extern struct {480pub const sockaddr = extern struct {
480 len: u8,481 len: u8,
481 family: u8,482 family: sa_family_t,
482 data: [14]u8,483 data: [14]u8,
483484
484 pub const SS_MAXSIZE = 128;485 pub const SS_MAXSIZE = 128;
485 pub const storage = std.x.os.Socket.Address.Native.Storage;486 pub const storage = extern struct {
487 len: u8 align(8),
488 family: sa_family_t,
489 padding: [126]u8 = undefined,
490
491 comptime {
492 assert(@sizeOf(storage) == SS_MAXSIZE);
493 assert(@alignOf(storage) == 8);
494 }
495 };
486496
487 pub const in = extern struct {497 pub const in = extern struct {
488 len: u8 = @sizeOf(in),498 len: u8 = @sizeOf(in),
lib/std/c/freebsd.zig+11-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;
2const builtin = @import("builtin");3const builtin = @import("builtin");
3const maxInt = std.math.maxInt;4const maxInt = std.math.maxInt;
4const iovec = std.os.iovec;5const iovec = std.os.iovec;
...@@ -404,7 +405,16 @@ pub const sockaddr = extern struct {...@@ -404,7 +405,16 @@ pub const sockaddr = extern struct {
404 data: [14]u8,405 data: [14]u8,
405406
406 pub const SS_MAXSIZE = 128;407 pub const SS_MAXSIZE = 128;
407 pub const storage = std.x.os.Socket.Address.Native.Storage;408 pub const storage = extern struct {
409 len: u8 align(8),
410 family: sa_family_t,
411 padding: [126]u8 = undefined,
412
413 comptime {
414 assert(@sizeOf(storage) == SS_MAXSIZE);
415 assert(@alignOf(storage) == 8);
416 }
417 };
408418
409 pub const in = extern struct {419 pub const in = extern struct {
410 len: u8 = @sizeOf(in),420 len: u8 = @sizeOf(in),
lib/std/c/haiku.zig+11-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;
2const builtin = @import("builtin");3const builtin = @import("builtin");
3const maxInt = std.math.maxInt;4const maxInt = std.math.maxInt;
4const iovec = std.os.iovec;5const iovec = std.os.iovec;
...@@ -339,7 +340,16 @@ pub const sockaddr = extern struct {...@@ -339,7 +340,16 @@ pub const sockaddr = extern struct {
339 data: [14]u8,340 data: [14]u8,
340341
341 pub const SS_MAXSIZE = 128;342 pub const SS_MAXSIZE = 128;
342 pub const storage = std.x.os.Socket.Address.Native.Storage;343 pub const storage = extern struct {
344 len: u8 align(8),
345 family: sa_family_t,
346 padding: [126]u8 = undefined,
347
348 comptime {
349 assert(@sizeOf(storage) == SS_MAXSIZE);
350 assert(@alignOf(storage) == 8);
351 }
352 };
343353
344 pub const in = extern struct {354 pub const in = extern struct {
345 len: u8 = @sizeOf(in),355 len: u8 = @sizeOf(in),
lib/std/c/netbsd.zig+11-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;
2const builtin = @import("builtin");3const builtin = @import("builtin");
3const maxInt = std.math.maxInt;4const maxInt = std.math.maxInt;
4const iovec = std.os.iovec;5const iovec = std.os.iovec;
...@@ -481,7 +482,16 @@ pub const sockaddr = extern struct {...@@ -481,7 +482,16 @@ pub const sockaddr = extern struct {
481 data: [14]u8,482 data: [14]u8,
482483
483 pub const SS_MAXSIZE = 128;484 pub const SS_MAXSIZE = 128;
484 pub const storage = std.x.os.Socket.Address.Native.Storage;485 pub const storage = extern struct {
486 len: u8 align(8),
487 family: sa_family_t,
488 padding: [126]u8 = undefined,
489
490 comptime {
491 assert(@sizeOf(storage) == SS_MAXSIZE);
492 assert(@alignOf(storage) == 8);
493 }
494 };
485495
486 pub const in = extern struct {496 pub const in = extern struct {
487 len: u8 = @sizeOf(in),497 len: u8 = @sizeOf(in),
lib/std/c/openbsd.zig+11-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;
2const maxInt = std.math.maxInt;3const maxInt = std.math.maxInt;
3const builtin = @import("builtin");4const builtin = @import("builtin");
4const iovec = std.os.iovec;5const iovec = std.os.iovec;
...@@ -372,7 +373,16 @@ pub const sockaddr = extern struct {...@@ -372,7 +373,16 @@ pub const sockaddr = extern struct {
372 data: [14]u8,373 data: [14]u8,
373374
374 pub const SS_MAXSIZE = 256;375 pub const SS_MAXSIZE = 256;
375 pub const storage = std.x.os.Socket.Address.Native.Storage;376 pub const storage = extern struct {
377 len: u8 align(8),
378 family: sa_family_t,
379 padding: [254]u8 = undefined,
380
381 comptime {
382 assert(@sizeOf(storage) == SS_MAXSIZE);
383 assert(@alignOf(storage) == 8);
384 }
385 };
376386
377 pub const in = extern struct {387 pub const in = extern struct {
378 len: u8 = @sizeOf(in),388 len: u8 = @sizeOf(in),
lib/std/c/solaris.zig+10-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;
2const builtin = @import("builtin");3const builtin = @import("builtin");
3const maxInt = std.math.maxInt;4const maxInt = std.math.maxInt;
4const iovec = std.os.iovec;5const iovec = std.os.iovec;
...@@ -435,7 +436,15 @@ pub const sockaddr = extern struct {...@@ -435,7 +436,15 @@ pub const sockaddr = extern struct {
435 data: [14]u8,436 data: [14]u8,
436437
437 pub const SS_MAXSIZE = 256;438 pub const SS_MAXSIZE = 256;
438 pub const storage = std.x.os.Socket.Address.Native.Storage;439 pub const storage = extern struct {
440 family: sa_family_t align(8),
441 padding: [254]u8 = undefined,
442
443 comptime {
444 assert(@sizeOf(storage) == SS_MAXSIZE);
445 assert(@alignOf(storage) == 8);
446 }
447 };
439448
440 pub const in = extern struct {449 pub const in = extern struct {
441 family: sa_family_t = AF.INET,450 family: sa_family_t = AF.INET,
lib/std/crypto.zig+5
...@@ -176,6 +176,9 @@ const std = @import("std.zig");...@@ -176,6 +176,9 @@ const std = @import("std.zig");
176176
177pub const errors = @import("crypto/errors.zig");177pub const errors = @import("crypto/errors.zig");
178178
179pub const tls = @import("crypto/tls.zig");
180pub const Certificate = @import("crypto/Certificate.zig");
181
179test {182test {
180 _ = aead.aegis.Aegis128L;183 _ = aead.aegis.Aegis128L;
181 _ = aead.aegis.Aegis256;184 _ = aead.aegis.Aegis256;
...@@ -264,6 +267,8 @@ test {...@@ -264,6 +267,8 @@ test {
264 _ = utils;267 _ = utils;
265 _ = random;268 _ = random;
266 _ = errors;269 _ = errors;
270 _ = tls;
271 _ = Certificate;
267}272}
268273
269test "CSPRNG" {274test "CSPRNG" {
lib/std/crypto/Certificate.zig created+1115
...@@ -0,0 +1,1115 @@
1buffer: []const u8,
2index: u32,
3
4pub const Bundle = @import("Certificate/Bundle.zig");
5
6pub const Algorithm = enum {
7 sha1WithRSAEncryption,
8 sha224WithRSAEncryption,
9 sha256WithRSAEncryption,
10 sha384WithRSAEncryption,
11 sha512WithRSAEncryption,
12 ecdsa_with_SHA224,
13 ecdsa_with_SHA256,
14 ecdsa_with_SHA384,
15 ecdsa_with_SHA512,
16
17 pub const map = std.ComptimeStringMap(Algorithm, .{
18 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
19 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
20 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
21 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
22 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
23 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
24 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
25 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
26 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
27 });
28
29 pub fn Hash(comptime algorithm: Algorithm) type {
30 return switch (algorithm) {
31 .sha1WithRSAEncryption => crypto.hash.Sha1,
32 .ecdsa_with_SHA224, .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
33 .ecdsa_with_SHA256, .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
34 .ecdsa_with_SHA384, .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
35 .ecdsa_with_SHA512, .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
36 };
37 }
38};
39
40pub const AlgorithmCategory = enum {
41 rsaEncryption,
42 X9_62_id_ecPublicKey,
43
44 pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
45 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
46 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
47 });
48};
49
50pub const Attribute = enum {
51 commonName,
52 serialNumber,
53 countryName,
54 localityName,
55 stateOrProvinceName,
56 organizationName,
57 organizationalUnitName,
58 organizationIdentifier,
59 pkcs9_emailAddress,
60
61 pub const map = std.ComptimeStringMap(Attribute, .{
62 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
63 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
64 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
65 .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
66 .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
67 .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
68 .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
69 .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
70 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
71 });
72};
73
74pub const NamedCurve = enum {
75 secp384r1,
76 X9_62_prime256v1,
77
78 pub const map = std.ComptimeStringMap(NamedCurve, .{
79 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
80 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
81 });
82};
83
84pub const ExtensionId = enum {
85 subject_key_identifier,
86 key_usage,
87 private_key_usage_period,
88 subject_alt_name,
89 issuer_alt_name,
90 basic_constraints,
91 crl_number,
92 certificate_policies,
93 authority_key_identifier,
94
95 pub const map = std.ComptimeStringMap(ExtensionId, .{
96 .{ &[_]u8{ 0x55, 0x1D, 0x0E }, .subject_key_identifier },
97 .{ &[_]u8{ 0x55, 0x1D, 0x0F }, .key_usage },
98 .{ &[_]u8{ 0x55, 0x1D, 0x10 }, .private_key_usage_period },
99 .{ &[_]u8{ 0x55, 0x1D, 0x11 }, .subject_alt_name },
100 .{ &[_]u8{ 0x55, 0x1D, 0x12 }, .issuer_alt_name },
101 .{ &[_]u8{ 0x55, 0x1D, 0x13 }, .basic_constraints },
102 .{ &[_]u8{ 0x55, 0x1D, 0x14 }, .crl_number },
103 .{ &[_]u8{ 0x55, 0x1D, 0x20 }, .certificate_policies },
104 .{ &[_]u8{ 0x55, 0x1D, 0x23 }, .authority_key_identifier },
105 });
106};
107
108pub const GeneralNameTag = enum(u5) {
109 otherName = 0,
110 rfc822Name = 1,
111 dNSName = 2,
112 x400Address = 3,
113 directoryName = 4,
114 ediPartyName = 5,
115 uniformResourceIdentifier = 6,
116 iPAddress = 7,
117 registeredID = 8,
118 _,
119};
120
121pub const Parsed = struct {
122 certificate: Certificate,
123 issuer_slice: Slice,
124 subject_slice: Slice,
125 common_name_slice: Slice,
126 signature_slice: Slice,
127 signature_algorithm: Algorithm,
128 pub_key_algo: PubKeyAlgo,
129 pub_key_slice: Slice,
130 message_slice: Slice,
131 subject_alt_name_slice: Slice,
132 validity: Validity,
133
134 pub const PubKeyAlgo = union(AlgorithmCategory) {
135 rsaEncryption: void,
136 X9_62_id_ecPublicKey: NamedCurve,
137 };
138
139 pub const Validity = struct {
140 not_before: u64,
141 not_after: u64,
142 };
143
144 pub const Slice = der.Element.Slice;
145
146 pub fn slice(p: Parsed, s: Slice) []const u8 {
147 return p.certificate.buffer[s.start..s.end];
148 }
149
150 pub fn issuer(p: Parsed) []const u8 {
151 return p.slice(p.issuer_slice);
152 }
153
154 pub fn subject(p: Parsed) []const u8 {
155 return p.slice(p.subject_slice);
156 }
157
158 pub fn commonName(p: Parsed) []const u8 {
159 return p.slice(p.common_name_slice);
160 }
161
162 pub fn signature(p: Parsed) []const u8 {
163 return p.slice(p.signature_slice);
164 }
165
166 pub fn pubKey(p: Parsed) []const u8 {
167 return p.slice(p.pub_key_slice);
168 }
169
170 pub fn pubKeySigAlgo(p: Parsed) []const u8 {
171 return p.slice(p.pub_key_signature_algorithm_slice);
172 }
173
174 pub fn message(p: Parsed) []const u8 {
175 return p.slice(p.message_slice);
176 }
177
178 pub fn subjectAltName(p: Parsed) []const u8 {
179 return p.slice(p.subject_alt_name_slice);
180 }
181
182 pub const VerifyError = error{
183 CertificateIssuerMismatch,
184 CertificateNotYetValid,
185 CertificateExpired,
186 CertificateSignatureAlgorithmUnsupported,
187 CertificateSignatureAlgorithmMismatch,
188 CertificateFieldHasInvalidLength,
189 CertificateFieldHasWrongDataType,
190 CertificatePublicKeyInvalid,
191 CertificateSignatureInvalidLength,
192 CertificateSignatureInvalid,
193 CertificateSignatureUnsupportedBitCount,
194 CertificateSignatureNamedCurveUnsupported,
195 };
196
197 /// This function verifies:
198 /// * That the subject's issuer is indeed the provided issuer.
199 /// * The time validity of the subject.
200 /// * The signature.
201 pub fn verify(parsed_subject: Parsed, parsed_issuer: Parsed, now_sec: i64) VerifyError!void {
202 // Check that the subject's issuer name matches the issuer's
203 // subject name.
204 if (!mem.eql(u8, parsed_subject.issuer(), parsed_issuer.subject())) {
205 return error.CertificateIssuerMismatch;
206 }
207
208 if (now_sec < parsed_subject.validity.not_before)
209 return error.CertificateNotYetValid;
210 if (now_sec > parsed_subject.validity.not_after)
211 return error.CertificateExpired;
212
213 switch (parsed_subject.signature_algorithm) {
214 inline .sha1WithRSAEncryption,
215 .sha224WithRSAEncryption,
216 .sha256WithRSAEncryption,
217 .sha384WithRSAEncryption,
218 .sha512WithRSAEncryption,
219 => |algorithm| return verifyRsa(
220 algorithm.Hash(),
221 parsed_subject.message(),
222 parsed_subject.signature(),
223 parsed_issuer.pub_key_algo,
224 parsed_issuer.pubKey(),
225 ),
226
227 inline .ecdsa_with_SHA224,
228 .ecdsa_with_SHA256,
229 .ecdsa_with_SHA384,
230 .ecdsa_with_SHA512,
231 => |algorithm| return verify_ecdsa(
232 algorithm.Hash(),
233 parsed_subject.message(),
234 parsed_subject.signature(),
235 parsed_issuer.pub_key_algo,
236 parsed_issuer.pubKey(),
237 ),
238 }
239 }
240
241 pub const VerifyHostNameError = error{
242 CertificateHostMismatch,
243 CertificateFieldHasInvalidLength,
244 };
245
246 pub fn verifyHostName(parsed_subject: Parsed, host_name: []const u8) VerifyHostNameError!void {
247 // If the Subject Alternative Names extension is present, this is
248 // what to check. Otherwise, only the common name is checked.
249 const subject_alt_name = parsed_subject.subjectAltName();
250 if (subject_alt_name.len == 0) {
251 if (checkHostName(host_name, parsed_subject.commonName())) {
252 return;
253 } else {
254 return error.CertificateHostMismatch;
255 }
256 }
257
258 const general_names = try der.Element.parse(subject_alt_name, 0);
259 var name_i = general_names.slice.start;
260 while (name_i < general_names.slice.end) {
261 const general_name = try der.Element.parse(subject_alt_name, name_i);
262 name_i = general_name.slice.end;
263 switch (@intToEnum(GeneralNameTag, @enumToInt(general_name.identifier.tag))) {
264 .dNSName => {
265 const dns_name = subject_alt_name[general_name.slice.start..general_name.slice.end];
266 if (checkHostName(host_name, dns_name)) return;
267 },
268 else => {},
269 }
270 }
271
272 return error.CertificateHostMismatch;
273 }
274
275 fn checkHostName(host_name: []const u8, dns_name: []const u8) bool {
276 if (mem.eql(u8, dns_name, host_name)) {
277 return true; // exact match
278 }
279
280 if (mem.startsWith(u8, dns_name, "*.")) {
281 // wildcard certificate, matches any subdomain
282 // TODO: I think wildcards are not supposed to match any prefix but
283 // only match exactly one subdomain.
284 if (mem.endsWith(u8, host_name, dns_name[1..])) {
285 // The host_name has a subdomain, but the important part matches.
286 return true;
287 }
288 if (mem.eql(u8, dns_name[2..], host_name)) {
289 // The host_name has no subdomain and matches exactly.
290 return true;
291 }
292 }
293
294 return false;
295 }
296};
297
298pub fn parse(cert: Certificate) !Parsed {
299 const cert_bytes = cert.buffer;
300 const certificate = try der.Element.parse(cert_bytes, cert.index);
301 const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start);
302 const version = try der.Element.parse(cert_bytes, tbs_certificate.slice.start);
303 try checkVersion(cert_bytes, version);
304 const serial_number = try der.Element.parse(cert_bytes, version.slice.end);
305 // RFC 5280, section 4.1.2.3:
306 // "This field MUST contain the same algorithm identifier as
307 // the signatureAlgorithm field in the sequence Certificate."
308 const tbs_signature = try der.Element.parse(cert_bytes, serial_number.slice.end);
309 const issuer = try der.Element.parse(cert_bytes, tbs_signature.slice.end);
310 const validity = try der.Element.parse(cert_bytes, issuer.slice.end);
311 const not_before = try der.Element.parse(cert_bytes, validity.slice.start);
312 const not_before_utc = try parseTime(cert, not_before);
313 const not_after = try der.Element.parse(cert_bytes, not_before.slice.end);
314 const not_after_utc = try parseTime(cert, not_after);
315 const subject = try der.Element.parse(cert_bytes, validity.slice.end);
316
317 const pub_key_info = try der.Element.parse(cert_bytes, subject.slice.end);
318 const pub_key_signature_algorithm = try der.Element.parse(cert_bytes, pub_key_info.slice.start);
319 const pub_key_algo_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.start);
320 const pub_key_algo_tag = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);
321 var pub_key_algo: Parsed.PubKeyAlgo = undefined;
322 switch (pub_key_algo_tag) {
323 .rsaEncryption => {
324 pub_key_algo = .{ .rsaEncryption = {} };
325 },
326 .X9_62_id_ecPublicKey => {
327 // RFC 5480 Section 2.1.1.1 Named Curve
328 // ECParameters ::= CHOICE {
329 // namedCurve OBJECT IDENTIFIER
330 // -- implicitCurve NULL
331 // -- specifiedCurve SpecifiedECDomain
332 // }
333 const params_elem = try der.Element.parse(cert_bytes, pub_key_algo_elem.slice.end);
334 const named_curve = try parseNamedCurve(cert_bytes, params_elem);
335 pub_key_algo = .{ .X9_62_id_ecPublicKey = named_curve };
336 },
337 }
338 const pub_key_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.end);
339 const pub_key = try parseBitString(cert, pub_key_elem);
340
341 var common_name = der.Element.Slice.empty;
342 var name_i = subject.slice.start;
343 while (name_i < subject.slice.end) {
344 const rdn = try der.Element.parse(cert_bytes, name_i);
345 var rdn_i = rdn.slice.start;
346 while (rdn_i < rdn.slice.end) {
347 const atav = try der.Element.parse(cert_bytes, rdn_i);
348 var atav_i = atav.slice.start;
349 while (atav_i < atav.slice.end) {
350 const ty_elem = try der.Element.parse(cert_bytes, atav_i);
351 const ty = try parseAttribute(cert_bytes, ty_elem);
352 const val = try der.Element.parse(cert_bytes, ty_elem.slice.end);
353 switch (ty) {
354 .commonName => common_name = val.slice,
355 else => {},
356 }
357 atav_i = val.slice.end;
358 }
359 rdn_i = atav.slice.end;
360 }
361 name_i = rdn.slice.end;
362 }
363
364 const sig_algo = try der.Element.parse(cert_bytes, tbs_certificate.slice.end);
365 const algo_elem = try der.Element.parse(cert_bytes, sig_algo.slice.start);
366 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);
367 const sig_elem = try der.Element.parse(cert_bytes, sig_algo.slice.end);
368 const signature = try parseBitString(cert, sig_elem);
369
370 // Extensions
371 var subject_alt_name_slice = der.Element.Slice.empty;
372 ext: {
373 if (pub_key_info.slice.end >= tbs_certificate.slice.end)
374 break :ext;
375
376 const outer_extensions = try der.Element.parse(cert_bytes, pub_key_info.slice.end);
377 if (outer_extensions.identifier.tag != .bitstring)
378 break :ext;
379
380 const extensions = try der.Element.parse(cert_bytes, outer_extensions.slice.start);
381
382 var ext_i = extensions.slice.start;
383 while (ext_i < extensions.slice.end) {
384 const extension = try der.Element.parse(cert_bytes, ext_i);
385 ext_i = extension.slice.end;
386 const oid_elem = try der.Element.parse(cert_bytes, extension.slice.start);
387 const ext_id = parseExtensionId(cert_bytes, oid_elem) catch |err| switch (err) {
388 error.CertificateHasUnrecognizedObjectId => continue,
389 else => |e| return e,
390 };
391 const critical_elem = try der.Element.parse(cert_bytes, oid_elem.slice.end);
392 const ext_bytes_elem = if (critical_elem.identifier.tag != .boolean)
393 critical_elem
394 else
395 try der.Element.parse(cert_bytes, critical_elem.slice.end);
396 switch (ext_id) {
397 .subject_alt_name => subject_alt_name_slice = ext_bytes_elem.slice,
398 else => continue,
399 }
400 }
401 }
402
403 return .{
404 .certificate = cert,
405 .common_name_slice = common_name,
406 .issuer_slice = issuer.slice,
407 .subject_slice = subject.slice,
408 .signature_slice = signature,
409 .signature_algorithm = signature_algorithm,
410 .message_slice = .{ .start = certificate.slice.start, .end = tbs_certificate.slice.end },
411 .pub_key_algo = pub_key_algo,
412 .pub_key_slice = pub_key,
413 .validity = .{
414 .not_before = not_before_utc,
415 .not_after = not_after_utc,
416 },
417 .subject_alt_name_slice = subject_alt_name_slice,
418 };
419}
420
421pub fn verify(subject: Certificate, issuer: Certificate, now_sec: i64) !void {
422 const parsed_subject = try subject.parse();
423 const parsed_issuer = try issuer.parse();
424 return parsed_subject.verify(parsed_issuer, now_sec);
425}
426
427pub fn contents(cert: Certificate, elem: der.Element) []const u8 {
428 return cert.buffer[elem.slice.start..elem.slice.end];
429}
430
431pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {
432 if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType;
433 if (cert.buffer[elem.slice.start] != 0) return error.CertificateHasInvalidBitString;
434 return .{ .start = elem.slice.start + 1, .end = elem.slice.end };
435}
436
437/// Returns number of seconds since epoch.
438pub fn parseTime(cert: Certificate, elem: der.Element) !u64 {
439 const bytes = cert.contents(elem);
440 switch (elem.identifier.tag) {
441 .utc_time => {
442 // Example: "YYMMDD000000Z"
443 if (bytes.len != 13)
444 return error.CertificateTimeInvalid;
445 if (bytes[12] != 'Z')
446 return error.CertificateTimeInvalid;
447
448 return Date.toSeconds(.{
449 .year = @as(u16, 2000) + try parseTimeDigits(bytes[0..2].*, 0, 99),
450 .month = try parseTimeDigits(bytes[2..4].*, 1, 12),
451 .day = try parseTimeDigits(bytes[4..6].*, 1, 31),
452 .hour = try parseTimeDigits(bytes[6..8].*, 0, 23),
453 .minute = try parseTimeDigits(bytes[8..10].*, 0, 59),
454 .second = try parseTimeDigits(bytes[10..12].*, 0, 59),
455 });
456 },
457 .generalized_time => {
458 // Examples:
459 // "19920521000000Z"
460 // "19920622123421Z"
461 // "19920722132100.3Z"
462 if (bytes.len < 15)
463 return error.CertificateTimeInvalid;
464 return Date.toSeconds(.{
465 .year = try parseYear4(bytes[0..4]),
466 .month = try parseTimeDigits(bytes[4..6].*, 1, 12),
467 .day = try parseTimeDigits(bytes[6..8].*, 1, 31),
468 .hour = try parseTimeDigits(bytes[8..10].*, 0, 23),
469 .minute = try parseTimeDigits(bytes[10..12].*, 0, 59),
470 .second = try parseTimeDigits(bytes[12..14].*, 0, 59),
471 });
472 },
473 else => return error.CertificateFieldHasWrongDataType,
474 }
475}
476
477const Date = struct {
478 /// example: 1999
479 year: u16,
480 /// range: 1 to 12
481 month: u8,
482 /// range: 1 to 31
483 day: u8,
484 /// range: 0 to 59
485 hour: u8,
486 /// range: 0 to 59
487 minute: u8,
488 /// range: 0 to 59
489 second: u8,
490
491 /// Convert to number of seconds since epoch.
492 pub fn toSeconds(date: Date) u64 {
493 var sec: u64 = 0;
494
495 {
496 var year: u16 = 1970;
497 while (year < date.year) : (year += 1) {
498 const days: u64 = std.time.epoch.getDaysInYear(year);
499 sec += days * std.time.epoch.secs_per_day;
500 }
501 }
502
503 {
504 const is_leap = std.time.epoch.isLeapYear(date.year);
505 var month: u4 = 1;
506 while (month < date.month) : (month += 1) {
507 const days: u64 = std.time.epoch.getDaysInMonth(
508 @intToEnum(std.time.epoch.YearLeapKind, @boolToInt(is_leap)),
509 @intToEnum(std.time.epoch.Month, month),
510 );
511 sec += days * std.time.epoch.secs_per_day;
512 }
513 }
514
515 sec += (date.day - 1) * @as(u64, std.time.epoch.secs_per_day);
516 sec += date.hour * @as(u64, 60 * 60);
517 sec += date.minute * @as(u64, 60);
518 sec += date.second;
519
520 return sec;
521 }
522};
523
524pub fn parseTimeDigits(nn: @Vector(2, u8), min: u8, max: u8) !u8 {
525 const zero: @Vector(2, u8) = .{ '0', '0' };
526 const mm: @Vector(2, u8) = .{ 10, 1 };
527 const result = @reduce(.Add, (nn -% zero) *% mm);
528 if (result < min) return error.CertificateTimeInvalid;
529 if (result > max) return error.CertificateTimeInvalid;
530 return result;
531}
532
533test parseTimeDigits {
534 const expectEqual = std.testing.expectEqual;
535 try expectEqual(@as(u8, 0), try parseTimeDigits("00".*, 0, 99));
536 try expectEqual(@as(u8, 99), try parseTimeDigits("99".*, 0, 99));
537 try expectEqual(@as(u8, 42), try parseTimeDigits("42".*, 0, 99));
538
539 const expectError = std.testing.expectError;
540 try expectError(error.CertificateTimeInvalid, parseTimeDigits("13".*, 1, 12));
541 try expectError(error.CertificateTimeInvalid, parseTimeDigits("00".*, 1, 12));
542}
543
544pub fn parseYear4(text: *const [4]u8) !u16 {
545 const nnnn: @Vector(4, u16) = .{ text[0], text[1], text[2], text[3] };
546 const zero: @Vector(4, u16) = .{ '0', '0', '0', '0' };
547 const mmmm: @Vector(4, u16) = .{ 1000, 100, 10, 1 };
548 const result = @reduce(.Add, (nnnn -% zero) *% mmmm);
549 if (result > 9999) return error.CertificateTimeInvalid;
550 return result;
551}
552
553test parseYear4 {
554 const expectEqual = std.testing.expectEqual;
555 try expectEqual(@as(u16, 0), try parseYear4("0000"));
556 try expectEqual(@as(u16, 9999), try parseYear4("9999"));
557 try expectEqual(@as(u16, 1988), try parseYear4("1988"));
558
559 const expectError = std.testing.expectError;
560 try expectError(error.CertificateTimeInvalid, parseYear4("999b"));
561 try expectError(error.CertificateTimeInvalid, parseYear4("crap"));
562}
563
564pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {
565 return parseEnum(Algorithm, bytes, element);
566}
567
568pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {
569 return parseEnum(AlgorithmCategory, bytes, element);
570}
571
572pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute {
573 return parseEnum(Attribute, bytes, element);
574}
575
576pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve {
577 return parseEnum(NamedCurve, bytes, element);
578}
579
580pub fn parseExtensionId(bytes: []const u8, element: der.Element) !ExtensionId {
581 return parseEnum(ExtensionId, bytes, element);
582}
583
584fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E {
585 if (element.identifier.tag != .object_identifier)
586 return error.CertificateFieldHasWrongDataType;
587 const oid_bytes = bytes[element.slice.start..element.slice.end];
588 return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId;
589}
590
591pub fn checkVersion(bytes: []const u8, version: der.Element) !void {
592 if (@bitCast(u8, version.identifier) != 0xa0 or
593 !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02"))
594 {
595 return error.UnsupportedCertificateVersion;
596 }
597}
598
599fn verifyRsa(
600 comptime Hash: type,
601 message: []const u8,
602 sig: []const u8,
603 pub_key_algo: Parsed.PubKeyAlgo,
604 pub_key: []const u8,
605) !void {
606 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
607 const pk_components = try rsa.PublicKey.parseDer(pub_key);
608 const exponent = pk_components.exponent;
609 const modulus = pk_components.modulus;
610 if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid;
611 if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength;
612
613 const hash_der = switch (Hash) {
614 crypto.hash.Sha1 => [_]u8{
615 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
616 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
617 },
618 crypto.hash.sha2.Sha224 => [_]u8{
619 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
620 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
621 0x00, 0x04, 0x1c,
622 },
623 crypto.hash.sha2.Sha256 => [_]u8{
624 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
625 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
626 0x00, 0x04, 0x20,
627 },
628 crypto.hash.sha2.Sha384 => [_]u8{
629 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
630 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
631 0x00, 0x04, 0x30,
632 },
633 crypto.hash.sha2.Sha512 => [_]u8{
634 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
635 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
636 0x00, 0x04, 0x40,
637 },
638 else => @compileError("unreachable"),
639 };
640
641 var msg_hashed: [Hash.digest_length]u8 = undefined;
642 Hash.hash(message, &msg_hashed, .{});
643
644 var rsa_mem_buf: [512 * 64]u8 = undefined;
645 var fba = std.heap.FixedBufferAllocator.init(&rsa_mem_buf);
646 const ally = fba.allocator();
647
648 switch (modulus.len) {
649 inline 128, 256, 512 => |modulus_len| {
650 const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3;
651 const em: [modulus_len]u8 =
652 [2]u8{ 0, 1 } ++
653 ([1]u8{0xff} ** ps_len) ++
654 [1]u8{0} ++
655 hash_der ++
656 msg_hashed;
657
658 const public_key = rsa.PublicKey.fromBytes(exponent, modulus, ally) catch |err| switch (err) {
659 error.OutOfMemory => unreachable, // rsa_mem_buf is big enough
660 };
661 const em_dec = rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key, ally) catch |err| switch (err) {
662 error.OutOfMemory => unreachable, // rsa_mem_buf is big enough
663
664 error.MessageTooLong => unreachable,
665 error.NegativeIntoUnsigned => @panic("TODO make RSA not emit this error"),
666 error.TargetTooSmall => @panic("TODO make RSA not emit this error"),
667 error.BufferTooSmall => @panic("TODO make RSA not emit this error"),
668 };
669
670 if (!mem.eql(u8, &em, &em_dec)) {
671 return error.CertificateSignatureInvalid;
672 }
673 },
674 else => {
675 return error.CertificateSignatureUnsupportedBitCount;
676 },
677 }
678}
679
680fn verify_ecdsa(
681 comptime Hash: type,
682 message: []const u8,
683 encoded_sig: []const u8,
684 pub_key_algo: Parsed.PubKeyAlgo,
685 sec1_pub_key: []const u8,
686) !void {
687 const sig_named_curve = switch (pub_key_algo) {
688 .X9_62_id_ecPublicKey => |named_curve| named_curve,
689 else => return error.CertificateSignatureAlgorithmMismatch,
690 };
691
692 switch (sig_named_curve) {
693 .secp384r1 => {
694 const P = crypto.ecc.P384;
695 const Ecdsa = crypto.sign.ecdsa.Ecdsa(P, Hash);
696 const sig = Ecdsa.Signature.fromDer(encoded_sig) catch |err| switch (err) {
697 error.InvalidEncoding => return error.CertificateSignatureInvalid,
698 };
699 const pub_key = Ecdsa.PublicKey.fromSec1(sec1_pub_key) catch |err| switch (err) {
700 error.InvalidEncoding => return error.CertificateSignatureInvalid,
701 error.NonCanonical => return error.CertificateSignatureInvalid,
702 error.NotSquare => return error.CertificateSignatureInvalid,
703 };
704 sig.verify(message, pub_key) catch |err| switch (err) {
705 error.IdentityElement => return error.CertificateSignatureInvalid,
706 error.NonCanonical => return error.CertificateSignatureInvalid,
707 error.SignatureVerificationFailed => return error.CertificateSignatureInvalid,
708 };
709 },
710 .X9_62_prime256v1 => {
711 return error.CertificateSignatureNamedCurveUnsupported;
712 },
713 }
714}
715
716const std = @import("../std.zig");
717const crypto = std.crypto;
718const mem = std.mem;
719const Certificate = @This();
720
721pub const der = struct {
722 pub const Class = enum(u2) {
723 universal,
724 application,
725 context_specific,
726 private,
727 };
728
729 pub const PC = enum(u1) {
730 primitive,
731 constructed,
732 };
733
734 pub const Identifier = packed struct(u8) {
735 tag: Tag,
736 pc: PC,
737 class: Class,
738 };
739
740 pub const Tag = enum(u5) {
741 boolean = 1,
742 integer = 2,
743 bitstring = 3,
744 octetstring = 4,
745 null = 5,
746 object_identifier = 6,
747 sequence = 16,
748 sequence_of = 17,
749 utc_time = 23,
750 generalized_time = 24,
751 _,
752 };
753
754 pub const Element = struct {
755 identifier: Identifier,
756 slice: Slice,
757
758 pub const Slice = struct {
759 start: u32,
760 end: u32,
761
762 pub const empty: Slice = .{ .start = 0, .end = 0 };
763 };
764
765 pub const ParseError = error{CertificateFieldHasInvalidLength};
766
767 pub fn parse(bytes: []const u8, index: u32) ParseError!Element {
768 var i = index;
769 const identifier = @bitCast(Identifier, bytes[i]);
770 i += 1;
771 const size_byte = bytes[i];
772 i += 1;
773 if ((size_byte >> 7) == 0) {
774 return .{
775 .identifier = identifier,
776 .slice = .{
777 .start = i,
778 .end = i + size_byte,
779 },
780 };
781 }
782
783 const len_size = @truncate(u7, size_byte);
784 if (len_size > @sizeOf(u32)) {
785 return error.CertificateFieldHasInvalidLength;
786 }
787
788 const end_i = i + len_size;
789 var long_form_size: u32 = 0;
790 while (i < end_i) : (i += 1) {
791 long_form_size = (long_form_size << 8) | bytes[i];
792 }
793
794 return .{
795 .identifier = identifier,
796 .slice = .{
797 .start = i,
798 .end = i + long_form_size,
799 },
800 };
801 }
802 };
803};
804
805test {
806 _ = Bundle;
807}
808
809/// TODO: replace this with Frank's upcoming RSA implementation. the verify
810/// function won't have the possibility of failure - it will either identify a
811/// valid signature or an invalid signature.
812/// This code is borrowed from https://github.com/shiguredo/tls13-zig
813/// which is licensed under the Apache License Version 2.0, January 2004
814/// http://www.apache.org/licenses/
815/// The code has been modified.
816pub const rsa = struct {
817 const BigInt = std.math.big.int.Managed;
818
819 pub const PSSSignature = struct {
820 pub fn fromBytes(comptime modulus_len: usize, msg: []const u8) [modulus_len]u8 {
821 var result = [1]u8{0} ** modulus_len;
822 std.mem.copy(u8, &result, msg);
823 return result;
824 }
825
826 pub fn verify(comptime modulus_len: usize, sig: [modulus_len]u8, msg: []const u8, public_key: PublicKey, comptime Hash: type, allocator: std.mem.Allocator) !void {
827 const mod_bits = try countBits(public_key.n.toConst(), allocator);
828 const em_dec = try encrypt(modulus_len, sig, public_key, allocator);
829
830 try EMSA_PSS_VERIFY(msg, &em_dec, mod_bits - 1, Hash.digest_length, Hash, allocator);
831 }
832
833 fn EMSA_PSS_VERIFY(msg: []const u8, em: []const u8, emBit: usize, sLen: usize, comptime Hash: type, allocator: std.mem.Allocator) !void {
834 // TODO
835 // 1. If the length of M is greater than the input limitation for
836 // the hash function (2^61 - 1 octets for SHA-1), output
837 // "inconsistent" and stop.
838
839 // emLen = \ceil(emBits/8)
840 const emLen = ((emBit - 1) / 8) + 1;
841 std.debug.assert(emLen == em.len);
842
843 // 2. Let mHash = Hash(M), an octet string of length hLen.
844 var mHash: [Hash.digest_length]u8 = undefined;
845 Hash.hash(msg, &mHash, .{});
846
847 // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
848 if (emLen < Hash.digest_length + sLen + 2) {
849 return error.InvalidSignature;
850 }
851
852 // 4. If the rightmost octet of EM does not have hexadecimal value
853 // 0xbc, output "inconsistent" and stop.
854 if (em[em.len - 1] != 0xbc) {
855 return error.InvalidSignature;
856 }
857
858 // 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM,
859 // and let H be the next hLen octets.
860 const maskedDB = em[0..(emLen - Hash.digest_length - 1)];
861 const h = em[(emLen - Hash.digest_length - 1)..(emLen - 1)];
862
863 // 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
864 // maskedDB are not all equal to zero, output "inconsistent" and
865 // stop.
866 const zero_bits = emLen * 8 - emBit;
867 var mask: u8 = maskedDB[0];
868 var i: usize = 0;
869 while (i < 8 - zero_bits) : (i += 1) {
870 mask = mask >> 1;
871 }
872 if (mask != 0) {
873 return error.InvalidSignature;
874 }
875
876 // 7. Let dbMask = MGF(H, emLen - hLen - 1).
877 const mgf_len = emLen - Hash.digest_length - 1;
878 var mgf_out = try allocator.alloc(u8, ((mgf_len - 1) / Hash.digest_length + 1) * Hash.digest_length);
879 defer allocator.free(mgf_out);
880 var dbMask = try MGF1(mgf_out, h, mgf_len, Hash, allocator);
881
882 // 8. Let DB = maskedDB \xor dbMask.
883 i = 0;
884 while (i < dbMask.len) : (i += 1) {
885 dbMask[i] = maskedDB[i] ^ dbMask[i];
886 }
887
888 // 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
889 // in DB to zero.
890 i = 0;
891 mask = 0;
892 while (i < 8 - zero_bits) : (i += 1) {
893 mask = mask << 1;
894 mask += 1;
895 }
896 dbMask[0] = dbMask[0] & mask;
897
898 // 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not
899 // zero or if the octet at position emLen - hLen - sLen - 1 (the
900 // leftmost position is "position 1") does not have hexadecimal
901 // value 0x01, output "inconsistent" and stop.
902 if (dbMask[mgf_len - sLen - 2] != 0x00) {
903 return error.InvalidSignature;
904 }
905
906 if (dbMask[mgf_len - sLen - 1] != 0x01) {
907 return error.InvalidSignature;
908 }
909
910 // 11. Let salt be the last sLen octets of DB.
911 const salt = dbMask[(mgf_len - sLen)..];
912
913 // 12. Let
914 // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
915 // M' is an octet string of length 8 + hLen + sLen with eight
916 // initial zero octets.
917 var m_p = try allocator.alloc(u8, 8 + Hash.digest_length + sLen);
918 defer allocator.free(m_p);
919 std.mem.copy(u8, m_p, &([_]u8{0} ** 8));
920 std.mem.copy(u8, m_p[8..], &mHash);
921 std.mem.copy(u8, m_p[(8 + Hash.digest_length)..], salt);
922
923 // 13. Let H' = Hash(M'), an octet string of length hLen.
924 var h_p: [Hash.digest_length]u8 = undefined;
925 Hash.hash(m_p, &h_p, .{});
926
927 // 14. If H = H', output "consistent". Otherwise, output
928 // "inconsistent".
929 if (!std.mem.eql(u8, h, &h_p)) {
930 return error.InvalidSignature;
931 }
932 }
933
934 fn MGF1(out: []u8, seed: []const u8, len: usize, comptime Hash: type, allocator: std.mem.Allocator) ![]u8 {
935 var counter: usize = 0;
936 var idx: usize = 0;
937 var c: [4]u8 = undefined;
938
939 var hash = try allocator.alloc(u8, seed.len + c.len);
940 defer allocator.free(hash);
941 std.mem.copy(u8, hash, seed);
942 var hashed: [Hash.digest_length]u8 = undefined;
943
944 while (idx < len) {
945 c[0] = @intCast(u8, (counter >> 24) & 0xFF);
946 c[1] = @intCast(u8, (counter >> 16) & 0xFF);
947 c[2] = @intCast(u8, (counter >> 8) & 0xFF);
948 c[3] = @intCast(u8, counter & 0xFF);
949
950 std.mem.copy(u8, hash[seed.len..], &c);
951 Hash.hash(hash, &hashed, .{});
952
953 std.mem.copy(u8, out[idx..], &hashed);
954 idx += hashed.len;
955
956 counter += 1;
957 }
958
959 return out[0..len];
960 }
961 };
962
963 pub const PublicKey = struct {
964 n: BigInt,
965 e: BigInt,
966
967 pub fn deinit(self: *PublicKey) void {
968 self.n.deinit();
969 self.e.deinit();
970 }
971
972 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8, allocator: std.mem.Allocator) !PublicKey {
973 var _n = try BigInt.init(allocator);
974 errdefer _n.deinit();
975 try setBytes(&_n, modulus_bytes, allocator);
976
977 var _e = try BigInt.init(allocator);
978 errdefer _e.deinit();
979 try setBytes(&_e, pub_bytes, allocator);
980
981 return .{
982 .n = _n,
983 .e = _e,
984 };
985 }
986
987 pub fn parseDer(pub_key: []const u8) !struct { modulus: []const u8, exponent: []const u8 } {
988 const pub_key_seq = try der.Element.parse(pub_key, 0);
989 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
990 const modulus_elem = try der.Element.parse(pub_key, pub_key_seq.slice.start);
991 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
992 const exponent_elem = try der.Element.parse(pub_key, modulus_elem.slice.end);
993 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
994 // Skip over meaningless zeroes in the modulus.
995 const modulus_raw = pub_key[modulus_elem.slice.start..modulus_elem.slice.end];
996 const modulus_offset = for (modulus_raw) |byte, i| {
997 if (byte != 0) break i;
998 } else modulus_raw.len;
999 return .{
1000 .modulus = modulus_raw[modulus_offset..],
1001 .exponent = pub_key[exponent_elem.slice.start..exponent_elem.slice.end],
1002 };
1003 }
1004 };
1005
1006 fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey, allocator: std.mem.Allocator) ![modulus_len]u8 {
1007 var m = try BigInt.init(allocator);
1008 defer m.deinit();
1009
1010 try setBytes(&m, &msg, allocator);
1011
1012 if (m.order(public_key.n) != .lt) {
1013 return error.MessageTooLong;
1014 }
1015
1016 var e = try BigInt.init(allocator);
1017 defer e.deinit();
1018
1019 try pow_montgomery(&e, &m, &public_key.e, &public_key.n, allocator);
1020
1021 var res: [modulus_len]u8 = undefined;
1022
1023 try toBytes(&res, &e, allocator);
1024
1025 return res;
1026 }
1027
1028 fn setBytes(r: *BigInt, bytes: []const u8, allcator: std.mem.Allocator) !void {
1029 try r.set(0);
1030 var tmp = try BigInt.init(allcator);
1031 defer tmp.deinit();
1032 for (bytes) |b| {
1033 try r.shiftLeft(r, 8);
1034 try tmp.set(b);
1035 try r.add(r, &tmp);
1036 }
1037 }
1038
1039 fn pow_montgomery(r: *BigInt, a: *const BigInt, x: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
1040 var bin_raw: [512]u8 = undefined;
1041 try toBytes(&bin_raw, x, allocator);
1042
1043 var i: usize = 0;
1044 while (bin_raw[i] == 0x00) : (i += 1) {}
1045 const bin = bin_raw[i..];
1046
1047 try r.set(1);
1048 var r1 = try BigInt.init(allocator);
1049 defer r1.deinit();
1050 try BigInt.copy(&r1, a.toConst());
1051 i = 0;
1052 while (i < bin.len * 8) : (i += 1) {
1053 if (((bin[i / 8] >> @intCast(u3, (7 - (i % 8)))) & 0x1) == 0) {
1054 try BigInt.mul(&r1, r, &r1);
1055 try mod(&r1, &r1, n, allocator);
1056 try BigInt.sqr(r, r);
1057 try mod(r, r, n, allocator);
1058 } else {
1059 try BigInt.mul(r, r, &r1);
1060 try mod(r, r, n, allocator);
1061 try BigInt.sqr(&r1, &r1);
1062 try mod(&r1, &r1, n, allocator);
1063 }
1064 }
1065 }
1066
1067 fn toBytes(out: []u8, a: *const BigInt, allocator: std.mem.Allocator) !void {
1068 const Error = error{
1069 BufferTooSmall,
1070 };
1071
1072 var mask = try BigInt.initSet(allocator, 0xFF);
1073 defer mask.deinit();
1074 var tmp = try BigInt.init(allocator);
1075 defer tmp.deinit();
1076
1077 var a_copy = try BigInt.init(allocator);
1078 defer a_copy.deinit();
1079 try a_copy.copy(a.toConst());
1080
1081 // Encoding into big-endian bytes
1082 var i: usize = 0;
1083 while (i < out.len) : (i += 1) {
1084 try tmp.bitAnd(&a_copy, &mask);
1085 const b = try tmp.to(u8);
1086 out[out.len - i - 1] = b;
1087 try a_copy.shiftRight(&a_copy, 8);
1088 }
1089
1090 if (!a_copy.eqZero()) {
1091 return Error.BufferTooSmall;
1092 }
1093 }
1094
1095 fn mod(rem: *BigInt, a: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
1096 var q = try BigInt.init(allocator);
1097 defer q.deinit();
1098
1099 try BigInt.divFloor(&q, rem, a, n);
1100 }
1101
1102 fn countBits(a: std.math.big.int.Const, allocator: std.mem.Allocator) !usize {
1103 var i: usize = 0;
1104 var a_copy = try BigInt.init(allocator);
1105 defer a_copy.deinit();
1106 try a_copy.copy(a);
1107
1108 while (!a_copy.eqZero()) {
1109 try a_copy.shiftRight(&a_copy, 1);
1110 i += 1;
1111 }
1112
1113 return i;
1114 }
1115};
lib/std/crypto/Certificate/Bundle.zig created+189
...@@ -0,0 +1,189 @@
1//! A set of certificates. Typically pre-installed on every operating system,
2//! these are "Certificate Authorities" used to validate SSL certificates.
3//! This data structure stores certificates in DER-encoded form, all of them
4//! concatenated together in the `bytes` array. The `map` field contains an
5//! index from the DER-encoded subject name to the index of the containing
6//! certificate within `bytes`.
7
8/// The key is the contents slice of the subject.
9map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .{},
10bytes: std.ArrayListUnmanaged(u8) = .{},
11
12pub const VerifyError = Certificate.Parsed.VerifyError || error{
13 CertificateIssuerNotFound,
14};
15
16pub fn verify(cb: Bundle, subject: Certificate.Parsed, now_sec: i64) VerifyError!void {
17 const bytes_index = cb.find(subject.issuer()) orelse return error.CertificateIssuerNotFound;
18 const issuer_cert: Certificate = .{
19 .buffer = cb.bytes.items,
20 .index = bytes_index,
21 };
22 // Every certificate in the bundle is pre-parsed before adding it, ensuring
23 // that parsing will succeed here.
24 const issuer = issuer_cert.parse() catch unreachable;
25 try subject.verify(issuer, now_sec);
26}
27
28/// The returned bytes become invalid after calling any of the rescan functions
29/// or add functions.
30pub fn find(cb: Bundle, subject_name: []const u8) ?u32 {
31 const Adapter = struct {
32 cb: Bundle,
33
34 pub fn hash(ctx: @This(), k: []const u8) u64 {
35 _ = ctx;
36 return std.hash_map.hashString(k);
37 }
38
39 pub fn eql(ctx: @This(), a: []const u8, b_key: der.Element.Slice) bool {
40 const b = ctx.cb.bytes.items[b_key.start..b_key.end];
41 return mem.eql(u8, a, b);
42 }
43 };
44 return cb.map.getAdapted(subject_name, Adapter{ .cb = cb });
45}
46
47pub fn deinit(cb: *Bundle, gpa: Allocator) void {
48 cb.map.deinit(gpa);
49 cb.bytes.deinit(gpa);
50 cb.* = undefined;
51}
52
53/// Clears the set of certificates and then scans the host operating system
54/// file system standard locations for certificates.
55/// For operating systems that do not have standard CA installations to be
56/// found, this function clears the set of certificates.
57pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
58 switch (builtin.os.tag) {
59 .linux => return rescanLinux(cb, gpa),
60 .windows => {
61 // TODO
62 },
63 .macos => {
64 // TODO
65 },
66 else => {},
67 }
68}
69
70pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
71 var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {
72 error.FileNotFound => return,
73 else => |e| return e,
74 };
75 defer dir.close();
76
77 cb.bytes.clearRetainingCapacity();
78 cb.map.clearRetainingCapacity();
79
80 var it = dir.iterate();
81 while (try it.next()) |entry| {
82 switch (entry.kind) {
83 .File, .SymLink => {},
84 else => continue,
85 }
86
87 try addCertsFromFile(cb, gpa, dir.dir, entry.name);
88 }
89
90 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
91}
92
93pub fn addCertsFromFile(
94 cb: *Bundle,
95 gpa: Allocator,
96 dir: fs.Dir,
97 sub_file_path: []const u8,
98) !void {
99 var file = try dir.openFile(sub_file_path, .{});
100 defer file.close();
101
102 const size = try file.getEndPos();
103
104 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
105 // This is possible by computing the decoded length and reserving the space
106 // for the decoded bytes first.
107 const decoded_size_upper_bound = size / 4 * 3;
108 const needed_capacity = std.math.cast(u32, decoded_size_upper_bound + size) orelse
109 return error.CertificateAuthorityBundleTooBig;
110 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
111 const end_reserved = @intCast(u32, cb.bytes.items.len + decoded_size_upper_bound);
112 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
113 const end_index = try file.readAll(buffer);
114 const encoded_bytes = buffer[0..end_index];
115
116 const begin_marker = "-----BEGIN CERTIFICATE-----";
117 const end_marker = "-----END CERTIFICATE-----";
118
119 const now_sec = std.time.timestamp();
120
121 var start_index: usize = 0;
122 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
123 const cert_start = begin_marker_start + begin_marker.len;
124 const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
125 return error.MissingEndCertificateMarker;
126 start_index = cert_end + end_marker.len;
127 const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
128 const decoded_start = @intCast(u32, cb.bytes.items.len);
129 const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
130 cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
131 // Even though we could only partially parse the certificate to find
132 // the subject name, we pre-parse all of them to make sure and only
133 // include in the bundle ones that we know will parse. This way we can
134 // use `catch unreachable` later.
135 const parsed_cert = try Certificate.parse(.{
136 .buffer = cb.bytes.items,
137 .index = decoded_start,
138 });
139 if (now_sec > parsed_cert.validity.not_after) {
140 // Ignore expired cert.
141 cb.bytes.items.len = decoded_start;
142 continue;
143 }
144 const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb });
145 if (gop.found_existing) {
146 cb.bytes.items.len = decoded_start;
147 } else {
148 gop.value_ptr.* = decoded_start;
149 }
150 }
151}
152
153const builtin = @import("builtin");
154const std = @import("../../std.zig");
155const fs = std.fs;
156const mem = std.mem;
157const crypto = std.crypto;
158const Allocator = std.mem.Allocator;
159const Certificate = std.crypto.Certificate;
160const der = Certificate.der;
161const Bundle = @This();
162
163const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
164
165const MapContext = struct {
166 cb: *const Bundle,
167
168 pub fn hash(ctx: MapContext, k: der.Element.Slice) u64 {
169 return std.hash_map.hashString(ctx.cb.bytes.items[k.start..k.end]);
170 }
171
172 pub fn eql(ctx: MapContext, a: der.Element.Slice, b: der.Element.Slice) bool {
173 const bytes = ctx.cb.bytes.items;
174 return mem.eql(
175 u8,
176 bytes[a.start..a.end],
177 bytes[b.start..b.end],
178 );
179 }
180};
181
182test "scan for OS-provided certificates" {
183 if (builtin.os.tag == .wasi) return error.SkipZigTest;
184
185 var bundle: Bundle = .{};
186 defer bundle.deinit(std.testing.allocator);
187
188 try bundle.rescan(std.testing.allocator);
189}
lib/std/crypto/aegis.zig+2-2
...@@ -174,7 +174,7 @@ pub const Aegis128L = struct {...@@ -174,7 +174,7 @@ pub const Aegis128L = struct {
174 acc |= (computed_tag[j] ^ tag[j]);174 acc |= (computed_tag[j] ^ tag[j]);
175 }175 }
176 if (acc != 0) {176 if (acc != 0) {
177 mem.set(u8, m, 0xaa);177 @memset(m.ptr, undefined, m.len);
178 return error.AuthenticationFailed;178 return error.AuthenticationFailed;
179 }179 }
180 }180 }
...@@ -343,7 +343,7 @@ pub const Aegis256 = struct {...@@ -343,7 +343,7 @@ pub const Aegis256 = struct {
343 acc |= (computed_tag[j] ^ tag[j]);343 acc |= (computed_tag[j] ^ tag[j]);
344 }344 }
345 if (acc != 0) {345 if (acc != 0) {
346 mem.set(u8, m, 0xaa);346 @memset(m.ptr, undefined, m.len);
347 return error.AuthenticationFailed;347 return error.AuthenticationFailed;
348 }348 }
349 }349 }
lib/std/crypto/aes_gcm.zig+1-1
...@@ -91,7 +91,7 @@ fn AesGcm(comptime Aes: anytype) type {...@@ -91,7 +91,7 @@ fn AesGcm(comptime Aes: anytype) type {
91 acc |= (computed_tag[p] ^ tag[p]);91 acc |= (computed_tag[p] ^ tag[p]);
92 }92 }
93 if (acc != 0) {93 if (acc != 0) {
94 mem.set(u8, m, 0xaa);94 @memset(m.ptr, undefined, m.len);
95 return error.AuthenticationFailed;95 return error.AuthenticationFailed;
96 }96 }
9797
lib/std/crypto/sha2.zig+22
...@@ -142,6 +142,11 @@ fn Sha2x32(comptime params: Sha2Params32) type {...@@ -142,6 +142,11 @@ fn Sha2x32(comptime params: Sha2Params32) type {
142 d.total_len += b.len;142 d.total_len += b.len;
143 }143 }
144144
145 pub fn peek(d: Self) [digest_length]u8 {
146 var copy = d;
147 return copy.finalResult();
148 }
149
145 pub fn final(d: *Self, out: *[digest_length]u8) void {150 pub fn final(d: *Self, out: *[digest_length]u8) void {
146 // The buffer here will never be completely full.151 // The buffer here will never be completely full.
147 mem.set(u8, d.buf[d.buf_len..], 0);152 mem.set(u8, d.buf[d.buf_len..], 0);
...@@ -175,6 +180,12 @@ fn Sha2x32(comptime params: Sha2Params32) type {...@@ -175,6 +180,12 @@ fn Sha2x32(comptime params: Sha2Params32) type {
175 }180 }
176 }181 }
177182
183 pub fn finalResult(d: *Self) [digest_length]u8 {
184 var result: [digest_length]u8 = undefined;
185 d.final(&result);
186 return result;
187 }
188
178 const W = [64]u32{189 const W = [64]u32{
179 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,190 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
180 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,191 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
...@@ -621,6 +632,11 @@ fn Sha2x64(comptime params: Sha2Params64) type {...@@ -621,6 +632,11 @@ fn Sha2x64(comptime params: Sha2Params64) type {
621 d.total_len += b.len;632 d.total_len += b.len;
622 }633 }
623634
635 pub fn peek(d: Self) [digest_length]u8 {
636 var copy = d;
637 return copy.finalResult();
638 }
639
624 pub fn final(d: *Self, out: *[digest_length]u8) void {640 pub fn final(d: *Self, out: *[digest_length]u8) void {
625 // The buffer here will never be completely full.641 // The buffer here will never be completely full.
626 mem.set(u8, d.buf[d.buf_len..], 0);642 mem.set(u8, d.buf[d.buf_len..], 0);
...@@ -654,6 +670,12 @@ fn Sha2x64(comptime params: Sha2Params64) type {...@@ -654,6 +670,12 @@ fn Sha2x64(comptime params: Sha2Params64) type {
654 }670 }
655 }671 }
656672
673 pub fn finalResult(d: *Self) [digest_length]u8 {
674 var result: [digest_length]u8 = undefined;
675 d.final(&result);
676 return result;
677 }
678
657 fn round(d: *Self, b: *const [128]u8) void {679 fn round(d: *Self, b: *const [128]u8) void {
658 var s: [80]u64 = undefined;680 var s: [80]u64 = undefined;
659681
lib/std/crypto/tls.zig created+494
...@@ -0,0 +1,494 @@
1//! Plaintext:
2//! * type: ContentType
3//! * legacy_record_version: u16 = 0x0303,
4//! * length: u16,
5//! - The length (in bytes) of the following TLSPlaintext.fragment. The
6//! length MUST NOT exceed 2^14 bytes.
7//! * fragment: opaque
8//! - the data being transmitted
9//!
10//! Ciphertext
11//! * ContentType opaque_type = application_data; /* 23 */
12//! * ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
13//! * uint16 length;
14//! * opaque encrypted_record[TLSCiphertext.length];
15//!
16//! Handshake:
17//! * type: HandshakeType
18//! * length: u24
19//! * data: opaque
20//!
21//! ServerHello:
22//! * ProtocolVersion legacy_version = 0x0303;
23//! * Random random;
24//! * opaque legacy_session_id_echo<0..32>;
25//! * CipherSuite cipher_suite;
26//! * uint8 legacy_compression_method = 0;
27//! * Extension extensions<6..2^16-1>;
28//!
29//! Extension:
30//! * ExtensionType extension_type;
31//! * opaque extension_data<0..2^16-1>;
32
33const std = @import("../std.zig");
34const Tls = @This();
35const net = std.net;
36const mem = std.mem;
37const crypto = std.crypto;
38const assert = std.debug.assert;
39
40pub const Client = @import("tls/Client.zig");
41
42pub const record_header_len = 5;
43pub const max_ciphertext_len = (1 << 14) + 256;
44pub const max_ciphertext_record_len = max_ciphertext_len + record_header_len;
45pub const hello_retry_request_sequence = [32]u8{
46 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
47 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C,
48};
49
50pub const close_notify_alert = [_]u8{
51 @enumToInt(AlertLevel.warning),
52 @enumToInt(AlertDescription.close_notify),
53};
54
55pub const ProtocolVersion = enum(u16) {
56 tls_1_2 = 0x0303,
57 tls_1_3 = 0x0304,
58 _,
59};
60
61pub const ContentType = enum(u8) {
62 invalid = 0,
63 change_cipher_spec = 20,
64 alert = 21,
65 handshake = 22,
66 application_data = 23,
67 _,
68};
69
70pub const HandshakeType = enum(u8) {
71 client_hello = 1,
72 server_hello = 2,
73 new_session_ticket = 4,
74 end_of_early_data = 5,
75 encrypted_extensions = 8,
76 certificate = 11,
77 certificate_request = 13,
78 certificate_verify = 15,
79 finished = 20,
80 key_update = 24,
81 message_hash = 254,
82 _,
83};
84
85pub const ExtensionType = enum(u16) {
86 /// RFC 6066
87 server_name = 0,
88 /// RFC 6066
89 max_fragment_length = 1,
90 /// RFC 6066
91 status_request = 5,
92 /// RFC 8422, 7919
93 supported_groups = 10,
94 /// RFC 8446
95 signature_algorithms = 13,
96 /// RFC 5764
97 use_srtp = 14,
98 /// RFC 6520
99 heartbeat = 15,
100 /// RFC 7301
101 application_layer_protocol_negotiation = 16,
102 /// RFC 6962
103 signed_certificate_timestamp = 18,
104 /// RFC 7250
105 client_certificate_type = 19,
106 /// RFC 7250
107 server_certificate_type = 20,
108 /// RFC 7685
109 padding = 21,
110 /// RFC 8446
111 pre_shared_key = 41,
112 /// RFC 8446
113 early_data = 42,
114 /// RFC 8446
115 supported_versions = 43,
116 /// RFC 8446
117 cookie = 44,
118 /// RFC 8446
119 psk_key_exchange_modes = 45,
120 /// RFC 8446
121 certificate_authorities = 47,
122 /// RFC 8446
123 oid_filters = 48,
124 /// RFC 8446
125 post_handshake_auth = 49,
126 /// RFC 8446
127 signature_algorithms_cert = 50,
128 /// RFC 8446
129 key_share = 51,
130
131 _,
132};
133
134pub const AlertLevel = enum(u8) {
135 warning = 1,
136 fatal = 2,
137 _,
138};
139
140pub const AlertDescription = enum(u8) {
141 close_notify = 0,
142 unexpected_message = 10,
143 bad_record_mac = 20,
144 record_overflow = 22,
145 handshake_failure = 40,
146 bad_certificate = 42,
147 unsupported_certificate = 43,
148 certificate_revoked = 44,
149 certificate_expired = 45,
150 certificate_unknown = 46,
151 illegal_parameter = 47,
152 unknown_ca = 48,
153 access_denied = 49,
154 decode_error = 50,
155 decrypt_error = 51,
156 protocol_version = 70,
157 insufficient_security = 71,
158 internal_error = 80,
159 inappropriate_fallback = 86,
160 user_canceled = 90,
161 missing_extension = 109,
162 unsupported_extension = 110,
163 unrecognized_name = 112,
164 bad_certificate_status_response = 113,
165 unknown_psk_identity = 115,
166 certificate_required = 116,
167 no_application_protocol = 120,
168 _,
169};
170
171pub const SignatureScheme = enum(u16) {
172 // RSASSA-PKCS1-v1_5 algorithms
173 rsa_pkcs1_sha256 = 0x0401,
174 rsa_pkcs1_sha384 = 0x0501,
175 rsa_pkcs1_sha512 = 0x0601,
176
177 // ECDSA algorithms
178 ecdsa_secp256r1_sha256 = 0x0403,
179 ecdsa_secp384r1_sha384 = 0x0503,
180 ecdsa_secp521r1_sha512 = 0x0603,
181
182 // RSASSA-PSS algorithms with public key OID rsaEncryption
183 rsa_pss_rsae_sha256 = 0x0804,
184 rsa_pss_rsae_sha384 = 0x0805,
185 rsa_pss_rsae_sha512 = 0x0806,
186
187 // EdDSA algorithms
188 ed25519 = 0x0807,
189 ed448 = 0x0808,
190
191 // RSASSA-PSS algorithms with public key OID RSASSA-PSS
192 rsa_pss_pss_sha256 = 0x0809,
193 rsa_pss_pss_sha384 = 0x080a,
194 rsa_pss_pss_sha512 = 0x080b,
195
196 // Legacy algorithms
197 rsa_pkcs1_sha1 = 0x0201,
198 ecdsa_sha1 = 0x0203,
199
200 _,
201};
202
203pub const NamedGroup = enum(u16) {
204 // Elliptic Curve Groups (ECDHE)
205 secp256r1 = 0x0017,
206 secp384r1 = 0x0018,
207 secp521r1 = 0x0019,
208 x25519 = 0x001D,
209 x448 = 0x001E,
210
211 // Finite Field Groups (DHE)
212 ffdhe2048 = 0x0100,
213 ffdhe3072 = 0x0101,
214 ffdhe4096 = 0x0102,
215 ffdhe6144 = 0x0103,
216 ffdhe8192 = 0x0104,
217
218 _,
219};
220
221pub const CipherSuite = enum(u16) {
222 AES_128_GCM_SHA256 = 0x1301,
223 AES_256_GCM_SHA384 = 0x1302,
224 CHACHA20_POLY1305_SHA256 = 0x1303,
225 AES_128_CCM_SHA256 = 0x1304,
226 AES_128_CCM_8_SHA256 = 0x1305,
227 AEGIS_256_SHA384 = 0x1306,
228 AEGIS_128L_SHA256 = 0x1307,
229 _,
230};
231
232pub const CertificateType = enum(u8) {
233 X509 = 0,
234 RawPublicKey = 2,
235 _,
236};
237
238pub const KeyUpdateRequest = enum(u8) {
239 update_not_requested = 0,
240 update_requested = 1,
241 _,
242};
243
244pub fn HandshakeCipherT(comptime AeadType: type, comptime HashType: type) type {
245 return struct {
246 pub const AEAD = AeadType;
247 pub const Hash = HashType;
248 pub const Hmac = crypto.auth.hmac.Hmac(Hash);
249 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
250
251 handshake_secret: [Hkdf.prk_length]u8,
252 master_secret: [Hkdf.prk_length]u8,
253 client_handshake_key: [AEAD.key_length]u8,
254 server_handshake_key: [AEAD.key_length]u8,
255 client_finished_key: [Hmac.key_length]u8,
256 server_finished_key: [Hmac.key_length]u8,
257 client_handshake_iv: [AEAD.nonce_length]u8,
258 server_handshake_iv: [AEAD.nonce_length]u8,
259 transcript_hash: Hash,
260 };
261}
262
263pub const HandshakeCipher = union(enum) {
264 AES_128_GCM_SHA256: HandshakeCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
265 AES_256_GCM_SHA384: HandshakeCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
266 CHACHA20_POLY1305_SHA256: HandshakeCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
267 AEGIS_256_SHA384: HandshakeCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha384),
268 AEGIS_128L_SHA256: HandshakeCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
269};
270
271pub fn ApplicationCipherT(comptime AeadType: type, comptime HashType: type) type {
272 return struct {
273 pub const AEAD = AeadType;
274 pub const Hash = HashType;
275 pub const Hmac = crypto.auth.hmac.Hmac(Hash);
276 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
277
278 client_secret: [Hash.digest_length]u8,
279 server_secret: [Hash.digest_length]u8,
280 client_key: [AEAD.key_length]u8,
281 server_key: [AEAD.key_length]u8,
282 client_iv: [AEAD.nonce_length]u8,
283 server_iv: [AEAD.nonce_length]u8,
284 };
285}
286
287/// Encryption parameters for application traffic.
288pub const ApplicationCipher = union(enum) {
289 AES_128_GCM_SHA256: ApplicationCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
290 AES_256_GCM_SHA384: ApplicationCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
291 CHACHA20_POLY1305_SHA256: ApplicationCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
292 AEGIS_256_SHA384: ApplicationCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha384),
293 AEGIS_128L_SHA256: ApplicationCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
294};
295
296pub fn hkdfExpandLabel(
297 comptime Hkdf: type,
298 key: [Hkdf.prk_length]u8,
299 label: []const u8,
300 context: []const u8,
301 comptime len: usize,
302) [len]u8 {
303 const max_label_len = 255;
304 const max_context_len = 255;
305 const tls13 = "tls13 ";
306 var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined;
307 mem.writeIntBig(u16, buf[0..2], len);
308 buf[2] = @intCast(u8, tls13.len + label.len);
309 buf[3..][0..tls13.len].* = tls13.*;
310 var i: usize = 3 + tls13.len;
311 mem.copy(u8, buf[i..], label);
312 i += label.len;
313 buf[i] = @intCast(u8, context.len);
314 i += 1;
315 mem.copy(u8, buf[i..], context);
316 i += context.len;
317
318 var result: [len]u8 = undefined;
319 Hkdf.expand(&result, buf[0..i], key);
320 return result;
321}
322
323pub fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 {
324 var result: [Hash.digest_length]u8 = undefined;
325 Hash.hash(&.{}, &result, .{});
326 return result;
327}
328
329pub fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8) [Hmac.mac_length]u8 {
330 var result: [Hmac.mac_length]u8 = undefined;
331 Hmac.create(&result, message, &key);
332 return result;
333}
334
335pub inline fn extension(comptime et: ExtensionType, bytes: anytype) [2 + 2 + bytes.len]u8 {
336 return int2(@enumToInt(et)) ++ array(1, bytes);
337}
338
339pub inline fn array(comptime elem_size: comptime_int, bytes: anytype) [2 + bytes.len]u8 {
340 comptime assert(bytes.len % elem_size == 0);
341 return int2(bytes.len) ++ bytes;
342}
343
344pub inline fn enum_array(comptime E: type, comptime tags: []const E) [2 + @sizeOf(E) * tags.len]u8 {
345 assert(@sizeOf(E) == 2);
346 var result: [tags.len * 2]u8 = undefined;
347 for (tags) |elem, i| {
348 result[i * 2] = @truncate(u8, @enumToInt(elem) >> 8);
349 result[i * 2 + 1] = @truncate(u8, @enumToInt(elem));
350 }
351 return array(2, result);
352}
353
354pub inline fn int2(x: u16) [2]u8 {
355 return .{
356 @truncate(u8, x >> 8),
357 @truncate(u8, x),
358 };
359}
360
361pub inline fn int3(x: u24) [3]u8 {
362 return .{
363 @truncate(u8, x >> 16),
364 @truncate(u8, x >> 8),
365 @truncate(u8, x),
366 };
367}
368
369/// An abstraction to ensure that protocol-parsing code does not perform an
370/// out-of-bounds read.
371pub const Decoder = struct {
372 buf: []u8,
373 /// Points to the next byte in buffer that will be decoded.
374 idx: usize = 0,
375 /// Up to this point in `buf` we have already checked that `cap` is greater than it.
376 our_end: usize = 0,
377 /// Beyond this point in `buf` is extra tag-along bytes beyond the amount we
378 /// requested with `readAtLeast`.
379 their_end: usize = 0,
380 /// Points to the end within buffer that has been filled. Beyond this point
381 /// in buf is undefined bytes.
382 cap: usize = 0,
383 /// Debug helper to prevent illegal calls to read functions.
384 disable_reads: bool = false,
385
386 pub fn fromTheirSlice(buf: []u8) Decoder {
387 return .{
388 .buf = buf,
389 .their_end = buf.len,
390 .cap = buf.len,
391 .disable_reads = true,
392 };
393 }
394
395 /// Use this function to increase `their_end`.
396 pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void {
397 assert(!d.disable_reads);
398 const existing_amt = d.cap - d.idx;
399 d.their_end = d.idx + their_amt;
400 if (their_amt <= existing_amt) return;
401 const request_amt = their_amt - existing_amt;
402 const dest = d.buf[d.cap..];
403 if (request_amt > dest.len) return error.TlsRecordOverflow;
404 const actual_amt = try stream.readAtLeast(dest, request_amt);
405 if (actual_amt < request_amt) return error.TlsConnectionTruncated;
406 d.cap += actual_amt;
407 }
408
409 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.
410 /// Use when `our_amt` is calculated by us, not by them.
411 pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void {
412 assert(!d.disable_reads);
413 try readAtLeast(d, stream, our_amt);
414 d.our_end = d.idx + our_amt;
415 }
416
417 /// Use this function to increase `our_end`.
418 /// This should always be called with an amount provided by us, not them.
419 pub fn ensure(d: *Decoder, amt: usize) !void {
420 d.our_end = @max(d.idx + amt, d.our_end);
421 if (d.our_end > d.their_end) return error.TlsDecodeError;
422 }
423
424 /// Use this function to increase `idx`.
425 pub fn decode(d: *Decoder, comptime T: type) T {
426 switch (@typeInfo(T)) {
427 .Int => |info| switch (info.bits) {
428 8 => {
429 skip(d, 1);
430 return d.buf[d.idx - 1];
431 },
432 16 => {
433 skip(d, 2);
434 const b0: u16 = d.buf[d.idx - 2];
435 const b1: u16 = d.buf[d.idx - 1];
436 return (b0 << 8) | b1;
437 },
438 24 => {
439 skip(d, 3);
440 const b0: u24 = d.buf[d.idx - 3];
441 const b1: u24 = d.buf[d.idx - 2];
442 const b2: u24 = d.buf[d.idx - 1];
443 return (b0 << 16) | (b1 << 8) | b2;
444 },
445 else => @compileError("unsupported int type: " ++ @typeName(T)),
446 },
447 .Enum => |info| {
448 const int = d.decode(info.tag_type);
449 if (info.is_exhaustive) @compileError("exhaustive enum cannot be used");
450 return @intToEnum(T, int);
451 },
452 else => @compileError("unsupported type: " ++ @typeName(T)),
453 }
454 }
455
456 /// Use this function to increase `idx`.
457 pub fn array(d: *Decoder, comptime len: usize) *[len]u8 {
458 skip(d, len);
459 return d.buf[d.idx - len ..][0..len];
460 }
461
462 /// Use this function to increase `idx`.
463 pub fn slice(d: *Decoder, len: usize) []u8 {
464 skip(d, len);
465 return d.buf[d.idx - len ..][0..len];
466 }
467
468 /// Use this function to increase `idx`.
469 pub fn skip(d: *Decoder, amt: usize) void {
470 d.idx += amt;
471 assert(d.idx <= d.our_end); // insufficient ensured bytes
472 }
473
474 pub fn eof(d: Decoder) bool {
475 assert(d.our_end <= d.their_end);
476 assert(d.idx <= d.our_end);
477 return d.idx == d.their_end;
478 }
479
480 /// Provide the length they claim, and receive a sub-decoder specific to that slice.
481 /// The parent decoder is advanced to the end.
482 pub fn sub(d: *Decoder, their_len: usize) !Decoder {
483 const end = d.idx + their_len;
484 if (end > d.their_end) return error.TlsDecodeError;
485 const sub_buf = d.buf[d.idx..end];
486 d.idx = end;
487 d.our_end = end;
488 return fromTheirSlice(sub_buf);
489 }
490
491 pub fn rest(d: Decoder) []u8 {
492 return d.buf[d.idx..d.cap];
493 }
494};
lib/std/crypto/tls/Client.zig created+1308
...@@ -0,0 +1,1308 @@
1const std = @import("../../std.zig");
2const tls = std.crypto.tls;
3const Client = @This();
4const net = std.net;
5const mem = std.mem;
6const crypto = std.crypto;
7const assert = std.debug.assert;
8const Certificate = std.crypto.Certificate;
9
10const max_ciphertext_len = tls.max_ciphertext_len;
11const hkdfExpandLabel = tls.hkdfExpandLabel;
12const int2 = tls.int2;
13const int3 = tls.int3;
14const array = tls.array;
15const enum_array = tls.enum_array;
16
17read_seq: u64,
18write_seq: u64,
19/// The starting index of cleartext bytes inside `partially_read_buffer`.
20partial_cleartext_idx: u15,
21/// The ending index of cleartext bytes inside `partially_read_buffer` as well
22/// as the starting index of ciphertext bytes.
23partial_ciphertext_idx: u15,
24/// The ending index of ciphertext bytes inside `partially_read_buffer`.
25partial_ciphertext_end: u15,
26/// When this is true, the stream may still not be at the end because there
27/// may be data in `partially_read_buffer`.
28received_close_notify: bool,
29/// By default, reaching the end-of-stream when reading from the server will
30/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
31/// message has been received. By setting this flag to `true`, instead, the
32/// end-of-stream will be forwarded to the application layer above TLS.
33/// This makes the application vulnerable to truncation attacks unless the
34/// application layer itself verifies that the amount of data received equals
35/// the amount of data expected, such as HTTP with the Content-Length header.
36allow_truncation_attacks: bool = false,
37application_cipher: tls.ApplicationCipher,
38/// The size is enough to contain exactly one TLSCiphertext record.
39/// This buffer is segmented into four parts:
40/// 0. unused
41/// 1. cleartext
42/// 2. ciphertext
43/// 3. unused
44/// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and
45/// `partial_ciphertext_end` describe the span of the segments.
46partially_read_buffer: [tls.max_ciphertext_record_len]u8,
47
48/// This is an example of the type that is needed by the read and write
49/// functions. It can have any fields but it must at least have these
50/// functions.
51///
52/// Note that `std.net.Stream` conforms to this interface.
53///
54/// This declaration serves as documentation only.
55pub const StreamInterface = struct {
56 /// Can be any error set.
57 pub const ReadError = error{};
58
59 /// Returns the number of bytes read. The number read may be less than the
60 /// buffer space provided. End-of-stream is indicated by a return value of 0.
61 ///
62 /// The `iovecs` parameter is mutable because so that function may to
63 /// mutate the fields in order to handle partial reads from the underlying
64 /// stream layer.
65 pub fn readv(this: @This(), iovecs: []std.os.iovec) ReadError!usize {
66 _ = .{ this, iovecs };
67 @panic("unimplemented");
68 }
69
70 /// Can be any error set.
71 pub const WriteError = error{};
72
73 /// Returns the number of bytes read, which may be less than the buffer
74 /// space provided. A short read does not indicate end-of-stream.
75 pub fn writev(this: @This(), iovecs: []const std.os.iovec_const) WriteError!usize {
76 _ = .{ this, iovecs };
77 @panic("unimplemented");
78 }
79
80 /// Returns the number of bytes read, which may be less than the buffer
81 /// space provided, indicating end-of-stream.
82 /// The `iovecs` parameter is mutable in case this function needs to mutate
83 /// the fields in order to handle partial writes from the underlying layer.
84 pub fn writevAll(this: @This(), iovecs: []std.os.iovec_const) WriteError!usize {
85 // This can be implemented in terms of writev, or specialized if desired.
86 _ = .{ this, iovecs };
87 @panic("unimplemented");
88 }
89};
90
91/// Initiates a TLS handshake and establishes a TLSv1.3 session with `stream`, which
92/// must conform to `StreamInterface`.
93///
94/// `host` is only borrowed during this function call.
95pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) !Client {
96 const host_len = @intCast(u16, host.len);
97
98 var random_buffer: [128]u8 = undefined;
99 crypto.random.bytes(&random_buffer);
100 const hello_rand = random_buffer[0..32].*;
101 const legacy_session_id = random_buffer[32..64].*;
102 const x25519_kp_seed = random_buffer[64..96].*;
103 const secp256r1_kp_seed = random_buffer[96..128].*;
104
105 const x25519_kp = crypto.dh.X25519.KeyPair.create(x25519_kp_seed) catch |err| switch (err) {
106 // Only possible to happen if the private key is all zeroes.
107 error.IdentityElement => return error.InsufficientEntropy,
108 };
109 const secp256r1_kp = crypto.sign.ecdsa.EcdsaP256Sha256.KeyPair.create(secp256r1_kp_seed) catch |err| switch (err) {
110 // Only possible to happen if the private key is all zeroes.
111 error.IdentityElement => return error.InsufficientEntropy,
112 };
113
114 const extensions_payload =
115 tls.extension(.supported_versions, [_]u8{
116 0x02, // byte length of supported versions
117 0x03, 0x04, // TLS 1.3
118 }) ++ tls.extension(.signature_algorithms, enum_array(tls.SignatureScheme, &.{
119 .ecdsa_secp256r1_sha256,
120 .ecdsa_secp384r1_sha384,
121 .ecdsa_secp521r1_sha512,
122 .rsa_pss_rsae_sha256,
123 .rsa_pss_rsae_sha384,
124 .rsa_pss_rsae_sha512,
125 .rsa_pkcs1_sha256,
126 .rsa_pkcs1_sha384,
127 .rsa_pkcs1_sha512,
128 .ed25519,
129 })) ++ tls.extension(.supported_groups, enum_array(tls.NamedGroup, &.{
130 .secp256r1,
131 .x25519,
132 })) ++ tls.extension(
133 .key_share,
134 array(1, int2(@enumToInt(tls.NamedGroup.x25519)) ++
135 array(1, x25519_kp.public_key) ++
136 int2(@enumToInt(tls.NamedGroup.secp256r1)) ++
137 array(1, secp256r1_kp.public_key.toUncompressedSec1())),
138 ) ++
139 int2(@enumToInt(tls.ExtensionType.server_name)) ++
140 int2(host_len + 5) ++ // byte length of this extension payload
141 int2(host_len + 3) ++ // server_name_list byte count
142 [1]u8{0x00} ++ // name_type
143 int2(host_len);
144
145 const extensions_header =
146 int2(@intCast(u16, extensions_payload.len + host_len)) ++
147 extensions_payload;
148
149 const legacy_compression_methods = 0x0100;
150
151 const client_hello =
152 int2(@enumToInt(tls.ProtocolVersion.tls_1_2)) ++
153 hello_rand ++
154 [1]u8{32} ++ legacy_session_id ++
155 cipher_suites ++
156 int2(legacy_compression_methods) ++
157 extensions_header;
158
159 const out_handshake =
160 [_]u8{@enumToInt(tls.HandshakeType.client_hello)} ++
161 int3(@intCast(u24, client_hello.len + host_len)) ++
162 client_hello;
163
164 const plaintext_header = [_]u8{
165 @enumToInt(tls.ContentType.handshake),
166 0x03, 0x01, // legacy_record_version
167 } ++ int2(@intCast(u16, out_handshake.len + host_len)) ++ out_handshake;
168
169 {
170 var iovecs = [_]std.os.iovec_const{
171 .{
172 .iov_base = &plaintext_header,
173 .iov_len = plaintext_header.len,
174 },
175 .{
176 .iov_base = host.ptr,
177 .iov_len = host.len,
178 },
179 };
180 try stream.writevAll(&iovecs);
181 }
182
183 const client_hello_bytes1 = plaintext_header[5..];
184
185 var handshake_cipher: tls.HandshakeCipher = undefined;
186 var handshake_buffer: [8000]u8 = undefined;
187 var d: tls.Decoder = .{ .buf = &handshake_buffer };
188 {
189 try d.readAtLeastOurAmt(stream, tls.record_header_len);
190 const ct = d.decode(tls.ContentType);
191 d.skip(2); // legacy_record_version
192 const record_len = d.decode(u16);
193 try d.readAtLeast(stream, record_len);
194 const server_hello_fragment = d.buf[d.idx..][0..record_len];
195 var ptd = try d.sub(record_len);
196 switch (ct) {
197 .alert => {
198 try ptd.ensure(2);
199 const level = ptd.decode(tls.AlertLevel);
200 const desc = ptd.decode(tls.AlertDescription);
201 _ = level;
202 _ = desc;
203 return error.TlsAlert;
204 },
205 .handshake => {
206 try ptd.ensure(4);
207 const handshake_type = ptd.decode(tls.HandshakeType);
208 if (handshake_type != .server_hello) return error.TlsUnexpectedMessage;
209 const length = ptd.decode(u24);
210 var hsd = try ptd.sub(length);
211 try hsd.ensure(2 + 32 + 1 + 32 + 2 + 1 + 2);
212 const legacy_version = hsd.decode(u16);
213 const random = hsd.array(32);
214 if (mem.eql(u8, random, &tls.hello_retry_request_sequence)) {
215 // This is a HelloRetryRequest message. This client implementation
216 // does not expect to get one.
217 return error.TlsUnexpectedMessage;
218 }
219 const legacy_session_id_echo_len = hsd.decode(u8);
220 if (legacy_session_id_echo_len != 32) return error.TlsIllegalParameter;
221 const legacy_session_id_echo = hsd.array(32);
222 if (!mem.eql(u8, legacy_session_id_echo, &legacy_session_id))
223 return error.TlsIllegalParameter;
224 const cipher_suite_tag = hsd.decode(tls.CipherSuite);
225 hsd.skip(1); // legacy_compression_method
226 const extensions_size = hsd.decode(u16);
227 var all_extd = try hsd.sub(extensions_size);
228 var supported_version: u16 = 0;
229 var shared_key: [32]u8 = undefined;
230 var have_shared_key = false;
231 while (!all_extd.eof()) {
232 try all_extd.ensure(2 + 2);
233 const et = all_extd.decode(tls.ExtensionType);
234 const ext_size = all_extd.decode(u16);
235 var extd = try all_extd.sub(ext_size);
236 switch (et) {
237 .supported_versions => {
238 if (supported_version != 0) return error.TlsIllegalParameter;
239 try extd.ensure(2);
240 supported_version = extd.decode(u16);
241 },
242 .key_share => {
243 if (have_shared_key) return error.TlsIllegalParameter;
244 have_shared_key = true;
245 try extd.ensure(4);
246 const named_group = extd.decode(tls.NamedGroup);
247 const key_size = extd.decode(u16);
248 try extd.ensure(key_size);
249 switch (named_group) {
250 .x25519 => {
251 if (key_size != 32) return error.TlsIllegalParameter;
252 const server_pub_key = extd.array(32);
253
254 shared_key = crypto.dh.X25519.scalarmult(
255 x25519_kp.secret_key,
256 server_pub_key.*,
257 ) catch return error.TlsDecryptFailure;
258 },
259 .secp256r1 => {
260 const server_pub_key = extd.slice(key_size);
261
262 const PublicKey = crypto.sign.ecdsa.EcdsaP256Sha256.PublicKey;
263 const pk = PublicKey.fromSec1(server_pub_key) catch {
264 return error.TlsDecryptFailure;
265 };
266 const mul = pk.p.mulPublic(secp256r1_kp.secret_key.bytes, .Big) catch {
267 return error.TlsDecryptFailure;
268 };
269 shared_key = mul.affineCoordinates().x.toBytes(.Big);
270 },
271 else => {
272 return error.TlsIllegalParameter;
273 },
274 }
275 },
276 else => {},
277 }
278 }
279 if (!have_shared_key) return error.TlsIllegalParameter;
280
281 const tls_version = if (supported_version == 0) legacy_version else supported_version;
282 if (tls_version != @enumToInt(tls.ProtocolVersion.tls_1_3))
283 return error.TlsIllegalParameter;
284
285 switch (cipher_suite_tag) {
286 inline .AES_128_GCM_SHA256,
287 .AES_256_GCM_SHA384,
288 .CHACHA20_POLY1305_SHA256,
289 .AEGIS_256_SHA384,
290 .AEGIS_128L_SHA256,
291 => |tag| {
292 const P = std.meta.TagPayloadByName(tls.HandshakeCipher, @tagName(tag));
293 handshake_cipher = @unionInit(tls.HandshakeCipher, @tagName(tag), .{
294 .handshake_secret = undefined,
295 .master_secret = undefined,
296 .client_handshake_key = undefined,
297 .server_handshake_key = undefined,
298 .client_finished_key = undefined,
299 .server_finished_key = undefined,
300 .client_handshake_iv = undefined,
301 .server_handshake_iv = undefined,
302 .transcript_hash = P.Hash.init(.{}),
303 });
304 const p = &@field(handshake_cipher, @tagName(tag));
305 p.transcript_hash.update(client_hello_bytes1); // Client Hello part 1
306 p.transcript_hash.update(host); // Client Hello part 2
307 p.transcript_hash.update(server_hello_fragment);
308 const hello_hash = p.transcript_hash.peek();
309 const zeroes = [1]u8{0} ** P.Hash.digest_length;
310 const early_secret = P.Hkdf.extract(&[1]u8{0}, &zeroes);
311 const empty_hash = tls.emptyHash(P.Hash);
312 const hs_derived_secret = hkdfExpandLabel(P.Hkdf, early_secret, "derived", &empty_hash, P.Hash.digest_length);
313 p.handshake_secret = P.Hkdf.extract(&hs_derived_secret, &shared_key);
314 const ap_derived_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "derived", &empty_hash, P.Hash.digest_length);
315 p.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
316 const client_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
317 const server_secret = hkdfExpandLabel(P.Hkdf, p.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);
318 p.client_finished_key = hkdfExpandLabel(P.Hkdf, client_secret, "finished", "", P.Hmac.key_length);
319 p.server_finished_key = hkdfExpandLabel(P.Hkdf, server_secret, "finished", "", P.Hmac.key_length);
320 p.client_handshake_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
321 p.server_handshake_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
322 p.client_handshake_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
323 p.server_handshake_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
324 },
325 else => {
326 return error.TlsIllegalParameter;
327 },
328 }
329 },
330 else => return error.TlsUnexpectedMessage,
331 }
332 }
333
334 // This is used for two purposes:
335 // * Detect whether a certificate is the first one presented, in which case
336 // we need to verify the host name.
337 // * Flip back and forth between the two cleartext buffers in order to keep
338 // the previous certificate in memory so that it can be verified by the
339 // next one.
340 var cert_index: usize = 0;
341 var read_seq: u64 = 0;
342 var prev_cert: Certificate.Parsed = undefined;
343 // Set to true once a trust chain has been established from the first
344 // certificate to a root CA.
345 const HandshakeState = enum {
346 /// In this state we expect only an encrypted_extensions message.
347 encrypted_extensions,
348 /// In this state we expect certificate messages.
349 certificate,
350 /// In this state we expect certificate or certificate_verify messages.
351 /// certificate messages are ignored since the trust chain is already
352 /// established.
353 trust_chain_established,
354 /// In this state, we expect only the finished message.
355 finished,
356 };
357 var handshake_state: HandshakeState = .encrypted_extensions;
358 var cleartext_bufs: [2][8000]u8 = undefined;
359 var main_cert_pub_key_algo: Certificate.AlgorithmCategory = undefined;
360 var main_cert_pub_key_buf: [300]u8 = undefined;
361 var main_cert_pub_key_len: u16 = undefined;
362 const now_sec = std.time.timestamp();
363
364 while (true) {
365 try d.readAtLeastOurAmt(stream, tls.record_header_len);
366 const record_header = d.buf[d.idx..][0..5];
367 const ct = d.decode(tls.ContentType);
368 d.skip(2); // legacy_version
369 const record_len = d.decode(u16);
370 try d.readAtLeast(stream, record_len);
371 var record_decoder = try d.sub(record_len);
372 switch (ct) {
373 .change_cipher_spec => {
374 try record_decoder.ensure(1);
375 if (record_decoder.decode(u8) != 0x01) return error.TlsIllegalParameter;
376 },
377 .application_data => {
378 const cleartext_buf = &cleartext_bufs[cert_index % 2];
379
380 const cleartext = switch (handshake_cipher) {
381 inline else => |*p| c: {
382 const P = @TypeOf(p.*);
383 const ciphertext_len = record_len - P.AEAD.tag_length;
384 try record_decoder.ensure(ciphertext_len + P.AEAD.tag_length);
385 const ciphertext = record_decoder.slice(ciphertext_len);
386 if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow;
387 const cleartext = cleartext_buf[0..ciphertext.len];
388 const auth_tag = record_decoder.array(P.AEAD.tag_length).*;
389 const V = @Vector(P.AEAD.nonce_length, u8);
390 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
391 const operand: V = pad ++ @bitCast([8]u8, big(read_seq));
392 read_seq += 1;
393 const nonce = @as(V, p.server_handshake_iv) ^ operand;
394 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, p.server_handshake_key) catch
395 return error.TlsBadRecordMac;
396 break :c cleartext;
397 },
398 };
399
400 const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]);
401 if (inner_ct != .handshake) return error.TlsUnexpectedMessage;
402
403 var ctd = tls.Decoder.fromTheirSlice(cleartext[0 .. cleartext.len - 1]);
404 while (true) {
405 try ctd.ensure(4);
406 const handshake_type = ctd.decode(tls.HandshakeType);
407 const handshake_len = ctd.decode(u24);
408 var hsd = try ctd.sub(handshake_len);
409 const wrapped_handshake = ctd.buf[ctd.idx - handshake_len - 4 .. ctd.idx];
410 const handshake = ctd.buf[ctd.idx - handshake_len .. ctd.idx];
411 switch (handshake_type) {
412 .encrypted_extensions => {
413 if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage;
414 handshake_state = .certificate;
415 switch (handshake_cipher) {
416 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
417 }
418 try hsd.ensure(2);
419 const total_ext_size = hsd.decode(u16);
420 var all_extd = try hsd.sub(total_ext_size);
421 while (!all_extd.eof()) {
422 try all_extd.ensure(4);
423 const et = all_extd.decode(tls.ExtensionType);
424 const ext_size = all_extd.decode(u16);
425 var extd = try all_extd.sub(ext_size);
426 _ = extd;
427 switch (et) {
428 .server_name => {},
429 else => {},
430 }
431 }
432 },
433 .certificate => cert: {
434 switch (handshake_cipher) {
435 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
436 }
437 switch (handshake_state) {
438 .certificate => {},
439 .trust_chain_established => break :cert,
440 else => return error.TlsUnexpectedMessage,
441 }
442 try hsd.ensure(1 + 4);
443 const cert_req_ctx_len = hsd.decode(u8);
444 if (cert_req_ctx_len != 0) return error.TlsIllegalParameter;
445 const certs_size = hsd.decode(u24);
446 var certs_decoder = try hsd.sub(certs_size);
447 while (!certs_decoder.eof()) {
448 try certs_decoder.ensure(3);
449 const cert_size = certs_decoder.decode(u24);
450 var certd = try certs_decoder.sub(cert_size);
451
452 const subject_cert: Certificate = .{
453 .buffer = certd.buf,
454 .index = @intCast(u32, certd.idx),
455 };
456 const subject = try subject_cert.parse();
457 if (cert_index == 0) {
458 // Verify the host on the first certificate.
459 try subject.verifyHostName(host);
460
461 // Keep track of the public key for the
462 // certificate_verify message later.
463 main_cert_pub_key_algo = subject.pub_key_algo;
464 const pub_key = subject.pubKey();
465 if (pub_key.len > main_cert_pub_key_buf.len)
466 return error.CertificatePublicKeyInvalid;
467 @memcpy(&main_cert_pub_key_buf, pub_key.ptr, pub_key.len);
468 main_cert_pub_key_len = @intCast(@TypeOf(main_cert_pub_key_len), pub_key.len);
469 } else {
470 try prev_cert.verify(subject, now_sec);
471 }
472
473 if (ca_bundle.verify(subject, now_sec)) |_| {
474 handshake_state = .trust_chain_established;
475 break :cert;
476 } else |err| switch (err) {
477 error.CertificateIssuerNotFound => {},
478 else => |e| return e,
479 }
480
481 prev_cert = subject;
482 cert_index += 1;
483
484 try certs_decoder.ensure(2);
485 const total_ext_size = certs_decoder.decode(u16);
486 var all_extd = try certs_decoder.sub(total_ext_size);
487 _ = all_extd;
488 }
489 },
490 .certificate_verify => {
491 switch (handshake_state) {
492 .trust_chain_established => handshake_state = .finished,
493 .certificate => return error.TlsCertificateNotVerified,
494 else => return error.TlsUnexpectedMessage,
495 }
496
497 try hsd.ensure(4);
498 const scheme = hsd.decode(tls.SignatureScheme);
499 const sig_len = hsd.decode(u16);
500 try hsd.ensure(sig_len);
501 const encoded_sig = hsd.slice(sig_len);
502 const max_digest_len = 64;
503 var verify_buffer =
504 ([1]u8{0x20} ** 64) ++
505 "TLS 1.3, server CertificateVerify\x00".* ++
506 @as([max_digest_len]u8, undefined);
507
508 const verify_bytes = switch (handshake_cipher) {
509 inline else => |*p| v: {
510 const transcript_digest = p.transcript_hash.peek();
511 verify_buffer[verify_buffer.len - max_digest_len ..][0..transcript_digest.len].* = transcript_digest;
512 p.transcript_hash.update(wrapped_handshake);
513 break :v verify_buffer[0 .. verify_buffer.len - max_digest_len + transcript_digest.len];
514 },
515 };
516 const main_cert_pub_key = main_cert_pub_key_buf[0..main_cert_pub_key_len];
517
518 switch (scheme) {
519 inline .ecdsa_secp256r1_sha256,
520 .ecdsa_secp384r1_sha384,
521 => |comptime_scheme| {
522 if (main_cert_pub_key_algo != .X9_62_id_ecPublicKey)
523 return error.TlsBadSignatureScheme;
524 const Ecdsa = SchemeEcdsa(comptime_scheme);
525 const sig = try Ecdsa.Signature.fromDer(encoded_sig);
526 const key = try Ecdsa.PublicKey.fromSec1(main_cert_pub_key);
527 try sig.verify(verify_bytes, key);
528 },
529 .rsa_pss_rsae_sha256 => {
530 if (main_cert_pub_key_algo != .rsaEncryption)
531 return error.TlsBadSignatureScheme;
532
533 const Hash = crypto.hash.sha2.Sha256;
534 const rsa = Certificate.rsa;
535 const components = try rsa.PublicKey.parseDer(main_cert_pub_key);
536 const exponent = components.exponent;
537 const modulus = components.modulus;
538 var rsa_mem_buf: [512 * 32]u8 = undefined;
539 var fba = std.heap.FixedBufferAllocator.init(&rsa_mem_buf);
540 const ally = fba.allocator();
541 switch (modulus.len) {
542 inline 128, 256, 512 => |modulus_len| {
543 const key = try rsa.PublicKey.fromBytes(exponent, modulus, ally);
544 const sig = rsa.PSSSignature.fromBytes(modulus_len, encoded_sig);
545 try rsa.PSSSignature.verify(modulus_len, sig, verify_bytes, key, Hash, ally);
546 },
547 else => {
548 return error.TlsBadRsaSignatureBitCount;
549 },
550 }
551 },
552 else => {
553 return error.TlsBadSignatureScheme;
554 },
555 }
556 },
557 .finished => {
558 if (handshake_state != .finished) return error.TlsUnexpectedMessage;
559 // This message is to trick buggy proxies into behaving correctly.
560 const client_change_cipher_spec_msg = [_]u8{
561 @enumToInt(tls.ContentType.change_cipher_spec),
562 0x03, 0x03, // legacy protocol version
563 0x00, 0x01, // length
564 0x01,
565 };
566 const app_cipher = switch (handshake_cipher) {
567 inline else => |*p, tag| c: {
568 const P = @TypeOf(p.*);
569 const finished_digest = p.transcript_hash.peek();
570 p.transcript_hash.update(wrapped_handshake);
571 const expected_server_verify_data = tls.hmac(P.Hmac, &finished_digest, p.server_finished_key);
572 if (!mem.eql(u8, &expected_server_verify_data, handshake))
573 return error.TlsDecryptError;
574 const handshake_hash = p.transcript_hash.finalResult();
575 const verify_data = tls.hmac(P.Hmac, &handshake_hash, p.client_finished_key);
576 const out_cleartext = [_]u8{
577 @enumToInt(tls.HandshakeType.finished),
578 0, 0, verify_data.len, // length
579 } ++ verify_data ++ [1]u8{@enumToInt(tls.ContentType.handshake)};
580
581 const wrapped_len = out_cleartext.len + P.AEAD.tag_length;
582
583 var finished_msg = [_]u8{
584 @enumToInt(tls.ContentType.application_data),
585 0x03, 0x03, // legacy protocol version
586 0, wrapped_len, // byte length of encrypted record
587 } ++ @as([wrapped_len]u8, undefined);
588
589 const ad = finished_msg[0..5];
590 const ciphertext = finished_msg[5..][0..out_cleartext.len];
591 const auth_tag = finished_msg[finished_msg.len - P.AEAD.tag_length ..];
592 const nonce = p.client_handshake_iv;
593 P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key);
594
595 const both_msgs = client_change_cipher_spec_msg ++ finished_msg;
596 try stream.writeAll(&both_msgs);
597
598 const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
599 const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);
600 break :c @unionInit(tls.ApplicationCipher, @tagName(tag), .{
601 .client_secret = client_secret,
602 .server_secret = server_secret,
603 .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length),
604 .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length),
605 .client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length),
606 .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length),
607 });
608 },
609 };
610 const leftover = d.rest();
611 var client: Client = .{
612 .read_seq = 0,
613 .write_seq = 0,
614 .partial_cleartext_idx = 0,
615 .partial_ciphertext_idx = 0,
616 .partial_ciphertext_end = @intCast(u15, leftover.len),
617 .received_close_notify = false,
618 .application_cipher = app_cipher,
619 .partially_read_buffer = undefined,
620 };
621 mem.copy(u8, &client.partially_read_buffer, leftover);
622 return client;
623 },
624 else => {
625 return error.TlsUnexpectedMessage;
626 },
627 }
628 if (ctd.eof()) break;
629 }
630 },
631 else => {
632 return error.TlsUnexpectedMessage;
633 },
634 }
635 }
636}
637
638/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
639/// Returns the number of plaintext bytes sent, which may be fewer than `bytes.len`.
640pub fn write(c: *Client, stream: anytype, bytes: []const u8) !usize {
641 return writeEnd(c, stream, bytes, false);
642}
643
644/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
645pub fn writeAll(c: *Client, stream: anytype, bytes: []const u8) !void {
646 var index: usize = 0;
647 while (index < bytes.len) {
648 index += try c.write(stream, bytes[index..]);
649 }
650}
651
652/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
653/// If `end` is true, then this function additionally sends a `close_notify` alert,
654/// which is necessary for the server to distinguish between a properly finished
655/// TLS session, or a truncation attack.
656pub fn writeAllEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !void {
657 var index: usize = 0;
658 while (index < bytes.len) {
659 index += try c.writeEnd(stream, bytes[index..], end);
660 }
661}
662
663/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
664/// Returns the number of plaintext bytes sent, which may be fewer than `bytes.len`.
665/// If `end` is true, then this function additionally sends a `close_notify` alert,
666/// which is necessary for the server to distinguish between a properly finished
667/// TLS session, or a truncation attack.
668pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize {
669 var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined;
670 var iovecs_buf: [6]std.os.iovec_const = undefined;
671 var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data);
672 if (end) {
673 prepared.iovec_end += prepareCiphertextRecord(
674 c,
675 iovecs_buf[prepared.iovec_end..],
676 ciphertext_buf[prepared.ciphertext_end..],
677 &tls.close_notify_alert,
678 .alert,
679 ).iovec_end;
680 }
681
682 const iovec_end = prepared.iovec_end;
683 const overhead_len = prepared.overhead_len;
684
685 // Ideally we would call writev exactly once here, however, we must ensure
686 // that we don't return with a record partially written.
687 var i: usize = 0;
688 var total_amt: usize = 0;
689 while (true) {
690 var amt = try stream.writev(iovecs_buf[i..iovec_end]);
691 while (amt >= iovecs_buf[i].iov_len) {
692 const encrypted_amt = iovecs_buf[i].iov_len;
693 total_amt += encrypted_amt - overhead_len;
694 amt -= encrypted_amt;
695 i += 1;
696 // Rely on the property that iovecs delineate records, meaning that
697 // if amt equals zero here, we have fortunately found ourselves
698 // with a short read that aligns at the record boundary.
699 if (i >= iovec_end) return total_amt;
700 // We also cannot return on a vector boundary if the final close_notify is
701 // not sent; otherwise the caller would not know to retry the call.
702 if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt;
703 }
704 iovecs_buf[i].iov_base += amt;
705 iovecs_buf[i].iov_len -= amt;
706 }
707}
708
709fn prepareCiphertextRecord(
710 c: *Client,
711 iovecs: []std.os.iovec_const,
712 ciphertext_buf: []u8,
713 bytes: []const u8,
714 inner_content_type: tls.ContentType,
715) struct {
716 iovec_end: usize,
717 ciphertext_end: usize,
718 /// How many bytes are taken up by overhead per record.
719 overhead_len: usize,
720} {
721 // Due to the trailing inner content type byte in the ciphertext, we need
722 // an additional buffer for storing the cleartext into before encrypting.
723 var cleartext_buf: [max_ciphertext_len]u8 = undefined;
724 var ciphertext_end: usize = 0;
725 var iovec_end: usize = 0;
726 var bytes_i: usize = 0;
727 switch (c.application_cipher) {
728 inline else => |*p| {
729 const P = @TypeOf(p.*);
730 const V = @Vector(P.AEAD.nonce_length, u8);
731 const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1;
732 const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len;
733 while (true) {
734 const encrypted_content_len = @intCast(u16, @min(
735 @min(bytes.len - bytes_i, max_ciphertext_len - 1),
736 ciphertext_buf.len - close_notify_alert_reserved -
737 overhead_len - ciphertext_end,
738 ));
739 if (encrypted_content_len == 0) return .{
740 .iovec_end = iovec_end,
741 .ciphertext_end = ciphertext_end,
742 .overhead_len = overhead_len,
743 };
744
745 mem.copy(u8, &cleartext_buf, bytes[bytes_i..][0..encrypted_content_len]);
746 cleartext_buf[encrypted_content_len] = @enumToInt(inner_content_type);
747 bytes_i += encrypted_content_len;
748 const ciphertext_len = encrypted_content_len + 1;
749 const cleartext = cleartext_buf[0..ciphertext_len];
750
751 const record_start = ciphertext_end;
752 const ad = ciphertext_buf[ciphertext_end..][0..5];
753 ad.* =
754 [_]u8{@enumToInt(tls.ContentType.application_data)} ++
755 int2(@enumToInt(tls.ProtocolVersion.tls_1_2)) ++
756 int2(ciphertext_len + P.AEAD.tag_length);
757 ciphertext_end += ad.len;
758 const ciphertext = ciphertext_buf[ciphertext_end..][0..ciphertext_len];
759 ciphertext_end += ciphertext_len;
760 const auth_tag = ciphertext_buf[ciphertext_end..][0..P.AEAD.tag_length];
761 ciphertext_end += auth_tag.len;
762 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
763 const operand: V = pad ++ @bitCast([8]u8, big(c.write_seq));
764 c.write_seq += 1; // TODO send key_update on overflow
765 const nonce = @as(V, p.client_iv) ^ operand;
766 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key);
767
768 const record = ciphertext_buf[record_start..ciphertext_end];
769 iovecs[iovec_end] = .{
770 .iov_base = record.ptr,
771 .iov_len = record.len,
772 };
773 iovec_end += 1;
774 }
775 },
776 }
777}
778
779pub fn eof(c: Client) bool {
780 return c.received_close_notify and
781 c.partial_cleartext_idx >= c.partial_ciphertext_idx and
782 c.partial_ciphertext_idx >= c.partial_ciphertext_end;
783}
784
785/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
786/// Returns the number of bytes read, calling the underlying read function the
787/// minimal number of times until the buffer has at least `len` bytes filled.
788/// If the number read is less than `len` it means the stream reached the end.
789/// Reaching the end of the stream is not an error condition.
790pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize {
791 var iovecs = [1]std.os.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }};
792 return readvAtLeast(c, stream, &iovecs, len);
793}
794
795/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
796pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize {
797 return readAtLeast(c, stream, buffer, 1);
798}
799
800/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
801/// Returns the number of bytes read. If the number read is smaller than
802/// `buffer.len`, it means the stream reached the end. Reaching the end of the
803/// stream is not an error condition.
804pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize {
805 return readAtLeast(c, stream, buffer, buffer.len);
806}
807
808/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
809/// Returns the number of bytes read. If the number read is less than the space
810/// provided it means the stream reached the end. Reaching the end of the
811/// stream is not an error condition.
812/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
813/// order to handle partial reads from the underlying stream layer.
814pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize {
815 return readvAtLeast(c, stream, iovecs);
816}
817
818/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
819/// Returns the number of bytes read, calling the underlying read function the
820/// minimal number of times until the iovecs have at least `len` bytes filled.
821/// If the number read is less than `len` it means the stream reached the end.
822/// Reaching the end of the stream is not an error condition.
823/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
824/// order to handle partial reads from the underlying stream layer.
825pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: usize) !usize {
826 if (c.eof()) return 0;
827
828 var off_i: usize = 0;
829 var vec_i: usize = 0;
830 while (true) {
831 var amt = try c.readvAdvanced(stream, iovecs[vec_i..]);
832 off_i += amt;
833 if (c.eof() or off_i >= len) return off_i;
834 while (amt >= iovecs[vec_i].iov_len) {
835 amt -= iovecs[vec_i].iov_len;
836 vec_i += 1;
837 }
838 iovecs[vec_i].iov_base += amt;
839 iovecs[vec_i].iov_len -= amt;
840 }
841}
842
843/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
844/// Returns number of bytes that have been read, populated inside `iovecs`. A
845/// return value of zero bytes does not mean end of stream. Instead, check the `eof()`
846/// for the end of stream. The `eof()` may be true after any call to
847/// `read`, including when greater than zero bytes are returned, and this
848/// function asserts that `eof()` is `false`.
849/// See `readv` for a higher level function that has the same, familiar API as
850/// other read functions, such as `std.fs.File.read`.
851pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec) !usize {
852 var vp: VecPut = .{ .iovecs = iovecs };
853
854 // Give away the buffered cleartext we have, if any.
855 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];
856 if (partial_cleartext.len > 0) {
857 const amt = @intCast(u15, vp.put(partial_cleartext));
858 c.partial_cleartext_idx += amt;
859 if (amt < partial_cleartext.len) {
860 // We still have cleartext left so we cannot issue another read() call yet.
861 assert(vp.total == amt);
862 return amt;
863 }
864 if (c.received_close_notify) {
865 c.partial_ciphertext_end = 0;
866 assert(vp.total == amt);
867 return amt;
868 }
869 if (c.partial_ciphertext_end == c.partial_ciphertext_idx) {
870 c.partial_cleartext_idx = 0;
871 c.partial_ciphertext_idx = 0;
872 c.partial_ciphertext_end = 0;
873 }
874 }
875
876 assert(!c.received_close_notify);
877
878 // Ideally, this buffer would never be used. It is needed when `iovecs` are
879 // too small to fit the cleartext, which may be as large as `max_ciphertext_len`.
880 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;
881 // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`.
882 var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined;
883 // How many bytes left in the user's buffer.
884 const free_size = vp.freeSize();
885 // The amount of the user's buffer that we need to repurpose for storing
886 // ciphertext. The end of the buffer will be used for such purposes.
887 const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len;
888 // The amount of the user's buffer that will be used to give cleartext. The
889 // beginning of the buffer will be used for such purposes.
890 const cleartext_buf_len = free_size - ciphertext_buf_len;
891 const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..];
892
893 var ask_iovecs_buf: [2]std.os.iovec = .{
894 .{
895 .iov_base = first_iov.ptr,
896 .iov_len = first_iov.len,
897 },
898 .{
899 .iov_base = &in_stack_buffer,
900 .iov_len = in_stack_buffer.len,
901 },
902 };
903
904 // Cleartext capacity of output buffer, in records, rounded up.
905 const buf_cap = (cleartext_buf_len +| (max_ciphertext_len - 1)) / max_ciphertext_len;
906 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);
907 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len);
908 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);
909 const actual_read_len = try stream.readv(ask_iovecs);
910 if (actual_read_len == 0) {
911 // This is either a truncation attack, a bug in the server, or an
912 // intentional omission of the close_notify message due to truncation
913 // detection handled above the TLS layer.
914 if (c.allow_truncation_attacks) {
915 c.received_close_notify = true;
916 } else {
917 return error.TlsConnectionTruncated;
918 }
919 }
920
921 // There might be more bytes inside `in_stack_buffer` that need to be processed,
922 // but at least frag0 will have one complete ciphertext record.
923 const frag0_end = @min(c.partially_read_buffer.len, c.partial_ciphertext_end + actual_read_len);
924 const frag0 = c.partially_read_buffer[c.partial_ciphertext_idx..frag0_end];
925 var frag1 = in_stack_buffer[0..actual_read_len -| first_iov.len];
926 // We need to decipher frag0 and frag1 but there may be a ciphertext record
927 // straddling the boundary. We can handle this with two memcpy() calls to
928 // assemble the straddling record in between handling the two sides.
929 var frag = frag0;
930 var in: usize = 0;
931 while (true) {
932 if (in == frag.len) {
933 // Perfect split.
934 if (frag.ptr == frag1.ptr) {
935 c.partial_ciphertext_end = c.partial_ciphertext_idx;
936 return vp.total;
937 }
938 frag = frag1;
939 in = 0;
940 continue;
941 }
942
943 if (in + tls.record_header_len > frag.len) {
944 if (frag.ptr == frag1.ptr)
945 return finishRead(c, frag, in, vp.total);
946
947 const first = frag[in..];
948
949 if (frag1.len < tls.record_header_len)
950 return finishRead2(c, first, frag1, vp.total);
951
952 // A record straddles the two fragments. Copy into the now-empty first fragment.
953 const record_len_byte_0: u16 = straddleByte(frag, frag1, in + 3);
954 const record_len_byte_1: u16 = straddleByte(frag, frag1, in + 4);
955 const record_len = (record_len_byte_0 << 8) | record_len_byte_1;
956 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;
957
958 const full_record_len = record_len + tls.record_header_len;
959 const second_len = full_record_len - first.len;
960 if (frag1.len < second_len)
961 return finishRead2(c, first, frag1, vp.total);
962
963 mem.copy(u8, frag[0..in], first);
964 mem.copy(u8, frag[first.len..], frag1[0..second_len]);
965 frag = frag[0..full_record_len];
966 frag1 = frag1[second_len..];
967 in = 0;
968 continue;
969 }
970 const ct = @intToEnum(tls.ContentType, frag[in]);
971 in += 1;
972 const legacy_version = mem.readIntBig(u16, frag[in..][0..2]);
973 in += 2;
974 _ = legacy_version;
975 const record_len = mem.readIntBig(u16, frag[in..][0..2]);
976 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;
977 in += 2;
978 const end = in + record_len;
979 if (end > frag.len) {
980 // We need the record header on the next iteration of the loop.
981 in -= tls.record_header_len;
982
983 if (frag.ptr == frag1.ptr)
984 return finishRead(c, frag, in, vp.total);
985
986 // A record straddles the two fragments. Copy into the now-empty first fragment.
987 const first = frag[in..];
988 const full_record_len = record_len + tls.record_header_len;
989 const second_len = full_record_len - first.len;
990 if (frag1.len < second_len)
991 return finishRead2(c, first, frag1, vp.total);
992
993 mem.copy(u8, frag[0..in], first);
994 mem.copy(u8, frag[first.len..], frag1[0..second_len]);
995 frag = frag[0..full_record_len];
996 frag1 = frag1[second_len..];
997 in = 0;
998 continue;
999 }
1000 switch (ct) {
1001 .alert => {
1002 if (in + 2 > frag.len) return error.TlsDecodeError;
1003 const level = @intToEnum(tls.AlertLevel, frag[in]);
1004 const desc = @intToEnum(tls.AlertDescription, frag[in + 1]);
1005 _ = level;
1006 _ = desc;
1007 return error.TlsAlert;
1008 },
1009 .application_data => {
1010 const cleartext = switch (c.application_cipher) {
1011 inline else => |*p| c: {
1012 const P = @TypeOf(p.*);
1013 const V = @Vector(P.AEAD.nonce_length, u8);
1014 const ad = frag[in - 5 ..][0..5];
1015 const ciphertext_len = record_len - P.AEAD.tag_length;
1016 const ciphertext = frag[in..][0..ciphertext_len];
1017 in += ciphertext_len;
1018 const auth_tag = frag[in..][0..P.AEAD.tag_length].*;
1019 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1020 const operand: V = pad ++ @bitCast([8]u8, big(c.read_seq));
1021 const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand;
1022 const out_buf = vp.peek();
1023 const cleartext_buf = if (ciphertext.len <= out_buf.len)
1024 out_buf
1025 else
1026 &cleartext_stack_buffer;
1027 const cleartext = cleartext_buf[0..ciphertext.len];
1028 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_key) catch
1029 return error.TlsBadRecordMac;
1030 break :c cleartext;
1031 },
1032 };
1033
1034 c.read_seq = try std.math.add(u64, c.read_seq, 1);
1035
1036 const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]);
1037 switch (inner_ct) {
1038 .alert => {
1039 const level = @intToEnum(tls.AlertLevel, cleartext[0]);
1040 const desc = @intToEnum(tls.AlertDescription, cleartext[1]);
1041 if (desc == .close_notify) {
1042 c.received_close_notify = true;
1043 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1044 return vp.total;
1045 }
1046 _ = level;
1047 return error.TlsAlert;
1048 },
1049 .handshake => {
1050 var ct_i: usize = 0;
1051 while (true) {
1052 const handshake_type = @intToEnum(tls.HandshakeType, cleartext[ct_i]);
1053 ct_i += 1;
1054 const handshake_len = mem.readIntBig(u24, cleartext[ct_i..][0..3]);
1055 ct_i += 3;
1056 const next_handshake_i = ct_i + handshake_len;
1057 if (next_handshake_i > cleartext.len - 1)
1058 return error.TlsBadLength;
1059 const handshake = cleartext[ct_i..next_handshake_i];
1060 switch (handshake_type) {
1061 .new_session_ticket => {
1062 // This client implementation ignores new session tickets.
1063 },
1064 .key_update => {
1065 switch (c.application_cipher) {
1066 inline else => |*p| {
1067 const P = @TypeOf(p.*);
1068 const server_secret = hkdfExpandLabel(P.Hkdf, p.server_secret, "traffic upd", "", P.Hash.digest_length);
1069 p.server_secret = server_secret;
1070 p.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
1071 p.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
1072 },
1073 }
1074 c.read_seq = 0;
1075
1076 switch (@intToEnum(tls.KeyUpdateRequest, handshake[0])) {
1077 .update_requested => {
1078 switch (c.application_cipher) {
1079 inline else => |*p| {
1080 const P = @TypeOf(p.*);
1081 const client_secret = hkdfExpandLabel(P.Hkdf, p.client_secret, "traffic upd", "", P.Hash.digest_length);
1082 p.client_secret = client_secret;
1083 p.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
1084 p.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
1085 },
1086 }
1087 c.write_seq = 0;
1088 },
1089 .update_not_requested => {},
1090 _ => return error.TlsIllegalParameter,
1091 }
1092 },
1093 else => {
1094 return error.TlsUnexpectedMessage;
1095 },
1096 }
1097 ct_i = next_handshake_i;
1098 if (ct_i >= cleartext.len - 1) break;
1099 }
1100 },
1101 .application_data => {
1102 // Determine whether the output buffer or a stack
1103 // buffer was used for storing the cleartext.
1104 if (cleartext.ptr == &cleartext_stack_buffer) {
1105 // Stack buffer was used, so we must copy to the output buffer.
1106 const msg = cleartext[0 .. cleartext.len - 1];
1107 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1108 // We have already run out of room in iovecs. Continue
1109 // appending to `partially_read_buffer`.
1110 const dest = c.partially_read_buffer[c.partial_ciphertext_idx..];
1111 mem.copy(u8, dest, msg);
1112 c.partial_ciphertext_idx = @intCast(@TypeOf(c.partial_ciphertext_idx), c.partial_ciphertext_idx + msg.len);
1113 } else {
1114 const amt = vp.put(msg);
1115 if (amt < msg.len) {
1116 const rest = msg[amt..];
1117 c.partial_cleartext_idx = 0;
1118 c.partial_ciphertext_idx = @intCast(@TypeOf(c.partial_ciphertext_idx), rest.len);
1119 mem.copy(u8, &c.partially_read_buffer, rest);
1120 }
1121 }
1122 } else {
1123 // Output buffer was used directly which means no
1124 // memory copying needs to occur, and we can move
1125 // on to the next ciphertext record.
1126 vp.next(cleartext.len - 1);
1127 }
1128 },
1129 else => {
1130 return error.TlsUnexpectedMessage;
1131 },
1132 }
1133 },
1134 else => {
1135 return error.TlsUnexpectedMessage;
1136 },
1137 }
1138 in = end;
1139 }
1140}
1141
1142fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
1143 const saved_buf = frag[in..];
1144 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1145 // There is cleartext at the beginning already which we need to preserve.
1146 c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), c.partial_ciphertext_idx + saved_buf.len);
1147 mem.copy(u8, c.partially_read_buffer[c.partial_ciphertext_idx..], saved_buf);
1148 } else {
1149 c.partial_cleartext_idx = 0;
1150 c.partial_ciphertext_idx = 0;
1151 c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), saved_buf.len);
1152 mem.copy(u8, &c.partially_read_buffer, saved_buf);
1153 }
1154 return out;
1155}
1156
1157fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usize {
1158 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1159 // There is cleartext at the beginning already which we need to preserve.
1160 c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), c.partial_ciphertext_idx + first.len + frag1.len);
1161 mem.copy(u8, c.partially_read_buffer[c.partial_ciphertext_idx..], first);
1162 mem.copy(u8, c.partially_read_buffer[c.partial_ciphertext_idx + first.len ..], frag1);
1163 } else {
1164 c.partial_cleartext_idx = 0;
1165 c.partial_ciphertext_idx = 0;
1166 c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), first.len + frag1.len);
1167 mem.copy(u8, &c.partially_read_buffer, first);
1168 mem.copy(u8, c.partially_read_buffer[first.len..], frag1);
1169 }
1170 return out;
1171}
1172
1173fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 {
1174 if (index < s1.len) {
1175 return s1[index];
1176 } else {
1177 return s2[index - s1.len];
1178 }
1179}
1180
1181const builtin = @import("builtin");
1182const native_endian = builtin.cpu.arch.endian();
1183
1184inline fn big(x: anytype) @TypeOf(x) {
1185 return switch (native_endian) {
1186 .Big => x,
1187 .Little => @byteSwap(x),
1188 };
1189}
1190
1191fn SchemeEcdsa(comptime scheme: tls.SignatureScheme) type {
1192 return switch (scheme) {
1193 .ecdsa_secp256r1_sha256 => crypto.sign.ecdsa.EcdsaP256Sha256,
1194 .ecdsa_secp384r1_sha384 => crypto.sign.ecdsa.EcdsaP384Sha384,
1195 .ecdsa_secp521r1_sha512 => crypto.sign.ecdsa.EcdsaP512Sha512,
1196 else => @compileError("bad scheme"),
1197 };
1198}
1199
1200/// Abstraction for sending multiple byte buffers to a slice of iovecs.
1201const VecPut = struct {
1202 iovecs: []const std.os.iovec,
1203 idx: usize = 0,
1204 off: usize = 0,
1205 total: usize = 0,
1206
1207 /// Returns the amount actually put which is always equal to bytes.len
1208 /// unless the vectors ran out of space.
1209 fn put(vp: *VecPut, bytes: []const u8) usize {
1210 var bytes_i: usize = 0;
1211 while (true) {
1212 const v = vp.iovecs[vp.idx];
1213 const dest = v.iov_base[vp.off..v.iov_len];
1214 const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)];
1215 mem.copy(u8, dest, src);
1216 bytes_i += src.len;
1217 vp.off += src.len;
1218 if (vp.off >= v.iov_len) {
1219 vp.off = 0;
1220 vp.idx += 1;
1221 if (vp.idx >= vp.iovecs.len) {
1222 vp.total += bytes_i;
1223 return bytes_i;
1224 }
1225 }
1226 if (bytes_i >= bytes.len) {
1227 vp.total += bytes_i;
1228 return bytes_i;
1229 }
1230 }
1231 }
1232
1233 /// Returns the next buffer that consecutive bytes can go into.
1234 fn peek(vp: VecPut) []u8 {
1235 if (vp.idx >= vp.iovecs.len) return &.{};
1236 const v = vp.iovecs[vp.idx];
1237 return v.iov_base[vp.off..v.iov_len];
1238 }
1239
1240 // After writing to the result of peek(), one can call next() to
1241 // advance the cursor.
1242 fn next(vp: *VecPut, len: usize) void {
1243 vp.total += len;
1244 vp.off += len;
1245 if (vp.off >= vp.iovecs[vp.idx].iov_len) {
1246 vp.off = 0;
1247 vp.idx += 1;
1248 }
1249 }
1250
1251 fn freeSize(vp: VecPut) usize {
1252 if (vp.idx >= vp.iovecs.len) return 0;
1253 var total: usize = 0;
1254 total += vp.iovecs[vp.idx].iov_len - vp.off;
1255 if (vp.idx + 1 >= vp.iovecs.len) return total;
1256 for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.iov_len;
1257 return total;
1258 }
1259};
1260
1261/// Limit iovecs to a specific byte size.
1262fn limitVecs(iovecs: []std.os.iovec, len: usize) []std.os.iovec {
1263 var vec_i: usize = 0;
1264 var bytes_left: usize = len;
1265 while (true) {
1266 if (bytes_left >= iovecs[vec_i].iov_len) {
1267 bytes_left -= iovecs[vec_i].iov_len;
1268 vec_i += 1;
1269 if (vec_i == iovecs.len or bytes_left == 0) return iovecs[0..vec_i];
1270 continue;
1271 }
1272 iovecs[vec_i].iov_len = bytes_left;
1273 return iovecs[0..vec_i];
1274 }
1275}
1276
1277/// The priority order here is chosen based on what crypto algorithms Zig has
1278/// available in the standard library as well as what is faster. Following are
1279/// a few data points on the relative performance of these algorithms.
1280///
1281/// Measurement taken with 0.11.0-dev.810+c2f5848fe
1282/// on x86_64-linux Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz:
1283/// zig run .lib/std/crypto/benchmark.zig -OReleaseFast
1284/// aegis-128l: 15382 MiB/s
1285/// aegis-256: 9553 MiB/s
1286/// aes128-gcm: 3721 MiB/s
1287/// aes256-gcm: 3010 MiB/s
1288/// chacha20Poly1305: 597 MiB/s
1289///
1290/// Measurement taken with 0.11.0-dev.810+c2f5848fe
1291/// on x86_64-linux Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz:
1292/// zig run .lib/std/crypto/benchmark.zig -OReleaseFast -mcpu=baseline
1293/// aegis-128l: 629 MiB/s
1294/// chacha20Poly1305: 529 MiB/s
1295/// aegis-256: 461 MiB/s
1296/// aes128-gcm: 138 MiB/s
1297/// aes256-gcm: 120 MiB/s
1298const cipher_suites = enum_array(tls.CipherSuite, &.{
1299 .AEGIS_128L_SHA256,
1300 .AEGIS_256_SHA384,
1301 .AES_128_GCM_SHA256,
1302 .AES_256_GCM_SHA384,
1303 .CHACHA20_POLY1305_SHA256,
1304});
1305
1306test {
1307 _ = StreamInterface;
1308}
lib/std/http.zig+297-4
...@@ -1,8 +1,301 @@...@@ -1,8 +1,301 @@
1const std = @import("std.zig");1pub const Client = @import("http/Client.zig");
2
3/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
4/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton
5/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
6pub const Method = enum {
7 GET,
8 HEAD,
9 POST,
10 PUT,
11 DELETE,
12 CONNECT,
13 OPTIONS,
14 TRACE,
15 PATCH,
16
17 /// Returns true if a request of this method is allowed to have a body
18 /// Actual behavior from servers may vary and should still be checked
19 pub fn requestHasBody(self: Method) bool {
20 return switch (self) {
21 .POST, .PUT, .PATCH => true,
22 .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
23 };
24 }
25
26 /// Returns true if a response to this method is allowed to have a body
27 /// Actual behavior from clients may vary and should still be checked
28 pub fn responseHasBody(self: Method) bool {
29 return switch (self) {
30 .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
31 .HEAD, .PUT, .TRACE => false,
32 };
33 }
34
35 /// An HTTP method is safe if it doesn't alter the state of the server.
36 /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
37 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
38 pub fn safe(self: Method) bool {
39 return switch (self) {
40 .GET, .HEAD, .OPTIONS, .TRACE => true,
41 .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
42 };
43 }
44
45 /// An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
46 /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
47 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
48 pub fn idempotent(self: Method) bool {
49 return switch (self) {
50 .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
51 .CONNECT, .POST, .PATCH => false,
52 };
53 }
54
55 /// A cacheable response is an HTTP response that can be cached, that is stored to be retrieved and used later, saving a new request to the server.
56 /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
57 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
58 pub fn cacheable(self: Method) bool {
59 return switch (self) {
60 .GET, .HEAD => true,
61 .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
62 };
63 }
64};
65
66/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
67pub const Status = enum(u10) {
68 @"continue" = 100, // RFC7231, Section 6.2.1
69 switching_protocols = 101, // RFC7231, Section 6.2.2
70 processing = 102, // RFC2518
71 early_hints = 103, // RFC8297
72
73 ok = 200, // RFC7231, Section 6.3.1
74 created = 201, // RFC7231, Section 6.3.2
75 accepted = 202, // RFC7231, Section 6.3.3
76 non_authoritative_info = 203, // RFC7231, Section 6.3.4
77 no_content = 204, // RFC7231, Section 6.3.5
78 reset_content = 205, // RFC7231, Section 6.3.6
79 partial_content = 206, // RFC7233, Section 4.1
80 multi_status = 207, // RFC4918
81 already_reported = 208, // RFC5842
82 im_used = 226, // RFC3229
83
84 multiple_choice = 300, // RFC7231, Section 6.4.1
85 moved_permanently = 301, // RFC7231, Section 6.4.2
86 found = 302, // RFC7231, Section 6.4.3
87 see_other = 303, // RFC7231, Section 6.4.4
88 not_modified = 304, // RFC7232, Section 4.1
89 use_proxy = 305, // RFC7231, Section 6.4.5
90 temporary_redirect = 307, // RFC7231, Section 6.4.7
91 permanent_redirect = 308, // RFC7538
92
93 bad_request = 400, // RFC7231, Section 6.5.1
94 unauthorized = 401, // RFC7235, Section 3.1
95 payment_required = 402, // RFC7231, Section 6.5.2
96 forbidden = 403, // RFC7231, Section 6.5.3
97 not_found = 404, // RFC7231, Section 6.5.4
98 method_not_allowed = 405, // RFC7231, Section 6.5.5
99 not_acceptable = 406, // RFC7231, Section 6.5.6
100 proxy_auth_required = 407, // RFC7235, Section 3.2
101 request_timeout = 408, // RFC7231, Section 6.5.7
102 conflict = 409, // RFC7231, Section 6.5.8
103 gone = 410, // RFC7231, Section 6.5.9
104 length_required = 411, // RFC7231, Section 6.5.10
105 precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2
106 payload_too_large = 413, // RFC7231, Section 6.5.11
107 uri_too_long = 414, // RFC7231, Section 6.5.12
108 unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3
109 range_not_satisfiable = 416, // RFC7233, Section 4.4
110 expectation_failed = 417, // RFC7231, Section 6.5.14
111 teapot = 418, // RFC 7168, 2.3.3
112 misdirected_request = 421, // RFC7540, Section 9.1.2
113 unprocessable_entity = 422, // RFC4918
114 locked = 423, // RFC4918
115 failed_dependency = 424, // RFC4918
116 too_early = 425, // RFC8470
117 upgrade_required = 426, // RFC7231, Section 6.5.15
118 precondition_required = 428, // RFC6585
119 too_many_requests = 429, // RFC6585
120 header_fields_too_large = 431, // RFC6585
121 unavailable_for_legal_reasons = 451, // RFC7725
122
123 internal_server_error = 500, // RFC7231, Section 6.6.1
124 not_implemented = 501, // RFC7231, Section 6.6.2
125 bad_gateway = 502, // RFC7231, Section 6.6.3
126 service_unavailable = 503, // RFC7231, Section 6.6.4
127 gateway_timeout = 504, // RFC7231, Section 6.6.5
128 http_version_not_supported = 505, // RFC7231, Section 6.6.6
129 variant_also_negotiates = 506, // RFC2295
130 insufficient_storage = 507, // RFC4918
131 loop_detected = 508, // RFC5842
132 not_extended = 510, // RFC2774
133 network_authentication_required = 511, // RFC6585
134
135 _,
136
137 pub fn phrase(self: Status) ?[]const u8 {
138 return switch (self) {
139 // 1xx statuses
140 .@"continue" => "Continue",
141 .switching_protocols => "Switching Protocols",
142 .processing => "Processing",
143 .early_hints => "Early Hints",
2144
3pub const Method = @import("http/method.zig").Method;145 // 2xx statuses
4pub const Status = @import("http/status.zig").Status;146 .ok => "OK",
147 .created => "Created",
148 .accepted => "Accepted",
149 .non_authoritative_info => "Non-Authoritative Information",
150 .no_content => "No Content",
151 .reset_content => "Reset Content",
152 .partial_content => "Partial Content",
153 .multi_status => "Multi-Status",
154 .already_reported => "Already Reported",
155 .im_used => "IM Used",
156
157 // 3xx statuses
158 .multiple_choice => "Multiple Choice",
159 .moved_permanently => "Moved Permanently",
160 .found => "Found",
161 .see_other => "See Other",
162 .not_modified => "Not Modified",
163 .use_proxy => "Use Proxy",
164 .temporary_redirect => "Temporary Redirect",
165 .permanent_redirect => "Permanent Redirect",
166
167 // 4xx statuses
168 .bad_request => "Bad Request",
169 .unauthorized => "Unauthorized",
170 .payment_required => "Payment Required",
171 .forbidden => "Forbidden",
172 .not_found => "Not Found",
173 .method_not_allowed => "Method Not Allowed",
174 .not_acceptable => "Not Acceptable",
175 .proxy_auth_required => "Proxy Authentication Required",
176 .request_timeout => "Request Timeout",
177 .conflict => "Conflict",
178 .gone => "Gone",
179 .length_required => "Length Required",
180 .precondition_failed => "Precondition Failed",
181 .payload_too_large => "Payload Too Large",
182 .uri_too_long => "URI Too Long",
183 .unsupported_media_type => "Unsupported Media Type",
184 .range_not_satisfiable => "Range Not Satisfiable",
185 .expectation_failed => "Expectation Failed",
186 .teapot => "I'm a teapot",
187 .misdirected_request => "Misdirected Request",
188 .unprocessable_entity => "Unprocessable Entity",
189 .locked => "Locked",
190 .failed_dependency => "Failed Dependency",
191 .too_early => "Too Early",
192 .upgrade_required => "Upgrade Required",
193 .precondition_required => "Precondition Required",
194 .too_many_requests => "Too Many Requests",
195 .header_fields_too_large => "Request Header Fields Too Large",
196 .unavailable_for_legal_reasons => "Unavailable For Legal Reasons",
197
198 // 5xx statuses
199 .internal_server_error => "Internal Server Error",
200 .not_implemented => "Not Implemented",
201 .bad_gateway => "Bad Gateway",
202 .service_unavailable => "Service Unavailable",
203 .gateway_timeout => "Gateway Timeout",
204 .http_version_not_supported => "HTTP Version Not Supported",
205 .variant_also_negotiates => "Variant Also Negotiates",
206 .insufficient_storage => "Insufficient Storage",
207 .loop_detected => "Loop Detected",
208 .not_extended => "Not Extended",
209 .network_authentication_required => "Network Authentication Required",
210
211 else => return null,
212 };
213 }
214
215 pub const Class = enum {
216 informational,
217 success,
218 redirect,
219 client_error,
220 server_error,
221 };
222
223 pub fn class(self: Status) ?Class {
224 return switch (@enumToInt(self)) {
225 100...199 => .informational,
226 200...299 => .success,
227 300...399 => .redirect,
228 400...499 => .client_error,
229 500...599 => .server_error,
230 else => null,
231 };
232 }
233
234 test {
235 try std.testing.expectEqualStrings("OK", Status.ok.phrase().?);
236 try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?);
237 }
238
239 test {
240 try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class());
241 try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class());
242 }
243};
244
245pub const Headers = struct {
246 state: State = .start,
247 invalid_index: u32 = undefined,
248
249 pub const State = enum { invalid, start, line, nl_r, nl_n, nl2_r, finished };
250
251 /// Returns how many bytes are processed into headers. Always less than or
252 /// equal to bytes.len. If the amount returned is less than bytes.len, it
253 /// means the headers ended and the first byte after the double \r\n\r\n is
254 /// located at `bytes[result]`.
255 pub fn feed(h: *Headers, bytes: []const u8) usize {
256 for (bytes) |b, i| {
257 switch (h.state) {
258 .start => switch (b) {
259 '\r' => h.state = .nl_r,
260 '\n' => return invalid(h, i),
261 else => {},
262 },
263 .nl_r => switch (b) {
264 '\n' => h.state = .nl_n,
265 else => return invalid(h, i),
266 },
267 .nl_n => switch (b) {
268 '\r' => h.state = .nl2_r,
269 else => h.state = .line,
270 },
271 .nl2_r => switch (b) {
272 '\n' => h.state = .finished,
273 else => return invalid(h, i),
274 },
275 .line => switch (b) {
276 '\r' => h.state = .nl_r,
277 '\n' => return invalid(h, i),
278 else => {},
279 },
280 .invalid => return i,
281 .finished => return i,
282 }
283 }
284 return bytes.len;
285 }
286
287 fn invalid(h: *Headers, i: usize) usize {
288 h.invalid_index = @intCast(u32, i);
289 h.state = .invalid;
290 return i;
291 }
292};
293
294const std = @import("std.zig");
5295
6test {296test {
7 std.testing.refAllDecls(@This());297 _ = Client;
298 _ = Method;
299 _ = Status;
300 _ = Headers;
8}301}
lib/std/http/Client.zig created+181
...@@ -0,0 +1,181 @@
1//! This API is a barely-touched, barely-functional http client, just the
2//! absolute minimum thing I needed in order to test `std.crypto.tls`. Bear
3//! with me and I promise the API will become useful and streamlined.
4
5const std = @import("../std.zig");
6const assert = std.debug.assert;
7const http = std.http;
8const net = std.net;
9const Client = @This();
10const Url = std.Url;
11
12allocator: std.mem.Allocator,
13headers: std.ArrayListUnmanaged(u8) = .{},
14active_requests: usize = 0,
15ca_bundle: std.crypto.Certificate.Bundle = .{},
16
17/// TODO: emit error.UnexpectedEndOfStream or something like that when the read
18/// data does not match the content length. This is necessary since HTTPS disables
19/// close_notify protection on underlying TLS streams.
20pub const Request = struct {
21 client: *Client,
22 stream: net.Stream,
23 headers: std.ArrayListUnmanaged(u8) = .{},
24 tls_client: std.crypto.tls.Client,
25 protocol: Protocol,
26 response_headers: http.Headers = .{},
27
28 pub const Protocol = enum { http, https };
29
30 pub const Options = struct {
31 method: http.Method = .GET,
32 };
33
34 pub fn deinit(req: *Request) void {
35 req.client.active_requests -= 1;
36 req.headers.deinit(req.client.allocator);
37 req.* = undefined;
38 }
39
40 pub fn addHeader(req: *Request, name: []const u8, value: []const u8) !void {
41 const gpa = req.client.allocator;
42 // Ensure an extra +2 for the \r\n in end()
43 try req.headers.ensureUnusedCapacity(gpa, name.len + value.len + 6);
44 req.headers.appendSliceAssumeCapacity(name);
45 req.headers.appendSliceAssumeCapacity(": ");
46 req.headers.appendSliceAssumeCapacity(value);
47 req.headers.appendSliceAssumeCapacity("\r\n");
48 }
49
50 pub fn end(req: *Request) !void {
51 req.headers.appendSliceAssumeCapacity("\r\n");
52 switch (req.protocol) {
53 .http => {
54 try req.stream.writeAll(req.headers.items);
55 },
56 .https => {
57 try req.tls_client.writeAll(req.stream, req.headers.items);
58 },
59 }
60 }
61
62 pub fn readAll(req: *Request, buffer: []u8) !usize {
63 return readAtLeast(req, buffer, buffer.len);
64 }
65
66 pub fn read(req: *Request, buffer: []u8) !usize {
67 return readAtLeast(req, buffer, 1);
68 }
69
70 pub fn readAtLeast(req: *Request, buffer: []u8, len: usize) !usize {
71 assert(len <= buffer.len);
72 var index: usize = 0;
73 while (index < len) {
74 const headers_finished = req.response_headers.state == .finished;
75 const amt = try readAdvanced(req, buffer[index..]);
76 if (amt == 0 and headers_finished) break;
77 index += amt;
78 }
79 return index;
80 }
81
82 /// This one can return 0 without meaning EOF.
83 /// TODO change to readvAdvanced
84 pub fn readAdvanced(req: *Request, buffer: []u8) !usize {
85 if (req.response_headers.state == .finished) return readRaw(req, buffer);
86
87 const amt = try readRaw(req, buffer);
88 const data = buffer[0..amt];
89 const i = req.response_headers.feed(data);
90 if (req.response_headers.state == .invalid) return error.InvalidHttpHeaders;
91 if (i < data.len) {
92 const rest = data[i..];
93 std.mem.copy(u8, buffer, rest);
94 return rest.len;
95 }
96 return 0;
97 }
98
99 /// Only abstracts over http/https.
100 fn readRaw(req: *Request, buffer: []u8) !usize {
101 switch (req.protocol) {
102 .http => return req.stream.read(buffer),
103 .https => return req.tls_client.read(req.stream, buffer),
104 }
105 }
106
107 /// Only abstracts over http/https.
108 fn readAtLeastRaw(req: *Request, buffer: []u8, len: usize) !usize {
109 switch (req.protocol) {
110 .http => return req.stream.readAtLeast(buffer, len),
111 .https => return req.tls_client.readAtLeast(req.stream, buffer, len),
112 }
113 }
114};
115
116pub fn deinit(client: *Client) void {
117 assert(client.active_requests == 0);
118 client.headers.deinit(client.allocator);
119 client.* = undefined;
120}
121
122pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
123 const protocol = std.meta.stringToEnum(Request.Protocol, url.scheme) orelse
124 return error.UnsupportedUrlScheme;
125 const port: u16 = url.port orelse switch (protocol) {
126 .http => 80,
127 .https => 443,
128 };
129
130 var req: Request = .{
131 .client = client,
132 .stream = try net.tcpConnectToHost(client.allocator, url.host, port),
133 .protocol = protocol,
134 .tls_client = undefined,
135 };
136 client.active_requests += 1;
137 errdefer req.deinit();
138
139 switch (protocol) {
140 .http => {},
141 .https => {
142 req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, url.host);
143 // This is appropriate for HTTPS because the HTTP headers contain
144 // the content length which is used to detect truncation attacks.
145 req.tls_client.allow_truncation_attacks = true;
146 },
147 }
148
149 try req.headers.ensureUnusedCapacity(
150 client.allocator,
151 @tagName(options.method).len +
152 1 +
153 url.path.len +
154 " HTTP/1.1\r\nHost: ".len +
155 url.host.len +
156 "\r\nUpgrade-Insecure-Requests: 1\r\n".len +
157 client.headers.items.len +
158 2, // for the \r\n at the end of headers
159 );
160 req.headers.appendSliceAssumeCapacity(@tagName(options.method));
161 req.headers.appendSliceAssumeCapacity(" ");
162 req.headers.appendSliceAssumeCapacity(url.path);
163 req.headers.appendSliceAssumeCapacity(" HTTP/1.1\r\nHost: ");
164 req.headers.appendSliceAssumeCapacity(url.host);
165 switch (protocol) {
166 .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"),
167 .http => req.headers.appendSliceAssumeCapacity("\r\n"),
168 }
169 req.headers.appendSliceAssumeCapacity(client.headers.items);
170
171 return req;
172}
173
174pub fn addHeader(client: *Client, name: []const u8, value: []const u8) !void {
175 const gpa = client.allocator;
176 try client.headers.ensureUnusedCapacity(gpa, name.len + value.len + 4);
177 client.headers.appendSliceAssumeCapacity(name);
178 client.headers.appendSliceAssumeCapacity(": ");
179 client.headers.appendSliceAssumeCapacity(value);
180 client.headers.appendSliceAssumeCapacity("\r\n");
181}
lib/std/http/method.zig deleted-65
...@@ -1,65 +0,0 @@
1//! HTTP Methods
2//! https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
3
4// Style guide is violated here so that @tagName can be used effectively
5/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton
6/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
7pub const Method = enum {
8 GET,
9 HEAD,
10 POST,
11 PUT,
12 DELETE,
13 CONNECT,
14 OPTIONS,
15 TRACE,
16 PATCH,
17
18 /// Returns true if a request of this method is allowed to have a body
19 /// Actual behavior from servers may vary and should still be checked
20 pub fn requestHasBody(self: Method) bool {
21 return switch (self) {
22 .POST, .PUT, .PATCH => true,
23 .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
24 };
25 }
26
27 /// Returns true if a response to this method is allowed to have a body
28 /// Actual behavior from clients may vary and should still be checked
29 pub fn responseHasBody(self: Method) bool {
30 return switch (self) {
31 .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
32 .HEAD, .PUT, .TRACE => false,
33 };
34 }
35
36 /// An HTTP method is safe if it doesn't alter the state of the server.
37 /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
38 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
39 pub fn safe(self: Method) bool {
40 return switch (self) {
41 .GET, .HEAD, .OPTIONS, .TRACE => true,
42 .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
43 };
44 }
45
46 /// An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
47 /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
48 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
49 pub fn idempotent(self: Method) bool {
50 return switch (self) {
51 .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
52 .CONNECT, .POST, .PATCH => false,
53 };
54 }
55
56 /// A cacheable response is an HTTP response that can be cached, that is stored to be retrieved and used later, saving a new request to the server.
57 /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
58 /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
59 pub fn cacheable(self: Method) bool {
60 return switch (self) {
61 .GET, .HEAD => true,
62 .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
63 };
64 }
65};
lib/std/http/status.zig deleted-182
...@@ -1,182 +0,0 @@
1//! HTTP Status
2//! https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
3
4const std = @import("../std.zig");
5
6pub const Status = enum(u10) {
7 @"continue" = 100, // RFC7231, Section 6.2.1
8 switching_protocols = 101, // RFC7231, Section 6.2.2
9 processing = 102, // RFC2518
10 early_hints = 103, // RFC8297
11
12 ok = 200, // RFC7231, Section 6.3.1
13 created = 201, // RFC7231, Section 6.3.2
14 accepted = 202, // RFC7231, Section 6.3.3
15 non_authoritative_info = 203, // RFC7231, Section 6.3.4
16 no_content = 204, // RFC7231, Section 6.3.5
17 reset_content = 205, // RFC7231, Section 6.3.6
18 partial_content = 206, // RFC7233, Section 4.1
19 multi_status = 207, // RFC4918
20 already_reported = 208, // RFC5842
21 im_used = 226, // RFC3229
22
23 multiple_choice = 300, // RFC7231, Section 6.4.1
24 moved_permanently = 301, // RFC7231, Section 6.4.2
25 found = 302, // RFC7231, Section 6.4.3
26 see_other = 303, // RFC7231, Section 6.4.4
27 not_modified = 304, // RFC7232, Section 4.1
28 use_proxy = 305, // RFC7231, Section 6.4.5
29 temporary_redirect = 307, // RFC7231, Section 6.4.7
30 permanent_redirect = 308, // RFC7538
31
32 bad_request = 400, // RFC7231, Section 6.5.1
33 unauthorized = 401, // RFC7235, Section 3.1
34 payment_required = 402, // RFC7231, Section 6.5.2
35 forbidden = 403, // RFC7231, Section 6.5.3
36 not_found = 404, // RFC7231, Section 6.5.4
37 method_not_allowed = 405, // RFC7231, Section 6.5.5
38 not_acceptable = 406, // RFC7231, Section 6.5.6
39 proxy_auth_required = 407, // RFC7235, Section 3.2
40 request_timeout = 408, // RFC7231, Section 6.5.7
41 conflict = 409, // RFC7231, Section 6.5.8
42 gone = 410, // RFC7231, Section 6.5.9
43 length_required = 411, // RFC7231, Section 6.5.10
44 precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2
45 payload_too_large = 413, // RFC7231, Section 6.5.11
46 uri_too_long = 414, // RFC7231, Section 6.5.12
47 unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3
48 range_not_satisfiable = 416, // RFC7233, Section 4.4
49 expectation_failed = 417, // RFC7231, Section 6.5.14
50 teapot = 418, // RFC 7168, 2.3.3
51 misdirected_request = 421, // RFC7540, Section 9.1.2
52 unprocessable_entity = 422, // RFC4918
53 locked = 423, // RFC4918
54 failed_dependency = 424, // RFC4918
55 too_early = 425, // RFC8470
56 upgrade_required = 426, // RFC7231, Section 6.5.15
57 precondition_required = 428, // RFC6585
58 too_many_requests = 429, // RFC6585
59 header_fields_too_large = 431, // RFC6585
60 unavailable_for_legal_reasons = 451, // RFC7725
61
62 internal_server_error = 500, // RFC7231, Section 6.6.1
63 not_implemented = 501, // RFC7231, Section 6.6.2
64 bad_gateway = 502, // RFC7231, Section 6.6.3
65 service_unavailable = 503, // RFC7231, Section 6.6.4
66 gateway_timeout = 504, // RFC7231, Section 6.6.5
67 http_version_not_supported = 505, // RFC7231, Section 6.6.6
68 variant_also_negotiates = 506, // RFC2295
69 insufficient_storage = 507, // RFC4918
70 loop_detected = 508, // RFC5842
71 not_extended = 510, // RFC2774
72 network_authentication_required = 511, // RFC6585
73
74 _,
75
76 pub fn phrase(self: Status) ?[]const u8 {
77 return switch (self) {
78 // 1xx statuses
79 .@"continue" => "Continue",
80 .switching_protocols => "Switching Protocols",
81 .processing => "Processing",
82 .early_hints => "Early Hints",
83
84 // 2xx statuses
85 .ok => "OK",
86 .created => "Created",
87 .accepted => "Accepted",
88 .non_authoritative_info => "Non-Authoritative Information",
89 .no_content => "No Content",
90 .reset_content => "Reset Content",
91 .partial_content => "Partial Content",
92 .multi_status => "Multi-Status",
93 .already_reported => "Already Reported",
94 .im_used => "IM Used",
95
96 // 3xx statuses
97 .multiple_choice => "Multiple Choice",
98 .moved_permanently => "Moved Permanently",
99 .found => "Found",
100 .see_other => "See Other",
101 .not_modified => "Not Modified",
102 .use_proxy => "Use Proxy",
103 .temporary_redirect => "Temporary Redirect",
104 .permanent_redirect => "Permanent Redirect",
105
106 // 4xx statuses
107 .bad_request => "Bad Request",
108 .unauthorized => "Unauthorized",
109 .payment_required => "Payment Required",
110 .forbidden => "Forbidden",
111 .not_found => "Not Found",
112 .method_not_allowed => "Method Not Allowed",
113 .not_acceptable => "Not Acceptable",
114 .proxy_auth_required => "Proxy Authentication Required",
115 .request_timeout => "Request Timeout",
116 .conflict => "Conflict",
117 .gone => "Gone",
118 .length_required => "Length Required",
119 .precondition_failed => "Precondition Failed",
120 .payload_too_large => "Payload Too Large",
121 .uri_too_long => "URI Too Long",
122 .unsupported_media_type => "Unsupported Media Type",
123 .range_not_satisfiable => "Range Not Satisfiable",
124 .expectation_failed => "Expectation Failed",
125 .teapot => "I'm a teapot",
126 .misdirected_request => "Misdirected Request",
127 .unprocessable_entity => "Unprocessable Entity",
128 .locked => "Locked",
129 .failed_dependency => "Failed Dependency",
130 .too_early => "Too Early",
131 .upgrade_required => "Upgrade Required",
132 .precondition_required => "Precondition Required",
133 .too_many_requests => "Too Many Requests",
134 .header_fields_too_large => "Request Header Fields Too Large",
135 .unavailable_for_legal_reasons => "Unavailable For Legal Reasons",
136
137 // 5xx statuses
138 .internal_server_error => "Internal Server Error",
139 .not_implemented => "Not Implemented",
140 .bad_gateway => "Bad Gateway",
141 .service_unavailable => "Service Unavailable",
142 .gateway_timeout => "Gateway Timeout",
143 .http_version_not_supported => "HTTP Version Not Supported",
144 .variant_also_negotiates => "Variant Also Negotiates",
145 .insufficient_storage => "Insufficient Storage",
146 .loop_detected => "Loop Detected",
147 .not_extended => "Not Extended",
148 .network_authentication_required => "Network Authentication Required",
149
150 else => return null,
151 };
152 }
153
154 pub const Class = enum {
155 informational,
156 success,
157 redirect,
158 client_error,
159 server_error,
160 };
161
162 pub fn class(self: Status) ?Class {
163 return switch (@enumToInt(self)) {
164 100...199 => .informational,
165 200...299 => .success,
166 300...399 => .redirect,
167 400...499 => .client_error,
168 500...599 => .server_error,
169 else => null,
170 };
171 }
172};
173
174test {
175 try std.testing.expectEqualStrings("OK", Status.ok.phrase().?);
176 try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?);
177}
178
179test {
180 try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class());
181 try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class());
182}
lib/std/meta.zig+8-4
...@@ -810,21 +810,25 @@ test "std.meta.activeTag" {...@@ -810,21 +810,25 @@ test "std.meta.activeTag" {
810810
811const TagPayloadType = TagPayload;811const TagPayloadType = TagPayload;
812812
813///Given a tagged union type, and an enum, return the type of the union813pub fn TagPayloadByName(comptime U: type, comptime tag_name: []const u8) type {
814/// field corresponding to the enum tag.
815pub fn TagPayload(comptime U: type, comptime tag: Tag(U)) type {
816 comptime debug.assert(trait.is(.Union)(U));814 comptime debug.assert(trait.is(.Union)(U));
817815
818 const info = @typeInfo(U).Union;816 const info = @typeInfo(U).Union;
819817
820 inline for (info.fields) |field_info| {818 inline for (info.fields) |field_info| {
821 if (comptime mem.eql(u8, field_info.name, @tagName(tag)))819 if (comptime mem.eql(u8, field_info.name, tag_name))
822 return field_info.type;820 return field_info.type;
823 }821 }
824822
825 unreachable;823 unreachable;
826}824}
827825
826/// Given a tagged union type, and an enum, return the type of the union field
827/// corresponding to the enum tag.
828pub fn TagPayload(comptime U: type, comptime tag: Tag(U)) type {
829 return TagPayloadByName(U, @tagName(tag));
830}
831
828test "std.meta.TagPayload" {832test "std.meta.TagPayload" {
829 const Event = union(enum) {833 const Event = union(enum) {
830 Moved: struct {834 Moved: struct {
lib/std/net.zig+41
...@@ -1672,6 +1672,40 @@ pub const Stream = struct {...@@ -1672,6 +1672,40 @@ pub const Stream = struct {
1672 }1672 }
1673 }1673 }
16741674
1675 pub fn readv(s: Stream, iovecs: []const os.iovec) ReadError!usize {
1676 if (builtin.os.tag == .windows) {
1677 // TODO improve this to use ReadFileScatter
1678 if (iovecs.len == 0) return @as(usize, 0);
1679 const first = iovecs[0];
1680 return os.windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null, io.default_mode);
1681 }
1682
1683 return os.readv(s.handle, iovecs);
1684 }
1685
1686 /// Returns the number of bytes read. If the number read is smaller than
1687 /// `buffer.len`, it means the stream reached the end. Reaching the end of
1688 /// a stream is not an error condition.
1689 pub fn readAll(s: Stream, buffer: []u8) ReadError!usize {
1690 return readAtLeast(s, buffer, buffer.len);
1691 }
1692
1693 /// Returns the number of bytes read, calling the underlying read function
1694 /// the minimal number of times until the buffer has at least `len` bytes
1695 /// filled. If the number read is less than `len` it means the stream
1696 /// reached the end. Reaching the end of the stream is not an error
1697 /// condition.
1698 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
1699 assert(len <= buffer.len);
1700 var index: usize = 0;
1701 while (index < len) {
1702 const amt = try s.read(buffer[index..]);
1703 if (amt == 0) break;
1704 index += amt;
1705 }
1706 return index;
1707 }
1708
1675 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's1709 /// TODO in evented I/O mode, this implementation incorrectly uses the event loop's
1676 /// file system thread instead of non-blocking. It needs to be reworked to properly1710 /// file system thread instead of non-blocking. It needs to be reworked to properly
1677 /// use non-blocking I/O.1711 /// use non-blocking I/O.
...@@ -1687,6 +1721,13 @@ pub const Stream = struct {...@@ -1687,6 +1721,13 @@ pub const Stream = struct {
1687 }1721 }
1688 }1722 }
16891723
1724 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
1725 var index: usize = 0;
1726 while (index < bytes.len) {
1727 index += try self.write(bytes[index..]);
1728 }
1729 }
1730
1690 /// See https://github.com/ziglang/zig/issues/76991731 /// See https://github.com/ziglang/zig/issues/7699
1691 /// See equivalent function: `std.fs.File.writev`.1732 /// See equivalent function: `std.fs.File.writev`.
1692 pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize {1733 pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize {
lib/std/os.zig+3-2
...@@ -767,6 +767,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -767,6 +767,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
767 .ISDIR => return error.IsDir,767 .ISDIR => return error.IsDir,
768 .NOBUFS => return error.SystemResources,768 .NOBUFS => return error.SystemResources,
769 .NOMEM => return error.SystemResources,769 .NOMEM => return error.SystemResources,
770 .CONNRESET => return error.ConnectionResetByPeer,
770 else => |err| return unexpectedErrno(err),771 else => |err| return unexpectedErrno(err),
771 }772 }
772 }773 }
...@@ -5685,11 +5686,11 @@ pub fn sendmsg(...@@ -5685,11 +5686,11 @@ pub fn sendmsg(
5685 /// The file descriptor of the sending socket.5686 /// The file descriptor of the sending socket.
5686 sockfd: socket_t,5687 sockfd: socket_t,
5687 /// Message header and iovecs5688 /// Message header and iovecs
5688 msg: msghdr_const,5689 msg: *const msghdr_const,
5689 flags: u32,5690 flags: u32,
5690) SendMsgError!usize {5691) SendMsgError!usize {
5691 while (true) {5692 while (true) {
5692 const rc = system.sendmsg(sockfd, @ptrCast(*const std.x.os.Socket.Message, &msg), @intCast(c_int, flags));5693 const rc = system.sendmsg(sockfd, msg, flags);
5693 if (builtin.os.tag == .windows) {5694 if (builtin.os.tag == .windows) {
5694 if (rc == windows.ws2_32.SOCKET_ERROR) {5695 if (rc == windows.ws2_32.SOCKET_ERROR) {
5695 switch (windows.ws2_32.WSAGetLastError()) {5696 switch (windows.ws2_32.WSAGetLastError()) {
lib/std/os/linux.zig+41-12
...@@ -1226,11 +1226,14 @@ pub fn getsockopt(fd: i32, level: u32, optname: u32, noalias optval: [*]u8, noal...@@ -1226,11 +1226,14 @@ pub fn getsockopt(fd: i32, level: u32, optname: u32, noalias optval: [*]u8, noal
1226 return syscall5(.getsockopt, @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen));1226 return syscall5(.getsockopt, @bitCast(usize, @as(isize, fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen));
1227}1227}
12281228
1229pub fn sendmsg(fd: i32, msg: *const std.x.os.Socket.Message, flags: c_int) usize {1229pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize {
1230 const fd_usize = @bitCast(usize, @as(isize, fd));
1231 const msg_usize = @ptrToInt(msg);
1230 if (native_arch == .x86) {1232 if (native_arch == .x86) {
1231 return socketcall(SC.sendmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)) });1233 return socketcall(SC.sendmsg, &[3]usize{ fd_usize, msg_usize, flags });
1234 } else {
1235 return syscall3(.sendmsg, fd_usize, msg_usize, flags);
1232 }1236 }
1233 return syscall3(.sendmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)));
1234}1237}
12351238
1236pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize {1239pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize {
...@@ -1274,24 +1277,42 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize...@@ -1274,24 +1277,42 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize
1274}1277}
12751278
1276pub fn connect(fd: i32, addr: *const anyopaque, len: socklen_t) usize {1279pub fn connect(fd: i32, addr: *const anyopaque, len: socklen_t) usize {
1280 const fd_usize = @bitCast(usize, @as(isize, fd));
1281 const addr_usize = @ptrToInt(addr);
1277 if (native_arch == .x86) {1282 if (native_arch == .x86) {
1278 return socketcall(SC.connect, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len });1283 return socketcall(SC.connect, &[3]usize{ fd_usize, addr_usize, len });
1284 } else {
1285 return syscall3(.connect, fd_usize, addr_usize, len);
1279 }1286 }
1280 return syscall3(.connect, @bitCast(usize, @as(isize, fd)), @ptrToInt(addr), len);
1281}1287}
12821288
1283pub fn recvmsg(fd: i32, msg: *std.x.os.Socket.Message, flags: c_int) usize {1289pub fn recvmsg(fd: i32, msg: *msghdr, flags: u32) usize {
1290 const fd_usize = @bitCast(usize, @as(isize, fd));
1291 const msg_usize = @ptrToInt(msg);
1284 if (native_arch == .x86) {1292 if (native_arch == .x86) {
1285 return socketcall(SC.recvmsg, &[3]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)) });1293 return socketcall(SC.recvmsg, &[3]usize{ fd_usize, msg_usize, flags });
1294 } else {
1295 return syscall3(.recvmsg, fd_usize, msg_usize, flags);
1286 }1296 }
1287 return syscall3(.recvmsg, @bitCast(usize, @as(isize, fd)), @ptrToInt(msg), @bitCast(usize, @as(isize, flags)));
1288}1297}
12891298
1290pub fn recvfrom(fd: i32, noalias buf: [*]u8, len: usize, flags: u32, noalias addr: ?*sockaddr, noalias alen: ?*socklen_t) usize {1299pub fn recvfrom(
1300 fd: i32,
1301 noalias buf: [*]u8,
1302 len: usize,
1303 flags: u32,
1304 noalias addr: ?*sockaddr,
1305 noalias alen: ?*socklen_t,
1306) usize {
1307 const fd_usize = @bitCast(usize, @as(isize, fd));
1308 const buf_usize = @ptrToInt(buf);
1309 const addr_usize = @ptrToInt(addr);
1310 const alen_usize = @ptrToInt(alen);
1291 if (native_arch == .x86) {1311 if (native_arch == .x86) {
1292 return socketcall(SC.recvfrom, &[6]usize{ @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen) });1312 return socketcall(SC.recvfrom, &[6]usize{ fd_usize, buf_usize, len, flags, addr_usize, alen_usize });
1313 } else {
1314 return syscall6(.recvfrom, fd_usize, buf_usize, len, flags, addr_usize, alen_usize);
1293 }1315 }
1294 return syscall6(.recvfrom, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @ptrToInt(alen));
1295}1316}
12961317
1297pub fn shutdown(fd: i32, how: i32) usize {1318pub fn shutdown(fd: i32, how: i32) usize {
...@@ -3219,7 +3240,15 @@ pub const sockaddr = extern struct {...@@ -3219,7 +3240,15 @@ pub const sockaddr = extern struct {
3219 data: [14]u8,3240 data: [14]u8,
32203241
3221 pub const SS_MAXSIZE = 128;3242 pub const SS_MAXSIZE = 128;
3222 pub const storage = std.x.os.Socket.Address.Native.Storage;3243 pub const storage = extern struct {
3244 family: sa_family_t align(8),
3245 padding: [SS_MAXSIZE - @sizeOf(sa_family_t)]u8 = undefined,
3246
3247 comptime {
3248 assert(@sizeOf(storage) == SS_MAXSIZE);
3249 assert(@alignOf(storage) == 8);
3250 }
3251 };
32233252
3224 /// IPv4 socket address3253 /// IPv4 socket address
3225 pub const in = extern struct {3254 pub const in = extern struct {
lib/std/os/linux/seccomp.zig+8-10
...@@ -6,16 +6,14 @@...@@ -6,16 +6,14 @@
6//! isn't that useful for general-purpose applications, and so a mode that6//! isn't that useful for general-purpose applications, and so a mode that
7//! utilizes user-supplied filters mode was added.7//! utilizes user-supplied filters mode was added.
8//!8//!
9//! Seccomp filters are classic BPF programs, which means that all the9//! Seccomp filters are classic BPF programs. Conceptually, a seccomp program
10//! information under `std.x.net.bpf` applies here as well. Conceptually, a10//! is attached to the kernel and is executed on each syscall. The "packet"
11//! seccomp program is attached to the kernel and is executed on each syscall.11//! being validated is the `data` structure, and the verdict is an action that
12//! The "packet" being validated is the `data` structure, and the verdict is an12//! the kernel performs on the calling process. The actions are variations on a
13//! action that the kernel performs on the calling process. The actions are13//! "pass" or "fail" result, where a pass allows the syscall to continue and a
14//! variations on a "pass" or "fail" result, where a pass allows the syscall to14//! fail blocks the syscall and returns some sort of error value. See the full
15//! continue and a fail blocks the syscall and returns some sort of error value.15//! list of actions under ::RET for more information. Finally, only word-sized,
16//! See the full list of actions under ::RET for more information. Finally, only16//! absolute loads (`ld [k]`) are supported to read from the `data` structure.
17//! word-sized, absolute loads (`ld [k]`) are supported to read from the `data`
18//! structure.
19//!17//!
20//! There are some issues with the filter API that have traditionally made18//! There are some issues with the filter API that have traditionally made
21//! writing them a pain:19//! writing them a pain:
lib/std/os/windows/ws2_32.zig+14-5
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../../std.zig");1const std = @import("../../std.zig");
2const assert = std.debug.assert;
2const windows = std.os.windows;3const windows = std.os.windows;
34
4const WINAPI = windows.WINAPI;5const WINAPI = windows.WINAPI;
...@@ -1106,7 +1107,15 @@ pub const sockaddr = extern struct {...@@ -1106,7 +1107,15 @@ pub const sockaddr = extern struct {
1106 data: [14]u8,1107 data: [14]u8,
11071108
1108 pub const SS_MAXSIZE = 128;1109 pub const SS_MAXSIZE = 128;
1109 pub const storage = std.x.os.Socket.Address.Native.Storage;1110 pub const storage = extern struct {
1111 family: ADDRESS_FAMILY align(8),
1112 padding: [SS_MAXSIZE - @sizeOf(ADDRESS_FAMILY)]u8 = undefined,
1113
1114 comptime {
1115 assert(@sizeOf(storage) == SS_MAXSIZE);
1116 assert(@alignOf(storage) == 8);
1117 }
1118 };
11101119
1111 /// IPv4 socket address1120 /// IPv4 socket address
1112 pub const in = extern struct {1121 pub const in = extern struct {
...@@ -1207,7 +1216,7 @@ pub const LPFN_GETACCEPTEXSOCKADDRS = *const fn (...@@ -1207,7 +1216,7 @@ pub const LPFN_GETACCEPTEXSOCKADDRS = *const fn (
12071216
1208pub const LPFN_WSASENDMSG = *const fn (1217pub const LPFN_WSASENDMSG = *const fn (
1209 s: SOCKET,1218 s: SOCKET,
1210 lpMsg: *const std.x.os.Socket.Message,1219 lpMsg: *const WSAMSG_const,
1211 dwFlags: u32,1220 dwFlags: u32,
1212 lpNumberOfBytesSent: ?*u32,1221 lpNumberOfBytesSent: ?*u32,
1213 lpOverlapped: ?*OVERLAPPED,1222 lpOverlapped: ?*OVERLAPPED,
...@@ -1216,7 +1225,7 @@ pub const LPFN_WSASENDMSG = *const fn (...@@ -1216,7 +1225,7 @@ pub const LPFN_WSASENDMSG = *const fn (
12161225
1217pub const LPFN_WSARECVMSG = *const fn (1226pub const LPFN_WSARECVMSG = *const fn (
1218 s: SOCKET,1227 s: SOCKET,
1219 lpMsg: *std.x.os.Socket.Message,1228 lpMsg: *WSAMSG,
1220 lpdwNumberOfBytesRecv: ?*u32,1229 lpdwNumberOfBytesRecv: ?*u32,
1221 lpOverlapped: ?*OVERLAPPED,1230 lpOverlapped: ?*OVERLAPPED,
1222 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,1231 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
...@@ -2090,7 +2099,7 @@ pub extern "ws2_32" fn WSASend(...@@ -2090,7 +2099,7 @@ pub extern "ws2_32" fn WSASend(
20902099
2091pub extern "ws2_32" fn WSASendMsg(2100pub extern "ws2_32" fn WSASendMsg(
2092 s: SOCKET,2101 s: SOCKET,
2093 lpMsg: *const std.x.os.Socket.Message,2102 lpMsg: *WSAMSG_const,
2094 dwFlags: u32,2103 dwFlags: u32,
2095 lpNumberOfBytesSent: ?*u32,2104 lpNumberOfBytesSent: ?*u32,
2096 lpOverlapped: ?*OVERLAPPED,2105 lpOverlapped: ?*OVERLAPPED,
...@@ -2099,7 +2108,7 @@ pub extern "ws2_32" fn WSASendMsg(...@@ -2099,7 +2108,7 @@ pub extern "ws2_32" fn WSASendMsg(
20992108
2100pub extern "ws2_32" fn WSARecvMsg(2109pub extern "ws2_32" fn WSARecvMsg(
2101 s: SOCKET,2110 s: SOCKET,
2102 lpMsg: *std.x.os.Socket.Message,2111 lpMsg: *WSAMSG,
2103 lpdwNumberOfBytesRecv: ?*u32,2112 lpdwNumberOfBytesRecv: ?*u32,
2104 lpOverlapped: ?*OVERLAPPED,2113 lpOverlapped: ?*OVERLAPPED,
2105 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,2114 lpCompletionRoutine: ?LPWSAOVERLAPPED_COMPLETION_ROUTINE,
lib/std/std.zig+1-1
...@@ -42,6 +42,7 @@ pub const Target = @import("target.zig").Target;...@@ -42,6 +42,7 @@ pub const Target = @import("target.zig").Target;
42pub const Thread = @import("Thread.zig");42pub const Thread = @import("Thread.zig");
43pub const Treap = @import("treap.zig").Treap;43pub const Treap = @import("treap.zig").Treap;
44pub const Tz = tz.Tz;44pub const Tz = tz.Tz;
45pub const Url = @import("Url.zig");
4546
46pub const array_hash_map = @import("array_hash_map.zig");47pub const array_hash_map = @import("array_hash_map.zig");
47pub const atomic = @import("atomic.zig");48pub const atomic = @import("atomic.zig");
...@@ -90,7 +91,6 @@ pub const tz = @import("tz.zig");...@@ -90,7 +91,6 @@ pub const tz = @import("tz.zig");
90pub const unicode = @import("unicode.zig");91pub const unicode = @import("unicode.zig");
91pub const valgrind = @import("valgrind.zig");92pub const valgrind = @import("valgrind.zig");
92pub const wasm = @import("wasm.zig");93pub const wasm = @import("wasm.zig");
93pub const x = @import("x.zig");
94pub const zig = @import("zig.zig");94pub const zig = @import("zig.zig");
95pub const start = @import("start.zig");95pub const start = @import("start.zig");
9696
lib/std/x.zig deleted-19
...@@ -1,19 +0,0 @@
1const std = @import("std.zig");
2
3pub const os = struct {
4 pub const Socket = @import("x/os/socket.zig").Socket;
5 pub usingnamespace @import("x/os/io.zig");
6 pub usingnamespace @import("x/os/net.zig");
7};
8
9pub const net = struct {
10 pub const ip = @import("x/net/ip.zig");
11 pub const tcp = @import("x/net/tcp.zig");
12 pub const bpf = @import("x/net/bpf.zig");
13};
14
15test {
16 inline for (.{ os, net }) |module| {
17 std.testing.refAllDecls(module);
18 }
19}
lib/std/x/net/bpf.zig deleted-1003
...@@ -1,1003 +0,0 @@
1//! This package provides instrumentation for creating Berkeley Packet Filter[1]
2//! (BPF) programs, along with a simulator for running them.
3//!
4//! BPF is a mechanism for cheap, in-kernel packet filtering. Programs are
5//! attached to a network device and executed for every packet that flows
6//! through it. The program must then return a verdict: the amount of packet
7//! bytes that the kernel should copy into userspace. Execution speed is
8//! achieved by having programs run in a limited virtual machine, which has the
9//! added benefit of graceful failure in the face of buggy programs.
10//!
11//! The BPF virtual machine has a 32-bit word length and a small number of
12//! word-sized registers:
13//!
14//! - The accumulator, `a`: The source/destination of arithmetic and logic
15//! operations.
16//! - The index register, `x`: Used as an offset for indirect memory access and
17//! as a comparison value for conditional jumps.
18//! - The scratch memory store, `M[0]..M[15]`: Used for saving the value of a/x
19//! for later use.
20//!
21//! The packet being examined is an array of bytes, and is addressed using plain
22//! array subscript notation, e.g. [10] for the byte at offset 10. An implicit
23//! program counter, `pc`, is intialized to zero and incremented for each instruction.
24//!
25//! The machine has a fixed instruction set with the following form, where the
26//! numbers represent bit length:
27//!
28//! ```
29//! ┌───────────┬──────┬──────┐
30//! │ opcode:16 │ jt:8 │ jt:8 │
31//! ├───────────┴──────┴──────┤
32//! │ k:32 │
33//! └─────────────────────────┘
34//! ```
35//!
36//! The `opcode` indicates the instruction class and its addressing mode.
37//! Opcodes are generated by performing binary addition on the 8-bit class and
38//! mode constants. For example, the opcode for loading a byte from the packet
39//! at X + 2, (`ldb [x + 2]`), is:
40//!
41//! ```
42//! LD | IND | B = 0x00 | 0x40 | 0x20
43//! = 0x60
44//! ```
45//!
46//! `jt` is an offset used for conditional jumps, and increments the program
47//! counter by its amount if the comparison was true. Conversely, `jf`
48//! increments the counter if it was false. These fields are ignored in all
49//! other cases. `k` is a generic variable used for various purposes, most
50//! commonly as some sort of constant.
51//!
52//! This package contains opcode extensions used by different implementations,
53//! where "extension" is anything outside of the original that was imported into
54//! 4.4BSD[2]. These are marked with "EXTENSION", along with a list of
55//! implementations that use them.
56//!
57//! Most of the doc-comments use the BPF assembly syntax as described in the
58//! original paper[1]. For the sake of completeness, here is the complete
59//! instruction set, along with the extensions:
60//!
61//!```
62//! opcode addressing modes
63//! ld #k #len M[k] [k] [x + k]
64//! ldh [k] [x + k]
65//! ldb [k] [x + k]
66//! ldx #k #len M[k] 4 * ([k] & 0xf) arc4random()
67//! st M[k]
68//! stx M[k]
69//! jmp L
70//! jeq #k, Lt, Lf
71//! jgt #k, Lt, Lf
72//! jge #k, Lt, Lf
73//! jset #k, Lt, Lf
74//! add #k x
75//! sub #k x
76//! mul #k x
77//! div #k x
78//! or #k x
79//! and #k x
80//! lsh #k x
81//! rsh #k x
82//! neg #k x
83//! mod #k x
84//! xor #k x
85//! ret #k a
86//! tax
87//! txa
88//! ```
89//!
90//! Finally, a note on program design. The lack of backwards jumps leads to a
91//! "return early, return often" control flow. Take for example the program
92//! generated from the tcpdump filter `ip`:
93//!
94//! ```
95//! (000) ldh [12] ; Ethernet Packet Type
96//! (001) jeq #0x86dd, 2, 7 ; ETHERTYPE_IPV6
97//! (002) ldb [20] ; IPv6 Next Header
98//! (003) jeq #0x6, 10, 4 ; TCP
99//! (004) jeq #0x2c, 5, 11 ; IPv6 Fragment Header
100//! (005) ldb [54] ; TCP Source Port
101//! (006) jeq #0x6, 10, 11 ; IPPROTO_TCP
102//! (007) jeq #0x800, 8, 11 ; ETHERTYPE_IP
103//! (008) ldb [23] ; IPv4 Protocol
104//! (009) jeq #0x6, 10, 11 ; IPPROTO_TCP
105//! (010) ret #262144 ; copy 0x40000
106//! (011) ret #0 ; skip packet
107//! ```
108//!
109//! Here we can make a few observations:
110//!
111//! - The problem "filter only tcp packets" has essentially been transformed
112//! into a series of layer checks.
113//! - There are two distinct branches in the code, one for validating IPv4
114//! headers and one for IPv6 headers.
115//! - Most conditional jumps in these branches lead directly to the last two
116//! instructions, a pass or fail. Thus the goal of a program is to find the
117//! fastest route to a pass/fail comparison.
118//!
119//! [1]: S. McCanne and V. Jacobson, "The BSD Packet Filter: A New Architecture
120//! for User-level Packet Capture", Proceedings of the 1993 Winter USENIX.
121//! [2]: https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/src/sys/net/bpf.h
122const std = @import("std");
123const builtin = @import("builtin");
124const native_endian = builtin.target.cpu.arch.endian();
125const mem = std.mem;
126const math = std.math;
127const random = std.crypto.random;
128const assert = std.debug.assert;
129const expectEqual = std.testing.expectEqual;
130const expectError = std.testing.expectError;
131const expect = std.testing.expect;
132
133// instruction classes
134/// ld, ldh, ldb: Load data into a.
135pub const LD = 0x00;
136/// ldx: Load data into x.
137pub const LDX = 0x01;
138/// st: Store into scratch memory the value of a.
139pub const ST = 0x02;
140/// st: Store into scratch memory the value of x.
141pub const STX = 0x03;
142/// alu: Wrapping arithmetic/bitwise operations on a using the value of k/x.
143pub const ALU = 0x04;
144/// jmp, jeq, jgt, je, jset: Increment the program counter based on a comparison
145/// between k/x and the accumulator.
146pub const JMP = 0x05;
147/// ret: Return a verdict using the value of k/the accumulator.
148pub const RET = 0x06;
149/// tax, txa: Register value copying between X and a.
150pub const MISC = 0x07;
151
152// Size of data to be loaded from the packet.
153/// ld: 32-bit full word.
154pub const W = 0x00;
155/// ldh: 16-bit half word.
156pub const H = 0x08;
157/// ldb: Single byte.
158pub const B = 0x10;
159
160// Addressing modes used for loads to a/x.
161/// #k: The immediate value stored in k.
162pub const IMM = 0x00;
163/// [k]: The value at offset k in the packet.
164pub const ABS = 0x20;
165/// [x + k]: The value at offset x + k in the packet.
166pub const IND = 0x40;
167/// M[k]: The value of the k'th scratch memory register.
168pub const MEM = 0x60;
169/// #len: The size of the packet.
170pub const LEN = 0x80;
171/// 4 * ([k] & 0xf): Four times the low four bits of the byte at offset k in the
172/// packet. This is used for efficiently loading the header length of an IP
173/// packet.
174pub const MSH = 0xa0;
175/// arc4random: 32-bit integer generated from a CPRNG (see arc4random(3)) loaded into a.
176/// EXTENSION. Defined for:
177/// - OpenBSD.
178pub const RND = 0xc0;
179
180// Modifiers for different instruction classes.
181/// Use the value of k for alu operations (add #k).
182/// Compare against the value of k for jumps (jeq #k, Lt, Lf).
183/// Return the value of k for returns (ret #k).
184pub const K = 0x00;
185/// Use the value of x for alu operations (add x).
186/// Compare against the value of X for jumps (jeq x, Lt, Lf).
187pub const X = 0x08;
188/// Return the value of a for returns (ret a).
189pub const A = 0x10;
190
191// ALU Operations on a using the value of k/x.
192// All arithmetic operations are defined to overflow the value of a.
193/// add: a = a + k
194/// a = a + x.
195pub const ADD = 0x00;
196/// sub: a = a - k
197/// a = a - x.
198pub const SUB = 0x10;
199/// mul: a = a * k
200/// a = a * x.
201pub const MUL = 0x20;
202/// div: a = a / k
203/// a = a / x.
204/// Truncated division.
205pub const DIV = 0x30;
206/// or: a = a | k
207/// a = a | x.
208pub const OR = 0x40;
209/// and: a = a & k
210/// a = a & x.
211pub const AND = 0x50;
212/// lsh: a = a << k
213/// a = a << x.
214/// a = a << k, a = a << x.
215pub const LSH = 0x60;
216/// rsh: a = a >> k
217/// a = a >> x.
218pub const RSH = 0x70;
219/// neg: a = -a.
220/// Note that this isn't a binary negation, rather the value of `~a + 1`.
221pub const NEG = 0x80;
222/// mod: a = a % k
223/// a = a % x.
224/// EXTENSION. Defined for:
225/// - Linux.
226/// - NetBSD + Minix 3.
227/// - FreeBSD and derivitives.
228pub const MOD = 0x90;
229/// xor: a = a ^ k
230/// a = a ^ x.
231/// EXTENSION. Defined for:
232/// - Linux.
233/// - NetBSD + Minix 3.
234/// - FreeBSD and derivitives.
235pub const XOR = 0xa0;
236
237// Jump operations using a comparison between a and x/k.
238/// jmp L: pc += k.
239/// No comparison done here.
240pub const JA = 0x00;
241/// jeq #k, Lt, Lf: pc += (a == k) ? jt : jf.
242/// jeq x, Lt, Lf: pc += (a == x) ? jt : jf.
243pub const JEQ = 0x10;
244/// jgt #k, Lt, Lf: pc += (a > k) ? jt : jf.
245/// jgt x, Lt, Lf: pc += (a > x) ? jt : jf.
246pub const JGT = 0x20;
247/// jge #k, Lt, Lf: pc += (a >= k) ? jt : jf.
248/// jge x, Lt, Lf: pc += (a >= x) ? jt : jf.
249pub const JGE = 0x30;
250/// jset #k, Lt, Lf: pc += (a & k > 0) ? jt : jf.
251/// jset x, Lt, Lf: pc += (a & x > 0) ? jt : jf.
252pub const JSET = 0x40;
253
254// Miscellaneous operations/register copy.
255/// tax: x = a.
256pub const TAX = 0x00;
257/// txa: a = x.
258pub const TXA = 0x80;
259
260/// The 16 registers in the scratch memory store as named enums.
261pub const Scratch = enum(u4) { m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 };
262pub const MEMWORDS = 16;
263pub const MAXINSNS = switch (builtin.os.tag) {
264 .linux => 4096,
265 else => 512,
266};
267pub const MINBUFSIZE = 32;
268pub const MAXBUFSIZE = 1 << 21;
269
270pub const Insn = extern struct {
271 opcode: u16,
272 jt: u8,
273 jf: u8,
274 k: u32,
275
276 /// Implements the `std.fmt.format` API.
277 /// The formatting is similar to the output of tcpdump -dd.
278 pub fn format(
279 self: Insn,
280 comptime layout: []const u8,
281 opts: std.fmt.FormatOptions,
282 writer: anytype,
283 ) !void {
284 _ = opts;
285 if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
286
287 try std.fmt.format(
288 writer,
289 "Insn{{ 0x{X:0<2}, {d}, {d}, 0x{X:0<8} }}",
290 .{ self.opcode, self.jt, self.jf, self.k },
291 );
292 }
293
294 const Size = enum(u8) {
295 word = W,
296 half_word = H,
297 byte = B,
298 };
299
300 fn stmt(opcode: u16, k: u32) Insn {
301 return .{
302 .opcode = opcode,
303 .jt = 0,
304 .jf = 0,
305 .k = k,
306 };
307 }
308
309 pub fn ld_imm(value: u32) Insn {
310 return stmt(LD | IMM, value);
311 }
312
313 pub fn ld_abs(size: Size, offset: u32) Insn {
314 return stmt(LD | ABS | @enumToInt(size), offset);
315 }
316
317 pub fn ld_ind(size: Size, offset: u32) Insn {
318 return stmt(LD | IND | @enumToInt(size), offset);
319 }
320
321 pub fn ld_mem(reg: Scratch) Insn {
322 return stmt(LD | MEM, @enumToInt(reg));
323 }
324
325 pub fn ld_len() Insn {
326 return stmt(LD | LEN | W, 0);
327 }
328
329 pub fn ld_rnd() Insn {
330 return stmt(LD | RND | W, 0);
331 }
332
333 pub fn ldx_imm(value: u32) Insn {
334 return stmt(LDX | IMM, value);
335 }
336
337 pub fn ldx_mem(reg: Scratch) Insn {
338 return stmt(LDX | MEM, @enumToInt(reg));
339 }
340
341 pub fn ldx_len() Insn {
342 return stmt(LDX | LEN | W, 0);
343 }
344
345 pub fn ldx_msh(offset: u32) Insn {
346 return stmt(LDX | MSH | B, offset);
347 }
348
349 pub fn st(reg: Scratch) Insn {
350 return stmt(ST, @enumToInt(reg));
351 }
352 pub fn stx(reg: Scratch) Insn {
353 return stmt(STX, @enumToInt(reg));
354 }
355
356 const AluOp = enum(u16) {
357 add = ADD,
358 sub = SUB,
359 mul = MUL,
360 div = DIV,
361 @"or" = OR,
362 @"and" = AND,
363 lsh = LSH,
364 rsh = RSH,
365 mod = MOD,
366 xor = XOR,
367 };
368
369 const Source = enum(u16) {
370 k = K,
371 x = X,
372 };
373 const KOrX = union(Source) {
374 k: u32,
375 x: void,
376 };
377
378 pub fn alu_neg() Insn {
379 return stmt(ALU | NEG, 0);
380 }
381
382 pub fn alu(op: AluOp, source: KOrX) Insn {
383 return stmt(
384 ALU | @enumToInt(op) | @enumToInt(source),
385 if (source == .k) source.k else 0,
386 );
387 }
388
389 const JmpOp = enum(u16) {
390 jeq = JEQ,
391 jgt = JGT,
392 jge = JGE,
393 jset = JSET,
394 };
395
396 pub fn jmp_ja(location: u32) Insn {
397 return stmt(JMP | JA, location);
398 }
399
400 pub fn jmp(op: JmpOp, source: KOrX, jt: u8, jf: u8) Insn {
401 return Insn{
402 .opcode = JMP | @enumToInt(op) | @enumToInt(source),
403 .jt = jt,
404 .jf = jf,
405 .k = if (source == .k) source.k else 0,
406 };
407 }
408
409 const Verdict = enum(u16) {
410 k = K,
411 a = A,
412 };
413 const KOrA = union(Verdict) {
414 k: u32,
415 a: void,
416 };
417
418 pub fn ret(verdict: KOrA) Insn {
419 return stmt(
420 RET | @enumToInt(verdict),
421 if (verdict == .k) verdict.k else 0,
422 );
423 }
424
425 pub fn tax() Insn {
426 return stmt(MISC | TAX, 0);
427 }
428
429 pub fn txa() Insn {
430 return stmt(MISC | TXA, 0);
431 }
432};
433
434fn opcodeEqual(opcode: u16, insn: Insn) !void {
435 try expectEqual(opcode, insn.opcode);
436}
437
438test "opcodes" {
439 try opcodeEqual(0x00, Insn.ld_imm(0));
440 try opcodeEqual(0x20, Insn.ld_abs(.word, 0));
441 try opcodeEqual(0x28, Insn.ld_abs(.half_word, 0));
442 try opcodeEqual(0x30, Insn.ld_abs(.byte, 0));
443 try opcodeEqual(0x40, Insn.ld_ind(.word, 0));
444 try opcodeEqual(0x48, Insn.ld_ind(.half_word, 0));
445 try opcodeEqual(0x50, Insn.ld_ind(.byte, 0));
446 try opcodeEqual(0x60, Insn.ld_mem(.m0));
447 try opcodeEqual(0x80, Insn.ld_len());
448 try opcodeEqual(0xc0, Insn.ld_rnd());
449
450 try opcodeEqual(0x01, Insn.ldx_imm(0));
451 try opcodeEqual(0x61, Insn.ldx_mem(.m0));
452 try opcodeEqual(0x81, Insn.ldx_len());
453 try opcodeEqual(0xb1, Insn.ldx_msh(0));
454
455 try opcodeEqual(0x02, Insn.st(.m0));
456 try opcodeEqual(0x03, Insn.stx(.m0));
457
458 try opcodeEqual(0x04, Insn.alu(.add, .{ .k = 0 }));
459 try opcodeEqual(0x14, Insn.alu(.sub, .{ .k = 0 }));
460 try opcodeEqual(0x24, Insn.alu(.mul, .{ .k = 0 }));
461 try opcodeEqual(0x34, Insn.alu(.div, .{ .k = 0 }));
462 try opcodeEqual(0x44, Insn.alu(.@"or", .{ .k = 0 }));
463 try opcodeEqual(0x54, Insn.alu(.@"and", .{ .k = 0 }));
464 try opcodeEqual(0x64, Insn.alu(.lsh, .{ .k = 0 }));
465 try opcodeEqual(0x74, Insn.alu(.rsh, .{ .k = 0 }));
466 try opcodeEqual(0x94, Insn.alu(.mod, .{ .k = 0 }));
467 try opcodeEqual(0xa4, Insn.alu(.xor, .{ .k = 0 }));
468 try opcodeEqual(0x84, Insn.alu_neg());
469 try opcodeEqual(0x0c, Insn.alu(.add, .x));
470 try opcodeEqual(0x1c, Insn.alu(.sub, .x));
471 try opcodeEqual(0x2c, Insn.alu(.mul, .x));
472 try opcodeEqual(0x3c, Insn.alu(.div, .x));
473 try opcodeEqual(0x4c, Insn.alu(.@"or", .x));
474 try opcodeEqual(0x5c, Insn.alu(.@"and", .x));
475 try opcodeEqual(0x6c, Insn.alu(.lsh, .x));
476 try opcodeEqual(0x7c, Insn.alu(.rsh, .x));
477 try opcodeEqual(0x9c, Insn.alu(.mod, .x));
478 try opcodeEqual(0xac, Insn.alu(.xor, .x));
479
480 try opcodeEqual(0x05, Insn.jmp_ja(0));
481 try opcodeEqual(0x15, Insn.jmp(.jeq, .{ .k = 0 }, 0, 0));
482 try opcodeEqual(0x25, Insn.jmp(.jgt, .{ .k = 0 }, 0, 0));
483 try opcodeEqual(0x35, Insn.jmp(.jge, .{ .k = 0 }, 0, 0));
484 try opcodeEqual(0x45, Insn.jmp(.jset, .{ .k = 0 }, 0, 0));
485 try opcodeEqual(0x1d, Insn.jmp(.jeq, .x, 0, 0));
486 try opcodeEqual(0x2d, Insn.jmp(.jgt, .x, 0, 0));
487 try opcodeEqual(0x3d, Insn.jmp(.jge, .x, 0, 0));
488 try opcodeEqual(0x4d, Insn.jmp(.jset, .x, 0, 0));
489
490 try opcodeEqual(0x06, Insn.ret(.{ .k = 0 }));
491 try opcodeEqual(0x16, Insn.ret(.a));
492
493 try opcodeEqual(0x07, Insn.tax());
494 try opcodeEqual(0x87, Insn.txa());
495}
496
497pub const Error = error{
498 InvalidOpcode,
499 InvalidOffset,
500 InvalidLocation,
501 DivisionByZero,
502 NoReturn,
503};
504
505/// A simple implementation of the BPF virtual-machine.
506/// Use this to run/debug programs.
507pub fn simulate(
508 packet: []const u8,
509 filter: []const Insn,
510 byte_order: std.builtin.Endian,
511) Error!u32 {
512 assert(filter.len > 0 and filter.len < MAXINSNS);
513 assert(packet.len < MAXBUFSIZE);
514 const len = @intCast(u32, packet.len);
515
516 var a: u32 = 0;
517 var x: u32 = 0;
518 var m = mem.zeroes([MEMWORDS]u32);
519 var pc: usize = 0;
520
521 while (pc < filter.len) : (pc += 1) {
522 const i = filter[pc];
523 // Cast to a wider type to protect against overflow.
524 const k = @as(u64, i.k);
525 const remaining = filter.len - (pc + 1);
526
527 // Do validation/error checking here to compress the second switch.
528 switch (i.opcode) {
529 LD | ABS | W => if (k + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset,
530 LD | ABS | H => if (k + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset,
531 LD | ABS | B => if (k >= packet.len) return error.InvalidOffset,
532 LD | IND | W => if (k + x + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset,
533 LD | IND | H => if (k + x + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset,
534 LD | IND | B => if (k + x >= packet.len) return error.InvalidOffset,
535
536 LDX | MSH | B => if (k >= packet.len) return error.InvalidOffset,
537 ST, STX, LD | MEM, LDX | MEM => if (i.k >= MEMWORDS) return error.InvalidOffset,
538
539 JMP | JA => if (remaining <= i.k) return error.InvalidOffset,
540 JMP | JEQ | K,
541 JMP | JGT | K,
542 JMP | JGE | K,
543 JMP | JSET | K,
544 JMP | JEQ | X,
545 JMP | JGT | X,
546 JMP | JGE | X,
547 JMP | JSET | X,
548 => if (remaining <= i.jt or remaining <= i.jf) return error.InvalidLocation,
549 else => {},
550 }
551 switch (i.opcode) {
552 LD | IMM => a = i.k,
553 LD | MEM => a = m[i.k],
554 LD | LEN | W => a = len,
555 LD | RND | W => a = random.int(u32),
556 LD | ABS | W => a = mem.readInt(u32, packet[i.k..][0..@sizeOf(u32)], byte_order),
557 LD | ABS | H => a = mem.readInt(u16, packet[i.k..][0..@sizeOf(u16)], byte_order),
558 LD | ABS | B => a = packet[i.k],
559 LD | IND | W => a = mem.readInt(u32, packet[i.k + x ..][0..@sizeOf(u32)], byte_order),
560 LD | IND | H => a = mem.readInt(u16, packet[i.k + x ..][0..@sizeOf(u16)], byte_order),
561 LD | IND | B => a = packet[i.k + x],
562
563 LDX | IMM => x = i.k,
564 LDX | MEM => x = m[i.k],
565 LDX | LEN | W => x = len,
566 LDX | MSH | B => x = @as(u32, @truncate(u4, packet[i.k])) << 2,
567
568 ST => m[i.k] = a,
569 STX => m[i.k] = x,
570
571 ALU | ADD | K => a +%= i.k,
572 ALU | SUB | K => a -%= i.k,
573 ALU | MUL | K => a *%= i.k,
574 ALU | DIV | K => a = try math.divTrunc(u32, a, i.k),
575 ALU | OR | K => a |= i.k,
576 ALU | AND | K => a &= i.k,
577 ALU | LSH | K => a = math.shl(u32, a, i.k),
578 ALU | RSH | K => a = math.shr(u32, a, i.k),
579 ALU | MOD | K => a = try math.mod(u32, a, i.k),
580 ALU | XOR | K => a ^= i.k,
581 ALU | ADD | X => a +%= x,
582 ALU | SUB | X => a -%= x,
583 ALU | MUL | X => a *%= x,
584 ALU | DIV | X => a = try math.divTrunc(u32, a, x),
585 ALU | OR | X => a |= x,
586 ALU | AND | X => a &= x,
587 ALU | LSH | X => a = math.shl(u32, a, x),
588 ALU | RSH | X => a = math.shr(u32, a, x),
589 ALU | MOD | X => a = try math.mod(u32, a, x),
590 ALU | XOR | X => a ^= x,
591 ALU | NEG => a = @bitCast(u32, -%@bitCast(i32, a)),
592
593 JMP | JA => pc += i.k,
594 JMP | JEQ | K => pc += if (a == i.k) i.jt else i.jf,
595 JMP | JGT | K => pc += if (a > i.k) i.jt else i.jf,
596 JMP | JGE | K => pc += if (a >= i.k) i.jt else i.jf,
597 JMP | JSET | K => pc += if (a & i.k > 0) i.jt else i.jf,
598 JMP | JEQ | X => pc += if (a == x) i.jt else i.jf,
599 JMP | JGT | X => pc += if (a > x) i.jt else i.jf,
600 JMP | JGE | X => pc += if (a >= x) i.jt else i.jf,
601 JMP | JSET | X => pc += if (a & x > 0) i.jt else i.jf,
602
603 RET | K => return i.k,
604 RET | A => return a,
605
606 MISC | TAX => x = a,
607 MISC | TXA => a = x,
608 else => return error.InvalidOpcode,
609 }
610 }
611
612 return error.NoReturn;
613}
614
615// This program is the BPF form of the tcpdump filter:
616//
617// tcpdump -dd 'ip host mirror.internode.on.net and tcp port ftp-data'
618//
619// As of January 2022, mirror.internode.on.net resolves to 150.101.135.3
620//
621// For reference, here's what it looks like in BPF assembler.
622// Note that the jumps are used for TCP/IP layer checks.
623//
624// ```
625// ldh [12] (#proto)
626// jeq #0x0800 (ETHERTYPE_IP), L1, fail
627// L1: ld [26]
628// jeq #150.101.135.3, L2, dest
629// dest: ld [30]
630// jeq #150.101.135.3, L2, fail
631// L2: ldb [23]
632// jeq #0x6 (IPPROTO_TCP), L3, fail
633// L3: ldh [20]
634// jset #0x1fff, fail, plen
635// plen: ldx 4 * ([14] & 0xf)
636// ldh [x + 14]
637// jeq #0x14 (FTP), pass, dstp
638// dstp: ldh [x + 16]
639// jeq #0x14 (FTP), pass, fail
640// pass: ret #0x40000
641// fail: ret #0
642// ```
643const tcpdump_filter = [_]Insn{
644 Insn.ld_abs(.half_word, 12),
645 Insn.jmp(.jeq, .{ .k = 0x800 }, 0, 14),
646 Insn.ld_abs(.word, 26),
647 Insn.jmp(.jeq, .{ .k = 0x96658703 }, 2, 0),
648 Insn.ld_abs(.word, 30),
649 Insn.jmp(.jeq, .{ .k = 0x96658703 }, 0, 10),
650 Insn.ld_abs(.byte, 23),
651 Insn.jmp(.jeq, .{ .k = 0x6 }, 0, 8),
652 Insn.ld_abs(.half_word, 20),
653 Insn.jmp(.jset, .{ .k = 0x1fff }, 6, 0),
654 Insn.ldx_msh(14),
655 Insn.ld_ind(.half_word, 14),
656 Insn.jmp(.jeq, .{ .k = 0x14 }, 2, 0),
657 Insn.ld_ind(.half_word, 16),
658 Insn.jmp(.jeq, .{ .k = 0x14 }, 0, 1),
659 Insn.ret(.{ .k = 0x40000 }),
660 Insn.ret(.{ .k = 0 }),
661};
662
663// This packet is the output of `ls` on mirror.internode.on.net:/, captured
664// using the filter above.
665//
666// zig fmt: off
667const ftp_data = [_]u8{
668 // ethernet - 14 bytes: IPv4(0x0800) from a4:71:74:ad:4b:f0 -> de:ad:be:ef:f0:0f
669 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0f, 0xa4, 0x71, 0x74, 0xad, 0x4b, 0xf0, 0x08, 0x00,
670 // IPv4 - 20 bytes: TCP data from 150.101.135.3 -> 192.168.1.3
671 0x45, 0x00, 0x01, 0xf2, 0x70, 0x3b, 0x40, 0x00, 0x37, 0x06, 0xf2, 0xb6,
672 0x96, 0x65, 0x87, 0x03, 0xc0, 0xa8, 0x01, 0x03,
673 // TCP - 32 bytes: Source port: 20 (FTP). Payload = 446 bytes
674 0x00, 0x14, 0x80, 0x6d, 0x35, 0x81, 0x2d, 0x40, 0x4f, 0x8a, 0x29, 0x9e, 0x80, 0x18, 0x00, 0x2e,
675 0x88, 0x8d, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x0b, 0x59, 0x5d, 0x09, 0x32, 0x8b, 0x51, 0xa0
676} ++
677 // Raw line-based FTP data - 446 bytes
678 "lrwxrwxrwx 1 root root 12 Feb 14 2012 debian -> .pub2/debian\r\n" ++
679 "lrwxrwxrwx 1 root root 15 Feb 14 2012 debian-cd -> .pub2/debian-cd\r\n" ++
680 "lrwxrwxrwx 1 root root 9 Mar 9 2018 linux -> pub/linux\r\n" ++
681 "drwxr-xr-X 3 mirror mirror 4096 Sep 20 08:10 pub\r\n" ++
682 "lrwxrwxrwx 1 root root 12 Feb 14 2012 ubuntu -> .pub2/ubuntu\r\n" ++
683 "-rw-r--r-- 1 root root 1044 Jan 20 2015 welcome.msg\r\n";
684// zig fmt: on
685
686test "tcpdump filter" {
687 try expectEqual(
688 @as(u32, 0x40000),
689 try simulate(ftp_data, &tcpdump_filter, .Big),
690 );
691}
692
693fn expectPass(data: anytype, filter: []const Insn) !void {
694 try expectEqual(
695 @as(u32, 0),
696 try simulate(mem.asBytes(data), filter, .Big),
697 );
698}
699
700fn expectFail(expected_error: anyerror, data: anytype, filter: []const Insn) !void {
701 try expectError(
702 expected_error,
703 simulate(mem.asBytes(data), filter, native_endian),
704 );
705}
706
707test "simulator coverage" {
708 const some_data = [_]u8{
709 0xaa, 0xbb, 0xcc, 0xdd, 0x7f,
710 };
711
712 try expectPass(&some_data, &.{
713 // ld #10
714 // ldx #1
715 // st M[0]
716 // stx M[1]
717 // fail if A != 10
718 Insn.ld_imm(10),
719 Insn.ldx_imm(1),
720 Insn.st(.m0),
721 Insn.stx(.m1),
722 Insn.jmp(.jeq, .{ .k = 10 }, 1, 0),
723 Insn.ret(.{ .k = 1 }),
724 // ld [0]
725 // fail if A != 0xaabbccdd
726 Insn.ld_abs(.word, 0),
727 Insn.jmp(.jeq, .{ .k = 0xaabbccdd }, 1, 0),
728 Insn.ret(.{ .k = 2 }),
729 // ldh [0]
730 // fail if A != 0xaabb
731 Insn.ld_abs(.half_word, 0),
732 Insn.jmp(.jeq, .{ .k = 0xaabb }, 1, 0),
733 Insn.ret(.{ .k = 3 }),
734 // ldb [0]
735 // fail if A != 0xaa
736 Insn.ld_abs(.byte, 0),
737 Insn.jmp(.jeq, .{ .k = 0xaa }, 1, 0),
738 Insn.ret(.{ .k = 4 }),
739 // ld [x + 0]
740 // fail if A != 0xbbccdd7f
741 Insn.ld_ind(.word, 0),
742 Insn.jmp(.jeq, .{ .k = 0xbbccdd7f }, 1, 0),
743 Insn.ret(.{ .k = 5 }),
744 // ldh [x + 0]
745 // fail if A != 0xbbcc
746 Insn.ld_ind(.half_word, 0),
747 Insn.jmp(.jeq, .{ .k = 0xbbcc }, 1, 0),
748 Insn.ret(.{ .k = 6 }),
749 // ldb [x + 0]
750 // fail if A != 0xbb
751 Insn.ld_ind(.byte, 0),
752 Insn.jmp(.jeq, .{ .k = 0xbb }, 1, 0),
753 Insn.ret(.{ .k = 7 }),
754 // ld M[0]
755 // fail if A != 10
756 Insn.ld_mem(.m0),
757 Insn.jmp(.jeq, .{ .k = 10 }, 1, 0),
758 Insn.ret(.{ .k = 8 }),
759 // ld #len
760 // fail if A != 5
761 Insn.ld_len(),
762 Insn.jmp(.jeq, .{ .k = some_data.len }, 1, 0),
763 Insn.ret(.{ .k = 9 }),
764 // ld #0
765 // ld arc4random()
766 // fail if A == 0
767 Insn.ld_imm(0),
768 Insn.ld_rnd(),
769 Insn.jmp(.jgt, .{ .k = 0 }, 1, 0),
770 Insn.ret(.{ .k = 10 }),
771 // ld #3
772 // ldx #10
773 // st M[2]
774 // txa
775 // fail if a != x
776 Insn.ld_imm(3),
777 Insn.ldx_imm(10),
778 Insn.st(.m2),
779 Insn.txa(),
780 Insn.jmp(.jeq, .x, 1, 0),
781 Insn.ret(.{ .k = 11 }),
782 // ldx M[2]
783 // fail if A <= X
784 Insn.ldx_mem(.m2),
785 Insn.jmp(.jgt, .x, 1, 0),
786 Insn.ret(.{ .k = 12 }),
787 // ldx #len
788 // fail if a <= x
789 Insn.ldx_len(),
790 Insn.jmp(.jgt, .x, 1, 0),
791 Insn.ret(.{ .k = 13 }),
792 // a = 4 * (0x7f & 0xf)
793 // x = 4 * ([4] & 0xf)
794 // fail if a != x
795 Insn.ld_imm(4 * (0x7f & 0xf)),
796 Insn.ldx_msh(4),
797 Insn.jmp(.jeq, .x, 1, 0),
798 Insn.ret(.{ .k = 14 }),
799 // ld #(u32)-1
800 // ldx #2
801 // add #1
802 // fail if a != 0
803 Insn.ld_imm(0xffffffff),
804 Insn.ldx_imm(2),
805 Insn.alu(.add, .{ .k = 1 }),
806 Insn.jmp(.jeq, .{ .k = 0 }, 1, 0),
807 Insn.ret(.{ .k = 15 }),
808 // sub #1
809 // fail if a != (u32)-1
810 Insn.alu(.sub, .{ .k = 1 }),
811 Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0),
812 Insn.ret(.{ .k = 16 }),
813 // add x
814 // fail if a != 1
815 Insn.alu(.add, .x),
816 Insn.jmp(.jeq, .{ .k = 1 }, 1, 0),
817 Insn.ret(.{ .k = 17 }),
818 // sub x
819 // fail if a != (u32)-1
820 Insn.alu(.sub, .x),
821 Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0),
822 Insn.ret(.{ .k = 18 }),
823 // ld #16
824 // mul #2
825 // fail if a != 32
826 Insn.ld_imm(16),
827 Insn.alu(.mul, .{ .k = 2 }),
828 Insn.jmp(.jeq, .{ .k = 32 }, 1, 0),
829 Insn.ret(.{ .k = 19 }),
830 // mul x
831 // fail if a != 64
832 Insn.alu(.mul, .x),
833 Insn.jmp(.jeq, .{ .k = 64 }, 1, 0),
834 Insn.ret(.{ .k = 20 }),
835 // div #2
836 // fail if a != 32
837 Insn.alu(.div, .{ .k = 2 }),
838 Insn.jmp(.jeq, .{ .k = 32 }, 1, 0),
839 Insn.ret(.{ .k = 21 }),
840 // div x
841 // fail if a != 16
842 Insn.alu(.div, .x),
843 Insn.jmp(.jeq, .{ .k = 16 }, 1, 0),
844 Insn.ret(.{ .k = 22 }),
845 // or #4
846 // fail if a != 20
847 Insn.alu(.@"or", .{ .k = 4 }),
848 Insn.jmp(.jeq, .{ .k = 20 }, 1, 0),
849 Insn.ret(.{ .k = 23 }),
850 // or x
851 // fail if a != 22
852 Insn.alu(.@"or", .x),
853 Insn.jmp(.jeq, .{ .k = 22 }, 1, 0),
854 Insn.ret(.{ .k = 24 }),
855 // and #6
856 // fail if a != 6
857 Insn.alu(.@"and", .{ .k = 0b110 }),
858 Insn.jmp(.jeq, .{ .k = 6 }, 1, 0),
859 Insn.ret(.{ .k = 25 }),
860 // and x
861 // fail if a != 2
862 Insn.alu(.@"and", .x),
863 Insn.jmp(.jeq, .x, 1, 0),
864 Insn.ret(.{ .k = 26 }),
865 // xor #15
866 // fail if a != 13
867 Insn.alu(.xor, .{ .k = 0b1111 }),
868 Insn.jmp(.jeq, .{ .k = 0b1101 }, 1, 0),
869 Insn.ret(.{ .k = 27 }),
870 // xor x
871 // fail if a != 15
872 Insn.alu(.xor, .x),
873 Insn.jmp(.jeq, .{ .k = 0b1111 }, 1, 0),
874 Insn.ret(.{ .k = 28 }),
875 // rsh #1
876 // fail if a != 7
877 Insn.alu(.rsh, .{ .k = 1 }),
878 Insn.jmp(.jeq, .{ .k = 0b0111 }, 1, 0),
879 Insn.ret(.{ .k = 29 }),
880 // rsh x
881 // fail if a != 1
882 Insn.alu(.rsh, .x),
883 Insn.jmp(.jeq, .{ .k = 0b0001 }, 1, 0),
884 Insn.ret(.{ .k = 30 }),
885 // lsh #1
886 // fail if a != 2
887 Insn.alu(.lsh, .{ .k = 1 }),
888 Insn.jmp(.jeq, .{ .k = 0b0010 }, 1, 0),
889 Insn.ret(.{ .k = 31 }),
890 // lsh x
891 // fail if a != 8
892 Insn.alu(.lsh, .x),
893 Insn.jmp(.jeq, .{ .k = 0b1000 }, 1, 0),
894 Insn.ret(.{ .k = 32 }),
895 // mod 6
896 // fail if a != 2
897 Insn.alu(.mod, .{ .k = 6 }),
898 Insn.jmp(.jeq, .{ .k = 2 }, 1, 0),
899 Insn.ret(.{ .k = 33 }),
900 // mod x
901 // fail if a != 0
902 Insn.alu(.mod, .x),
903 Insn.jmp(.jeq, .{ .k = 0 }, 1, 0),
904 Insn.ret(.{ .k = 34 }),
905 // tax
906 // neg
907 // fail if a != (u32)-2
908 Insn.txa(),
909 Insn.alu_neg(),
910 Insn.jmp(.jeq, .{ .k = ~@as(u32, 2) + 1 }, 1, 0),
911 Insn.ret(.{ .k = 35 }),
912 // ja #1 (skip the next instruction)
913 Insn.jmp_ja(1),
914 Insn.ret(.{ .k = 36 }),
915 // ld #20
916 // tax
917 // fail if a != 20
918 // fail if a != x
919 Insn.ld_imm(20),
920 Insn.tax(),
921 Insn.jmp(.jeq, .{ .k = 20 }, 1, 0),
922 Insn.ret(.{ .k = 37 }),
923 Insn.jmp(.jeq, .x, 1, 0),
924 Insn.ret(.{ .k = 38 }),
925 // ld #19
926 // fail if a == 20
927 // fail if a == x
928 // fail if a >= 20
929 // fail if a >= X
930 Insn.ld_imm(19),
931 Insn.jmp(.jeq, .{ .k = 20 }, 0, 1),
932 Insn.ret(.{ .k = 39 }),
933 Insn.jmp(.jeq, .x, 0, 1),
934 Insn.ret(.{ .k = 40 }),
935 Insn.jmp(.jgt, .{ .k = 20 }, 0, 1),
936 Insn.ret(.{ .k = 41 }),
937 Insn.jmp(.jgt, .x, 0, 1),
938 Insn.ret(.{ .k = 42 }),
939 // ld #21
940 // fail if a < 20
941 // fail if a < x
942 Insn.ld_imm(21),
943 Insn.jmp(.jgt, .{ .k = 20 }, 1, 0),
944 Insn.ret(.{ .k = 43 }),
945 Insn.jmp(.jgt, .x, 1, 0),
946 Insn.ret(.{ .k = 44 }),
947 // ldx #22
948 // fail if a < 22
949 // fail if a < x
950 Insn.ldx_imm(22),
951 Insn.jmp(.jge, .{ .k = 22 }, 0, 1),
952 Insn.ret(.{ .k = 45 }),
953 Insn.jmp(.jge, .x, 0, 1),
954 Insn.ret(.{ .k = 46 }),
955 // ld #23
956 // fail if a >= 22
957 // fail if a >= x
958 Insn.ld_imm(23),
959 Insn.jmp(.jge, .{ .k = 22 }, 1, 0),
960 Insn.ret(.{ .k = 47 }),
961 Insn.jmp(.jge, .x, 1, 0),
962 Insn.ret(.{ .k = 48 }),
963 // ldx #0b10100
964 // fail if a & 0b10100 == 0
965 // fail if a & x == 0
966 Insn.ldx_imm(0b10100),
967 Insn.jmp(.jset, .{ .k = 0b10100 }, 1, 0),
968 Insn.ret(.{ .k = 47 }),
969 Insn.jmp(.jset, .x, 1, 0),
970 Insn.ret(.{ .k = 48 }),
971 // ldx #0
972 // fail if a & 0 > 0
973 // fail if a & x > 0
974 Insn.ldx_imm(0),
975 Insn.jmp(.jset, .{ .k = 0 }, 0, 1),
976 Insn.ret(.{ .k = 49 }),
977 Insn.jmp(.jset, .x, 0, 1),
978 Insn.ret(.{ .k = 50 }),
979 Insn.ret(.{ .k = 0 }),
980 });
981 try expectPass(&some_data, &.{
982 Insn.ld_imm(35),
983 Insn.ld_imm(0),
984 Insn.ret(.a),
985 });
986
987 // Errors
988 try expectFail(error.NoReturn, &some_data, &.{
989 Insn.ld_imm(10),
990 });
991 try expectFail(error.InvalidOpcode, &some_data, &.{
992 Insn.stmt(0x7f, 0xdeadbeef),
993 });
994 try expectFail(error.InvalidOffset, &some_data, &.{
995 Insn.stmt(LD | ABS | W, 10),
996 });
997 try expectFail(error.InvalidLocation, &some_data, &.{
998 Insn.jmp(.jeq, .{ .k = 0 }, 10, 0),
999 });
1000 try expectFail(error.InvalidLocation, &some_data, &.{
1001 Insn.jmp(.jeq, .{ .k = 0 }, 0, 10),
1002 });
1003}
lib/std/x/net/ip.zig deleted-57
...@@ -1,57 +0,0 @@
1const std = @import("../../std.zig");
2
3const fmt = std.fmt;
4
5const IPv4 = std.x.os.IPv4;
6const IPv6 = std.x.os.IPv6;
7const Socket = std.x.os.Socket;
8
9/// A generic IP abstraction.
10const ip = @This();
11
12/// A union of all eligible types of IP addresses.
13pub const Address = union(enum) {
14 ipv4: IPv4.Address,
15 ipv6: IPv6.Address,
16
17 /// Instantiate a new address with a IPv4 host and port.
18 pub fn initIPv4(host: IPv4, port: u16) Address {
19 return .{ .ipv4 = .{ .host = host, .port = port } };
20 }
21
22 /// Instantiate a new address with a IPv6 host and port.
23 pub fn initIPv6(host: IPv6, port: u16) Address {
24 return .{ .ipv6 = .{ .host = host, .port = port } };
25 }
26
27 /// Re-interpret a generic socket address into an IP address.
28 pub fn from(address: Socket.Address) ip.Address {
29 return switch (address) {
30 .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
31 .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
32 };
33 }
34
35 /// Re-interpret an IP address into a generic socket address.
36 pub fn into(self: ip.Address) Socket.Address {
37 return switch (self) {
38 .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
39 .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
40 };
41 }
42
43 /// Implements the `std.fmt.format` API.
44 pub fn format(
45 self: ip.Address,
46 comptime layout: []const u8,
47 opts: fmt.FormatOptions,
48 writer: anytype,
49 ) !void {
50 if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
51 _ = opts;
52 switch (self) {
53 .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
54 .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
55 }
56 }
57};
lib/std/x/net/tcp.zig deleted-447
...@@ -1,447 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3
4const io = std.io;
5const os = std.os;
6const ip = std.x.net.ip;
7
8const fmt = std.fmt;
9const mem = std.mem;
10const testing = std.testing;
11const native_os = builtin.os;
12
13const IPv4 = std.x.os.IPv4;
14const IPv6 = std.x.os.IPv6;
15const Socket = std.x.os.Socket;
16const Buffer = std.x.os.Buffer;
17
18/// A generic TCP socket abstraction.
19const tcp = @This();
20
21/// A TCP client-address pair.
22pub const Connection = struct {
23 client: tcp.Client,
24 address: ip.Address,
25
26 /// Enclose a TCP client and address into a client-address pair.
27 pub fn from(conn: Socket.Connection) tcp.Connection {
28 return .{
29 .client = tcp.Client.from(conn.socket),
30 .address = ip.Address.from(conn.address),
31 };
32 }
33
34 /// Unravel a TCP client-address pair into a socket-address pair.
35 pub fn into(self: tcp.Connection) Socket.Connection {
36 return .{
37 .socket = self.client.socket,
38 .address = self.address.into(),
39 };
40 }
41
42 /// Closes the underlying client of the connection.
43 pub fn deinit(self: tcp.Connection) void {
44 self.client.deinit();
45 }
46};
47
48/// Possible domains that a TCP client/listener may operate over.
49pub const Domain = enum(u16) {
50 ip = os.AF.INET,
51 ipv6 = os.AF.INET6,
52};
53
54/// A TCP client.
55pub const Client = struct {
56 socket: Socket,
57
58 /// Implements `std.io.Reader`.
59 pub const Reader = struct {
60 client: Client,
61 flags: u32,
62
63 /// Implements `readFn` for `std.io.Reader`.
64 pub fn read(self: Client.Reader, buffer: []u8) !usize {
65 return self.client.read(buffer, self.flags);
66 }
67 };
68
69 /// Implements `std.io.Writer`.
70 pub const Writer = struct {
71 client: Client,
72 flags: u32,
73
74 /// Implements `writeFn` for `std.io.Writer`.
75 pub fn write(self: Client.Writer, buffer: []const u8) !usize {
76 return self.client.write(buffer, self.flags);
77 }
78 };
79
80 /// Opens a new client.
81 pub fn init(domain: tcp.Domain, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Client {
82 return Client{
83 .socket = try Socket.init(
84 @enumToInt(domain),
85 os.SOCK.STREAM,
86 os.IPPROTO.TCP,
87 flags,
88 ),
89 };
90 }
91
92 /// Enclose a TCP client over an existing socket.
93 pub fn from(socket: Socket) Client {
94 return Client{ .socket = socket };
95 }
96
97 /// Closes the client.
98 pub fn deinit(self: Client) void {
99 self.socket.deinit();
100 }
101
102 /// Shutdown either the read side, write side, or all sides of the client's underlying socket.
103 pub fn shutdown(self: Client, how: os.ShutdownHow) !void {
104 return self.socket.shutdown(how);
105 }
106
107 /// Have the client attempt to the connect to an address.
108 pub fn connect(self: Client, address: ip.Address) !void {
109 return self.socket.connect(address.into());
110 }
111
112 /// Extracts the error set of a function.
113 /// TODO: remove after Socket.{read, write} error unions are well-defined across different platforms
114 fn ErrorSetOf(comptime Function: anytype) type {
115 return @typeInfo(@typeInfo(@TypeOf(Function)).Fn.return_type.?).ErrorUnion.error_set;
116 }
117
118 /// Wrap `tcp.Client` into `std.io.Reader`.
119 pub fn reader(self: Client, flags: u32) io.Reader(Client.Reader, ErrorSetOf(Client.Reader.read), Client.Reader.read) {
120 return .{ .context = .{ .client = self, .flags = flags } };
121 }
122
123 /// Wrap `tcp.Client` into `std.io.Writer`.
124 pub fn writer(self: Client, flags: u32) io.Writer(Client.Writer, ErrorSetOf(Client.Writer.write), Client.Writer.write) {
125 return .{ .context = .{ .client = self, .flags = flags } };
126 }
127
128 /// Read data from the socket into the buffer provided with a set of flags
129 /// specified. It returns the number of bytes read into the buffer provided.
130 pub fn read(self: Client, buf: []u8, flags: u32) !usize {
131 return self.socket.read(buf, flags);
132 }
133
134 /// Write a buffer of data provided to the socket with a set of flags specified.
135 /// It returns the number of bytes that are written to the socket.
136 pub fn write(self: Client, buf: []const u8, flags: u32) !usize {
137 return self.socket.write(buf, flags);
138 }
139
140 /// Writes multiple I/O vectors with a prepended message header to the socket
141 /// with a set of flags specified. It returns the number of bytes that are
142 /// written to the socket.
143 pub fn writeMessage(self: Client, msg: Socket.Message, flags: u32) !usize {
144 return self.socket.writeMessage(msg, flags);
145 }
146
147 /// Read multiple I/O vectors with a prepended message header from the socket
148 /// with a set of flags specified. It returns the number of bytes that were
149 /// read into the buffer provided.
150 pub fn readMessage(self: Client, msg: *Socket.Message, flags: u32) !usize {
151 return self.socket.readMessage(msg, flags);
152 }
153
154 /// Query and return the latest cached error on the client's underlying socket.
155 pub fn getError(self: Client) !void {
156 return self.socket.getError();
157 }
158
159 /// Query the read buffer size of the client's underlying socket.
160 pub fn getReadBufferSize(self: Client) !u32 {
161 return self.socket.getReadBufferSize();
162 }
163
164 /// Query the write buffer size of the client's underlying socket.
165 pub fn getWriteBufferSize(self: Client) !u32 {
166 return self.socket.getWriteBufferSize();
167 }
168
169 /// Query the address that the client's socket is locally bounded to.
170 pub fn getLocalAddress(self: Client) !ip.Address {
171 return ip.Address.from(try self.socket.getLocalAddress());
172 }
173
174 /// Query the address that the socket is connected to.
175 pub fn getRemoteAddress(self: Client) !ip.Address {
176 return ip.Address.from(try self.socket.getRemoteAddress());
177 }
178
179 /// Have close() or shutdown() syscalls block until all queued messages in the client have been successfully
180 /// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
181 /// if the host does not support the option for a socket to linger around up until a timeout specified in
182 /// seconds.
183 pub fn setLinger(self: Client, timeout_seconds: ?u16) !void {
184 return self.socket.setLinger(timeout_seconds);
185 }
186
187 /// Have keep-alive messages be sent periodically. The timing in which keep-alive messages are sent are
188 /// dependant on operating system settings. It returns `error.UnsupportedSocketOption` if the host does
189 /// not support periodically sending keep-alive messages on connection-oriented sockets.
190 pub fn setKeepAlive(self: Client, enabled: bool) !void {
191 return self.socket.setKeepAlive(enabled);
192 }
193
194 /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
195 /// the host does not support sockets disabling Nagle's algorithm.
196 pub fn setNoDelay(self: Client, enabled: bool) !void {
197 if (@hasDecl(os.TCP, "NODELAY")) {
198 const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
199 return self.socket.setOption(os.IPPROTO.TCP, os.TCP.NODELAY, bytes);
200 }
201 return error.UnsupportedSocketOption;
202 }
203
204 /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
205 /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
206 pub fn setQuickACK(self: Client, enabled: bool) !void {
207 if (@hasDecl(os.TCP, "QUICKACK")) {
208 return self.socket.setOption(os.IPPROTO.TCP, os.TCP.QUICKACK, mem.asBytes(&@as(u32, @boolToInt(enabled))));
209 }
210 return error.UnsupportedSocketOption;
211 }
212
213 /// Set the write buffer size of the socket.
214 pub fn setWriteBufferSize(self: Client, size: u32) !void {
215 return self.socket.setWriteBufferSize(size);
216 }
217
218 /// Set the read buffer size of the socket.
219 pub fn setReadBufferSize(self: Client, size: u32) !void {
220 return self.socket.setReadBufferSize(size);
221 }
222
223 /// Set a timeout on the socket that is to occur if no messages are successfully written
224 /// to its bound destination after a specified number of milliseconds. A subsequent write
225 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
226 pub fn setWriteTimeout(self: Client, milliseconds: u32) !void {
227 return self.socket.setWriteTimeout(milliseconds);
228 }
229
230 /// Set a timeout on the socket that is to occur if no messages are successfully read
231 /// from its bound destination after a specified number of milliseconds. A subsequent
232 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
233 /// exceeded.
234 pub fn setReadTimeout(self: Client, milliseconds: u32) !void {
235 return self.socket.setReadTimeout(milliseconds);
236 }
237};
238
239/// A TCP listener.
240pub const Listener = struct {
241 socket: Socket,
242
243 /// Opens a new listener.
244 pub fn init(domain: tcp.Domain, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Listener {
245 return Listener{
246 .socket = try Socket.init(
247 @enumToInt(domain),
248 os.SOCK.STREAM,
249 os.IPPROTO.TCP,
250 flags,
251 ),
252 };
253 }
254
255 /// Closes the listener.
256 pub fn deinit(self: Listener) void {
257 self.socket.deinit();
258 }
259
260 /// Shuts down the underlying listener's socket. The next subsequent call, or
261 /// a current pending call to accept() after shutdown is called will return
262 /// an error.
263 pub fn shutdown(self: Listener) !void {
264 return self.socket.shutdown(.recv);
265 }
266
267 /// Binds the listener's socket to an address.
268 pub fn bind(self: Listener, address: ip.Address) !void {
269 return self.socket.bind(address.into());
270 }
271
272 /// Start listening for incoming connections.
273 pub fn listen(self: Listener, max_backlog_size: u31) !void {
274 return self.socket.listen(max_backlog_size);
275 }
276
277 /// Accept a pending incoming connection queued to the kernel backlog
278 /// of the listener's socket.
279 pub fn accept(self: Listener, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !tcp.Connection {
280 return tcp.Connection.from(try self.socket.accept(flags));
281 }
282
283 /// Query and return the latest cached error on the listener's underlying socket.
284 pub fn getError(self: Client) !void {
285 return self.socket.getError();
286 }
287
288 /// Query the address that the listener's socket is locally bounded to.
289 pub fn getLocalAddress(self: Listener) !ip.Address {
290 return ip.Address.from(try self.socket.getLocalAddress());
291 }
292
293 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
294 /// the host does not support sockets listening the same address.
295 pub fn setReuseAddress(self: Listener, enabled: bool) !void {
296 return self.socket.setReuseAddress(enabled);
297 }
298
299 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
300 /// the host does not supports sockets listening on the same port.
301 pub fn setReusePort(self: Listener, enabled: bool) !void {
302 return self.socket.setReusePort(enabled);
303 }
304
305 /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
306 /// support TCP Fast Open.
307 pub fn setFastOpen(self: Listener, enabled: bool) !void {
308 if (@hasDecl(os.TCP, "FASTOPEN")) {
309 return self.socket.setOption(os.IPPROTO.TCP, os.TCP.FASTOPEN, mem.asBytes(&@as(u32, @boolToInt(enabled))));
310 }
311 return error.UnsupportedSocketOption;
312 }
313
314 /// Set a timeout on the listener that is to occur if no new incoming connections come in
315 /// after a specified number of milliseconds. A subsequent accept call to the listener
316 /// will thereafter return `error.WouldBlock` should the timeout be exceeded.
317 pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void {
318 return self.socket.setReadTimeout(milliseconds);
319 }
320};
321
322test "tcp: create client/listener pair" {
323 if (native_os.tag == .wasi) return error.SkipZigTest;
324
325 const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
326 defer listener.deinit();
327
328 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
329 try listener.listen(128);
330
331 var binded_address = try listener.getLocalAddress();
332 switch (binded_address) {
333 .ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
334 .ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
335 }
336
337 const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
338 defer client.deinit();
339
340 try client.connect(binded_address);
341
342 const conn = try listener.accept(.{ .close_on_exec = true });
343 defer conn.deinit();
344}
345
346test "tcp/client: 1ms read timeout" {
347 if (native_os.tag == .wasi) return error.SkipZigTest;
348
349 const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
350 defer listener.deinit();
351
352 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
353 try listener.listen(128);
354
355 var binded_address = try listener.getLocalAddress();
356 switch (binded_address) {
357 .ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
358 .ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
359 }
360
361 const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
362 defer client.deinit();
363
364 try client.connect(binded_address);
365 try client.setReadTimeout(1);
366
367 const conn = try listener.accept(.{ .close_on_exec = true });
368 defer conn.deinit();
369
370 var buf: [1]u8 = undefined;
371 try testing.expectError(error.WouldBlock, client.reader(0).read(&buf));
372}
373
374test "tcp/client: read and write multiple vectors" {
375 if (native_os.tag == .wasi) return error.SkipZigTest;
376
377 if (builtin.os.tag == .windows) {
378 // https://github.com/ziglang/zig/issues/13893
379 return error.SkipZigTest;
380 }
381
382 const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
383 defer listener.deinit();
384
385 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
386 try listener.listen(128);
387
388 var binded_address = try listener.getLocalAddress();
389 switch (binded_address) {
390 .ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
391 .ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
392 }
393
394 const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
395 defer client.deinit();
396
397 try client.connect(binded_address);
398
399 const conn = try listener.accept(.{ .close_on_exec = true });
400 defer conn.deinit();
401
402 const message = "hello world";
403 _ = try conn.client.writeMessage(Socket.Message.fromBuffers(&[_]Buffer{
404 Buffer.from(message[0 .. message.len / 2]),
405 Buffer.from(message[message.len / 2 ..]),
406 }), 0);
407
408 var buf: [message.len + 1]u8 = undefined;
409 var msg = Socket.Message.fromBuffers(&[_]Buffer{
410 Buffer.from(buf[0 .. message.len / 2]),
411 Buffer.from(buf[message.len / 2 ..]),
412 });
413 _ = try client.readMessage(&msg, 0);
414
415 try testing.expectEqualStrings(message, buf[0..message.len]);
416}
417
418test "tcp/listener: bind to unspecified ipv4 address" {
419 if (native_os.tag == .wasi) return error.SkipZigTest;
420
421 const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
422 defer listener.deinit();
423
424 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
425 try listener.listen(128);
426
427 const address = try listener.getLocalAddress();
428 try testing.expect(address == .ipv4);
429}
430
431test "tcp/listener: bind to unspecified ipv6 address" {
432 if (native_os.tag == .wasi) return error.SkipZigTest;
433
434 if (builtin.os.tag == .windows) {
435 // https://github.com/ziglang/zig/issues/13893
436 return error.SkipZigTest;
437 }
438
439 const listener = try tcp.Listener.init(.ipv6, .{ .close_on_exec = true });
440 defer listener.deinit();
441
442 try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0));
443 try listener.listen(128);
444
445 const address = try listener.getLocalAddress();
446 try testing.expect(address == .ipv6);
447}
lib/std/x/os/io.zig deleted-224
...@@ -1,224 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3
4const os = std.os;
5const mem = std.mem;
6const testing = std.testing;
7const native_os = builtin.os;
8const linux = std.os.linux;
9
10/// POSIX `iovec`, or Windows `WSABUF`. The difference between the two are the ordering
11/// of fields, alongside the length being represented as either a ULONG or a size_t.
12pub const Buffer = if (native_os.tag == .windows)
13 extern struct {
14 len: c_ulong,
15 ptr: usize,
16
17 pub fn from(slice: []const u8) Buffer {
18 return .{ .len = @intCast(c_ulong, slice.len), .ptr = @ptrToInt(slice.ptr) };
19 }
20
21 pub fn into(self: Buffer) []const u8 {
22 return @intToPtr([*]const u8, self.ptr)[0..self.len];
23 }
24
25 pub fn intoMutable(self: Buffer) []u8 {
26 return @intToPtr([*]u8, self.ptr)[0..self.len];
27 }
28 }
29else
30 extern struct {
31 ptr: usize,
32 len: usize,
33
34 pub fn from(slice: []const u8) Buffer {
35 return .{ .ptr = @ptrToInt(slice.ptr), .len = slice.len };
36 }
37
38 pub fn into(self: Buffer) []const u8 {
39 return @intToPtr([*]const u8, self.ptr)[0..self.len];
40 }
41
42 pub fn intoMutable(self: Buffer) []u8 {
43 return @intToPtr([*]u8, self.ptr)[0..self.len];
44 }
45 };
46
47pub const Reactor = struct {
48 pub const InitFlags = enum {
49 close_on_exec,
50 };
51
52 pub const Event = struct {
53 data: usize,
54 is_error: bool,
55 is_hup: bool,
56 is_readable: bool,
57 is_writable: bool,
58 };
59
60 pub const Interest = struct {
61 hup: bool = false,
62 oneshot: bool = false,
63 readable: bool = false,
64 writable: bool = false,
65 };
66
67 fd: os.fd_t,
68
69 pub fn init(flags: std.enums.EnumFieldStruct(Reactor.InitFlags, bool, false)) !Reactor {
70 var raw_flags: u32 = 0;
71 const set = std.EnumSet(Reactor.InitFlags).init(flags);
72 if (set.contains(.close_on_exec)) raw_flags |= linux.EPOLL.CLOEXEC;
73 return Reactor{ .fd = try os.epoll_create1(raw_flags) };
74 }
75
76 pub fn deinit(self: Reactor) void {
77 os.close(self.fd);
78 }
79
80 pub fn update(self: Reactor, fd: os.fd_t, identifier: usize, interest: Reactor.Interest) !void {
81 var flags: u32 = 0;
82 flags |= if (interest.oneshot) linux.EPOLL.ONESHOT else linux.EPOLL.ET;
83 if (interest.hup) flags |= linux.EPOLL.RDHUP;
84 if (interest.readable) flags |= linux.EPOLL.IN;
85 if (interest.writable) flags |= linux.EPOLL.OUT;
86
87 const event = &linux.epoll_event{
88 .events = flags,
89 .data = .{ .ptr = identifier },
90 };
91
92 os.epoll_ctl(self.fd, linux.EPOLL.CTL_MOD, fd, event) catch |err| switch (err) {
93 error.FileDescriptorNotRegistered => try os.epoll_ctl(self.fd, linux.EPOLL.CTL_ADD, fd, event),
94 else => return err,
95 };
96 }
97
98 pub fn remove(self: Reactor, fd: os.fd_t) !void {
99 // directly from man epoll_ctl BUGS section
100 // In kernel versions before 2.6.9, the EPOLL_CTL_DEL operation re‐
101 // quired a non-null pointer in event, even though this argument is
102 // ignored. Since Linux 2.6.9, event can be specified as NULL when
103 // using EPOLL_CTL_DEL. Applications that need to be portable to
104 // kernels before 2.6.9 should specify a non-null pointer in event.
105 var event = linux.epoll_event{
106 .events = 0,
107 .data = .{ .ptr = 0 },
108 };
109
110 return os.epoll_ctl(self.fd, linux.EPOLL.CTL_DEL, fd, &event);
111 }
112
113 pub fn poll(self: Reactor, comptime max_num_events: comptime_int, closure: anytype, timeout_milliseconds: ?u64) !void {
114 var events: [max_num_events]linux.epoll_event = undefined;
115
116 const num_events = os.epoll_wait(self.fd, &events, if (timeout_milliseconds) |ms| @intCast(i32, ms) else -1);
117 for (events[0..num_events]) |ev| {
118 const is_error = ev.events & linux.EPOLL.ERR != 0;
119 const is_hup = ev.events & (linux.EPOLL.HUP | linux.EPOLL.RDHUP) != 0;
120 const is_readable = ev.events & linux.EPOLL.IN != 0;
121 const is_writable = ev.events & linux.EPOLL.OUT != 0;
122
123 try closure.call(Reactor.Event{
124 .data = ev.data.ptr,
125 .is_error = is_error,
126 .is_hup = is_hup,
127 .is_readable = is_readable,
128 .is_writable = is_writable,
129 });
130 }
131 }
132};
133
134test "reactor/linux: drive async tcp client/listener pair" {
135 if (native_os.tag != .linux) return error.SkipZigTest;
136
137 const ip = std.x.net.ip;
138 const tcp = std.x.net.tcp;
139
140 const IPv4 = std.x.os.IPv4;
141 const IPv6 = std.x.os.IPv6;
142
143 const reactor = try Reactor.init(.{ .close_on_exec = true });
144 defer reactor.deinit();
145
146 const listener = try tcp.Listener.init(.ip, .{
147 .close_on_exec = true,
148 .nonblocking = true,
149 });
150 defer listener.deinit();
151
152 try reactor.update(listener.socket.fd, 0, .{ .readable = true });
153 try reactor.poll(1, struct {
154 fn call(event: Reactor.Event) !void {
155 try testing.expectEqual(Reactor.Event{
156 .data = 0,
157 .is_error = false,
158 .is_hup = true,
159 .is_readable = false,
160 .is_writable = false,
161 }, event);
162 }
163 }, null);
164
165 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
166 try listener.listen(128);
167
168 var binded_address = try listener.getLocalAddress();
169 switch (binded_address) {
170 .ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
171 .ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
172 }
173
174 const client = try tcp.Client.init(.ip, .{
175 .close_on_exec = true,
176 .nonblocking = true,
177 });
178 defer client.deinit();
179
180 try reactor.update(client.socket.fd, 1, .{ .readable = true, .writable = true });
181 try reactor.poll(1, struct {
182 fn call(event: Reactor.Event) !void {
183 try testing.expectEqual(Reactor.Event{
184 .data = 1,
185 .is_error = false,
186 .is_hup = true,
187 .is_readable = false,
188 .is_writable = true,
189 }, event);
190 }
191 }, null);
192
193 client.connect(binded_address) catch |err| switch (err) {
194 error.WouldBlock => {},
195 else => return err,
196 };
197
198 try reactor.poll(1, struct {
199 fn call(event: Reactor.Event) !void {
200 try testing.expectEqual(Reactor.Event{
201 .data = 1,
202 .is_error = false,
203 .is_hup = false,
204 .is_readable = false,
205 .is_writable = true,
206 }, event);
207 }
208 }, null);
209
210 try reactor.poll(1, struct {
211 fn call(event: Reactor.Event) !void {
212 try testing.expectEqual(Reactor.Event{
213 .data = 0,
214 .is_error = false,
215 .is_hup = false,
216 .is_readable = true,
217 .is_writable = false,
218 }, event);
219 }
220 }, null);
221
222 try reactor.remove(client.socket.fd);
223 try reactor.remove(listener.socket.fd);
224}
lib/std/x/os/net.zig deleted-605
...@@ -1,605 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3
4const os = std.os;
5const fmt = std.fmt;
6const mem = std.mem;
7const math = std.math;
8const testing = std.testing;
9const native_os = builtin.os;
10const have_ifnamesize = @hasDecl(os.system, "IFNAMESIZE");
11
12pub const ResolveScopeIdError = error{
13 NameTooLong,
14 PermissionDenied,
15 AddressFamilyNotSupported,
16 ProtocolFamilyNotAvailable,
17 ProcessFdQuotaExceeded,
18 SystemFdQuotaExceeded,
19 SystemResources,
20 ProtocolNotSupported,
21 SocketTypeNotSupported,
22 InterfaceNotFound,
23 FileSystem,
24 Unexpected,
25};
26
27/// Resolves a network interface name into a scope/zone ID. It returns
28/// an error if either resolution fails, or if the interface name is
29/// too long.
30pub fn resolveScopeId(name: []const u8) ResolveScopeIdError!u32 {
31 if (have_ifnamesize) {
32 if (name.len >= os.IFNAMESIZE) return error.NameTooLong;
33
34 if (native_os.tag == .windows or comptime native_os.tag.isDarwin()) {
35 var interface_name: [os.IFNAMESIZE:0]u8 = undefined;
36 mem.copy(u8, &interface_name, name);
37 interface_name[name.len] = 0;
38
39 const rc = blk: {
40 if (native_os.tag == .windows) {
41 break :blk os.windows.ws2_32.if_nametoindex(@ptrCast([*:0]const u8, &interface_name));
42 } else {
43 const index = os.system.if_nametoindex(@ptrCast([*:0]const u8, &interface_name));
44 break :blk @bitCast(u32, index);
45 }
46 };
47 if (rc == 0) {
48 return error.InterfaceNotFound;
49 }
50 return rc;
51 }
52
53 if (native_os.tag == .linux) {
54 const fd = try os.socket(os.AF.INET, os.SOCK.DGRAM, 0);
55 defer os.closeSocket(fd);
56
57 var f: os.ifreq = undefined;
58 mem.copy(u8, &f.ifrn.name, name);
59 f.ifrn.name[name.len] = 0;
60
61 try os.ioctl_SIOCGIFINDEX(fd, &f);
62
63 return @bitCast(u32, f.ifru.ivalue);
64 }
65 }
66
67 return error.InterfaceNotFound;
68}
69
70/// An IPv4 address comprised of 4 bytes.
71pub const IPv4 = extern struct {
72 /// A IPv4 host-port pair.
73 pub const Address = extern struct {
74 host: IPv4,
75 port: u16,
76 };
77
78 /// Octets of a IPv4 address designating the local host.
79 pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
80
81 /// The IPv4 address of the local host.
82 pub const localhost: IPv4 = .{ .octets = localhost_octets };
83
84 /// Octets of an unspecified IPv4 address.
85 pub const unspecified_octets = [_]u8{0} ** 4;
86
87 /// An unspecified IPv4 address.
88 pub const unspecified: IPv4 = .{ .octets = unspecified_octets };
89
90 /// Octets of a broadcast IPv4 address.
91 pub const broadcast_octets = [_]u8{255} ** 4;
92
93 /// An IPv4 broadcast address.
94 pub const broadcast: IPv4 = .{ .octets = broadcast_octets };
95
96 /// The prefix octet pattern of a link-local IPv4 address.
97 pub const link_local_prefix = [_]u8{ 169, 254 };
98
99 /// The prefix octet patterns of IPv4 addresses intended for
100 /// documentation.
101 pub const documentation_prefixes = [_][]const u8{
102 &[_]u8{ 192, 0, 2 },
103 &[_]u8{ 198, 51, 100 },
104 &[_]u8{ 203, 0, 113 },
105 };
106
107 octets: [4]u8,
108
109 /// Returns whether or not the two addresses are equal to, less than, or
110 /// greater than each other.
111 pub fn cmp(self: IPv4, other: IPv4) math.Order {
112 return mem.order(u8, &self.octets, &other.octets);
113 }
114
115 /// Returns true if both addresses are semantically equivalent.
116 pub fn eql(self: IPv4, other: IPv4) bool {
117 return mem.eql(u8, &self.octets, &other.octets);
118 }
119
120 /// Returns true if the address is a loopback address.
121 pub fn isLoopback(self: IPv4) bool {
122 return self.octets[0] == 127;
123 }
124
125 /// Returns true if the address is an unspecified IPv4 address.
126 pub fn isUnspecified(self: IPv4) bool {
127 return mem.eql(u8, &self.octets, &unspecified_octets);
128 }
129
130 /// Returns true if the address is a private IPv4 address.
131 pub fn isPrivate(self: IPv4) bool {
132 return self.octets[0] == 10 or
133 (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or
134 (self.octets[0] == 192 and self.octets[1] == 168);
135 }
136
137 /// Returns true if the address is a link-local IPv4 address.
138 pub fn isLinkLocal(self: IPv4) bool {
139 return mem.startsWith(u8, &self.octets, &link_local_prefix);
140 }
141
142 /// Returns true if the address is a multicast IPv4 address.
143 pub fn isMulticast(self: IPv4) bool {
144 return self.octets[0] >= 224 and self.octets[0] <= 239;
145 }
146
147 /// Returns true if the address is a IPv4 broadcast address.
148 pub fn isBroadcast(self: IPv4) bool {
149 return mem.eql(u8, &self.octets, &broadcast_octets);
150 }
151
152 /// Returns true if the address is in a range designated for documentation. Refer
153 /// to IETF RFC 5737 for more details.
154 pub fn isDocumentation(self: IPv4) bool {
155 inline for (documentation_prefixes) |prefix| {
156 if (mem.startsWith(u8, &self.octets, prefix)) {
157 return true;
158 }
159 }
160 return false;
161 }
162
163 /// Implements the `std.fmt.format` API.
164 pub fn format(
165 self: IPv4,
166 comptime layout: []const u8,
167 opts: fmt.FormatOptions,
168 writer: anytype,
169 ) !void {
170 _ = opts;
171 if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
172
173 try fmt.format(writer, "{}.{}.{}.{}", .{
174 self.octets[0],
175 self.octets[1],
176 self.octets[2],
177 self.octets[3],
178 });
179 }
180
181 /// Set of possible errors that may encountered when parsing an IPv4
182 /// address.
183 pub const ParseError = error{
184 UnexpectedEndOfOctet,
185 TooManyOctets,
186 OctetOverflow,
187 UnexpectedToken,
188 IncompleteAddress,
189 };
190
191 /// Parses an arbitrary IPv4 address.
192 pub fn parse(buf: []const u8) ParseError!IPv4 {
193 var octets: [4]u8 = undefined;
194 var octet: u8 = 0;
195
196 var index: u8 = 0;
197 var saw_any_digits: bool = false;
198
199 for (buf) |c| {
200 switch (c) {
201 '.' => {
202 if (!saw_any_digits) return error.UnexpectedEndOfOctet;
203 if (index == 3) return error.TooManyOctets;
204 octets[index] = octet;
205 index += 1;
206 octet = 0;
207 saw_any_digits = false;
208 },
209 '0'...'9' => {
210 saw_any_digits = true;
211 octet = math.mul(u8, octet, 10) catch return error.OctetOverflow;
212 octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow;
213 },
214 else => return error.UnexpectedToken,
215 }
216 }
217
218 if (index == 3 and saw_any_digits) {
219 octets[index] = octet;
220 return IPv4{ .octets = octets };
221 }
222
223 return error.IncompleteAddress;
224 }
225
226 /// Maps the address to its IPv6 equivalent. In most cases, you would
227 /// want to map the address to its IPv6 equivalent rather than directly
228 /// re-interpreting the address.
229 pub fn mapToIPv6(self: IPv4) IPv6 {
230 var octets: [16]u8 = undefined;
231 mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix);
232 mem.copy(u8, octets[12..], &self.octets);
233 return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
234 }
235
236 /// Directly re-interprets the address to its IPv6 equivalent. In most
237 /// cases, you would want to map the address to its IPv6 equivalent rather
238 /// than directly re-interpreting the address.
239 pub fn toIPv6(self: IPv4) IPv6 {
240 var octets: [16]u8 = undefined;
241 mem.set(u8, octets[0..12], 0);
242 mem.copy(u8, octets[12..], &self.octets);
243 return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
244 }
245};
246
247/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
248/// for a scope ID; cumulatively summing to 20 bytes in total.
249pub const IPv6 = extern struct {
250 /// A IPv6 host-port pair.
251 pub const Address = extern struct {
252 host: IPv6,
253 port: u16,
254 };
255
256 /// Octets of a IPv6 address designating the local host.
257 pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};
258
259 /// The IPv6 address of the local host.
260 pub const localhost: IPv6 = .{
261 .octets = localhost_octets,
262 .scope_id = no_scope_id,
263 };
264
265 /// Octets of an unspecified IPv6 address.
266 pub const unspecified_octets = [_]u8{0} ** 16;
267
268 /// An unspecified IPv6 address.
269 pub const unspecified: IPv6 = .{
270 .octets = unspecified_octets,
271 .scope_id = no_scope_id,
272 };
273
274 /// The prefix of a IPv6 address that is mapped to a IPv4 address.
275 pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2;
276
277 /// A marker value used to designate an IPv6 address with no
278 /// associated scope ID.
279 pub const no_scope_id = math.maxInt(u32);
280
281 octets: [16]u8,
282 scope_id: u32,
283
284 /// Returns whether or not the two addresses are equal to, less than, or
285 /// greater than each other.
286 pub fn cmp(self: IPv6, other: IPv6) math.Order {
287 return switch (mem.order(u8, self.octets, other.octets)) {
288 .eq => math.order(self.scope_id, other.scope_id),
289 else => |order| order,
290 };
291 }
292
293 /// Returns true if both addresses are semantically equivalent.
294 pub fn eql(self: IPv6, other: IPv6) bool {
295 return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets);
296 }
297
298 /// Returns true if the address is an unspecified IPv6 address.
299 pub fn isUnspecified(self: IPv6) bool {
300 return mem.eql(u8, &self.octets, &unspecified_octets);
301 }
302
303 /// Returns true if the address is a loopback address.
304 pub fn isLoopback(self: IPv6) bool {
305 return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and
306 mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 });
307 }
308
309 /// Returns true if the address maps to an IPv4 address.
310 pub fn mapsToIPv4(self: IPv6) bool {
311 return mem.startsWith(u8, &self.octets, &v4_mapped_prefix);
312 }
313
314 /// Returns an IPv4 address representative of the address should
315 /// it the address be mapped to an IPv4 address. It returns null
316 /// otherwise.
317 pub fn toIPv4(self: IPv6) ?IPv4 {
318 if (!self.mapsToIPv4()) return null;
319 return IPv4{ .octets = self.octets[12..][0..4].* };
320 }
321
322 /// Returns true if the address is a multicast IPv6 address.
323 pub fn isMulticast(self: IPv6) bool {
324 return self.octets[0] == 0xFF;
325 }
326
327 /// Returns true if the address is a unicast link local IPv6 address.
328 pub fn isLinkLocal(self: IPv6) bool {
329 return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80;
330 }
331
332 /// Returns true if the address is a deprecated unicast site local
333 /// IPv6 address. Refer to IETF RFC 3879 for more details as to
334 /// why they are deprecated.
335 pub fn isSiteLocal(self: IPv6) bool {
336 return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0;
337 }
338
339 /// IPv6 multicast address scopes.
340 pub const Scope = enum(u8) {
341 interface = 1,
342 link = 2,
343 realm = 3,
344 admin = 4,
345 site = 5,
346 organization = 8,
347 global = 14,
348 unknown = 0xFF,
349 };
350
351 /// Returns the multicast scope of the address.
352 pub fn scope(self: IPv6) Scope {
353 if (!self.isMulticast()) return .unknown;
354
355 return switch (self.octets[0] & 0x0F) {
356 1 => .interface,
357 2 => .link,
358 3 => .realm,
359 4 => .admin,
360 5 => .site,
361 8 => .organization,
362 14 => .global,
363 else => .unknown,
364 };
365 }
366
367 /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the
368 /// address lower-cased octets, while specifying 'X' or 'S' formats the
369 /// address using upper-cased ASCII octets.
370 ///
371 /// The default specifier is 'x'.
372 pub fn format(
373 self: IPv6,
374 comptime layout: []const u8,
375 opts: fmt.FormatOptions,
376 writer: anytype,
377 ) !void {
378 _ = opts;
379 const specifier = comptime &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) {
380 'x', 'X' => |specifier| specifier,
381 's' => 'x',
382 'S' => 'X',
383 else => std.fmt.invalidFmtError(layout, self),
384 }};
385
386 if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) {
387 return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{
388 0xFF,
389 0xFF,
390 self.octets[12],
391 self.octets[13],
392 self.octets[14],
393 self.octets[15],
394 });
395 }
396
397 const zero_span: struct { from: usize, to: usize } = span: {
398 var i: usize = 0;
399 while (i < self.octets.len) : (i += 2) {
400 if (self.octets[i] == 0 and self.octets[i + 1] == 0) break;
401 } else break :span .{ .from = 0, .to = 0 };
402
403 const from = i;
404
405 while (i < self.octets.len) : (i += 2) {
406 if (self.octets[i] != 0 or self.octets[i + 1] != 0) break;
407 }
408
409 break :span .{ .from = from, .to = i };
410 };
411
412 var i: usize = 0;
413 while (i != 16) : (i += 2) {
414 if (zero_span.from != zero_span.to and i == zero_span.from) {
415 try writer.writeAll("::");
416 } else if (i >= zero_span.from and i < zero_span.to) {} else {
417 if (i != 0 and i != zero_span.to) try writer.writeAll(":");
418
419 const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1];
420 try fmt.formatIntValue(val, specifier, .{}, writer);
421 }
422 }
423
424 if (self.scope_id != no_scope_id and self.scope_id != 0) {
425 try fmt.format(writer, "%{d}", .{self.scope_id});
426 }
427 }
428
429 /// Set of possible errors that may encountered when parsing an IPv6
430 /// address.
431 pub const ParseError = error{
432 MalformedV4Mapping,
433 InterfaceNotFound,
434 UnknownScopeId,
435 } || IPv4.ParseError;
436
437 /// Parses an arbitrary IPv6 address, including link-local addresses.
438 pub fn parse(buf: []const u8) ParseError!IPv6 {
439 if (mem.lastIndexOfScalar(u8, buf, '%')) |index| {
440 const ip_slice = buf[0..index];
441 const scope_id_slice = buf[index + 1 ..];
442
443 if (scope_id_slice.len == 0) return error.UnknownScopeId;
444
445 const scope_id: u32 = switch (scope_id_slice[0]) {
446 '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10),
447 else => resolveScopeId(scope_id_slice) catch |err| switch (err) {
448 error.InterfaceNotFound => return error.InterfaceNotFound,
449 else => err,
450 },
451 } catch return error.UnknownScopeId;
452
453 return parseWithScopeID(ip_slice, scope_id);
454 }
455
456 return parseWithScopeID(buf, no_scope_id);
457 }
458
459 /// Parses an IPv6 address with a pre-specified scope ID. Presumes
460 /// that the address is not a link-local address.
461 pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 {
462 var octets: [16]u8 = undefined;
463 var octet: u16 = 0;
464 var tail: [16]u8 = undefined;
465
466 var out: []u8 = &octets;
467 var index: u8 = 0;
468
469 var saw_any_digits: bool = false;
470 var abbrv: bool = false;
471
472 for (buf) |c, i| {
473 switch (c) {
474 ':' => {
475 if (!saw_any_digits) {
476 if (abbrv) return error.UnexpectedToken;
477 if (i != 0) abbrv = true;
478 mem.set(u8, out[index..], 0);
479 out = &tail;
480 index = 0;
481 continue;
482 }
483 if (index == 14) return error.TooManyOctets;
484
485 out[index] = @truncate(u8, octet >> 8);
486 index += 1;
487 out[index] = @truncate(u8, octet);
488 index += 1;
489
490 octet = 0;
491 saw_any_digits = false;
492 },
493 '.' => {
494 if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) {
495 return error.MalformedV4Mapping;
496 }
497 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
498 const v4 = try IPv4.parse(buf[start_index..]);
499 octets[10] = 0xFF;
500 octets[11] = 0xFF;
501 mem.copy(u8, octets[12..], &v4.octets);
502
503 return IPv6{ .octets = octets, .scope_id = scope_id };
504 },
505 else => {
506 saw_any_digits = true;
507 const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken;
508 octet = math.mul(u16, octet, 16) catch return error.OctetOverflow;
509 octet = math.add(u16, octet, digit) catch return error.OctetOverflow;
510 },
511 }
512 }
513
514 if (!saw_any_digits and !abbrv) {
515 return error.IncompleteAddress;
516 }
517
518 if (index == 14) {
519 out[14] = @truncate(u8, octet >> 8);
520 out[15] = @truncate(u8, octet);
521 } else {
522 out[index] = @truncate(u8, octet >> 8);
523 index += 1;
524 out[index] = @truncate(u8, octet);
525 index += 1;
526 mem.copy(u8, octets[16 - index ..], out[0..index]);
527 }
528
529 return IPv6{ .octets = octets, .scope_id = scope_id };
530 }
531};
532
533test {
534 testing.refAllDecls(@This());
535}
536
537test "ip: convert to and from ipv6" {
538 try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()});
539 try testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4());
540
541 try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()});
542 try testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4());
543
544 try testing.expect(IPv4.localhost.toIPv6().toIPv4() == null);
545 try testing.expectFmt("127.0.0.1", "{?}", .{IPv4.localhost.mapToIPv6().toIPv4()});
546}
547
548test "ipv4: parse & format" {
549 const cases = [_][]const u8{
550 "0.0.0.0",
551 "255.255.255.255",
552 "1.2.3.4",
553 "123.255.0.91",
554 "127.0.0.1",
555 };
556
557 for (cases) |case| {
558 try testing.expectFmt(case, "{}", .{try IPv4.parse(case)});
559 }
560}
561
562test "ipv6: parse & format" {
563 const inputs = [_][]const u8{
564 "FF01:0:0:0:0:0:0:FB",
565 "FF01::Fb",
566 "::1",
567 "::",
568 "2001:db8::",
569 "::1234:5678",
570 "2001:db8::1234:5678",
571 "::ffff:123.5.123.5",
572 };
573
574 const outputs = [_][]const u8{
575 "ff01::fb",
576 "ff01::fb",
577 "::1",
578 "::",
579 "2001:db8::",
580 "::1234:5678",
581 "2001:db8::1234:5678",
582 "::ffff:123.5.123.5",
583 };
584
585 for (inputs) |input, i| {
586 try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)});
587 }
588}
589
590test "ipv6: parse & format addresses with scope ids" {
591 if (!have_ifnamesize) return error.SkipZigTest;
592 const iface = if (native_os.tag == .linux)
593 "lo"
594 else
595 "lo0";
596 const input = "FF01::FB%" ++ iface;
597 const output = "ff01::fb%1";
598
599 const parsed = IPv6.parse(input) catch |err| switch (err) {
600 error.InterfaceNotFound => return,
601 else => return err,
602 };
603
604 try testing.expectFmt(output, "{}", .{parsed});
605}
lib/std/x/os/socket.zig deleted-320
...@@ -1,320 +0,0 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");
3const net = @import("net.zig");
4
5const os = std.os;
6const fmt = std.fmt;
7const mem = std.mem;
8const time = std.time;
9const meta = std.meta;
10const native_os = builtin.os;
11const native_endian = builtin.cpu.arch.endian();
12
13const Buffer = std.x.os.Buffer;
14
15const assert = std.debug.assert;
16
17/// A generic, cross-platform socket abstraction.
18pub const Socket = struct {
19 /// A socket-address pair.
20 pub const Connection = struct {
21 socket: Socket,
22 address: Socket.Address,
23
24 /// Enclose a socket and address into a socket-address pair.
25 pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
26 return .{ .socket = socket, .address = address };
27 }
28 };
29
30 /// A generic socket address abstraction. It is safe to directly access and modify
31 /// the fields of a `Socket.Address`.
32 pub const Address = union(enum) {
33 pub const Native = struct {
34 pub const requires_prepended_length = native_os.getVersionRange() == .semver;
35 pub const Length = if (requires_prepended_length) u8 else [0]u8;
36
37 pub const Family = if (requires_prepended_length) u8 else c_ushort;
38
39 /// POSIX `sockaddr.storage`. The expected size and alignment is specified in IETF RFC 2553.
40 pub const Storage = extern struct {
41 pub const expected_size = os.sockaddr.SS_MAXSIZE;
42 pub const expected_alignment = 8;
43
44 pub const padding_size = expected_size -
45 mem.alignForward(@sizeOf(Address.Native.Length), expected_alignment) -
46 mem.alignForward(@sizeOf(Address.Native.Family), expected_alignment);
47
48 len: Address.Native.Length align(expected_alignment) = undefined,
49 family: Address.Native.Family align(expected_alignment) = undefined,
50 padding: [padding_size]u8 align(expected_alignment) = undefined,
51
52 comptime {
53 assert(@sizeOf(Storage) == Storage.expected_size);
54 assert(@alignOf(Storage) == Storage.expected_alignment);
55 }
56 };
57 };
58
59 ipv4: net.IPv4.Address,
60 ipv6: net.IPv6.Address,
61
62 /// Instantiate a new address with a IPv4 host and port.
63 pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
64 return .{ .ipv4 = .{ .host = host, .port = port } };
65 }
66
67 /// Instantiate a new address with a IPv6 host and port.
68 pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
69 return .{ .ipv6 = .{ .host = host, .port = port } };
70 }
71
72 /// Parses a `sockaddr` into a generic socket address.
73 pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
74 switch (address.family) {
75 os.AF.INET => {
76 const info = @ptrCast(*const os.sockaddr.in, address);
77 const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
78 const port = mem.bigToNative(u16, info.port);
79 return Socket.Address.initIPv4(host, port);
80 },
81 os.AF.INET6 => {
82 const info = @ptrCast(*const os.sockaddr.in6, address);
83 const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
84 const port = mem.bigToNative(u16, info.port);
85 return Socket.Address.initIPv6(host, port);
86 },
87 else => unreachable,
88 }
89 }
90
91 /// Encodes a generic socket address into an extern union that may be reliably
92 /// casted into a `sockaddr` which may be passed into socket syscalls.
93 pub fn toNative(self: Socket.Address) extern union {
94 ipv4: os.sockaddr.in,
95 ipv6: os.sockaddr.in6,
96 } {
97 return switch (self) {
98 .ipv4 => |address| .{
99 .ipv4 = .{
100 .addr = @bitCast(u32, address.host.octets),
101 .port = mem.nativeToBig(u16, address.port),
102 },
103 },
104 .ipv6 => |address| .{
105 .ipv6 = .{
106 .addr = address.host.octets,
107 .port = mem.nativeToBig(u16, address.port),
108 .scope_id = address.host.scope_id,
109 .flowinfo = 0,
110 },
111 },
112 };
113 }
114
115 /// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
116 pub fn getNativeSize(self: Socket.Address) u32 {
117 return switch (self) {
118 .ipv4 => @sizeOf(os.sockaddr.in),
119 .ipv6 => @sizeOf(os.sockaddr.in6),
120 };
121 }
122
123 /// Implements the `std.fmt.format` API.
124 pub fn format(
125 self: Socket.Address,
126 comptime layout: []const u8,
127 opts: fmt.FormatOptions,
128 writer: anytype,
129 ) !void {
130 if (layout.len != 0) std.fmt.invalidFmtError(layout, self);
131 _ = opts;
132 switch (self) {
133 .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
134 .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
135 }
136 }
137 };
138
139 /// POSIX `msghdr`. Denotes a destination address, set of buffers, control data, and flags. Ported
140 /// directly from musl.
141 pub const Message = if (native_os.isAtLeast(.windows, .vista) != null and native_os.isAtLeast(.windows, .vista).?)
142 extern struct {
143 name: usize = @ptrToInt(@as(?[*]u8, null)),
144 name_len: c_int = 0,
145
146 buffers: usize = undefined,
147 buffers_len: c_ulong = undefined,
148
149 control: Buffer = .{
150 .ptr = @ptrToInt(@as(?[*]u8, null)),
151 .len = 0,
152 },
153 flags: c_ulong = 0,
154
155 pub usingnamespace MessageMixin(Message);
156 }
157 else if (native_os.tag == .windows)
158 extern struct {
159 name: usize = @ptrToInt(@as(?[*]u8, null)),
160 name_len: c_int = 0,
161
162 buffers: usize = undefined,
163 buffers_len: u32 = undefined,
164
165 control: Buffer = .{
166 .ptr = @ptrToInt(@as(?[*]u8, null)),
167 .len = 0,
168 },
169 flags: u32 = 0,
170
171 pub usingnamespace MessageMixin(Message);
172 }
173 else if (@sizeOf(usize) > 4 and native_endian == .Big)
174 extern struct {
175 name: usize = @ptrToInt(@as(?[*]u8, null)),
176 name_len: c_uint = 0,
177
178 buffers: usize = undefined,
179 _pad_1: c_int = 0,
180 buffers_len: c_int = undefined,
181
182 control: usize = @ptrToInt(@as(?[*]u8, null)),
183 _pad_2: c_int = 0,
184 control_len: c_uint = 0,
185
186 flags: c_int = 0,
187
188 pub usingnamespace MessageMixin(Message);
189 }
190 else if (@sizeOf(usize) > 4 and native_endian == .Little)
191 extern struct {
192 name: usize = @ptrToInt(@as(?[*]u8, null)),
193 name_len: c_uint = 0,
194
195 buffers: usize = undefined,
196 buffers_len: c_int = undefined,
197 _pad_1: c_int = 0,
198
199 control: usize = @ptrToInt(@as(?[*]u8, null)),
200 control_len: c_uint = 0,
201 _pad_2: c_int = 0,
202
203 flags: c_int = 0,
204
205 pub usingnamespace MessageMixin(Message);
206 }
207 else
208 extern struct {
209 name: usize = @ptrToInt(@as(?[*]u8, null)),
210 name_len: c_uint = 0,
211
212 buffers: usize = undefined,
213 buffers_len: c_int = undefined,
214
215 control: usize = @ptrToInt(@as(?[*]u8, null)),
216 control_len: c_uint = 0,
217
218 flags: c_int = 0,
219
220 pub usingnamespace MessageMixin(Message);
221 };
222
223 fn MessageMixin(comptime Self: type) type {
224 return struct {
225 pub fn fromBuffers(buffers: []const Buffer) Self {
226 var self: Self = .{};
227 self.setBuffers(buffers);
228 return self;
229 }
230
231 pub fn setName(self: *Self, name: []const u8) void {
232 self.name = @ptrToInt(name.ptr);
233 self.name_len = @intCast(meta.fieldInfo(Self, .name_len).type, name.len);
234 }
235
236 pub fn setBuffers(self: *Self, buffers: []const Buffer) void {
237 self.buffers = @ptrToInt(buffers.ptr);
238 self.buffers_len = @intCast(meta.fieldInfo(Self, .buffers_len).type, buffers.len);
239 }
240
241 pub fn setControl(self: *Self, control: []const u8) void {
242 if (native_os.tag == .windows) {
243 self.control = Buffer.from(control);
244 } else {
245 self.control = @ptrToInt(control.ptr);
246 self.control_len = @intCast(meta.fieldInfo(Self, .control_len).type, control.len);
247 }
248 }
249
250 pub fn setFlags(self: *Self, flags: u32) void {
251 self.flags = @intCast(meta.fieldInfo(Self, .flags).type, flags);
252 }
253
254 pub fn getName(self: Self) []const u8 {
255 return @intToPtr([*]const u8, self.name)[0..@intCast(usize, self.name_len)];
256 }
257
258 pub fn getBuffers(self: Self) []const Buffer {
259 return @intToPtr([*]const Buffer, self.buffers)[0..@intCast(usize, self.buffers_len)];
260 }
261
262 pub fn getControl(self: Self) []const u8 {
263 if (native_os.tag == .windows) {
264 return self.control.into();
265 } else {
266 return @intToPtr([*]const u8, self.control)[0..@intCast(usize, self.control_len)];
267 }
268 }
269
270 pub fn getFlags(self: Self) u32 {
271 return @intCast(u32, self.flags);
272 }
273 };
274 }
275
276 /// POSIX `linger`, denoting the linger settings of a socket.
277 ///
278 /// Microsoft's documentation and glibc denote the fields to be unsigned
279 /// short's on Windows, whereas glibc and musl denote the fields to be
280 /// int's on every other platform.
281 pub const Linger = extern struct {
282 pub const Field = switch (native_os.tag) {
283 .windows => c_ushort,
284 else => c_int,
285 };
286
287 enabled: Field,
288 timeout_seconds: Field,
289
290 pub fn init(timeout_seconds: ?u16) Socket.Linger {
291 return .{
292 .enabled = @intCast(Socket.Linger.Field, @boolToInt(timeout_seconds != null)),
293 .timeout_seconds = if (timeout_seconds) |seconds| @intCast(Socket.Linger.Field, seconds) else 0,
294 };
295 }
296 };
297
298 /// Possible set of flags to initialize a socket with.
299 pub const InitFlags = enum {
300 // Initialize a socket to be non-blocking.
301 nonblocking,
302
303 // Have a socket close itself on exec syscalls.
304 close_on_exec,
305 };
306
307 /// The underlying handle of a socket.
308 fd: os.socket_t,
309
310 /// Enclose a socket abstraction over an existing socket file descriptor.
311 pub fn from(fd: os.socket_t) Socket {
312 return Socket{ .fd = fd };
313 }
314
315 /// Mix in socket syscalls depending on the platform we are compiling against.
316 pub usingnamespace switch (native_os.tag) {
317 .windows => @import("socket_windows.zig"),
318 else => @import("socket_posix.zig"),
319 }.Mixin(Socket);
320};
lib/std/x/os/socket_posix.zig deleted-275
...@@ -1,275 +0,0 @@
1const std = @import("../../std.zig");
2
3const os = std.os;
4const mem = std.mem;
5const time = std.time;
6
7pub fn Mixin(comptime Socket: type) type {
8 return struct {
9 /// Open a new socket.
10 pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket {
11 var raw_flags: u32 = socket_type;
12 const set = std.EnumSet(Socket.InitFlags).init(flags);
13 if (set.contains(.close_on_exec)) raw_flags |= os.SOCK.CLOEXEC;
14 if (set.contains(.nonblocking)) raw_flags |= os.SOCK.NONBLOCK;
15 return Socket{ .fd = try os.socket(domain, raw_flags, protocol) };
16 }
17
18 /// Closes the socket.
19 pub fn deinit(self: Socket) void {
20 os.closeSocket(self.fd);
21 }
22
23 /// Shutdown either the read side, write side, or all side of the socket.
24 pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
25 return os.shutdown(self.fd, how);
26 }
27
28 /// Binds the socket to an address.
29 pub fn bind(self: Socket, address: Socket.Address) !void {
30 return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
31 }
32
33 /// Start listening for incoming connections on the socket.
34 pub fn listen(self: Socket, max_backlog_size: u31) !void {
35 return os.listen(self.fd, max_backlog_size);
36 }
37
38 /// Have the socket attempt to the connect to an address.
39 pub fn connect(self: Socket, address: Socket.Address) !void {
40 return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
41 }
42
43 /// Accept a pending incoming connection queued to the kernel backlog
44 /// of the socket.
45 pub fn accept(self: Socket, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket.Connection {
46 var address: Socket.Address.Native.Storage = undefined;
47 var address_len: u32 = @sizeOf(Socket.Address.Native.Storage);
48
49 var raw_flags: u32 = 0;
50 const set = std.EnumSet(Socket.InitFlags).init(flags);
51 if (set.contains(.close_on_exec)) raw_flags |= os.SOCK.CLOEXEC;
52 if (set.contains(.nonblocking)) raw_flags |= os.SOCK.NONBLOCK;
53
54 const socket = Socket{ .fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, raw_flags) };
55 const socket_address = Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
56
57 return Socket.Connection.from(socket, socket_address);
58 }
59
60 /// Read data from the socket into the buffer provided with a set of flags
61 /// specified. It returns the number of bytes read into the buffer provided.
62 pub fn read(self: Socket, buf: []u8, flags: u32) !usize {
63 return os.recv(self.fd, buf, flags);
64 }
65
66 /// Write a buffer of data provided to the socket with a set of flags specified.
67 /// It returns the number of bytes that are written to the socket.
68 pub fn write(self: Socket, buf: []const u8, flags: u32) !usize {
69 return os.send(self.fd, buf, flags);
70 }
71
72 /// Writes multiple I/O vectors with a prepended message header to the socket
73 /// with a set of flags specified. It returns the number of bytes that are
74 /// written to the socket.
75 pub fn writeMessage(self: Socket, msg: Socket.Message, flags: u32) !usize {
76 while (true) {
77 const rc = os.system.sendmsg(self.fd, &msg, @intCast(c_int, flags));
78 return switch (os.errno(rc)) {
79 .SUCCESS => return @intCast(usize, rc),
80 .ACCES => error.AccessDenied,
81 .AGAIN => error.WouldBlock,
82 .ALREADY => error.FastOpenAlreadyInProgress,
83 .BADF => unreachable, // always a race condition
84 .CONNRESET => error.ConnectionResetByPeer,
85 .DESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set.
86 .FAULT => unreachable, // An invalid user space address was specified for an argument.
87 .INTR => continue,
88 .INVAL => unreachable, // Invalid argument passed.
89 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
90 .MSGSIZE => error.MessageTooBig,
91 .NOBUFS => error.SystemResources,
92 .NOMEM => error.SystemResources,
93 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
94 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
95 .PIPE => error.BrokenPipe,
96 .AFNOSUPPORT => error.AddressFamilyNotSupported,
97 .LOOP => error.SymLinkLoop,
98 .NAMETOOLONG => error.NameTooLong,
99 .NOENT => error.FileNotFound,
100 .NOTDIR => error.NotDir,
101 .HOSTUNREACH => error.NetworkUnreachable,
102 .NETUNREACH => error.NetworkUnreachable,
103 .NOTCONN => error.SocketNotConnected,
104 .NETDOWN => error.NetworkSubsystemFailed,
105 else => |err| os.unexpectedErrno(err),
106 };
107 }
108 }
109
110 /// Read multiple I/O vectors with a prepended message header from the socket
111 /// with a set of flags specified. It returns the number of bytes that were
112 /// read into the buffer provided.
113 pub fn readMessage(self: Socket, msg: *Socket.Message, flags: u32) !usize {
114 while (true) {
115 const rc = os.system.recvmsg(self.fd, msg, @intCast(c_int, flags));
116 return switch (os.errno(rc)) {
117 .SUCCESS => @intCast(usize, rc),
118 .BADF => unreachable, // always a race condition
119 .FAULT => unreachable,
120 .INVAL => unreachable,
121 .NOTCONN => unreachable,
122 .NOTSOCK => unreachable,
123 .INTR => continue,
124 .AGAIN => error.WouldBlock,
125 .NOMEM => error.SystemResources,
126 .CONNREFUSED => error.ConnectionRefused,
127 .CONNRESET => error.ConnectionResetByPeer,
128 else => |err| os.unexpectedErrno(err),
129 };
130 }
131 }
132
133 /// Query the address that the socket is locally bounded to.
134 pub fn getLocalAddress(self: Socket) !Socket.Address {
135 var address: Socket.Address.Native.Storage = undefined;
136 var address_len: u32 = @sizeOf(Socket.Address.Native.Storage);
137 try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
138 return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
139 }
140
141 /// Query the address that the socket is connected to.
142 pub fn getRemoteAddress(self: Socket) !Socket.Address {
143 var address: Socket.Address.Native.Storage = undefined;
144 var address_len: u32 = @sizeOf(Socket.Address.Native.Storage);
145 try os.getpeername(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
146 return Socket.Address.fromNative(@ptrCast(*os.sockaddr, &address));
147 }
148
149 /// Query and return the latest cached error on the socket.
150 pub fn getError(self: Socket) !void {
151 return os.getsockoptError(self.fd);
152 }
153
154 /// Query the read buffer size of the socket.
155 pub fn getReadBufferSize(self: Socket) !u32 {
156 var value: u32 = undefined;
157 var value_len: u32 = @sizeOf(u32);
158
159 const rc = os.system.getsockopt(self.fd, os.SOL.SOCKET, os.SO.RCVBUF, mem.asBytes(&value), &value_len);
160 return switch (os.errno(rc)) {
161 .SUCCESS => value,
162 .BADF => error.BadFileDescriptor,
163 .FAULT => error.InvalidAddressSpace,
164 .INVAL => error.InvalidSocketOption,
165 .NOPROTOOPT => error.UnknownSocketOption,
166 .NOTSOCK => error.NotASocket,
167 else => |err| os.unexpectedErrno(err),
168 };
169 }
170
171 /// Query the write buffer size of the socket.
172 pub fn getWriteBufferSize(self: Socket) !u32 {
173 var value: u32 = undefined;
174 var value_len: u32 = @sizeOf(u32);
175
176 const rc = os.system.getsockopt(self.fd, os.SOL.SOCKET, os.SO.SNDBUF, mem.asBytes(&value), &value_len);
177 return switch (os.errno(rc)) {
178 .SUCCESS => value,
179 .BADF => error.BadFileDescriptor,
180 .FAULT => error.InvalidAddressSpace,
181 .INVAL => error.InvalidSocketOption,
182 .NOPROTOOPT => error.UnknownSocketOption,
183 .NOTSOCK => error.NotASocket,
184 else => |err| os.unexpectedErrno(err),
185 };
186 }
187
188 /// Set a socket option.
189 pub fn setOption(self: Socket, level: u32, code: u32, value: []const u8) !void {
190 return os.setsockopt(self.fd, level, code, value);
191 }
192
193 /// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully
194 /// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
195 /// if the host does not support the option for a socket to linger around up until a timeout specified in
196 /// seconds.
197 pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void {
198 if (@hasDecl(os.SO, "LINGER")) {
199 const settings = Socket.Linger.init(timeout_seconds);
200 return self.setOption(os.SOL.SOCKET, os.SO.LINGER, mem.asBytes(&settings));
201 }
202
203 return error.UnsupportedSocketOption;
204 }
205
206 /// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive
207 /// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if
208 /// the host does not support periodically sending keep-alive messages on connection-oriented sockets.
209 pub fn setKeepAlive(self: Socket, enabled: bool) !void {
210 if (@hasDecl(os.SO, "KEEPALIVE")) {
211 return self.setOption(os.SOL.SOCKET, os.SO.KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
212 }
213 return error.UnsupportedSocketOption;
214 }
215
216 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
217 /// the host does not support sockets listening the same address.
218 pub fn setReuseAddress(self: Socket, enabled: bool) !void {
219 if (@hasDecl(os.SO, "REUSEADDR")) {
220 return self.setOption(os.SOL.SOCKET, os.SO.REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled))));
221 }
222 return error.UnsupportedSocketOption;
223 }
224
225 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
226 /// the host does not supports sockets listening on the same port.
227 pub fn setReusePort(self: Socket, enabled: bool) !void {
228 if (@hasDecl(os.SO, "REUSEPORT")) {
229 return self.setOption(os.SOL.SOCKET, os.SO.REUSEPORT, mem.asBytes(&@as(u32, @boolToInt(enabled))));
230 }
231 return error.UnsupportedSocketOption;
232 }
233
234 /// Set the write buffer size of the socket.
235 pub fn setWriteBufferSize(self: Socket, size: u32) !void {
236 return self.setOption(os.SOL.SOCKET, os.SO.SNDBUF, mem.asBytes(&size));
237 }
238
239 /// Set the read buffer size of the socket.
240 pub fn setReadBufferSize(self: Socket, size: u32) !void {
241 return self.setOption(os.SOL.SOCKET, os.SO.RCVBUF, mem.asBytes(&size));
242 }
243
244 /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
245 /// set on a non-blocking socket.
246 ///
247 /// Set a timeout on the socket that is to occur if no messages are successfully written
248 /// to its bound destination after a specified number of milliseconds. A subsequent write
249 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
250 pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
251 const timeout = os.timeval{
252 .tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
253 .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
254 };
255
256 return self.setOption(os.SOL.SOCKET, os.SO.SNDTIMEO, mem.asBytes(&timeout));
257 }
258
259 /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
260 /// set on a non-blocking socket.
261 ///
262 /// Set a timeout on the socket that is to occur if no messages are successfully read
263 /// from its bound destination after a specified number of milliseconds. A subsequent
264 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
265 /// exceeded.
266 pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
267 const timeout = os.timeval{
268 .tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
269 .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
270 };
271
272 return self.setOption(os.SOL.SOCKET, os.SO.RCVTIMEO, mem.asBytes(&timeout));
273 }
274 };
275}
lib/std/x/os/socket_windows.zig deleted-458
...@@ -1,458 +0,0 @@
1const std = @import("../../std.zig");
2const net = @import("net.zig");
3
4const os = std.os;
5const mem = std.mem;
6
7const windows = std.os.windows;
8const ws2_32 = windows.ws2_32;
9
10pub fn Mixin(comptime Socket: type) type {
11 return struct {
12 /// Open a new socket.
13 pub fn init(domain: u32, socket_type: u32, protocol: u32, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket {
14 var raw_flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED;
15 const set = std.EnumSet(Socket.InitFlags).init(flags);
16 if (set.contains(.close_on_exec)) raw_flags |= ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
17
18 const fd = ws2_32.WSASocketW(
19 @intCast(i32, domain),
20 @intCast(i32, socket_type),
21 @intCast(i32, protocol),
22 null,
23 0,
24 raw_flags,
25 );
26 if (fd == ws2_32.INVALID_SOCKET) {
27 return switch (ws2_32.WSAGetLastError()) {
28 .WSANOTINITIALISED => {
29 _ = try windows.WSAStartup(2, 2);
30 return init(domain, socket_type, protocol, flags);
31 },
32 .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
33 .WSAEMFILE => error.ProcessFdQuotaExceeded,
34 .WSAENOBUFS => error.SystemResources,
35 .WSAEPROTONOSUPPORT => error.ProtocolNotSupported,
36 else => |err| windows.unexpectedWSAError(err),
37 };
38 }
39
40 if (set.contains(.nonblocking)) {
41 var enabled: c_ulong = 1;
42 const rc = ws2_32.ioctlsocket(fd, ws2_32.FIONBIO, &enabled);
43 if (rc == ws2_32.SOCKET_ERROR) {
44 return windows.unexpectedWSAError(ws2_32.WSAGetLastError());
45 }
46 }
47
48 return Socket{ .fd = fd };
49 }
50
51 /// Closes the socket.
52 pub fn deinit(self: Socket) void {
53 _ = ws2_32.closesocket(self.fd);
54 }
55
56 /// Shutdown either the read side, write side, or all side of the socket.
57 pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
58 const rc = ws2_32.shutdown(self.fd, switch (how) {
59 .recv => ws2_32.SD_RECEIVE,
60 .send => ws2_32.SD_SEND,
61 .both => ws2_32.SD_BOTH,
62 });
63 if (rc == ws2_32.SOCKET_ERROR) {
64 return switch (ws2_32.WSAGetLastError()) {
65 .WSAECONNABORTED => return error.ConnectionAborted,
66 .WSAECONNRESET => return error.ConnectionResetByPeer,
67 .WSAEINPROGRESS => return error.BlockingOperationInProgress,
68 .WSAEINVAL => unreachable,
69 .WSAENETDOWN => return error.NetworkSubsystemFailed,
70 .WSAENOTCONN => return error.SocketNotConnected,
71 .WSAENOTSOCK => unreachable,
72 .WSANOTINITIALISED => unreachable,
73 else => |err| return windows.unexpectedWSAError(err),
74 };
75 }
76 }
77
78 /// Binds the socket to an address.
79 pub fn bind(self: Socket, address: Socket.Address) !void {
80 const rc = ws2_32.bind(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize()));
81 if (rc == ws2_32.SOCKET_ERROR) {
82 return switch (ws2_32.WSAGetLastError()) {
83 .WSAENETDOWN => error.NetworkSubsystemFailed,
84 .WSAEACCES => error.AccessDenied,
85 .WSAEADDRINUSE => error.AddressInUse,
86 .WSAEADDRNOTAVAIL => error.AddressNotAvailable,
87 .WSAEFAULT => error.BadAddress,
88 .WSAEINPROGRESS => error.WouldBlock,
89 .WSAEINVAL => error.AlreadyBound,
90 .WSAENOBUFS => error.NoEphemeralPortsAvailable,
91 .WSAENOTSOCK => error.NotASocket,
92 else => |err| windows.unexpectedWSAError(err),
93 };
94 }
95 }
96
97 /// Start listening for incoming connections on the socket.
98 pub fn listen(self: Socket, max_backlog_size: u31) !void {
99 const rc = ws2_32.listen(self.fd, max_backlog_size);
100 if (rc == ws2_32.SOCKET_ERROR) {
101 return switch (ws2_32.WSAGetLastError()) {
102 .WSAENETDOWN => error.NetworkSubsystemFailed,
103 .WSAEADDRINUSE => error.AddressInUse,
104 .WSAEISCONN => error.AlreadyConnected,
105 .WSAEINVAL => error.SocketNotBound,
106 .WSAEMFILE, .WSAENOBUFS => error.SystemResources,
107 .WSAENOTSOCK => error.FileDescriptorNotASocket,
108 .WSAEOPNOTSUPP => error.OperationNotSupported,
109 .WSAEINPROGRESS => error.WouldBlock,
110 else => |err| windows.unexpectedWSAError(err),
111 };
112 }
113 }
114
115 /// Have the socket attempt to the connect to an address.
116 pub fn connect(self: Socket, address: Socket.Address) !void {
117 const rc = ws2_32.connect(self.fd, @ptrCast(*const ws2_32.sockaddr, &address.toNative()), @intCast(c_int, address.getNativeSize()));
118 if (rc == ws2_32.SOCKET_ERROR) {
119 return switch (ws2_32.WSAGetLastError()) {
120 .WSAEADDRINUSE => error.AddressInUse,
121 .WSAEADDRNOTAVAIL => error.AddressNotAvailable,
122 .WSAECONNREFUSED => error.ConnectionRefused,
123 .WSAETIMEDOUT => error.ConnectionTimedOut,
124 .WSAEFAULT => error.BadAddress,
125 .WSAEINVAL => error.ListeningSocket,
126 .WSAEISCONN => error.AlreadyConnected,
127 .WSAENOTSOCK => error.NotASocket,
128 .WSAEACCES => error.BroadcastNotEnabled,
129 .WSAENOBUFS => error.SystemResources,
130 .WSAEAFNOSUPPORT => error.AddressFamilyNotSupported,
131 .WSAEINPROGRESS, .WSAEWOULDBLOCK => error.WouldBlock,
132 .WSAEHOSTUNREACH, .WSAENETUNREACH => error.NetworkUnreachable,
133 else => |err| windows.unexpectedWSAError(err),
134 };
135 }
136 }
137
138 /// Accept a pending incoming connection queued to the kernel backlog
139 /// of the socket.
140 pub fn accept(self: Socket, flags: std.enums.EnumFieldStruct(Socket.InitFlags, bool, false)) !Socket.Connection {
141 var address: Socket.Address.Native.Storage = undefined;
142 var address_len: c_int = @sizeOf(Socket.Address.Native.Storage);
143
144 const fd = ws2_32.accept(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
145 if (fd == ws2_32.INVALID_SOCKET) {
146 return switch (ws2_32.WSAGetLastError()) {
147 .WSANOTINITIALISED => unreachable,
148 .WSAECONNRESET => error.ConnectionResetByPeer,
149 .WSAEFAULT => unreachable,
150 .WSAEINVAL => error.SocketNotListening,
151 .WSAEMFILE => error.ProcessFdQuotaExceeded,
152 .WSAENETDOWN => error.NetworkSubsystemFailed,
153 .WSAENOBUFS => error.FileDescriptorNotASocket,
154 .WSAEOPNOTSUPP => error.OperationNotSupported,
155 .WSAEWOULDBLOCK => error.WouldBlock,
156 else => |err| windows.unexpectedWSAError(err),
157 };
158 }
159
160 const socket = Socket.from(fd);
161 errdefer socket.deinit();
162
163 const socket_address = Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address));
164
165 const set = std.EnumSet(Socket.InitFlags).init(flags);
166 if (set.contains(.nonblocking)) {
167 var enabled: c_ulong = 1;
168 const rc = ws2_32.ioctlsocket(fd, ws2_32.FIONBIO, &enabled);
169 if (rc == ws2_32.SOCKET_ERROR) {
170 return windows.unexpectedWSAError(ws2_32.WSAGetLastError());
171 }
172 }
173
174 return Socket.Connection.from(socket, socket_address);
175 }
176
177 /// Read data from the socket into the buffer provided with a set of flags
178 /// specified. It returns the number of bytes read into the buffer provided.
179 pub fn read(self: Socket, buf: []u8, flags: u32) !usize {
180 var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = buf.ptr }};
181 var num_bytes: u32 = undefined;
182 var flags_ = flags;
183
184 const rc = ws2_32.WSARecv(self.fd, bufs, 1, &num_bytes, &flags_, null, null);
185 if (rc == ws2_32.SOCKET_ERROR) {
186 return switch (ws2_32.WSAGetLastError()) {
187 .WSAECONNABORTED => error.ConnectionAborted,
188 .WSAECONNRESET => error.ConnectionResetByPeer,
189 .WSAEDISCON => error.ConnectionClosedByPeer,
190 .WSAEFAULT => error.BadBuffer,
191 .WSAEINPROGRESS,
192 .WSAEWOULDBLOCK,
193 .WSA_IO_PENDING,
194 .WSAETIMEDOUT,
195 => error.WouldBlock,
196 .WSAEINTR => error.Cancelled,
197 .WSAEINVAL => error.SocketNotBound,
198 .WSAEMSGSIZE => error.MessageTooLarge,
199 .WSAENETDOWN => error.NetworkSubsystemFailed,
200 .WSAENETRESET => error.NetworkReset,
201 .WSAENOTCONN => error.SocketNotConnected,
202 .WSAENOTSOCK => error.FileDescriptorNotASocket,
203 .WSAEOPNOTSUPP => error.OperationNotSupported,
204 .WSAESHUTDOWN => error.AlreadyShutdown,
205 .WSA_OPERATION_ABORTED => error.OperationAborted,
206 else => |err| windows.unexpectedWSAError(err),
207 };
208 }
209
210 return @intCast(usize, num_bytes);
211 }
212
213 /// Write a buffer of data provided to the socket with a set of flags specified.
214 /// It returns the number of bytes that are written to the socket.
215 pub fn write(self: Socket, buf: []const u8, flags: u32) !usize {
216 var bufs = &[_]ws2_32.WSABUF{.{ .len = @intCast(u32, buf.len), .buf = @intToPtr([*]u8, @ptrToInt(buf.ptr)) }};
217 var num_bytes: u32 = undefined;
218
219 const rc = ws2_32.WSASend(self.fd, bufs, 1, &num_bytes, flags, null, null);
220 if (rc == ws2_32.SOCKET_ERROR) {
221 return switch (ws2_32.WSAGetLastError()) {
222 .WSAECONNABORTED => error.ConnectionAborted,
223 .WSAECONNRESET => error.ConnectionResetByPeer,
224 .WSAEFAULT => error.BadBuffer,
225 .WSAEINPROGRESS,
226 .WSAEWOULDBLOCK,
227 .WSA_IO_PENDING,
228 .WSAETIMEDOUT,
229 => error.WouldBlock,
230 .WSAEINTR => error.Cancelled,
231 .WSAEINVAL => error.SocketNotBound,
232 .WSAEMSGSIZE => error.MessageTooLarge,
233 .WSAENETDOWN => error.NetworkSubsystemFailed,
234 .WSAENETRESET => error.NetworkReset,
235 .WSAENOBUFS => error.BufferDeadlock,
236 .WSAENOTCONN => error.SocketNotConnected,
237 .WSAENOTSOCK => error.FileDescriptorNotASocket,
238 .WSAEOPNOTSUPP => error.OperationNotSupported,
239 .WSAESHUTDOWN => error.AlreadyShutdown,
240 .WSA_OPERATION_ABORTED => error.OperationAborted,
241 else => |err| windows.unexpectedWSAError(err),
242 };
243 }
244
245 return @intCast(usize, num_bytes);
246 }
247
248 /// Writes multiple I/O vectors with a prepended message header to the socket
249 /// with a set of flags specified. It returns the number of bytes that are
250 /// written to the socket.
251 pub fn writeMessage(self: Socket, msg: Socket.Message, flags: u32) !usize {
252 const call = try windows.loadWinsockExtensionFunction(ws2_32.LPFN_WSASENDMSG, self.fd, ws2_32.WSAID_WSASENDMSG);
253
254 var num_bytes: u32 = undefined;
255
256 const rc = call(self.fd, &msg, flags, &num_bytes, null, null);
257 if (rc == ws2_32.SOCKET_ERROR) {
258 return switch (ws2_32.WSAGetLastError()) {
259 .WSAECONNABORTED => error.ConnectionAborted,
260 .WSAECONNRESET => error.ConnectionResetByPeer,
261 .WSAEFAULT => error.BadBuffer,
262 .WSAEINPROGRESS,
263 .WSAEWOULDBLOCK,
264 .WSA_IO_PENDING,
265 .WSAETIMEDOUT,
266 => error.WouldBlock,
267 .WSAEINTR => error.Cancelled,
268 .WSAEINVAL => error.SocketNotBound,
269 .WSAEMSGSIZE => error.MessageTooLarge,
270 .WSAENETDOWN => error.NetworkSubsystemFailed,
271 .WSAENETRESET => error.NetworkReset,
272 .WSAENOBUFS => error.BufferDeadlock,
273 .WSAENOTCONN => error.SocketNotConnected,
274 .WSAENOTSOCK => error.FileDescriptorNotASocket,
275 .WSAEOPNOTSUPP => error.OperationNotSupported,
276 .WSAESHUTDOWN => error.AlreadyShutdown,
277 .WSA_OPERATION_ABORTED => error.OperationAborted,
278 else => |err| windows.unexpectedWSAError(err),
279 };
280 }
281
282 return @intCast(usize, num_bytes);
283 }
284
285 /// Read multiple I/O vectors with a prepended message header from the socket
286 /// with a set of flags specified. It returns the number of bytes that were
287 /// read into the buffer provided.
288 pub fn readMessage(self: Socket, msg: *Socket.Message, flags: u32) !usize {
289 _ = flags;
290 const call = try windows.loadWinsockExtensionFunction(ws2_32.LPFN_WSARECVMSG, self.fd, ws2_32.WSAID_WSARECVMSG);
291
292 var num_bytes: u32 = undefined;
293
294 const rc = call(self.fd, msg, &num_bytes, null, null);
295 if (rc == ws2_32.SOCKET_ERROR) {
296 return switch (ws2_32.WSAGetLastError()) {
297 .WSAECONNABORTED => error.ConnectionAborted,
298 .WSAECONNRESET => error.ConnectionResetByPeer,
299 .WSAEDISCON => error.ConnectionClosedByPeer,
300 .WSAEFAULT => error.BadBuffer,
301 .WSAEINPROGRESS,
302 .WSAEWOULDBLOCK,
303 .WSA_IO_PENDING,
304 .WSAETIMEDOUT,
305 => error.WouldBlock,
306 .WSAEINTR => error.Cancelled,
307 .WSAEINVAL => error.SocketNotBound,
308 .WSAEMSGSIZE => error.MessageTooLarge,
309 .WSAENETDOWN => error.NetworkSubsystemFailed,
310 .WSAENETRESET => error.NetworkReset,
311 .WSAENOTCONN => error.SocketNotConnected,
312 .WSAENOTSOCK => error.FileDescriptorNotASocket,
313 .WSAEOPNOTSUPP => error.OperationNotSupported,
314 .WSAESHUTDOWN => error.AlreadyShutdown,
315 .WSA_OPERATION_ABORTED => error.OperationAborted,
316 else => |err| windows.unexpectedWSAError(err),
317 };
318 }
319
320 return @intCast(usize, num_bytes);
321 }
322
323 /// Query the address that the socket is locally bounded to.
324 pub fn getLocalAddress(self: Socket) !Socket.Address {
325 var address: Socket.Address.Native.Storage = undefined;
326 var address_len: c_int = @sizeOf(Socket.Address.Native.Storage);
327
328 const rc = ws2_32.getsockname(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
329 if (rc == ws2_32.SOCKET_ERROR) {
330 return switch (ws2_32.WSAGetLastError()) {
331 .WSANOTINITIALISED => unreachable,
332 .WSAEFAULT => unreachable,
333 .WSAENETDOWN => error.NetworkSubsystemFailed,
334 .WSAENOTSOCK => error.FileDescriptorNotASocket,
335 .WSAEINVAL => error.SocketNotBound,
336 else => |err| windows.unexpectedWSAError(err),
337 };
338 }
339
340 return Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address));
341 }
342
343 /// Query the address that the socket is connected to.
344 pub fn getRemoteAddress(self: Socket) !Socket.Address {
345 var address: Socket.Address.Native.Storage = undefined;
346 var address_len: c_int = @sizeOf(Socket.Address.Native.Storage);
347
348 const rc = ws2_32.getpeername(self.fd, @ptrCast(*ws2_32.sockaddr, &address), &address_len);
349 if (rc == ws2_32.SOCKET_ERROR) {
350 return switch (ws2_32.WSAGetLastError()) {
351 .WSANOTINITIALISED => unreachable,
352 .WSAEFAULT => unreachable,
353 .WSAENETDOWN => error.NetworkSubsystemFailed,
354 .WSAENOTSOCK => error.FileDescriptorNotASocket,
355 .WSAEINVAL => error.SocketNotBound,
356 else => |err| windows.unexpectedWSAError(err),
357 };
358 }
359
360 return Socket.Address.fromNative(@ptrCast(*ws2_32.sockaddr, &address));
361 }
362
363 /// Query and return the latest cached error on the socket.
364 pub fn getError(self: Socket) !void {
365 _ = self;
366 return {};
367 }
368
369 /// Query the read buffer size of the socket.
370 pub fn getReadBufferSize(self: Socket) !u32 {
371 _ = self;
372 return 0;
373 }
374
375 /// Query the write buffer size of the socket.
376 pub fn getWriteBufferSize(self: Socket) !u32 {
377 _ = self;
378 return 0;
379 }
380
381 /// Set a socket option.
382 pub fn setOption(self: Socket, level: u32, code: u32, value: []const u8) !void {
383 const rc = ws2_32.setsockopt(self.fd, @intCast(i32, level), @intCast(i32, code), value.ptr, @intCast(i32, value.len));
384 if (rc == ws2_32.SOCKET_ERROR) {
385 return switch (ws2_32.WSAGetLastError()) {
386 .WSANOTINITIALISED => unreachable,
387 .WSAENETDOWN => return error.NetworkSubsystemFailed,
388 .WSAEFAULT => unreachable,
389 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
390 .WSAEINVAL => return error.SocketNotBound,
391 else => |err| windows.unexpectedWSAError(err),
392 };
393 }
394 }
395
396 /// Have close() or shutdown() syscalls block until all queued messages in the socket have been successfully
397 /// sent, or if the timeout specified in seconds has been reached. It returns `error.UnsupportedSocketOption`
398 /// if the host does not support the option for a socket to linger around up until a timeout specified in
399 /// seconds.
400 pub fn setLinger(self: Socket, timeout_seconds: ?u16) !void {
401 const settings = Socket.Linger.init(timeout_seconds);
402 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.LINGER, mem.asBytes(&settings));
403 }
404
405 /// On connection-oriented sockets, have keep-alive messages be sent periodically. The timing in which keep-alive
406 /// messages are sent are dependant on operating system settings. It returns `error.UnsupportedSocketOption` if
407 /// the host does not support periodically sending keep-alive messages on connection-oriented sockets.
408 pub fn setKeepAlive(self: Socket, enabled: bool) !void {
409 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.KEEPALIVE, mem.asBytes(&@as(u32, @boolToInt(enabled))));
410 }
411
412 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
413 /// the host does not support sockets listening the same address.
414 pub fn setReuseAddress(self: Socket, enabled: bool) !void {
415 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.REUSEADDR, mem.asBytes(&@as(u32, @boolToInt(enabled))));
416 }
417
418 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
419 /// the host does not supports sockets listening on the same port.
420 ///
421 /// TODO: verify if this truly mimicks SO.REUSEPORT behavior, or if SO.REUSE_UNICASTPORT provides the correct behavior
422 pub fn setReusePort(self: Socket, enabled: bool) !void {
423 try self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.BROADCAST, mem.asBytes(&@as(u32, @boolToInt(enabled))));
424 try self.setReuseAddress(enabled);
425 }
426
427 /// Set the write buffer size of the socket.
428 pub fn setWriteBufferSize(self: Socket, size: u32) !void {
429 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.SNDBUF, mem.asBytes(&size));
430 }
431
432 /// Set the read buffer size of the socket.
433 pub fn setReadBufferSize(self: Socket, size: u32) !void {
434 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.RCVBUF, mem.asBytes(&size));
435 }
436
437 /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
438 /// set on a non-blocking socket.
439 ///
440 /// Set a timeout on the socket that is to occur if no messages are successfully written
441 /// to its bound destination after a specified number of milliseconds. A subsequent write
442 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
443 pub fn setWriteTimeout(self: Socket, milliseconds: u32) !void {
444 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.SNDTIMEO, mem.asBytes(&milliseconds));
445 }
446
447 /// WARNING: Timeouts only affect blocking sockets. It is undefined behavior if a timeout is
448 /// set on a non-blocking socket.
449 ///
450 /// Set a timeout on the socket that is to occur if no messages are successfully read
451 /// from its bound destination after a specified number of milliseconds. A subsequent
452 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
453 /// exceeded.
454 pub fn setReadTimeout(self: Socket, milliseconds: u32) !void {
455 return self.setOption(ws2_32.SOL.SOCKET, ws2_32.SO.RCVTIMEO, mem.asBytes(&milliseconds));
456 }
457 };
458}
src/Compilation.zig+11-1
...@@ -584,7 +584,17 @@ pub const AllErrors = struct {...@@ -584,7 +584,17 @@ pub const AllErrors = struct {
584 Message.HashContext,584 Message.HashContext,
585 std.hash_map.default_max_load_percentage,585 std.hash_map.default_max_load_percentage,
586 ).init(allocator);586 ).init(allocator);
587 const err_source = try module_err_msg.src_loc.file_scope.getSource(module.gpa);587 const err_source = module_err_msg.src_loc.file_scope.getSource(module.gpa) catch |err| {
588 const file_path = try module_err_msg.src_loc.file_scope.fullPath(allocator);
589 try errors.append(.{
590 .plain = .{
591 .msg = try std.fmt.allocPrint(allocator, "unable to load '{s}': {s}", .{
592 file_path, @errorName(err),
593 }),
594 },
595 });
596 return;
597 };
588 const err_span = try module_err_msg.src_loc.span(module.gpa);598 const err_span = try module_err_msg.src_loc.span(module.gpa);
589 const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main);599 const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main);
590600