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 }