| author | |
| committer | |
| log | c9ef277fa7e43f119a7f2896635b4fdf9c97edbe |
| tree | dd0fa2ef288eec970e973476a4f6809723a04bd0 |
| parent | 8bd734d60cd55d65ea52a051ccdc35939edeb99c |
| parent | 7178451d6258d9d04fdf03269478948643c39f02 |
| signature |
networking: delete std.x; add std.crypto.tls and std.http.Client38 files changed, 3917 insertions(+), 3707 deletions(-)
lib/std/Url.zig created+98| ... | @@ -0,0 +1,98 @@ | ||
| 1 | scheme: []const u8, | ||
| 2 | host: []const u8, | ||
| 3 | path: []const u8, | ||
| 4 | port: ?u16, | ||
| 5 | |||
| 6 | /// TODO: redo this implementation according to RFC 1738. This code is only a | ||
| 7 | /// placeholder for now. | ||
| 8 | pub 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 | |||
| 80 | const Url = @This(); | ||
| 81 | const std = @import("std.zig"); | ||
| 82 | const testing = std.testing; | ||
| 83 | |||
| 84 | test "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 | |||
| 92 | test "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; |
| 209 | pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const std.x.os.Socket.Message, flags: c_int) isize; | 209 | pub extern "c" fn sendmsg(sockfd: c.fd_t, msg: *const c.msghdr_const, flags: u32) isize; |
| 210 | 210 | ||
| 211 | pub extern "c" fn recv(sockfd: c.fd_t, arg1: ?*anyopaque, arg2: usize, arg3: c_int) isize; | 211 | pub extern "c" fn recv(sockfd: c.fd_t, arg1: ?*anyopaque, arg2: usize, arg3: c_int) isize; |
| 212 | pub extern "c" fn recvfrom( | 212 | pub 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; |
| 220 | pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *std.x.os.Socket.Message, flags: c_int) isize; | 220 | pub extern "c" fn recvmsg(sockfd: c.fd_t, msg: *c.msghdr, flags: u32) isize; |
| 221 | 221 | ||
| 222 | pub extern "c" fn kill(pid: c.pid_t, sig: c_int) c_int; | 222 | pub extern "c" fn kill(pid: c.pid_t, sig: c_int) c_int; |
| 223 | pub extern "c" fn getdirentries(fd: c.fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize; | 223 | pub 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, |
| 1008 | 1008 | ||
| 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 @@ |
| 1 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const std = @import("../std.zig"); | 2 | const std = @import("../std.zig"); |
| 3 | const assert = std.debug.assert; | ||
| 3 | const maxInt = std.math.maxInt; | 4 | const maxInt = std.math.maxInt; |
| 4 | const iovec = std.os.iovec; | 5 | const iovec = std.os.iovec; |
| 5 | 6 | ||
| ... | @@ -478,11 +479,20 @@ pub const CLOCK = struct { | ... | @@ -478,11 +479,20 @@ pub const CLOCK = struct { |
| 478 | 479 | ||
| 479 | pub const sockaddr = extern struct { | 480 | pub 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, |
| 483 | 484 | ||
| 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 | }; | ||
| 486 | 496 | ||
| 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 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | ||
| 2 | const builtin = @import("builtin"); | 3 | const builtin = @import("builtin"); |
| 3 | const maxInt = std.math.maxInt; | 4 | const maxInt = std.math.maxInt; |
| 4 | const iovec = std.os.iovec; | 5 | const 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, |
| 405 | 406 | ||
| 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 | }; | ||
| 408 | 418 | ||
| 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 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | ||
| 2 | const builtin = @import("builtin"); | 3 | const builtin = @import("builtin"); |
| 3 | const maxInt = std.math.maxInt; | 4 | const maxInt = std.math.maxInt; |
| 4 | const iovec = std.os.iovec; | 5 | const 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, |
| 340 | 341 | ||
| 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 | }; | ||
| 343 | 353 | ||
| 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 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | ||
| 2 | const builtin = @import("builtin"); | 3 | const builtin = @import("builtin"); |
| 3 | const maxInt = std.math.maxInt; | 4 | const maxInt = std.math.maxInt; |
| 4 | const iovec = std.os.iovec; | 5 | const 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, |
| 482 | 483 | ||
| 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 | }; | ||
| 485 | 495 | ||
| 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 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | ||
| 2 | const maxInt = std.math.maxInt; | 3 | const maxInt = std.math.maxInt; |
| 3 | const builtin = @import("builtin"); | 4 | const builtin = @import("builtin"); |
| 4 | const iovec = std.os.iovec; | 5 | const 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, |
| 373 | 374 | ||
| 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 | }; | ||
| 376 | 386 | ||
| 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 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | ||
| 2 | const builtin = @import("builtin"); | 3 | const builtin = @import("builtin"); |
| 3 | const maxInt = std.math.maxInt; | 4 | const maxInt = std.math.maxInt; |
| 4 | const iovec = std.os.iovec; | 5 | const 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, |
| 436 | 437 | ||
| 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 | }; | ||
| 439 | 448 | ||
| 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"); |
| 176 | 176 | ||
| 177 | pub const errors = @import("crypto/errors.zig"); | 177 | pub const errors = @import("crypto/errors.zig"); |
| 178 | 178 | ||
| 179 | pub const tls = @import("crypto/tls.zig"); | ||
| 180 | pub const Certificate = @import("crypto/Certificate.zig"); | ||
| 181 | |||
| 179 | test { | 182 | test { |
| 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 | } |
| 268 | 273 | ||
| 269 | test "CSPRNG" { | 274 | test "CSPRNG" { |
lib/std/crypto/Certificate.zig created+1115| ... | @@ -0,0 +1,1115 @@ | ||
| 1 | buffer: []const u8, | ||
| 2 | index: u32, | ||
| 3 | |||
| 4 | pub const Bundle = @import("Certificate/Bundle.zig"); | ||
| 5 | |||
| 6 | pub 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 | |||
| 40 | pub 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 | |||
| 50 | pub 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 | |||
| 74 | pub 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 | |||
| 84 | pub 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 | |||
| 108 | pub 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 | |||
| 121 | pub 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 | |||
| 298 | pub 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 | |||
| 421 | pub 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 | |||
| 427 | pub fn contents(cert: Certificate, elem: der.Element) []const u8 { | ||
| 428 | return cert.buffer[elem.slice.start..elem.slice.end]; | ||
| 429 | } | ||
| 430 | |||
| 431 | pub 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. | ||
| 438 | pub 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 | |||
| 477 | const 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 | |||
| 524 | pub 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 | |||
| 533 | test 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 | |||
| 544 | pub 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 | |||
| 553 | test 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 | |||
| 564 | pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm { | ||
| 565 | return parseEnum(Algorithm, bytes, element); | ||
| 566 | } | ||
| 567 | |||
| 568 | pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory { | ||
| 569 | return parseEnum(AlgorithmCategory, bytes, element); | ||
| 570 | } | ||
| 571 | |||
| 572 | pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute { | ||
| 573 | return parseEnum(Attribute, bytes, element); | ||
| 574 | } | ||
| 575 | |||
| 576 | pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve { | ||
| 577 | return parseEnum(NamedCurve, bytes, element); | ||
| 578 | } | ||
| 579 | |||
| 580 | pub fn parseExtensionId(bytes: []const u8, element: der.Element) !ExtensionId { | ||
| 581 | return parseEnum(ExtensionId, bytes, element); | ||
| 582 | } | ||
| 583 | |||
| 584 | fn 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 | |||
| 591 | pub 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 | |||
| 599 | fn 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 | |||
| 680 | fn 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 | |||
| 716 | const std = @import("../std.zig"); | ||
| 717 | const crypto = std.crypto; | ||
| 718 | const mem = std.mem; | ||
| 719 | const Certificate = @This(); | ||
| 720 | |||
| 721 | pub 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 | |||
| 805 | test { | ||
| 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. | ||
| 816 | pub 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. | ||
| 9 | map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .{}, | ||
| 10 | bytes: std.ArrayListUnmanaged(u8) = .{}, | ||
| 11 | |||
| 12 | pub const VerifyError = Certificate.Parsed.VerifyError || error{ | ||
| 13 | CertificateIssuerNotFound, | ||
| 14 | }; | ||
| 15 | |||
| 16 | pub 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. | ||
| 30 | pub 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 | |||
| 47 | pub 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. | ||
| 57 | pub 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 | |||
| 70 | pub 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 | |||
| 93 | pub 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 | |||
| 153 | const builtin = @import("builtin"); | ||
| 154 | const std = @import("../../std.zig"); | ||
| 155 | const fs = std.fs; | ||
| 156 | const mem = std.mem; | ||
| 157 | const crypto = std.crypto; | ||
| 158 | const Allocator = std.mem.Allocator; | ||
| 159 | const Certificate = std.crypto.Certificate; | ||
| 160 | const der = Certificate.der; | ||
| 161 | const Bundle = @This(); | ||
| 162 | |||
| 163 | const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n"); | ||
| 164 | |||
| 165 | const 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 | |||
| 182 | test "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 | } |
| 97 | 97 |
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 | } |
| 144 | 144 | ||
| 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 | } |
| 177 | 182 | ||
| 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 | } |
| 623 | 634 | ||
| 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 | } |
| 656 | 672 | ||
| 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; |
| 659 | 681 |
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 | |||
| 33 | const std = @import("../std.zig"); | ||
| 34 | const Tls = @This(); | ||
| 35 | const net = std.net; | ||
| 36 | const mem = std.mem; | ||
| 37 | const crypto = std.crypto; | ||
| 38 | const assert = std.debug.assert; | ||
| 39 | |||
| 40 | pub const Client = @import("tls/Client.zig"); | ||
| 41 | |||
| 42 | pub const record_header_len = 5; | ||
| 43 | pub const max_ciphertext_len = (1 << 14) + 256; | ||
| 44 | pub const max_ciphertext_record_len = max_ciphertext_len + record_header_len; | ||
| 45 | pub 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 | |||
| 50 | pub const close_notify_alert = [_]u8{ | ||
| 51 | @enumToInt(AlertLevel.warning), | ||
| 52 | @enumToInt(AlertDescription.close_notify), | ||
| 53 | }; | ||
| 54 | |||
| 55 | pub const ProtocolVersion = enum(u16) { | ||
| 56 | tls_1_2 = 0x0303, | ||
| 57 | tls_1_3 = 0x0304, | ||
| 58 | _, | ||
| 59 | }; | ||
| 60 | |||
| 61 | pub 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 | |||
| 70 | pub 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 | |||
| 85 | pub 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 | |||
| 134 | pub const AlertLevel = enum(u8) { | ||
| 135 | warning = 1, | ||
| 136 | fatal = 2, | ||
| 137 | _, | ||
| 138 | }; | ||
| 139 | |||
| 140 | pub 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 | |||
| 171 | pub 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 | |||
| 203 | pub 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 | |||
| 221 | pub 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 | |||
| 232 | pub const CertificateType = enum(u8) { | ||
| 233 | X509 = 0, | ||
| 234 | RawPublicKey = 2, | ||
| 235 | _, | ||
| 236 | }; | ||
| 237 | |||
| 238 | pub const KeyUpdateRequest = enum(u8) { | ||
| 239 | update_not_requested = 0, | ||
| 240 | update_requested = 1, | ||
| 241 | _, | ||
| 242 | }; | ||
| 243 | |||
| 244 | pub 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 | |||
| 263 | pub 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 | |||
| 271 | pub 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. | ||
| 288 | pub 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 | |||
| 296 | pub 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 | |||
| 323 | pub 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 | |||
| 329 | pub 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 | |||
| 335 | pub inline fn extension(comptime et: ExtensionType, bytes: anytype) [2 + 2 + bytes.len]u8 { | ||
| 336 | return int2(@enumToInt(et)) ++ array(1, bytes); | ||
| 337 | } | ||
| 338 | |||
| 339 | pub 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 | |||
| 344 | pub 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 | |||
| 354 | pub inline fn int2(x: u16) [2]u8 { | ||
| 355 | return .{ | ||
| 356 | @truncate(u8, x >> 8), | ||
| 357 | @truncate(u8, x), | ||
| 358 | }; | ||
| 359 | } | ||
| 360 | |||
| 361 | pub 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. | ||
| 371 | pub 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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const tls = std.crypto.tls; | ||
| 3 | const Client = @This(); | ||
| 4 | const net = std.net; | ||
| 5 | const mem = std.mem; | ||
| 6 | const crypto = std.crypto; | ||
| 7 | const assert = std.debug.assert; | ||
| 8 | const Certificate = std.crypto.Certificate; | ||
| 9 | |||
| 10 | const max_ciphertext_len = tls.max_ciphertext_len; | ||
| 11 | const hkdfExpandLabel = tls.hkdfExpandLabel; | ||
| 12 | const int2 = tls.int2; | ||
| 13 | const int3 = tls.int3; | ||
| 14 | const array = tls.array; | ||
| 15 | const enum_array = tls.enum_array; | ||
| 16 | |||
| 17 | read_seq: u64, | ||
| 18 | write_seq: u64, | ||
| 19 | /// The starting index of cleartext bytes inside `partially_read_buffer`. | ||
| 20 | partial_cleartext_idx: u15, | ||
| 21 | /// The ending index of cleartext bytes inside `partially_read_buffer` as well | ||
| 22 | /// as the starting index of ciphertext bytes. | ||
| 23 | partial_ciphertext_idx: u15, | ||
| 24 | /// The ending index of ciphertext bytes inside `partially_read_buffer`. | ||
| 25 | partial_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`. | ||
| 28 | received_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. | ||
| 36 | allow_truncation_attacks: bool = false, | ||
| 37 | application_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. | ||
| 46 | partially_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. | ||
| 55 | pub 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. | ||
| 95 | pub 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`. | ||
| 640 | pub 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`. | ||
| 645 | pub 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. | ||
| 656 | pub 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. | ||
| 668 | pub 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 | |||
| 709 | fn 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 | |||
| 779 | pub 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. | ||
| 790 | pub 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`. | ||
| 796 | pub 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. | ||
| 804 | pub 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. | ||
| 814 | pub 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. | ||
| 825 | pub 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`. | ||
| 851 | pub 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 | |||
| 1142 | fn 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 | |||
| 1157 | fn 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 | |||
| 1173 | fn 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 | |||
| 1181 | const builtin = @import("builtin"); | ||
| 1182 | const native_endian = builtin.cpu.arch.endian(); | ||
| 1183 | |||
| 1184 | inline fn big(x: anytype) @TypeOf(x) { | ||
| 1185 | return switch (native_endian) { | ||
| 1186 | .Big => x, | ||
| 1187 | .Little => @byteSwap(x), | ||
| 1188 | }; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | fn 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. | ||
| 1201 | const 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. | ||
| 1262 | fn 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 | ||
| 1298 | const 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 | |||
| 1306 | test { | ||
| 1307 | _ = StreamInterface; | ||
| 1308 | } | ||
lib/std/http.zig+297-4| ... | @@ -1,8 +1,301 @@ | ... | @@ -1,8 +1,301 @@ |
| 1 | const std = @import("std.zig"); | 1 | pub 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 | ||
| 6 | pub 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 | ||
| 67 | pub 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", | ||
| 2 | 144 | ||
| 3 | pub const Method = @import("http/method.zig").Method; | 145 | // 2xx statuses |
| 4 | pub 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 | |||
| 245 | pub 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 | |||
| 294 | const std = @import("std.zig"); | ||
| 5 | 295 | ||
| 6 | test { | 296 | test { |
| 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 | |||
| 5 | const std = @import("../std.zig"); | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const http = std.http; | ||
| 8 | const net = std.net; | ||
| 9 | const Client = @This(); | ||
| 10 | const Url = std.Url; | ||
| 11 | |||
| 12 | allocator: std.mem.Allocator, | ||
| 13 | headers: std.ArrayListUnmanaged(u8) = .{}, | ||
| 14 | active_requests: usize = 0, | ||
| 15 | ca_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. | ||
| 20 | pub 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 | |||
| 116 | pub fn deinit(client: *Client) void { | ||
| 117 | assert(client.active_requests == 0); | ||
| 118 | client.headers.deinit(client.allocator); | ||
| 119 | client.* = undefined; | ||
| 120 | } | ||
| 121 | |||
| 122 | pub 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 | |||
| 174 | pub 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 | ||
| 7 | pub 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 | |||
| 4 | const std = @import("../std.zig"); | ||
| 5 | |||
| 6 | pub 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 | |||
| 174 | test { | ||
| 175 | try std.testing.expectEqualStrings("OK", Status.ok.phrase().?); | ||
| 176 | try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?); | ||
| 177 | } | ||
| 178 | |||
| 179 | test { | ||
| 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" { |
| 810 | 810 | ||
| 811 | const TagPayloadType = TagPayload; | 811 | const TagPayloadType = TagPayload; |
| 812 | 812 | ||
| 813 | ///Given a tagged union type, and an enum, return the type of the union | 813 | pub fn TagPayloadByName(comptime U: type, comptime tag_name: []const u8) type { |
| 814 | /// field corresponding to the enum tag. | ||
| 815 | pub 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)); |
| 817 | 815 | ||
| 818 | const info = @typeInfo(U).Union; | 816 | const info = @typeInfo(U).Union; |
| 819 | 817 | ||
| 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 | } |
| 824 | 822 | ||
| 825 | unreachable; | 823 | unreachable; |
| 826 | } | 824 | } |
| 827 | 825 | ||
| 826 | /// Given a tagged union type, and an enum, return the type of the union field | ||
| 827 | /// corresponding to the enum tag. | ||
| 828 | pub fn TagPayload(comptime U: type, comptime tag: Tag(U)) type { | ||
| 829 | return TagPayloadByName(U, @tagName(tag)); | ||
| 830 | } | ||
| 831 | |||
| 828 | test "std.meta.TagPayload" { | 832 | test "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 | } |
| 1674 | 1674 | ||
| 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's | 1709 | /// 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 properly | 1710 | /// 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 | } |
| 1689 | 1723 | ||
| 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/7699 | 1731 | /// 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 iovecs | 5688 | /// 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 | } |
| 1228 | 1228 | ||
| 1229 | pub fn sendmsg(fd: i32, msg: *const std.x.os.Socket.Message, flags: c_int) usize { | 1229 | pub 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 | } |
| 1235 | 1238 | ||
| 1236 | pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize { | 1239 | 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,24 +1277,42 @@ pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize |
| 1274 | } | 1277 | } |
| 1275 | 1278 | ||
| 1276 | pub fn connect(fd: i32, addr: *const anyopaque, len: socklen_t) usize { | 1279 | pub 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 | } |
| 1282 | 1288 | ||
| 1283 | pub fn recvmsg(fd: i32, msg: *std.x.os.Socket.Message, flags: c_int) usize { | 1289 | pub 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 | } |
| 1289 | 1298 | ||
| 1290 | pub fn recvfrom(fd: i32, noalias buf: [*]u8, len: usize, flags: u32, noalias addr: ?*sockaddr, noalias alen: ?*socklen_t) usize { | 1299 | pub 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 | } |
| 1296 | 1317 | ||
| 1297 | pub fn shutdown(fd: i32, how: i32) usize { | 1318 | pub 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, |
| 3220 | 3241 | ||
| 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 | }; | ||
| 3223 | 3252 | ||
| 3224 | /// IPv4 socket address | 3253 | /// 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 that | 6 | //! 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 the | 9 | //! Seccomp filters are classic BPF programs. Conceptually, a seccomp program |
| 10 | //! information under `std.x.net.bpf` applies here as well. Conceptually, a | 10 | //! 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 an | 12 | //! 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 are | 13 | //! "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 to | 14 | //! 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, only | 16 | //! 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 made | 18 | //! 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 @@ |
| 1 | const std = @import("../../std.zig"); | 1 | const std = @import("../../std.zig"); |
| 2 | const assert = std.debug.assert; | ||
| 2 | const windows = std.os.windows; | 3 | const windows = std.os.windows; |
| 3 | 4 | ||
| 4 | const WINAPI = windows.WINAPI; | 5 | const 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, |
| 1107 | 1108 | ||
| 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 | }; | ||
| 1110 | 1119 | ||
| 1111 | /// IPv4 socket address | 1120 | /// 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 ( |
| 1207 | 1216 | ||
| 1208 | pub const LPFN_WSASENDMSG = *const fn ( | 1217 | pub 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 ( |
| 1216 | 1225 | ||
| 1217 | pub const LPFN_WSARECVMSG = *const fn ( | 1226 | pub 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( |
| 2090 | 2099 | ||
| 2091 | pub extern "ws2_32" fn WSASendMsg( | 2100 | pub 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( |
| 2099 | 2108 | ||
| 2100 | pub extern "ws2_32" fn WSARecvMsg( | 2109 | pub 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; |
| 42 | pub const Thread = @import("Thread.zig"); | 42 | pub const Thread = @import("Thread.zig"); |
| 43 | pub const Treap = @import("treap.zig").Treap; | 43 | pub const Treap = @import("treap.zig").Treap; |
| 44 | pub const Tz = tz.Tz; | 44 | pub const Tz = tz.Tz; |
| 45 | pub const Url = @import("Url.zig"); | ||
| 45 | 46 | ||
| 46 | pub const array_hash_map = @import("array_hash_map.zig"); | 47 | pub const array_hash_map = @import("array_hash_map.zig"); |
| 47 | pub const atomic = @import("atomic.zig"); | 48 | pub 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"); |
| 90 | pub const unicode = @import("unicode.zig"); | 91 | pub const unicode = @import("unicode.zig"); |
| 91 | pub const valgrind = @import("valgrind.zig"); | 92 | pub const valgrind = @import("valgrind.zig"); |
| 92 | pub const wasm = @import("wasm.zig"); | 93 | pub const wasm = @import("wasm.zig"); |
| 93 | pub const x = @import("x.zig"); | ||
| 94 | pub const zig = @import("zig.zig"); | 94 | pub const zig = @import("zig.zig"); |
| 95 | pub const start = @import("start.zig"); | 95 | pub const start = @import("start.zig"); |
| 96 | 96 |
lib/std/x.zig deleted-19| ... | @@ -1,19 +0,0 @@ | ||
| 1 | const std = @import("std.zig"); | ||
| 2 | |||
| 3 | pub 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 | |||
| 9 | pub 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 | |||
| 15 | test { | ||
| 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 | ||
| 122 | const std = @import("std"); | ||
| 123 | const builtin = @import("builtin"); | ||
| 124 | const native_endian = builtin.target.cpu.arch.endian(); | ||
| 125 | const mem = std.mem; | ||
| 126 | const math = std.math; | ||
| 127 | const random = std.crypto.random; | ||
| 128 | const assert = std.debug.assert; | ||
| 129 | const expectEqual = std.testing.expectEqual; | ||
| 130 | const expectError = std.testing.expectError; | ||
| 131 | const expect = std.testing.expect; | ||
| 132 | |||
| 133 | // instruction classes | ||
| 134 | /// ld, ldh, ldb: Load data into a. | ||
| 135 | pub const LD = 0x00; | ||
| 136 | /// ldx: Load data into x. | ||
| 137 | pub const LDX = 0x01; | ||
| 138 | /// st: Store into scratch memory the value of a. | ||
| 139 | pub const ST = 0x02; | ||
| 140 | /// st: Store into scratch memory the value of x. | ||
| 141 | pub const STX = 0x03; | ||
| 142 | /// alu: Wrapping arithmetic/bitwise operations on a using the value of k/x. | ||
| 143 | pub const ALU = 0x04; | ||
| 144 | /// jmp, jeq, jgt, je, jset: Increment the program counter based on a comparison | ||
| 145 | /// between k/x and the accumulator. | ||
| 146 | pub const JMP = 0x05; | ||
| 147 | /// ret: Return a verdict using the value of k/the accumulator. | ||
| 148 | pub const RET = 0x06; | ||
| 149 | /// tax, txa: Register value copying between X and a. | ||
| 150 | pub const MISC = 0x07; | ||
| 151 | |||
| 152 | // Size of data to be loaded from the packet. | ||
| 153 | /// ld: 32-bit full word. | ||
| 154 | pub const W = 0x00; | ||
| 155 | /// ldh: 16-bit half word. | ||
| 156 | pub const H = 0x08; | ||
| 157 | /// ldb: Single byte. | ||
| 158 | pub const B = 0x10; | ||
| 159 | |||
| 160 | // Addressing modes used for loads to a/x. | ||
| 161 | /// #k: The immediate value stored in k. | ||
| 162 | pub const IMM = 0x00; | ||
| 163 | /// [k]: The value at offset k in the packet. | ||
| 164 | pub const ABS = 0x20; | ||
| 165 | /// [x + k]: The value at offset x + k in the packet. | ||
| 166 | pub const IND = 0x40; | ||
| 167 | /// M[k]: The value of the k'th scratch memory register. | ||
| 168 | pub const MEM = 0x60; | ||
| 169 | /// #len: The size of the packet. | ||
| 170 | pub 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. | ||
| 174 | pub const MSH = 0xa0; | ||
| 175 | /// arc4random: 32-bit integer generated from a CPRNG (see arc4random(3)) loaded into a. | ||
| 176 | /// EXTENSION. Defined for: | ||
| 177 | /// - OpenBSD. | ||
| 178 | pub 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). | ||
| 184 | pub 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). | ||
| 187 | pub const X = 0x08; | ||
| 188 | /// Return the value of a for returns (ret a). | ||
| 189 | pub 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. | ||
| 195 | pub const ADD = 0x00; | ||
| 196 | /// sub: a = a - k | ||
| 197 | /// a = a - x. | ||
| 198 | pub const SUB = 0x10; | ||
| 199 | /// mul: a = a * k | ||
| 200 | /// a = a * x. | ||
| 201 | pub const MUL = 0x20; | ||
| 202 | /// div: a = a / k | ||
| 203 | /// a = a / x. | ||
| 204 | /// Truncated division. | ||
| 205 | pub const DIV = 0x30; | ||
| 206 | /// or: a = a | k | ||
| 207 | /// a = a | x. | ||
| 208 | pub const OR = 0x40; | ||
| 209 | /// and: a = a & k | ||
| 210 | /// a = a & x. | ||
| 211 | pub const AND = 0x50; | ||
| 212 | /// lsh: a = a << k | ||
| 213 | /// a = a << x. | ||
| 214 | /// a = a << k, a = a << x. | ||
| 215 | pub const LSH = 0x60; | ||
| 216 | /// rsh: a = a >> k | ||
| 217 | /// a = a >> x. | ||
| 218 | pub const RSH = 0x70; | ||
| 219 | /// neg: a = -a. | ||
| 220 | /// Note that this isn't a binary negation, rather the value of `~a + 1`. | ||
| 221 | pub 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. | ||
| 228 | pub 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. | ||
| 235 | pub 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. | ||
| 240 | pub const JA = 0x00; | ||
| 241 | /// jeq #k, Lt, Lf: pc += (a == k) ? jt : jf. | ||
| 242 | /// jeq x, Lt, Lf: pc += (a == x) ? jt : jf. | ||
| 243 | pub const JEQ = 0x10; | ||
| 244 | /// jgt #k, Lt, Lf: pc += (a > k) ? jt : jf. | ||
| 245 | /// jgt x, Lt, Lf: pc += (a > x) ? jt : jf. | ||
| 246 | pub const JGT = 0x20; | ||
| 247 | /// jge #k, Lt, Lf: pc += (a >= k) ? jt : jf. | ||
| 248 | /// jge x, Lt, Lf: pc += (a >= x) ? jt : jf. | ||
| 249 | pub 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. | ||
| 252 | pub const JSET = 0x40; | ||
| 253 | |||
| 254 | // Miscellaneous operations/register copy. | ||
| 255 | /// tax: x = a. | ||
| 256 | pub const TAX = 0x00; | ||
| 257 | /// txa: a = x. | ||
| 258 | pub const TXA = 0x80; | ||
| 259 | |||
| 260 | /// The 16 registers in the scratch memory store as named enums. | ||
| 261 | pub const Scratch = enum(u4) { m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 }; | ||
| 262 | pub const MEMWORDS = 16; | ||
| 263 | pub const MAXINSNS = switch (builtin.os.tag) { | ||
| 264 | .linux => 4096, | ||
| 265 | else => 512, | ||
| 266 | }; | ||
| 267 | pub const MINBUFSIZE = 32; | ||
| 268 | pub const MAXBUFSIZE = 1 << 21; | ||
| 269 | |||
| 270 | pub 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 | |||
| 434 | fn opcodeEqual(opcode: u16, insn: Insn) !void { | ||
| 435 | try expectEqual(opcode, insn.opcode); | ||
| 436 | } | ||
| 437 | |||
| 438 | test "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 | |||
| 497 | pub 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. | ||
| 507 | pub 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 | // ``` | ||
| 643 | const 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 | ||
| 667 | const 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 | |||
| 686 | test "tcpdump filter" { | ||
| 687 | try expectEqual( | ||
| 688 | @as(u32, 0x40000), | ||
| 689 | try simulate(ftp_data, &tcpdump_filter, .Big), | ||
| 690 | ); | ||
| 691 | } | ||
| 692 | |||
| 693 | fn 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 | |||
| 700 | fn 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 | |||
| 707 | test "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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | |||
| 3 | const fmt = std.fmt; | ||
| 4 | |||
| 5 | const IPv4 = std.x.os.IPv4; | ||
| 6 | const IPv6 = std.x.os.IPv6; | ||
| 7 | const Socket = std.x.os.Socket; | ||
| 8 | |||
| 9 | /// A generic IP abstraction. | ||
| 10 | const ip = @This(); | ||
| 11 | |||
| 12 | /// A union of all eligible types of IP addresses. | ||
| 13 | pub 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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | const io = std.io; | ||
| 5 | const os = std.os; | ||
| 6 | const ip = std.x.net.ip; | ||
| 7 | |||
| 8 | const fmt = std.fmt; | ||
| 9 | const mem = std.mem; | ||
| 10 | const testing = std.testing; | ||
| 11 | const native_os = builtin.os; | ||
| 12 | |||
| 13 | const IPv4 = std.x.os.IPv4; | ||
| 14 | const IPv6 = std.x.os.IPv6; | ||
| 15 | const Socket = std.x.os.Socket; | ||
| 16 | const Buffer = std.x.os.Buffer; | ||
| 17 | |||
| 18 | /// A generic TCP socket abstraction. | ||
| 19 | const tcp = @This(); | ||
| 20 | |||
| 21 | /// A TCP client-address pair. | ||
| 22 | pub 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. | ||
| 49 | pub const Domain = enum(u16) { | ||
| 50 | ip = os.AF.INET, | ||
| 51 | ipv6 = os.AF.INET6, | ||
| 52 | }; | ||
| 53 | |||
| 54 | /// A TCP client. | ||
| 55 | pub 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. | ||
| 240 | pub 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 | |||
| 322 | test "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 | |||
| 346 | test "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 | |||
| 374 | test "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 | |||
| 418 | test "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 | |||
| 431 | test "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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | const os = std.os; | ||
| 5 | const mem = std.mem; | ||
| 6 | const testing = std.testing; | ||
| 7 | const native_os = builtin.os; | ||
| 8 | const 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. | ||
| 12 | pub 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 | } | ||
| 29 | else | ||
| 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 | |||
| 47 | pub 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 | |||
| 134 | test "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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | const os = std.os; | ||
| 5 | const fmt = std.fmt; | ||
| 6 | const mem = std.mem; | ||
| 7 | const math = std.math; | ||
| 8 | const testing = std.testing; | ||
| 9 | const native_os = builtin.os; | ||
| 10 | const have_ifnamesize = @hasDecl(os.system, "IFNAMESIZE"); | ||
| 11 | |||
| 12 | pub 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. | ||
| 30 | pub 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. | ||
| 71 | pub 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. | ||
| 249 | pub 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 | |||
| 533 | test { | ||
| 534 | testing.refAllDecls(@This()); | ||
| 535 | } | ||
| 536 | |||
| 537 | test "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 | |||
| 548 | test "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 | |||
| 562 | test "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 | |||
| 590 | test "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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const net = @import("net.zig"); | ||
| 4 | |||
| 5 | const os = std.os; | ||
| 6 | const fmt = std.fmt; | ||
| 7 | const mem = std.mem; | ||
| 8 | const time = std.time; | ||
| 9 | const meta = std.meta; | ||
| 10 | const native_os = builtin.os; | ||
| 11 | const native_endian = builtin.cpu.arch.endian(); | ||
| 12 | |||
| 13 | const Buffer = std.x.os.Buffer; | ||
| 14 | |||
| 15 | const assert = std.debug.assert; | ||
| 16 | |||
| 17 | /// A generic, cross-platform socket abstraction. | ||
| 18 | pub 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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | |||
| 3 | const os = std.os; | ||
| 4 | const mem = std.mem; | ||
| 5 | const time = std.time; | ||
| 6 | |||
| 7 | pub 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 @@ | ||
| 1 | const std = @import("../../std.zig"); | ||
| 2 | const net = @import("net.zig"); | ||
| 3 | |||
| 4 | const os = std.os; | ||
| 5 | const mem = std.mem; | ||
| 6 | |||
| 7 | const windows = std.os.windows; | ||
| 8 | const ws2_32 = windows.ws2_32; | ||
| 9 | |||
| 10 | pub 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); |
| 590 | 600 |