Skip to content

Commit e77ea78

Browse files
committed
work on socket options
1 parent 1d8229c commit e77ea78

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

ljsocket.lua

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,16 @@ do
878878
e.SO_KEEPALIVE = 0x0008
879879
e.SO_DONTROUTE = 0x0010
880880
e.SO_BROADCAST = 0x0020
881+
e.SO_LINGER = 0x0080
882+
e.SO_OOBINLINE = 0x0100
883+
e.SO_SNDBUF = 0x1001
884+
e.SO_RCVBUF = 0x1002
885+
e.SO_SNDLOWAT = 0x1003
886+
e.SO_RCVLOWAT = 0x1004
887+
e.SO_SNDTIMEO = 0x1005
888+
e.SO_RCVTIMEO = 0x1006
889+
e.SO_ERROR = 0x1007
890+
e.SO_TYPE = 0x1008
881891
errno.EINVAL = 22
882892
errno.EAGAIN = 35
883893
errno.EWOULDBLOCK = errno.EAGAIN
@@ -1253,6 +1263,54 @@ do
12531263
)
12541264
end
12551265

1266+
function meta:get_option(key, level)
1267+
level = level or "socket"
1268+
1269+
local env = SO
1270+
1271+
if level == "tcp" then env = TCP end
1272+
1273+
local val
1274+
local size
1275+
1276+
-- Determine the appropriate type and size for the option
1277+
if key:lower() == "rcvtimeo" or key:lower() == "sndtimeo" then
1278+
if ffi.os == "Windows" then
1279+
val = ffi.new("int[1]")
1280+
size = ffi.new("uint32_t[1]", ffi.sizeof("int"))
1281+
else
1282+
val = timeval()
1283+
size = ffi.new("uint32_t[1]", ffi.sizeof(timeval))
1284+
end
1285+
else
1286+
-- Default to int for most socket options
1287+
val = ffi.new("int[1]")
1288+
size = ffi.new("uint32_t[1]", ffi.sizeof("int"))
1289+
end
1290+
1291+
local ok, err, num = socket.getsockopt(
1292+
self.fd,
1293+
SOL.strict_lookup(level),
1294+
env.strict_lookup(key),
1295+
ffi.cast("void *", val),
1296+
size
1297+
)
1298+
1299+
if not ok then return ok, err, num end
1300+
1301+
-- Convert the result based on the option type
1302+
if key:lower() == "rcvtimeo" or key:lower() == "sndtimeo" then
1303+
if ffi.os == "Windows" then
1304+
return val[0]
1305+
else
1306+
return val.tv_usec / 1000
1307+
end
1308+
else
1309+
-- Return as number for most options
1310+
return val[0]
1311+
end
1312+
end
1313+
12561314
function meta:settimeout(t)
12571315
self:set_option("rcvtimeo", t)
12581316
end
@@ -1476,4 +1534,8 @@ do
14761534
end
14771535
end
14781536

1537+
M.e = e
1538+
M.errno = errno
1539+
M.socket = socket
1540+
14791541
return M

