~aleteoryx/nex.nelua

ref: 2716ae08bc3884cc8a26b95b6f858b1dcddb7c99 nex.nelua/socket.nelua -rw-r--r-- 5.6 KiB
2716ae08Aleteoryx initial commit, with a lot done already 18 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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)