uses data.String
data SocketStatus {
bool doAgain
}
data TLSStatus {
int state
}
data MonitorData {
int socketHandle
MonitorEvent mevent
}
interface TCPLib {
int connect(char address[], int port)
void disconnect(int hnd)
void setBlocking(int hnd, bool blocking)
int send(int hnd, byte content[])
int send_nb(int hnd, byte content[], SocketStatus status)
byte[] recv(int hnd, int length)
byte[] recv_nb(int hnd, int length, SocketStatus status)
int accept(int hnd)
int bind(char address[], int port, bool reuseAddress)
void unbind(int hnd)
void getLocalAddress(int hnd, NetworkEndpoint ha)
void getRemoteAddress(int hnd, NetworkEndpoint ha)
int createSelect(int eventArrayLength)
bool setEventArrayLength(int selectHandle, int l)
bool addSocket(int selectHandle, int socketHandle, Data se)
void armSendNotify(int selectHandle, int socketHandle, Data se)
void remSocket(int selectHandle, int socketHandle)
int wait(int selectHandle, Data events[])
int waitTime(int selectHandle, Data events[], int waitTime)
void destroySelect(int selectHandle)
}
interface SSLLib {
//certificate stores
int createCertStore()
bool addCertificate(int hnd, char cert[])
bool loadLocation(int hnd, char cert[])
void freeCertStore(int hnd)
//contexts
int createContext(bool serverMode) //true for server, false for client
bool setCertificate(int hnd, byte cert[], byte key[])
bool setCertificateChain(int hnd, String chain[])
bool setCipherSet(int hnd, int set)
void freeContext(int hnd)
//SSL
int makeSSL(int cxt)
bool accept(int hnd, int soc_fd)
int accept_nb(int hnd, int soc_fd)
bool connect(int hnd, int soc_fd)
int connect_nb(int hnd, int soc_fd)
char[] getPeerCertificate(int hnd)
String[] getPeerCertChain(int hnd)
int verifyCertificate(int hnd, int st_hnd, VerifyStatus vfs, char cert[], String chain[])
int write(int hnd, byte dat[])
int write_nb(int hnd, byte dat[], TLSStatus status)
byte[] read(int hnd, int len)
byte[] read_nb(int hnd, int len, TLSStatus status)
void closeSSL(int hnd)
int closeSSL_nb(int hnd)
void freeSSL(int hnd)
}
data BufferedData {
byte value[]
BufferedData next
}
const int MONITOR_EVENT_ARRAY_LENGTH = 16
component provides net.TCPServerSocket, net.TCPSocket(Destructor), net.TCPMonitor(Destructor), net.TLSCertStore(Destructor), net.TLSContext(Destructor), net.TLS(Destructor) requires native TCPLib lib, native SSLLib slib, io.Output out, data.IntUtil iu {
implementation TCPMonitor {
int handle
MonitorEvent events[] = new MonitorEvent[MONITOR_EVENT_ARRAY_LENGTH]
int lastEventCount = 0
TCPMonitor:TCPMonitor()
{
handle = lib.createSelect(MONITOR_EVENT_ARRAY_LENGTH)
if (handle == 0) throw new Exception("failed to create TCP selector")
}
bool TCPMonitor:addSocket(store TCPSocket s, opt store Data userData)
{
if (s.monitorID != 0)
{
throw new Exception("socket is already associated with a monitor")
}
MonitorData mdata = new MonitorData(s.platformSocket, new MonitorEvent(s, userData))
if (!lib.addSocket(handle, s.platformSocket, mdata))
{
throw new Exception("could not add socket to platform-level selector")
}
s.monitorID = handle
s.monitorData = mdata
return true
}
void TCPMonitor:armSendNotify(TCPSocket s)
{
if (s.monitorID != handle)
{
throw new Exception("socket not added to this monitor instance; use addSocket() first")
}
lib.armSendNotify(handle, s.platformSocket, s.monitorData)
}
void TCPMonitor:remSocket(TCPSocket s)
{
lib.remSocket(handle, s.platformSocket)
s.monitorID = 0
s.monitorData = null
}
MonitorEvent[] TCPMonitor:wait(opt int ms)
{
int r = 0
//native lib API doesn't do reference decrements, so we do it here
for (int i = 0; i < lastEventCount; i++)
{
events[i] = null
}
if (isset ms)
r = lib.waitTime(handle, events, ms)
else
r = lib.wait(handle, events)
lastEventCount = r
if (r != 0)
{
return dana.sub(events, 0, r-1)
}
return null
}
void Destructor:destroy()
{
lib.destroySelect(handle)
}
}
implementation TCPSocket {
bool connected
bool remoteConnected
int platformSocket
bool nonBlocking
SocketStatus status = new SocketStatus()
Mutex accessLock = new Mutex()
Mutex connectLock = new Mutex()
Data sendWaitData = null
//if this socket is associated with a monitor, we note the monitor's ID here
int monitorID
//custom data for the monitor is stored here
Data monitorData
BufferedData buffer
BufferedData bufferEnd
int bufferTotal
bool addToSendBuffer(byte content[])
{
BufferedData nbuf = new BufferedData(content)
if (bufferEnd == null)
{
buffer = nbuf
}
else
{
bufferEnd.next = nbuf
}
bufferEnd = nbuf
bufferTotal += content.arrayLength
//out.println(">> buffered $(content.arrayLength) bytes")
return true
}
int TCPSocket:send(byte content[])
{
mutex(accessLock)
{
if (connected)
{
if (nonBlocking)
{
int n = 0
if (buffer == null)
{
n = lib.send_nb(platformSocket, content, status)
//out.println(">> sent $n bytes of $(content.arrayLength)")
if (n < content.arrayLength)
{
//add the remaining content to a buffer and trigger sendWait
addToSendBuffer(dana.sub(content, n, content.arrayLength-1))
emitevent sendWait(sendWaitData)
}
}
else
{
addToSendBuffer(content)
}
return n
}
else
{
return lib.send(platformSocket, content)
}
}
else
{
throw new Exception("Socket is not connected for data send")
}
}
}
int TCPSocket:sendBuffer()
{
mutex(accessLock)
{
int total = 0
BufferedData w = buffer
while (w != null)
{
int n = lib.send_nb(platformSocket, w.value, status)
//out.println(">> sent[b] $n bytes of $(w.value.arrayLength)")
bufferTotal -= n
total += n
if (n < w.value.arrayLength)
{
w.value = dana.sub(w.value, n, w.value.arrayLength-1)
buffer = w
emitevent sendWait(sendWaitData)
break
}
else
{
w = w.next
}
}
if (w == null)
{
buffer = null
bufferEnd = null
//out.println("[buffer fully sent]")
}
return total
}
}
byte[] TCPSocket:recv(int length)
{
mutex(accessLock)
{
if (connected)
{
byte res[]
if (nonBlocking)
{
res = lib.recv_nb(platformSocket, length, status)
if (res == null && status.doAgain == false)
{
remoteConnected = false
}
}
else
{
res = lib.recv(platformSocket, length)
}
return res
}
else
{
throw new Exception("Socket is not connected for data receive")
}
}
}
bool TCPSocket:accept(TCPServerSocket server)
{
mutex(connectLock)
{
if (connected)
{
lib.disconnect(platformSocket)
connected = false
}
if (server.connected)
{
int clientHandle = lib.accept(server.platformSocket)
if (clientHandle == 0)
return false
platformSocket = clientHandle
connected = true
remoteConnected = true
return true
}
else
{
throw new Exception("Server socket is not bound for accept")
}
}
}
bool TCPSocket:connect(char address[], int port)
{
mutex(connectLock)
{
if (connected)
{
lib.disconnect(platformSocket)
connected = false
}
int platformHandle = lib.connect(address, port)
if (platformHandle == 0)
{
throw new Exception("failed to connect to remote address $address:$port")
}
platformSocket = platformHandle
connected = true
remoteConnected = true
return true
}
}
void TCPSocket:disconnect()
{
mutex(connectLock)
{
if (connected)
{
lib.disconnect(platformSocket)
connected = false
remoteConnected = false
platformSocket = 0
}
}
}
NetworkEndpoint TCPSocket:getLocalEndpoint()
{
NetworkEndpoint ha = new NetworkEndpoint()
mutex(connectLock)
{
if (connected)
{
lib.getLocalAddress(platformSocket, ha)
}
else
{
throw new Exception("Socket is not connected")
}
}
return ha
}
NetworkEndpoint TCPSocket:getRemoteEndpoint()
{
NetworkEndpoint ha = new NetworkEndpoint()
mutex(connectLock)
{
if (connected)
{
lib.getRemoteAddress(platformSocket, ha)
}
else
{
throw new Exception("Socket is not connected")
}
}
return ha
}
bool TCPSocket:setNonBlocking(opt store Data swd)
{
nonBlocking = true
sendWaitData = swd
lib.setBlocking(platformSocket, false)
return true
}
int TCPSocket:getBufferUnsent()
{
return bufferTotal
}
bool TCPSocket:connected()
{
return remoteConnected
}
void Destructor:destroy()
{
if (connected)
{
lib.disconnect(platformSocket)
connected = false
platformSocket = 0
}
}
}
implementation TCPServerSocket {
bool connected
int platformSocket
Mutex accessLock = new Mutex()
bool TCPServerSocket:bind(char address[], int port, opt bool reuseAddress)
{
mutex(accessLock)
{
int platformHandle = lib.bind(address, port, reuseAddress)
if (platformHandle == 0)
throw new Exception("Could not bind to address '$address'")
platformSocket = platformHandle
connected = true
return true
}
}
void TCPServerSocket:unbind()
{
mutex(accessLock)
{
if (connected)
{
lib.unbind(platformSocket)
connected = false
}
}
}
}
implementation TLSCertStore {
int handle
TLSCertStore:TLSCertStore()
{
handle = slib.createCertStore()
}
//add a trusted certificate (PEM-encoded x509)
bool TLSCertStore:addCertificate(char cert[])
{
return slib.addCertificate(handle, cert)
}
//load all certificates from the given system path
bool TLSCertStore:loadLocation(char path[])
{
return slib.loadLocation(handle, path)
}
void Destructor:destroy()
{
slib.freeCertStore(handle)
}
}
implementation TLSContext {
int platformHandle
TLSContext:TLSContext(byte mode)
{
if (mode == TLSContext.CLIENT)
platformHandle = slib.createContext(false)
else
platformHandle = slib.createContext(true)
}
bool TLSContext:setCertificate(char cert[], byte key[])
{
return slib.setCertificate(platformHandle, cert, key)
}
bool TLSContext:setCertificateChain(String chain[])
{
return slib.setCertificateChain(platformHandle, chain)
}
void TLSContext:setCipherSet(int set)
{
slib.setCipherSet(platformHandle, set)
}
void Destructor:destroy()
{
slib.freeContext(platformHandle)
}
}
implementation TLS {
int platformHandle
bool closed
bool nonBlocking
Mutex connectLock = new Mutex()
TLSContext context
TCPSocket socket
TLSStatus status = new TLSStatus()
Data sendWaitData
Mutex bufferLock = new Mutex()
BufferedData buffer
BufferedData bufferEnd
int bufferTotal
bool addToSendBuffer(byte content[])
{
BufferedData nbuf = new BufferedData(content)
if (bufferEnd == null)
{
buffer = nbuf
}
else
{
bufferEnd.next = nbuf
}
bufferEnd = nbuf
bufferTotal += content.arrayLength
return true
}
TLS:TLS(TLSContext cxt)
{
context = cxt
platformHandle = slib.makeSSL(cxt.platformHandle)
}
int TLS:accept(store TCPSocket s)
{
if (s.nonBlocking) nonBlocking = true
socket = s
if (!nonBlocking)
{
return slib.accept(platformHandle, s.platformSocket)
}
else
{
return slib.accept_nb(platformHandle, s.platformSocket)
}
}
int TLS:connect(store TCPSocket s)
{
if (s.nonBlocking) nonBlocking = true
socket = s
if (!nonBlocking)
{
return slib.connect(platformHandle, s.platformSocket)
}
else
{
return slib.connect_nb(platformHandle, s.platformSocket)
}
}
VerifyStatus TLS:verifyCertificate(TLSCertStore st, char cert[], String chain[])
{
VerifyStatus vf = new VerifyStatus()
slib.verifyCertificate(platformHandle, st.handle, vf, cert, chain)
return vf
}
char[] TLS:getPeerCertificate()
{
return slib.getPeerCertificate(platformHandle)
}
String[] TLS:getPeerCertChain()
{
return slib.getPeerCertChain(platformHandle)
}
int TLS:send(byte dat[])
{
if (!nonBlocking)
{
return slib.write(platformHandle, dat)
}
else
{
int n = 0
mutex(bufferLock)
{
if (buffer == null)
{
n = slib.write_nb(platformHandle, dat, status)
if (n < dat.arrayLength)
{
//add the remaining content to a buffer and trigger sendWait
addToSendBuffer(dana.sub(dat, n, dat.arrayLength-1))
emitevent sendWait(sendWaitData)
}
}
else
{
addToSendBuffer(dat)
}
}
return n
}
}
int TLS:sendBuffer()
{
mutex(bufferLock)
{
int total = 0
BufferedData w = buffer
while (w != null)
{
int n = slib.write_nb(platformHandle, w.value, status)
bufferTotal -= n
total += n
if (n < w.value.arrayLength)
{
w.value = dana.sub(w.value, n, w.value.arrayLength-1)
buffer = w
emitevent sendWait(sendWaitData)
break
}
else
{
w = w.next
}
}
if (w == null)
{
buffer = null
bufferEnd = null
}
return total
}
}
byte[] TLS:recv(int len)
{
if (!nonBlocking)
return slib.read(platformHandle, len)
else
return slib.read_nb(platformHandle, len, status)
}
bool TLS:setNonBlocking(opt store Data swd)
{
nonBlocking = true
sendWaitData = swd
return true
}
int TLS:getBufferUnsent()
{
return bufferTotal
}
int TLS:getStatus()
{
return status.state
}
TCPSocket TLS:getSocket()
{
return socket
}
int TLS:close()
{
if (platformHandle != 0 && !closed)
{
socket = null
if (!nonBlocking)
{
slib.closeSSL(platformHandle)
closed = true
return TLS.OK
}
else
{
return slib.closeSSL_nb(platformHandle)
}
}
return TLS.FAIL
}
void Destructor:destroy()
{
if (platformHandle != 0 && !closed)
{
slib.closeSSL(platformHandle)
}
if (platformHandle != 0)
slib.freeSSL(platformHandle)
}
}
}