HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component net.TCP by barry
expand copy to clipboardexpand
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)
			}
		
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n net.TCP -m "reason for update" -u yourUsername
Version 2 by barry
Version 1 (this version) by barry
Notes for this version: Standard Library Initialisation