aboutsummaryrefslogtreecommitdiff
path: root/lib/std/x/os/socket.zig
blob: ed354681ed38eece75eabe68bc16e565a332d5eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
const std = @import("../../std.zig");
const builtin = @import("builtin");
const net = @import("net.zig");

const os = std.os;
const fmt = std.fmt;
const mem = std.mem;
const time = std.time;
const meta = std.meta;
const native_os = builtin.os;
const native_endian = builtin.cpu.arch.endian();

const Buffer = std.x.os.Buffer;

const assert = std.debug.assert;

/// A generic, cross-platform socket abstraction.
pub const Socket = struct {
    /// A socket-address pair.
    pub const Connection = struct {
        socket: Socket,
        address: Socket.Address,

        /// Enclose a socket and address into a socket-address pair.
        pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
            return .{ .socket = socket, .address = address };
        }
    };

    /// A generic socket address abstraction. It is safe to directly access and modify
    /// the fields of a `Socket.Address`.
    pub const Address = union(enum) {
        pub const Native = struct {
            pub const requires_prepended_length = native_os.getVersionRange() == .semver;
            pub const Length = if (requires_prepended_length) u8 else [0]u8;

            pub const Family = if (requires_prepended_length) u8 else c_ushort;

            /// POSIX `sockaddr.storage`. The expected size and alignment is specified in IETF RFC 2553.
            pub const Storage = extern struct {
                pub const expected_size = os.sockaddr.SS_MAXSIZE;
                pub const expected_alignment = 8;

                pub const padding_size = expected_size -
                    mem.alignForward(@sizeOf(Address.Native.Length), expected_alignment) -
                    mem.alignForward(@sizeOf(Address.Native.Family), expected_alignment);

                len: Address.Native.Length align(expected_alignment) = undefined,
                family: Address.Native.Family align(expected_alignment) = undefined,
                padding: [padding_size]u8 align(expected_alignment) = undefined,

                comptime {
                    assert(@sizeOf(Storage) == Storage.expected_size);
                    assert(@alignOf(Storage) == Storage.expected_alignment);
                }
            };
        };

        ipv4: net.IPv4.Address,
        ipv6: net.IPv6.Address,

        /// Instantiate a new address with a IPv4 host and port.
        pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
            return .{ .ipv4 = .{ .host = host, .port = port } };
        }

        /// Instantiate a new address with a IPv6 host and port.
        pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
            return .{ .ipv6 = .{ .host = host, .port = port } };
        }

        /// Parses a `sockaddr` into a generic socket address.
        pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
            switch (address.family) {
                os.AF.INET => {
                    const info = @ptrCast(*const os.sockaddr.in, address);
                    const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
                    const port = mem.bigToNative(u16, info.port);
                    return Socket.Address.initIPv4(host, port);
                },
                os.AF.INET6 => {
                    const info = @ptrCast(*const os.sockaddr.in6, address);
                    const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
                    const port = mem.bigToNative(u16, info.port);
                    return Socket.Address.initIPv6(host, port);
                },
                else => unreachable,
            }
        }

        /// Encodes a generic socket address into an extern union that may be reliably
        /// casted into a `sockaddr` which may be passed into socket syscalls.
        pub fn toNative(self: Socket.Address) extern union {
            ipv4: os.sockaddr.in,
            ipv6: os.sockaddr.in6,
        } {
            return switch (self) {
                .ipv4 => |address| .{
                    .ipv4 = .{
                        .addr = @bitCast(u32, address.host.octets),
                        .port = mem.nativeToBig(u16, address.port),
                    },
                },
                .ipv6 => |address| .{
                    .ipv6 = .{
                        .addr = address.host.octets,
                        .port = mem.nativeToBig(u16, address.port),
                        .scope_id = address.host.scope_id,
                        .flowinfo = 0,
                    },
                },
            };
        }

        /// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
        pub fn getNativeSize(self: Socket.Address) u32 {
            return switch (self) {
                .ipv4 => @sizeOf(os.sockaddr.in),
                .ipv6 => @sizeOf(os.sockaddr.in6),
            };
        }

        /// Implements the `std.fmt.format` API.
        pub fn format(
            self: Socket.Address,
            comptime layout: []const u8,
            opts: fmt.FormatOptions,
            writer: anytype,
        ) !void {
            _ = opts;
            _ = layout;
            switch (self) {
                .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
                .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
            }
        }
    };

    /// POSIX `msghdr`. Denotes a destination address, set of buffers, control data, and flags. Ported
    /// directly from musl.
    pub const Message = if (native_os.isAtLeast(.windows, .vista) != null and native_os.isAtLeast(.windows, .vista).?)
        extern struct {
            name: usize = @ptrToInt(@as(?[*]u8, null)),
            name_len: c_int = 0,

            buffers: usize = undefined,
            buffers_len: c_ulong = undefined,

            control: Buffer = .{
                .ptr = @ptrToInt(@as(?[*]u8, null)),
                .len = 0,
            },
            flags: c_ulong = 0,

            pub usingnamespace MessageMixin(Message);
        }
    else if (native_os.tag == .windows)
        extern struct {
            name: usize = @ptrToInt(@as(?[*]u8, null)),
            name_len: c_int = 0,

            buffers: usize = undefined,
            buffers_len: u32 = undefined,

            control: Buffer = .{
                .ptr = @ptrToInt(@as(?[*]u8, null)),
                .len = 0,
            },
            flags: u32 = 0,

            pub usingnamespace MessageMixin(Message);
        }
    else if (@sizeOf(usize) > 4 and native_endian == .Big)
        extern struct {
            name: usize = @ptrToInt(@as(?[*]u8, null)),
            name_len: c_uint = 0,

            buffers: usize = undefined,
            _pad_1: c_int = 0,
            buffers_len: c_int = undefined,

            control: usize = @ptrToInt(@as(?[*]u8, null)),
            _pad_2: c_int = 0,
            control_len: c_uint = 0,

            flags: c_int = 0,

            pub usingnamespace MessageMixin(Message);
        }
    else if (@sizeOf(usize) > 4 and native_endian == .Little)
        extern struct {
            name: usize = @ptrToInt(@as(?[*]u8, null)),
            name_len: c_uint = 0,

            buffers: usize = undefined,
            buffers_len: c_int = undefined,
            _pad_1: c_int = 0,

            control: usize = @ptrToInt(@as(?[*]u8, null)),
            control_len: c_uint = 0,
            _pad_2: c_int = 0,

            flags: c_int = 0,

            pub usingnamespace MessageMixin(Message);
        }
    else
        extern struct {
            name: usize = @ptrToInt(@as(?[*]u8, null)),
            name_len: c_uint = 0,

            buffers: usize = undefined,
            buffers_len: c_int = undefined,

            control: usize = @ptrToInt(@as(?[*]u8, null)),
            control_len: c_uint = 0,

            flags: c_int = 0,

            pub usingnamespace MessageMixin(Message);
        };

    fn MessageMixin(comptime Self: type) type {
        return struct {
            pub fn fromBuffers(buffers: []const Buffer) Self {
                var self: Self = .{};
                self.setBuffers(buffers);
                return self;
            }

            pub fn setName(self: *Self, name: []const u8) void {
                self.name = @ptrToInt(name.ptr);
                self.name_len = @intCast(meta.fieldInfo(Self, .name_len).field_type, name.len);
            }

            pub fn setBuffers(self: *Self, buffers: []const Buffer) void {
                self.buffers = @ptrToInt(buffers.ptr);
                self.buffers_len = @intCast(meta.fieldInfo(Self, .buffers_len).field_type, buffers.len);
            }

            pub fn setControl(self: *Self, control: []const u8) void {
                if (native_os.tag == .windows) {
                    self.control = Buffer.from(control);
                } else {
                    self.control = @ptrToInt(control.ptr);
                    self.control_len = @intCast(meta.fieldInfo(Self, .control_len).field_type, control.len);
                }
            }

            pub fn setFlags(self: *Self, flags: u32) void {
                self.flags = @intCast(meta.fieldInfo(Self, .flags).field_type, flags);
            }

            pub fn getName(self: Self) []const u8 {
                return @intToPtr([*]const u8, self.name)[0..@intCast(usize, self.name_len)];
            }

            pub fn getBuffers(self: Self) []const Buffer {
                return @intToPtr([*]const Buffer, self.buffers)[0..@intCast(usize, self.buffers_len)];
            }

            pub fn getControl(self: Self) []const u8 {
                if (native_os.tag == .windows) {
                    return self.control.into();
                } else {
                    return @intToPtr([*]const u8, self.control)[0..@intCast(usize, self.control_len)];
                }
            }

            pub fn getFlags(self: Self) u32 {
                return @intCast(u32, self.flags);
            }
        };
    }

    /// POSIX `linger`, denoting the linger settings of a socket.
    ///
    /// Microsoft's documentation and glibc denote the fields to be unsigned
    /// short's on Windows, whereas glibc and musl denote the fields to be
    /// int's on every other platform.
    pub const Linger = extern struct {
        pub const Field = switch (native_os.tag) {
            .windows => c_ushort,
            else => c_int,
        };

        enabled: Field,
        timeout_seconds: Field,

        pub fn init(timeout_seconds: ?u16) Socket.Linger {
            return .{
                .enabled = @intCast(Socket.Linger.Field, @boolToInt(timeout_seconds != null)),
                .timeout_seconds = if (timeout_seconds) |seconds| @intCast(Socket.Linger.Field, seconds) else 0,
            };
        }
    };

    /// Possible set of flags to initialize a socket with.
    pub const InitFlags = enum {
        // Initialize a socket to be non-blocking.
        nonblocking,

        // Have a socket close itself on exec syscalls.
        close_on_exec,
    };

    /// The underlying handle of a socket.
    fd: os.socket_t,

    /// Enclose a socket abstraction over an existing socket file descriptor.
    pub fn from(fd: os.socket_t) Socket {
        return Socket{ .fd = fd };
    }

    /// Mix in socket syscalls depending on the platform we are compiling against.
    pub usingnamespace switch (native_os.tag) {
        .windows => @import("socket_windows.zig"),
        else => @import("socket_posix.zig"),
    }.Mixin(Socket);
};