2 Commits

Author SHA1 Message Date
Alexander Heldt
c76d1aaa53 Send some messages 2025-11-10 20:21:50 +01:00
Alexander Heldt
d69be40dcd Add tcp module 2025-11-10 20:21:50 +01:00
4 changed files with 342 additions and 1 deletions

View File

@@ -1,5 +1,41 @@
import gleam/io
import tcp/reason
import tcp/tcp
pub fn main() -> Nil {
io.println("Hello from musicplayer!")
let socket_path = "/tmp/musicplayer.sock"
case tcp.connect(socket_path) {
Error(r) ->
io.println("Failed to connect to socket: " <> reason.to_string(r))
Ok(socket) -> {
io.println("connected")
let messages = ["hello, \n", "world!\n"]
messages |> send_messages(socket, _)
io.println("closing")
tcp.close(socket)
}
}
}
fn send_messages(socket: tcp.Socket, messages: List(String)) -> Nil {
case messages {
[] -> Nil
[message, ..rest] -> {
send_message(socket, message)
send_messages(socket, rest)
}
}
}
fn send_message(socket: tcp.Socket, message: String) -> Nil {
case tcp.send(socket, message) {
Error(r) ->
io.println("Failed to send message to socket: " <> reason.to_string(r))
Ok(_) -> io.println("Sent message to socket")
}
}

240
src/tcp/reason.gleam Normal file
View File

@@ -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)"
}
}

57
src/tcp/tcp.gleam Normal file
View File

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

8
src/tcp/tcp_ffi.erl Normal file
View File

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