Current section
Files
Jump to
Current section
Files
src/toss.erl
-module(toss).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/toss.gleam").
-export([use_ipv4/1, use_ipv6/1, using_interface/2, reuse_address/1, send_to/4, send_to_host/4, 'receive'/3, receive_forever/2, connect_to/3, connect_to_host/3, send/2, join_multicast_group/3, leave_multicast_group/3, loop_mutlicast/2, local_port/1, close/1, open/1, new/1, receive_next_datagram_as_message/1, select_udp_messages/2, describe_error/1]).
-export_type([socket/0, connected_sender/0, socket_options/0, udp_message/0, any_/0, mode_value/0, active_value/0, gen_udp_option/0, error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" Work with UDP sockets on the Erlang target.\n").
-type socket() :: any().
-type connected_sender() :: any().
-opaque socket_options() :: {socket_options, integer(), list(gen_udp_option())}.
-type udp_message() :: {datagram,
socket(),
{ok, glip:ip_address()} | {error, nil},
integer(),
bitstring()} |
{udp_error, socket(), error()}.
-type any_() :: any().
-type mode_value() :: binary.
-type active_value() :: any().
-type gen_udp_option() :: {active, active_value()} |
{mode, mode_value()} |
{reuseaddr, boolean()} |
{reuseport, boolean()} |
{add_membership, {glip:external_ip_address(), glip:external_ip_address()}} |
{drop_membership, {glip:external_ip_address(), glip:external_ip_address()}} |
{multicast_loop, boolean()} |
{ip, glip:external_ip_address()} |
inet |
inet6.
-type error() :: not_owner |
timeout |
bad_argument |
eaddrinuse |
eaddrnotavail |
eafnosupport |
ealready |
econnaborted |
econnrefused |
econnreset |
edestaddrreq |
ehostdown |
ehostunreach |
einprogress |
eisconn |
emsgsize |
enetdown |
enetunreach |
enopkg |
enoprotoopt |
enotconn |
enotty |
enotsock |
eproto |
eprotonosupport |
eprototype |
esocktnosupport |
etimedout |
ewouldblock |
exbadport |
exbadseq |
nxdomain |
eacces |
eagain |
ebadf |
ebadmsg |
ebusy |
edeadlk |
edeadlock |
edquot |
eexist |
efault |
efbig |
eftype |
eintr |
einval |
eio |
eisdir |
eloop |
emfile |
emlink |
emultihop |
enametoolong |
enfile |
enobufs |
enodev |
enolck |
enolink |
enoent |
enomem |
enospc |
enosr |
enostr |
enosys |
enotblk |
enotdir |
enotsup |
enxio |
eopnotsupp |
eoverflow |
eperm |
epipe |
erange |
erofs |
espipe |
esrch |
estale |
etxtbsy |
exdev.
-file("src/toss.gleam", 60).
-spec add_option(socket_options(), gen_udp_option()) -> socket_options().
add_option(Options, Option) ->
{socket_options,
erlang:element(2, Options),
[Option | erlang:element(3, Options)]}.
-file("src/toss.gleam", 33).
?DOC(
" Specifies to open the socket in IPv4 mode.\n"
" You can not send to IPv6 addresses when opening a socket with this option.\n"
).
-spec use_ipv4(socket_options()) -> socket_options().
use_ipv4(Options) ->
add_option(Options, inet).
-file("src/toss.gleam", 42).
?DOC(
" Specifies to open the socket in IPv6 mode.\n"
" On dual stack systems,\n"
" you can send to IPv4-mapped IPv6 addresses,\n"
" and respectively datagrams received from IPv4 addresses will\n"
" have an address in the IPv4-mapped format.\n"
).
-spec use_ipv6(socket_options()) -> socket_options().
use_ipv6(Options) ->
add_option(Options, inet6).
-file("src/toss.gleam", 48).
?DOC(
" If the local host has multiple interfaces,\n"
" specifies which one to use by IP address.\n"
).
-spec using_interface(socket_options(), glip:ip_address()) -> socket_options().
using_interface(Options, Ip) ->
add_option(Options, {ip, glip_ffi:to_external_ip(Ip)}).
-file("src/toss.gleam", 55).
?DOC(
" Allows reusing an already open local address and port\n"
" Otherwise, an error will be returned from `open`,\n"
" if a socket on the port is already open.\n"
).
-spec reuse_address(socket_options()) -> socket_options().
reuse_address(Options) ->
_pipe = Options,
_pipe@1 = add_option(_pipe, {reuseaddr, true}),
add_option(_pipe@1, {reuseport, true}).
-file("src/toss.gleam", 117).
?DOC(" Sends a UDP datagram to the specified destination by IP address.\n").
-spec send_to(socket(), glip:ip_address(), integer(), bitstring()) -> {ok, nil} |
{error, error()}.
send_to(Socket, Host, Port, Data) ->
toss_ffi:send(Socket, Host, Port, Data).
-file("src/toss.gleam", 126).
?DOC(" Sends a UDP datagram to the specified destination by hostname.\n").
-spec send_to_host(socket(), binary(), integer(), bitstring()) -> {ok, nil} |
{error, error()}.
send_to_host(Socket, Host, Port, Data) ->
toss_ffi:send(Socket, Host, Port, Data).
-file("src/toss.gleam", 142).
?DOC(
" Receives a UDP datagram from the socket.\n"
" The source address, port, and the datagram are returned on success.\n"
" The maximum length will affect memory allocation,\n"
" so it should be selected conservatively.\n"
" The address will be an error if the sender does not have a socket address,\n"
" or the Erlang VM\n"
" [doesn't recognise the address](https://www.erlang.org/doc/apps/kernel/inet#t:returned_non_ip_address/0)\n"
" (toss does currently not support local Unix domain sockets).\n"
).
-spec 'receive'(socket(), integer(), integer()) -> {ok,
{{ok, glip:ip_address()} | {error, nil}, integer(), bitstring()}} |
{error, error()}.
'receive'(Socket, Max_length, Timeout) ->
toss_ffi:recv(Socket, Max_length, Timeout).
-file("src/toss.gleam", 151).
?DOC(
" Receives a UDP datagram from the socket, without a timeout.\n"
" See [`receive`](#receive) for details.\n"
).
-spec receive_forever(socket(), integer()) -> {ok,
{{ok, glip:ip_address()} | {error, nil}, integer(), bitstring()}} |
{error, error()}.
receive_forever(Socket, Max_length) ->
toss_ffi:recv(Socket, Max_length).
-file("src/toss.gleam", 163).
?DOC(
" Modifies the socket to only receive data from the specified source.\n"
" Other messages are discarded on arrival by the OS protocol stack.\n"
" Returns a handle to the socket,\n"
" which can be used to send data without specifying the destination every time.\n"
" Note that multiple calls to `connect_to` will override any previous calls -\n"
" all previously returned senders will also change behaviour.\n"
).
-spec connect_to(socket(), glip:ip_address(), integer()) -> {ok,
connected_sender()} |
{error, error()}.
connect_to(Socket, Host, Port) ->
toss_ffi:connect(Socket, Host, Port).
-file("src/toss.gleam", 173).
?DOC(
" Like `connect_to`, but uses a host name instead of an IP address.\n"
" If you have an IP address as a string,\n"
" convert it first using `parse_ip`, and use `connect_to`.\n"
).
-spec connect_to_host(socket(), binary(), integer()) -> {ok, connected_sender()} |
{error, error()}.
connect_to_host(Socket, Host, Port) ->
toss_ffi:connect(Socket, Host, Port).
-file("src/toss.gleam", 181).
?DOC(" Sends a UDP datagram to the peer of a connected socket.\n").
-spec send(connected_sender(), bitstring()) -> {ok, nil} | {error, error()}.
send(Sender, Data) ->
toss_ffi:send(Sender, Data).
-file("src/toss.gleam", 83).
?DOC(" Joins a multicast group on the given local interface.\n").
-spec join_multicast_group(socket(), glip:ip_address(), glip:ip_address()) -> {ok,
nil} |
{error, error()}.
join_multicast_group(Socket, Multicast_address, Interface_address) ->
toss_ffi:setopts(
Socket,
[{add_membership,
{glip_ffi:to_external_ip(Multicast_address),
glip_ffi:to_external_ip(Interface_address)}}]
).
-file("src/toss.gleam", 97).
?DOC(" Leaves a multicast group on the given local interface.\n").
-spec leave_multicast_group(socket(), glip:ip_address(), glip:ip_address()) -> {ok,
nil} |
{error, error()}.
leave_multicast_group(Socket, Multicast_address, Interface_address) ->
toss_ffi:setopts(
Socket,
[{drop_membership,
{glip_ffi:to_external_ip(Multicast_address),
glip_ffi:to_external_ip(Interface_address)}}]
).
-file("src/toss.gleam", 111).
?DOC(" Sets whether sent multicast packets are looped back to the socket.\n").
-spec loop_mutlicast(socket(), boolean()) -> {ok, nil} | {error, error()}.
loop_mutlicast(Socket, Loop) ->
toss_ffi:setopts(Socket, [{multicast_loop, Loop}]).
-file("src/toss.gleam", 78).
?DOC(" Returns the local port, useful if the socket was opened with port 0\n").
-spec local_port(socket()) -> {ok, integer()} | {error, nil}.
local_port(Socket) ->
_pipe = inet:port(Socket),
gleam@result:replace_error(_pipe, nil).
-file("src/toss.gleam", 72).
?DOC(
" Closes the socket, freeing up any resources it uses.\n"
" The socket and any associated senders can no longer be used after this.\n"
).
-spec close(socket()) -> nil.
close(Socket) ->
gen_udp:close(Socket),
nil.
-file("src/toss.gleam", 66).
?DOC(
" Opens a UDP socket.\n"
" When freshly opened, the socket will receive data from any source.\n"
).
-spec open(socket_options()) -> {ok, socket()} | {error, error()}.
open(Options) ->
toss_ffi:open(erlang:element(2, Options), erlang:element(3, Options)).
-file("src/toss.gleam", 27).
?DOC(
" Constructs the default options to open a socket with,\n"
" binding it to the given local port.\n"
" 0 can be used to let the OS automatically choose a free port.\n"
).
-spec new(integer()) -> socket_options().
new(Port) ->
{socket_options, Port, [{mode, binary}, {active, toss_ffi:passive()}]}.
-file("src/toss.gleam", 234).
?DOC(
" Switch the socket to active (once) mode,\n"
" meaning that the next datagram received on the socket\n"
" will be sent as an Erlang message to the socket owner's inbox.\n"
"\n"
" This is useful for when you wish to have an OTP actor handle incoming messages,\n"
" as using the [`receive`](#receive) function would result in the actor being\n"
" blocked and unable to handle other messages while waiting for the next packet.\n"
"\n"
" Messages will be sent to the process that controls the socket,\n"
" which is the process that established the socket with the [`open`](#open) function.\n"
"\n"
" In order to continue receiving messages,\n"
" you will need to call this function again after receiving a message.\n"
" This is intended to provide backpressure to the OS socket,\n"
" instead of flooding the inbox on the Erlang side,\n"
" which could happen if switching to full active mode.\n"
).
-spec receive_next_datagram_as_message(socket()) -> {ok, nil} | {error, error()}.
receive_next_datagram_as_message(Socket) ->
toss_ffi:setopts(Socket, [{active, toss_ffi:active_once()}]).
-file("src/toss.gleam", 204).
?DOC(
" Configure a selector to receive messages from UDP sockets.\n"
" You will also need to call\n"
" [`receive_next_datagram_as_message`](#receive_next_datagram_as_message)\n"
" to use the selector successfully - once initially,\n"
" and again after receiving each message.\n"
"\n"
" Note that this will receive messages from all UDP sockets that the process controls,\n"
" rather than any specific one.\n"
" If you wish to only handle messages from one socket then use one process per socket.\n"
).
-spec select_udp_messages(
gleam@erlang@process:selector(FAN),
fun((udp_message()) -> FAN)
) -> gleam@erlang@process:selector(FAN).
select_udp_messages(Selector, Mapper) ->
Udp = erlang:binary_to_atom(<<"udp"/utf8>>),
Error = erlang:binary_to_atom(<<"udp_error"/utf8>>),
Map = fun(Message) -> Mapper(toss_ffi:map_udp_message(Message)) end,
_pipe = Selector,
_pipe@1 = gleam@erlang@process:select_record(_pipe, Udp, 4, Map),
gleam@erlang@process:select_record(_pipe@1, Error, 2, Map).
-file("src/toss.gleam", 462).
?DOC(" Convert an error into a human-readable description\n").
-spec describe_error(error()) -> binary().
describe_error(Error) ->
case Error of
not_owner ->
<<"Socket not owned by the process trying to use it"/utf8>>;
timeout ->
<<"Operation timed out"/utf8>>;
bad_argument ->
<<"Bad argument (probably invalid port number)"/utf8>>;
eaddrinuse ->
<<"Address already in use"/utf8>>;
eaddrnotavail ->
<<"Cannot assign requested address"/utf8>>;
eafnosupport ->
<<"Address family not supported"/utf8>>;
ealready ->
<<"Operation already in progress"/utf8>>;
econnaborted ->
<<"Connection aborted"/utf8>>;
econnrefused ->
<<"Connection refused"/utf8>>;
econnreset ->
<<"Connection reset by peer"/utf8>>;
edestaddrreq ->
<<"Destination address required"/utf8>>;
ehostdown ->
<<"Host is down"/utf8>>;
ehostunreach ->
<<"No route to host"/utf8>>;
einprogress ->
<<"Operation now in progress"/utf8>>;
eisconn ->
<<"Socket is already connected"/utf8>>;
emsgsize ->
<<"Message too long"/utf8>>;
enetdown ->
<<"Network is down"/utf8>>;
enetunreach ->
<<"Network is unreachable"/utf8>>;
enopkg ->
<<"Package not installed"/utf8>>;
enoprotoopt ->
<<"Protocol not available"/utf8>>;
enotconn ->
<<"Socket is not connected"/utf8>>;
enotty ->
<<"Inappropriate ioctl for device"/utf8>>;
enotsock ->
<<"Socket operation on non-socket"/utf8>>;
eproto ->
<<"Protocol error"/utf8>>;
eprotonosupport ->
<<"Protocol not supported"/utf8>>;
eprototype ->
<<"Protocol wrong type for socket"/utf8>>;
esocktnosupport ->
<<"Socket type not supported"/utf8>>;
etimedout ->
<<"Connection timed out"/utf8>>;
ewouldblock ->
<<"Operation would block"/utf8>>;
exbadport ->
<<"Bad port number"/utf8>>;
exbadseq ->
<<"Bad sequence number"/utf8>>;
nxdomain ->
<<"Non-existent domain"/utf8>>;
eacces ->
<<"Permission denied"/utf8>>;
eagain ->
<<"Resource temporarily unavailable"/utf8>>;
ebadf ->
<<"Bad file descriptor"/utf8>>;
ebadmsg ->
<<"Bad message"/utf8>>;
ebusy ->
<<"Device or resource busy"/utf8>>;
edeadlk ->
<<"Resource deadlock avoided"/utf8>>;
edeadlock ->
<<"Resource deadlock avoided"/utf8>>;
edquot ->
<<"Disk quota exceeded"/utf8>>;
eexist ->
<<"File exists"/utf8>>;
efault ->
<<"Bad address"/utf8>>;
efbig ->
<<"File too large"/utf8>>;
eftype ->
<<"Inappropriate file type or format"/utf8>>;
eintr ->
<<"Interrupted system call"/utf8>>;
einval ->
<<"Invalid argument"/utf8>>;
eio ->
<<"Input/output error"/utf8>>;
eisdir ->
<<"Is a directory"/utf8>>;
eloop ->
<<"Too many levels of symbolic links"/utf8>>;
emfile ->
<<"Too many open files"/utf8>>;
emlink ->
<<"Too many links"/utf8>>;
emultihop ->
<<"Multihop attempted"/utf8>>;
enametoolong ->
<<"File name too long"/utf8>>;
enfile ->
<<"Too many open files in system"/utf8>>;
enobufs ->
<<"No buffer space available"/utf8>>;
enodev ->
<<"No such device"/utf8>>;
enolck ->
<<"No locks available"/utf8>>;
enolink ->
<<"Link has been severed"/utf8>>;
enoent ->
<<"No such file or directory"/utf8>>;
enomem ->
<<"Out of memory"/utf8>>;
enospc ->
<<"No space left on device"/utf8>>;
enosr ->
<<"Out of streams resources"/utf8>>;
enostr ->
<<"Device not a stream"/utf8>>;
enosys ->
<<"Function not implemented"/utf8>>;
enotblk ->
<<"Block device required"/utf8>>;
enotdir ->
<<"Not a directory"/utf8>>;
enotsup ->
<<"Operation not supported"/utf8>>;
enxio ->
<<"No such device or address"/utf8>>;
eopnotsupp ->
<<"Operation not supported on socket"/utf8>>;
eoverflow ->
<<"Value too large for defined data type"/utf8>>;
eperm ->
<<"Operation not permitted"/utf8>>;
epipe ->
<<"Broken pipe"/utf8>>;
erange ->
<<"Result too large"/utf8>>;
erofs ->
<<"Read-only file system"/utf8>>;
espipe ->
<<"Illegal seek"/utf8>>;
esrch ->
<<"No such process"/utf8>>;
estale ->
<<"Stale file handle"/utf8>>;
etxtbsy ->
<<"Text file busy"/utf8>>;
exdev ->
<<"Cross-device link"/utf8>>
end.