test/options.lua

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
local socket = require("ljsocket")
2+
local ffi = require("ffi")
3+
4+
-- Create a socket for testing
5+
local sock = socket.create("inet", "stream", "tcp")
6+
assert(sock, "Failed to create socket")
7+
8+
-- Platform-specific expected values
9+
-- On macOS, boolean options return their SO_* constant value when enabled
10+
local function get_expected_enabled_value(option_name)
11+
if ffi.os == "OSX" then
12+
return socket.e["SO_" .. option_name:upper()]
13+
else
14+
return 1 -- Most other platforms return 1
15+
end
16+
end
17+
18+
do -- SO_REUSEADDR set and get
19+
local ok, err = sock:set_option("reuseaddr", 1)
20+
assert(ok, "Failed to set reuseaddr: " .. tostring(err))
21+
22+
local value, err = sock:get_option("reuseaddr")
23+
assert(value, "Failed to get reuseaddr: " .. tostring(err))
24+
25+
local expected = get_expected_enabled_value("reuseaddr")
26+
print(" Set: 1, Got: " .. tostring(value) .. ", Expected: " .. expected)
27+
assert(value == expected, "SO_REUSEADDR mismatch")
28+
end
29+
30+
do -- SO_REUSEADDR toggle off
31+
local ok, err = sock:set_option("reuseaddr", 0)
32+
assert(ok, "Failed to set reuseaddr to 0: " .. tostring(err))
33+
34+
local value, err = sock:get_option("reuseaddr")
35+
assert(value, "Failed to get reuseaddr: " .. tostring(err))
36+
print(" Set: 0, Got: " .. tostring(value))
37+
assert(value == 0, "SO_REUSEADDR should be 0 when disabled")
38+
39+
-- Set it back to 1
40+
sock:set_option("reuseaddr", 1)
41+
end
42+
43+
do -- SO_KEEPALIVE
44+
local ok, err = sock:set_option("keepalive", 1)
45+
assert(ok, "Failed to set keepalive: " .. tostring(err))
46+
47+
local value, err = sock:get_option("keepalive")
48+
assert(value, "Failed to get keepalive: " .. tostring(err))
49+
50+
local expected = get_expected_enabled_value("keepalive")
51+
print(" Set: 1, Got: " .. tostring(value) .. ", Expected: " .. expected)
52+
assert(value == expected, "SO_KEEPALIVE mismatch")
53+
end
54+
55+
do -- SO_SNDBUF
56+
-- First get the default value
57+
local default_value, err = sock:get_option("sndbuf")
58+
assert(default_value, "Failed to get default sndbuf: " .. tostring(err))
59+
print(" Default sndbuf: " .. tostring(default_value))
60+
61+
-- Try to set a new value
62+
local new_size = 65536
63+
local ok, err = sock:set_option("sndbuf", new_size)
64+
assert(ok, "Failed to set sndbuf: " .. tostring(err))
65+
66+
-- Get it back
67+
local value, err = sock:get_option("sndbuf")
68+
assert(value, "Failed to get sndbuf after set: " .. tostring(err))
69+
print(" Set: " .. new_size .. ", Got: " .. tostring(value))
70+
-- Note: OS may adjust the value (often doubles it), so we just check it's not nil and > 0
71+
assert(value > 0, "SO_SNDBUF should be greater than 0")
72+
assert(value >= new_size or value >= new_size/2, "SO_SNDBUF should be at least half the requested size")
73+
end
74+
75+
do -- SO_RCVBUF
76+
-- First get the default value
77+
local default_value, err = sock:get_option("rcvbuf")
78+
assert(default_value, "Failed to get default rcvbuf: " .. tostring(err))
79+
print(" Default rcvbuf: " .. tostring(default_value))
80+
81+
-- Try to set a new value
82+
local new_size = 65536
83+
local ok, err = sock:set_option("rcvbuf", new_size)
84+
assert(ok, "Failed to set rcvbuf: " .. tostring(err))
85+
86+
-- Get it back
87+
local value, err = sock:get_option("rcvbuf")
88+
assert(value, "Failed to get rcvbuf after set: " .. tostring(err))
89+
print(" Set: " .. new_size .. ", Got: " .. tostring(value))
90+
assert(value > 0, "SO_RCVBUF should be greater than 0")
91+
assert(value >= new_size or value >= new_size/2, "SO_RCVBUF should be at least half the requested size")
92+
end
93+
94+
do -- SO_BROADCAST
95+
local ok, err = sock:set_option("broadcast", 1)
96+
assert(ok, "Failed to set broadcast: " .. tostring(err))
97+
98+
local value, err = sock:get_option("broadcast")
99+
assert(value, "Failed to get broadcast: " .. tostring(err))
100+
101+
local expected = get_expected_enabled_value("broadcast")
102+
print(" Set: 1, Got: " .. tostring(value) .. ", Expected: " .. expected)
103+
assert(value == expected, "SO_BROADCAST mismatch")
104+
end
105+
106+
do -- SO_TYPE (read-only)
107+
local value, err = sock:get_option("type")
108+
assert(value, "Failed to get socket type: " .. tostring(err))
109+
assert(value == socket.e.SOCK_STREAM, "SO_TYPE should be SOCK_STREAM")
110+
end
111+
112+
do -- SO_ERROR (read-only)
113+
local value, err = sock:get_option("error")
114+
assert(value, "Failed to get socket error: " .. tostring(err))
115+
assert(value == 0, "SO_ERROR should be 0 for a healthy socket")
116+
end
117+
118+
for i = 1, 5 do -- Multiple toggles of boolean option
119+
-- Toggle on
120+
sock:set_option("reuseaddr", 1)
121+
local val_on = sock:get_option("reuseaddr")
122+
assert(val_on == get_expected_enabled_value("reuseaddr"),
123+
"Iteration " .. i .. ": ON value mismatch")
124+
125+
-- Toggle off
126+
sock:set_option("reuseaddr", 0)
127+
local val_off = sock:get_option("reuseaddr")
128+
assert(val_off == 0, "Iteration " .. i .. ": OFF value should be 0")
129+
end
130+
131+
-- Clean up
132+
sock:close()

0 commit comments

Comments
 (0)