--[[
an asynchronous socket library.
if i feel like making a full executor work this might get more complicated,
but for now it's a standalone function.
]]
-- c imports
## cinclude '<sys/socket.h>'
local AF_INET: cint <cimport, nodecl>
local AF_INET6: cint <cimport, nodecl>
-- local AF_UNIX: cint <cimport, nodecl>
local SOCK_STREAM: cint <cimport, nodecl>
-- local SOCK_DGRAM: cint <cimport, nodecl>
-- local SOCK_RAW: cint <cimport, nodecl>
local MSG_DONTWAIT: cint <cimport, nodecl>
local function c_socket(domain: cint, type: cint, protocol: cint): cint <cimport 'socket', nodecl> end
local function c_accept(fd: cint, address: pointer, address_len: *cint): cint <cimport 'accept', nodecl> end
local function c_listen(fd: cint, backlog: cint): cint <cimport 'listen', nodecl> end
local function c_send(fd: cint, buf: *[0]byte, len: usize, flags: cint): isize <cimport 'send', nodecl> end
## cinclude '<arpa/inet.h>'
local function c_inet_pton(af: cint, src: cstring <const>, dst: pointer): cint <cimport 'inet_pton', nodecl> end
local function c_htons(hostshort: uint16): uint16 <cimport 'htons'> end
local function c_ntohs(hostshort: uint16): uint16 <cimport 'ntohs'> end
## cinclude '<sys/epoll.h>'
local EPOLL_CLOEXEC: cint <cimport, nodecl>
local EPOLL_CTL_ADD: cint <cimport, nodecl>
local EPOLL_CTL_MOD: cint <cimport, nodecl>
local EPOLL_CTL_DEL: cint <cimport, nodecl>
## for i,epoll_val in ipairs({ 'IN', 'OUT', 'RDHUP', 'PRI', 'ERR', 'HUP', 'ET'}) do
local #|'EPOLL' .. epoll_val|# : uint32 <cimport, nodecl>
## end
local epoll_data = @union{
ptr: pointer,
fd: cint,
u32: uint32,
u64: uint64
}
local epoll_event = @record{
events: uint32,
data: epoll_data
}
local function c_epoll_create(flags: cint): cint <cimport 'epoll_create1', nodecl> end
local function c_epoll_ctl(epfd: cint, op: cint, fd: cint, event: *epoll_event): cint <cimport 'epoll_ctl', nodecl> end
local function c_epoll_wait(epfd: cint, events: *[0]epoll_event, maxevents: cint, timeout: cint): cint <cimport 'epoll_wait', nodecl> end
## cinclude '<unistd.h>'
local function c_close(fd: cint): cint <cimport 'close', nodecl> end
## cinclude '<netinet/in.h>'
-- end c imports
require 'string'
require 'coroutine'
require 'math'
require 'C.stdio'
require 'allocators.pool'
local function fakeuse(...: varargs) end
-- 192.168.2.1:2000 -> 192.168.2.1,2000
local inet_re = '^(%d?%d?%d%.%d?%d?%d%.%d?%d?%d%.%d?%d?%d):(%d?%d?%d?%d?%d)$'
-- {::1}:2000 -> ::1,2000
local inet6_re = '^{([^}]+)}:(%d?%d?%d?%d?%d)$'
local function die_errno(cond: boolean, msg: string): void
if cond then
C.perror(nilptr)
error(msg)
end
end
local handler_req = @enum{
init = 0,
read_line,
write,
close
}
local handler_state = @record{
fd: cint,
co: coroutine,
last_req: handler_req
}
function handler_state:step(epfd: cint, state: *handler_state): (boolean)
return false
end
local function listen_sock(sock: cint, handler: function(): void): void
die_errno(c_listen(sock, 32) ~= 0, "couldn't listen on TCP socket")
local epfd = c_epoll_create(0)
die_errno(epfd == -1, "couldn't create epoll instance")
-- opt out of GC because we give references to the kernel
local alloc: PoolAllocator(@handler_state, 1024) -- probably overkill on max size
local sock_event: epoll_event = {
events = EPOLLIN,
data = { ptr = nilptr }
}
c_epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &sock_event)
while true do
local maxevents <comptime> = 8
local events: [maxevents]epoll_event = {}
local event_count = c_epoll_wait(epfd, &events, maxevents, -1)
die_errno(event_count == -1, "couldn't wait on epoll instance")
print("got events from epoll_wait:", event_count)
for i = 0, < event_count do
if events[i].data.ptr == nilptr then
local fd = c_accept(sock, nilptr, nilptr)
local state = alloc:new(@handler_state)
if state == nilptr then -- drop connections if allocation fails
local errmsg = "The server is overloaded, please try again later.\n"
c_send(fd, errmsg.data, errmsg.size, 0)
c_close(fd)
continue
end
state.fd = fd
state.co = coroutine.create(handler)
state.last_req = handler_req.init
local ok, events = state:step()
if ok then
local fd_event: epoll_event = {
events = events,
data = { ptr = (@pointer)(state) }
}
c_epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &fd_event)
else
local errmsg = "Internal error, please try again later.\n"
c_send(fd, errmsg.data, errmsg.size, 0)
c_close(fd)
alloc:delete()
end
else
print("TODO")
end
end
end
end
local function listen_tcp(address: string, handler: function(): void): void
local matched, matches = string.match(address, inet_re)
if matched then
local c_err: cint = 0
local s_addr, s_port = matches:unpack(1, 2)
local addr: uint32
die_errno(c_inet_pton(AF_INET, (@cstring)(s_addr), &addr) <= 0,
"bad IPv4 address")
local i_port = tointeger(s_port)
assert((i_port >= 0) and (i_port < 65535), "port not within range [0,65536)")
local port: uint16 = c_htons(i_port)
local fd = c_socket(AF_INET, SOCK_STREAM, 0)
die_errno(fd == -1, "couldn't open TCP socket")
fakeuse(port)
##[==[ cemit [[
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = port;
sa.sin_addr.s_addr = addr;
c_err = bind(fd, &sa, sizeof(sa));
]] ]==]
die_errno(c_err == -1, "couldn't bind TCP socket")
listen_sock(fd, handler)
end
end
listen_tcp("127.0.0.1:1900", function() end)