-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsocket.lua
More file actions
216 lines (188 loc) · 5.06 KB
/
socket.lua
File metadata and controls
216 lines (188 loc) · 5.06 KB
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
local M = {}; M.__index = M
require('log')
local function construct()
local self = setmetatable({
fd = -1
}, M)
return self
end
setmetatable(M, {__call = construct})
local ffi = require('ffi')
-- This file is ultimately a losing battle, every OS is going to implement this stuff slightly differently
-- But it'll do to get things started (on OS X)
-- Later, this should just be a C module itself with a configure script or WHATEVER
-- Something that generated this block from system headers could work too. I do like not having any actual C.
-- Tested on Linux and it seems to work just fine with only a couple of constants changed for SO_REUSEADDR.
-- Maybe I'll just keep it unless someone complains.
ffi.cdef [[
typedef size_t socklen_t;
typedef unsigned int nfds_t;
typedef struct {
uint8_t sa_len;
unsigned char sa_family;
unsigned char sa_data[14];
} sockaddr;
typedef struct {
uint8_t sin_len;
uint8_t sin_family;
uint16_t sin_port;
uint32_t sin_addr;
char sin_zero[8];
} sockaddr_in;
typedef struct {
int fd;
short events;
short revents;
} pollfd;
int socket(int domain, int type, int protocol);
int bind(int socket, const sockaddr_in *address, socklen_t address_len);
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *restrict address, socklen_t *address_len);
size_t read(int fildes, void *buf, size_t nbyte);
size_t write(int fildes, const void *buf, size_t nbyte);
int close(int fd);
int fcntl(int fd, int cmd, ...);
int poll(pollfd *fds, nfds_t nfds, int timeout);
int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);
]]
PF_INET = 2
SOCK_STREAM = 1
POLLIN=0x0001
POLLPRI=0x0002
POLLOUT=0x0004
POLLRDNORM=0x0040
POLLWRNORM=POLLOUT
POLLRDBAND=0x0080
POLLWRBAND=0x0100
POLLERR=0x0008
POLLHUP=0x0010
POLLNVAL=0x0020
O_NONBLOCK=0x0004
F_GETFL=3
F_SETFL=4
if ffi.os == 'Linux' then
SO_REUSEADDR=2
SOL_SOCKET=1
elseif ffi.os == 'OSX' then
SO_REUSEADDR=4
SOL_SOCKET=0xffff
else
print("Hey! I don't know what platform you're on, go add your platform values to socket.lua!")
end
local sockaddr_in
local mt = {}
sockaddr_in = ffi.metatype('sockaddr_in', mt)
pollfd = ffi.metatype('pollfd', mt)
--
-- Internal functions
--
local bit = require('bit')
local function htons(num)
return bit.bor(bit.lshift(bit.band(num, 0xff), 8), bit.rshift(bit.band(num, 0xff00), 8))
end
function nonblock(fd)
flags = ffi.C.fcntl(fd, F_GETFL, ffi.new("int", 0))
if flags == -1 then
flags = 0
end
ffi.C.fcntl(fd, F_SETFL, ffi.new("int", bit.bor(flags, O_NONBLOCK)))
end
function M:listen(addr, port)
self.fd = ffi.C.socket(PF_INET, SOCK_STREAM, 0)
if self.fd < 0 then
perror('socket')
return false
end
local val = ffi.new("int[1]", 1)
if ffi.C.setsockopt(self.fd, SOL_SOCKET, SO_REUSEADDR, val, ffi.sizeof("int")) ~= 0 then
perror('setsockopt '..ffi.errno()..' ')
end
local addr = sockaddr_in(16, PF_INET, htons(port), addr, '\0\0\0\0\0\0\0\0')
res = ffi.C.bind(self.fd, addr, 16)
if res ~= 0 then
perror('bind')
return false
end
res = ffi.C.listen(self.fd, 100)
if res ~= 0 then
perror('listen')
return false
end
nonblock(self.fd)
return true
end
-- take an array of sockets (this lua structure variety)
-- return an array of sockets with pending events
function M.poll(socks, timeout)
local fds = ffi.new('pollfd[?]', #socks)
for i=0,#socks - 1 do
fds[i].fd = socks[i+1].fd
fds[i].events = bit.bor(bit.bor(POLLIN, POLLERR), POLLHUP)
fds[i].revents = 0 -- Not necessary, ffi.new zeros memory
end
res = ffi.C.poll(fds, #socks, timeout)
local retsocks = {}
for i=0,#socks - 1 do
if bit.band(fds[i].revents, POLLIN) ~= 0 then
retsocks[#retsocks + 1] = socks[i + 1]
end
end
return retsocks
end
--function M:poll(timeout)
-- local pfd = pollfd(self.fd, POLLIN, 0)
-- res = ffi.C.poll(pfd, 1, timeout)
-- return res > 0
--end
function M:accept()
newsock = construct()
newsock.fd = ffi.C.accept(self.fd, nil, nil)
nonblock(newsock.fd)
return newsock
end
function M:read(len)
buffer = ffi.new("char[?]", len)
size = ffi.C.read(self.fd, buffer, len)
return size, ffi.string(buffer)
end
function M:readline(len)
-- This seems really really dumb (inefficient), FIXME later (build it into the C module when there is one)
-- Or at least do buffered reads and smartly build the line up
local cbuf = ffi.new("char[1]")
local line = ''
local state = 0
local done = false
while not done do
local size = ffi.C.read(self.fd, cbuf, 1)
if size < 0 then
return nil
elseif size == 1 then
local c = ffi.string(cbuf, 1)
if state == 0 then
if c == '\r' then
state = 1
elseif c == '\n' then
-- Something's behaving badly sending just a \n line ending, but useful for testing with netcat,
-- and probably wise in general
done=true
else
line = line..c
end
elseif state == 1 then
if c == '\n' then
done=true
else
state = 0
end
end
end
end
return line
end
function M:write(str)
ffi.C.write(self.fd, str, #str)
end
function M:close()
ffi.C.close(self.fd)
end
return M