'Example program for the Network Client/Server command library
'Creates a chat server and allows up to 10 chat clients to connect.

$include "NCS_chat.inc"

'variables
WINDOW cserver
int ListenSocket=-1
int DataSockets[11]
int Authorized[10]
string Response[10]
int x
int ncsStatus
int server_port=10000
'clear variables
for x=0 to 9
	DataSockets[x] = -1
	Authorized[x] = FALSE
next x
DataSockets[10] = -1
'open server window 
OpenWindow cserver,0,0,800,300,@CAPTION|@MINBOX|@FIXEDSIZE,0,"NCS Chat Server",&cserver_handler

'intialize client/server library and open port.
'10 maximum client connections 1 listener
'1 extra socket to send "Connection refused" message.
NCSInit(11,1)
ncsStatus = NCSOpenForClients("",server_port,12)
if ncsStatus < 0
	AppendEdit(cserver,SERVER_STATUS,"Unable to start server",TRUE)
else
	
	ListenSocket = ncsStatus
	ncsStatus = NCSOnConnectMessage(cserver.hwnd,ListenSocket,msg_clientConnect)
	if ncsStatus < 0
		AppendEdit(cserver,SERVER_STATUS,"Unable to register message",TRUE)
	else
		AppendEdit(cserver,SERVER_STATUS,"Server started and waiting for connections",TRUE)
		'timer to scan for dropped connections
		StartTimer cserver,10000,tmr_disconnectScan
	endif
endif

waituntil IsWindowClosed(cserver)
'cleanup and exit
for x=0 to 9
	if DataSockets[x] > -1
		NCSCloseSocket(DataSockets[x])
	endif
next x
if ListenSocket > -1
	NCSCloseSocket(ListenSocket)
endif
NCSFree()

'handler
Sub cserver_handler
	int l,t,w,h,x
	Select @message
		Case @idcreate
			CenterWindow cserver
			'create the edit control for status display
			GetClientSize cserver,l,t,w,h
			Control cserver,@EDIT,"",0,0,300,h,@CTEDITMULTI|@CTEDITRO|@HSCROLL|@VSCROLL,SERVER_STATUS
			Setfont cserver,"Arial",8,400,0,SERVER_STATUS
		Case @idclosewindow
			'tell any connected clients we are closing
			for x = 0 to 9
				if DataSockets[x] > -1
					SendData2ChatClient(Xfer_Type_ServerClosing,NULL,0,x)
				endif
			next x
			CloseWindow cserver
		Case @idtimer
			if @code = tmr_disconnectScan
				'check for dropped connections
				for x = 0 to 9
					if DataSockets[x] > -1
						if NCSCheckConnection(DataSockets[x]) = 0
							'client dropped connection, close our socket
							DisconnectClient(x)	
						endif
					endif
				next x
			endif
		default
			chatserver_private()
	Endselect
Endsub

'subroutine to handle messages from the client/server library
Sub chatserver_private()
	int SocketSlot,ncsStatus
	string challenge
	Select TRUE
		Case @message = msg_clientConnect
			'do we have a free slot?
			SocketSlot = FindFreeSlot4Client()
			if SocketSlot > -1
				'accept the connection
				ncsStatus = NCSAcceptConnection(ListenSocket)
				if ncsStatus > -1
					'set up a data message
					DataSockets[SocketSlot] = ncsStatus
					NCSOnDataMessage(cserver.hwnd,DataSockets[SocketSlot],msg_clientDataFirst+SocketSlot)
					'challenge the client
					challenge = NCSCreateChallengeString()
					Response[SocketSlot] = NCSCreateResponseString(challenge,RESP_MULT,RESP_MASK)
					SendData2ChatClient(Xfer_Type_Challenge,challenge,len(challenge)+1,SocketSlot)
					AppendEdit(cserver,SERVER_STATUS,"Client accepted on socket"+str$(ncsStatus)+", assigned to slot"+str$(SocketSlot),TRUE)
				else
					'error accepting connection
					AppendEdit(cserver,SERVER_STATUS,"ERROR accepting a connect from a TC client, Errorcode "+str$(NCSGetLastError()),TRUE)
				endif
			else
				'no free sockets, send a connection refused message
				DataSockets[10] = NCSAcceptConnection(ListenSocket)
				SendData2ChatClient(Xfer_Type_ConnectionRefused,NULL,0,10)
				NCSCloseSocket(DataSockets[10])
				DataSockets[10] = -1
			endif
		Case (@message >= msg_clientDataFirst) and (@message <= msg_clientDataLast)
			ProcessDataFromClient(@message - msg_clientDataFirst)
	Endselect
