// 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
}
}