فهرست منبع

net: rework errors to be cross-platform

Laytan Laats 5 ماه پیش
والد
کامیت
ff7d55a8e1

+ 6 - 8
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,

+ 275 - 0
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,
+}

+ 200 - 160
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),
+_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
+	}
+}
 
-	// NOTE: No, really. Presumably this means something different for nonblocking sockets...
-	Timeout           = c.int(posix.EWOULDBLOCK),
+_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
+	}
 }
 
-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.
+_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
+	}
 }
 
-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.
+_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
+	}
 }
 
-// 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.
+_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_Manner :: enum c.int {
-	Receive = c.int(posix.SHUT_RD),
-	Send    = c.int(posix.SHUT_WR),
-	Both    = c.int(posix.SHUT_RDWR),
+_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
+	}
 }
 
-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),
+_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
+	}
 }
 
-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),
+_shutdown_error :: proc() -> Shutdown_Error {
+	#partial switch posix.errno() {
+	case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
+		return .Invalid_Argument
+	case:
+		return .Unknown
+	}
 }
 
-Set_Blocking_Error :: enum c.int {
-	None = 0,
+_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
+	}
+}
 
-	// TODO: Add errors for `set_blocking`
+_set_blocking_error :: proc() -> Set_Blocking_Error {
+	#partial switch posix.errno() {
+	case .EBADF:
+		return .Invalid_Argument
+	case:
+		return .Unknown
+	}
 }

+ 238 - 169
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)
+}
+
+_last_platform_error_string :: proc() -> string {
+	description, _ := reflect.enum_name_from_value(_last_error)
+	return description
+}
+
+_set_last_platform_error :: proc(err: i32) {
+	_last_error = freebsd.Errno(err)
 }
 
-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.
+_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
+	}
 }
 
-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.
+_dial_error :: proc(errno: freebsd.Errno) -> Dial_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#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
+	}
 }
 
-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,
+_bind_error :: proc(errno: freebsd.Errno) -> Bind_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#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
+	}
 }
 
-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,
-
-	// 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,
+_listen_error :: proc(errno: freebsd.Errno) -> Listen_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#partial switch errno {
+	case .EBADF, .ENOTSOCK:
+		return .Invalid_Argument
+	case .EDESTADDRREQ, .EOPNOTSUPP:
+		return .Unsupported_Socket
+	case .EINVAL:
+		return .Already_Connected
+	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,
-
-	// 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,
+_accept_error :: proc(errno: freebsd.Errno) -> Accept_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#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
+	}
 }
 
-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,
-
-	// 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,
+_tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#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
+	}
 }
 
-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,
-
-	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,
+_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
+	}
 }
 
-// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware.
-UDP_Send_Error :: distinct TCP_Send_Error
+_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
+	}
+}
 
-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,
+_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 :: 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,
+_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 :: 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,
+_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 :: enum c.int {
-	None             = 0,
-	Not_Descriptor   = cast(c.int)freebsd.Errno.EBADF,
-	Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
+_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
+	}
 }

+ 236 - 148
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),
-
-	// 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...
+_bind_error :: proc(errno: linux.Errno) -> Bind_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#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
+
+	#partial switch errno {
+	case .EBADF, .ENOTSOCK:
+		return .Invalid_Argument
+	case .EDESTADDRREQ, .EOPNOTSUPP:
+		return .Unsupported_Socket
+	case .EINVAL:
+		return .Already_Connected
+	case:
+		return .Unknown
+	}
+}
+
+_accept_error :: proc(errno: linux.Errno) -> Accept_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#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
+	}
+}
 
-	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).
+_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
+	assert(errno != nil)
+	_last_error = errno
 
-	// 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, .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
+	}
 }
 
-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),
-
-	// 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.
+_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
+	}
 }
 
-// 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.
-
-	// 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.
+_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
+	}
 }
 
-// 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_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
+	}
 }
 
-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),
+_shutdown_error :: proc(errno: linux.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 :: 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),
+_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 :: enum c.int {
-	None = 0,
+_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_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))
-}
+	#partial switch errno {
+	case .EBADF:
+		return .Invalid_Argument
+	case:
+		return .Unknown
+	}
+}

+ 20 - 0
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
+}

+ 210 - 218
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,
-
-	Network_Subsystem_Failure          = win.WSAENETDOWN,
-	Blocking_Call_In_Progress          = win.WSAEINPROGRESS,
-	Not_Socket                         = win.WSAENOTSOCK,
+_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
+	}
+}
 
-	// TODO: are those errors possible?
-	Network_Subsystem_Not_Initialized  = win.WSAENOTINITIALISED,
-	Invalid_Argument_Pointer           = win.WSAEFAULT,
-}
+_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
+	}
+}

+ 1 - 1
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)
 }
 

+ 2 - 2
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

+ 2 - 2
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)
 

+ 2 - 2
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, {}
-}
+}

+ 7 - 6
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()
-}
+}

+ 44 - 13
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)
 }

+ 32 - 51
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

+ 40 - 51
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

+ 43 - 42
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
 }

+ 88 - 37
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

+ 1 - 0
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)

+ 15 - 8
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) {