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