-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.