-module(mug). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/mug.gleam"). -export([describe_error/1, new/2, ip_version_preference/2, timeout/2, connect/1, send_builder/2, send/2, 'receive'/2, receive_exact/3, shutdown/1, receive_next_packet_as_message/1, select_tcp_messages/2]). -export_type([socket/0, do_not_leak/0, connect_error/0, error/0, connection_options/0, ip_version_preference/0, mode_value/0, active_value/0, gen_tcp_option/0, tcp_message/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. -type socket() :: any(). -type do_not_leak() :: any(). -type connect_error() :: {connect_failed_ipv4, error()} | {connect_failed_ipv6, error()} | {connect_failed_both, error(), error()}. -type error() :: closed | timeout | 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. -type connection_options() :: {connection_options, binary(), integer(), integer(), ip_version_preference()}. -type ip_version_preference() :: ipv4_only | ipv4_preferred | ipv6_only | ipv6_preferred. -type mode_value() :: binary. -type active_value() :: any(). -type gen_tcp_option() :: inet | inet6 | {active, active_value()} | {mode, mode_value()}. -type tcp_message() :: {packet, socket(), bitstring()} | {socket_closed, socket()} | {tcp_error, socket(), error()}. -file("src/mug.gleam", 191). ?DOC(" Convert an error into a human-readable description\n"). -spec describe_error(error()) -> binary(). describe_error(Error) -> case Error of closed -> <<"Connection closed"/utf8>>; timeout -> <<"Operation timed out"/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. -file("src/mug.gleam", 303). ?DOC(" Create a new set of connection options.\n"). -spec new(binary(), integer()) -> connection_options(). new(Host, Port) -> {connection_options, Host, Port, 1000, ipv6_preferred}. -file("src/mug.gleam", 317). ?DOC( " What approach to take selecting between IPv4 and IPv6.\n" " See the `IpVersionPreference` type for documentation.\n" "\n" " The default is `Ipv6Preferred`.\n" ). -spec ip_version_preference(connection_options(), ip_version_preference()) -> connection_options(). ip_version_preference(Options, Preference) -> {connection_options, erlang:element(2, Options), erlang:element(3, Options), erlang:element(4, Options), Preference}. -file("src/mug.gleam", 328). ?DOC( " Specify a timeout for the connection to be established, in milliseconds.\n" "\n" " The default is 1000ms.\n" ). -spec timeout(connection_options(), integer()) -> connection_options(). timeout(Options, Timeout) -> {connection_options, erlang:element(2, Options), erlang:element(3, Options), Timeout, erlang:element(5, Options)}. -file("src/mug.gleam", 367). ?DOC( " Establish a TCP connection to the server specified in the connection\n" " options.\n" "\n" " Returns an error if the connection could not be established.\n" "\n" " The socket is created in passive mode, meaning the the `receive` function is\n" " to be called to receive packets from the client. The\n" " `receive_next_packet_as_message` function can be used to switch the socket\n" " to active mode and receive the next packet as an Erlang message.\n" ). -spec connect(connection_options()) -> {ok, socket()} | {error, connect_error()}. connect(Options) -> Host = unicode:characters_to_list(erlang:element(2, Options)), Connect = fun(Inet) -> Gen_options = [Inet, {active, mug_ffi:passive()}, {mode, binary}], gen_tcp:connect( Host, erlang:element(3, Options), Gen_options, erlang:element(4, Options) ) end, case erlang:element(5, Options) of ipv4_only -> _pipe = Connect(inet), case _pipe of {ok, X} -> {ok, X}; {error, Error} -> {error, {connect_failed_ipv4, Error}} end; ipv6_only -> _pipe@1 = Connect(inet6), case _pipe@1 of {ok, X@1} -> {ok, X@1}; {error, Error@1} -> {error, {connect_failed_ipv6, Error@1}} end; ipv4_preferred -> case Connect(inet) of {ok, Conn} -> {ok, Conn}; {error, Ipv4} -> case Connect(inet6) of {ok, Conn@1} -> {ok, Conn@1}; {error, Ipv6} -> {error, {connect_failed_both, Ipv4, Ipv6}} end end; ipv6_preferred -> case Connect(inet6) of {ok, Conn@2} -> {ok, Conn@2}; {error, Ipv6@1} -> case Connect(inet) of {ok, Conn@3} -> {ok, Conn@3}; {error, Ipv4@1} -> {error, {connect_failed_both, Ipv4@1, Ipv6@1}} end end end. -file("src/mug.gleam", 446). ?DOC( " Send a message to the client, the data in `BytesBuilder`. Using this function\n" " is more efficient turning an `BytesBuilder` or a `StringBuilder` into a\n" " `BitArray` to use with the `send` function.\n" ). -spec send_builder(socket(), gleam@bytes_tree:bytes_tree()) -> {ok, nil} | {error, error()}. send_builder(Socket, Packet) -> mug_ffi:send(Socket, Packet). -file("src/mug.gleam", 437). ?DOC(" Send a message to the client.\n"). -spec send(socket(), bitstring()) -> {ok, nil} | {error, error()}. send(Socket, Message) -> mug_ffi:send(Socket, gleam@bytes_tree:from_bit_array(Message)). -file("src/mug.gleam", 453). ?DOC( " Receive a message from the client.\n" "\n" " Errors if the socket is closed, if the timeout is reached, or if there is\n" " some other problem receiving the packet.\n" ). -spec 'receive'(socket(), integer()) -> {ok, bitstring()} | {error, error()}. 'receive'(Socket, Timeout) -> gen_tcp:recv(Socket, 0, Timeout). -file("src/mug.gleam", 470). ?DOC( " Receive the specified number of bytes from the client, unless the socket\n" " was closed, from the other side. In that case, the last read may return\n" " less bytes.\n" " If the specified number of bytes is not available to read from the socket\n" " then the function will block until the bytes are available, or until the\n" " timeout is reached.\n" " This directly calls the underlying Erlang function `gen_tcp:recv/3`.\n" "\n" " Errors if the socket is closed, if the timeout is reached, or if there is\n" " some other problem receiving the packet.\n" ). -spec receive_exact(socket(), integer(), integer()) -> {ok, bitstring()} | {error, error()}. receive_exact(Socket, Size, Timeout) -> gen_tcp:recv(Socket, Size, Timeout). -file("src/mug.gleam", 489). ?DOC( " Close the socket, ensuring that any data buffered in the socket is flushed\n" " to the operating system kernel socket first.\n" ). -spec shutdown(socket()) -> {ok, nil} | {error, error()}. shutdown(Socket) -> mug_ffi:shutdown(Socket). -file("src/mug.gleam", 502). ?DOC( " Switch the socket to active mode, meaning that the next packet received on\n" " the socket 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\n" " messages as using the `receive` function would result in the actor being\n" " blocked and unable to handle other messages while waiting for the next\n" " packet.\n" "\n" " Messages will be send to the process that controls the socket, which is the\n" " process that established the socket with the `connect` function.\n" ). -spec receive_next_packet_as_message(socket()) -> nil. receive_next_packet_as_message(Socket) -> inet:setopts(Socket, [{active, mug_ffi:active_once()}]), nil. -file("src/mug.gleam", 541). -spec map_tcp_message(fun((tcp_message()) -> HKN)) -> fun((gleam@dynamic:dynamic_()) -> HKN). map_tcp_message(Mapper) -> fun(Message) -> Mapper(mug_ffi:coerce_tcp_message(Message)) end. -file("src/mug.gleam", 527). ?DOC( " Configure a selector to receive messages from TCP sockets.\n" "\n" " Note this will receive messages from all TCP sockets that the process\n" " controls, rather than any specific one. If you wish to only handle messages\n" " from one socket then use one process per socket.\n" ). -spec select_tcp_messages( gleam@erlang@process:selector(HKK), fun((tcp_message()) -> HKK) ) -> gleam@erlang@process:selector(HKK). select_tcp_messages(Selector, Mapper) -> Tcp = erlang:binary_to_atom(<<"tcp"/utf8>>), Closed = erlang:binary_to_atom(<<"tcp_closed"/utf8>>), Error = erlang:binary_to_atom(<<"tcp_error"/utf8>>), _pipe = Selector, _pipe@1 = gleam@erlang@process:select_record( _pipe, Tcp, 2, map_tcp_message(Mapper) ), _pipe@2 = gleam@erlang@process:select_record( _pipe@1, Closed, 1, map_tcp_message(Mapper) ), gleam@erlang@process:select_record( _pipe@2, Error, 2, map_tcp_message(Mapper) ).