Socket.lua (7761B)
1 -- Socket Abstraction Layer 2 -- Provides a unified BSD-like socket interface for TCP and UDP 3 4 local Socket = {} 5 6 -- Socket types 7 Socket.TYPE = { 8 TCP = 1, 9 UDP = 2, 10 } 11 12 -- Socket states 13 Socket.STATE = { 14 CLOSED = 0, 15 BOUND = 1, 16 LISTENING = 2, 17 CONNECTING = 3, 18 CONNECTED = 4, 19 } 20 21 ---Create a new socket 22 ---@param NetworkStack table Network stack instance 23 ---@param type number Socket type (Socket.TYPE.TCP or Socket.TYPE.UDP) 24 ---@return table socket Socket object 25 function Socket.create(NetworkStack, type) 26 local socket = { 27 type = type, 28 state = Socket.STATE.CLOSED, 29 NetworkStack = NetworkStack, 30 local_port = nil, 31 remote_ip = nil, 32 remote_port = nil, 33 connection = nil, -- For TCP 34 receive_queue = {}, 35 callbacks = {}, 36 } 37 38 -- Add methods 39 socket.bind = function(self, port) 40 return Socket.bind(self, port) 41 end 42 43 socket.connect = function(self, ip, port) 44 return Socket.connect(self, ip, port) 45 end 46 47 socket.send = function(self, data) 48 return Socket.send(self, data) 49 end 50 51 socket.recv = function(self, max_len) 52 return Socket.recv(self, max_len) 53 end 54 55 socket.close = function(self) 56 return Socket.close(self) 57 end 58 59 socket.on = function(self, event, callback) 60 return Socket.on(self, event, callback) 61 end 62 63 return socket 64 end 65 66 ---Bind socket to a local port 67 ---@param socket table Socket object 68 ---@param port number Port to bind to 69 ---@return boolean success True if bound successfully 70 function Socket.bind(socket, port) 71 if socket.state ~= Socket.STATE.CLOSED then 72 return false 73 end 74 75 if socket.type == Socket.TYPE.UDP then 76 -- Bind UDP socket 77 local result = socket.NetworkStack.udp_bind(port, function(src_ip, src_port, data) 78 table.insert(socket.receive_queue, { 79 from_ip = src_ip, 80 from_port = src_port, 81 data = data 82 }) 83 84 if socket.callbacks.data then 85 socket.callbacks.data(src_ip, src_port, data) 86 end 87 end) 88 89 if result then 90 socket.local_port = port 91 socket.state = Socket.STATE.BOUND 92 return true 93 end 94 elseif socket.type == Socket.TYPE.TCP then 95 -- TCP bind (for listening) - not fully implemented 96 socket.local_port = port 97 socket.state = Socket.STATE.BOUND 98 return true 99 end 100 101 return false 102 end 103 104 ---Connect socket to remote address (TCP only) 105 ---@param socket table Socket object 106 ---@param ip table|string Remote IP address 107 ---@param port number Remote port 108 ---@return boolean success True if connection initiated 109 function Socket.connect(socket, ip, port) 110 if socket.type ~= Socket.TYPE.TCP then 111 return false 112 end 113 114 if socket.state ~= Socket.STATE.CLOSED and socket.state ~= Socket.STATE.BOUND then 115 return false 116 end 117 118 -- Parse IP if string 119 if type(ip) == "string" then 120 ip = socket.NetworkStack.parse_ip(ip) 121 end 122 123 if not ip then 124 return false 125 end 126 127 -- Load TCP if not loaded 128 if not socket.NetworkStack.TCP then 129 socket.NetworkStack.load_tcp() 130 end 131 132 if not socket.NetworkStack.TCP then 133 return false 134 end 135 136 -- Create TCP connection 137 socket.connection = socket.NetworkStack.TCP.connect( 138 socket.NetworkStack, ip, port, socket.local_port 139 ) 140 141 if not socket.connection then 142 return false 143 end 144 145 -- Set up callbacks 146 socket.connection.callbacks.on_connected = function() 147 socket.state = Socket.STATE.CONNECTED 148 if socket.callbacks.connected then 149 socket.callbacks.connected() 150 end 151 end 152 153 socket.connection.callbacks.on_data = function(data) 154 table.insert(socket.receive_queue, {data = data}) 155 if socket.callbacks.data then 156 socket.callbacks.data(data) 157 end 158 end 159 160 socket.connection.callbacks.on_closed = function() 161 socket.state = Socket.STATE.CLOSED 162 if socket.callbacks.closed then 163 socket.callbacks.closed() 164 end 165 end 166 167 socket.remote_ip = ip 168 socket.remote_port = port 169 socket.state = Socket.STATE.CONNECTING 170 socket.local_port = socket.connection.src_port 171 172 return true 173 end 174 175 ---Send data through socket 176 ---@param socket table Socket object 177 ---@param data string Data to send 178 ---@param dst_ip table|nil Destination IP (UDP only) 179 ---@param dst_port number|nil Destination port (UDP only) 180 ---@return boolean success True if sent successfully 181 function Socket.send(socket, data, dst_ip, dst_port) 182 if socket.type == Socket.TYPE.TCP then 183 if socket.state ~= Socket.STATE.CONNECTED then 184 return false 185 end 186 187 return socket.NetworkStack.TCP.send(socket.connection, data) 188 elseif socket.type == Socket.TYPE.UDP then 189 if socket.state ~= Socket.STATE.BOUND and socket.state ~= Socket.STATE.CONNECTED then 190 return false 191 end 192 193 -- Use provided destination or default 194 dst_ip = dst_ip or socket.remote_ip 195 dst_port = dst_port or socket.remote_port 196 197 if not dst_ip or not dst_port then 198 return false 199 end 200 201 -- Parse IP if string 202 if type(dst_ip) == "string" then 203 dst_ip = socket.NetworkStack.parse_ip(dst_ip) 204 end 205 206 if not dst_ip then 207 return false 208 end 209 210 local src_port = socket.local_port or socket.NetworkStack.get_ephemeral_port() 211 return socket.NetworkStack.send_udp(dst_ip, src_port, dst_port, data) 212 end 213 214 return false 215 end 216 217 ---Receive data from socket 218 ---@param socket table Socket object 219 ---@param max_len number|nil Maximum length to receive 220 ---@return string|nil data Received data 221 ---@return table|nil from_info Source info (UDP: {ip, port}) 222 function Socket.recv(socket, max_len) 223 if #socket.receive_queue == 0 then 224 return nil 225 end 226 227 local packet = table.remove(socket.receive_queue, 1) 228 229 if socket.type == Socket.TYPE.TCP then 230 local data = packet.data 231 if max_len and #data > max_len then 232 data = string.sub(data, 1, max_len) 233 -- Put remainder back in queue 234 table.insert(socket.receive_queue, 1, { 235 data = string.sub(packet.data, max_len + 1) 236 }) 237 end 238 return data 239 elseif socket.type == Socket.TYPE.UDP then 240 return packet.data, {ip = packet.from_ip, port = packet.from_port} 241 end 242 243 return nil 244 end 245 246 ---Close socket 247 ---@param socket table Socket object 248 function Socket.close(socket) 249 if socket.type == Socket.TYPE.TCP then 250 if socket.connection then 251 socket.NetworkStack.TCP.close(socket.connection) 252 end 253 elseif socket.type == Socket.TYPE.UDP then 254 if socket.local_port then 255 socket.NetworkStack.udp_unbind(socket.local_port) 256 end 257 end 258 259 socket.state = Socket.STATE.CLOSED 260 socket.receive_queue = {} 261 end 262 263 ---Register callback for socket events 264 ---@param socket table Socket object 265 ---@param event string Event name ("connected", "data", "closed", "error") 266 ---@param callback function Callback function 267 function Socket.on(socket, event, callback) 268 socket.callbacks[event] = callback 269 end 270 271 ---Create a TCP socket 272 ---@param NetworkStack table Network stack instance 273 ---@return table socket TCP socket 274 function Socket.tcp(NetworkStack) 275 return Socket.create(NetworkStack, Socket.TYPE.TCP) 276 end 277 278 ---Create a UDP socket 279 ---@param NetworkStack table Network stack instance 280 ---@return table socket UDP socket 281 function Socket.udp(NetworkStack) 282 return Socket.create(NetworkStack, Socket.TYPE.UDP) 283 end 284 285 return Socket