Endsub

Sub ProcessDataFromClient(int client)
	int xfercode
	int datalen
	int ncsStatus
	char buffer[max_data_transfer]
	pointer pBuffer=&buffer
	'read the header
	ncsStatus = NCSReadData(DataSockets[client],pBuffer,len(chat_packet_header),0)
	if ncsStatus < len(chat_packet_header)
		'couldnt read the header, bad packet or error
		return
	else
		xfercode = #<int>pBuffer:pBuffer+=4
		datalen = #<int>pBuffer:pBuffer+=4
		if datalen
			if datalen > (max_data_transfer - len(chat_packet_header))
				'datalen exceeds maximum transfer, ignore the packet
				return
			else
				ncsStatus = NCSReadData(DataSockets[client],pBuffer,datalen,0)
				if ncsStatus < datalen
					'bad packet or error
					return
				endif
			endif
		endif
		'ok if we got here then we successfully read a packet from a client.
		if Authorized[client]
			select xfercode
				case Xfer_Type_ChatSend
					'forward the line of text to all clients
					for x=0 to 9
						if DataSockets[x] > -1
							SendData2ChatClient(Xfer_Type_ChatSend,pBuffer,datalen,x)
						endif
					next x
				case Xfer_Type_ClientDisconnect
					AppendEdit(cserver,SERVER_STATUS,"Client"+str$(client)+" closed, freeing socket", TRUE)
					DisconnectClient(client)
			endselect
		else
			if xfercode = Xfer_Type_Response
				if #<string>pBuffer = Response[client]
					'client sent proper response
					Authorized[client] = TRUE
					SendData2ChatClient(Xfer_Type_ClientApproved,NULL,0,client)
				else
					'wrong response,  disconnect
					SendData2ChatClient(Xfer_Type_ChallengeFailed,NULL,0,client)
					DisconnectClient(client)
				endif
			else
				'disconnect the client, the only valid code would be Xfer_Type_Response
				'So either this isn't a chat client, or a client didn't connect properly
				DisconnectClient(client)
			endif
		endif
	endif
	if DataSockets[client] > -1
		'rearm the registered data message
		NCSOnDataMessage(cserver.hwnd,DataSockets[client],msg_clientDataFirst+client)
	endif
Endsub

Sub DisconnectClient(int client)
	if DataSockets[client] > -1
		NCSCloseSocket(DataSockets[client])
	endif
	DataSockets[client] = -1
	Authorized[client] = FALSE
	Response[client] = ""
Endsub

'finds a free array slot or -1 if server is full
Sub FindFreeSlot4Client(),int
int x
	for x=0 to 9
		if DataSockets[x] = -1
			return x
		endif
	next x
	return -1
Endsub

Sub SendData2ChatClient(int xfercode, pointer buffer, int datalen, int client)
	int ncsStatus
	char xferbuffer[max_data_transfer]
	pointer pxfer = &xferbuffer
	if len(chat_packet_header) + datalen > max_data_transfer
		return
	endif
	#<chat_packet_header>pxfer.xfercode = xfercode
	#<chat_packet_header>pxfer.datalen = datalen
	pxfer += len(chat_packet_header)
	if buffer AND datalen
		'copy the data
		RtlMoveMemory(pxfer,buffer,datalen)
	endif
	ncsStatus = NCSSendData(DataSockets[client],&xferbuffer,len(chat_packet_header) + datalen )
	if ncsStatus < -1
		'log the error
		AppendEdit(cserver,SERVER_STATUS,"Error sending data to client Errorcode:"+str$(NCSGetLastError),TRUE)
	endif
Endsub

'subroutine to easily append text in an edit control.
CONST WM_GETTEXTLENGTH = 0xE
Sub AppendEdit(window win,int id,string text,int addNL)
	int _textlen:_textlen =SendMessage(win, WM_GETTEXTLENGTH,0,0,id)
	ControlCmd win, id, @EDSETSELECTION, _textlen, _textlen
	if(addNL)
		ControlCmd win, id, @EDREPLACESEL, text + "\n"
	else
		ControlCmd win, id, @EDREPLACESEL, text
	endif
Endsub
