| author | |
| committer | |
| log | cd0d514643404103a83881fc4d7c46674ed9f991 |
| tree | 9fd84f8cb8cad2278680b72e4043ac10084d3d12 |
| parent | ebcfc86bb9c8cec1a66511858a6443b1927191f2 |
Playtime is over. I'm working on networking now.22 files changed, 143 insertions(+), 3448 deletions(-)
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 | ||
| ... | @@ -476,11 +477,20 @@ pub const CLOCK = struct { | ... | @@ -476,11 +477,20 @@ pub const CLOCK = struct { |
| 476 | 477 | ||
| 477 | pub const sockaddr = extern struct { | 478 | pub const sockaddr = extern struct { |
| 478 | len: u8, | 479 | len: u8, |
| 479 | family: u8, | 480 | family: sa_family_t, |
| 480 | data: [14]u8, | 481 | data: [14]u8, |
| 481 | 482 | ||
| 482 | pub const SS_MAXSIZE = 128; | 483 | pub const SS_MAXSIZE = 128; |
| 483 | pub const storage = std.x.os.Socket.Address.Native.Storage; | 484 | pub const storage = extern struct { |
| 485 | len: u8 align(8), | ||
| 486 | family: sa_family_t, | ||
| 487 | padding: [126]u8 = undefined, | ||
| 488 | |||
| 489 | comptime { | ||
| 490 | assert(@sizeOf(storage) == SS_MAXSIZE); | ||
| 491 | assert(@alignOf(storage) == 8); | ||
| 492 | } | ||
| 493 | }; | ||
| 484 | 494 | ||
| 485 | pub const in = extern struct { | 495 | pub const in = extern struct { |
| 486 | len: u8 = @sizeOf(in), | 496 | 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; |
| ... | @@ -401,7 +402,16 @@ pub const sockaddr = extern struct { | ... | @@ -401,7 +402,16 @@ pub const sockaddr = extern struct { |
| 401 | data: [14]u8, | 402 | data: [14]u8, |
| 402 | 403 | ||
| 403 | pub const SS_MAXSIZE = 128; | 404 | pub const SS_MAXSIZE = 128; |
| 404 | pub const storage = std.x.os.Socket.Address.Native.Storage; | 405 | pub const storage = extern struct { |
| 406 | len: u8 align(8), | ||
| 407 | family: sa_family_t, | ||
| 408 | padding: [126]u8 = undefined, | ||
| 409 | |||
| 410 | comptime { | ||
| 411 | assert(@sizeOf(storage) == SS_MAXSIZE); | ||
| 412 | assert(@alignOf(storage) == 8); | ||
| 413 | } | ||
| 414 | }; | ||
| 405 | 415 | ||
| 406 | pub const in = extern struct { | 416 | pub const in = extern struct { |
| 407 | len: u8 = @sizeOf(in), | 417 | 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/os.zig+2-2| ... | @@ -5616,11 +5616,11 @@ pub fn sendmsg( | ... | @@ -5616,11 +5616,11 @@ pub fn sendmsg( |
| 5616 | /// The file descriptor of the sending socket. | 5616 | /// The file descriptor of the sending socket. |
| 5617 | sockfd: socket_t, | 5617 | sockfd: socket_t, |
| 5618 | /// Message header and iovecs | 5618 | /// Message header and iovecs |
| 5619 | msg: msghdr_const, | 5619 | msg: *const msghdr_const, |
| 5620 | flags: u32, | 5620 | flags: u32, |
| 5621 | ) SendMsgError!usize { | 5621 | ) SendMsgError!usize { |
| 5622 | while (true) { | 5622 | while (true) { |
| 5623 | const rc = system.sendmsg(sockfd, @ptrCast(*const std.x.os.Socket.Message, &msg), @intCast(c_int, flags)); | 5623 | const rc = system.sendmsg(sockfd, msg, flags); |
| 5624 | if (builtin.os.tag == .windows) { | 5624 | if (builtin.os.tag == .windows) { |
| 5625 | if (rc == windows.ws2_32.SOCKET_ERROR) { | 5625 | if (rc == windows.ws2_32.SOCKET_ERROR) { |
| 5626 | switch (windows.ws2_32.WSAGetLastError()) { | 5626 | 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| ... | @@ -90,7 +90,6 @@ pub const tz = @import("tz.zig"); | ... | @@ -90,7 +90,6 @@ pub const tz = @import("tz.zig"); |
| 90 | pub const unicode = @import("unicode.zig"); | 90 | pub const unicode = @import("unicode.zig"); |
| 91 | pub const valgrind = @import("valgrind.zig"); | 91 | pub const valgrind = @import("valgrind.zig"); |
| 92 | pub const wasm = @import("wasm.zig"); | 92 | pub const wasm = @import("wasm.zig"); |
| 93 | pub const x = @import("x.zig"); | ||
| 94 | pub const zig = @import("zig.zig"); | 93 | pub const zig = @import("zig.zig"); |
| 95 | pub const start = @import("start.zig"); | 94 | pub const start = @import("start.zig"); |
| 96 | 95 |
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 | } | ||