luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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