From ff7d55a8e1ab8fd031378d9994f4aaf8394e7255 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 5 Apr 2025 15:53:11 +0200 Subject: [PATCH] net: rework errors to be cross-platform --- core/net/common.odin | 14 +- core/net/errors.odin | 275 ++++++++++++++ core/net/errors_darwin.odin | 364 ++++++++++-------- core/net/errors_freebsd.odin | 399 +++++++++++--------- core/net/errors_linux.odin | 380 +++++++++++-------- core/net/errors_others.odin | 20 + core/net/errors_windows.odin | 428 +++++++++++----------- core/net/interface.odin | 2 +- core/net/interface_darwin.odin | 4 +- core/net/interface_freebsd.odin | 4 +- core/net/interface_linux.odin | 4 +- core/net/interface_windows.odin | 13 +- core/net/socket.odin | 57 ++- core/net/socket_darwin.odin | 83 ++--- core/net/socket_freebsd.odin | 91 ++--- core/net/socket_linux.odin | 85 ++--- core/net/socket_windows.odin | 125 +++++-- core/sys/windows/ws2_32.odin | 1 + tests/core/net/test_core_net_freebsd.odin | 23 +- 19 files changed, 1458 insertions(+), 914 deletions(-) create mode 100644 core/net/errors.odin create mode 100644 core/net/errors_others.odin diff --git a/core/net/common.odin b/core/net/common.odin index 12add8225..b38291a2c 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true) Maybe :: runtime.Maybe Network_Error :: union #shared_nil { - General_Error, - Platform_Error, Create_Socket_Error, Dial_Error, Listen_Error, @@ -65,6 +63,7 @@ Network_Error :: union #shared_nil { TCP_Recv_Error, UDP_Recv_Error, Shutdown_Error, + Interfaces_Error, Socket_Option_Error, Set_Blocking_Error, Parse_Endpoint_Error, @@ -74,14 +73,13 @@ Network_Error :: union #shared_nil { #assert(size_of(Network_Error) == 8) -General_Error :: enum u32 { - None = 0, - Unable_To_Enumerate_Network_Interfaces = 1, +Interfaces_Error :: enum u32 { + None, + Unable_To_Enumerate_Network_Interfaces, + Allocation_Failure, + Unknown, } -// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. -Platform_Error :: enum u32 {} - Parse_Endpoint_Error :: enum u32 { None = 0, Bad_Port = 1, diff --git a/core/net/errors.odin b/core/net/errors.odin new file mode 100644 index 000000000..53c936a66 --- /dev/null +++ b/core/net/errors.odin @@ -0,0 +1,275 @@ +package net + +/* +Retrieve a platform specific error code, for when the categorized cross-platform errors are not enough. + +Platforms specific returns: +- Darwin: `posix.Errno` (`core:sys/posix`) +- Linux: `linux.Errno` (`core:sys/linux`) +- FreeBSD: `freebsd.Errno` (`core:sys/freebsd`) +- Windows: `windows.System_Error` (`core:sys/windows`) +*/ +@(require_results) +last_platform_error :: proc() -> i32 { + return _last_platform_error() +} + +/* +Retrieve a stringified version of the last platform error. +*/ +@(require_results) +last_platform_error_string :: proc() -> string { + return _last_platform_error_string() +} + +set_last_platform_error :: proc(err: i32) { + _set_last_platform_error(err) +} + +Create_Socket_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid/unsupported family or protocol. + Invalid_Argument, + // The user has no permission to create a socket of this type and/or protocol. + Insufficient_Permissions, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Dial_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid endpoint and/or options. + Invalid_Argument, + // An attempt was made to connect to a broadcast socket on a socket that doesn't support it. + Broadcast_Not_Supported, + // The socket is already connected. + Already_Connected, + // The socket is already in the progress of making a connection. + Already_Connecting, + // The address is already in use. + Address_In_Use, + // Could not reach the remote host. + Host_Unreachable, + // The remote host refused the connection or isn't listening. + Refused, + // The connection was reset by the remote host. + Reset, + // Timed out before making a connection. + Timeout, + // Non-blocking socket that would need to block waiting to connect. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + // Endpoint given without a port, which is required. + Port_Required, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Bind_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or endpoint, or invalid combination of the two. + Invalid_Argument, + // The socket is already bound to an address. + Already_Bound, + // The address is protected and the current user has insufficient permissions to access it. + Insufficient_Permissions_For_Address, + // The address is already in use. + Address_In_Use, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Listen_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // The socket or backlog is invalid. + Invalid_Argument, + // The socket is valid, but does not support listening. + Unsupported_Socket, + // The socket is already connected. + Already_Connected, + // The address is already in use. + Address_In_Use, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Accept_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket, or options. + Invalid_Argument, + // The given socket does not support accepting connections. + Unsupported_Socket, + // accept called on a socket which is not listening. + Not_Listening, + // A connection arrived but was closed while in the listen queue. + Aborted, + // Timed out before being able to accept a connection. + Timeout, + // Non-blocking socket that would need to block waiting for a connection. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +TCP_Recv_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // The socket is not connected. + Not_Connected, + // Connection was closed/broken/shutdown while receiving data. + Connection_Closed, + // Timed out before being able to receive any data. + Timeout, + // Non-blocking socket that would need to block waiting on data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +UDP_Recv_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // "Connection" was refused by remote, or closed/broken/shutdown while receiving data. + Connection_Refused, + // Timed out before being able to receive any data. + Timeout, + // Non-blocking socket that would need to block waiting on data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + // Linux and UDP only: indicates the buffer was too small to receive all data, and the excess is truncated and discarded. + Excess_Truncated, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +TCP_Send_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // Connection was closed/broken/shutdown while receiving data. + Connection_Closed, + // The socket is not connected. + Not_Connected, + // Could not reach the remote host. + Host_Unreachable, + // Timed out before being able to send any data. + Timeout, + // Non-blocking socket that would need to block waiting on the remote to be able to receive the data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +UDP_Send_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // Could not reach the remote host. + Host_Unreachable, + // "Connection" was refused by remote, or closed/broken/shutdown while sending data. + Connection_Refused, + // Timed out before being able to send any data. + Timeout, + // Non-blocking socket that would need to block waiting on the remote to be able to receive the data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Shutdown_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Socket is invalid or not connected, or the manner given is invalid. + Invalid_Argument, + // Connection was closed/aborted/shutdown. + Connection_Closed, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Socket_Option_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Socket is invalid, not connected, or the connection has been closed/reset/shutdown. + Invalid_Socket, + // Unknown or unsupported option for the socket. + Invalid_Option, + // Invalid level or value. + Invalid_Value, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Set_Blocking_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Socket is invalid. + Invalid_Argument, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index ccf1e0f7f..7aaa220e9 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -20,192 +20,232 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import "core:sys/posix" -@(private) -ESHUTDOWN :: 58 - -Create_Socket_Error :: enum c.int { - None = 0, - Family_Not_Supported_For_This_Socket = c.int(posix.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(posix.EMFILE), - No_Buffer_Space_Available = c.int(posix.ENOBUFS), - No_Memory_Available = c.int(posix.ENOMEM), - Protocol_Unsupported_By_System = c.int(posix.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(posix.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(posix.EPROTONOSUPPORT), +_last_platform_error :: proc() -> i32 { + return i32(posix.errno()) } -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, // Attempted to dial an endpointing without a port being set. - - Address_In_Use = c.int(posix.EADDRINUSE), - In_Progress = c.int(posix.EINPROGRESS), - Cannot_Use_Any_Address = c.int(posix.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(posix.EAFNOSUPPORT), - Refused = c.int(posix.ECONNREFUSED), - Is_Listening_Socket = c.int(posix.EACCES), - Already_Connected = c.int(posix.EISCONN), - Network_Unreachable = c.int(posix.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(posix.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(posix.ENOBUFS), - Not_Socket = c.int(posix.ENOTSOCK), - Timeout = c.int(posix.ETIMEDOUT), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(posix.EWOULDBLOCK), +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(posix.errno()) + return description } -Bind_Error :: enum c.int { - None = 0, - Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access. - - Address_In_Use = c.int(posix.EADDRINUSE), // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), // The address is not a local address on this machine. - Broadcast_Disabled = c.int(posix.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = c.int(posix.EFAULT), // The address family of the address does not match that of the socket. - Already_Bound = c.int(posix.EINVAL), // The socket is already bound to an address. - No_Ports_Available = c.int(posix.ENOBUFS), // There are not enough ephemeral ports available. +_set_last_platform_error :: proc(err: i32) { + posix.errno(posix.Errno(err)) } -Listen_Error :: enum c.int { - None = 0, - Address_In_Use = c.int(posix.EADDRINUSE), - Already_Connected = c.int(posix.EISCONN), - No_Socket_Descriptors_Available = c.int(posix.EMFILE), - No_Buffer_Space_Available = c.int(posix.ENOBUFS), - Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), - Not_Socket = c.int(posix.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(posix.EOPNOTSUPP), +_create_socket_error :: proc() -> Create_Socket_Error { + #partial switch posix.errno() { + case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EPROTOTYPE: + return .Invalid_Argument + case .EACCES: + return .Insufficient_Permissions + case: + return .Unknown + } } -Accept_Error :: enum c.int { - None = 0, - // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. - Reset = c.int(posix.ECONNRESET), - Not_Listening = c.int(posix.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(posix.EMFILE), - No_Buffer_Space_Available = c.int(posix.ENOBUFS), - Not_Socket = c.int(posix.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(posix.EOPNOTSUPP), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(posix.EWOULDBLOCK), +_dial_error :: proc() -> Dial_Error { + #partial switch posix.errno() { + case .ENOBUFS: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL: + return .Invalid_Argument + case .EISCONN: + return .Already_Connected + case .EALREADY: + return .Already_Connecting + case .EADDRINUSE: + return .Address_In_Use + case .ENETDOWN: + return .Network_Unreachable + case .EHOSTUNREACH: + return .Host_Unreachable + case .ECONNREFUSED: + return .Refused + case .ECONNRESET: + return .Reset + case .ETIMEDOUT: + return .Timeout + case .EINPROGRESS: + return .Would_Block + case .EINTR: + return .Interrupted + case .EACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } } -TCP_Recv_Error :: enum c.int { - None = 0, - Shutdown = ESHUTDOWN, - Not_Connected = c.int(posix.ENOTCONN), - - // TODO(tetra): Is this error actually possible here? - Connection_Broken = c.int(posix.ENETRESET), - Not_Socket = c.int(posix.ENOTSOCK), - Aborted = c.int(posix.ECONNABORTED), - - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = c.int(posix.ECONNRESET), - Offline = c.int(posix.ENETDOWN), - Host_Unreachable = c.int(posix.EHOSTUNREACH), - Interrupted = c.int(posix.EINTR), - - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(posix.EWOULDBLOCK), +_bind_error :: proc() -> Bind_Error { + #partial switch posix.errno() { + case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP: + return .Invalid_Argument + case .EINVAL: + return .Already_Bound + case .EACCES: + return .Insufficient_Permissions_For_Address + case .EADDRINUSE: + return .Address_In_Use + case: + return .Unknown + } } -UDP_Recv_Error :: enum c.int { - None = 0, - Buffer_Too_Small = c.int(posix.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. - Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(posix.EWOULDBLOCK), - Socket_Not_Bound = c.int(posix.EINVAL), // The socket must be bound for this operation, but isn't. +_listen_error :: proc() -> Listen_Error { + #partial switch posix.errno() { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .EDESTADDRREQ, .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Already_Connected + case: + return .Unknown + } } -TCP_Send_Error :: enum c.int { - None = 0, - - Aborted = c.int(posix.ECONNABORTED), - Connection_Closed = c.int(posix.ECONNRESET), - Not_Connected = c.int(posix.ENOTCONN), - Shutdown = ESHUTDOWN, - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(posix.ENOBUFS), - Offline = c.int(posix.ENETDOWN), - Host_Unreachable = c.int(posix.EHOSTUNREACH), - Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - Timeout = c.int(posix.EWOULDBLOCK), - Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. +_accept_error :: proc() -> Accept_Error { + #partial switch posix.errno() { + case .EMFILE, .ENFILE, .ENOMEM: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .EOPNOTSUPP: + return .Unsupported_Socket + case .ECONNABORTED: + return .Aborted + case .EWOULDBLOCK: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -// TODO -UDP_Send_Error :: enum c.int { - None = 0, - Message_Too_Long = c.int(posix.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent. - - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(posix.ENETUNREACH), - No_Outbound_Ports_Available = c.int(posix.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(posix.EWOULDBLOCK), - Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(posix.ENOBUFS), - No_Memory_Available = c.int(posix.ENOMEM), // No memory was available to properly manage the send queue. +_tcp_recv_error :: proc() -> TCP_Recv_Error { + #partial switch posix.errno() { + case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ENOTCONN: + return .Not_Connected + case .ECONNRESET: + return .Connection_Closed + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Shutdown_Manner :: enum c.int { - Receive = c.int(posix.SHUT_RD), - Send = c.int(posix.SHUT_WR), - Both = c.int(posix.SHUT_RDWR), +_udp_recv_error :: proc() -> UDP_Recv_Error { + #partial switch posix.errno() { + case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .ENOTCONN: + return .Connection_Refused + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Shutdown_Error :: enum c.int { - None = 0, - Aborted = c.int(posix.ECONNABORTED), - Reset = c.int(posix.ECONNRESET), - Offline = c.int(posix.ENETDOWN), - Not_Connected = c.int(posix.ENOTCONN), - Not_Socket = c.int(posix.ENOTSOCK), - Invalid_Manner = c.int(posix.EINVAL), +_tcp_send_error :: proc() -> TCP_Send_Error { + #partial switch posix.errno() { + case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Closed + case .ENOTCONN: + return .Not_Connected + case .EHOSTUNREACH: + return .Host_Unreachable + case .ENETDOWN, .ENETUNREACH: + return .Network_Unreachable + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Socket_Option_Error :: enum c.int { - None = 0, - Offline = c.int(posix.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(posix.ENETRESET), - Invalid_Option_For_Socket = c.int(posix.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(posix.ENOTCONN), - Not_Socket = c.int(posix.ENOTSOCK), +_udp_send_error :: proc() -> UDP_Send_Error { + #partial switch posix.errno() { + case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Refused + case .EHOSTUNREACH: + return .Host_Unreachable + case .ENETDOWN, .ENETUNREACH: + return .Network_Unreachable + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Set_Blocking_Error :: enum c.int { - None = 0, - - // TODO: Add errors for `set_blocking` +_shutdown_error :: proc() -> Shutdown_Error { + #partial switch posix.errno() { + case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN: + return .Invalid_Argument + case: + return .Unknown + } +} + +_socket_option_error :: proc() -> Socket_Option_Error { + #partial switch posix.errno() { + case .ENOBUFS: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EISCONN: + return .Invalid_Socket + case .EINVAL, .ENOPROTOOPT: + return .Invalid_Option + case .EFAULT, .EDOM: + return .Invalid_Value + case: + return .Unknown + } +} + +_set_blocking_error :: proc() -> Set_Blocking_Error { + #partial switch posix.errno() { + case .EBADF: + return .Invalid_Argument + case: + return .Unknown + } } diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin index 486732a95..707ffd0dd 100644 --- a/core/net/errors_freebsd.odin +++ b/core/net/errors_freebsd.odin @@ -20,198 +20,267 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import "core:sys/freebsd" -Create_Socket_Error :: enum c.int { - None = 0, - Access_Denied = cast(c.int)freebsd.Errno.EACCES, - Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, - Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, - Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, - No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, - Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM, - Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT, - Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE, +@(private="file", thread_local) +_last_error: freebsd.Errno + +_last_platform_error :: proc() -> i32 { + return i32(_last_error) } -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, - Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, - Already_Connected = cast(c.int)freebsd.Errno.EISCONN, - Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT, - Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED, - // `Refused` alias for `core:net` tests. - // The above default name `Refused_By_Remote_Host` is more explicit. - Refused = Refused_By_Remote_Host, - Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET, - Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH, - Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, - Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, - Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS, - Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, - Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY, - Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES, - Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, - - // NOTE: There are additional connect() error possibilities, but they are - // strictly for addresses in the UNIX domain. +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(_last_error) + return description } -Bind_Error :: enum c.int { - None = 0, - Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - - // NOTE: bind() can also return EINVAL if the underlying `addrlen` is an - // invalid length for the address family. This shouldn't happen for the net - // package, but it's worth noting. - Already_Bound = cast(c.int)freebsd.Errno.EINVAL, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, - Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, - Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT, - Protected_Address = cast(c.int)freebsd.Errno.EACCES, - Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - - // NOTE: There are additional bind() error possibilities, but they are - // strictly for addresses in the UNIX domain. +_set_last_platform_error :: proc(err: i32) { + _last_error = freebsd.Errno(err) } -Listen_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ, - Already_Connected = cast(c.int)freebsd.Errno.EINVAL, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP, +_create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EPROTOTYPE: + return .Invalid_Argument + case .EACCES, .EPERM: + return .Insufficient_Permissions + case: + return .Unknown + } } -Accept_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Interrupted = cast(c.int)freebsd.Errno.EINTR, - Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, - Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL, - Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT, +_dial_error :: proc(errno: freebsd.Errno) -> Dial_Error { + assert(errno != nil) + _last_error = errno - // NOTE: This is the same as EWOULDBLOCK. - No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN, - // `Would_Block` alias for `core:net` tests. - Would_Block = cast(c.int)freebsd.Errno.EAGAIN, - - New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED, + #partial switch errno { + case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN: + return .Invalid_Argument + case .EISCONN: + return .Already_Connected + case .EALREADY: + return .Already_Connecting + case .EADDRINUSE: + return .Address_In_Use + case .ENETUNREACH: + return .Network_Unreachable + case .EHOSTUNREACH: + return .Host_Unreachable + case .ECONNREFUSED: + return .Refused + case .ECONNRESET: + return .Reset + case .ETIMEDOUT: + return .Timeout + case .EINPROGRESS: + return .Would_Block + case .EINTR: + return .Interrupted + case .EACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } } -TCP_Recv_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, +_bind_error :: proc(errno: freebsd.Errno) -> Bind_Error { + assert(errno != nil) + _last_error = errno - // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), - // but I'm including them for completeness's sake. - Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE, - Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, - - Timeout = cast(c.int)freebsd.Errno.EAGAIN, - Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, - Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + #partial switch errno { + case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT: + return .Insufficient_Resources + case .EBADF: + return .Invalid_Argument + case .EINVAL: + return .Already_Bound + case .EACCES: + return .Insufficient_Permissions_For_Address + case .EADDRINUSE: + return .Address_In_Use + case: + return .Unknown + } } -UDP_Recv_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, +_listen_error :: proc(errno: freebsd.Errno) -> Listen_Error { + assert(errno != nil) + _last_error = errno - // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), - // but I'm including them for completeness's sake. - Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE, - Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, - - Timeout = cast(c.int)freebsd.Errno.EAGAIN, - Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, - Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + #partial switch errno { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .EDESTADDRREQ, .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Already_Connected + case: + return .Unknown + } } -TCP_Send_Error :: enum c.int { - None = 0, - Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, +_accept_error :: proc(errno: freebsd.Errno) -> Accept_Error { + assert(errno != nil) + _last_error = errno - Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE, - - /* The socket is marked non-blocking, or MSG_DONTWAIT is - specified, and the requested operation would block. */ - Would_Block = cast(c.int)freebsd.Errno.EAGAIN, - - /* NOTE: This error arises for two distinct reasons: - - 1. The system was unable to allocate an internal buffer. - The operation may succeed when buffers become available. - - 2. The output queue for a network interface was full. - This generally indicates that the interface has stopped - sending, but may be caused by transient congestion. - */ - No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, - - Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, - Already_Connected = cast(c.int)freebsd.Errno.EISCONN, - ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED, - Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN, - Network_Down = cast(c.int)freebsd.Errno.ENETDOWN, - Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, - Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE, + #partial switch errno { + case .EMFILE, .ENFILE: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .EINVAL: + return .Not_Listening + case .ECONNABORTED: + return .Aborted + case .EWOULDBLOCK: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware. -UDP_Send_Error :: distinct TCP_Send_Error +_tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error { + assert(errno != nil) + _last_error = errno -Shutdown_Manner :: enum c.int { - Receive = cast(c.int)freebsd.Shutdown_Method.RD, - Send = cast(c.int)freebsd.Shutdown_Method.WR, - Both = cast(c.int)freebsd.Shutdown_Method.RDWR, + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ENOTCONN: + return .Not_Connected + case .ECONNRESET: + return .Connection_Closed + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Shutdown_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, +_udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ECONNRESET, .ENOTCONN: + return .Connection_Refused + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Socket_Option_Error :: enum c.int { - None = 0, - Value_Out_Of_Range = -1, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT, - Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - // This error can arise for many different reasons. - Invalid_Value = cast(c.int)freebsd.Errno.EINVAL, - System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM, - Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS, +_tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Closed + case .ENOTCONN: + return .Not_Connected + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Set_Blocking_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY, +_udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Refused + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN: + return .Invalid_Argument + case: + return .Unknown + } +} + +_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .ENOMEM, .ENOBUFS: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK: + return .Invalid_Socket + case .ENOPROTOOPT: + return .Invalid_Option + case .EINVAL, .EFAULT: + return .Invalid_Value + case: + return .Unknown + } +} + +_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTTY: + return .Invalid_Argument + case: + return .Unknown + } } diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 3cd51e6fd..da9811d71 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -21,181 +21,269 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import "core:sys/linux" -Create_Socket_Error :: enum c.int { - None = 0, - Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - No_Memory_Available_Available = c.int(linux.Errno.ENOMEM), - Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT), +@(private="file", thread_local) +_last_error: linux.Errno + +_last_platform_error :: proc() -> i32 { + return i32(_last_error) } -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, - - Address_In_Use = c.int(linux.Errno.EADDRINUSE), - In_Progress = c.int(linux.Errno.EINPROGRESS), - Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT), - Refused = c.int(linux.Errno.ECONNREFUSED), - Is_Listening_Socket = c.int(linux.Errno.EACCES), - Already_Connected = c.int(linux.Errno.EISCONN), - Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Timeout = c.int(linux.Errno.ETIMEDOUT), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(linux.Errno.EWOULDBLOCK), +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(_last_error) + return description } -Bind_Error :: enum c.int { - None = 0, - Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine. - Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket. - Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address. - No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available. +_set_last_platform_error :: proc(err: i32) { + _last_error = linux.Errno(err) } -Listen_Error :: enum c.int { - None = 0, - Address_In_Use = c.int(linux.Errno.EADDRINUSE), - Already_Connected = c.int(linux.Errno.EISCONN), - No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP), +_create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EPROTOTYPE: + return .Invalid_Argument + case .EACCES, .EPERM: + return .Insufficient_Permissions + case: + return .Unknown + } } -Accept_Error :: enum c.int { - None = 0, - Not_Listening = c.int(linux.Errno.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE), - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP), +_dial_error :: proc(errno: linux.Errno) -> Dial_Error { + assert(errno != nil) + _last_error = errno - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(linux.Errno.EWOULDBLOCK), + #partial switch errno { + case .EAGAIN: + return .Insufficient_Resources + case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT: + return .Invalid_Argument + case .EISCONN: + return .Already_Connected + case .EALREADY: + return .Already_Connecting + case .EADDRINUSE: + return .Address_In_Use + case .ENETUNREACH: + return .Network_Unreachable + case .EHOSTUNREACH: + return .Host_Unreachable + case .ECONNREFUSED: + return .Refused + case .ECONNRESET: + return .Reset + case .ETIMEDOUT: + return .Timeout + case .EINPROGRESS: + return .Would_Block + case .EINTR: + return .Interrupted + case .EACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } } -TCP_Recv_Error :: enum c.int { - None = 0, - Shutdown = c.int(linux.Errno.ESHUTDOWN), - Not_Connected = c.int(linux.Errno.ENOTCONN), - Connection_Broken = c.int(linux.Errno.ENETRESET), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Aborted = c.int(linux.Errno.ECONNABORTED), +_bind_error :: proc(errno: linux.Errno) -> Bind_Error { + assert(errno != nil) + _last_error = errno - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = c.int(linux.Errno.ECONNRESET), - Offline = c.int(linux.Errno.ENETDOWN), - Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), - Interrupted = c.int(linux.Errno.EINTR), - Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + #partial switch errno { + case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT: + return .Insufficient_Resources + case .EINVAL: + return .Already_Bound + case .EBADF: + return .Invalid_Argument + case .EACCES: + return .Insufficient_Permissions_For_Address + case .EADDRINUSE: + return .Address_In_Use + case: + return .Unknown + } } -UDP_Recv_Error :: enum c.int { - None = 0, +_listen_error :: proc(errno: linux.Errno) -> Listen_Error { + assert(errno != nil) + _last_error = errno - Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. - Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(linux.Errno.EWOULDBLOCK), - Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't. + #partial switch errno { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .EDESTADDRREQ, .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Already_Connected + case: + return .Unknown + } } -TCP_Send_Error :: enum c.int { - None = 0, - Aborted = c.int(linux.Errno.ECONNABORTED), - Connection_Closed = c.int(linux.Errno.ECONNRESET), - Not_Connected = c.int(linux.Errno.ENOTCONN), - Shutdown = c.int(linux.Errno.ESHUTDOWN), +_accept_error :: proc(errno: linux.Errno) -> Accept_Error { + assert(errno != nil) + _last_error = errno - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Offline = c.int(linux.Errno.ENETDOWN), - Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), - Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). - Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. + #partial switch errno { + case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .EINVAL: + return .Not_Listening + case .ECONNABORTED: + return .Aborted + case .EWOULDBLOCK: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -// TODO -UDP_Send_Error :: enum c.int { - None = 0, - Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent. +_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error { + assert(errno != nil) + _last_error = errno - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(linux.Errno.ENETUNREACH), - No_Outbound_Ports_Available = c.int(linux.Errno.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(linux.Errno.EWOULDBLOCK), - Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue. + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ENOTCONN: + return .Not_Connected + case .ECONNREFUSED: + return .Connection_Closed + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -// TODO(flysand): slight regression -Shutdown_Manner :: enum c.int { - Receive = c.int(linux.Shutdown_How.RD), - Send = c.int(linux.Shutdown_How.WR), - Both = c.int(linux.Shutdown_How.RDWR), +_udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ECONNREFUSED, .ENOTCONN: + return .Connection_Refused + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Shutdown_Error :: enum c.int { - None = 0, - Aborted = c.int(linux.Errno.ECONNABORTED), - Reset = c.int(linux.Errno.ECONNRESET), - Offline = c.int(linux.Errno.ENETDOWN), - Not_Connected = c.int(linux.Errno.ENOTCONN), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Invalid_Manner = c.int(linux.Errno.EINVAL), +_tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Closed + case .ENOTCONN: + return .Not_Connected + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Socket_Option_Error :: enum c.int { - None = 0, - Offline = c.int(linux.Errno.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET), - Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN), - Not_Socket = c.int(linux.Errno.ENOTSOCK), +_udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Refused + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } } -Set_Blocking_Error :: enum c.int { - None = 0, +_shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error { + assert(errno != nil) + _last_error = errno - // TODO: add errors occuring on followig calls: - // flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0) - // linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK)) -} \ No newline at end of file + #partial switch errno { + case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN: + return .Invalid_Argument + case: + return .Unknown + } +} + +_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .ENOMEM, .ENOBUFS: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK: + return .Invalid_Socket + case .ENOPROTOOPT, .EINVAL: + return .Invalid_Option + case .EFAULT, .EDOM: + return .Invalid_Value + case: + return .Unknown + } +} + +_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF: + return .Invalid_Argument + case: + return .Unknown + } +} diff --git a/core/net/errors_others.odin b/core/net/errors_others.odin new file mode 100644 index 000000000..bda0fd28f --- /dev/null +++ b/core/net/errors_others.odin @@ -0,0 +1,20 @@ +#+build !darwin +#+build !linux +#+build !freebsd +#+build !windows +package net + +@(private="file", thread_local) +_last_error: i32 + +_last_platform_error :: proc() -> i32 { + return _last_error +} + +_last_platform_error_string :: proc() -> string { + return "" +} + +_set_last_platform_error :: proc(err: i32) { + _last_error = err +} diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index f41bcf888..b30046a17 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -20,250 +20,242 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import win "core:sys/windows" -Create_Socket_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, - No_Socket_Descriptors_Available = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT, - Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE, - Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT, +_last_platform_error :: proc() -> i32 { + return i32(win.WSAGetLastError()) } -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, - Address_In_Use = win.WSAEADDRINUSE, - In_Progress = win.WSAEALREADY, - Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, - Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT, - Refused = win.WSAECONNREFUSED, - Is_Listening_Socket = win.WSAEINVAL, - Already_Connected = win.WSAEISCONN, - Network_Unreachable = win.WSAENETUNREACH, // Device is offline - Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, - Timeout = win.WSAETIMEDOUT, - Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError())) + return description } -Bind_Error :: enum c.int { - None = 0, - Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine. - Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket. - Already_Bound = win.WSAEINVAL, // The socket is already bound to an address. - No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available. +_set_last_platform_error :: proc(err: i32) { + win.WSASetLastError(err) } -Listen_Error :: enum c.int { - None = 0, - Address_In_Use = win.WSAEADDRINUSE, - Already_Connected = win.WSAEISCONN, - No_Socket_Descriptors_Available = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Nonlocal_Address = win.WSAEADDRNOTAVAIL, - Not_Socket = win.WSAENOTSOCK, - Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, +_create_socket_error :: proc() -> Create_Socket_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT: + return .Network_Unreachable + case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT: + return .Invalid_Argument + case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT: + return .Insufficient_Resources + case: + return .Unknown + } } -Accept_Error :: enum c.int { - None = 0, - Not_Listening = win.WSAEINVAL, - No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, - Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = win.WSAEWOULDBLOCK, +_dial_error :: proc() -> Dial_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEADDRINUSE: + return .Address_In_Use + case .WSAEINTR: + return .Interrupted + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAEALREADY: + return .Already_Connecting + case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL: + return .Invalid_Argument + case .WSAECONNREFUSED: + return .Refused + case .WSAEISCONN: + return .Already_Connected + case .WSAEHOSTUNREACH: + return .Host_Unreachable + case .WSAENOBUFS: + return .Insufficient_Resources + case .WSAETIMEDOUT: + return .Timeout + case .WSAEACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } } -TCP_Recv_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - Not_Connected = win.WSAENOTCONN, - Bad_Buffer = win.WSAEFAULT, - Keepalive_Failure = win.WSAENETRESET, - Not_Socket = win.WSAENOTSOCK, - Shutdown = win.WSAESHUTDOWN, - Would_Block = win.WSAEWOULDBLOCK, - Aborted = win.WSAECONNABORTED, - Timeout = win.WSAETIMEDOUT, - - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = win.WSAECONNRESET, - - // TODO: verify can actually happen - Host_Unreachable = win.WSAEHOSTUNREACH, +_bind_error :: proc() -> Bind_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEADDRINUSE: + return .Address_In_Use + case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK: + return .Invalid_Argument + case: + return .Unknown + } } -UDP_Recv_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - Aborted = win.WSAECONNABORTED, - Buffer_Too_Small = win.WSAEMSGSIZE, // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. - Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. - Shutdown = win.WSAESHUTDOWN, - Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set. - Bad_Buffer = win.WSAEFAULT, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. - Would_Block = win.WSAEWOULDBLOCK, - Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. - Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. - Timeout = win.WSAETIMEDOUT, - - // TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled. - Incorrectly_Configured = win.WSAEINVAL, - TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint. +_listen_error :: proc() -> Listen_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEMFILE, .WSAENOBUFS: + return .Insufficient_Resources + case .WSAEADDRINUSE: + return .Address_In_Use + case .WSAEINPROGRESS, .WSAENOTSOCK: + return .Invalid_Argument + case .WSAEISCONN: + return .Already_Connected + case .WSAEOPNOTSUPP, .WSAEINVAL: + return .Unsupported_Socket + case: + return .Unknown + } } -// TODO: consider merging some errors to make handling them easier -// TODO: verify once more what errors to actually expose -TCP_Send_Error :: enum c.int { - None = 0, - - Aborted = win.WSAECONNABORTED, - Not_Connected = win.WSAENOTCONN, - Shutdown = win.WSAESHUTDOWN, - Connection_Closed = win.WSAECONNRESET, - No_Buffer_Space_Available = win.WSAENOBUFS, - Network_Subsystem_Failure = win.WSAENETDOWN, - Host_Unreachable = win.WSAEHOSTUNREACH, - Would_Block = win.WSAEWOULDBLOCK, - - // TODO: verify possible, as not mentioned in docs - Offline = win.WSAENETUNREACH, - Timeout = win.WSAETIMEDOUT, - - // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, - - // Connection is broken due to keepalive activity detecting a failure during the operation. - Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? - Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket. +_accept_error :: proc() -> Accept_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEMFILE, .WSAENOBUFS: + return .Insufficient_Resources + case .WSAECONNRESET: + return .Aborted + case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAEINVAL: + return .Not_Listening + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAEOPNOTSUPP: + return .Unsupported_Socket + case: + return .Unknown + } } -UDP_Send_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - - Aborted = win.WSAECONNABORTED, - Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size. - Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. - Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation. - - // TODO: not functionally different from Reset; merge? - Keepalive_Failure = win.WSAENETRESET, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. - - // This socket is unidirectional and cannot be used to send any data. - // TODO: verify possible; decide whether to keep if not - Receive_Only = win.WSAEOPNOTSUPP, - Would_Block = win.WSAEWOULDBLOCK, - Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. - Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address. - Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket. - Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. - Timeout = win.WSAETIMEDOUT, +_tcp_recv_error :: proc() -> TCP_Recv_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP: + return .Invalid_Argument + case .WSAENOTCONN: + return .Not_Connected + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET: + return .Connection_Closed + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case: + return .Unknown + } } -Shutdown_Manner :: enum c.int { - Receive = win.SD_RECEIVE, - Send = win.SD_SEND, - Both = win.SD_BOTH, +_udp_recv_error :: proc() -> UDP_Recv_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEFAULT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEISCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET: + return .Connection_Refused + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case: + return .Unknown + } } -Shutdown_Error :: enum c.int { - None = 0, - Aborted = win.WSAECONNABORTED, - Reset = win.WSAECONNRESET, - Offline = win.WSAENETDOWN, - Not_Connected = win.WSAENOTCONN, - Not_Socket = win.WSAENOTSOCK, - Invalid_Manner = win.WSAEINVAL, +_tcp_send_error :: proc() -> TCP_Send_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAENOBUFS: + return .Insufficient_Resources + case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET: + return .Connection_Closed + case .WSAENOTCONN: + return .Not_Connected + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case .WSAEHOSTUNREACH: + return .Host_Unreachable + case: + return .Unknown + } } -Socket_Option :: enum c.int { - // bool: Whether the address that this socket is bound to can be reused by other sockets. - // This allows you to bypass the cooldown period if a program dies while the socket is bound. - Reuse_Address = win.SO_REUSEADDR, - - // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. - Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, - - // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding - Keep_Alive = win.SO_KEEPALIVE, - - // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. - Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, - - // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. - Dont_Linger = win.SO_DONTLINGER, - - // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data. - Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, - - // bool: When true, disables send-coalescing, therefore reducing latency. - TCP_Nodelay = win.TCP_NODELAY, - - // win.LINGER: Customizes how long (if at all) the socket will remain open when there - // is some remaining data waiting to be sent, and net.close() is called. - Linger = win.SO_LINGER, - - // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. - Receive_Buffer_Size = win.SO_RCVBUF, - - // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. - Send_Buffer_Size = win.SO_SNDBUF, - - // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Receive_Timeout = win.SO_RCVTIMEO, - - // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Send_Timeout = win.SO_SNDTIMEO, - - // bool: Allow sending to, receiving from, and binding to, a broadcast address. - Broadcast = win.SO_BROADCAST, +_udp_send_error :: proc() -> UDP_Send_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH: + return .Network_Unreachable + case .WSAENOBUFS: + return .Insufficient_Resources + case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET: + return .Connection_Refused + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case: + return .Unknown + } } -Socket_Option_Error :: enum c.int { - None = 0, - Linger_Only_Supports_Whole_Seconds = 1, - - // The given value is too big or small to be given to the OS. - Value_Out_Of_Range, - - Network_Subsystem_Failure = win.WSAENETDOWN, - Timeout_When_Keepalive_Set = win.WSAENETRESET, - Invalid_Option_For_Socket = win.WSAENOPROTOOPT, - Reset_When_Keepalive_Set = win.WSAENOTCONN, - Not_Socket = win.WSAENOTSOCK, +_shutdown_error :: proc() -> Shutdown_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAENETDOWN, .WSANOTINITIALISED: + return .Network_Unreachable + case .WSAECONNABORTED, .WSAECONNRESET: + return .Connection_Closed + case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK: + return .Invalid_Argument + case: + return .Unknown + } } -Set_Blocking_Error :: enum c.int { - None = 0, +_socket_option_error :: proc() -> Socket_Option_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAENETDOWN, .WSANOTINITIALISED: + return .Network_Unreachable + case .WSAEFAULT, .WSAEINVAL: + return .Invalid_Value + case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK: + return .Invalid_Socket + case .WSAENOPROTOOPT: + return .Invalid_Option + case: + return .Unknown + } +} - Network_Subsystem_Failure = win.WSAENETDOWN, - Blocking_Call_In_Progress = win.WSAEINPROGRESS, - Not_Socket = win.WSAENOTSOCK, - - // TODO: are those errors possible? - Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED, - Invalid_Argument_Pointer = win.WSAEFAULT, -} \ No newline at end of file +_set_blocking_error :: proc() -> Set_Blocking_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAENETDOWN, .WSANOTINITIALISED: + return .Network_Unreachable + case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT: + return .Invalid_Argument + case: + return .Unknown + } +} diff --git a/core/net/interface.odin b/core/net/interface.odin index 775a812f3..4d499a008 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3 /* `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. */ -enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { return _enumerate_interfaces(allocator) } diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 9aa6cbd1a..f189e5844 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -26,7 +26,7 @@ import "core:sys/posix" foreign import lib "system:System.framework" @(private) -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { context.allocator = allocator head: ^ifaddrs @@ -47,7 +47,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] iface.adapter_name = key_ptr^ } if mem_err != nil { - return {}, .Unable_To_Enumerate_Network_Interfaces + return {}, .Allocation_Failure } address: Address diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin index 50e2d1a96..90a538a04 100644 --- a/core/net/interface_freebsd.odin +++ b/core/net/interface_freebsd.odin @@ -25,7 +25,7 @@ import "core:strings" import "core:sys/freebsd" @(private) -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { // This is a simplified implementation of `getifaddrs` from the FreeBSD // libc using only Odin and syscalls. context.allocator = allocator @@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] // Allocate and get the entries. buf, alloc_err := make([]byte, needed) if alloc_err != nil { - return nil, .Unable_To_Enumerate_Network_Interfaces + return nil, .Allocation_Failure } defer delete(buf) diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index 28724735b..e329803c5 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -30,7 +30,7 @@ package net // NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html // apparently musl libc uses this to enumerate network interfaces @(private) -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { context.allocator = allocator // head: ^os.ifaddrs @@ -143,4 +143,4 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] // } // return _interfaces[:], {} return nil, {} -} \ No newline at end of file +} diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index a6eb72846..571fb322f 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -23,7 +23,7 @@ package net import sys "core:sys/windows" import strings "core:strings" -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { context.allocator = allocator buf: []u8 @@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] case 0: break gaa case: - return {}, Platform_Error(res) + set_last_platform_error(i32(res)) + return {}, .Unknown } } @@ -63,13 +64,13 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] _interfaces := make([dynamic]Network_Interface, 0, allocator) for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator) - if err1 != nil { return {}, Platform_Error(err1) } + if err1 != nil { return {}, .Allocation_Failure } description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator) - if err2 != nil { return {}, Platform_Error(err2) } + if err2 != nil { return {}, .Allocation_Failure } dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator) - if err3 != nil { return {}, Platform_Error(err3) } + if err3 != nil { return {}, .Allocation_Failure } interface := Network_Interface{ adapter_name = strings.clone(string(adapter.AdapterName)), @@ -176,4 +177,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) { case: return // Empty or invalid address type } unreachable() -} \ No newline at end of file +} diff --git a/core/net/socket.odin b/core/net/socket.odin index c5ea11e11..11655cf4f 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -35,6 +35,8 @@ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket { `a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent. Calls `parse_hostname_or_endpoint` and `dial_tcp_from_host_or_endpoint`. + + Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { target := parse_hostname_or_endpoint(hostname_and_port) or_return @@ -47,6 +49,8 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option `parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP. If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param. + + Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { target := parse_hostname_or_endpoint(hostname) or_return @@ -62,6 +66,8 @@ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, o /* Expects the `host` as Host. + + Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { if host.port == 0 { @@ -76,6 +82,8 @@ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socke /* Expects the `target` as a Host_OrEndpoint. Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`. + + Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { switch t in target { @@ -87,11 +95,20 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa unreachable() } -// Dial from an Address +/* + Dial from an Address. + + Errors that can be returned: `Create_Socket_Error`, or `Dial_Error` +*/ dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { return dial_tcp_from_endpoint({address, port}, options) } +/* + Dial from an Endpoint. + + Errors that can be returned: `Create_Socket_Error`, or `Dial_Error` +*/ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { return _dial_tcp_from_endpoint(endpoint, options) } @@ -105,11 +122,11 @@ dial_tcp :: proc{ dial_tcp_from_host_or_endpoint, } -create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { return _create_socket(family, protocol) } -bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { return _bind(socket, ep) } @@ -119,7 +136,7 @@ bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. */ -make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) { +make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) { sock := create_socket(family, .UDP) or_return socket = sock.(UDP_Socket) return @@ -131,6 +148,8 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use. + + Errors that can be returned: `Parse_Endpoint_Error`, `Create_Socket_Error`, or `Bind_Error` */ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) { if bound_address == nil { @@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP return } +/* + Creates a TCP socket and starts listening on the given endpoint. + + Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error` +*/ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { assert(backlog > 0 && backlog < int(max(i32))) @@ -150,11 +174,11 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC /* Returns the endpoint that the given socket is listening / bound on. */ -bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) { +bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) { return _bound_endpoint(socket) } -accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { return _accept_tcp(socket, options) } @@ -162,11 +186,11 @@ close :: proc(socket: Any_Socket) { _close(socket) } -recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { return _recv_tcp(socket, buf) } -recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { return _recv_udp(socket, buf) } @@ -175,6 +199,8 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it will always return `nil`. + + Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error` */ recv_any :: proc(socket: Any_Socket, buf: []byte) -> ( bytes_read: int, @@ -197,7 +223,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any} Repeatedly sends data until the entire buffer is sent. If a send fails before all data is sent, returns the amount sent up to that point. */ -send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { return _send_tcp(socket, buf) } @@ -207,10 +233,15 @@ send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: N Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. UDP packets are not guarenteed to be received in order. */ -send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { return _send_udp(socket, buf, to) } +/* + Sends data over the socket. + + Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error` +*/ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> ( bytes_written: int, err: Network_Error, @@ -226,14 +257,14 @@ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> send :: proc{send_tcp, send_udp, send_any} -shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { return _shutdown(socket, manner) } -set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { return _set_option(socket, option, value, loc) } -set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { return _set_blocking(socket, should_block) } diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index a132a6a95..1e3fd8f4e 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -37,8 +37,14 @@ Socket_Option :: enum c.int { Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO), } +Shutdown_Manner :: enum c.int { + Receive = c.int(posix.SHUT_RD), + Send = c.int(posix.SHUT_WR), + Both = c.int(posix.SHUT_RDWR), +} + @(private) -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { c_type: posix.Sock c_protocol: posix.Protocol c_family: posix.AF @@ -59,7 +65,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so sock := posix.socket(c_family, c_type, c_protocol) if sock < 0 { - err = Create_Socket_Error(posix.errno()) + err = _create_socket_error() return } @@ -88,28 +94,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { - errno := posix.errno() + err = _dial_error() close(skt) - return {}, Dial_Error(errno) } return } -// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it. -MAX_PRIVILEGED_PORT :: 1023 - @(private) -_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { - errno := posix.errno() - if errno == .EACCES && ep.port <= MAX_PRIVILEGED_PORT { - err = .Privileged_Port_Without_Root - } else { - err = Bind_Error(errno) - } + err = _bind_error() } return @@ -128,25 +125,23 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ // bypass the cooldown period, and allow the next run of the program to // use the same address immediately. // - // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! - set_option(sock, .Reuse_Address, true) or_return + _ = set_option(sock, .Reuse_Address, true) bind(sock, interface_endpoint) or_return if posix.listen(posix.FD(skt), i32(backlog)) != .OK { - err = Listen_Error(posix.errno()) - return + err = _listen_error() } return } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { addr: posix.sockaddr_storage addr_len := posix.socklen_t(size_of(addr)) if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { - err = Listen_Error(posix.errno()) + err = _listen_error() return } @@ -155,12 +150,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { addr: posix.sockaddr_storage addr_len := posix.socklen_t(size_of(addr)) client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len) if client_sock < 0 { - err = Accept_Error(posix.errno()) + err = _accept_error() return } @@ -176,14 +171,14 @@ _close :: proc(skt: Any_Socket) { } @(private) -_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { if len(buf) <= 0 { return } res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {}) if res < 0 { - err = TCP_Recv_Error(posix.errno()) + err = _tcp_recv_error() return } @@ -191,7 +186,7 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ } @(private) -_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { if len(buf) <= 0 { return } @@ -200,7 +195,7 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp fromsize := posix.socklen_t(size_of(from)) res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize) if res < 0 { - err = UDP_Recv_Error(posix.errno()) + err = _udp_recv_error() return } @@ -210,20 +205,13 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp } @(private) -_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}) if res < 0 { - errno := posix.errno() - if errno == .EPIPE { - // EPIPE arises if the socket has been closed remotely. - err = TCP_Send_Error.Connection_Closed - return - } - - err = TCP_Send_Error(errno) + err = _tcp_send_error() return } @@ -233,21 +221,14 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net } @(private) -_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { toaddr := _endpoint_to_sockaddr(to) for bytes_written < len(buf) { limit := min(1<<31, len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len)) if res < 0 { - errno := posix.errno() - if errno == .EPIPE { - // EPIPE arises if the socket has been closed remotely. - err = UDP_Send_Error.Not_Socket - return - } - - err = UDP_Send_Error(errno) + err = _udp_send_error() return } @@ -257,16 +238,16 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: } @(private) -_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { s := any_socket_to_socket(skt) if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK { - err = Shutdown_Error(posix.errno()) + err = _shutdown_error() } return } @(private) -_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; @@ -337,19 +318,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca skt := any_socket_to_socket(s) if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK { - return Socket_Option_Error(posix.errno()) + return _socket_option_error() } return nil } @(private) -_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { socket := any_socket_to_socket(socket) flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0) if flags_ < 0 { - return Set_Blocking_Error(posix.errno()) + return _set_blocking_error() } flags := transmute(posix.O_Flags)flags_ @@ -360,7 +341,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E } if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 { - return Set_Blocking_Error(posix.errno()) + return _set_blocking_error() } return nil diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin index 3a3774007..4824b63dd 100644 --- a/core/net/socket_freebsd.odin +++ b/core/net/socket_freebsd.odin @@ -54,8 +54,14 @@ Socket_Option :: enum c.int { Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, } +Shutdown_Manner :: enum c.int { + Receive = cast(c.int)freebsd.Shutdown_Method.RD, + Send = cast(c.int)freebsd.Shutdown_Method.WR, + Both = cast(c.int)freebsd.Shutdown_Method.RDWR, +} + @(private) -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { sys_family: freebsd.Protocol_Family = --- sys_protocol: freebsd.Protocol = --- sys_socket_type: freebsd.Socket_Type = --- @@ -72,24 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol) if errno != nil { - err = cast(Create_Socket_Error)errno - return - } - - // NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE - // error is raised during the writing of a socket that may be closed. - // This behavior is unlikely to be expected by general users. - // - // There are two workarounds. One is to apply the .NOSIGNAL flag when using - // the `sendto` syscall. However, that would prevent users of this library - // from re-enabling the SIGPIPE-raising functionality, if they really - // wanted it. - // - // So I have disabled it here with this socket option for all sockets. - truth: b32 = true - errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth)) - if errno != nil { - err = cast(Socket_Option_Error)errno + err = _create_socket_error(errno) return } @@ -115,19 +104,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) if errno != nil { close(socket) - return {}, cast(Dial_Error)errno + return {}, _dial_error(errno) } return } @(private) -_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { sockaddr := _endpoint_to_sockaddr(ep) real_socket := any_socket_to_socket(socket) errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) if errno != nil { - err = cast(Bind_Error)errno + err = _bind_error(errno) } return } @@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T errno := freebsd.listen(cast(Fd)socket, backlog) if errno != nil { - err = cast(Listen_Error)errno + err = _listen_error(errno) return } @@ -151,12 +140,12 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { sockaddr: freebsd.Socket_Address_Storage errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) if errno != nil { - err = cast(Listen_Error)errno + err = _listen_error(errno) return } @@ -165,12 +154,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { sockaddr: freebsd.Socket_Address_Storage result, errno := freebsd.accept(cast(Fd)sock, &sockaddr) if errno != nil { - err = cast(Accept_Error)errno + err = _accept_error(errno) return } @@ -187,20 +176,20 @@ _close :: proc(socket: Any_Socket) { } @(private) -_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { if len(buf) == 0 { return } result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE) if errno != nil { - err = cast(TCP_Recv_Error)errno + err = _tcp_recv_error(errno) return } return result, nil } @(private) -_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { if len(buf) == 0 { return } @@ -208,21 +197,21 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from) if errno != nil { - err = cast(UDP_Recv_Error)errno + err = _udp_recv_error(errno) return } return result, _sockaddr_to_endpoint(&from), nil } @(private) -_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE) + result, errno := freebsd.send(cast(Fd)socket, remaining, .NOSIGNAL) if errno != nil { - err = cast(TCP_Send_Error)errno + err = _tcp_send_error(errno) return } bytes_written += result @@ -231,15 +220,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: } @(private) -_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { toaddr := _endpoint_to_sockaddr(to) for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr) + result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NOSIGNAL, &toaddr) if errno != nil { - err = cast(UDP_Send_Error)errno + err = _udp_send_error(errno) return } bytes_written += result @@ -248,17 +237,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt } @(private) -_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { real_socket := cast(Fd)any_socket_to_socket(socket) errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner) if errno != nil { - return cast(Shutdown_Error)errno + return _shutdown_error(errno) } return } @(private) -_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { // NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32 // bits for a boolean socket option value. Nothing less will work. bool_value: b32 @@ -315,25 +304,25 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc : case u16: int_value = cast(i32)real case i32: int_value = real case u32: - if real > u32(max(i32)) { return .Value_Out_Of_Range } + if real > u32(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case i64: - if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range } + if real > i64(max(i32)) || real < i64(min(i32)) { return .Invalid_Value } int_value = cast(i32)real case u64: - if real > u64(max(i32)) { return .Value_Out_Of_Range } + if real > u64(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case i128: - if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range } + if real > i128(max(i32)) || real < i128(min(i32)) { return .Invalid_Value } int_value = cast(i32)real case u128: - if real > u128(max(i32)) { return .Value_Out_Of_Range } + if real > u128(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case int: - if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range } + if real > int(max(i32)) || real < int(min(i32)) { return .Invalid_Value } int_value = cast(i32)real case uint: - if real > uint(max(i32)) { return .Value_Out_Of_Range } + if real > uint(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case: panic("set_option() value must be an integer here", loc) @@ -347,19 +336,19 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc : real_socket := any_socket_to_socket(socket) errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len) if errno != nil { - return cast(Socket_Option_Error)errno + return _socket_option_error(errno) } return nil } @(private) -_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { real_socket := any_socket_to_socket(socket) flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket) if errno != nil { - return cast(Set_Blocking_Error)errno + return _set_blocking_error(errno) } if should_block { @@ -370,7 +359,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags) if errno != nil { - return cast(Set_Blocking_Error)errno + return _set_blocking_error(errno) } return diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index cafec747d..7dd1ccc9c 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -38,15 +38,21 @@ Socket_Option :: enum c.int { Broadcast = c.int(linux.Socket_Option.BROADCAST), } +Shutdown_Manner :: enum c.int { + Receive = c.int(linux.Shutdown_How.RD), + Send = c.int(linux.Shutdown_How.WR), + Both = c.int(linux.Shutdown_How.RDWR), +} + // Wrappers and unwrappers for system-native types @(private="file") -_unwrap_os_socket :: proc "contextless" (sock: Any_Socket)->linux.Fd { +_unwrap_os_socket :: proc "contextless" (sock: Any_Socket) -> linux.Fd { return linux.Fd(any_socket_to_socket(sock)) } @(private="file") -_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol)->Any_Socket { +_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol) -> Any_Socket { switch protocol { case .TCP: return TCP_Socket(Socket(sock)) case .UDP: return UDP_Socket(Socket(sock)) @@ -56,7 +62,7 @@ _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol } @(private="file") -_unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_Family { +_unwrap_os_family :: proc "contextless" (family: Address_Family) -> linux.Address_Family { switch family { case .IP4: return .INET case .IP6: return .INET6 @@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_ } @(private="file") -_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(linux.Protocol, linux.Socket_Type) { +_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol) -> (linux.Protocol, linux.Socket_Type) { switch protocol { case .TCP: return .TCP, .STREAM case .UDP: return .UDP, .DGRAM @@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li } @(private="file") -_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any) { +_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint) -> linux.Sock_Addr_Any { switch address in endpoint.address { case IP4_Address: return { @@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any } @(private="file") -_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { +_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any) -> Endpoint { #partial switch addr.family { case .INET: return { @@ -117,12 +123,12 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { } } -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Create_Socket_Error) { family := _unwrap_os_family(family) proto, socktype := _unwrap_os_proto_socktype(protocol) sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) if errno != .NONE { - return {}, Create_Socket_Error(errno) + return {}, _create_socket_error(errno) } return _wrap_os_socket(sock, protocol), nil } @@ -138,7 +144,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket - return {}, Create_Socket_Error(errno) + return {}, _create_socket_error(errno) } // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to @@ -149,7 +155,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio errno = linux.connect(linux.Fd(os_sock), &addr) if errno != .NONE { close(cast(TCP_Socket) os_sock) - return {}, Dial_Error(errno) + return {}, _dial_error(errno) } // NOTE(tetra): Not vital to succeed; error ignored no_delay: b32 = cast(b32) options.no_delay @@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } @(private) -_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) { +_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) { addr := _unwrap_os_addr(endpoint) errno := linux.bind(_unwrap_os_socket(sock), &addr) if errno != .NONE { - return Bind_Error(errno) + return _bind_error(errno) } return nil } @@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, os_sock: linux.Fd os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { - err = Create_Socket_Error(errno) + err = _create_socket_error(errno) return } socket = cast(TCP_Socket)os_sock @@ -193,31 +199,30 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! do_reuse_addr: b32 = true if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE { - err = Listen_Error(errno) + err = _listen_error(errno) return } // Bind the socket to endpoint address if errno = linux.bind(os_sock, &ep_address); errno != .NONE { - err = Bind_Error(errno) + err = _bind_error(errno) return } // Listen on bound socket if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE { - err = Listen_Error(errno) - return + err = _listen_error(errno) } return } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { addr: linux.Sock_Addr_Any errno := linux.getsockname(_unwrap_os_socket(sock), &addr) if errno != .NONE { - err = Listen_Error(errno) + err = _listen_error(errno) return } @@ -226,11 +231,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) { +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Accept_Error) { addr: linux.Sock_Addr_Any client_sock, errno := linux.accept(linux.Fd(sock), &addr) if errno != .NONE { - return {}, {}, Accept_Error(errno) + return {}, {}, _accept_error(errno) } // NOTE(tetra): Not vital to succeed; error ignored val: b32 = cast(b32) options.no_delay @@ -244,19 +249,19 @@ _close :: proc(sock: Any_Socket) { } @(private) -_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { +_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Recv_Error) { if len(buf) <= 0 { return 0, nil } bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {}) if errno != .NONE { - return 0, TCP_Recv_Error(errno) + return 0, _tcp_recv_error(errno) } return int(bytes_read), nil } @(private) -_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_Error) { +_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, UDP_Recv_Error) { if len(buf) <= 0 { // NOTE(flysand): It was returning no error, I didn't change anything return 0, {}, {} @@ -268,28 +273,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_ from_addr: linux.Sock_Addr_Any bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr) if errno != .NONE { - return 0, {}, UDP_Recv_Error(errno) + return 0, {}, _udp_recv_error(errno) } if bytes_read > len(buf) { // NOTE(tetra): The buffer has been filled, with a partial message. - return len(buf), {}, .Buffer_Too_Small + return len(buf), {}, .Excess_Truncated } return bytes_read, _wrap_os_addr(from_addr), nil } @(private) -_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { +_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Send_Error) { total_written := 0 for total_written < len(buf) { limit := min(int(max(i32)), len(buf) - total_written) remaining := buf[total_written:][:limit] res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) - if errno == .EPIPE { - // If the peer is disconnected when we are trying to send we will get an `EPIPE` error, - // so we turn that into a clearer error - return total_written, TCP_Send_Error.Connection_Closed - } else if errno != .NONE { - return total_written, TCP_Send_Error(errno) + if errno != .NONE { + return total_written, _tcp_send_error(errno) } total_written += int(res) } @@ -297,28 +298,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { } @(private) -_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, Network_Error) { +_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, UDP_Send_Error) { to_addr := _unwrap_os_addr(to) bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr) if errno != .NONE { - return bytes_written, UDP_Send_Error(errno) + return bytes_written, _udp_send_error(errno) } return int(bytes_written), nil } @(private) -_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { os_sock := _unwrap_os_socket(sock) errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner) if errno != .NONE { - return Shutdown_Error(errno) + return _shutdown_error(errno) } return nil } // TODO(flysand): Figure out what we want to do with this on core:sys/ level. @(private) -_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { level: int if option == .TCP_Nodelay { level = int(linux.SOL_TCP) @@ -388,19 +389,19 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := errno = linux.setsockopt(os_sock, level, int(option), &int_value) } if errno != .NONE { - return Socket_Option_Error(errno) + return _socket_option_error(errno) } return nil } @(private) -_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { errno: linux.Errno flags: linux.Open_Flags os_sock := _unwrap_os_socket(sock) flags, errno = linux.fcntl(os_sock, linux.F_GETFL) if errno != .NONE { - return Set_Blocking_Error(errno) + return _set_blocking_error(errno) } if should_block { flags -= {.NONBLOCK} @@ -409,7 +410,7 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err } errno = linux.fcntl(os_sock, linux.F_SETFL, flags) if errno != .NONE { - return Set_Blocking_Error(errno) + return _set_blocking_error(errno) } return nil } diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index f19be536a..64bacf6b5 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -24,13 +24,67 @@ import "core:c" import win "core:sys/windows" import "core:time" +Socket_Option :: enum c.int { + // bool: Whether the address that this socket is bound to can be reused by other sockets. + // This allows you to bypass the cooldown period if a program dies while the socket is bound. + Reuse_Address = win.SO_REUSEADDR, + + // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. + Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, + + // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding + Keep_Alive = win.SO_KEEPALIVE, + + // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. + Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, + + // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. + Dont_Linger = win.SO_DONTLINGER, + + // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data. + Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, + + // bool: When true, disables send-coalescing, therefore reducing latency. + TCP_Nodelay = win.TCP_NODELAY, + + // win.LINGER: Customizes how long (if at all) the socket will remain open when there + // is some remaining data waiting to be sent, and net.close() is called. + Linger = win.SO_LINGER, + + // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. + Receive_Buffer_Size = win.SO_RCVBUF, + + // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. + Send_Buffer_Size = win.SO_SNDBUF, + + // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Receive_Timeout = win.SO_RCVTIMEO, + + // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Send_Timeout = win.SO_SNDTIMEO, + + // bool: Allow sending to, receiving from, and binding to, a broadcast address. + Broadcast = win.SO_BROADCAST, +} + + +Shutdown_Manner :: enum c.int { + Receive = win.SD_RECEIVE, + Send = win.SD_SEND, + Both = win.SD_BOTH, +} + @(init, private) ensure_winsock_initialized :: proc() { win.ensure_winsock_initialized() } @(private) -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { c_type, c_protocol, c_family: c.int switch family { @@ -49,7 +103,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so sock := win.socket(c_family, c_type, c_protocol) if sock == win.INVALID_SOCKET { - err = Create_Socket_Error(win.WSAGetLastError()) + err = _create_socket_error() return } @@ -80,7 +134,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) if res < 0 { - err = Dial_Error(win.WSAGetLastError()) + err = _dial_error() close(socket) return {}, err } @@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } @(private) -_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { sockaddr := _endpoint_to_sockaddr(ep) sock := any_socket_to_socket(socket) res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr)) if res < 0 { - err = Bind_Error(win.WSAGetLastError()) + err = _bind_error() } return } @@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T bind(sock, interface_endpoint) or_return if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR { - err = Listen_Error(win.WSAGetLastError()) + err = _listen_error() } return } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { sockaddr: win.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { - err = Listen_Error(win.WSAGetLastError()) + err = _listen_error() return } @@ -136,7 +190,7 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { for { sockaddr: win.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) @@ -150,7 +204,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client // can do this to match the behaviour. continue } - err = Accept_Error(e) + err = _accept_error() return } client = TCP_Socket(client_sock) @@ -170,20 +224,20 @@ _close :: proc(socket: Any_Socket) { } @(private) -_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { if len(buf) <= 0 { return } res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0) if res < 0 { - err = TCP_Recv_Error(win.WSAGetLastError()) + err = _tcp_recv_error() return } return int(res), nil } @(private) -_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { if len(buf) <= 0 { return } @@ -192,7 +246,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e fromsize := c.int(size_of(from)) res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) if res < 0 { - err = UDP_Recv_Error(win.WSAGetLastError()) + err = _udp_recv_error() return } @@ -202,13 +256,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e } @(private) -_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:] res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0) if res < 0 { - err = TCP_Send_Error(win.WSAGetLastError()) + err = _tcp_send_error() return } bytes_written += int(res) @@ -217,34 +271,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: } @(private) -_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { - if len(buf) > int(max(c.int)) { - // NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading. - err = .Message_Too_Long - return - } +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { toaddr := _endpoint_to_sockaddr(to) - res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) - if res < 0 { - err = UDP_Send_Error(win.WSAGetLastError()) - return + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:] + res := win.sendto(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0, &toaddr, size_of(toaddr)) + if res < 0 { + err = _udp_send_error() + return + } + + bytes_written += int(res) } - bytes_written = int(res) return } @(private) -_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { s := any_socket_to_socket(socket) res := win.shutdown(win.SOCKET(s), c.int(manner)) if res < 0 { - return Shutdown_Error(win.WSAGetLastError()) + return _shutdown_error() } return } @(private) -_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP bool_value: b32 @@ -283,11 +337,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) num_secs := i64(time.duration_seconds(t)) - if time.Duration(num_secs * 1e9) != t { - return .Linger_Only_Supports_Whole_Seconds - } if num_secs > i64(max(u16)) { - return .Value_Out_Of_Range + return .Invalid_Value } linger_value.l_onoff = 1 linger_value.l_linger = c.ushort(num_secs) @@ -323,19 +374,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca socket := any_socket_to_socket(s) res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len) if res < 0 { - return Socket_Option_Error(win.WSAGetLastError()) + return _socket_option_error() } return nil } @(private) -_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { socket := any_socket_to_socket(socket) arg: win.DWORD = 0 if should_block else 1 res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg) if res == win.SOCKET_ERROR { - return Set_Blocking_Error(win.WSAGetLastError()) + return _set_blocking_error() } return nil diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 5b2952495..d808bcd09 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -91,6 +91,7 @@ foreign ws2_32 { WSACleanup :: proc() -> c_int --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror) WSAGetLastError :: proc() -> c_int --- + WSASetLastError :: proc(err: c_int) --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll) WSAPoll :: proc(fdArray: ^WSA_POLLFD, fds: c_ulong, timeout: c_int) -> c_int --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaduplicatesocketw) diff --git a/tests/core/net/test_core_net_freebsd.odin b/tests/core/net/test_core_net_freebsd.odin index 39e364e80..590db7de0 100644 --- a/tests/core/net/test_core_net_freebsd.odin +++ b/tests/core/net/test_core_net_freebsd.odin @@ -17,9 +17,6 @@ import "core:net" import "core:time" import "core:testing" -ENDPOINT_DUPLICATE_BINDING := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 11000} -ENDPOINT_EPIPE_TEST := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 11001} - @test test_duplicate_binding :: proc(t: ^testing.T) { // FreeBSD has the capacity to permit multiple processes and sockets to @@ -35,11 +32,16 @@ test_duplicate_binding :: proc(t: ^testing.T) { if !testing.expect_value(t, err_set1, nil) { return } - err_bind1 := net.bind(tcp_socket1, ENDPOINT_DUPLICATE_BINDING) + err_bind1 := net.bind(tcp_socket1, {net.IP4_Loopback, 0}) if !testing.expect_value(t, err_bind1, nil) { return } + ep, err_bound := net.bound_endpoint(tcp_socket1) + if !testing.expect_value(t, err_bound, nil) { + return + } + raw_socket2, err_create2 := net.create_socket(.IP4, .TCP) if !testing.expect_value(t, err_create2, nil) { return @@ -50,7 +52,7 @@ test_duplicate_binding :: proc(t: ^testing.T) { if !testing.expect_value(t, err_set2, nil) { return } - err_bind2 := net.bind(tcp_socket2, ENDPOINT_DUPLICATE_BINDING) + err_bind2 := net.bind(tcp_socket2, ep) if !testing.expect_value(t, err_bind2, nil) { return } @@ -60,13 +62,18 @@ test_duplicate_binding :: proc(t: ^testing.T) { test_sigpipe_bypass :: proc(t: ^testing.T) { // If the internals aren't working as expected, this test will fail by raising SIGPIPE. - server_socket, listen_err := net.listen_tcp(ENDPOINT_EPIPE_TEST) + server_socket, listen_err := net.listen_tcp({net.IP4_Loopback, 0}) if !testing.expect_value(t, listen_err, nil) { return } defer net.close(server_socket) - client_socket, dial_err := net.dial_tcp(ENDPOINT_EPIPE_TEST) + ep, bound_err := net.bound_endpoint(server_socket) + if !testing.expect_value(t, bound_err, nil) { + return + } + + client_socket, dial_err := net.dial_tcp(ep) if !testing.expect_value(t, dial_err, nil) { return } @@ -80,7 +87,7 @@ test_sigpipe_bypass :: proc(t: ^testing.T) { data := "Hellope!" bytes_written, err_send := net.send(client_socket, transmute([]u8)data) - if !testing.expect_value(t, err_send, net.TCP_Send_Error.Cannot_Send_More_Data) { + if !testing.expect_value(t, err_send, net.TCP_Send_Error.Connection_Closed) { return } if !testing.expect_value(t, bytes_written, 0) {