authorgravatar for kenta@lithdew.netlithdew <kenta@lithdew.net> 2021-04-30 21:08:49+09:00
committergravatar for kenta@lithdew.netlithdew <kenta@lithdew.net> 2021-05-03 14:49:10+09:00
log13068da43e8fbd0ae5d03aff27fb4e8802e1218c
treea5fd65eb86f80ac4b0d6495ac3b34784b45d0612
parent2ab588049e41a96337a4fa8c2d9507320bc4278b

x/os, x/net: re-approach `Address`, rename namespace `TCP -> tcp`

Address comments from @ifreund and @MasterQ32 to address unsafeness and ergonomics of the `Address` API. Rename the `TCP` namespace to `tcp` as it does not contain any top-level fields. Fix missing reference to `sockaddr` which was identified by @kprotty in os/bits/linux/arm64.zig.

8 files changed, 534 insertions(+), 435 deletions(-)

lib/std/builtin.zig+1-17
...@@ -150,23 +150,7 @@ pub const Mode = enum {...@@ -150,23 +150,7 @@ pub const Mode = enum {
150150
151/// This data structure is used by the Zig language code generation and151/// This data structure is used by the Zig language code generation and
152/// therefore must be kept in sync with the compiler implementation.152/// therefore must be kept in sync with the compiler implementation.
153pub const CallingConvention = enum {153pub const CallingConvention = enum { Unspecified, C, Naked, Async, Inline, Interrupt, Signal, Stdcall, Fastcall, Vectorcall, Thiscall, APCS, AAPCS, AAPCSVFP, SysV };
154 Unspecified,
155 C,
156 Naked,
157 Async,
158 Inline,
159 Interrupt,
160 Signal,
161 Stdcall,
162 Fastcall,
163 Vectorcall,
164 Thiscall,
165 APCS,
166 AAPCS,
167 AAPCSVFP,
168 SysV
169};
170154
171/// This data structure is used by the Zig language code generation and155/// This data structure is used by the Zig language code generation and
172/// therefore must be kept in sync with the compiler implementation.156/// therefore must be kept in sync with the compiler implementation.
lib/std/compress/deflate.zig+2-4
...@@ -662,14 +662,12 @@ test "lengths overflow" {...@@ -662,14 +662,12 @@ test "lengths overflow" {
662 // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316)662 // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316)
663 // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6663 // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6
664 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11664 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11
665 const stream = [_]u8{665 const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 };
666 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110
667 };
668666
669 const reader = std.io.fixedBufferStream(&stream).reader();667 const reader = std.io.fixedBufferStream(&stream).reader();
670 var window: [0x8000]u8 = undefined;668 var window: [0x8000]u8 = undefined;
671 var inflate = inflateStream(reader, &window);669 var inflate = inflateStream(reader, &window);
672670
673 var buf: [1]u8 = undefined;671 var buf: [1]u8 = undefined;
674 std.testing.expectError(error.InvalidLength, inflate.read(&buf)); 672 std.testing.expectError(error.InvalidLength, inflate.read(&buf));
675}673}
lib/std/os/bits/linux/arm64.zig+1
...@@ -9,6 +9,7 @@...@@ -9,6 +9,7 @@
9const std = @import("../../../std.zig");9const std = @import("../../../std.zig");
10const linux = std.os.linux;10const linux = std.os.linux;
11const socklen_t = linux.socklen_t;11const socklen_t = linux.socklen_t;
12const sockaddr = linux.sockaddr;
12const iovec = linux.iovec;13const iovec = linux.iovec;
13const iovec_const = linux.iovec_const;14const iovec_const = linux.iovec_const;
14const uid_t = linux.uid_t;15const uid_t = linux.uid_t;
lib/std/x.zig+15-1
...@@ -1,8 +1,22 @@...@@ -1,8 +1,22 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std.zig");
8
1pub const os = struct {9pub const os = struct {
2 pub const Socket = @import("x/os/Socket.zig");10 pub const Socket = @import("x/os/Socket.zig");
3 pub usingnamespace @import("x/os/net.zig");11 pub usingnamespace @import("x/os/net.zig");
4};12};
513
6pub const net = struct {14pub const net = struct {
7 pub const TCP = @import("x/net/TCP.zig");15 pub const tcp = @import("x/net/tcp.zig");
8};16};
17
18test {
19 inline for (.{ os, net }) |module| {
20 std.testing.refAllDecls(module);
21 }
22}
lib/std/x/net/TCP.zig deleted-399
...@@ -1,399 +0,0 @@
1const std = @import("../../std.zig");
2
3const os = std.os;
4const fmt = std.fmt;
5const mem = std.mem;
6const testing = std.testing;
7
8const IPv4 = std.x.os.IPv4;
9const IPv6 = std.x.os.IPv6;
10const Socket = std.x.os.Socket;
11
12/// A generic TCP socket abstraction.
13const TCP = @This();
14
15/// A TCP client-address pair.
16pub const Connection = struct {
17 client: TCP.Client,
18 address: TCP.Address,
19
20 /// Enclose a TCP client and address into a client-address pair.
21 pub fn from(socket: Socket, address: TCP.Address) Connection {
22 return .{ .client = TCP.Client.from(socket), .address = address };
23 }
24
25 /// Closes the underlying client of the connection.
26 pub fn deinit(self: TCP.Connection) void {
27 self.client.deinit();
28 }
29};
30
31/// Possible domains that a TCP client/listener may operate over.
32pub const Domain = extern enum(u16) {
33 ip = os.AF_INET,
34 ipv6 = os.AF_INET6,
35};
36
37/// A TCP client.
38pub const Client = struct {
39 socket: Socket,
40
41 /// Opens a new client.
42 pub fn init(domain: TCP.Domain, flags: u32) !Client {
43 return Client{
44 .socket = try Socket.init(
45 @enumToInt(domain),
46 os.SOCK_STREAM | flags,
47 os.IPPROTO_TCP,
48 ),
49 };
50 }
51
52 /// Enclose a TCP client over an existing socket.
53 pub fn from(socket: Socket) Client {
54 return Client{ .socket = socket };
55 }
56
57 /// Closes the client.
58 pub fn deinit(self: Client) void {
59 self.socket.deinit();
60 }
61
62 /// Shutdown either the read side, write side, or all sides of the client's underlying socket.
63 pub fn shutdown(self: Client, how: os.ShutdownHow) !void {
64 return self.socket.shutdown(how);
65 }
66
67 /// Have the client attempt to the connect to an address.
68 pub fn connect(self: Client, address: TCP.Address) !void {
69 return self.socket.connect(TCP.Address, address);
70 }
71
72 /// Read data from the socket into the buffer provided. It returns the
73 /// number of bytes read into the buffer provided.
74 pub fn read(self: Client, buf: []u8) !usize {
75 return self.socket.read(buf);
76 }
77
78 /// Read data from the socket into the buffer provided with a set of flags
79 /// specified. It returns the number of bytes read into the buffer provided.
80 pub fn recv(self: Client, buf: []u8, flags: u32) !usize {
81 return self.socket.recv(buf, flags);
82 }
83
84 /// Write a buffer of data provided to the socket. It returns the number
85 /// of bytes that are written to the socket.
86 pub fn write(self: Client, buf: []const u8) !usize {
87 return self.socket.write(buf);
88 }
89
90 /// Writes multiple I/O vectors to the socket. It returns the number
91 /// of bytes that are written to the socket.
92 pub fn writev(self: Client, buffers: []const os.iovec_const) !usize {
93 return self.socket.writev(buffers);
94 }
95
96 /// Write a buffer of data provided to the socket with a set of flags specified.
97 /// It returns the number of bytes that are written to the socket.
98 pub fn send(self: Client, buf: []const u8, flags: u32) !usize {
99 return self.socket.send(buf, flags);
100 }
101
102 /// Writes multiple I/O vectors with a prepended message header to the socket
103 /// with a set of flags specified. It returns the number of bytes that are
104 /// written to the socket.
105 pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize {
106 return self.socket.sendmsg(msg, flags);
107 }
108
109 /// Query and return the latest cached error on the client's underlying socket.
110 pub fn getError(self: Client) !void {
111 return self.socket.getError();
112 }
113
114 /// Query the read buffer size of the client's underlying socket.
115 pub fn getReadBufferSize(self: Client) !u32 {
116 return self.socket.getReadBufferSize();
117 }
118
119 /// Query the write buffer size of the client's underlying socket.
120 pub fn getWriteBufferSize(self: Client) !u32 {
121 return self.socket.getWriteBufferSize();
122 }
123
124 /// Query the address that the client's socket is locally bounded to.
125 pub fn getLocalAddress(self: Client) !TCP.Address {
126 return self.socket.getLocalAddress(TCP.Address);
127 }
128
129 /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
130 /// the host does not support sockets disabling Nagle's algorithm.
131 pub fn setNoDelay(self: Client, enabled: bool) !void {
132 if (comptime @hasDecl(os, "TCP_NODELAY")) {
133 const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
134 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes);
135 }
136 return error.UnsupportedSocketOption;
137 }
138
139 /// Set the write buffer size of the socket.
140 pub fn setWriteBufferSize(self: Client, size: u32) !void {
141 return self.socket.setWriteBufferSize(size);
142 }
143
144 /// Set the read buffer size of the socket.
145 pub fn setReadBufferSize(self: Client, size: u32) !void {
146 return self.socket.setReadBufferSize(size);
147 }
148
149 /// Set a timeout on the socket that is to occur if no messages are successfully written
150 /// to its bound destination after a specified number of milliseconds. A subsequent write
151 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
152 pub fn setWriteTimeout(self: Client, milliseconds: usize) !void {
153 return self.socket.setWriteTimeout(milliseconds);
154 }
155
156 /// Set a timeout on the socket that is to occur if no messages are successfully read
157 /// from its bound destination after a specified number of milliseconds. A subsequent
158 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
159 /// exceeded.
160 pub fn setReadTimeout(self: Client, milliseconds: usize) !void {
161 return self.socket.setReadTimeout(milliseconds);
162 }
163};
164
165/// A TCP listener.
166pub const Listener = struct {
167 socket: Socket,
168
169 /// Opens a new listener.
170 pub fn init(domain: TCP.Domain, flags: u32) !Listener {
171 return Listener{
172 .socket = try Socket.init(
173 @enumToInt(domain),
174 os.SOCK_STREAM | flags,
175 os.IPPROTO_TCP,
176 ),
177 };
178 }
179
180 /// Closes the listener.
181 pub fn deinit(self: Listener) void {
182 self.socket.deinit();
183 }
184
185 /// Shuts down the underlying listener's socket. The next subsequent call, or
186 /// a current pending call to accept() after shutdown is called will return
187 /// an error.
188 pub fn shutdown(self: Listener) !void {
189 return self.socket.shutdown(.recv);
190 }
191
192 /// Binds the listener's socket to an address.
193 pub fn bind(self: Listener, address: TCP.Address) !void {
194 return self.socket.bind(TCP.Address, address);
195 }
196
197 /// Start listening for incoming connections.
198 pub fn listen(self: Listener, max_backlog_size: u31) !void {
199 return self.socket.listen(max_backlog_size);
200 }
201
202 /// Accept a pending incoming connection queued to the kernel backlog
203 /// of the listener's socket.
204 pub fn accept(self: Listener, flags: u32) !TCP.Connection {
205 return self.socket.accept(TCP.Connection, TCP.Address, flags);
206 }
207
208 /// Query and return the latest cached error on the listener's underlying socket.
209 pub fn getError(self: Client) !void {
210 return self.socket.getError();
211 }
212
213 /// Query the address that the listener's socket is locally bounded to.
214 pub fn getLocalAddress(self: Listener) !TCP.Address {
215 return self.socket.getLocalAddress(TCP.Address);
216 }
217
218 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
219 /// the host does not support sockets listening the same address.
220 pub fn setReuseAddress(self: Listener, enabled: bool) !void {
221 return self.socket.setReuseAddress(enabled);
222 }
223
224 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
225 /// the host does not supports sockets listening on the same port.
226 pub fn setReusePort(self: Listener, enabled: bool) !void {
227 return self.socket.setReusePort(enabled);
228 }
229
230 /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
231 /// support TCP Fast Open.
232 pub fn setFastOpen(self: Listener, enabled: bool) !void {
233 if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
234 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
235 }
236 return error.UnsupportedSocketOption;
237 }
238
239 /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
240 /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
241 pub fn setQuickACK(self: Listener, enabled: bool) !void {
242 if (comptime @hasDecl(os, "TCP_QUICKACK")) {
243 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
244 }
245 return error.UnsupportedSocketOption;
246 }
247
248 /// Set a timeout on the listener that is to occur if no new incoming connections come in
249 /// after a specified number of milliseconds. A subsequent accept call to the listener
250 /// will thereafter return `error.WouldBlock` should the timeout be exceeded.
251 pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void {
252 return self.socket.setReadTimeout(milliseconds);
253 }
254};
255
256/// A TCP socket address designated by a host IP and port. A TCP socket
257/// address comprises of 28 bytes. It may freely be used in place of
258/// `sockaddr` when working with socket syscalls.
259///
260/// It is not recommended to touch the fields of an `Address`, but to
261/// instead make use of its available accessor methods.
262pub const Address = extern struct {
263 family: u16,
264 port: u16,
265 host: extern union {
266 ipv4: extern struct {
267 address: IPv4,
268 },
269 ipv6: extern struct {
270 flow_info: u32 = 0,
271 address: IPv6,
272 },
273 },
274
275 /// Instantiate a new TCP address with a IPv4 host and port.
276 pub fn initIPv4(host: IPv4, port: u16) Address {
277 return Address{
278 .family = os.AF_INET,
279 .port = mem.nativeToBig(u16, port),
280 .host = .{
281 .ipv4 = .{
282 .address = host,
283 },
284 },
285 };
286 }
287
288 /// Instantiate a new TCP address with a IPv6 host and port.
289 pub fn initIPv6(host: IPv6, port: u16) Address {
290 return Address{
291 .family = os.AF_INET6,
292 .port = mem.nativeToBig(u16, port),
293 .host = .{
294 .ipv6 = .{
295 .address = host,
296 },
297 },
298 };
299 }
300
301 /// Extract the host of the address.
302 pub fn getHost(self: Address) union(enum) { v4: IPv4, v6: IPv6 } {
303 return switch (self.family) {
304 os.AF_INET => .{ .v4 = self.host.ipv4.address },
305 os.AF_INET6 => .{ .v6 = self.host.ipv6.address },
306 else => unreachable,
307 };
308 }
309
310 /// Extract the port of the address.
311 pub fn getPort(self: Address) u16 {
312 return mem.nativeToBig(u16, self.port);
313 }
314
315 /// Set the port of the address.
316 pub fn setPort(self: *Address, port: u16) void {
317 self.port = mem.nativeToBig(u16, port);
318 }
319
320 /// Implements the `std.fmt.format` API.
321 pub fn format(
322 self: Address,
323 comptime layout: []const u8,
324 opts: fmt.FormatOptions,
325 writer: anytype,
326 ) !void {
327 switch (self.getHost()) {
328 .v4 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
329 .v6 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
330 }
331 }
332};
333
334test {
335 testing.refAllDecls(@This());
336}
337
338test "tcp: create non-blocking pair" {
339 const a = try TCP.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
340 defer a.deinit();
341
342 try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
343 try a.listen(128);
344
345 const binded_address = try a.getLocalAddress();
346
347 const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
348 defer b.deinit();
349
350 testing.expectError(error.WouldBlock, b.connect(binded_address));
351 try b.getError();
352
353 const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
354 defer ab.deinit();
355}
356
357test "tcp/client: set read timeout of 1 millisecond on blocking client" {
358 const a = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
359 defer a.deinit();
360
361 try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
362 try a.listen(128);
363
364 const binded_address = try a.getLocalAddress();
365
366 const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC);
367 defer b.deinit();
368
369 try b.connect(binded_address);
370 try b.setReadTimeout(1);
371
372 const ab = try a.accept(os.SOCK_CLOEXEC);
373 defer ab.deinit();
374
375 var buf: [1]u8 = undefined;
376 testing.expectError(error.WouldBlock, b.read(&buf));
377}
378
379test "tcp/listener: bind to unspecified ipv4 address" {
380 const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
381 defer socket.deinit();
382
383 try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
384 try socket.listen(128);
385
386 const address = try socket.getLocalAddress();
387 testing.expect(address.getHost() == .v4);
388}
389
390test "tcp/listener: bind to unspecified ipv6 address" {
391 const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC);
392 defer socket.deinit();
393
394 try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0));
395 try socket.listen(128);
396
397 const address = try socket.getLocalAddress();
398 testing.expect(address.getHost() == .v6);
399}
lib/std/x/net/tcp.zig created+383
...@@ -0,0 +1,383 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("../../std.zig");
8
9const os = std.os;
10const fmt = std.fmt;
11const mem = std.mem;
12const testing = std.testing;
13
14const IPv4 = std.x.os.IPv4;
15const IPv6 = std.x.os.IPv6;
16const Socket = std.x.os.Socket;
17
18/// A generic TCP socket abstraction.
19const tcp = @This();
20
21/// A union of all eligible types of socket addresses over TCP.
22pub const Address = union(enum) {
23 ipv4: IPv4.Address,
24 ipv6: IPv6.Address,
25
26 /// Instantiate a new address with a IPv4 host and port.
27 pub fn initIPv4(host: IPv4, port: u16) Address {
28 return .{ .ipv4 = .{ .host = host, .port = port } };
29 }
30
31 /// Instantiate a new address with a IPv6 host and port.
32 pub fn initIPv6(host: IPv6, port: u16) Address {
33 return .{ .ipv6 = .{ .host = host, .port = port } };
34 }
35
36 /// Re-interpret a generic socket address into a TCP socket address.
37 pub fn from(address: Socket.Address) tcp.Address {
38 return switch (address) {
39 .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
40 .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
41 };
42 }
43
44 /// Re-interpret a TCP socket address into a generic socket address.
45 pub fn into(self: tcp.Address) Socket.Address {
46 return switch (self) {
47 .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
48 .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
49 };
50 }
51
52 /// Implements the `std.fmt.format` API.
53 pub fn format(
54 self: tcp.Address,
55 comptime layout: []const u8,
56 opts: fmt.FormatOptions,
57 writer: anytype,
58 ) !void {
59 switch (self) {
60 .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
61 .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
62 }
63 }
64};
65
66/// A TCP client-address pair.
67pub const Connection = struct {
68 client: tcp.Client,
69 address: tcp.Address,
70
71 /// Enclose a TCP client and address into a client-address pair.
72 pub fn from(conn: Socket.Connection) tcp.Connection {
73 return .{
74 .client = tcp.Client.from(conn.socket),
75 .address = tcp.Address.from(conn.address),
76 };
77 }
78
79 /// Unravel a TCP client-address pair into a socket-address pair.
80 pub fn into(self: tcp.Connection) Socket.Connection {
81 return .{
82 .socket = self.client.socket,
83 .address = self.address.into(),
84 };
85 }
86
87 /// Closes the underlying client of the connection.
88 pub fn deinit(self: tcp.Connection) void {
89 self.client.deinit();
90 }
91};
92
93/// Possible domains that a TCP client/listener may operate over.
94pub const Domain = extern enum(u16) {
95 ip = os.AF_INET,
96 ipv6 = os.AF_INET6,
97};
98
99/// A TCP client.
100pub const Client = struct {
101 socket: Socket,
102
103 /// Opens a new client.
104 pub fn init(domain: tcp.Domain, flags: u32) !Client {
105 return Client{
106 .socket = try Socket.init(
107 @enumToInt(domain),
108 os.SOCK_STREAM | flags,
109 os.IPPROTO_TCP,
110 ),
111 };
112 }
113
114 /// Enclose a TCP client over an existing socket.
115 pub fn from(socket: Socket) Client {
116 return Client{ .socket = socket };
117 }
118
119 /// Closes the client.
120 pub fn deinit(self: Client) void {
121 self.socket.deinit();
122 }
123
124 /// Shutdown either the read side, write side, or all sides of the client's underlying socket.
125 pub fn shutdown(self: Client, how: os.ShutdownHow) !void {
126 return self.socket.shutdown(how);
127 }
128
129 /// Have the client attempt to the connect to an address.
130 pub fn connect(self: Client, address: tcp.Address) !void {
131 return self.socket.connect(address.into());
132 }
133
134 /// Read data from the socket into the buffer provided. It returns the
135 /// number of bytes read into the buffer provided.
136 pub fn read(self: Client, buf: []u8) !usize {
137 return self.socket.read(buf);
138 }
139
140 /// Read data from the socket into the buffer provided with a set of flags
141 /// specified. It returns the number of bytes read into the buffer provided.
142 pub fn recv(self: Client, buf: []u8, flags: u32) !usize {
143 return self.socket.recv(buf, flags);
144 }
145
146 /// Write a buffer of data provided to the socket. It returns the number
147 /// of bytes that are written to the socket.
148 pub fn write(self: Client, buf: []const u8) !usize {
149 return self.socket.write(buf);
150 }
151
152 /// Writes multiple I/O vectors to the socket. It returns the number
153 /// of bytes that are written to the socket.
154 pub fn writev(self: Client, buffers: []const os.iovec_const) !usize {
155 return self.socket.writev(buffers);
156 }
157
158 /// Write a buffer of data provided to the socket with a set of flags specified.
159 /// It returns the number of bytes that are written to the socket.
160 pub fn send(self: Client, buf: []const u8, flags: u32) !usize {
161 return self.socket.send(buf, flags);
162 }
163
164 /// Writes multiple I/O vectors with a prepended message header to the socket
165 /// with a set of flags specified. It returns the number of bytes that are
166 /// written to the socket.
167 pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize {
168 return self.socket.sendmsg(msg, flags);
169 }
170
171 /// Query and return the latest cached error on the client's underlying socket.
172 pub fn getError(self: Client) !void {
173 return self.socket.getError();
174 }
175
176 /// Query the read buffer size of the client's underlying socket.
177 pub fn getReadBufferSize(self: Client) !u32 {
178 return self.socket.getReadBufferSize();
179 }
180
181 /// Query the write buffer size of the client's underlying socket.
182 pub fn getWriteBufferSize(self: Client) !u32 {
183 return self.socket.getWriteBufferSize();
184 }
185
186 /// Query the address that the client's socket is locally bounded to.
187 pub fn getLocalAddress(self: Client) !tcp.Address {
188 return tcp.Address.from(try self.socket.getLocalAddress());
189 }
190
191 /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
192 /// the host does not support sockets disabling Nagle's algorithm.
193 pub fn setNoDelay(self: Client, enabled: bool) !void {
194 if (comptime @hasDecl(os, "TCP_NODELAY")) {
195 const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
196 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes);
197 }
198 return error.UnsupportedSocketOption;
199 }
200
201 /// Set the write buffer size of the socket.
202 pub fn setWriteBufferSize(self: Client, size: u32) !void {
203 return self.socket.setWriteBufferSize(size);
204 }
205
206 /// Set the read buffer size of the socket.
207 pub fn setReadBufferSize(self: Client, size: u32) !void {
208 return self.socket.setReadBufferSize(size);
209 }
210
211 /// Set a timeout on the socket that is to occur if no messages are successfully written
212 /// to its bound destination after a specified number of milliseconds. A subsequent write
213 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
214 pub fn setWriteTimeout(self: Client, milliseconds: usize) !void {
215 return self.socket.setWriteTimeout(milliseconds);
216 }
217
218 /// Set a timeout on the socket that is to occur if no messages are successfully read
219 /// from its bound destination after a specified number of milliseconds. A subsequent
220 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
221 /// exceeded.
222 pub fn setReadTimeout(self: Client, milliseconds: usize) !void {
223 return self.socket.setReadTimeout(milliseconds);
224 }
225};
226
227/// A TCP listener.
228pub const Listener = struct {
229 socket: Socket,
230
231 /// Opens a new listener.
232 pub fn init(domain: tcp.Domain, flags: u32) !Listener {
233 return Listener{
234 .socket = try Socket.init(
235 @enumToInt(domain),
236 os.SOCK_STREAM | flags,
237 os.IPPROTO_TCP,
238 ),
239 };
240 }
241
242 /// Closes the listener.
243 pub fn deinit(self: Listener) void {
244 self.socket.deinit();
245 }
246
247 /// Shuts down the underlying listener's socket. The next subsequent call, or
248 /// a current pending call to accept() after shutdown is called will return
249 /// an error.
250 pub fn shutdown(self: Listener) !void {
251 return self.socket.shutdown(.recv);
252 }
253
254 /// Binds the listener's socket to an address.
255 pub fn bind(self: Listener, address: tcp.Address) !void {
256 return self.socket.bind(address.into());
257 }
258
259 /// Start listening for incoming connections.
260 pub fn listen(self: Listener, max_backlog_size: u31) !void {
261 return self.socket.listen(max_backlog_size);
262 }
263
264 /// Accept a pending incoming connection queued to the kernel backlog
265 /// of the listener's socket.
266 pub fn accept(self: Listener, flags: u32) !tcp.Connection {
267 return tcp.Connection.from(try self.socket.accept(flags));
268 }
269
270 /// Query and return the latest cached error on the listener's underlying socket.
271 pub fn getError(self: Client) !void {
272 return self.socket.getError();
273 }
274
275 /// Query the address that the listener's socket is locally bounded to.
276 pub fn getLocalAddress(self: Listener) !tcp.Address {
277 return tcp.Address.from(try self.socket.getLocalAddress());
278 }
279
280 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
281 /// the host does not support sockets listening the same address.
282 pub fn setReuseAddress(self: Listener, enabled: bool) !void {
283 return self.socket.setReuseAddress(enabled);
284 }
285
286 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
287 /// the host does not supports sockets listening on the same port.
288 pub fn setReusePort(self: Listener, enabled: bool) !void {
289 return self.socket.setReusePort(enabled);
290 }
291
292 /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
293 /// support TCP Fast Open.
294 pub fn setFastOpen(self: Listener, enabled: bool) !void {
295 if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
296 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
297 }
298 return error.UnsupportedSocketOption;
299 }
300
301 /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
302 /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
303 pub fn setQuickACK(self: Listener, enabled: bool) !void {
304 if (comptime @hasDecl(os, "TCP_QUICKACK")) {
305 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
306 }
307 return error.UnsupportedSocketOption;
308 }
309
310 /// Set a timeout on the listener that is to occur if no new incoming connections come in
311 /// after a specified number of milliseconds. A subsequent accept call to the listener
312 /// will thereafter return `error.WouldBlock` should the timeout be exceeded.
313 pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void {
314 return self.socket.setReadTimeout(milliseconds);
315 }
316};
317
318test {
319 testing.refAllDecls(@This());
320}
321
322test "tcp: create non-blocking pair" {
323 const listener = try tcp.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
324 defer listener.deinit();
325
326 try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
327 try listener.listen(128);
328
329 const binded_address = try listener.getLocalAddress();
330
331 const client = try tcp.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
332 defer client.deinit();
333
334 testing.expectError(error.WouldBlock, client.connect(binded_address));
335 try client.getError();
336
337 const conn = try listener.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
338 defer conn.deinit();
339}
340
341test "tcp/client: set read timeout of 1 millisecond on blocking client" {
342 const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
343 defer listener.deinit();
344
345 try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
346 try listener.listen(128);
347
348 const binded_address = try listener.getLocalAddress();
349
350 const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
351 defer client.deinit();
352
353 try client.connect(binded_address);
354 try client.setReadTimeout(1);
355
356 const conn = try listener.accept(os.SOCK_CLOEXEC);
357 defer conn.deinit();
358
359 var buf: [1]u8 = undefined;
360 testing.expectError(error.WouldBlock, client.read(&buf));
361}
362
363test "tcp/listener: bind to unspecified ipv4 address" {
364 const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
365 defer listener.deinit();
366
367 try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
368 try listener.listen(128);
369
370 const address = try listener.getLocalAddress();
371 testing.expect(address == .ipv4);
372}
373
374test "tcp/listener: bind to unspecified ipv6 address" {
375 const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC);
376 defer listener.deinit();
377
378 try listener.bind(tcp.Address.initIPv6(IPv6.unspecified, 0));
379 try listener.listen(128);
380
381 const address = try listener.getLocalAddress();
382 testing.expect(address == .ipv6);
383}
lib/std/x/os/Socket.zig+114-14
...@@ -1,4 +1,11 @@...@@ -1,4 +1,11 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
1const std = @import("../../std.zig");7const std = @import("../../std.zig");
8const net = @import("net.zig");
29
3const os = std.os;10const os = std.os;
4const mem = std.mem;11const mem = std.mem;
...@@ -7,6 +14,98 @@ const time = std.time;...@@ -7,6 +14,98 @@ const time = std.time;
7/// A generic socket abstraction.14/// A generic socket abstraction.
8const Socket = @This();15const Socket = @This();
916
17/// A socket-address pair.
18pub const Connection = struct {
19 socket: Socket,
20 address: Socket.Address,
21
22 /// Enclose a socket and address into a socket-address pair.
23 pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
24 return .{ .socket = socket, .address = address };
25 }
26};
27
28/// A generic socket address abstraction. It is safe to directly access and modify
29/// the fields of a `Socket.Address`.
30pub const Address = union(enum) {
31 ipv4: net.IPv4.Address,
32 ipv6: net.IPv6.Address,
33
34 /// Instantiate a new address with a IPv4 host and port.
35 pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
36 return .{ .ipv4 = .{ .host = host, .port = port } };
37 }
38
39 /// Instantiate a new address with a IPv6 host and port.
40 pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
41 return .{ .ipv6 = .{ .host = host, .port = port } };
42 }
43
44 /// Parses a `sockaddr` into a generic socket address.
45 pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
46 switch (address.family) {
47 os.AF_INET => {
48 const info = @ptrCast(*const os.sockaddr_in, address);
49 const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
50 const port = mem.bigToNative(u16, info.port);
51 return Socket.Address.initIPv4(host, port);
52 },
53 os.AF_INET6 => {
54 const info = @ptrCast(*const os.sockaddr_in6, address);
55 const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
56 const port = mem.bigToNative(u16, info.port);
57 return Socket.Address.initIPv6(host, port);
58 },
59 else => unreachable,
60 }
61 }
62
63 /// Encodes a generic socket address into an extern union that may be reliably
64 /// casted into a `sockaddr` which may be passed into socket syscalls.
65 pub fn toNative(self: Socket.Address) extern union {
66 ipv4: os.sockaddr_in,
67 ipv6: os.sockaddr_in6,
68 } {
69 return switch (self) {
70 .ipv4 => |address| .{
71 .ipv4 = .{
72 .addr = @bitCast(u32, address.host.octets),
73 .port = mem.nativeToBig(u16, address.port),
74 },
75 },
76 .ipv6 => |address| .{
77 .ipv6 = .{
78 .addr = address.host.octets,
79 .port = mem.nativeToBig(u16, address.port),
80 .scope_id = address.host.scope_id,
81 .flowinfo = 0,
82 },
83 },
84 };
85 }
86
87 /// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
88 pub fn getNativeSize(self: Socket.Address) u32 {
89 return switch (self) {
90 .ipv4 => @sizeOf(os.sockaddr_in),
91 .ipv6 => @sizeOf(os.sockaddr_in6),
92 };
93 }
94
95 /// Implements the `std.fmt.format` API.
96 pub fn format(
97 self: Socket.Address,
98 comptime layout: []const u8,
99 opts: fmt.FormatOptions,
100 writer: anytype,
101 ) !void {
102 switch (self) {
103 .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
104 .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
105 }
106 }
107};
108
10/// The underlying handle of a socket.109/// The underlying handle of a socket.
11fd: os.socket_t,110fd: os.socket_t,
12111
...@@ -31,8 +130,8 @@ pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {...@@ -31,8 +130,8 @@ pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
31}130}
32131
33/// Binds the socket to an address.132/// Binds the socket to an address.
34pub fn bind(self: Socket, comptime Address: type, address: Address) !void {133pub fn bind(self: Socket, address: Socket.Address) !void {
35 return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));134 return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
36}135}
37136
38/// Start listening for incoming connections on the socket.137/// Start listening for incoming connections on the socket.
...@@ -41,19 +140,20 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {...@@ -41,19 +140,20 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {
41}140}
42141
43/// Have the socket attempt to the connect to an address.142/// Have the socket attempt to the connect to an address.
44pub fn connect(self: Socket, comptime Address: type, address: Address) !void {143pub fn connect(self: Socket, address: Socket.Address) !void {
45 return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));144 return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
46}145}
47146
48/// Accept a pending incoming connection queued to the kernel backlog147/// Accept a pending incoming connection queued to the kernel backlog
49/// of the socket.148/// of the socket.
50pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection {149pub fn accept(self: Socket, flags: u32) !Socket.Connection {
51 var address: Address = undefined;150 var address: os.sockaddr = undefined;
52 var address_len: u32 = @sizeOf(Address);151 var address_len: u32 = @sizeOf(os.sockaddr);
53152
54 const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags);153 const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) };
154 const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
55155
56 return Connection.from(.{ .fd = fd }, address);156 return Socket.Connection.from(socket, socket_address);
57}157}
58158
59/// Read data from the socket into the buffer provided. It returns the159/// Read data from the socket into the buffer provided. It returns the
...@@ -94,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {...@@ -94,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
94}194}
95195
96/// Query the address that the socket is locally bounded to.196/// Query the address that the socket is locally bounded to.
97pub fn getLocalAddress(self: Socket, comptime Address: type) !Address {197pub fn getLocalAddress(self: Socket) !Socket.Address {
98 var address: Address = undefined;198 var address: os.sockaddr = undefined;
99 var address_len: u32 = @sizeOf(Address);199 var address_len: u32 = @sizeOf(os.sockaddr);
100 try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);200 try os.getsockname(self.fd, &address, &address_len);
101 return address;201 return Socket.Address.fromNative(@alignCast(4, &address));
102}202}
103203
104/// Query and return the latest cached error on the socket.204/// Query and return the latest cached error on the socket.
lib/std/x/os/net.zig+18
...@@ -1,3 +1,9 @@...@@ -1,3 +1,9 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
1const std = @import("../../std.zig");7const std = @import("../../std.zig");
28
3const os = std.os;9const os = std.os;
...@@ -27,6 +33,12 @@ pub fn resolveScopeID(name: []const u8) !u32 {...@@ -27,6 +33,12 @@ pub fn resolveScopeID(name: []const u8) !u32 {
2733
28/// An IPv4 address comprised of 4 bytes.34/// An IPv4 address comprised of 4 bytes.
29pub const IPv4 = extern struct {35pub const IPv4 = extern struct {
36 /// A IPv4 host-port pair.
37 pub const Address = extern struct {
38 host: IPv4,
39 port: u16,
40 };
41
30 /// Octets of a IPv4 address designating the local host.42 /// Octets of a IPv4 address designating the local host.
31 pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };43 pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
3244
...@@ -200,6 +212,12 @@ pub const IPv4 = extern struct {...@@ -200,6 +212,12 @@ pub const IPv4 = extern struct {
200/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes212/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
201/// for a scope ID; cumulatively summing to 20 bytes in total.213/// for a scope ID; cumulatively summing to 20 bytes in total.
202pub const IPv6 = extern struct {214pub const IPv6 = extern struct {
215 /// A IPv6 host-port pair.
216 pub const Address = extern struct {
217 host: IPv6,
218 port: u16,
219 };
220
203 /// Octets of a IPv6 address designating the local host.221 /// Octets of a IPv6 address designating the local host.
204 pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};222 pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};
205223