-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsocket.lua
More file actions
177 lines (155 loc) · 5.22 KB
/
socket.lua
File metadata and controls
177 lines (155 loc) · 5.22 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
local cur_path = (...):match("(.-)[^%(.|/)]+$")
local ffi = require("ffi")
local C = ffi.C
-- We may already have some of these thanks to koreader-base ffi modules,
-- so we load each symbol one-by-one in a protected call...
require(cur_path .. "consts_h")
require(cur_path .. "socket_h")
require(cur_path .. "poll_h")
local sockaddr_pt = ffi.typeof("struct sockaddr *")
-- To match FD_ISSET behavior with poll
local POLLIN_SET = bit.bor(C.POLLRDNORM, C.POLLRDBAND, C.POLLIN, C.POLLHUP, C.POLLERR)
local Socket = {
AF_UNIX = C.AF_UNIX,
SOCK_DGRAM = C.SOCK_DGRAM,
__index = {},
}
function Socket.new(domain, stype, protocol)
local instance = {
fd = C.socket(domain, bit.bor(stype, C.SOCK_NONBLOCK, C.SOCK_CLOEXEC), protocol),
}
if instance.fd < 0 then
return nil
else
return setmetatable(instance, Socket)
end
end
function Socket.__index:connect(saddr, saddr_type)
while true do
local re = C.connect(self.fd, ffi.cast(sockaddr_pt, saddr), ffi.sizeof(saddr_type))
if re == 0 then
return 0
elseif re == -1 then
if re == C.EISCONN then
-- Already connected (connect() race)
return 0
elseif re ~= C.EINTR then
-- Actual error, otherwise, retry on EINTR
return re
end
end
end
end
function Socket.__index:bind(saddr, saddr_type)
return C.bind(self.fd, ffi.cast(sockaddr_pt, saddr), ffi.sizeof(saddr_type))
end
function Socket.__index:close()
return C.close(self.fd)
end
function Socket.__index:send(buf, len, flags)
local pos = 0
while len > pos do
-- NOTE: buf is a Lua string, so this isn't as nice as with real pointer arithmetic...
local nw = C.send(self.fd, pos == 0 and buf or buf:sub(1 + pos), len - pos, bit.bor(flags, C.MSG_NOSIGNAL))
if nw == -1 then
local errno = ffi.errno()
if errno ~= C.EINTR then
if errno == C.EAGAIN then
local pfd = ffi.new("struct pollfd")
pfd.fd = self.fd
pfd.events = C.POLLOUT
C.poll(pfd, 1, -1)
-- Back to send
else
-- Actual error :(
return -1
end
end
-- EINTR: Back to send
else
pos = pos + nw
end
end
return pos
end
function Socket.__index:recv(buf, len, flags)
local re = C.recv(self.fd, buf, len, flags)
if re < 0 then
return nil, re
else
return ffi.string(buf, re), re
end
end
function Socket.__index:canRead(timeout)
local pfd = ffi.new("struct pollfd")
pfd.fd = self.fd
pfd.events = C.POLLIN
local re = C.poll(pfd, 1, timeout or 0)
if re > 0 and bit.band(pfd.revents, POLLIN_SET) ~= 0 then
-- We've got something to read!
return true
end
return false
end
function Socket.__index:recvAll(flags, event_queue)
-- NOTE: Length stolen from https://w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface.h#n15
local buf_len = 8192 + 1
local buf = ffi.new("unsigned char[?]", buf_len)
local full_buf = {}
local full_buf_len = 0
local pfd = ffi.new("struct pollfd")
pfd.fd = self.fd
pfd.events = C.POLLIN
while true do
-- No timeout, we handle retries at a higher level, where appropriate
-- (e.g., WpaClient:scanThenGetResults & WpaClient:sendCmd).
local re = C.poll(pfd, 1, 0)
if re == -1 then
local errno = ffi.errno()
if errno ~= C.EINTR then
return nil, re
end
-- EINTR: Back to poll
elseif re > 0 then
if bit.band(pfd.revents, POLLIN_SET) ~= 0 then
local data
data, re = self:recv(buf, buf_len, flags)
if re < 0 then
local errno = ffi.errno()
if errno ~= C.EINTR and errno ~= C.EAGAIN then
return nil, re
end
-- EINTR or EAGAIN: Back to poll
else
full_buf_len = full_buf_len + re
if data:sub(1, 1) == "<" then
-- Record unsolicited messages in event_queue for later use
event_queue:parse(data)
elseif data:sub(1, 7) == "IFNAME=" then
-- Ditto
event_queue:parse_ifname(data)
else
table.insert(full_buf, data)
-- Break on control command replies
if re > 0 then
if data == "OK\n"
or data:sub(1, 4) == "FAIL" then
-- We're done
break
end
end
end
end
end
elseif re == 0 then
-- Timeout or nothing to read
break
end
end
return table.concat(full_buf), full_buf_len
end
function Socket.__index:closeOnError(msg)
C.close(self.fd)
return msg
end
return Socket