1 module networkd;
2 
3 import std.socket;
4 import utils.misc;
5 import utils.baseconv : denaryToChar, charToDenary;//needed for converting message size to array of char
6 import utils.lists;
7 import crypto.rsa;// for encrypting messages
8 
9 /// Used by `Node` to store messages that aren't completely received, temporarily
10 private struct IncomingMessage{
11 	char[] buffer; /// The received message
12 	uint size = 0; /// The size of the message, we use uint instead of uinteger because the size can not be more than 2^32 bytes
13 }
14 
15 
16 /// Stores data about the NetEvent returned by `Node.getEvent`
17 struct NetEvent{
18 	/// Enum defining all possible NetEvent types
19 	enum Type{
20 		MessageEvent, /// When a message has been completely transfered (received)
21 		PartMessageEvent, /// When a part of a message is received. This can be used to estimate the time-left...
22 		ConnectionAccepted, /// When the listener accepts an incoming connection. The connection ID of this connection can be retrieved by `NetEvent.conID`
23 		ConnectionClosed, /// When a connection is closed
24 		KeysReceived, /// when a connection sends its public key for encrypting messages
25 		Timeout, /// Nothing happened, `Node.getEvent` exited because of timeout
26 	}
27 	/// PartMessageEvent, returned by `NetEvent.getEventData!(NetEvent.Type.PartMessageEvent)`
28 	/// Stores information about transmission of a message, which isn't completely received yet.
29 	/// 
30 	/// The values provided can be (will be) incorrect if less than 4 bytes have been received
31 	struct PartMessageEvent{
32 		uint received; /// The number of bytes that have been received
33 		uint size; /// The length of message when transfer will be complete
34 	}
35 	private Type _type;
36 	private uinteger senderConID;
37 	private union{
38 		char[] messageEvent;
39 		PartMessageEvent partMessageEvent;
40 	}
41 	/// is true, if the event is data-being-received, if it was sent encrypted
42 	/// 
43 	/// this should only considered if the type==Type.MessageEvent, or type==Type.PartMessageEvent
44 	/// in case of PartMessageEvent, its value is only correct if 5 or more bytes have been received, because the 5th byte tells
45 	/// if the message is encrypted or not
46 	bool encrypted = false;
47 	/// Returns the Type for this Event
48 	@property Type type(){
49 		return _type;
50 	}
51 	/// Returns the connection ID associated with the NetEvent
52 	@property uinteger conID(){
53 		return senderConID;
54 	}
55 	/// Returns more data on the NetEvent, for each NetEvent Type, the returned data type(s) is different
56 	/// Call it like:
57 	/// ```
58 	/// NetEvent.getEventData!(NetEvent.Type.SOMETYPE);
59 	/// ```
60 	/// 
61 	/// For `NetEvent.Type.MessageEvent`, the message received is returned as `char[]`
62 	/// For `NetEvent.Type.partMessageEvent`, `partMessageEvent` is returned which contains `received` bytes, and `size`
63 	/// For `NetEvent.Type.ConnectionAccepted` and `...ConnectionClosed`, no data is returned, exception will be thrown instead.
64 	@property auto getEventData(Type T)(){
65 		// make sure that the type is correct
66 		//since it's a template, and Type T will be known at compile time, we'll use static
67 		if (T != type){
68 			throw new Exception("Provided NetEvent Type differs from actual NetEvent Type");
69 		}
70 		// now a static if for every type...
71 		static if (T == Type.MessageEvent){
72 			return messageEvent;
73 		}else static if (T == Type.PartMessageEvent){
74 			return partMessageEvent;
75 		}else static if (T == Type.ConnectionAccepted){
76 			throw new Exception("No further data can be retrieved from NetEvent.Type.ConnectionAccepted using NetEvent.getEventData");
77 		}else static if (T == Type.ConnectionClosed){
78 			throw new Exception("No further data can be retrieved from NetEvent.Type.ConnectionClosed using NetEvent.getEventData");
79 		}else static if (T == Type.KeysReceived){
80 			throw new Exception("No further data can be retrieved from NetEvent.Type.KeysReceived using NetEvent.getEventData");
81 		}
82 	}
83 	//constructors, different for each NetEvent Type
84 	// we'll mark them private as all NetEvents are constructed in this module
85 	private{
86 		this(uinteger conID, char[] eventData, bool wasEncrypted=false){
87 			messageEvent = eventData.dup;
88 			_type = Type.MessageEvent;
89 			senderConID = conID;
90 			encrypted = wasEncrypted;
91 		}
92 		this(uinteger conID, PartMessageEvent eventData, bool wasEncrypted=false){
93 			partMessageEvent = eventData;
94 			_type = Type.PartMessageEvent;
95 			senderConID = conID;
96 			encrypted = wasEncrypted;
97 		}
98 		this(uinteger conID, Type t, bool wasEncrypted=false){
99 			_type = t;
100 			senderConID = conID;
101 			encrypted = wasEncrypted;
102 		}
103 	}
104 }
105 
106 class Node{
107 private:
108 	/// enum defining types of messages, only used by Node, not used outside from this class
109 	enum MessageType : char{
110 		PlainMessage = 0, /// a plain unencrypted message, should trigger a NetEvent.Type.MessageEvent at receiver
111 		EncryptedMessage = 1, /// an encrypted message, should trigger a NetEvent.Type.MessageEvent at receiver
112 		PublicKey = 2, /// the key to encrypt messages that are to be sent to the the connection that sent this message type
113 	}
114 	/// address to which listener listens
115 	InternetAddress listenerAddr;
116 	/// To receive incoming connections
117 	Socket listener;
118 	/// List of all connected Sockets
119 	Socket[] connections;
120 	/// Determines whether any new incoming connection will be accepted or not
121 	bool isAcceptingConnections = false;
122 	/// messages that are not completely received yet, i.e only a part has been received, are stored here
123 	IncomingMessage[uinteger] incomingMessages;
124 	/// stores public keys for connections for encrypting out-going messages
125 	string[uinteger] publicKeys;
126 	/// stores public and private keys for this Node
127 	RSAKeyPair _keys;
128 
129 	///Called by `Node.getEvent` when a new message is received, with `buffer` containing the message, and `conID` as the 
130 	///connection ID
131 	///
132 	/// TODO refactor this, its starting to get a little bit complicated since I added encryption
133 	NetEvent[] addReceivedMessage(char[] buffer, uinteger conID){
134 		// check if the firt part of the message was already received
135 		if (conID in incomingMessages){
136 			// append this packet's content to previously received message(s)
137 			incomingMessages[conID].buffer ~= buffer;
138 			// check if the total size is known
139 			if (incomingMessages[conID].size == 0){
140 				// no, size not yet known, calculate it
141 				if (incomingMessages[conID].buffer.length >= 4){
142 					// size can be calculated, do it now
143 					incomingMessages[conID].size = cast(uint)charToDenary(incomingMessages[conID].buffer[0 .. 4].dup);
144 					// the first 4 bytes will be removed when transfer is complete, so no need to do it now
145 				}
146 			}
147 		}else{
148 			// this is the first message in this transfer, so make space in `incomingMessages`
149 			IncomingMessage msg;
150 			msg.buffer = buffer;
151 			// check if size has bee received
152 			if (msg.buffer.length >= 4){
153 				msg.size = cast(uint)charToDenary(msg.buffer[0 .. 4].dup);
154 			}
155 			// add it to `incomingMessages`
156 			incomingMessages[conID] = msg;
157 		}
158 		NetEvent[] result;
159 		// check if transfer is complete, case yes, add an NetEvent for it as complete message, else; as part message
160 		if (incomingMessages[conID].size > 0 && incomingMessages[conID].buffer.length >= incomingMessages[conID].size){
161 			// check if extra bytes were sent, consider those bytes as a separate message
162 			char[] otherMessage = null;
163 			if (incomingMessages[conID].buffer.length > incomingMessages[conID].size){
164 				otherMessage = incomingMessages[conID].buffer
165 					[incomingMessages[conID].size .. incomingMessages[conID].buffer.length].dup;
166 
167 				incomingMessages[conID].buffer.length = incomingMessages[conID].size;
168 			}
169 			// transfer complete, move it to `receivedMessages`
170 			char[] message = incomingMessages[conID].buffer[4 .. incomingMessages[conID].size];
171 			// check message type
172 			if (cast(MessageType)message[0] == MessageType.EncryptedMessage){
173 				message = cast(char[])RSA.decrypt(_keys.privateKey, cast(ubyte[])message[1 .. message.length]);
174 				result ~= NetEvent(conID, message.dup, true);
175 			}else if (cast(MessageType)message[0] == MessageType.PublicKey){
176 				// store this
177 				publicKeys[conID] = cast(string)message[1 .. message.length].dup;
178 				result ~= NetEvent(conID, NetEvent.Type.KeysReceived);
179 			}else if (cast(MessageType)message[0] == MessageType.PlainMessage){
180 				result ~= NetEvent(conID, message[1 .. message.length].dup);
181 			}
182 			// remove it from `incomingMessages`
183 			if (conID in incomingMessages){
184 				incomingMessages.remove(conID);
185 			}
186 
187 			// check if there were extra bytes, if yes, recursively call itself
188 			if (otherMessage != null){
189 				result ~= addReceivedMessage(otherMessage, conID);
190 			}
191 		}else{
192 			//add NetEvent for part message
193 			NetEvent.PartMessageEvent partMessage;
194 			if (incomingMessages[conID].buffer.length > 4){
195 				partMessage.received = cast(uint)incomingMessages[conID].buffer.length - 4;
196 			}else{
197 				partMessage.received = 0;
198 			}
199 			partMessage.size = incomingMessages[conID].size - 4;
200 			if (incomingMessages[conID].buffer.length >= 5 && incomingMessages[conID].buffer[4] == MessageType.EncryptedMessage){
201 				result ~= NetEvent(conID, partMessage, true);
202 			}else{
203 				result ~= NetEvent(conID, partMessage);
204 			}
205 
206 		}
207 		return result;
208 	}
209 
210 	/// Adds socket to `connections` array, returns connection ID
211 	uinteger addSocket(Socket connection){
212 		// add it to list
213 		//go through the list to find a free id, if none, expand the array
214 		uinteger i;
215 		for (i = 0; i < connections.length; i++){
216 			if (connections[i] is null){
217 				break;
218 			}
219 		}
220 		// check if has to expand array
221 		if (connections.length > 0 && i < connections.length && connections[i] is null){
222 			// there's space already, no need to expand
223 			connections[i] = connection;
224 		}else{
225 			// in case of no space, append it to end of array
226 			i = connections.length;
227 			connections ~= connection;
228 		}
229 		return i;
230 	}
231 	/// used to convert uinteger to char[] with length 4
232 	static char[] getSizeInChars(uinteger size, uinteger arrayLength=4){
233 		char[] msgSize = denaryToChar(size);
234 		// make it take 4 bytes
235 		if (msgSize.length < arrayLength){
236 			// fill the empty bytes with 0x00 to make it `arrayLength` bytes long
237 			uinteger oldLength = msgSize.length;
238 			char[] nSize;
239 			nSize.length = arrayLength;
240 			nSize[] = 0;
241 			nSize[arrayLength - oldLength .. arrayLength] = msgSize.dup;
242 			msgSize = nSize;
243 		}
244 		return msgSize;
245 	}
246 	/// used to send a message to a Socket, if its too large, it is sent in individual packets, each of 1024 bytes
247 	static bool sendPacket(Socket receiver, char[] message){
248 		bool r = true;
249 		//send it away, 1024 bytes at a time
250 		for (uinteger i = 0; i < message.length; i += 1024){
251 			// check if remaining message is less than 1024 bytes
252 			char[] toSend;
253 			if (message.length < i+1024){
254 				//then just send the remaining message
255 				toSend = message[i .. message.length];
256 			}else{
257 				toSend = message[i .. i + 1024];
258 			}
259 			/// now actually send it, and return false case of error
260 			if (receiver.send(toSend) == Socket.ERROR){
261 				r = false;
262 				break;
263 			}
264 		}
265 		return r;
266 	}
267 
268 public:
269 	/// `listenForConnections` if true enables the listener, and any incoming connections are accepted  
270 	/// `port` determines the port on which the listener will run
271 	this(bool listenForConections=false, ushort port=2525){
272 		if (listenForConections){
273 			listenerAddr = new InternetAddress(port);
274 			listener = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
275 			listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
276 			listener.bind(listenerAddr);
277 			listener.listen(15);
278 			isAcceptingConnections = true;
279 		}else{
280 			listenerAddr = null;
281 			listener = null;
282 		}
283 	}
284 	/// Closes all connections, including the listener, and destroys the Node
285 	~this(){
286 		closeAllConnections();
287 		// stop the listener too
288 		if (listener !is null){
289 			listener.shutdown(SocketShutdown.BOTH);
290 			listener.close();
291 			destroy(listener);
292 			destroy(listenerAddr);
293 		}
294 	}
295 	/// Closes all connections
296 	void closeAllConnections(){
297 		foreach(connection; connections){
298 			if (connection !is null){
299 				connection.shutdown(SocketShutdown.BOTH);
300 				connection.close();
301 				destroy(connection);
302 			}
303 		}
304 		connections.length = 0;
305 	}
306 	/// use this to set the public and private keys for this Node, or generate random using `Node.generateKeys`
307 	@property RSAKeyPair keys(RSAKeyPair newKeys){
308 		return _keys = newKeys;
309 	}
310 	/// generates keys for encrypting messages
311 	void generateKeys(uint length = 1024){
312 		_keys = RSA.generateKeyPair(length);
313 	}
314 	/// Returns: true if a connection has sent public key and messages sent to it are encrypted
315 	/// 
316 	/// This returning true only ensures that messages being sent to that connection are encrypted, for messages being received,
317 	/// check if a NetEvent with conID=this-connection and type==MessageEvent||PartMessageEvent has `NetEvent.encrypted=true`
318 	bool connectionIsEncrypted(uinteger conID){
319 		// see if it event exists
320 		if (connectionExists(conID) && conID in publicKeys){
321 			return true;
322 		}
323 		return false;
324 	}
325 	/// sends public key to all connections
326 	/// Returns: true on success, false on failure
327 	bool sendKeysToAllConnections(){
328 		if (_keys.publicKey.length > 0){
329 			char[] message = MessageType.PublicKey~cast(char[])_keys.publicKey.dup;
330 			message = getSizeInChars(message.length+4)~message;
331 			foreach (con; connections){
332 				if (!sendPacket(con, message)){
333 					return false;
334 				}
335 			}
336 			return true;
337 		}
338 		return false;
339 	}
340 	/// sends public key to a specific connection
341 	/// Returns: true on success, false on failure
342 	bool sendKey(uinteger conID){
343 		if (_keys.publicKey.length > 0 && connectionExists(conID)){
344 			char[] message = MessageType.PublicKey~cast(char[])_keys.publicKey.dup;
345 			message = getSizeInChars(message.length+4)~message;
346 			return sendPacket(connections[conID], message);
347 		}
348 		return false;
349 	}
350 	/// Creates a new connection to `address` using the `port`.
351 	/// 
352 	/// address can either be an IPv4 ip address or a host name
353 	/// Returns: the conection ID for the new connection if successful, throws exception on failure
354 	uinteger newConnection(string address, ushort port){
355 		InternetAddress addr = new InternetAddress(address, port);
356 		Socket connection = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
357 		connection.connect(addr);
358 		return addSocket(connection);
359 	}
360 	/// Closes a connection using it's connection ID
361 	/// Returns true on success, false on failure
362 	bool closeConnection(uinteger conID){
363 		if (connectionExists(conID)){
364 			connections[conID].shutdown(SocketShutdown.BOTH);
365 			connections[conID].close;
366 			destroy(connections[conID]);
367 			// mark it as 'free-slot'. because if we remove it, some connections might change their index.
368 			connections[conID] = null;
369 			// if it's at end, remove it as it's safe to do so
370 			if (conID+1 == connections.length){
371 				connections.length --;
372 			}
373 			// if it's keys are stored, remove those too
374 			if (conID in publicKeys){
375 				publicKeys.remove(conID);
376 			}
377 			return true;
378 		}else{
379 			return false;
380 		}
381 	}
382 
383 	/// Returns true if a connection ID is assigned to an existing connection
384 	bool connectionExists(uinteger conID){
385 		bool r = false;
386 		if (conID < connections.length && connections[conID] !is null){
387 			r = true;
388 		}
389 		return r;
390 	}
391 	/// Sends a message to a Node using connection ID
392 	/// 
393 	/// The message on the other end must be received using `networkd.Node.getEvent` because before sending, the message is not sent raw.
394 	/// The first 4 bytes (32 bits) contain the size of the message, including these 4 bytes
395 	/// This is followed by one char, which class Node uses to identify what type of message it is (from enum MessageType)
396 	/// This is followed by the content of the message. If the content is too large, it is split up into several packets.
397 	/// The max message size is 5 bytes less than 4 gigabytes (4294967291 bytes)
398 	/// 
399 	/// If the connection to which it is being sent has sent its encryption public key, the message's content will be encrypted before sending
400 	/// 
401 	/// Returns: true on success and false on failure
402 	bool sendMessage(uinteger conID, char[] message){
403 		bool r = false;
404 		//check if connection ID is valid
405 		if (connectionExists(conID)){
406 			// encrypt the message if possible
407 			message = message.dup;
408 			if (conID in publicKeys){
409 				message = cast(char[])RSA.encrypt(publicKeys[conID], cast(ubyte[])message);
410 				message = MessageType.EncryptedMessage~message;
411 			}else{
412 				message = MessageType.PlainMessage~message;
413 			}
414 			char[] msgSize = getSizeInChars(message.length + 4);//+4 for the size-chars
415 			/// only continue if size can fit in 4 bytes
416 			if (msgSize.length == 4){
417 				r = sendPacket(connections[conID],msgSize~message);
418 			}
419 		}
420 		return r;
421 	}
422 	///Waits for an NetEvent to occur, and returns it. A timeout can be provided, if null, max value is used
423 	///
424 	///Messages are not received till this function is called
425 	///
426 	///An NetEvent is either of these:
427 	///1. data received
428 	///2. connection accepted by listener
429 	///3. connection closed
430 	///4. timeout while waiting for the above to occur
431 	///
432 	///Returns: array containing events, or empty array in case of timeout or interruption
433 	NetEvent[] getEvent(TimeVal* timeout = null){
434 		SocketSet receiveSockets = new SocketSet;
435 		//add all active connections
436 		foreach(conn; connections){
437 			if (conn !is null){
438 				receiveSockets.add(conn);
439 			}
440 		}
441 		//add the listener if not null
442 		if (listener !is null){
443 			receiveSockets.add(listener);
444 		}
445 		// copy the timeout, coz idk why SocketSet.select resets it every time it timeouts
446 		TimeVal* usedTimeout;
447 		TimeVal tempTimeVal;
448 		if (timeout is null){
449 			usedTimeout = null;
450 		}else{
451 			tempTimeVal = *timeout;
452 			usedTimeout = &tempTimeVal;
453 		}
454 		// check if a message was received
455 		int eventCount = Socket.select(receiveSockets, null, null, usedTimeout);
456 		if (eventCount > 0){
457 			char[1024] buffer;
458 			NetEvent[] result = [];
459 			/// counts events processed
460 			uint i = 0;
461 			// check if a new connection needs to be accepted
462 			if (isAcceptingConnections && listener !is null && receiveSockets.isSet(listener)){
463 				// add new connection
464 				Socket client = listener.accept();
465 				client.setOption(SocketOptionLevel.TCP, SocketOption.KEEPALIVE, 1);
466 				uinteger conID = addSocket(client);
467 
468 				result ~= NetEvent(conID, NetEvent.Type.ConnectionAccepted);
469 				i++;
470 			}
471 			// check if a message was received
472 			foreach(conID; 0 .. connections.length){
473 				if (i >= eventCount){
474 					break;
475 				}
476 				//did this connection sent it?
477 				if (receiveSockets.isSet(connections[conID])){
478 					i ++;
479 					uinteger msgLen = connections[conID].receive(buffer);
480 					// check if connection was closed
481 					if (msgLen == 0 || msgLen == Socket.ERROR){
482 						if (msgLen == Socket.ERROR){
483 							closeConnection(conID);
484 						}else{
485 							// connection closed, remove from array, and clear any partially-received message from this connection
486 							connections[conID].destroy();
487 							connections[conID] = null;
488 						}
489 						// remove messages
490 						if (conID in incomingMessages){
491 							incomingMessages.remove(conID);
492 						}
493 						// remove key
494 						if (conID in publicKeys){
495 							publicKeys.remove(conID);
496 						}
497 						
498 						result ~= NetEvent(conID, NetEvent.Type.ConnectionClosed);
499 					}else{
500 						// a message was received
501 						result ~= addReceivedMessage(buffer[0 .. msgLen], conID);
502 					}
503 				}
504 			}
505 			return result;
506 		}
507 		.destroy(receiveSockets);
508 		return [];
509 	}
510 
511 	///Returns IP Address of a connection using connection ID
512 	///`local` if true, makes it return the local address, otherwise, remoteAddress is used
513 	///If connection doesn't exist, null is retured
514 	string getIPAddr(uinteger conID, bool local){
515 		//check if connection exists
516 		if (connectionExists(conID)){
517 			Address addr;
518 			if (local){
519 				addr = connections[conID].localAddress;
520 			}else{
521 				addr = connections[conID].remoteAddress;
522 			}
523 			return addr.toAddrString;
524 		}else{
525 			return null;
526 		}
527 	}
528 	/// Returns Host name of a connection using connection ID
529 	/// `local` if true, makes it return local host name, else, remote host name is used
530 	///If connection doesn't exist, null is retured
531 	string getHostName(uinteger conID, bool local){
532 		// check if valid connection
533 		if (connectionExists(conID)){
534 			Address addr;
535 			if (local){
536 				addr = connections[conID].localAddress;
537 			}else{
538 				addr = connections[conID].remoteAddress;
539 			}
540 			return addr.toHostNameString;
541 		}else{
542 			return null;
543 		}
544 	}
545 	/// Returns a list of all connection IDs that are currently assigned to connections, useful for running server
546 	/// 
547 	/// More information on connection can be retrieved using 
548 	uinteger[] getConnections(){
549 		LinkedList!uinteger list = new LinkedList!uinteger;
550 		for (uinteger i = 0; i < connections.length; i ++){
551 			if (connections[i] !is null){
552 				list.append(i);
553 			}
554 		}
555 		uinteger[] r = list.toArray;
556 		list.destroy;
557 		return r;
558 	}
559 	/// Specifies if incoming connections will be accepted by listener.
560 	/// It's value does not have any affect if `litenForConnections` was specified as `false` in constructor
561 	@property bool acceptConnections(){
562 		return isAcceptingConnections;
563 	}
564 	/// Specifies if incoming connections will be accepted by listener.
565 	/// It's value does not have any affect if `litenForConnections` was specified as `false` in constructor
566 	@property bool acceptConnections(bool newVal){
567 		return isAcceptingConnections = newVal;
568 	}
569 }
570 
571 
572 /// runs a loop waiting for events to occur, and calling a event-handler, while `isRunning == true`
573 /// 
574 /// `node` is the Node to run the loop for
575 /// `eventHandler` is the function to call when any event occurs
576 /// `isRunning` is the variable that specifies if the loop is still running, it can be terminated using `isRunning=false`
577 void runNetLoop(Node node, void function(NetEvent) eventHandler, ref shared(bool) isRunning, TimeVal timeout){
578 	while(isRunning){
579 		NetEvent[] events = node.getEvent(&timeout);
580 		foreach (event; events){
581 			eventHandler(event);
582 		}
583 	}
584 }