From 6889e2d66b710c241b3884fc28610a9e6be4e610 Mon Sep 17 00:00:00 2001
From: Matt Strapp <matt@mattstrapp.net>
Date: Mon, 25 Apr 2022 17:48:52 -0500
Subject: A

---
 src/client.cpp | 591 ++++++++++++++++++++++++++++++++++++++++++
 src/comms.cpp  | 169 ++++++++++++
 src/server.cpp | 796 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util.cpp   | 398 +++++++++++++++++++++++++++++
 4 files changed, 1954 insertions(+)
 create mode 100644 src/client.cpp
 create mode 100644 src/comms.cpp
 create mode 100644 src/server.cpp
 create mode 100644 src/util.cpp

(limited to 'src')

diff --git a/src/client.cpp b/src/client.cpp
new file mode 100644
index 0000000..baf2215
--- /dev/null
+++ b/src/client.cpp
@@ -0,0 +1,591 @@
+#include "client.hpp"
+
+// Connection 0: Client to Server data communication
+#define C2S_DATA 0
+// Connection 1: Server to Client data communication
+#define S2C_DATA 1
+// Other: Temporary file transer
+
+#define C2S_SERVICE_FREQ 1.0 // seconds
+#define MAX_CONCURRENCY_LIMIT 1000
+#define POLL_TIMEOUT 333 // poll timeout in ms
+
+int nConns; // number of active connections
+struct pollfd
+    peers[MAX_CONCURRENCY_LIMIT + 1]; // sockets to be monitored by poll()
+struct CONN_STAT
+    connStat[MAX_CONCURRENCY_LIMIT + 1]; // app-layer stats of the sockets
+BYTE *buf[MAX_CONCURRENCY_LIMIT + 1];
+
+struct sockaddr_in serverAddr;
+int id;
+
+// Duplicate exists in server and client
+void RemoveConnection(int i) {
+  close(peers[i].fd);
+  if (i < nConns) {
+    memmove(peers + i, peers + i + 1, (nConns - i) * sizeof(struct pollfd));
+    memmove(connStat + i, connStat + i + 1,
+            (nConns - i) * sizeof(struct CONN_STAT));
+    free(buf[i]);
+    memmove(buf + i, buf + i + 1, (nConns - i) * sizeof(BYTE *));
+  }
+  nConns--;
+  // Log("[%s] %d active connections after removal", SERVER_NAME, nConns);
+}
+
+int Send_Blocking(int sockFD, const BYTE *data, int len) {
+  int nSent = 0;
+  while (nSent < len) {
+    int n = send(sockFD, data + nSent, len - nSent, 0);
+    if (n >= 0) {
+      nSent += n;
+    } else if (n < 0 && (errno == ECONNRESET || errno == EPIPE)) {
+      Log("Connection closed.");
+      close(sockFD);
+      return -1;
+    } else {
+      Error("Unexpected error %d: %s.", errno, strerror(errno));
+    }
+  }
+  return 0;
+}
+
+void DoClient(const char *svrIP, int svrPort, const char *fileName) {
+  memset(&serverAddr, 0, sizeof(serverAddr));
+  serverAddr.sin_family = AF_INET;
+  serverAddr.sin_port = htons((unsigned short)svrPort);
+  inet_pton(AF_INET, svrIP, &serverAddr.sin_addr);
+
+  // ignore the SIGPIPE signal that may crash the program in some corner cases
+  signal(SIGPIPE, SIG_IGN);
+
+  // Creating unique identifier for this instance
+  // id = (int)(seconds since 1970) ^ (((int)(process id) & 0x0000FFFF) << 16)
+  struct timeb currentTime;
+  ftime(&currentTime);
+  id = currentTime.time ^ (((int)getpid() & 0x0000FFFF) << 16);
+  // Log("My identifier is %d (%08X)", id, id);
+
+  // create the data communication sockets
+  for (int i = 0; i < 2; i++) {
+    int fd = socket(AF_INET, SOCK_STREAM, 0);
+    if (fd == -1)
+      Error("Cannot create initial sockets.");
+
+    if (connect(fd, (const struct sockaddr *)&serverAddr, sizeof(serverAddr)) !=
+        0) {
+      Error("C2s cannot connect to server %s:%d.", svrIP, svrPort);
+    }
+
+    enum Direction direction;
+    if (i == C2S_DATA) {
+      direction = C2S;
+    } else {
+      direction = S2C;
+    }
+
+    // Notify the server this instance exists
+    BYTE connectBuf[20] = {0};
+    Header header;
+    header.setFlags(direction, SUCCESS, PUB, SIGN);
+    header.m_command = CONNECT;
+    memcpy(header.m_name, "--------", MAX_USERNAME_LEN);
+    header.m_size = 4;
+    header.encode(connectBuf);
+    i2buf(id, connectBuf + HEADER_LEN);
+    // header.displayContents(true);
+
+    if (Send_Blocking(fd, connectBuf, HEADER_LEN + header.m_size) < 0) {
+      Error("Cannot send CONNECT for connection %d", i);
+    }
+
+    SetNonBlockIO(fd); // TODO: Put this in somewhere
+
+    nConns++;
+    peers[i].fd = fd;
+    peers[i].revents = 0;
+
+    buf[i] = (BYTE *)malloc(BUF_LEN);
+
+    memset(&connStat[i], 0, sizeof(struct CONN_STAT));
+    ftime(&connStat[i].lastTime);
+    connStat[i].direction = direction;
+    connStat[i].linkType = MESSAGE_LINK;
+    connStat[i].nBuffered = 0;
+    connStat[i].messageLen = 0;
+
+    if (i == S2C_DATA) {
+      // Log("S2C_DATA is setting POLLRDNORM");
+      connStat[i].expectingHeader = true;
+      connStat[i].nToDo = HEADER_LEN;
+      peers[i].events |= POLLRDNORM; // S2C reads
+    }
+  }
+
+  // Client begins executing commands from the file.
+  FILE *fp = fopen(fileName, "r");
+  if (!fp)
+    Error("Failed to open the file: %s", fileName);
+
+  char *new_line_pos; // Remove '\n' from end of lines
+  char line[LINE_SIZE] = {0};
+  char disposableLine[LINE_SIZE] = {0};
+  char loginName[MAX_USERNAME_LEN + 1] = {0};
+  struct timeb
+      wakeUpTime; // if current time > wakeUpTime, get a line from the file
+  int flag, fd;
+  bool endOfFile = false;
+
+  // Wait a second for server to instanciate both connections.
+  // Sometimes responses aren't sent since the s2c connection hasn't
+  // finished connecting before the c2s connection sends a message.
+  sleep(1);
+
+  while (1) {
+    ftime(&currentTime);
+
+    double timeDiff = timeDifference(currentTime, wakeUpTime);
+    if ((timeDiff > 0) && (!endOfFile)) {
+      if (fgets(line, LINE_SIZE, fp) == NULL) {
+        Log("End of command file.");
+        endOfFile = true;
+        goto DO_POLL;
+      }
+
+      new_line_pos = strchr(line, '\n');
+      if (new_line_pos != NULL)
+        *new_line_pos = 0;
+
+      // TODO: Parse the command here, append messages to buffers as necessary
+      strncpy(disposableLine, line, LINE_SIZE);
+      enum Command command = parse_command(disposableLine);
+      strncpy(disposableLine, line, LINE_SIZE);
+      Header header;
+      char *tok, *rest;
+      int offset, nbytes, filenameLen = 0;
+      BYTE *stagingBuf = (BYTE *)malloc(BUF_LEN);
+
+      switch (command) {
+      case DELAY:
+        wakeUpTime = currentTime;
+        wakeUpTime.time += atoi(&line[6]);
+        break;
+      case REGISTER:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN);
+        header.m_command = REGISTER;
+        tok = strtok(disposableLine, " "); // REGISTER
+        tok = strtok(NULL, " ");           // [username]
+        memcpy(header.m_name, tok, strlen(tok));
+        tok = strtok(NULL, " "); // [password]
+        header.m_size = strlen(tok);
+        header.encode(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered]);
+        memcpy(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered + HEADER_LEN], tok,
+               strlen(tok));
+        prepareSend(C2S_DATA, header);
+        break;
+      case LOGIN:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN);
+        header.m_command = LOGIN;
+        tok = strtok(disposableLine, " "); // LOGIN
+        tok = strtok(NULL, " ");           // [username]
+        memcpy(header.m_name, tok, strlen(tok));
+        tok = strtok(NULL, " "); // [password]
+        header.m_size = strlen(tok);
+        header.encode(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered]);
+        memcpy(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered + HEADER_LEN], tok,
+               strlen(tok));
+        prepareSend(C2S_DATA, header);
+        break;
+      case LOGOUT:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN);
+        header.m_command = LOGOUT;
+        memcpy(header.m_name, "-logout-", 8);
+        header.m_size = 0;
+        header.encode(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered]);
+        prepareSend(C2S_DATA, header);
+        break;
+      case LIST:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN);
+        header.m_command = LIST;
+        memcpy(header.m_name, "--list--", 8);
+        header.m_size = 0;
+        header.encode(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered]);
+        prepareSend(C2S_DATA, header);
+        break;
+      case SEND:
+      case SENDA:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN); // Will update trace as needed
+        if (command == SEND)
+          header.m_trace = SIGN;
+        else
+          header.m_trace = ANNON;
+        header.m_command = command;
+        memcpy(header.m_name, "--send--", 8);
+        tok = strtok_r(disposableLine, " ", &rest); // SEND[A]
+        header.m_size = strlen(rest); // Everything after 'SEND[A] '
+        header.encode(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered]);
+        memcpy(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered + HEADER_LEN], rest,
+               header.m_size);
+        prepareSend(C2S_DATA, header);
+        break;
+      case SEND2:
+      case SENDA2:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN); // Will update trace as needed
+        if (command == SEND2)
+          header.m_trace = SIGN;
+        else
+          header.m_trace = ANNON;
+        header.m_command = command;
+        tok = strtok_r(disposableLine, " ", &rest); // SEND2[A]
+        tok = strtok_r(rest, " ", &rest);           // [username]
+        memcpy(header.m_name, tok, strlen(tok));
+        header.m_size = strlen(rest); // Everything after 'SEND2[A] [username] '
+        header.encode(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered]);
+        memcpy(&buf[C2S_DATA][connStat[C2S_DATA].nBuffered + HEADER_LEN], rest,
+               header.m_size);
+        prepareSend(C2S_DATA, header);
+        break;
+      case SENDF:
+      case SENDF2:
+        header.setFlags(C2S, SUCCESS, PUB, SIGN); // Will update trace as needed
+        header.m_command = command;
+        if (command == SENDF) {
+          header.m_recipient = PUB;
+          memcpy(header.m_name, "--senf--", 8);
+          tok = strtok(disposableLine, " "); // SENDF
+          tok = strtok(NULL, " ");           // [file name]
+        } else {                             // SENDF2
+          header.m_recipient = PRIV;
+          tok = strtok(disposableLine, " "); // SENDF2
+          tok = strtok(NULL, " ");           // [username]
+          memcpy(header.m_name, tok, strlen(tok));
+          tok = strtok(NULL, " "); // [file name]
+        }
+
+        offset = 0;
+        i2buf(id, &stagingBuf[offset]); //  stagingBuf skips header
+        offset += 4;                    // unique id
+        filenameLen = strlen(tok);
+        i2buf(filenameLen, &stagingBuf[offset]);
+        offset += 4; // int length of file name
+
+        memcpy(&stagingBuf[offset], tok, strlen(tok));
+        offset += strlen(tok); // file name
+
+        nbytes = file2buf(tok, &stagingBuf[offset + 4]);
+        if (nbytes == -1)
+          break;
+        i2buf(nbytes, &stagingBuf[offset]);
+        offset += 4;      // file size int
+        offset += nbytes; // file length (offest now tells how many bytes to
+                          // copy into buf)
+
+        header.m_size = offset;
+        // Make a new connection
+        if (nConns >= MAX_CONCURRENCY_LIMIT) {
+          Log("Cannot perform file transfer. Maximum concurency reached.");
+        } else {
+          fd = socket(AF_INET, SOCK_STREAM, 0);
+          if (fd == -1) {
+            Log("Cannot create new socket.");
+          } else if (connect(fd, (const struct sockaddr *)&serverAddr,
+                             sizeof(serverAddr)) != 0) {
+            Log("Cannot connect to server for file transfer");
+          } else {
+            if (command == SENDF)
+              Log("[%s] Sending %s to everyone currently logged in.",
+                  NAME, tok);
+            else
+              Log("[%s] Sending %s to %s.", NAME, tok, header.m_name);
+
+            SetNonBlockIO(fd);
+            nConns++;
+            peers[nConns - 1].fd = fd;
+            peers[nConns - 1].revents = 0;
+
+            buf[nConns - 1] = (BYTE *)malloc(BUF_LEN);
+            header.encode(buf[nConns - 1]);
+            memcpy(&buf[nConns - 1][HEADER_LEN], stagingBuf, header.m_size);
+
+            memset(&connStat[nConns - 1], 0, sizeof(struct CONN_STAT));
+            ftime(&connStat[nConns - 1].lastTime);
+            connStat[nConns - 1].direction = C2S;
+            connStat[nConns - 1].linkType = FILE_LINK;
+            // TODO: Kill the connection??
+            prepareSend(nConns - 1, header);
+          }
+        }
+        break;
+      default:
+        Log("Cannot execute %s command: %s", com2str(command), line);
+      }
+      free(stagingBuf);
+    }
+
+  DO_POLL:
+    int nReady = poll(peers, nConns, POLL_TIMEOUT);
+    // Log("nReady %d", nReady);
+
+    if (nConns == 0) {
+      Log("[%s] The server has gone offline.", NAME);
+      return;
+    }
+
+    for (int i = 0; i < nConns; i++) {
+      // Handle reading data (s2c)
+      if (peers[i].revents & (POLLRDNORM | POLLERR | POLLHUP)) {
+        // Log("Recieving nonblocking!");
+        if (connStat[i].direction == S2C) {
+          if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+              0) {
+            // Log("Line %d: Removing connection %d", __LINE__, i);
+            RemoveConnection(i);
+            goto NEXT_CONNECTION;
+          }
+
+          if (connStat[i].nToDo == 0) {
+            if (processReception(i) != 0) {
+              // Log("Line %d: Removing connection %d", __LINE__, i);
+              RemoveConnection(i);
+            }
+            goto NEXT_CONNECTION;
+          }
+        }
+      }
+
+      // Handle sending data (c2s)
+      if (peers[i].revents & (POLLWRNORM | POLLERR | POLLHUP)) {
+        // Log("Sending nonblocking!");
+        if (connStat[i].direction == C2S) {
+          if (Send_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+              0) {
+            // Log("Line %d: Removing connection %d", __LINE__, i);
+            RemoveConnection(i);
+            goto NEXT_CONNECTION;
+          }
+        }
+      }
+
+      // Check that the C2S connection is still open by sending a PING packet
+      if ((connStat[i].direction == C2S) &&
+          (timeDifference(currentTime, connStat[i].lastTime) >
+           C2S_SERVICE_FREQ) &&
+          (connStat[i].nToDo == 0) && (connStat[i].messageLen == 0) &&
+          (connStat[i].nBuffered == 0)) {
+        // Log("[%s] Pinging server on connection %d due to timeout",
+        // SERVER_NAME, i);
+        Header header;
+        header.setFlags(C2S, SUCCESS, PRIV, SIGN);
+        memcpy(header.m_name, "-client-", MAX_USERNAME_LEN);
+        header.m_command = PING;
+        header.m_size = 0;
+
+        header.encode(&buf[i][connStat[i].messageLen]);
+        connStat[i].nBuffered = HEADER_LEN + header.m_size;
+        connStat[i].messageLen = HEADER_LEN + header.m_size;
+        connStat[i].nToDo = HEADER_LEN + header.m_size;
+
+        peers[i].events |= POLLWRNORM;
+      }
+
+    NEXT_CONNECTION:
+      asm("nop");
+    }
+  }
+
+  // TODO: Close all open sockets
+  fclose(fp);
+}
+
+void prepareSend(int i, Header header) {
+  if (connStat[i].nBuffered == 0) {
+    connStat[i].messageLen = HEADER_LEN + header.m_size;
+    connStat[i].nToDo = HEADER_LEN + header.m_size;
+  }
+  connStat[i].nBuffered += HEADER_LEN + header.m_size;
+  peers[i].events |= POLLWRNORM;
+}
+
+int processReception(int i) {
+  Header header;
+  header.decode(buf[i]);
+
+  if (connStat[i]
+          .expectingHeader) { // Expecting a header read sequence was completed
+    // Log("\nProcessing header reception.");
+    // header.displayContents(true);
+    if (header.m_size > 0) {
+      connStat[i].expectingHeader = false;
+      connStat[i].nToDo = header.m_size;
+    } else if (header.m_size == 0) {
+      // printMessage(i);
+      doClientCommand(i);
+      connStat[i].expectingHeader = true;
+      connStat[i].nBuffered = 0;
+      connStat[i].nToDo = HEADER_LEN;
+      memset(buf[i], 0, BUF_LEN);
+    } else {
+      return -1; // Error, signal to caller to end connection
+    }
+  } else { // expecting a data read sequence was completed
+    // Log("\nProcessing data reception.");
+    // printMessage(i);
+    doClientCommand(i);
+    connStat[i].expectingHeader = true;
+    connStat[i].nBuffered = 0;
+    connStat[i].nToDo = HEADER_LEN;
+    memset(buf[i], 0, BUF_LEN);
+  }
+
+  if (connStat[i].shouldClose) {
+    // Log("[%s] Line %d: Removing connection %d due to shouldClose", SERVER_NAME,
+    // __LINE__, i);
+    RemoveConnection(i);
+  }
+
+  return 0;
+}
+
+void doClientCommand(int i) {
+  Header header;
+  header.decode(buf[i]);
+
+  int file_id;
+  int fd;
+  int offset;
+  char filename[5000];
+  int filenameLen, fileLen;
+
+  switch (header.m_command) {
+  case REGISTER:
+  case LOGIN:
+  case LOGOUT:
+  case LIST:
+    Log("[%s] %s", header.m_name, &buf[i][HEADER_LEN]);
+    break;
+  case SEND:
+  case SENDA:
+    Log("[%s] %s", header.m_name, &buf[i][HEADER_LEN]);
+    break;
+  case SEND2:
+  case SENDA2:
+    Log("<%s> %s", header.m_name, &buf[i][HEADER_LEN]);
+    break;
+  case SENDF:
+  case SENDF2:
+    // "[person] File transfer: <file name>"
+    // First 4 bytes are file id
+    buf2i(&buf[i][HEADER_LEN], file_id);
+    if (header.m_command == SENDF)
+      Log("[%s] Initiating file transfer: %s", header.m_name,
+          &buf[i][HEADER_LEN + 4]);
+    else
+      Log("<%s> Initiating file transfer: %s", header.m_name,
+          &buf[i][HEADER_LEN + 4]);
+    // Open new S2C connection with server for file transfer
+    fd = socket(AF_INET, SOCK_STREAM, 0);
+    if (fd == -1)
+      Log("[%s] Warning: Cannot create file transfer socket.", NAME);
+    else if (connect(fd, (const struct sockaddr *)&serverAddr,
+                     sizeof(serverAddr)) != 0)
+      Log("[%s] Warning: File transfer cannot connect to server", NAME);
+    else {
+      // Usual setup
+      SetNonBlockIO(fd);
+
+      if (nConns < MAX_CONCURRENCY_LIMIT) {
+        nConns++;
+        peers[nConns - 1].fd = fd;
+        peers[nConns - 1].revents = 0;
+
+        buf[nConns - 1] = (BYTE *)malloc(BUF_LEN);
+
+        memset(&connStat[nConns - 1], 0, sizeof(struct CONN_STAT));
+        ftime(&connStat[nConns - 1].lastTime);
+        connStat[nConns - 1].direction =
+            C2S; // will flip on completion of this send
+        connStat[nConns - 1].linkType = FILE_LINK;
+        connStat[nConns - 1].changeDirection = true;
+
+        Header outHeader;
+        outHeader.setFlags(S2C, SUCCESS, PUB,
+                           SIGN); // S2C used by server to set direction
+        if (header.m_command == SENDF2)
+          outHeader.m_recipient = PRIV;
+        outHeader.m_command = GETF;
+        memcpy(outHeader.m_name, header.m_name,
+               MAX_USERNAME_LEN); // pass along the name of the original sender
+        outHeader.m_size =
+            4 + header.m_size; // [client id] [file id] [file name]
+        outHeader.encode(buf[nConns - 1]);
+        offset = HEADER_LEN;
+        i2buf(id, buf[nConns - 1] + offset); // encode client id
+        offset += 4;
+        memcpy(&buf[nConns - 1][offset], &buf[i][HEADER_LEN],
+               header.m_size); // encode file id and file name
+        offset += header.m_size;
+
+        prepareSend(nConns - 1, outHeader);
+      } else
+        Log("[%s] Error: Cannot perform file transfer. Max concurrency "
+            "reached.",
+            NAME);
+    }
+    break;
+  case GETF:
+    if (header.m_flag == SUCCESS) {
+      buf2i(&buf[i][HEADER_LEN], filenameLen);
+      memcpy(filename, &buf[i][HEADER_LEN + 4], filenameLen);
+      buf2i(&buf[i][HEADER_LEN + 4 + filenameLen], fileLen);
+      buf2file(&buf[i][HEADER_LEN + 4 + filenameLen + 4], fileLen, filename);
+      if (header.m_recipient == PUB)
+        Log("[%s] File transfer complete: %s", header.m_name, filename);
+      else
+        Log("<%s> File transfer complete: %s", header.m_name, filename);
+    } else {
+      if (header.m_recipient == PUB)
+        Log("[%s] File transfer failed.", header.m_name);
+      else
+        Log("<%s> File transfer failed.", header.m_name);
+    }
+    connStat[i].shouldClose = true;
+    break;
+  case PING:
+    // Log("Ping from %s", header.m_name);
+    asm("nop"); // do nothing
+    break;
+  default:
+    Log("[%s] No doClientCommand() for %s", NAME,
+        com2str(header.m_command));
+  }
+}
+
+void printMessage(int i) {
+  Header h;
+  h.decode(buf[i]);
+
+  Log("[%s, %s, %s, %s] [%s]  [%s]  [%d]", !h.m_direction ? "C2S" : "S2C",
+      !h.m_flag ? "SUCCESS" : "FAIL", !h.m_recipient ? "PUB" : "PRIV",
+      !h.m_trace ? "SIGN" : "ANNON", com2str(h.m_command), h.m_name, h.m_size);
+
+  if (h.m_size > 0)
+    Log("\tData: [%s]", buf[i] + HEADER_LEN);
+}
+
+int main(int argc, char **argv) {
+
+  if (argc != 4) {
+    Log("Usage: %s [server IP] [server Port] [command script name]", argv[0]);
+    return -1;
+  }
+
+  const char *serverIP = argv[1];
+  int port = atoi(argv[2]);
+  const char *fileName = argv[3];
+
+  srand(time(NULL));
+
+  DoClient(serverIP, port, fileName);
+  return 0;
+}
diff --git a/src/comms.cpp b/src/comms.cpp
new file mode 100644
index 0000000..14fcd61
--- /dev/null
+++ b/src/comms.cpp
@@ -0,0 +1,169 @@
+#include "comms.hpp"
+
+int Send_NonBlocking(int sockFD, BYTE *data, struct CONN_STAT *pStat,
+                     struct pollfd *pPeer) {
+  while (pStat->nToDo > 0) {
+    // pStat keeps tracks of how many bytes have been sent, allowing us to
+    // "resume" when a previously non-writable socket becomes writable.
+    ftime(&(pStat->lastTime));
+    int n =
+        send(sockFD, data + pStat->messageLen - pStat->nToDo, pStat->nToDo, 0);
+    // Log("Send_NonBlocking n: %d", n);
+    if (n >= 0) {
+      pStat->nToDo -= n;
+    } else if (n < 0 && (errno == ECONNRESET || errno == EPIPE)) {
+      // Log("Connection closed.");
+      close(sockFD);
+      return -1;
+    } else if (n < 0 && (errno == EWOULDBLOCK)) {
+      // The socket becomes non-writable. Exit now to prevent blocking.
+      // OS will notify us when we can write
+      // Log("Writing would block. Waiting for OS to notify send.");
+      pPeer->events |= POLLWRNORM;
+      return 0;
+    } else {
+      Error("Unexpected send error %d: %s", errno, strerror(errno));
+    }
+  }
+
+  // Log("Message was completely written out (messagelen was message written
+  // out)"); Log("What's currently in there: nBuffered %d, messageLen: %d, nToDo
+  // %d", pStat->nBuffered, pStat->messageLen, pStat->nToDo);
+  memcpy(data, data + pStat->messageLen, BUF_LEN - pStat->messageLen); // Good
+  memset(data + pStat->nBuffered - pStat->messageLen, 0,
+         BUF_LEN - pStat->nBuffered + pStat->messageLen);
+  pStat->nBuffered -= pStat->messageLen;
+  if (pStat->nBuffered > 0) { // start off the next send if one is queued
+    Header nextHeader;
+    nextHeader.decode(data);
+    // Log("Another message is queued of size 13 + %d", nextHeader.m_size);
+    pStat->messageLen = HEADER_LEN + nextHeader.m_size;
+    pStat->nToDo = HEADER_LEN + nextHeader.m_size;
+  } else {
+    // Log("No other messages are queued");
+    pStat->messageLen = 0;
+    pStat->nToDo = 0;
+    pPeer->events &= ~POLLWRNORM; // no more bytes to send? Stop listening for
+                                  // the writable cue.
+  }
+  // Log("What's in there now: nBuffered %d, messageLen: %d, nToDo %d",
+  // pStat->nBuffered, pStat->messageLen, pStat->nToDo);
+
+  // Change the connection from a sender to a reciever (used by client)
+  if (pStat->changeDirection && (pStat->nToDo == 0)) {
+    // Log("Changing direction!: nBuffered: %d", pStat->nBuffered);
+    pStat->direction = (pStat->direction == C2S) ? S2C : C2S;
+    pStat->expectingHeader = true;
+    pStat->nToDo = HEADER_LEN;
+    pStat->changeDirection = false;
+    pPeer->events &= ~POLLWRNORM; // stop listening for writable cue
+    pPeer->events |= POLLRDNORM;  // start listening for readable cue
+  }
+
+  return 0;
+}
+
+// TODO: Question: Why isn't the pPeer datastructure used?
+int Recv_NonBlocking(int sockFD, BYTE *data, struct CONN_STAT *pStat,
+                     struct pollfd *pPeer) {
+  // pStat keeps tracks of how many bytes have been rcvd, allowing us to
+  // "resume" when a previously non-readable socket becomes readable. Log("Recv:
+  // pStat->nToDo %d",pStat->nToDo);
+  while (pStat->nToDo > 0) {
+    int n = recv(sockFD, data + pStat->nBuffered, pStat->nToDo, 0);
+    // Log("Rev_NonBlocking recieved %d bytes", n);
+    if (n > 0) {
+      pStat->nBuffered += n;
+      pStat->nToDo -= n;
+    } else if (n == 0 || (n < 0 && errno == ECONNRESET)) {
+      // Log("\nConnection closed. n = %d", n);
+      close(sockFD);
+      return -1;
+    } else if (n < 0 && (errno == EWOULDBLOCK)) {
+      // Log("\nWould block waiting for read...");
+      // The socket becomes non-readable. Exit now to prevent blocking.
+      // OS will notify us when we can read
+      return 0;
+    } else {
+      Error("Unexpected recv error %d: %s.", errno, strerror(errno));
+    }
+  }
+
+  return 0;
+}
+
+void SetNonBlockIO(int fd) {
+  int val = fcntl(fd, F_GETFL, 0);
+  if (fcntl(fd, F_SETFL, val | O_NONBLOCK) != 0) {
+    Error("Cannot set nonblocking I/O.");
+  }
+}
+
+Header::Header() {
+  m_direction = C2S;
+  m_flag = SUCCESS;
+  m_recipient = PUB;
+  m_trace = SIGN;
+  m_command = INVALID;
+  memset(m_name, 0, MAX_USERNAME_LEN + 1);
+  m_size = 0;
+}
+
+// encode the message to the buffer
+void Header::encode(BYTE *buf) {
+  memset(buf, 0, HEADER_LEN);
+  int index = 0;
+
+  BYTE dir_b = (BYTE)(m_direction << 7);
+  BYTE flag_b = (BYTE)(m_flag << 6);
+  BYTE recip_b = (BYTE)(m_recipient << 5);
+  BYTE trace_b = (BYTE)(m_trace << 4);
+  BYTE com_b = (BYTE)m_command & 0x0F;
+
+  buf[index++] = dir_b | flag_b | recip_b | trace_b | com_b;
+
+  memcpy(&buf[index], m_name, 8);
+  index += 8;
+
+  // NOTE: There was a problem encoding very large sizes.
+  i2buf(m_size, &buf[index]);
+  index += 4;
+}
+
+// decode a buffer into a message object
+void Header::decode(BYTE *buf) {
+  int index = 0;
+
+  m_direction = (enum Direction)((buf[index] >> 7) & 0x01);
+  m_flag = (enum Flag)((buf[index] >> 6) & 0x01);
+  m_recipient = (enum Recipient)((buf[index] >> 5) & 0x01);
+  m_trace = (enum Trace)((buf[index] >> 4) & 0x01);
+  m_command = (enum Command)(buf[index] & 0x0F);
+  index += 1;
+
+  for (int x = 0; x < MAX_USERNAME_LEN; x++)
+    m_name[x] = buf[index++];
+
+  buf2i(&buf[index], m_size);
+  index += 4;
+}
+
+void Header::setFlags(enum Direction direction, enum Flag flag,
+                      enum Recipient recipient, enum Trace trace) {
+  m_direction = direction;
+  m_flag = flag;
+  m_recipient = recipient;
+  m_trace = trace;
+}
+
+// Helper to print contents of class
+void Header::displayContents(bool tab = false) {
+  Log("%sDirection: %s,  Flag: %s,  Recipient: %s,  Trace: %s", tab ? "\t" : "",
+      !m_direction ? "C2S" : "S2C", !m_flag ? "SUCCESS" : "FAIL",
+      !m_recipient ? "PUB" : "PRIV", !m_trace ? "SIGN" : "ANNON");
+  Log("%sCommand: %s", tab ? "\t" : "", com2str(m_command));
+  char name[9] = {0};
+  memcpy(name, m_name, MAX_USERNAME_LEN);
+  Log("%sUsername: |%s|", tab ? "\t" : "", name);
+  Log("%sSize: %d", tab ? "\t" : "", m_size);
+}
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 0000000..270a350
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,796 @@
+#include "server.hpp"	
+
+#define MAX_CONCURRENCY_LIMIT 40000 // overkill
+#define POLL_TIMEOUT 1000           // Milliseconds
+#define S2C_SERVICE_FREQ 1.0        // Seconds
+#define USER_DATABASE "user_database.txt"
+#define FILE_DATABASE "file_database.txt"
+#define FILES_DIR "files"
+
+int nConns; // total # of data sockets
+struct pollfd
+    peers[MAX_CONCURRENCY_LIMIT + 1]; // sockets to be monitored by poll()
+struct CONN_STAT
+    connStat[MAX_CONCURRENCY_LIMIT + 1]; // app-layer stats of the sockets
+BYTE *buf[MAX_CONCURRENCY_LIMIT + 1];
+
+// Duplicate exists in server and client
+void RemoveConnection(int i) {
+  close(peers[i].fd);
+  if (i < nConns) {
+    memmove(peers + i, peers + i + 1, (nConns - i) * sizeof(struct pollfd));
+    memmove(connStat + i, connStat + i + 1,
+            (nConns - i) * sizeof(struct CONN_STAT));
+    free(buf[i]);
+    memmove(buf + i, buf + i + 1, (nConns - i) * sizeof(BYTE *));
+  }
+  nConns--;
+  Log("[%s] %d active connections after removal", NAME, nConns);
+}
+
+void DoServer(int svrPort) {
+  int maxConcurrency = 20; // TODO: Change this to be a #define
+  int listenFD = socket(AF_INET, SOCK_STREAM, 0);
+  if (listenFD < 0) {
+    Error("[%s] Cannot create listening socket.", NAME);
+  }
+  SetNonBlockIO(listenFD);
+
+  struct sockaddr_in serverAddr;
+  memset(&serverAddr, 0, sizeof(struct sockaddr_in));
+  serverAddr.sin_family = AF_INET;
+  serverAddr.sin_port = htons((unsigned short)svrPort);
+  serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+  int optval = 1;
+  int r =
+      setsockopt(listenFD, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
+  if (r != 0) {
+    Error("[%s] Cannot enable SO_REUSEADDR option.", NAME);
+  }
+
+  signal(SIGPIPE, SIG_IGN);
+
+  if (bind(listenFD, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) {
+    Error("[%s] Cannot bind to port %d.", NAME, svrPort);
+  }
+
+  if (listen(listenFD, 16) != 0) {
+    Error("[%s] Cannot listen to port %d.", NAME, svrPort);
+  }
+
+  nConns = 0;
+  memset(peers, 0, sizeof(peers));
+  peers[0].fd = listenFD;
+  peers[0].events = POLLRDNORM;
+  memset(connStat, 0, sizeof(connStat));
+
+  int connID = 0;
+  while (1) {
+    // monitor the listening sock and data socks, nConn+1 in total
+    int nReady = poll(peers, nConns + 1, POLL_TIMEOUT);
+    if (nReady < 0) {
+      Error("[%s] Invalid poll() return value.", NAME);
+    }
+
+    struct sockaddr_in clientAddr;
+    socklen_t clientAddrLen = sizeof(clientAddr);
+
+    struct timeb currentTime;
+    ftime(&currentTime);
+
+    // new incoming connection
+    if ((peers[0].revents & POLLRDNORM) && (nConns < maxConcurrency)) {
+      int fd = accept(listenFD, (struct sockaddr *)&clientAddr, &clientAddrLen);
+      if (fd != -1) {
+        SetNonBlockIO(fd);
+        nConns++;
+        Log("[%s] Accepted connection %d", NAME, nConns);
+        peers[nConns].fd = fd;
+        peers[nConns].events = POLLRDNORM;
+        peers[nConns].revents = 0;
+
+        memset(&connStat[nConns], 0, sizeof(struct CONN_STAT));
+        ftime(&connStat[nConns].lastTime);
+        connStat[nConns].recInitHeader =
+            false; // waiting to recieve initial header
+        connStat[nConns].expectingHeader =
+            true; // message to be read is a header
+        connStat[nConns].nToDo = HEADER_LEN;
+
+        buf[nConns] = (BYTE *)malloc(BUF_LEN);
+        memset(buf[nConns], 0, BUF_LEN);
+      }
+
+      if (--nReady <= 0)
+        continue;
+    }
+
+    for (int i = 1; i <= nConns; i++) {
+      // Log("nConns: %d peers[%d].revents: %02X (POLLRDNORM %02X, POLLWRNORM
+      // %02X, POLLERR %02X, POLLHUP %02X)", nConns, i, peers[i].revents,
+      // POLLRDNORM, POLLWRNORM, POLLERR, POLLHUP);
+      double timeDiff =
+          (currentTime.time + currentTime.millitm / (double)1000.0f) -
+          (connStat[i].lastTime.time +
+           connStat[i].lastTime.millitm / (double)1000.0f);
+
+      if (peers[i].revents &
+          (POLLRDNORM | POLLERR |
+           POLLHUP)) { // TODO: break out POLLERR and POLLHUP?
+        // Log("Servicing connection %d, POLLHUP: %d", i, peers[i].revents &
+        // POLLHUP);
+        //  Process initial communication
+        if (connStat[i].recInitHeader == false) {
+          if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+              0) {
+            Log("[%s] Line %d: Removing connection %d", NAME, __LINE__,
+                i);
+            RemoveConnection(i);
+            goto NEXT_CONNECTION;
+          }
+
+          if (connStat[i].nToDo == 0) {
+            Header header;
+            header.decode(buf[i]);
+            // Log("Initial communication for connection %d:", i);
+
+            if (!((header.m_command == CONNECT) | (header.m_command == SENDF) |
+                  (header.m_command == SENDF2) | (header.m_command == GETF))) {
+              Log("[%s] WARNING: Unexpected initial connection command %s",
+                  NAME, com2str(header.m_command));
+            }
+
+            connStat[i].recInitHeader = true;
+            connStat[i].expectingHeader =
+                true; // flag needed for processing below
+            if (processReception(i) != 0) {
+              Log("[%s] Line %d: Removing connection %d", NAME, __LINE__,
+                  i);
+              RemoveConnection(i);
+              goto NEXT_CONNECTION;
+            }
+          }
+        }      // End initial communication
+        else { // Standard polling recieve
+          // Log("Standard polling recieve/send: connStat[i].direction: %02X",
+          // connStat[i].direction);
+          if (connStat[i].direction == C2S) {
+            if (Recv_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+                0) {
+              Log("[%s] Line %d: Removing connection %d", NAME, __LINE__,
+                  i);
+              RemoveConnection(i);
+              goto NEXT_CONNECTION;
+            }
+
+            if (connStat[i].nToDo == 0) {
+              if (processReception(i) != 0) {
+                Log("[%s] Line %d: Removing connection %d", NAME,
+                    __LINE__, i);
+                RemoveConnection(i);
+                goto NEXT_CONNECTION;
+              }
+            }
+          } // end C2S
+        }   // End standard recieve
+      }     // if POLLRDNORM
+
+      // a data socket is writable
+      if (peers[i].revents & POLLWRNORM) {
+        // Log("Sending nonblocking!");
+        if (Send_NonBlocking(peers[i].fd, buf[i], &connStat[i], &peers[i]) <
+            0) {
+          Log("[%s] Line %d: Removing connection %d", NAME, __LINE__, i);
+          RemoveConnection(i);
+          goto NEXT_CONNECTION;
+        }
+      }
+
+      // a s2c connection hasn't been used in a while? Check that the client is
+      // still there with a ping
+      if ((connStat[i].direction == S2C) && (timeDiff > S2C_SERVICE_FREQ) &&
+          (connStat[i].nToDo == 0) && (connStat[i].messageLen == 0) &&
+          (connStat[i].nBuffered == 0)) {
+        // Log("[%s] Pinging [%s] on connection %d due to timeout", SERVER_NAME,
+        // connStat[i].name, i);
+        Header header;
+        header.setFlags(S2C, SUCCESS, PRIV, SIGN);
+        memcpy(header.m_name, NAME, MAX_USERNAME_LEN);
+        header.m_command = PING;
+        header.m_size = 0;
+
+        header.encode(&buf[i][connStat[i].messageLen]);
+        connStat[i].nBuffered = HEADER_LEN + header.m_size;
+        connStat[i].messageLen = HEADER_LEN + header.m_size;
+        connStat[i].nToDo = HEADER_LEN + header.m_size;
+
+        peers[i].events |= POLLWRNORM;
+      }
+
+    NEXT_CONNECTION:
+      asm("nop");
+      // if (--nReady <= 0) break;
+    } // for
+  }   // while
+} // do_server
+
+// buf has all buffers
+// Stat has all stat structs
+// i is the index of the link being processed
+int processReception(int i) {
+  Header header;
+  header.decode(buf[i]);
+  // header.displayContents(true);
+
+  if (connStat[i]
+          .expectingHeader) { // Expecting a header read sequence was completed
+    // Log("\nProcessing header reception. header.m_size: %d", header.m_size);
+    if (header.m_size > 0) {
+      connStat[i].expectingHeader = false;
+      connStat[i].nToDo = header.m_size;
+    } else if (header.m_size == 0) {
+      // printServerCommand(i);
+      doServerCommand(i);
+      connStat[i].expectingHeader = true;
+      connStat[i].nBuffered = 0;
+      connStat[i].nToDo = HEADER_LEN;
+      memset(buf[i], 0, BUF_LEN);
+    } else {
+      return -1; // Error, signal to caller to end connection
+    }
+  } else { // expecting a data read sequence was completed
+    // Log("\nProcessing data reception.");
+    // printServerCommand(i);
+    doServerCommand(i);
+    if (connStat[i].direction == C2S) { // CONNECT events for S2C can get here
+      connStat[i].expectingHeader = true;
+      connStat[i].nBuffered = 0;
+      connStat[i].nToDo = HEADER_LEN;
+      memset(buf[i], 0, BUF_LEN);
+    }
+  }
+
+  if (connStat[i].shouldClose) {
+    Log("[%s] Line %d: Removing connection %d due to shouldClose", NAME,
+        __LINE__, i);
+    RemoveConnection(i);
+  }
+
+  return 0;
+}
+
+void doServerCommand(int i) {
+  // Assume the buf[i] has a message at the beginning of the buffers
+  Header header;
+  header.decode(buf[i]);
+
+  struct timeb currentTime;
+  ftime(&currentTime);
+
+  char message[5000] = {0};
+  char username[20] = {0};
+  char password[20] = {0};
+  Header outHeader;
+  int numLoggedIn = 0;
+  bool foundActiveUser = false;
+  int s2cConnNumb = -1;
+  char server_filename[5000] = {0};
+  char filename[5000] = {0};
+  int filenameLen, fileLen, offset, nextFileId, fileId;
+  BYTE intbuf[5] = {0};
+  FILE *fp;
+
+  switch (header.m_command) {
+  case CONNECT:
+    buf2i(&buf[i][HEADER_LEN], connStat[i].id);
+    connStat[i].direction = header.m_direction;
+    connStat[i].linkType = MESSAGE_LINK;
+    connStat[i].lastTime = currentTime;
+    if (header.m_direction == C2S) {
+      Log("[---- : %s] id: %d, direction: C2S, connection number: %d.",
+          com2str(header.m_command), connStat[i].id, i);
+      connStat[i].expectingHeader = true;
+      connStat[i].nBuffered = 0;
+      connStat[i].nToDo = HEADER_LEN;
+    } else {
+      Log("[---- : %s] id: %d, direction: S2C, connection number: %d.",
+          com2str(header.m_command), connStat[i].id, i);
+      connStat[i].nBuffered = 0;
+      connStat[i].messageLen = 0;
+      connStat[i].nToDo = 0;
+      peers[i].events &= ~POLLRDNORM;
+      peers[i].events &= ~POLLWRNORM;
+    }
+    memset(buf[i], 0, BUF_LEN);
+    break;
+  case REGISTER:
+    memcpy(username, header.m_name, MAX_USERNAME_LEN);
+    memcpy(password, &buf[i][HEADER_LEN], header.m_size);
+    if (usernameQuery(USER_DATABASE, username) == true) {
+      Log("[---- : %s] Registration failed. Username [%s] is already "
+          "registered.",
+          com2str(header.m_command), username);
+      outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+      outHeader.m_command = REGISTER;
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Cannot register username [");
+      strcat(message, username);
+      strcat(message, "]. Username already registered.\n");
+      strcat(message,
+             "\tUsernames may only contain characters a-z, A-Z, or 0-9.\n");
+      strcat(message, "\tUsernames may be 4 to 8 characters long.");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    } else {
+      Log("[---- : %s] Successful registration (usr, pswd): %s  %s",
+          com2str(header.m_command), username, password);
+      recordEntry(USER_DATABASE, username, password);
+      outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+      outHeader.m_command = REGISTER;
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Successfully registered ");
+      strcat(message, username);
+      strcat(message, ".");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    }
+    break;
+  case LOGIN:
+    memcpy(username, header.m_name, MAX_USERNAME_LEN);
+    memcpy(password, &buf[i][HEADER_LEN], header.m_size);
+    if (loginQuery(USER_DATABASE, username, password) == true) {
+      // Check that user is not currently logged in
+      for (int x = 1; x <= nConns; x++) {
+        if ((connStat[x].isLoggedIn == true) &&
+            (strcmp(username, connStat[x].name) == 0)) {
+          Log("[%s : %s] Login failed. User is already logged in.",
+              header.m_name, com2str(header.m_command));
+          foundActiveUser = true;
+          outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+          outHeader.m_command = LOGIN;
+          memcpy(outHeader.m_name, NAME, strlen(NAME));
+          strcat(message, "Login failed. ");
+          strcat(message, username);
+          strcat(message, " is logged in on another device.");
+          outHeader.m_size = strlen(message);
+          sendMessageToId(connStat[i].id, false, outHeader, message);
+          break;
+        }
+      }
+
+      if (!foundActiveUser) {
+        for (int x = 1; x <= nConns; x++) {
+          if (connStat[i].id == connStat[x].id) {
+            memcpy(connStat[x].name, username, MAX_USERNAME_LEN);
+            // Log("Connection %d (%s) is loggin in.", x, connStat[x].name);
+            connStat[x].isLoggedIn = true;
+          }
+        }
+        Log("[%s : %s] Successful login (usr, pswd): %s  %s", connStat[i].name,
+            com2str(header.m_command), username, password);
+        outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+        outHeader.m_command = LOGIN;
+        memcpy(outHeader.m_name, NAME, strlen(NAME));
+        strcat(message, "Welcome ");
+        strcat(message, username);
+        strcat(message, ".");
+        outHeader.m_size = strlen(message);
+        sendMessageToId(connStat[i].id, true, outHeader, message);
+      }
+    } else {
+      Log("[---- : %s] Login failed (usr, pswd): %s  %s",
+          com2str(header.m_command), username, password);
+      outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+      outHeader.m_command = LOGIN;
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Login failed: Invalid username or password.");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    }
+    break;
+  case LOGOUT:
+    if (connStat[i].isLoggedIn) {
+      for (int x = 1; x <= nConns; x++) {
+        if (connStat[i].id == connStat[x].id) {
+          // Log("Connection %d (user %s) is logging off.", x,
+          // connStat[x].name);
+          connStat[x].isLoggedIn = false;
+          foundActiveUser = true;
+        }
+      }
+      Log("[%s : %s] Successful logout.", connStat[i].name,
+          com2str(header.m_command));
+      outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+      outHeader.m_command = LOGOUT;
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Goodbye ");
+      strcat(message, connStat[i].name);
+      strcat(message, ". Come back soon.");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    } else {
+      Log("[---- : %s] Failed: Connection %d is not logged in.",
+          com2str(header.m_command), i);
+      outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+      outHeader.m_command = LOGOUT;
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Logout failed. You are not logged in.");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    }
+    break;
+  case SEND:
+  case SENDA:
+  case SEND2:
+  case SENDA2:
+    if (connStat[i].isLoggedIn) {
+      outHeader.setFlags(S2C, SUCCESS, PUB, header.m_trace);
+      outHeader.m_command = header.m_command;
+      if (header.m_trace == SIGN)
+        memcpy(outHeader.m_name, connStat[i].name, strlen(connStat[i].name));
+      else
+        memcpy(outHeader.m_name, ANON, strlen(ANON));
+      memcpy(message, &buf[i][HEADER_LEN], header.m_size);
+      outHeader.m_size = strlen(message);
+      if ((header.m_command == SEND) || (header.m_command == SENDA)) {
+        Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command),
+            message);
+        sendMessageToAllLoggedIn(outHeader, message);
+      } else {
+        memcpy(username, header.m_name, MAX_USERNAME_LEN);
+        Log("[%s : %s %s] %s", connStat[i].name, com2str(header.m_command),
+            username, message);
+        sendMessageToName(username, outHeader, message);
+      }
+    } else {
+      Log("[%s : %s] Failed: Connection %d is not logged in.", connStat[i].name,
+          com2str(header.m_command), i);
+      outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+      outHeader.m_command = header.m_command;
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Users must be logged in to '");
+      strcat(message, com2str(header.m_command));
+      strcat(message, "'. Login with '");
+      strcat(message, com2str(LOGIN));
+      strcat(message, " [username] [password]'.");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    }
+    break;
+  case SENDF:  // Initial connection
+  case SENDF2: // Initial connection
+    buf2i(&buf[i][HEADER_LEN], connStat[i].id);
+    // Log("This connection's id: %d", connStat[i].id);
+    connStat[i].direction = header.m_direction;
+    connStat[i].linkType = FILE_LINK;
+    connStat[i].lastTime = currentTime;
+    connStat[i].expectingHeader = true;
+    connStat[i].nBuffered = 0;
+    connStat[i].nToDo = HEADER_LEN;
+    connStat[i].shouldClose =
+        true; // flag to close this connection once it's processed
+
+    for (int x = 1; x <= nConns; x++) {
+      if ((connStat[x].direction == C2S) &&
+          (connStat[x].linkType == MESSAGE_LINK) &&
+          (connStat[x].id == connStat[i].id)) {
+        strcat(connStat[i].name, connStat[x].name);
+        connStat[i].isLoggedIn = connStat[x].isLoggedIn;
+        foundActiveUser = true;
+        s2cConnNumb = x;
+        break;
+      }
+    }
+
+    if (s2cConnNumb == -1) {
+      Log("Enexpected state. Recieved %s on new connection without S2C "
+          "connection for messages.",
+          com2str(header.m_command));
+    } else if (!connStat[i].isLoggedIn) {
+      Log("[%s : %s] Failed: Connection %d is not logged in.", connStat[i].name,
+          com2str(header.m_command), i);
+      outHeader.setFlags(S2C, FAIL, PUB, SIGN);
+      outHeader.m_command = SEND; // avoid file transfer logic on client side
+      memcpy(outHeader.m_name, NAME, strlen(NAME));
+      strcat(message, "Users must be logged in to '");
+      strcat(message, com2str(header.m_command));
+      strcat(message, "'. Login with '");
+      strcat(message, com2str(LOGIN));
+      strcat(message, " [username] [password]'.");
+      outHeader.m_size = strlen(message);
+      sendMessageToId(connStat[i].id, false, outHeader, message);
+    } else {
+      // Save the file
+      nextFileId = getNumEntries(FILE_DATABASE);
+      if (nextFileId == -1) {
+        Log("Enexpected state. %s next File Id of -1.", FILE_DATABASE);
+      } else {
+        snprintf(server_filename, sizeof(server_filename), "%s/%d_", FILES_DIR,
+                 nextFileId);
+        offset = HEADER_LEN + 4;
+        buf2i(&buf[i][offset], filenameLen); // first 4 bytes is client id
+        offset += 4;
+        memcpy(&server_filename[strlen(server_filename)], &buf[i][offset],
+               filenameLen);
+        memcpy(filename, &buf[i][offset], filenameLen);
+        offset += filenameLen;
+        buf2i(&buf[i][offset], fileLen);
+        offset += 4;
+        // Log("[%s : %s] Saving file: %s", server_filename);
+        fp = fopen(FILE_DATABASE, "a");
+        fputs(server_filename, fp);
+        fputs("\n", fp);
+        fclose(fp);
+        buf2file(&buf[i][offset], fileLen, server_filename);
+
+        // Send SENDF/SENDF2 message to interested parties
+        outHeader.setFlags(S2C, SUCCESS, header.m_recipient,
+                           SIGN); // will update recipient as necessary
+        outHeader.m_command = header.m_command;
+        memcpy(outHeader.m_name, connStat[i].name, strlen(connStat[i].name));
+        i2buf(nextFileId, intbuf);
+        memcpy(message, intbuf, 4);
+        offset = 4;
+        memcpy(&message[offset], &buf[i][HEADER_LEN + 8], filenameLen);
+        offset += filenameLen;
+        outHeader.m_size = offset;
+
+        if (header.m_command == SENDF) {
+          Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command),
+              filename);
+          sendMessageToAllLoggedInExceptSender(connStat[i].id, outHeader,
+                                               message);
+        } else {
+          memcpy(username, header.m_name, MAX_USERNAME_LEN);
+          Log("[%s : %s %s] %s", connStat[i].name, com2str(header.m_command),
+              username, filename);
+          sendMessageToName(username, outHeader, message);
+        }
+      }
+    }
+    break;
+  case GETF:
+    buf2i(&buf[i][HEADER_LEN], connStat[i].id);
+    // header name is origional sender. Find client's name with client id
+    for (int x = 1; x <= nConns; x++) {
+      if ((connStat[i].id == connStat[x].id) &&
+          (connStat[x].direction == C2S) &&
+          (connStat[x].linkType == MESSAGE_LINK)) {
+        memcpy(connStat[i].name, connStat[x].name, MAX_USERNAME_LEN);
+        break;
+      }
+    }
+    buf2i(&buf[i][HEADER_LEN + 4], fileId);
+    connStat[i].direction = S2C;
+    connStat[i].linkType = FILE_LINK;
+    connStat[i].lastTime = currentTime;
+    // no need for shouldClose. Client will close connection and periodic ping will
+    // detect closure
+
+    snprintf(server_filename, sizeof(server_filename), "%s/%d_", FILES_DIR,
+             fileId);
+    memcpy(&server_filename[strlen(server_filename)], &buf[i][HEADER_LEN + 8],
+           header.m_size - 8);
+
+    memcpy(filename, &buf[i][HEADER_LEN + 8], header.m_size - 8);
+    filenameLen = strlen(filename);
+
+    // Log("Getting file %s", server_filename);
+    memset(buf[i], 0, BUF_LEN);
+
+    // Check that the file exists
+    fp = fopen(server_filename, "r");
+    if (fp) {
+      fclose(fp);
+      outHeader.setFlags(S2C, SUCCESS, header.m_recipient,
+                         SIGN); // will update recipient as necessary
+      if (header.m_command == SENDF2)
+        outHeader.m_recipient = PRIV;
+      outHeader.m_command = header.m_command;
+      memcpy(outHeader.m_name, header.m_name,
+             MAX_USERNAME_LEN); // pass along origional sender
+      offset = HEADER_LEN;
+      i2buf(filenameLen, &buf[i][offset]); // encode filename length
+      offset += 4;
+      memcpy(&buf[i][offset], filename,
+             strlen(filename)); // encode the filename
+      offset += strlen(filename);
+      fileLen = file2buf(server_filename, &buf[i][offset + 4]);
+      i2buf(fileLen, &buf[i][offset]);
+      offset += 4 + fileLen;
+      outHeader.m_size = offset - HEADER_LEN;
+      outHeader.encode(buf[i]);
+
+      connStat[i].nBuffered = offset;
+      connStat[i].messageLen = offset;
+      connStat[i].nToDo = offset;
+      peers[i].events &= ~POLLRDNORM;
+      peers[i].events |= POLLWRNORM;
+
+      Log("[%s : %s] %s", connStat[i].name, com2str(header.m_command),
+          filename);
+    } else {
+      Log("File not found: %s", filename);
+      // TODO: Send message if file is not found
+    }
+    break;
+  case LIST:
+    Log("[%s : %s]", connStat[i].name, com2str(header.m_command));
+    outHeader.setFlags(S2C, SUCCESS, PUB, SIGN);
+    outHeader.m_command = LIST;
+    memcpy(outHeader.m_name, NAME, strlen(NAME));
+    for (int x = 1; x <= nConns; x++) {
+      if ((connStat[x].direction == C2S) &&
+          (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].isLoggedIn)) {
+        numLoggedIn++;
+      }
+    }
+    snprintf(message, sizeof(message), "%d", numLoggedIn);
+    strcat(message, " user(s) currently logged in:");
+    for (int x = 1; x <= nConns; x++) {
+      if ((connStat[x].direction == C2S) &&
+          (connStat[x].linkType == MESSAGE_LINK) && (connStat[x].isLoggedIn)) {
+        strcat(message, "\n\t");
+        strcat(message, connStat[x].name);
+      }
+    }
+    outHeader.m_size = strlen(message);
+    sendMessageToId(connStat[i].id, false, outHeader, message);
+    break;
+  case PING:
+    // Log("Ping from %s", header.m_name);
+    asm("nop"); // do nothing
+    break;
+  default:
+    Log("No doServerCommand() for command %s", com2str(header.m_command));
+  }
+}
+
+// Send the message to every S2C with the specified id. If the reqLoggedIn
+// flag is set, require that the connection is logged in.
+void sendMessageToId(int id, bool reqLoggedIn, Header header, char *message) {
+  for (int i = 1; i <= nConns; i++) {
+    if ((connStat[i].direction == S2C) && (id == connStat[i].id)) {
+      if (reqLoggedIn) {
+        if (connStat[i].isLoggedIn) {
+          prepareMessage(i, header, message);
+        }
+      } else {
+        prepareMessage(i, header, message);
+      }
+    }
+  }
+}
+
+// Send the message to every S2C which is logged in.
+void sendMessageToAllLoggedIn(Header header, char *message) {
+  for (int i = 1; i <= nConns; i++) {
+    if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn) {
+      prepareMessage(i, header, message);
+    }
+  }
+}
+
+// Send the message to every s2cConnNumb
+void sendMessageToAllLoggedInExceptSender(int senderID, Header header,
+                                          char *message) {
+  for (int i = 1; i <= nConns; i++) {
+    if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn &&
+        (connStat[i].id != senderID)) {
+      prepareMessage(i, header, message);
+    }
+  }
+}
+
+// Send the message to all loged in users with the specified name.
+void sendMessageToName(char *name, Header header, char *message) {
+  for (int i = 1; i <= nConns; i++) {
+    if ((connStat[i].direction == S2C) && connStat[i].isLoggedIn &&
+        (strcmp(name, connStat[i].name) == 0)) {
+      prepareMessage(i, header, message);
+    }
+  }
+}
+
+// Load a message into a connection
+void prepareMessage(int i, Header header, char *message) {
+  if (connStat[i].direction == S2C) {
+    int offset = connStat[i].nBuffered;
+
+    // Log("Preparing message for connection %d", i);
+    // header.displayContents(true);
+    // Log("Message: |%s|", message);
+    // Log("offset: %d, nBuffered: %d, nToDo: %d: messageLen: %d", offset,
+    // connStat[i].nBuffered, connStat[i].nToDo, connStat[i].messageLen);
+
+    header.encode(&buf[i][offset]);
+    memcpy(&buf[i][offset + HEADER_LEN], message, header.m_size);
+
+    connStat[i].nBuffered += HEADER_LEN + header.m_size;
+    if (connStat[i].nToDo == 0) {
+      connStat[i].messageLen = HEADER_LEN + header.m_size;
+      connStat[i].nToDo = HEADER_LEN + header.m_size;
+    }
+
+    peers[i].events |= POLLWRNORM;
+  } else {
+    Log("[%s] WARNING: attempted to write to C2S connection %d", NAME,
+        i);
+  }
+}
+
+void printServerCommand(int i) {
+  Header h;
+  h.decode(buf[i]);
+
+  Log("[%s]: [%s, %s, %s, %s] [%s]  [%s]  [%d]", connStat[i].name,
+      !h.m_direction ? "C2S" : "S2C", !h.m_flag ? "SUCCESS" : "FAIL",
+      !h.m_recipient ? "PUB" : "PRIV", !h.m_trace ? "SIGN" : "ANNON",
+      com2str(h.m_command), h.m_name, h.m_size);
+
+  if (h.m_size > 0)
+    Log("\tData: [%s]", buf[i] + HEADER_LEN);
+}
+
+int main(int argc, char **argv) {
+  if (argc != 2) {
+    Log("Usage: %s reset", argv[0]);
+    Log("Usage: %s [server Port]", argv[0]);
+    return -1;
+  }
+
+  if (strcmp("reset", argv[1]) == 0) {
+    Log("Resetting server databases");
+    clearDatabase(USER_DATABASE);
+    clearDatabase(FILE_DATABASE);
+
+    int status;
+    pid_t cpid;
+
+    if ((cpid = fork()) == -1) {
+      perror("fork");
+      return 1;
+    }
+
+    if (cpid == 0) { // Child process executes "rm -rf users"
+      char command[40] = "rm -rf files"; // only way this worked
+      char *args[5] = {NULL};
+      args[0] = strtok(command, " ");
+      for (int i = 1; i < 3; i++)
+        args[i] = strtok(NULL, " ");
+      execvp(args[0], args);
+      return 0;
+    } else { // Parent process waits for "rm -rf users" to finish
+      pid_t rpid;
+      int status;
+      if ((rpid = wait(NULL)) == -1)
+        perror("wait");
+      else {
+        Log("Clearing file storage");
+        if (mkdir(FILES_DIR, S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
+          perror("mkdir");
+          return -1;
+        }
+      }
+      return 0;
+    }
+  }
+
+  // Make files directory if it doesn't exist
+  DIR *pDir;
+  struct stat s;
+
+  if (stat("files", &s) != 0) {
+    // The files directory doesn't exist
+    if (mkdir("files", S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
+      perror("mkdir");
+      return -1;
+    }
+  }
+
+  int port = atoi(argv[1]);
+  DoServer(port);
+
+  return 0;
+}
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..142fcbd
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,398 @@
+#include <string>
+#include <string.h>
+#include <cstddef>
+#include <stdio.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/timeb.h>
+
+#include "util.hpp"
+
+#define VALID_CHARACTERS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+enum Command parse_command(char* line){
+  /* Find a valid command in the first word of the line (case sensitive)
+  args:
+    line (char*): string of characters from the input file
+
+  returns:
+    (command): Returns the found command. Returns else if first word is invalid.
+  */
+  if (line == NULL){
+    return INVALID;
+  }
+
+  char* rest;
+  char* tok = strtok_r(line, " ", &rest);
+
+  if (tok == NULL){
+    return INVALID;
+  }
+
+  // REGISTER [username] [password]
+  if (strcmp(tok, "REGISTER") == 0){
+    char* username = strtok_r(rest, " ", &rest);
+    if(!isValidUsername(username)) return INVALID;
+
+    char* password = strtok_r(rest, " ", &rest);
+    if(password == NULL) return INVALID;
+    if(!isValidPassword(password)) return INVALID;
+
+    char* misc = strtok_r(rest, " ", &rest);
+    if(misc != NULL) return INVALID;
+
+    return REGISTER;
+  }
+
+  // LOGIN [username] [password]
+  if (strcmp(tok, "LOGIN") == 0){
+    char* username = strtok_r(rest, " ", &rest);
+    if(!isValidUsername(username)) return INVALID;
+
+    char* password = strtok_r(rest, " ", &rest);
+    if(password == NULL) return INVALID;
+    if(!isValidPassword(password)) return INVALID;
+
+    char* misc = strtok_r(rest, " ", &rest);
+    if(misc != NULL) return INVALID;
+
+    return LOGIN;
+  }
+
+  // LOGOUT
+  if (strcmp(tok, "LOGOUT") == 0){
+    char* misc = strtok_r(rest, " ", &rest);
+    if(misc != NULL) return INVALID;
+
+    return LOGOUT;
+  }
+
+  // SEND [msg]
+  if (strcmp(tok, "SEND") == 0){
+    if (int(strlen(rest) == 0)) return INVALID;
+
+    return SEND;
+  }
+
+  // SEND2 [username] [msg]
+  if (strcmp(tok, "SEND2") == 0){
+    char* username = strtok_r(rest, " ", &rest);
+    if(!isValidUsername(username)) return INVALID;
+
+    if (int(strlen(rest) == 0)) return INVALID;
+
+    return SEND2;
+  }
+
+  // SENDA [msg]
+  if (strcmp(tok, "SENDA") == 0){
+    if (int(strlen(rest) == 0)) return INVALID;
+
+    return SENDA;
+  }
+
+  // SENDA2 [username] [msg]
+  if (strcmp(tok, "SENDA2") == 0){
+    char* username = strtok_r(rest, " ", &rest);
+    if(!isValidUsername(username)) return INVALID;
+
+    if (int(strlen(rest) == 0)) return INVALID;
+
+    return SENDA2;
+  }
+
+  // SENDF [local file]
+  if (strcmp(tok, "SENDF") == 0){
+    char* file_name = strtok_r(rest, " ", &rest);
+    if(file_name == NULL) return INVALID;
+
+    if (int(strlen(rest) != 0)) return INVALID;
+
+    return SENDF;
+  }
+
+  // SENDF2 [username] [local file]
+  if (strcmp(tok, "SENDF2") == 0){
+    char* username = strtok_r(rest, " ", &rest);
+    if(!isValidUsername(username)) return INVALID;
+
+    char* file_name = strtok_r(rest, " ", &rest);
+    if(file_name == NULL) return INVALID;
+
+    if (int(strlen(rest) != 0)) return INVALID;
+
+    return SENDF2;
+  }
+
+  // LIST
+  if (strcmp(tok, "LIST") == 0){
+    char* misc = strtok_r(rest, " ", &rest);
+    if(misc != NULL) return INVALID;
+
+    return LIST;
+  }
+
+  // DELAY
+  if (strcmp(tok, "DELAY") == 0){
+    char* delay_time = strtok_r(rest, " ", &rest);
+    if(delay_time == NULL) return INVALID;
+
+    std::string str(delay_time);
+    std::size_t found = str.find_first_not_of("0123456789");
+    if(found != std::string::npos) return INVALID;
+
+    if (int(strlen(rest) != 0)) return INVALID;
+
+    return DELAY;
+  }
+
+  // default
+  return INVALID;
+}
+
+
+bool isValidUsername(char* username){
+  /* Helper function to check if a username string is valid
+  args:
+    username (char*): Username to be tested.
+
+  returns:
+    bool: Username is valid ? true, false
+  */
+  if(username == NULL) return false;
+  else if ((strlen(username) > MAX_USERNAME_LEN)||(strlen(username) < MIN_USERNAME_LEN)) return false;
+  else{
+    std::string str(username);
+    std::size_t found = str.find_first_not_of(VALID_CHARACTERS);
+    if(found != std::string::npos) return false;
+  }
+
+  return true;
+}
+
+
+bool isValidPassword(char* password){
+  if(password == NULL) return false;
+  else if ((strlen(password) > MAX_PASSWORD_LEN)||(strlen(password) < MIN_PASSWORD_LEN)) return false;
+  else{
+    std::string str(password);
+    std::size_t found = str.find_first_not_of(VALID_CHARACTERS);
+    if(found != std::string::npos) return false;
+  }
+
+  return true;
+}
+
+
+void Error(const char * format, ...) {
+	char msg[4096];
+	va_list argptr;
+	va_start(argptr, format);
+	vsprintf(msg, format, argptr);
+	va_end(argptr);
+	fprintf(stderr, "Error: %s\n", msg);
+	exit(-1);
+}
+
+void Log(const char * format, ...) {
+	char msg[2048];
+	va_list argptr;
+	va_start(argptr, format);
+	vsprintf(msg, format, argptr);
+	va_end(argptr);
+	fprintf(stderr, "%s\n", msg);
+}
+
+// convert buf[0:3] to int
+void buf2i(BYTE * buf, int &i){
+  // No way to avoid Segmentation fault. Some bytes may be 0
+  i = 0;
+  i += buf[3];
+  i += ((int)(buf[2])) << 8;
+  i += ((int)(buf[1])) << 16;
+  i += ((int)(buf[0])) << 24;
+}
+
+// convert int to buf[4] where buf[0] contains MSB's and buf[3] contains LSB's
+void i2buf(int &i, BYTE * buf){
+  // No way to avoid Segmentation fault. Some bytes may be 0
+  buf[0] = (BYTE)((i & 0xFF000000) >> 24);
+  buf[1] = (BYTE)((i & 0x00FF0000) >> 16);
+  buf[2] = (BYTE)((i & 0x0000FF00) >> 8);
+  buf[3] = (BYTE)(i & 0x000000FF);
+}
+
+const char * com2str(enum Command command){
+  const char * com2str[] = {"REGISTER",
+                            "LOGIN",
+                            "LOGOUT",
+                            "SEND",
+                            "SEND2",
+                            "SENDA",
+                            "SENDA2",
+                            "SENDF",
+                            "SENDF2",
+                            "LIST",
+                            "DELAY",
+                            "GETF",
+                            "PING",
+                            "CONNECT",
+                            "",
+                            "INVALID"
+                          };
+    return com2str[command];
+}
+
+// Computes the difference between times a and b in seconds
+// a - b = result
+double timeDifference(struct timeb a, struct timeb b){
+  return (a.time + a.millitm / (double) 1000.0f) - (b.time + b.millitm / (double) 1000.0f);
+}
+
+int getNumEntries(const char* database){
+  FILE* fp = fopen(database, "r");
+  if (!fp){
+    Log("Username check failed to open the file: %s", database);
+    return -1;
+  }
+
+  int numEntries = 0;
+
+  char line[LINE_SIZE] = {0};
+  while(fgets(line, LINE_SIZE, fp) != NULL){
+    char* new_line_pos = strchr(line, '\n');
+    if(new_line_pos != NULL)
+      *new_line_pos = 0;
+    // Including blank lines as entries. Don't use any blank lines.
+    numEntries++;
+    memset(line, 0, LINE_SIZE);
+  }
+
+  fclose(fp);
+  return numEntries;
+}
+
+// User/password is in registry: true
+// User/password is not in registry: false
+bool loginQuery(const char * database, char* username, char* password){
+  FILE* fp = fopen(database, "r");
+  if (!fp){
+    Log("Registry check failed to open the file: %s", database);
+    return false;
+  }
+
+  char line[LINE_SIZE] = {0};
+  while(fgets(line, LINE_SIZE, fp) != NULL){
+    char* new_line_pos = strchr(line, '\n');
+    if(new_line_pos != NULL)
+      *new_line_pos = 0;
+
+    char* rest;
+    char* usr = strtok_r(line, " ", &rest);
+    char* pswd = strtok_r(rest, " ", &rest);
+
+    //Log("username: |%s|   Password: |%s|", usr, pswd);
+
+    if((strcmp(username, usr) == 0) && (strcmp(password, pswd) == 0)){
+      fclose(fp);
+      return true;
+    }
+
+    memset(line, 0, LINE_SIZE);
+  }
+
+  fclose(fp);
+  return false;
+}
+
+// User is in registry: true
+// User is not in registry: false
+bool usernameQuery(const char * database, char* username){
+  FILE* fp = fopen(database, "r");
+  if (!fp){
+    Log("Username check failed to open the file: %s", database);
+    return false;
+  }
+
+  char line[LINE_SIZE] = {0};
+  while(fgets(line, LINE_SIZE, fp) != NULL){
+    char* new_line_pos = strchr(line, '\n');
+    if(new_line_pos != NULL)
+      *new_line_pos = 0;
+
+    char* rest;
+    char* usr = strtok_r(line, " ", &rest);
+
+    //Log("username: |%s|", usr);
+
+    if(strcmp(username, usr) == 0){
+      fclose(fp);
+      return true;
+    }
+
+    memset(line, 0, LINE_SIZE);
+  }
+
+  fclose(fp);
+  return false;
+}
+
+void recordEntry(const char * database, char* key, char* value){
+  FILE* fp = fopen(database, "a");
+
+  if (!fp){
+    Log("Registry append failed to open the file: %s", database);
+    return;
+  }
+
+  char line[LINE_SIZE] = {0};
+  strcat(line, key);
+  strcat(line, " ");
+  strcat(line, value);
+  strcat(line, "\n");
+
+  fputs(line, fp);
+  fclose(fp);
+}
+
+void clearDatabase(const char * database){
+  FILE* fp = fopen(database, "w");
+  fclose(fp);
+}
+
+
+int buf2file(BYTE* buf, int nbytes, char* filename){
+  FILE* f = fopen(filename, "w");
+  if(!f) return -1;
+
+  fwrite(buf, 1, nbytes, f);
+  fclose(f);
+  return 0;
+}
+
+
+int file2buf(char* filename, BYTE* buf){
+  FILE* f = fopen(filename, "r");
+  if(!f){
+    Log("Error: Cannot open %s", filename);
+    return -1;
+  }
+
+  fseek(f, 0, SEEK_END);  // jump to end of file
+  long nbytes = ftell(f);  // find current offset on file f
+  fseek(f, 0, SEEK_SET);  // jump to beginning of file
+
+  if(nbytes > MAX_REQUEST_SIZE){
+    Log("Error: File %s is too big: %d", filename, nbytes);
+    return -1;
+  }
+
+  int nread = fread(buf, 1, nbytes, f);
+  if(nread != nbytes){
+    Log("Error: Byte count mismatch with fread: %d, %d", nbytes, nread);
+    return -1;
+  }
+
+  fclose(f);
+  return nbytes;
+}
-- 
cgit v1.2.3