aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Strapp <matt@mattstrapp.net>2022-04-25 17:48:52 -0500
committerMatt Strapp <matt@mattstrapp.net>2022-04-25 17:49:31 -0500
commit6889e2d66b710c241b3884fc28610a9e6be4e610 (patch)
tree30aabe5e28a4306c41d7d73a248ed174bd36f0a5
downloadcsci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.gz
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.bz2
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.lz
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.xz
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.tar.zst
csci4211-6889e2d66b710c241b3884fc28610a9e6be4e610.zip
A
-rw-r--r--.gitignore92
-rw-r--r--Makefile33
-rw-r--r--README.md0
-rw-r--r--build/.keep0
-rw-r--r--include/client.hpp17
-rw-r--r--include/comms.hpp32
-rw-r--r--include/server.hpp27
-rw-r--r--include/util.hpp141
-rw-r--r--src/client.cpp591
-rw-r--r--src/comms.cpp169
-rw-r--r--src/server.cpp796
-rw-r--r--src/util.cpp398
-rw-r--r--tests/a.user1.txt4
-rw-r--r--tests/a.user2.txt4
-rw-r--r--tests/b.user1.txt4
-rw-r--r--tests/b.user2.txt6
-rw-r--r--tests/c.user1.txt4
-rw-r--r--tests/c.user2.txt6
-rw-r--r--tests/d.user1.txt4
-rw-r--r--tests/d.user2.txt4
-rw-r--r--tests/d.user3.txt12
21 files changed, 2344 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5a65a47
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,92 @@
+# ---> C
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# ---> C++
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+.dccache
+
+client
+server \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8820f85
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+
+.DELETE_ON_ERROR:
+
+CXX = g++
+SRCDIR := src
+INCLDIR = include
+BUILDDIR = build
+CFLAGS := -Wall -Wextra -g
+
+all: client server
+
+client: $(BUILDDIR)/client.o
+ $(CXX) $(CFLAGS) $(BUILDDIR)/client.o $(BUILDDIR)/util.o $(BUILDDIR)/comms.o -o client
+
+server: $(BUILDDIR)/server.o
+ $(CXX) $(CFLAGS) $(BUILDDIR)/server.o $(BUILDDIR)/util.o $(BUILDDIR)/comms.o -o server
+
+$(BUILDDIR)/client.o: $(SRCDIR)/client.cpp $(INCLDIR)/client.hpp $(BUILDDIR)/comms.o $(BUILDDIR)/util.o
+ $(CXX) $(CFLAGS) -I$(INCLDIR) $(SRCDIR)/client.cpp -c -o $(BUILDDIR)/client.o
+
+$(BUILDDIR)/server.o: $(SRCDIR)/server.cpp $(INCLDIR)/server.hpp $(BUILDDIR)/comms.o $(BUILDDIR)/util.o
+ $(CXX) $(CFLAGS) -I$(INCLDIR) $(SRCDIR)/server.cpp -c -o $(BUILDDIR)/server.o
+
+$(BUILDDIR)/comms.o: $(SRCDIR)/comms.cpp $(INCLDIR)/comms.hpp $(BUILDDIR)/util.o
+ $(CXX) $(CFLAGS) -I$(INCLDIR) $(SRCDIR)/comms.cpp -c -o $(BUILDDIR)/comms.o
+
+$(BUILDDIR)/util.o: $(SRCDIR)/util.cpp $(INCLDIR)/util.hpp
+ $(CXX) $(CFLAGS) -I$(INCLDIR) -c $(SRCDIR)/util.cpp -o $(BUILDDIR)/util.o
+
+clean:
+ -rm client server $(BUILDDIR)/*.o 2> /dev/null || true
+
+.PHONY: all clean
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README.md
diff --git a/build/.keep b/build/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/.keep
diff --git a/include/client.hpp b/include/client.hpp
new file mode 100644
index 0000000..dfc3e0c
--- /dev/null
+++ b/include/client.hpp
@@ -0,0 +1,17 @@
+#ifndef client_H
+#define client_H
+
+#include "comms.hpp"
+
+void RemoveConnection(int i);
+int Send_Blocking(int sockFD, const BYTE *data, int len);
+
+void DoClient(const char *svrIP, int svrPort, const char *fileName);
+
+void doClientCommand(int i);
+
+void prepareSend(int i, Header header);
+int processReception(int i);
+void printMessage(int i);
+
+#endif
diff --git a/include/comms.hpp b/include/comms.hpp
new file mode 100644
index 0000000..1b2a942
--- /dev/null
+++ b/include/comms.hpp
@@ -0,0 +1,32 @@
+#ifndef COMM_UTIL_H
+#define COMM_UTIL_H
+#include "util.hpp"
+
+struct CONN_STAT {
+ int id; // Unique id to identify a client instance
+ bool isLoggedIn; // Used by server to identify that this connection is logged
+ // in
+ struct timeb lastTime; // Last time this connection was used (used to detect
+ // s2c closure)
+ bool recInitHeader; // False until initial header is recieved
+ bool expectingHeader; // The message to be read is a header (irrelevent for
+ // senders)
+ enum LinkType linkType; // MESSAGE_LINK (persistent) or FILE_LINK (one off)
+ enum Direction direction; // C2S or S2C
+ char name[9]; // The user this connection is with
+ int nBuffered; // number of bytes in the buffer
+ int messageLen; // size of the current message being sent (includes header)
+ int nToDo; // num bytes to do before current messsage is completed (message
+ // len --> 0)
+ bool changeDirection; // Flag to change the direction of the connection once
+ // the current communication finishes
+ bool shouldClose; // Flag to destroy this connection
+};
+
+int Send_NonBlocking(int sockFD, BYTE *data, struct CONN_STAT *pStat,
+ struct pollfd *pPeer);
+int Recv_NonBlocking(int sockFD, BYTE *data, struct CONN_STAT *pStat,
+ struct pollfd *pPeer);
+void SetNonBlockIO(int fd);
+
+#endif
diff --git a/include/server.hpp b/include/server.hpp
new file mode 100644
index 0000000..ab77ad8
--- /dev/null
+++ b/include/server.hpp
@@ -0,0 +1,27 @@
+#ifndef server_H
+#define server_H
+
+// Comms includes util.hpp
+#include "comms.hpp"
+
+void SetNonBlockIO(int fd);
+void RemoveConnection(int i);
+
+int Send_NonBlocking(int sockFD, BYTE *data, struct CONN_STAT *pStat,
+ struct pollfd *pPeer);
+int Recv_NonBlocking(int sockFD, BYTE *data, struct CONN_STAT *pStat,
+ struct pollfd *pPeer);
+
+int processReception(int i);
+void doServerCommand(int i);
+void sendMessageToId(int id, bool reqLoggedIn, Header header, char *message);
+void sendMessageToAllLoggedIn(Header header, char *message);
+void sendMessageToAllLoggedInExceptSender(int senderID, Header header,
+ char *message);
+void sendMessageToName(char *name, Header header, char *message);
+void prepareMessage(int i, Header header, char *message);
+void printServerCommand(int i);
+
+void DoServer(int svrPort);
+
+#endif
diff --git a/include/util.hpp b/include/util.hpp
new file mode 100644
index 0000000..10887df
--- /dev/null
+++ b/include/util.hpp
@@ -0,0 +1,141 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+// Get your libraries here
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/timeb.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#define NAME "SERVER"
+#define ANON "Anonymous"
+#define LINE_SIZE 5000 // overkill
+
+#define BUF_LEN 12000000 // overkill
+#define MAX_REQUEST_SIZE 10000000
+#define MIN_USERNAME_LEN 4
+#define MAX_USERNAME_LEN 8
+#define MIN_PASSWORD_LEN 4
+#define MAX_PASSWORD_LEN 8
+
+enum Command {
+ REGISTER = 0, // [username] [password] Register a new account
+ LOGIN = 1, // [username] [password] Log in with an existing account and enter
+ // the chat room
+ LOGOUT = 2, // Log out and leave the chat room
+ SEND = 3, // [msg] Send a public message
+ SEND2 = 4, // [username] [msg] Send a private message to a user
+ SENDA = 5, // [msg] Send an anonymous public message
+ SENDA2 = 6, // [username] [msg] Send an anonymous private message to a user
+ SENDF = 7, // [local file] Send a file publicly
+ SENDF2 = 8, // [username] [local file] Send a file to a user privately
+ LIST = 9, // List all online users
+ DELAY = 10, // [N] A special command that delays for N seconds before
+ // executing the next command in the script
+
+ GETF = 11, // [filename] Server telling clinet to request a file, clinet
+ // requesting a file
+ PING = 12, // Check that connection is still there (nop)
+ CONNECT = 13, // [unique id] Allows the server to identify specific users when
+ // they are logged in multiple times
+
+ INVALID = 15 // Used when no valid command is found
+};
+
+enum LinkType { MESSAGE_LINK, FILE_LINK };
+
+typedef unsigned char BYTE;
+typedef unsigned int DWORD;
+typedef unsigned short WORD;
+
+enum Command parse_command(char *line);
+bool isValidUsername(char *username);
+bool isValidPassword(char *password);
+
+void Error(const char *format, ...);
+void Log(const char *format, ...);
+
+void buf2i(BYTE *buf, int &i);
+void i2buf(int &i, BYTE *buf);
+
+const char *com2str(enum Command command);
+
+double timeDifference(struct timeb a, struct timeb b);
+
+int getNumEntries(const char *databse);
+bool loginQuery(const char *database, char *username, char *password);
+bool usernameQuery(const char *database, char *username);
+
+void recordEntry(const char *database, char *key, char *value);
+void clearDatabase(const char *database);
+
+int buf2file(BYTE *buf, int nbytes, char *filename);
+int file2buf(char *filename, BYTE *buf);
+
+#define HEADER_LEN 13 // number of bytes in a message header
+
+enum Direction { C2S, S2C }; // client to server, server to client
+enum Flag { SUCCESS, FAIL }; // success, failure
+enum Recipient { PUB, PRIV }; // send to all, send to one
+enum Trace { SIGN, ANNON }; // send publically, send anonymously
+
+class Header {
+public:
+ enum Direction m_direction;
+ enum Flag m_flag;
+ enum Recipient m_recipient;
+ enum Trace m_trace;
+ enum Command m_command;
+
+ BYTE m_name[9];
+
+ int m_size;
+
+ Header(); // constructor
+
+ void encode(BYTE *buf); // encode message to buffer
+ void decode(BYTE *buf); // decode buffer to message
+
+ void setFlags(enum Direction direction, enum Flag flag,
+ enum Recipient recipient, enum Trace trace);
+
+ void displayContents(bool tab); // helper to print contents of class
+};
+
+inline bool operator==(const Header &lfs, const Header &rhs) {
+ bool result = true;
+
+ result &= lfs.m_direction == rhs.m_direction;
+ result &= lfs.m_flag == rhs.m_flag;
+ result &= lfs.m_recipient == rhs.m_recipient;
+ result &= lfs.m_trace == rhs.m_trace;
+ result &= lfs.m_command == rhs.m_command;
+
+ for (int i = 0; i < MAX_USERNAME_LEN; i++)
+ result &= lfs.m_name[i] == rhs.m_name[i];
+
+ result &= lfs.m_size == rhs.m_size;
+
+ return result;
+}
+
+inline bool operator!=(const Header &lhs, const Header &rhs) {
+ return !(lhs == rhs);
+}
+
+#endif
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;
+}
diff --git a/tests/a.user1.txt b/tests/a.user1.txt
new file mode 100644
index 0000000..20f6ae8
--- /dev/null
+++ b/tests/a.user1.txt
@@ -0,0 +1,4 @@
+REGISTER mario foobar00
+DELAY 1
+LOGIN mario foobar00
+DELAY 9999
diff --git a/tests/a.user2.txt b/tests/a.user2.txt
new file mode 100644
index 0000000..6dda110
--- /dev/null
+++ b/tests/a.user2.txt
@@ -0,0 +1,4 @@
+REGISTER luigi foobar00
+DELAY 1
+LOGIN luigi foobar00
+DELAY 9999
diff --git a/tests/b.user1.txt b/tests/b.user1.txt
new file mode 100644
index 0000000..20f6ae8
--- /dev/null
+++ b/tests/b.user1.txt
@@ -0,0 +1,4 @@
+REGISTER mario foobar00
+DELAY 1
+LOGIN mario foobar00
+DELAY 9999
diff --git a/tests/b.user2.txt b/tests/b.user2.txt
new file mode 100644
index 0000000..45203e6
--- /dev/null
+++ b/tests/b.user2.txt
@@ -0,0 +1,6 @@
+REGISTER luigi foobar00
+DELAY 1
+LOGIN luigi foobar00
+DELAY 1
+SEND HELLOWORLD
+DELAY 9999
diff --git a/tests/c.user1.txt b/tests/c.user1.txt
new file mode 100644
index 0000000..20f6ae8
--- /dev/null
+++ b/tests/c.user1.txt
@@ -0,0 +1,4 @@
+REGISTER mario foobar00
+DELAY 1
+LOGIN mario foobar00
+DELAY 9999
diff --git a/tests/c.user2.txt b/tests/c.user2.txt
new file mode 100644
index 0000000..704b379
--- /dev/null
+++ b/tests/c.user2.txt
@@ -0,0 +1,6 @@
+REGISTER luigi foobar00
+DELAY 1
+LOGIN luigi foobar00
+DELAY 1
+SENDF2 mario 1kb.user2
+DELAY 9999
diff --git a/tests/d.user1.txt b/tests/d.user1.txt
new file mode 100644
index 0000000..20f6ae8
--- /dev/null
+++ b/tests/d.user1.txt
@@ -0,0 +1,4 @@
+REGISTER mario foobar00
+DELAY 1
+LOGIN mario foobar00
+DELAY 9999
diff --git a/tests/d.user2.txt b/tests/d.user2.txt
new file mode 100644
index 0000000..6dda110
--- /dev/null
+++ b/tests/d.user2.txt
@@ -0,0 +1,4 @@
+REGISTER luigi foobar00
+DELAY 1
+LOGIN luigi foobar00
+DELAY 9999
diff --git a/tests/d.user3.txt b/tests/d.user3.txt
new file mode 100644
index 0000000..bf28b7b
--- /dev/null
+++ b/tests/d.user3.txt
@@ -0,0 +1,12 @@
+REGISTER wario foobar00
+DELAY 1
+LOGIN wario foobar00
+DELAY 1
+SENDF 1mb.1.user3
+SENDF 1mb.2.user3
+SENDF 1mb.3.user3
+SENDF 1mb.4.user3
+SENDF 1mb.5.user3
+SENDF 1mb.6.user3
+SENDF 1mb.7.user3
+DELAY 9999