From 6889e2d66b710c241b3884fc28610a9e6be4e610 Mon Sep 17 00:00:00 2001 From: Matt Strapp Date: Mon, 25 Apr 2022 17:48:52 -0500 Subject: A --- .gitignore | 92 +++++++ Makefile | 33 +++ README.md | 0 build/.keep | 0 include/client.hpp | 17 ++ include/comms.hpp | 32 +++ include/server.hpp | 27 ++ include/util.hpp | 141 ++++++++++ src/client.cpp | 591 +++++++++++++++++++++++++++++++++++++++ src/comms.cpp | 169 ++++++++++++ src/server.cpp | 796 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.cpp | 398 +++++++++++++++++++++++++++ tests/a.user1.txt | 4 + tests/a.user2.txt | 4 + tests/b.user1.txt | 4 + tests/b.user2.txt | 6 + tests/c.user1.txt | 4 + tests/c.user2.txt | 6 + tests/d.user1.txt | 4 + tests/d.user2.txt | 4 + tests/d.user3.txt | 12 + 21 files changed, 2344 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 build/.keep create mode 100644 include/client.hpp create mode 100644 include/comms.hpp create mode 100644 include/server.hpp create mode 100644 include/util.hpp create mode 100644 src/client.cpp create mode 100644 src/comms.cpp create mode 100644 src/server.cpp create mode 100644 src/util.cpp create mode 100644 tests/a.user1.txt create mode 100644 tests/a.user2.txt create mode 100644 tests/b.user1.txt create mode 100644 tests/b.user2.txt create mode 100644 tests/c.user1.txt create mode 100644 tests/c.user2.txt create mode 100644 tests/d.user1.txt create mode 100644 tests/d.user2.txt create mode 100644 tests/d.user3.txt 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 diff --git a/build/.keep b/build/.keep new file mode 100644 index 0000000..e69de29 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(¤tTime); + 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(¤tTime); + + 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: " + // 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(¤tTime); + + // 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(¤tTime); + + 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 +#include +#include +#include +#include +#include +#include + +#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 -- cgit v1.2.3