@@ 1,190 @@
+--[[
+ 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)