// socket.wren foreign class Socket { // Asynchronous static methods that perform raw socket operations. // These are the direct bindings to the C backend. foreign static connect_(host, port, callback) foreign static new_(callback) foreign static bind_(sock, host, port, callback) foreign static listen_(sock, backlog, callback) foreign static accept_(sock, callback) foreign static read_(sock, length, callback) foreign static readUntil_(sock, bytes, callback) foreign static readExactly_(sock, length, callback) foreign static write_(sock, data, callback) foreign static isReadable_(sock, callback) foreign static select_(sockets, callback) foreign static setNonBlocking(fd, flag) foreign static close_(sock, callback) } class SocketInstance { construct fromFd(socketFd) { _sock = socketFd _closed = false } sock { _sock } closed { _closed } // Instance methods providing a more convenient API. accept() { if (_closed) { return ["Socket is closed", null] } var result = null var done = false Socket.accept_(_sock, Fn.new { |err, newSock| if (err) { result = [err, null] } else { result = [null, newSock] } done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return ["Accept operation timed out", null] } return result } read(length) { if (_closed) { return ["Socket is closed", null] } if (length <= 0) { return ["Invalid read length", null] } var result = null var done = false Socket.read_(_sock, length, Fn.new { |err, data| result = [err, data] done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return ["Read operation timed out", null] } return result } readUntil(bytes) { if (_closed) { return ["Socket is closed", null] } if (!bytes || bytes.count == 0) { return ["Invalid until bytes", null] } var result = null var done = false Socket.readUntil_(_sock, bytes, Fn.new { |err, data| result = [err, data] done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return ["ReadUntil operation timed out", null] } return result } readExactly(length) { if (_closed) { return ["Socket is closed", null] } if (length <= 0) { return ["Invalid read length", null] } var result = null var done = false Socket.readExactly_(_sock, length, Fn.new { |err, data| result = [err, data] done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return ["ReadExactly operation timed out", null] } return result } write(data) { if (_closed) { return "Socket is closed" } if (!data) { return "Invalid write data" } var result = null var done = false Socket.write_(_sock, data, Fn.new { |err, nothing| result = err done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return "Write operation timed out" } return result } isReadable() { if (_closed) { return [false, "Socket is closed"] } var result = null var done = false Socket.isReadable_(_sock, Fn.new { |err, readable| result = [err, readable] done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return [false, "IsReadable operation timed out"] } return result } close() { if (_closed) { return null } var result = "Close operation did not complete" var done = false Socket.close_(_sock, Fn.new { |err, nothing| result = err done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } _closed = true return result } bind(host, port) { if (_closed) { return ["Socket is closed", false] } if (port < 0 || port > 65535) { return ["Invalid port", false] } var result = ["Bind operation did not complete", false] var done = false var bindHost = host if (!bindHost) { bindHost = "0.0.0.0" } Socket.bind_(_sock, bindHost, port, Fn.new { |err, success| if (err) { result = [err, false] } else { result = [null, success] } done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } return result } listen(backlog) { if (_closed) { return ["Socket is closed", false] } if (backlog < 0) { backlog = 128 } var result = ["Listen operation did not complete", false] var done = false Socket.listen_(_sock, backlog, Fn.new { |err, success| if (err) { result = [err, false] } else { result = [null, success] } done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } return result } // Static methods for creating client and server sockets. static connect(host, port) { if (!host || port < 0 || port > 65535) { return ["Invalid host or port", null] } var result = null var done = false Socket.connect_(host, port, Fn.new { |err, sock| if (err) { result = [err, null] } else { result = [null, SocketInstance.fromFd(sock)] } done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return ["Connect operation timed out", null] } return result } static new() { var result = null var done = false Socket.new_(Fn.new { |err, sock| if (err) { result = [err, null] } else { result = [null, SocketInstance.fromFd(sock)] } done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return ["Socket creation timed out", null] } return result } static select(sockets, timeoutMs) { if (!sockets || sockets.count == 0) { return [[], null] } var sockFds = [] for (sock in sockets) { if (sock is SocketInstance && !sock.closed) { sockFds.add(sock.sock) } } if (sockFds.count == 0) { return [[], null] } var result = null var done = false Socket.select_(sockFds, Fn.new { |err, readableFds| if (err) { result = [[], err] } else { var readableSockets = [] for (fd in readableFds) { for (sock in sockets) { if (sock.sock == fd) { readableSockets.add(sock) break } } } result = [readableSockets, null] } done = true }) var attempts = 0 while (!done && attempts < 1000) { attempts = attempts + 1 Fiber.yield() } if (!done) { return [[], "Select operation timed out"] } return result } }