Merge pull request #5007 from laytan/net-errors-overhaul

net: rework errors to be cross-platform
This commit is contained in:
Jeroen van Rijn
2025-04-05 17:53:10 +02:00
committed by GitHub
19 changed files with 1457 additions and 913 deletions
+6 -8
View File
@@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
Maybe :: runtime.Maybe Maybe :: runtime.Maybe
Network_Error :: union #shared_nil { Network_Error :: union #shared_nil {
General_Error,
Platform_Error,
Create_Socket_Error, Create_Socket_Error,
Dial_Error, Dial_Error,
Listen_Error, Listen_Error,
@@ -65,6 +63,7 @@ Network_Error :: union #shared_nil {
TCP_Recv_Error, TCP_Recv_Error,
UDP_Recv_Error, UDP_Recv_Error,
Shutdown_Error, Shutdown_Error,
Interfaces_Error,
Socket_Option_Error, Socket_Option_Error,
Set_Blocking_Error, Set_Blocking_Error,
Parse_Endpoint_Error, Parse_Endpoint_Error,
@@ -74,14 +73,13 @@ Network_Error :: union #shared_nil {
#assert(size_of(Network_Error) == 8) #assert(size_of(Network_Error) == 8)
General_Error :: enum u32 { Interfaces_Error :: enum u32 {
None = 0, None,
Unable_To_Enumerate_Network_Interfaces = 1, 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 { Parse_Endpoint_Error :: enum u32 {
None = 0, None = 0,
Bad_Port = 1, Bad_Port = 1,
+275
View File
@@ -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,
}
+201 -161
View File
@@ -20,192 +20,232 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import "core:sys/posix" import "core:sys/posix"
@(private) _last_platform_error :: proc() -> i32 {
ESHUTDOWN :: 58 return i32(posix.errno())
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),
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(posix.errno())
Port_Required = -1, // Attempted to dial an endpointing without a port being set. return description
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),
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, posix.errno(posix.Errno(err))
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.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc() -> Create_Socket_Error {
None = 0, #partial switch posix.errno() {
Address_In_Use = c.int(posix.EADDRINUSE), case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE:
Already_Connected = c.int(posix.EISCONN), return .Insufficient_Resources
No_Socket_Descriptors_Available = c.int(posix.EMFILE), case .EAFNOSUPPORT, .EPROTOTYPE:
No_Buffer_Space_Available = c.int(posix.ENOBUFS), return .Invalid_Argument
Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), case .EACCES:
Not_Socket = c.int(posix.ENOTSOCK), return .Insufficient_Permissions
Listening_Not_Supported_For_This_Socket = c.int(posix.EOPNOTSUPP), case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc() -> Dial_Error {
None = 0, #partial switch posix.errno() {
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. case .ENOBUFS:
Reset = c.int(posix.ECONNRESET), return .Insufficient_Resources
Not_Listening = c.int(posix.EINVAL), case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL:
No_Socket_Descriptors_Available_For_Client_Socket = c.int(posix.EMFILE), return .Invalid_Argument
No_Buffer_Space_Available = c.int(posix.ENOBUFS), case .EISCONN:
Not_Socket = c.int(posix.ENOTSOCK), return .Already_Connected
Not_Connection_Oriented_Socket = c.int(posix.EOPNOTSUPP), case .EALREADY:
return .Already_Connecting
// TODO: we may need special handling for this; maybe make a socket a struct with metadata? case .EADDRINUSE:
Would_Block = c.int(posix.EWOULDBLOCK), 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 { _bind_error :: proc() -> Bind_Error {
None = 0, #partial switch posix.errno() {
Shutdown = ESHUTDOWN, case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP:
Not_Connected = c.int(posix.ENOTCONN), return .Invalid_Argument
case .EINVAL:
// TODO(tetra): Is this error actually possible here? return .Already_Bound
Connection_Broken = c.int(posix.ENETRESET), case .EACCES:
Not_Socket = c.int(posix.ENOTSOCK), return .Insufficient_Permissions_For_Address
Aborted = c.int(posix.ECONNABORTED), case .EADDRINUSE:
return .Address_In_Use
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? case:
Connection_Closed = c.int(posix.ECONNRESET), return .Unknown
Offline = c.int(posix.ENETDOWN), }
Host_Unreachable = c.int(posix.EHOSTUNREACH),
Interrupted = c.int(posix.EINTR),
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(posix.EWOULDBLOCK),
} }
UDP_Recv_Error :: enum c.int { _listen_error :: proc() -> Listen_Error {
None = 0, #partial switch posix.errno() {
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. case .EBADF, .ENOTSOCK:
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. return .Invalid_Argument
Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor. case .EDESTADDRREQ, .EOPNOTSUPP:
Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory. return .Unsupported_Socket
Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). case .EINVAL:
return .Already_Connected
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. case:
// NOTE: No, really. Presumably this means something different for nonblocking sockets... return .Unknown
Timeout = c.int(posix.EWOULDBLOCK), }
Socket_Not_Bound = c.int(posix.EINVAL), // The socket must be bound for this operation, but isn't.
} }
TCP_Send_Error :: enum c.int { _accept_error :: proc() -> Accept_Error {
None = 0, #partial switch posix.errno() {
case .EMFILE, .ENFILE, .ENOMEM:
Aborted = c.int(posix.ECONNABORTED), return .Insufficient_Resources
Connection_Closed = c.int(posix.ECONNRESET), case .EBADF, .ENOTSOCK, .EFAULT:
Not_Connected = c.int(posix.ENOTCONN), return .Invalid_Argument
Shutdown = ESHUTDOWN, case .EOPNOTSUPP:
return .Unsupported_Socket
// The send queue was full. case .ECONNABORTED:
// This is usually a transient issue. return .Aborted
// case .EWOULDBLOCK:
// This also shouldn't normally happen on Linux, as data is dropped if it return .Would_Block
// doesn't fit in the send queue. case .EINTR:
No_Buffer_Space_Available = c.int(posix.ENOBUFS), return .Interrupted
Offline = c.int(posix.ENETDOWN), case:
Host_Unreachable = c.int(posix.EHOSTUNREACH), return .Unknown
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.
} }
// TODO _tcp_recv_error :: proc() -> TCP_Recv_Error {
UDP_Send_Error :: enum c.int { #partial switch posix.errno() {
None = 0, case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP:
Message_Too_Long = c.int(posix.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent. return .Invalid_Argument
case .ENOBUFS:
// TODO: not sure what the exact circumstances for this is yet return .Insufficient_Resources
Network_Unreachable = c.int(posix.ENETUNREACH), case .ENOTCONN:
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. return .Not_Connected
case .ECONNRESET:
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. return .Connection_Closed
// NOTE: No, really. Presumably this means something different for nonblocking sockets... case .ETIMEDOUT:
Timeout = c.int(posix.EWOULDBLOCK), return .Timeout
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. case .EAGAIN:
Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor. return .Would_Block
Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory. case .EINTR:
Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Interrupted
case:
// The send queue was full. return .Unknown
// 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.
} }
Shutdown_Manner :: enum c.int { _udp_recv_error :: proc() -> UDP_Recv_Error {
Receive = c.int(posix.SHUT_RD), #partial switch posix.errno() {
Send = c.int(posix.SHUT_WR), case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE:
Both = c.int(posix.SHUT_RDWR), return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _tcp_send_error :: proc() -> TCP_Send_Error {
None = 0, #partial switch posix.errno() {
Aborted = c.int(posix.ECONNABORTED), case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP:
Reset = c.int(posix.ECONNRESET), return .Invalid_Argument
Offline = c.int(posix.ENETDOWN), case .ENOBUFS:
Not_Connected = c.int(posix.ENOTCONN), return .Insufficient_Resources
Not_Socket = c.int(posix.ENOTSOCK), case .ECONNRESET, .EPIPE:
Invalid_Manner = c.int(posix.EINVAL), return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Socket_Option_Error :: enum c.int { _udp_send_error :: proc() -> UDP_Send_Error {
None = 0, #partial switch posix.errno() {
Offline = c.int(posix.ENETDOWN), case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ:
Timeout_When_Keepalive_Set = c.int(posix.ENETRESET), return .Invalid_Argument
Invalid_Option_For_Socket = c.int(posix.ENOPROTOOPT), case .ENOBUFS, .ENOMEM:
Reset_When_Keepalive_Set = c.int(posix.ENOTCONN), return .Insufficient_Resources
Not_Socket = c.int(posix.ENOTSOCK), case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Set_Blocking_Error :: enum c.int { _shutdown_error :: proc() -> Shutdown_Error {
None = 0, #partial switch posix.errno() {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
// TODO: Add errors for `set_blocking` _socket_option_error :: proc() -> Socket_Option_Error {
#partial switch posix.errno() {
case .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EISCONN:
return .Invalid_Socket
case .EINVAL, .ENOPROTOOPT:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc() -> Set_Blocking_Error {
#partial switch posix.errno() {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
} }
+234 -165
View File
@@ -20,198 +20,267 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import "core:sys/freebsd" import "core:sys/freebsd"
Create_Socket_Error :: enum c.int { @(private="file", thread_local)
None = 0, _last_error: freebsd.Errno
Access_Denied = cast(c.int)freebsd.Errno.EACCES,
Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, _last_platform_error :: proc() -> i32 {
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, return i32(_last_error)
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,
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(_last_error)
Port_Required = -1, return description
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.
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, _last_error = freebsd.Errno(err)
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.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ,
Already_Connected = cast(c.int)freebsd.Errno.EINVAL, #partial switch errno {
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP, return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc(errno: freebsd.Errno) -> Dial_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
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. #partial switch errno {
No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN, case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN:
// `Would_Block` alias for `core:net` tests. return .Invalid_Argument
Would_Block = cast(c.int)freebsd.Errno.EAGAIN, case .EISCONN:
return .Already_Connected
New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED, 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 { _bind_error :: proc(errno: freebsd.Errno) -> Bind_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
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(), #partial switch errno {
// but I'm including them for completeness's sake. case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE, return .Insufficient_Resources
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, case .EBADF:
return .Invalid_Argument
Timeout = cast(c.int)freebsd.Errno.EAGAIN, case .EINVAL:
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, return .Already_Bound
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
} }
UDP_Recv_Error :: enum c.int { _listen_error :: proc(errno: freebsd.Errno) -> Listen_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
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(), #partial switch errno {
// but I'm including them for completeness's sake. case .EBADF, .ENOTSOCK:
Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE, return .Invalid_Argument
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
Timeout = cast(c.int)freebsd.Errno.EAGAIN, case .EINVAL:
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, return .Already_Connected
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, case:
return .Unknown
}
} }
TCP_Send_Error :: enum c.int { _accept_error :: proc(errno: freebsd.Errno) -> Accept_Error {
None = 0, assert(errno != nil)
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, _last_error = errno
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, #partial switch errno {
case .EMFILE, .ENFILE:
/* The socket is marked non-blocking, or MSG_DONTWAIT is return .Insufficient_Resources
specified, and the requested operation would block. */ case .EBADF, .ENOTSOCK, .EFAULT:
Would_Block = cast(c.int)freebsd.Errno.EAGAIN, return .Invalid_Argument
case .EINVAL:
/* NOTE: This error arises for two distinct reasons: return .Not_Listening
case .ECONNABORTED:
1. The system was unable to allocate an internal buffer. return .Aborted
The operation may succeed when buffers become available. case .EWOULDBLOCK:
return .Would_Block
2. The output queue for a network interface was full. case .EINTR:
This generally indicates that the interface has stopped return .Interrupted
sending, but may be caused by transient congestion. case:
*/ return .Unknown
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,
} }
// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware. _tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error {
UDP_Send_Error :: distinct TCP_Send_Error assert(errno != nil)
_last_error = errno
Shutdown_Manner :: enum c.int { #partial switch errno {
Receive = cast(c.int)freebsd.Shutdown_Method.RD, case .EBADF, .ENOTSOCK, .EFAULT:
Send = cast(c.int)freebsd.Shutdown_Method.WR, return .Invalid_Argument
Both = cast(c.int)freebsd.Shutdown_Method.RDWR, case .ENOTCONN:
return .Not_Connected
case .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, #partial switch errno {
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Socket_Option_Error :: enum c.int { _tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error {
None = 0, assert(errno != nil)
Value_Out_Of_Range = -1, _last_error = errno
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, #partial switch errno {
Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT, case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, return .Invalid_Argument
// This error can arise for many different reasons. case .ENOBUFS:
Invalid_Value = cast(c.int)freebsd.Errno.EINVAL, return .Insufficient_Resources
System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM, case .ECONNRESET, .EPIPE:
Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS, return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Set_Blocking_Error :: enum c.int { _udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT:
return .Invalid_Option
case .EINVAL, .EFAULT:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTTY:
return .Invalid_Argument
case:
return .Unknown
}
} }
+233 -145
View File
@@ -21,181 +21,269 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import "core:sys/linux" import "core:sys/linux"
Create_Socket_Error :: enum c.int { @(private="file", thread_local)
None = 0, _last_error: linux.Errno
Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), _last_platform_error :: proc() -> i32 {
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), return i32(_last_error)
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),
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(_last_error)
Port_Required = -1, return description
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),
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, _last_error = linux.Errno(err)
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.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error {
None = 0, assert(errno != nil)
Address_In_Use = c.int(linux.Errno.EADDRINUSE), _last_error = errno
Already_Connected = c.int(linux.Errno.EISCONN),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), #partial switch errno {
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), return .Insufficient_Resources
Not_Socket = c.int(linux.Errno.ENOTSOCK), case .EAFNOSUPPORT, .EPROTOTYPE:
Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP), return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc(errno: linux.Errno) -> Dial_Error {
None = 0, assert(errno != nil)
Not_Listening = c.int(linux.Errno.EINVAL), _last_error = errno
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),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata? #partial switch errno {
Would_Block = c.int(linux.Errno.EWOULDBLOCK), 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 { _bind_error :: proc(errno: linux.Errno) -> Bind_Error {
None = 0, assert(errno != nil)
Shutdown = c.int(linux.Errno.ESHUTDOWN), _last_error = errno
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? #partial switch errno {
Connection_Closed = c.int(linux.Errno.ECONNRESET), case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
Offline = c.int(linux.Errno.ENETDOWN), return .Insufficient_Resources
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), case .EINVAL:
Interrupted = c.int(linux.Errno.EINTR), return .Already_Bound
Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... 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 { _listen_error :: proc(errno: linux.Errno) -> Listen_Error {
None = 0, assert(errno != nil)
_last_error = errno
Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. #partial switch errno {
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. case .EBADF, .ENOTSOCK:
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. return .Invalid_Argument
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. case .EDESTADDRREQ, .EOPNOTSUPP:
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Unsupported_Socket
case .EINVAL:
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout. return .Already_Connected
// NOTE: No, really. Presumably this means something different for nonblocking sockets... case:
Timeout = c.int(linux.Errno.EWOULDBLOCK), return .Unknown
Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't. }
} }
TCP_Send_Error :: enum c.int { _accept_error :: proc(errno: linux.Errno) -> Accept_Error {
None = 0, assert(errno != nil)
Aborted = c.int(linux.Errno.ECONNABORTED), _last_error = errno
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. #partial switch errno {
// This is usually a transient issue. case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM:
// return .Insufficient_Resources
// This also shouldn't normally happen on Linux, as data is dropped if it case .EBADF, .ENOTSOCK, .EFAULT:
// doesn't fit in the send queue. return .Invalid_Argument
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), case .EINVAL:
Offline = c.int(linux.Errno.ENETDOWN), return .Not_Listening
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), case .ECONNABORTED:
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Aborted
Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. case .EWOULDBLOCK:
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
// TODO _tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
UDP_Send_Error :: enum c.int { assert(errno != nil)
None = 0, _last_error = errno
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 #partial switch errno {
Network_Unreachable = c.int(linux.Errno.ENETUNREACH), case .EBADF, .ENOTSOCK, .EFAULT:
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. return .Invalid_Argument
case .ENOTCONN:
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. return .Not_Connected
// NOTE: No, really. Presumably this means something different for nonblocking sockets... case .ECONNREFUSED:
Timeout = c.int(linux.Errno.EWOULDBLOCK), return .Connection_Closed
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. case .ETIMEDOUT:
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. return .Timeout
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. case .EAGAIN:
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Would_Block
case .EINTR:
// The send queue was full. return .Interrupted
// This is usually a transient issue. case:
// return .Unknown
// 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.
} }
// TODO(flysand): slight regression _udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error {
Shutdown_Manner :: enum c.int { assert(errno != nil)
Receive = c.int(linux.Shutdown_How.RD), _last_error = errno
Send = c.int(linux.Shutdown_How.WR),
Both = c.int(linux.Shutdown_How.RDWR), #partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNREFUSED, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error {
None = 0, assert(errno != nil)
Aborted = c.int(linux.Errno.ECONNABORTED), _last_error = errno
Reset = c.int(linux.Errno.ECONNRESET),
Offline = c.int(linux.Errno.ENETDOWN), #partial switch errno {
Not_Connected = c.int(linux.Errno.ENOTCONN), case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
Not_Socket = c.int(linux.Errno.ENOTSOCK), return .Invalid_Argument
Invalid_Manner = c.int(linux.Errno.EINVAL), case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Socket_Option_Error :: enum c.int { _udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error {
None = 0, assert(errno != nil)
Offline = c.int(linux.Errno.ENETDOWN), _last_error = errno
Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET),
Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT), #partial switch errno {
Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN), case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
Not_Socket = c.int(linux.Errno.ENOTSOCK), return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Set_Blocking_Error :: enum c.int { _shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
None = 0, assert(errno != nil)
_last_error = errno
// TODO: add errors occuring on followig calls: #partial switch errno {
// flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0) case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
// linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK)) return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT, .EINVAL:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
} }
+20
View File
@@ -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
}
+209 -217
View File
@@ -20,250 +20,242 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import win "core:sys/windows" import win "core:sys/windows"
Create_Socket_Error :: enum c.int { _last_platform_error :: proc() -> i32 {
None = 0, return i32(win.WSAGetLastError())
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,
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError()))
Port_Required = -1, return description
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?
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, win.WSASetLastError(err)
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.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc() -> Create_Socket_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Address_In_Use = win.WSAEADDRINUSE, case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT:
Already_Connected = win.WSAEISCONN, return .Network_Unreachable
No_Socket_Descriptors_Available = win.WSAEMFILE, case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Invalid_Argument
Nonlocal_Address = win.WSAEADDRNOTAVAIL, case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT:
Not_Socket = win.WSAENOTSOCK, return .Insufficient_Resources
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc() -> Dial_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Not_Listening = win.WSAEINVAL, case .WSANOTINITIALISED, .WSAENETDOWN:
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, return .Network_Unreachable
No_Buffer_Space_Available = win.WSAENOBUFS, case .WSAEADDRINUSE:
Not_Socket = win.WSAENOTSOCK, return .Address_In_Use
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, case .WSAEINTR:
return .Interrupted
// TODO: we may need special handling for this; maybe make a socket a struct with metadata? case .WSAEWOULDBLOCK:
Would_Block = win.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 { _bind_error :: proc() -> Bind_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSANOTINITIALISED, .WSAENETDOWN:
Not_Connected = win.WSAENOTCONN, return .Network_Unreachable
Bad_Buffer = win.WSAEFAULT, case .WSAEADDRINUSE:
Keepalive_Failure = win.WSAENETRESET, return .Address_In_Use
Not_Socket = win.WSAENOTSOCK, case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK:
Shutdown = win.WSAESHUTDOWN, return .Invalid_Argument
Would_Block = win.WSAEWOULDBLOCK, case:
Aborted = win.WSAECONNABORTED, return .Unknown
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,
} }
UDP_Recv_Error :: enum c.int { _listen_error :: proc() -> Listen_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSANOTINITIALISED, .WSAENETDOWN:
Aborted = win.WSAECONNABORTED, return .Network_Unreachable
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. case .WSAEMFILE, .WSAENOBUFS:
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. return .Insufficient_Resources
Shutdown = win.WSAESHUTDOWN, case .WSAEADDRINUSE:
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set. return .Address_In_Use
Bad_Buffer = win.WSAEFAULT, case .WSAEINPROGRESS, .WSAENOTSOCK:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Invalid_Argument
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. case .WSAEISCONN:
Would_Block = win.WSAEWOULDBLOCK, return .Already_Connected
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. case .WSAEOPNOTSUPP, .WSAEINVAL:
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. return .Unsupported_Socket
Timeout = win.WSAETIMEDOUT, case:
return .Unknown
// 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.
} }
// TODO: consider merging some errors to make handling them easier _accept_error :: proc() -> Accept_Error {
// TODO: verify once more what errors to actually expose #partial switch win.System_Error(win.WSAGetLastError()) {
TCP_Send_Error :: enum c.int { case .WSANOTINITIALISED, .WSAENETDOWN:
None = 0, return .Network_Unreachable
case .WSAEMFILE, .WSAENOBUFS:
Aborted = win.WSAECONNABORTED, return .Insufficient_Resources
Not_Connected = win.WSAENOTCONN, case .WSAECONNRESET:
Shutdown = win.WSAESHUTDOWN, return .Aborted
Connection_Closed = win.WSAECONNRESET, case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Invalid_Argument
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSAEINTR:
Host_Unreachable = win.WSAEHOSTUNREACH, return .Interrupted
Would_Block = win.WSAEWOULDBLOCK, case .WSAEINVAL:
return .Not_Listening
// TODO: verify possible, as not mentioned in docs case .WSAEWOULDBLOCK:
Offline = win.WSAENETUNREACH, return .Would_Block
Timeout = win.WSAETIMEDOUT, case .WSAEOPNOTSUPP:
return .Unsupported_Socket
// A broadcast address was specified, but the .Broadcast socket option isn't set. case:
Broadcast_Disabled = win.WSAEACCES, return .Unknown
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.
} }
UDP_Send_Error :: enum c.int { _tcp_recv_error :: proc() -> TCP_Recv_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
Aborted = win.WSAECONNABORTED, case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP:
Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size. return .Invalid_Argument
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. case .WSAENOTCONN:
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set. return .Not_Connected
Broadcast_Disabled = win.WSAEACCES, case .WSAEINTR:
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation. return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
// TODO: not functionally different from Reset; merge? return .Connection_Closed
Keepalive_Failure = win.WSAENETRESET, case .WSAEWOULDBLOCK:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Would_Block
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. case .WSAETIMEDOUT:
return .Timeout
// This socket is unidirectional and cannot be used to send any data. case:
// TODO: verify possible; decide whether to keep if not return .Unknown
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,
} }
Shutdown_Manner :: enum c.int { _udp_recv_error :: proc() -> UDP_Recv_Error {
Receive = win.SD_RECEIVE, #partial switch win.System_Error(win.WSAGetLastError()) {
Send = win.SD_SEND, case .WSANOTINITIALISED, .WSAENETDOWN:
Both = win.SD_BOTH, 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 { _tcp_send_error :: proc() -> TCP_Send_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Aborted = win.WSAECONNABORTED, case .WSANOTINITIALISED, .WSAENETDOWN:
Reset = win.WSAECONNRESET, return .Network_Unreachable
Offline = win.WSAENETDOWN, case .WSAENOBUFS:
Not_Connected = win.WSAENOTCONN, return .Insufficient_Resources
Not_Socket = win.WSAENOTSOCK, case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL:
Invalid_Manner = win.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 { _udp_send_error :: proc() -> UDP_Send_Error {
// bool: Whether the address that this socket is bound to can be reused by other sockets. #partial switch win.System_Error(win.WSAGetLastError()) {
// This allows you to bypass the cooldown period if a program dies while the socket is bound. case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH:
Reuse_Address = win.SO_REUSEADDR, return .Network_Unreachable
case .WSAENOBUFS:
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket. return .Insufficient_Resources
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ:
return .Invalid_Argument
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding case .WSAEINTR:
Keep_Alive = win.SO_KEEPALIVE, return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. return .Connection_Refused
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, case .WSAEWOULDBLOCK:
return .Would_Block
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. case .WSAETIMEDOUT:
Dont_Linger = win.SO_DONTLINGER, return .Timeout
case:
// 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. return .Unknown
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,
} }
Socket_Option_Error :: enum c.int { _shutdown_error :: proc() -> Shutdown_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Linger_Only_Supports_Whole_Seconds = 1, case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
// The given value is too big or small to be given to the OS. case .WSAECONNABORTED, .WSAECONNRESET:
Value_Out_Of_Range, return .Connection_Closed
case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK:
Network_Subsystem_Failure = win.WSAENETDOWN, return .Invalid_Argument
Timeout_When_Keepalive_Set = win.WSAENETRESET, case:
Invalid_Option_For_Socket = win.WSAENOPROTOOPT, return .Unknown
Reset_When_Keepalive_Set = win.WSAENOTCONN, }
Not_Socket = win.WSAENOTSOCK,
} }
Set_Blocking_Error :: enum c.int { _socket_option_error :: proc() -> Socket_Option_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINVAL:
return .Invalid_Value
case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK:
return .Invalid_Socket
case .WSAENOPROTOOPT:
return .Invalid_Option
case:
return .Unknown
}
}
Network_Subsystem_Failure = win.WSAENETDOWN, _set_blocking_error :: proc() -> Set_Blocking_Error {
Blocking_Call_In_Progress = win.WSAEINPROGRESS, #partial switch win.System_Error(win.WSAGetLastError()) {
Not_Socket = win.WSAENOTSOCK, case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
// TODO: are those errors possible? case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT:
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED, return .Invalid_Argument
Invalid_Argument_Pointer = win.WSAEFAULT, case:
return .Unknown
}
} }
+1 -1
View File
@@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3
/* /*
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties. `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) return _enumerate_interfaces(allocator)
} }
+2 -2
View File
@@ -26,7 +26,7 @@ import "core:sys/posix"
foreign import lib "system:System.framework" foreign import lib "system:System.framework"
@(private) @(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 context.allocator = allocator
head: ^ifaddrs head: ^ifaddrs
@@ -47,7 +47,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
iface.adapter_name = key_ptr^ iface.adapter_name = key_ptr^
} }
if mem_err != nil { if mem_err != nil {
return {}, .Unable_To_Enumerate_Network_Interfaces return {}, .Allocation_Failure
} }
address: Address address: Address
+2 -2
View File
@@ -25,7 +25,7 @@ import "core:strings"
import "core:sys/freebsd" import "core:sys/freebsd"
@(private) @(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 // This is a simplified implementation of `getifaddrs` from the FreeBSD
// libc using only Odin and syscalls. // libc using only Odin and syscalls.
context.allocator = allocator context.allocator = allocator
@@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// Allocate and get the entries. // Allocate and get the entries.
buf, alloc_err := make([]byte, needed) buf, alloc_err := make([]byte, needed)
if alloc_err != nil { if alloc_err != nil {
return nil, .Unable_To_Enumerate_Network_Interfaces return nil, .Allocation_Failure
} }
defer delete(buf) defer delete(buf)
+1 -1
View File
@@ -30,7 +30,7 @@ package net
// NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html // NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html
// apparently musl libc uses this to enumerate network interfaces // apparently musl libc uses this to enumerate network interfaces
@(private) @(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 context.allocator = allocator
// head: ^os.ifaddrs // head: ^os.ifaddrs
+6 -5
View File
@@ -23,7 +23,7 @@ package net
import sys "core:sys/windows" import sys "core:sys/windows"
import strings "core:strings" 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 context.allocator = allocator
buf: []u8 buf: []u8
@@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
case 0: case 0:
break gaa break gaa
case: 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) _interfaces := make([dynamic]Network_Interface, 0, allocator)
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { 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) 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) 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) 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{ interface := Network_Interface{
adapter_name = strings.clone(string(adapter.AdapterName)), adapter_name = strings.clone(string(adapter.AdapterName)),
+44 -13
View File
@@ -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. `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`. 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) { 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 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. `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. 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) { 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 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. 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) { dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
if host.port == 0 { 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. Expects the `target` as a Host_OrEndpoint.
Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`. 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) { 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 { switch t in target {
@@ -87,11 +95,20 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa
unreachable() 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) { 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) 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) { dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
return _dial_tcp_from_endpoint(endpoint, options) return _dial_tcp_from_endpoint(endpoint, options)
} }
@@ -105,11 +122,11 @@ dial_tcp :: proc{
dial_tcp_from_host_or_endpoint, 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) 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) 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. 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 sock := create_socket(family, .UDP) or_return
socket = sock.(UDP_Socket) socket = sock.(UDP_Socket)
return 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. 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. 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) { make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
if bound_address == nil { if bound_address == nil {
@@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP
return 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) { listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
assert(backlog > 0 && backlog < int(max(i32))) 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. 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) 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) return _accept_tcp(socket, options)
} }
@@ -162,11 +186,11 @@ close :: proc(socket: Any_Socket) {
_close(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) 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) 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 Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it
will always return `nil`. will always return `nil`.
Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error`
*/ */
recv_any :: proc(socket: Any_Socket, buf: []byte) -> ( recv_any :: proc(socket: Any_Socket, buf: []byte) -> (
bytes_read: int, bytes_read: int,
@@ -197,7 +223,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any}
Repeatedly sends data until the entire buffer is sent. 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. 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) 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. 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. 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) 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) -> ( send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> (
bytes_written: int, bytes_written: int,
err: Network_Error, 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} 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) 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) 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) return _set_blocking(socket, should_block)
} }
+32 -51
View File
@@ -37,8 +37,14 @@ Socket_Option :: enum c.int {
Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO), 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) @(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_type: posix.Sock
c_protocol: posix.Protocol c_protocol: posix.Protocol
c_family: posix.AF 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) sock := posix.socket(c_family, c_type, c_protocol)
if sock < 0 { if sock < 0 {
err = Create_Socket_Error(posix.errno()) err = _create_socket_error()
return return
} }
@@ -88,28 +94,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
errno := posix.errno() err = _dial_error()
close(skt) close(skt)
return {}, Dial_Error(errno)
} }
return 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) @(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) sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt) s := any_socket_to_socket(skt)
if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
errno := posix.errno() err = _bind_error()
if errno == .EACCES && ep.port <= MAX_PRIVILEGED_PORT {
err = .Privileged_Port_Without_Root
} else {
err = Bind_Error(errno)
}
} }
return 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 // bypass the cooldown period, and allow the next run of the program to
// use the same address immediately. // 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)
set_option(sock, .Reuse_Address, true) or_return
bind(sock, interface_endpoint) or_return bind(sock, interface_endpoint) or_return
if posix.listen(posix.FD(skt), i32(backlog)) != .OK { if posix.listen(posix.FD(skt), i32(backlog)) != .OK {
err = Listen_Error(posix.errno()) err = _listen_error()
return
} }
return return
} }
@(private) @(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: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr)) addr_len := posix.socklen_t(size_of(addr))
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { 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 return
} }
@@ -155,12 +150,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(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: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr)) addr_len := posix.socklen_t(size_of(addr))
client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len) client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len)
if client_sock < 0 { if client_sock < 0 {
err = Accept_Error(posix.errno()) err = _accept_error()
return return
} }
@@ -176,14 +171,14 @@ _close :: proc(skt: Any_Socket) {
} }
@(private) @(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 { if len(buf) <= 0 {
return return
} }
res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {}) res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {})
if res < 0 { if res < 0 {
err = TCP_Recv_Error(posix.errno()) err = _tcp_recv_error()
return return
} }
@@ -191,7 +186,7 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ
} }
@(private) @(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 { if len(buf) <= 0 {
return 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)) fromsize := posix.socklen_t(size_of(from))
res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize) res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize)
if res < 0 { if res < 0 {
err = UDP_Recv_Error(posix.errno()) err = _udp_recv_error()
return return
} }
@@ -210,20 +205,13 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
} }
@(private) @(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) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] remaining := buf[bytes_written:][:limit]
res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}) res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL})
if res < 0 { if res < 0 {
errno := posix.errno() err = _tcp_send_error()
if errno == .EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = TCP_Send_Error.Connection_Closed
return
}
err = TCP_Send_Error(errno)
return return
} }
@@ -233,21 +221,14 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net
} }
@(private) @(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) toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(1<<31, len(buf) - bytes_written) limit := min(1<<31, len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] 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)) res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len))
if res < 0 { if res < 0 {
errno := posix.errno() err = _udp_send_error()
if errno == .EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = UDP_Send_Error.Not_Socket
return
}
err = UDP_Send_Error(errno)
return return
} }
@@ -257,16 +238,16 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
} }
@(private) @(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) s := any_socket_to_socket(skt)
if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK { if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK {
err = Shutdown_Error(posix.errno()) err = _shutdown_error()
} }
return return
} }
@(private) @(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 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; // 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) skt := any_socket_to_socket(s)
if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK { 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 return nil
} }
@(private) @(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) socket := any_socket_to_socket(socket)
flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0) flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0)
if flags_ < 0 { if flags_ < 0 {
return Set_Blocking_Error(posix.errno()) return _set_blocking_error()
} }
flags := transmute(posix.O_Flags)flags_ 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 { if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 {
return Set_Blocking_Error(posix.errno()) return _set_blocking_error()
} }
return nil return nil
+40 -51
View File
@@ -54,8 +54,14 @@ Socket_Option :: enum c.int {
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, 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) @(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_family: freebsd.Protocol_Family = ---
sys_protocol: freebsd.Protocol = --- sys_protocol: freebsd.Protocol = ---
sys_socket_type: freebsd.Socket_Type = --- 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) new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol)
if errno != nil { if errno != nil {
err = cast(Create_Socket_Error)errno err = _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
return 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) errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil { if errno != nil {
close(socket) close(socket)
return {}, cast(Dial_Error)errno return {}, _dial_error(errno)
} }
return return
} }
@(private) @(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) sockaddr := _endpoint_to_sockaddr(ep)
real_socket := any_socket_to_socket(socket) real_socket := any_socket_to_socket(socket)
errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil { if errno != nil {
err = cast(Bind_Error)errno err = _bind_error(errno)
} }
return return
} }
@@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
errno := freebsd.listen(cast(Fd)socket, backlog) errno := freebsd.listen(cast(Fd)socket, backlog)
if errno != nil { if errno != nil {
err = cast(Listen_Error)errno err = _listen_error(errno)
return return
} }
@@ -151,12 +140,12 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
} }
@(private) @(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 sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil { if errno != nil {
err = cast(Listen_Error)errno err = _listen_error(errno)
return return
} }
@@ -165,12 +154,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(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 sockaddr: freebsd.Socket_Address_Storage
result, errno := freebsd.accept(cast(Fd)sock, &sockaddr) result, errno := freebsd.accept(cast(Fd)sock, &sockaddr)
if errno != nil { if errno != nil {
err = cast(Accept_Error)errno err = _accept_error(errno)
return return
} }
@@ -187,20 +176,20 @@ _close :: proc(socket: Any_Socket) {
} }
@(private) @(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 { if len(buf) == 0 {
return return
} }
result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE) result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE)
if errno != nil { if errno != nil {
err = cast(TCP_Recv_Error)errno err = _tcp_recv_error(errno)
return return
} }
return result, nil return result, nil
} }
@(private) @(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 { if len(buf) == 0 {
return 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) result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from)
if errno != nil { if errno != nil {
err = cast(UDP_Recv_Error)errno err = _udp_recv_error(errno)
return return
} }
return result, _sockaddr_to_endpoint(&from), nil return result, _sockaddr_to_endpoint(&from), nil
} }
@(private) @(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) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] 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 { if errno != nil {
err = cast(TCP_Send_Error)errno err = _tcp_send_error(errno)
return return
} }
bytes_written += result bytes_written += result
@@ -231,15 +220,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
} }
@(private) @(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) toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] 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 { if errno != nil {
err = cast(UDP_Send_Error)errno err = _udp_send_error(errno)
return return
} }
bytes_written += result bytes_written += result
@@ -248,17 +237,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt
} }
@(private) @(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) real_socket := cast(Fd)any_socket_to_socket(socket)
errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner) errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner)
if errno != nil { if errno != nil {
return cast(Shutdown_Error)errno return _shutdown_error(errno)
} }
return return
} }
@(private) @(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 // NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32
// bits for a boolean socket option value. Nothing less will work. // bits for a boolean socket option value. Nothing less will work.
bool_value: b32 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 u16: int_value = cast(i32)real
case i32: int_value = real case i32: int_value = real
case u32: 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 int_value = cast(i32)real
case i64: 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 int_value = cast(i32)real
case u64: 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 int_value = cast(i32)real
case i128: 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 int_value = cast(i32)real
case u128: 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 int_value = cast(i32)real
case int: 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 int_value = cast(i32)real
case uint: 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 int_value = cast(i32)real
case: case:
panic("set_option() value must be an integer here", loc) 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) real_socket := any_socket_to_socket(socket)
errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len) errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len)
if errno != nil { if errno != nil {
return cast(Socket_Option_Error)errno return _socket_option_error(errno)
} }
return nil return nil
} }
@(private) @(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) real_socket := any_socket_to_socket(socket)
flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket) flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket)
if errno != nil { if errno != nil {
return cast(Set_Blocking_Error)errno return _set_blocking_error(errno)
} }
if should_block { 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) errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags)
if errno != nil { if errno != nil {
return cast(Set_Blocking_Error)errno return _set_blocking_error(errno)
} }
return return
+43 -42
View File
@@ -38,15 +38,21 @@ Socket_Option :: enum c.int {
Broadcast = c.int(linux.Socket_Option.BROADCAST), 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 // Wrappers and unwrappers for system-native types
@(private="file") @(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)) return linux.Fd(any_socket_to_socket(sock))
} }
@(private="file") @(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 { switch protocol {
case .TCP: return TCP_Socket(Socket(sock)) case .TCP: return TCP_Socket(Socket(sock))
case .UDP: return UDP_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") @(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 { switch family {
case .IP4: return .INET case .IP4: return .INET
case .IP6: return .INET6 case .IP6: return .INET6
@@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_
} }
@(private="file") @(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 { switch protocol {
case .TCP: return .TCP, .STREAM case .TCP: return .TCP, .STREAM
case .UDP: return .UDP, .DGRAM case .UDP: return .UDP, .DGRAM
@@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li
} }
@(private="file") @(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 { switch address in endpoint.address {
case IP4_Address: case IP4_Address:
return { return {
@@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any
} }
@(private="file") @(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 { #partial switch addr.family {
case .INET: case .INET:
return { 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) family := _unwrap_os_family(family)
proto, socktype := _unwrap_os_proto_socktype(protocol) proto, socktype := _unwrap_os_proto_socktype(protocol)
sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
if errno != .NONE { if errno != .NONE {
return {}, Create_Socket_Error(errno) return {}, _create_socket_error(errno)
} }
return _wrap_os_socket(sock, protocol), nil 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) os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE { if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket // 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 // 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 // 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) errno = linux.connect(linux.Fd(os_sock), &addr)
if errno != .NONE { if errno != .NONE {
close(cast(TCP_Socket) os_sock) close(cast(TCP_Socket) os_sock)
return {}, Dial_Error(errno) return {}, _dial_error(errno)
} }
// NOTE(tetra): Not vital to succeed; error ignored // NOTE(tetra): Not vital to succeed; error ignored
no_delay: b32 = cast(b32) options.no_delay no_delay: b32 = cast(b32) options.no_delay
@@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
} }
@(private) @(private)
_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) { _bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) {
addr := _unwrap_os_addr(endpoint) addr := _unwrap_os_addr(endpoint)
errno := linux.bind(_unwrap_os_socket(sock), &addr) errno := linux.bind(_unwrap_os_socket(sock), &addr)
if errno != .NONE { if errno != .NONE {
return Bind_Error(errno) return _bind_error(errno)
} }
return nil return nil
} }
@@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
os_sock: linux.Fd os_sock: linux.Fd
os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE { if errno != .NONE {
err = Create_Socket_Error(errno) err = _create_socket_error(errno)
return return
} }
socket = cast(TCP_Socket)os_sock 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! // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
do_reuse_addr: b32 = true do_reuse_addr: b32 = true
if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE { 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 return
} }
// Bind the socket to endpoint address // Bind the socket to endpoint address
if errno = linux.bind(os_sock, &ep_address); errno != .NONE { if errno = linux.bind(os_sock, &ep_address); errno != .NONE {
err = Bind_Error(errno) err = _bind_error(errno)
return return
} }
// Listen on bound socket // Listen on bound socket
if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE { if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE {
err = Listen_Error(errno) err = _listen_error(errno)
return
} }
return return
} }
@(private) @(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 addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr) errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE { if errno != .NONE {
err = Listen_Error(errno) err = _listen_error(errno)
return return
} }
@@ -226,11 +231,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(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 addr: linux.Sock_Addr_Any
client_sock, errno := linux.accept(linux.Fd(sock), &addr) client_sock, errno := linux.accept(linux.Fd(sock), &addr)
if errno != .NONE { if errno != .NONE {
return {}, {}, Accept_Error(errno) return {}, {}, _accept_error(errno)
} }
// NOTE(tetra): Not vital to succeed; error ignored // NOTE(tetra): Not vital to succeed; error ignored
val: b32 = cast(b32) options.no_delay val: b32 = cast(b32) options.no_delay
@@ -244,19 +249,19 @@ _close :: proc(sock: Any_Socket) {
} }
@(private) @(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 { if len(buf) <= 0 {
return 0, nil return 0, nil
} }
bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {}) bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {})
if errno != .NONE { if errno != .NONE {
return 0, TCP_Recv_Error(errno) return 0, _tcp_recv_error(errno)
} }
return int(bytes_read), nil return int(bytes_read), nil
} }
@(private) @(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 { if len(buf) <= 0 {
// NOTE(flysand): It was returning no error, I didn't change anything // NOTE(flysand): It was returning no error, I didn't change anything
return 0, {}, {} return 0, {}, {}
@@ -268,28 +273,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_
from_addr: linux.Sock_Addr_Any from_addr: linux.Sock_Addr_Any
bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr) bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr)
if errno != .NONE { if errno != .NONE {
return 0, {}, UDP_Recv_Error(errno) return 0, {}, _udp_recv_error(errno)
} }
if bytes_read > len(buf) { if bytes_read > len(buf) {
// NOTE(tetra): The buffer has been filled, with a partial message. // 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 return bytes_read, _wrap_os_addr(from_addr), nil
} }
@(private) @(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 total_written := 0
for total_written < len(buf) { for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written) limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit] remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
if errno == .EPIPE { if errno != .NONE {
// If the peer is disconnected when we are trying to send we will get an `EPIPE` error, return total_written, _tcp_send_error(errno)
// 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)
} }
total_written += int(res) total_written += int(res)
} }
@@ -297,28 +298,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
} }
@(private) @(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) to_addr := _unwrap_os_addr(to)
bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr) bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr)
if errno != .NONE { if errno != .NONE {
return bytes_written, UDP_Send_Error(errno) return bytes_written, _udp_send_error(errno)
} }
return int(bytes_written), nil return int(bytes_written), nil
} }
@(private) @(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) os_sock := _unwrap_os_socket(sock)
errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner) errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner)
if errno != .NONE { if errno != .NONE {
return Shutdown_Error(errno) return _shutdown_error(errno)
} }
return nil return nil
} }
// TODO(flysand): Figure out what we want to do with this on core:sys/ level. // TODO(flysand): Figure out what we want to do with this on core:sys/ level.
@(private) @(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 level: int
if option == .TCP_Nodelay { if option == .TCP_Nodelay {
level = int(linux.SOL_TCP) 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) errno = linux.setsockopt(os_sock, level, int(option), &int_value)
} }
if errno != .NONE { if errno != .NONE {
return Socket_Option_Error(errno) return _socket_option_error(errno)
} }
return nil return nil
} }
@(private) @(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 errno: linux.Errno
flags: linux.Open_Flags flags: linux.Open_Flags
os_sock := _unwrap_os_socket(sock) os_sock := _unwrap_os_socket(sock)
flags, errno = linux.fcntl(os_sock, linux.F_GETFL) flags, errno = linux.fcntl(os_sock, linux.F_GETFL)
if errno != .NONE { if errno != .NONE {
return Set_Blocking_Error(errno) return _set_blocking_error(errno)
} }
if should_block { if should_block {
flags -= {.NONBLOCK} 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) errno = linux.fcntl(os_sock, linux.F_SETFL, flags)
if errno != .NONE { if errno != .NONE {
return Set_Blocking_Error(errno) return _set_blocking_error(errno)
} }
return nil return nil
} }
+88 -37
View File
@@ -24,13 +24,67 @@ import "core:c"
import win "core:sys/windows" import win "core:sys/windows"
import "core:time" 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) @(init, private)
ensure_winsock_initialized :: proc() { ensure_winsock_initialized :: proc() {
win.ensure_winsock_initialized() win.ensure_winsock_initialized()
} }
@(private) @(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 c_type, c_protocol, c_family: c.int
switch family { 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) sock := win.socket(c_family, c_type, c_protocol)
if sock == win.INVALID_SOCKET { if sock == win.INVALID_SOCKET {
err = Create_Socket_Error(win.WSAGetLastError()) err = _create_socket_error()
return return
} }
@@ -80,7 +134,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
if res < 0 { if res < 0 {
err = Dial_Error(win.WSAGetLastError()) err = _dial_error()
close(socket) close(socket)
return {}, err return {}, err
} }
@@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
} }
@(private) @(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) sockaddr := _endpoint_to_sockaddr(ep)
sock := any_socket_to_socket(socket) sock := any_socket_to_socket(socket)
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr)) res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
if res < 0 { if res < 0 {
err = Bind_Error(win.WSAGetLastError()) err = _bind_error()
} }
return return
} }
@@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
bind(sock, interface_endpoint) or_return bind(sock, interface_endpoint) or_return
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR { if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError()) err = _listen_error()
} }
return return
} }
@(private) @(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 sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr)) sockaddrlen := c.int(size_of(sockaddr))
if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError()) err = _listen_error()
return return
} }
@@ -136,7 +190,7 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(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 { for {
sockaddr: win.SOCKADDR_STORAGE_LH sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr)) 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. // can do this to match the behaviour.
continue continue
} }
err = Accept_Error(e) err = _accept_error()
return return
} }
client = TCP_Socket(client_sock) client = TCP_Socket(client_sock)
@@ -170,20 +224,20 @@ _close :: proc(socket: Any_Socket) {
} }
@(private) @(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 { if len(buf) <= 0 {
return return
} }
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0) res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
if res < 0 { if res < 0 {
err = TCP_Recv_Error(win.WSAGetLastError()) err = _tcp_recv_error()
return return
} }
return int(res), nil return int(res), nil
} }
@(private) @(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 { if len(buf) <= 0 {
return return
} }
@@ -192,7 +246,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
fromsize := c.int(size_of(from)) fromsize := c.int(size_of(from))
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
if res < 0 { if res < 0 {
err = UDP_Recv_Error(win.WSAGetLastError()) err = _udp_recv_error()
return return
} }
@@ -202,13 +256,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
} }
@(private) @(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) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:] remaining := buf[bytes_written:]
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0) res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
if res < 0 { if res < 0 {
err = TCP_Send_Error(win.WSAGetLastError()) err = _tcp_send_error()
return return
} }
bytes_written += int(res) bytes_written += int(res)
@@ -217,34 +271,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
} }
@(private) @(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) {
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
}
toaddr := _endpoint_to_sockaddr(to) toaddr := _endpoint_to_sockaddr(to)
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) for bytes_written < len(buf) {
if res < 0 { limit := min(int(max(i32)), len(buf) - bytes_written)
err = UDP_Send_Error(win.WSAGetLastError()) remaining := buf[bytes_written:]
return 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 return
} }
@(private) @(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) s := any_socket_to_socket(socket)
res := win.shutdown(win.SOCKET(s), c.int(manner)) res := win.shutdown(win.SOCKET(s), c.int(manner))
if res < 0 { if res < 0 {
return Shutdown_Error(win.WSAGetLastError()) return _shutdown_error()
} }
return return
} }
@(private) @(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 level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
bool_value: b32 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) t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc)
num_secs := i64(time.duration_seconds(t)) 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)) { if num_secs > i64(max(u16)) {
return .Value_Out_Of_Range return .Invalid_Value
} }
linger_value.l_onoff = 1 linger_value.l_onoff = 1
linger_value.l_linger = c.ushort(num_secs) 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) socket := any_socket_to_socket(s)
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len) res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
if res < 0 { if res < 0 {
return Socket_Option_Error(win.WSAGetLastError()) return _socket_option_error()
} }
return nil return nil
} }
@(private) @(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) socket := any_socket_to_socket(socket)
arg: win.DWORD = 0 if should_block else 1 arg: win.DWORD = 0 if should_block else 1
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg) res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
if res == win.SOCKET_ERROR { if res == win.SOCKET_ERROR {
return Set_Blocking_Error(win.WSAGetLastError()) return _set_blocking_error()
} }
return nil return nil
+1
View File
@@ -91,6 +91,7 @@ foreign ws2_32 {
WSACleanup :: proc() -> c_int --- WSACleanup :: proc() -> c_int ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror) // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror)
WSAGetLastError :: proc() -> c_int --- WSAGetLastError :: proc() -> c_int ---
WSASetLastError :: proc(err: c_int) ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll) // [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 --- 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) // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaduplicatesocketw)
+15 -8
View File
@@ -17,9 +17,6 @@ import "core:net"
import "core:time" import "core:time"
import "core:testing" 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
test_duplicate_binding :: proc(t: ^testing.T) { test_duplicate_binding :: proc(t: ^testing.T) {
// FreeBSD has the capacity to permit multiple processes and sockets to // 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) { if !testing.expect_value(t, err_set1, nil) {
return 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) { if !testing.expect_value(t, err_bind1, nil) {
return 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) raw_socket2, err_create2 := net.create_socket(.IP4, .TCP)
if !testing.expect_value(t, err_create2, nil) { if !testing.expect_value(t, err_create2, nil) {
return return
@@ -50,7 +52,7 @@ test_duplicate_binding :: proc(t: ^testing.T) {
if !testing.expect_value(t, err_set2, nil) { if !testing.expect_value(t, err_set2, nil) {
return 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) { if !testing.expect_value(t, err_bind2, nil) {
return return
} }
@@ -60,13 +62,18 @@ test_duplicate_binding :: proc(t: ^testing.T) {
test_sigpipe_bypass :: 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. // 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) { if !testing.expect_value(t, listen_err, nil) {
return return
} }
defer net.close(server_socket) 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) { if !testing.expect_value(t, dial_err, nil) {
return return
} }
@@ -80,7 +87,7 @@ test_sigpipe_bypass :: proc(t: ^testing.T) {
data := "Hellope!" data := "Hellope!"
bytes_written, err_send := net.send(client_socket, transmute([]u8)data) 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 return
} }
if !testing.expect_value(t, bytes_written, 0) { if !testing.expect_value(t, bytes_written, 0) {