/*
* melisma - extensible and scriptable audio engine
* Copyright (C) 2026 Ludvig Gunne Lindström
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "melisma.h"
#include "melisma/ring.h"
#define SOCKADDR_TYPENAME "sockaddr"
#define UDP_SOCKET_TYPENAME "udp socket"
#define UDP_PACKET_SIZE 65536
struct udp_socket
{
int fd;
int family;
int event;
int bound;
int callb;
lua_State *state;
ml_ring *recvring;
ml_ring *sendring;
uint8_t pollbuf[UDP_PACKET_SIZE];
uint8_t procbuf[UDP_PACKET_SIZE];
};
// TODO: CHECK BUFFER SIZES FOR ADDRESSES
static int l_sockaddr_unix(lua_State *s)
{
size_t len;
const char *path = luaL_checklstring(s,1,&len);
struct sockaddr_storage *sa = lua_newuserdata(s,sizeof *sa);
luaL_setmetatable(s,SOCKADDR_TYPENAME);
memset(sa,0,sizeof *sa);
((struct sockaddr_un*)sa)->sun_family = AF_UNIX;
memcpy(((struct sockaddr_un*)sa)->sun_path,path,len);
return 1;
}
static int l_sockaddr_inet(lua_State *s)
{
const char *addr = luaL_checkstring(s,1);
int port = luaL_checkinteger(s,2);
struct in_addr in_addr;
if (inet_pton(AF_INET,addr,&in_addr) != 1)
{
luaL_error(s,"invalid ipv4 address '%s'",addr);
}
struct sockaddr_storage *sa = lua_newuserdata(s,sizeof *sa);
luaL_setmetatable(s,SOCKADDR_TYPENAME);
memset(sa,0,sizeof *sa);
((struct sockaddr_in*)sa)->sin_family = AF_INET;
((struct sockaddr_in*)sa)->sin_port = htons(port);
memcpy(&((struct sockaddr_in*)sa)->sin_addr,&in_addr,sizeof in_addr);
return 1;
}
static void recv_handler(int fd, int flags, void *args)
{
struct udp_socket *us = args;
ssize_t len = recvfrom(fd,us->pollbuf,sizeof us->pollbuf,0,NULL,NULL);
if (len < 0)
{
perror("recvfrom");
return;
}
if (!ml_ring_has_space(us->recvring,len + sizeof len))
{
fprintf(stderr,"dropped udp packet");
}
ml_ring_write_blocking(us->recvring,&len,sizeof len);
ml_ring_write_blocking(us->recvring,us->pollbuf,len);
}
static void send_handler(int fd, int flags, void *args)
{
struct udp_socket *us = args;
size_t sa_len,len;
struct sockaddr_storage storage = {0};
ml_ring_read_blocking(us->sendring,&sa_len,sizeof sa_len);
ml_ring_read_blocking(us->sendring,&storage,sa_len);
ml_ring_read_blocking(us->sendring,&len,sizeof len);
ml_ring_read_blocking(us->sendring,us->pollbuf,len);
if (sendto(us->fd,us->pollbuf,len,0,(struct sockaddr*) &storage,sa_len) < 0)
{
perror("sendto");
}
uint64_t x;
if (read(us->event,&x,sizeof x) != sizeof x)
;
}
static int l_socket(lua_State *s)
{
int family = lua_tointeger(s,lua_upvalueindex(1));
int fd = socket(family,SOCK_DGRAM,0);
if (fd < 0)
{
const char *err = strerror(errno);
luaL_error(s,"failed to create socket: %s",err);
}
struct udp_socket *us = calloc(1,sizeof(*us));
us->fd = fd;
us->family = family;
us->sendring = ml_ring_init(UDP_PACKET_SIZE);
us->event = eventfd(0,EFD_SEMAPHORE);
ml_add_io_handler(us->event,POLLIN,send_handler,us);
*(struct udp_socket**) lua_newuserdata(s,sizeof(struct udp_socket*)) = us;
luaL_setmetatable(s,UDP_SOCKET_TYPENAME);
return 1;
}
static const char *family_name(int family)
{
switch (family)
{
case AF_UNIX: return "unix";
case AF_INET: return "inet";
default: assert(0);
}
}
static void proc(const ml_proc_args *args)
{
struct udp_socket *us = args->userp;
ssize_t len;
while (!ml_ring_empty(us->recvring))
{
ml_ring_read_blocking(us->recvring,&len,sizeof len);
ml_ring_read_blocking(us->recvring,us->procbuf,len);
lua_pushlstring(us->state,(const char*)us->procbuf,len);
int arg = luaL_ref(us->state,LUA_REGISTRYINDEX);
ml_schedule_event(0,us->callb,arg);
}
}
static size_t get_sockaddr_len(struct sockaddr *sa)
{
if (sa->sa_family == AF_UNIX)
{
const char *path = ((struct sockaddr_un*)sa)->sun_path;
if (path[0] == 0 && path[1] != 0)
{
return offsetof(struct sockaddr_un,sun_path) + strlen(path+1) + 1;
}
return offsetof(struct sockaddr_un,sun_path) + strlen(path);
}
else
{
return sizeof(struct sockaddr_in);
}
}
static void check_family(lua_State *s, struct udp_socket *us, struct sockaddr *sa)
{
if (us->family != sa->sa_family)
{
luaL_error(s,"sockaddr address family (%s) does not match socket address family (%s)",
family_name(sa->sa_family), family_name(us->family));
}
}
static int l_bind(lua_State *s)
{
struct udp_socket *us = *(struct udp_socket**) luaL_checkudata(s,1,UDP_SOCKET_TYPENAME);
struct sockaddr *sa = luaL_checkudata(s,2,SOCKADDR_TYPENAME);
luaL_checktype(s,3,LUA_TFUNCTION);
if (us->bound)
{
luaL_error(s,"socket is already bound");
}
check_family(s,us,sa);
size_t len = get_sockaddr_len(sa);
if (bind(us->fd,sa,len) < 0)
{
const char *err = strerror(errno);
luaL_error(s,"failed to bind socket: %s",err);
}
us->bound = 1;
us->recvring = ml_ring_init(UDP_PACKET_SIZE);
us->state = s;
ml_add_io_handler(us->fd,POLLIN,recv_handler,us);
lua_pushvalue(s,3);
us->callb = luaL_ref(s,LUA_REGISTRYINDEX);
ml_node *node = ml_create_node(proc,us);
ml_channel *src = ml_add_output(node);
ml_channel *dst = ml_add_input(ml_get_root_node());
ml_connect(src,dst);
return 0;
}
static int l_send(lua_State *s)
{
struct udp_socket *us = *(struct udp_socket**) luaL_checkudata(s,1,UDP_SOCKET_TYPENAME);
struct sockaddr *sa = luaL_checkudata(s,2,SOCKADDR_TYPENAME);
size_t len;
const char *data = luaL_checklstring(s,3,&len);
check_family(s,us,sa);
size_t sa_len = get_sockaddr_len(sa);
size_t full_len = sizeof len + len + sizeof sa_len + sa_len;
if (!ml_ring_has_space(us->sendring,full_len))
{
lua_pushboolean(s,0);
return 1;
}
ml_ring_write_blocking(us->sendring,&sa_len,sizeof sa_len);
ml_ring_write_blocking(us->sendring,sa,sa_len);
ml_ring_write_blocking(us->sendring,&len,sizeof len);
ml_ring_write_blocking(us->sendring,data,len);
uint64_t x = 1;
if (write(us->event,&x,sizeof x) < sizeof x)
;
lua_pushboolean(s,1);
return 1;
}
int luaopen_melisma_udp(lua_State *s)
{
lua_newtable(s);
int tbl = lua_gettop(s);
/*** UDP support.
local mudp = require 'melisma.udp'
local addr = mudp.sockaddr.inet('127.0.0.1',8000)
local sock = mudp.socket.inet()
sock:send(addr,'hello')
sock:bind(addr,print)
@module udp
*/
/*** Socket address description.
@type sockaddr
*/
lua_newtable(s);
int sockaddr_tbl = lua_gettop(s);
/*** Create a unix domain address description.
@static
@function unix
@tparam string path
@treturn sockaddr
*/
lua_pushcfunction(s,l_sockaddr_unix);
lua_setfield(s,sockaddr_tbl,"unix");
/*** Create a internet domain address description.
@static
@function inet
@tparam string host
@tparam integer port
@treturn sockaddr
*/
lua_pushcfunction(s,l_sockaddr_inet);
lua_setfield(s,sockaddr_tbl,"inet");
lua_pushvalue(s,sockaddr_tbl);
lua_setfield(s,tbl,"sockaddr");
luaL_newmetatable(s,SOCKADDR_TYPENAME);
int sockaddr_mt = lua_gettop(s);
lua_pushvalue(s,sockaddr_tbl);
lua_setfield(s,sockaddr_mt,"__index");
/*** Handle to a socket.
@type socket
*/
lua_newtable(s);
int socket_tbl = lua_gettop(s);
/*** Create a unix domain socket.
@static
@function unix
@treturn socket
*/
lua_pushinteger(s,AF_UNIX);
lua_pushcclosure(s,l_socket,1);
lua_setfield(s,socket_tbl,"unix");
/*** Create an internet domain socket.
@static
@function inet
@treturn socket
*/
lua_pushinteger(s,AF_INET);
lua_pushcclosure(s,l_socket,1);
lua_setfield(s,socket_tbl,"inet");
/*** Bind a socket to an address.
@function bind
@tparam sockaddr addr Address description.
@tparam function fn Packet callback function.
*/
lua_pushcfunction(s,l_bind);
lua_setfield(s,socket_tbl,"bind");
/*** Send a packet on a socket.
@function send
@tparam sockaddr addr Address description.
@tparam string pkt Packet to send.
*/
lua_pushcfunction(s,l_send);
lua_setfield(s,socket_tbl,"send");
lua_pushvalue(s,socket_tbl);
lua_setfield(s,tbl,"socket");
luaL_newmetatable(s,UDP_SOCKET_TYPENAME);
int socket_mt = lua_gettop(s);
lua_pushvalue(s,socket_tbl);
lua_setfield(s,socket_mt,"__index");
lua_settop(s,tbl);
return 1;
}