diff --git a/src/tcp/reason.gleam b/src/tcp/reason.gleam new file mode 100644 index 0000000..0e58a1c --- /dev/null +++ b/src/tcp/reason.gleam @@ -0,0 +1,240 @@ +pub type Reason { + /// from `connect` + Timeout + /// from `send` + Closed + + /// Address already in use + Eaddrinuse + /// Cannot assign requested address + Eaddrnotavail + /// Address family not supported by protocol family + Eafnosupport + /// Operation already in progress + Ealready + /// Software caused connection abort + Econnaborted + /// Connection refused + Econnrefused + /// Connection reset by peer + Econnreset + /// Destination address required + Edestaddrreq + /// Host is down + Ehostdown + /// Host is unreachable + Ehostunreach + /// Operation now in progress + Einprogress + /// Socket is already connected + Eisconn + /// Message too long + Emsgsize + /// Network is down + Enetdown + /// Network is unreachable + Enetunreach + /// Package not installed + Enopkg + /// Bad protocol option + Enoprotoopt + /// Socket is not connected + Enotconn + /// Inappropriate device for ioctl + Enotty + /// Socket operation on non-socket + Enotsock + /// Protocol error + Eproto + /// Protocol not supported + Eprotonosupport + /// Wrong protocol type for socket + Eprototype + /// Socket type not supported + Esocktnosupport + /// Connection timed out + Etimedout + /// Operation would block + Ewouldblock + /// Invalid exchange + Exbadport + /// Invalid sequence number + Exbadseq + /// Permission denied + Eacces + /// Resource temporarily unavailable + Eagain + /// Bad file number + Ebadf + /// Not a data message + Ebadmsg + /// File busy + Ebusy + /// Resource deadlock avoided + Edeadlk + /// Resource deadlock avoided + Edeadlock + /// Disk quota exceeded + Edquot + /// File already exists + Eexist + /// Bad address in system call argument + Efault + /// File too large + Efbig + /// Inappropriate file type or format + Eftype + /// Interrupted system call + Eintr + /// Invalid argument + Einval + /// I/O error + Eio + /// Illegal operation on a directory + Eisdir + /// Too many levels of symbolic links + Eloop + /// Too many open files + Emfile + /// Too many links + Emlink + /// Multihop attempted + Emultihop + /// Filename too long + Enametoolong + /// File table overflow + Enfile + /// No buffer space available + Enobufs + /// No such device + Enodev + /// No locks available + Enolck + /// Link has been severed + Enolink + /// No such file or directory + Enoent + /// Not enough memory + Enomem + /// No space left on device + Enospc + /// Out of stream resources or not a stream device + Enosr + /// Not a stream device + Enostr + /// Function not implemented + Enosys + /// Block device required + Enotblk + /// Not a directory + Enotdir + /// Operation not supported + Enotsup + /// No such device or address + Enxio + /// Operation not supported on socket + Eopnotsupp + /// Value too large for defined data type + Eoverflow + /// Not owner + Eperm + /// Broken pipe + Epipe + /// Math result unrepresentable + Erange + /// Read-only filesystem + Erofs + /// Invalid seek + Espipe + /// No such process + Esrch + /// Stale remote file handle + Estale + /// Text file or pseudo-device busy + Etxtbsy + /// Cross-device link + Exdev +} + +pub fn to_string(reason: Reason) -> String { + case reason { + Closed -> "Connection closed (closed)" + Eacces -> "Permission denied (eacces)" + Eaddrinuse -> "Address already in use (eaddrinuse)" + Eaddrnotavail -> "Cannot assign requested address (eaddrnotavail)" + Eafnosupport -> + "Address family not supported by protocol family (eafnosupport)" + Eagain -> "Resource temporarily unavailable (eagain)" + Ealready -> "Operation already in progress (ealready)" + Ebadf -> "Bad file number (ebadf)" + Ebadmsg -> "Not a data message (ebadmsg)" + Ebusy -> "File busy (ebusy)" + Econnaborted -> "Software caused connection abort (econnaborted)" + Econnrefused -> "Connection refused (econnrefused)" + Econnreset -> "Connection reset by peer (econnreset)" + Edeadlk -> "Resource deadlock avoided (edeadlk)" + Edeadlock -> "Resource deadlock avoided (edeadlock)" + Edestaddrreq -> "Destination address required (edestaddrreq)" + Edquot -> "Disk quota exceeded (edquot)" + Eexist -> "File already exists (eexist)" + Efault -> "Bad address in system call argument (efault)" + Efbig -> "File too large (efbig)" + Eftype -> "Inappropriate file type or format (eftype)" + Ehostdown -> "Host is down (ehostdown)" + Ehostunreach -> "Host is unreachable (ehostunreach)" + Einprogress -> "Operation now in progress (einprogress)" + Eintr -> "Interrupted system call (eintr)" + Einval -> "Invalid argument (einval)" + Eio -> "I/O error (eio)" + Eisconn -> "Socket is already connected (eisconn)" + Eisdir -> "Illegal operation on a directory (eisdir)" + Eloop -> "Too many levels of symbolic links (eloop)" + Emfile -> "Too many open files (emfile)" + Emlink -> "Too many links (emlink)" + Emsgsize -> "Message too long (emsgsize)" + Emultihop -> "Multihop attempted (emultihop)" + Enametoolong -> "Filename too long (enametoolong)" + Enetdown -> "Network is down (enetdown)" + Enetunreach -> "Network is unreachable (enetunreach)" + Enfile -> "File table overflow (enfile)" + Enobufs -> "No buffer space available (enobufs)" + Enodev -> "No such device (enodev)" + Enoent -> "No such file or directory (enoent)" + Enolck -> "No locks available (enolck)" + Enolink -> "Link has been severed (enolink)" + Enomem -> "Not enough memory (enomem)" + Enopkg -> "Package not installed (enopkg)" + Enoprotoopt -> "Bad protocol option (enoprotoopt)" + Enospc -> "No space left on device (enospc)" + Enosr -> "Out of stream resources or not a stream device (enosr)" + Enostr -> "Not a stream device (enostr)" + Enosys -> "Function not implemented (enosys)" + Enotblk -> "Block device required (enotblk)" + Enotconn -> "Socket is not connected (enotconn)" + Enotdir -> "Not a directory (enotdir)" + Enotsock -> "Socket operation on non-socket (enotsock)" + Enotsup -> "Operation not supported (enotsup)" + Enotty -> "Inappropriate device for ioctl (enotty)" + Enxio -> "No such device or address (enxio)" + Eopnotsupp -> "Operation not supported on socket (eopnotsupp)" + Eoverflow -> "Value too large for defined data type (eoverflow)" + Eperm -> "Not owner (eperm)" + Epipe -> "Broken pipe (epipe)" + Eproto -> "Protocol error (eproto)" + Eprotonosupport -> "Protocol not supported (eprotonosupport)" + Eprototype -> "Wrong protocol type for socket (eprototype)" + Erange -> "Math result unrepresentable (erange)" + Erofs -> "Read-only filesystem (erofs)" + Esocktnosupport -> "Socket type not supported (esocktnosupport)" + Espipe -> "Invalid seek (espipe)" + Esrch -> "No such process (esrch)" + Estale -> "Stale remote file handle (estale)" + Etimedout -> "Connection timed out (etimedout)" + Etxtbsy -> "Text file or pseudo-device busy (etxtbsy)" + Ewouldblock -> "Operation would block (ewouldblock)" + Exbadport -> "Invalid exchange (exbadport)" + Exbadseq -> "Invalid sequence number (exbadseq)" + Exdev -> "Cross-device link (exdev)" + Timeout -> "Operation timed out (timeout)" + } +} diff --git a/src/tcp/tcp.gleam b/src/tcp/tcp.gleam new file mode 100644 index 0000000..6bc1976 --- /dev/null +++ b/src/tcp/tcp.gleam @@ -0,0 +1,57 @@ +import gleam/bytes_tree + +import tcp/reason.{type Reason} + +pub type Socket + +// https://www.erlang.org/doc/apps/kernel/inet#t:address_family/0 +// local socket +type Local { + Local(socket_path: String) +} + +type ModeValue { + Binary +} + +type TCPOption { + Active(Bool) + Mode(ModeValue) +} + +pub fn connect(socket_path: String) -> Result(Socket, Reason) { + let options = [Mode(Binary), Active(False)] + + // timeout in ms + let timeout = 1000 + + gen_tcp_connect(Local(socket_path), 0, options, timeout) +} + +pub fn send(socket: Socket, message: String) -> Result(Nil, Reason) { + gen_tcp_send(socket, bytes_tree.from_string(message)) +} + +pub fn close(socket: Socket) -> Nil { + gen_tcp_close(socket) +} + +// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#connect/4 +@external(erlang, "gen_tcp", "connect") +fn gen_tcp_connect( + address: Local, + port: Int, + options: List(TCPOption), + timeout: Int, +) -> Result(Socket, Reason) + +// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#send/2 +@external(erlang, "ipc_ffi", "send") +fn gen_tcp_send( + socket: Socket, + packet: bytes_tree.BytesTree, +) -> Result(Nil, Reason) + +// https://www.erlang.org/doc/apps/kernel/gen_tcp.html#close/1 +@external(erlang, "gen_tcp", "close") +fn gen_tcp_close(socket: Socket) -> Nil diff --git a/src/tcp/tcp_ffi.erl b/src/tcp/tcp_ffi.erl new file mode 100644 index 0000000..c651fec --- /dev/null +++ b/src/tcp/tcp_ffi.erl @@ -0,0 +1,8 @@ +-module(tcp_ffi). +-export([send/2]). + +send(Socket, Packet) -> + case gen_tcp:send(Socket, Packet) of + ok -> {ok, nil}; + Res -> Res + end.