From 32e2a09b8d421f2ff88cdbc188f60a7ea68bb5df Mon Sep 17 00:00:00 2001 From: slapelachie Date: Fri, 21 Mar 2025 10:59:34 +1000 Subject: [PATCH] init --- .clang-format | 18 ++ Makefile | 27 +++ dbclient.c | 151 +++++++++++++++++ dbclient.h | 81 +++++++++ dbserver.c | 443 ++++++++++++++++++++++++++++++++++++++++++++++++++ dbserver.h | 270 ++++++++++++++++++++++++++++++ stringstore.c | 72 ++++++++ 7 files changed, 1062 insertions(+) create mode 100644 .clang-format create mode 100644 Makefile create mode 100644 dbclient.c create mode 100644 dbclient.h create mode 100644 dbserver.c create mode 100644 dbserver.h create mode 100644 stringstore.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..cb6e9eb --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +--- +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 79 +TabWidth: 8 +ContinuationIndentWidth: 8 +UseTab: Never + +PointerAlignment: Right +DerivePointerAlignment: false +AlignAfterOpenBracket: DontAlign +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +IncludeBlocks: Regroup +BreakBeforeBraces: Attach +--- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ef3ba16 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CC=gcc +CFLAGS=-std=gnu99 -Wall -pedantic -I/local/courses/csse2310/include +LIBCFLAGS=-fPIC $(CFLAGS) +LDFLAGS=-L/local/courses/csse2310/lib -lcsse2310a4 + +.PHONY: all debug profile clean +.DEFAULT_GOAL := all + +all: dbclient dbserver libstringstore.so + +debug: CFLAGS += -g +debug: all + +profile: CFLAGS += -pg -fprofile-arcs -ftest-coverage +profile: all + +dbserver: dbserver.c dbserver.h + $(CC) $(CFLAGS) -pthread $(LDFLAGS) -lcsse2310a3 -lstringstore -o $@ $^ + +stringstore.o: stringstore.c /local/courses/csse2310/include/stringstore.h + $(CC) $(LIBCFLAGS) -c $< + +libstringstore.so: stringstore.o + $(CC) -shared -o $@ stringstore.o + +clean: + rm -f dbclient dbserver libstringstore.so *.o diff --git a/dbclient.c b/dbclient.c new file mode 100644 index 0000000..0a72406 --- /dev/null +++ b/dbclient.c @@ -0,0 +1,151 @@ +#include "dbclient.h" + +char *construct_header(char *key, char *value) { + char *header = NULL; + + // If there is no value, make a get request, otherwise make a put request + // with that value + if (!value) { + size_t needed_size = snprintf(NULL, 0, GET_REQUEST, key); + header = (char *)malloc(needed_size + sizeof(char)); + sprintf(header, GET_REQUEST, key); + } else { + size_t body_size = strlen(value) * sizeof(char); + size_t needed_size = + snprintf(NULL, 0, PUT_REQUEST, key, body_size, value); + header = (char *)malloc(needed_size + sizeof(char)); + sprintf(header, PUT_REQUEST, key, body_size, value); + } + + return header; +} + +int handle_connect(char *port, int *fd) { + struct addrinfo *ai = NULL; + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + + // Set hints + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + // Get address information + int err; + if ((err = getaddrinfo("localhost", port, &hints, &ai))) { + freeaddrinfo(ai); + fprintf(stderr, "%s\n", gai_strerror(err)); + return EXIT_ERR; + } + + // Connect to port + *fd = socket(AF_INET, SOCK_STREAM, 0); + // clang-format off + if (connect(*fd, (struct sockaddr *)ai->ai_addr, + sizeof(struct sockaddr))) { + // clang-format on + freeaddrinfo(ai); + close(*fd); + + fprintf(stderr, "dbclient: unable to connect to port %s\n", port); + return EXIT_PORT_ERR; + } + freeaddrinfo(ai); + + return EXIT_OK; +} + +int handle_response(FILE *recv, int valueSet) { + HttpHeader **responseHeaders = NULL; + memset(&responseHeaders, 0, sizeof(HttpHeader *)); + + int status; + char *statusExplain = NULL; + char *body = NULL; + get_HTTP_response(recv, &status, &statusExplain, &responseHeaders, &body); + + // Print the value only if GET and got status 200 + if (!valueSet && status == 200) { + printf("%s\n", body); + } + + free_array_of_headers(responseHeaders); + free(statusExplain); + free(body); + + if (status != 200 && valueSet) { + return EXIT_PUT_ERR; + } else if (status != 200) { + return EXIT_GET_ERR; + } + + return EXIT_OK; +} + +int handle_connection(char *port, char *key, char *value) { + int connectStatus = 0; + int sendFd; + if ((connectStatus = handle_connect(port, &sendFd))) { + close(sendFd); + return connectStatus; + } + + // Setup fd's for sending and receiving requests + int recvFd = dup(sendFd); + FILE *send = fdopen(sendFd, "w"); + FILE *recv = fdopen(recvFd, "r"); + + // Send Request + char *header = construct_header(key, value); + fprintf(send, "%s", header); + fflush(send); + fclose(send); + free(header); + close(sendFd); + + // Get Response + int valueSet = (value != NULL); + int exitCode = handle_response(recv, valueSet); + fclose(recv); + close(recvFd); + + return exitCode; +} + +int is_valid_key(char *key) { + for (int i = 0; i < strlen(key); i++) { + if (isspace(key[i])) { + return 0; + } + } + + return 1; +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "Usage: dbclient portnum key [value]\n"); + return EXIT_ERR; + } + + char *portnumArgument = strdup(argv[1]); + char *keyArgument = strdup(argv[2]); + char *valueArgument = NULL; + + if (argc > 3) { + valueArgument = strdup(argv[3]); + } + + if (!is_valid_key(keyArgument)) { + fprintf(stderr, "dbclient: key must not contain spaces or newlines\n"); + return EXIT_ERR; + } + + int exitStatus = + handle_connection(portnumArgument, keyArgument, valueArgument); + + free(portnumArgument); + free(keyArgument); + free(valueArgument); + + return exitStatus; +} diff --git a/dbclient.h b/dbclient.h new file mode 100644 index 0000000..e8979f4 --- /dev/null +++ b/dbclient.h @@ -0,0 +1,81 @@ +#ifndef DBCLIENT_H +#define DBCLIENT_H +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GET_REQUEST "GET /public/%s HTTP/1.1\r\n\r\n" +#define PUT_REQUEST "PUT /public/%s HTTP/1.1\r\nContent-Length: %lu\r\n\r\n%s" + +// Program exit codes +enum ExitCodes { + EXIT_OK = 0, + EXIT_ERR = 1, + EXIT_PORT_ERR = 2, + EXIT_GET_ERR = 3, + EXIT_PUT_ERR = 4 +}; + +/* construct_header() + * ------------------ + * Constructs the needed header to be sent + * + * key: the to check or update + * value: the value to assign to the key + * + * Returns: the header + */ +char *construct_header(char *key, char *value); + +/* handle_connect() + * ---------------- + * Handles the initial connection to the port given, updates the given + * file descriptors pointer to the created one + * + * port: the port number or service to connect to + * fd: the file descriptor that will be connected for the connection + * + * Returns: 1 if the address info couldnt be found, 2 if could not connect to + * port, 0 if successful + */ +int handle_connect(char *port, int *fd); + +/* handle_response() + * ----------------- + * Handles the response given from the connected server + * + * recv: the fd that will be used for reading + * valueSet: if the value argument was set + * + * Returns: 3 if GET failed, 4 if PUT failed, 0 if successful + */ +int handle_response(FILE *recv, int valueSet); + +/* handle_connection() + * ------------------- + * Handles the connection to the given port, sends a GET or PUT request + * depending on if the value argument is not NULL + * + * port: the port to connect to + * key: the key to retrieve or update + * value: the value to update the supplied key to + */ +int handle_connection(char *port, char *key, char *value); + +/* is_valid_key() + * -------------- + * Checks if the given key is valid according to the spec + * + * key: the key to check if valid + * + * Returns: 0 if not valid, 1 if valid + */ +int is_valid_key(char *key); + +#endif diff --git a/dbserver.c b/dbserver.c new file mode 100644 index 0000000..291c6f1 --- /dev/null +++ b/dbserver.c @@ -0,0 +1,443 @@ +#include "dbserver.h" + +int get_nth_char_position(const char *s, int c, int n) { + int charCount = 0; + + for (int i = 0; i < strlen(s); i++) { + if (s[i] == c && (++charCount) == n) { + return i; + } + } + return -1; +} + +int get_valid_number(char *rawNumber) { + if (strlen(rawNumber) == 0) { + return -1; + } + + for (int i = 0; i < strlen(rawNumber); i++) { + if (!isdigit(rawNumber[i])) { + return -1; + } + } + + return atoi(rawNumber); +} + +char *get_authkey_from_file(char *filePath) { + FILE *fin = NULL; + if ((fin = fopen(filePath, "r")) == NULL) { + return NULL; + } + + char *line = NULL; + if ((line = read_line(fin)) == NULL) { + return NULL; + } + + fclose(fin); + return line; +} + +struct addrinfo *get_address_info(int port) { + struct addrinfo *ai = 0; + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + // Convert port to string for use in bind + char portnum[6]; + sprintf(portnum, "%d", port); + + // Get the address info + int err; + if ((err = getaddrinfo(NULL, portnum, &hints, &ai))) { + freeaddrinfo(ai); + return NULL; + } + + return ai; +} + +int open_socket(int port) { + struct addrinfo *ai = 0; + if ((ai = get_address_info(port)) == NULL) { + return -1; + } + + // Bind socket to port + int recvFd = socket(AF_INET, SOCK_STREAM, 0); + + // Reuse ports immediately + int optionValue = 1; + // clang-format off + if (setsockopt(recvFd, SOL_SOCKET, SO_REUSEADDR, &optionValue, + sizeof(int)) < 0) { + // clang-format on + freeaddrinfo(ai); + return -1; + } + + // Bind fd to socket + if (bind(recvFd, (struct sockaddr *)ai->ai_addr, sizeof(struct sockaddr)) < + 0) { + freeaddrinfo(ai); + return -1; + } + freeaddrinfo(ai); + + // Backlogged if not being used right away + if (listen(recvFd, MAX_BACKLOG) < 0) { + return -1; + } + + // Get port number and print to stdout + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + if (getsockname(recvFd, (struct sockaddr *)&sin, &len) == -1) { + return -1; + } else { + fprintf(stderr, "%d\n", ntohs(sin.sin_port)); + } + + return recvFd; +} + +void serverstats_init() { + serverStats = + (struct ServerStatistics *)malloc(sizeof(struct ServerStatistics)); + + serverStats->currentConnections = 0; + serverStats->completedConnections = 0; + serverStats->authFails = 0; + serverStats->gets = 0; + serverStats->puts = 0; + serverStats->deletes = 0; + serverStats->connectionLock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; +} + +int handle_listen(int port, int connections, char *authKey) { + // Create a public (0) and private (1) stringstore + StringStore *stringStores[2] = {NULL, NULL}; + stringStores[0] = stringstore_init(); + stringStores[1] = stringstore_init(); + + struct sockaddr_in fromAddress = {0}; + socklen_t fromAddressSize = sizeof(fromAddress); + + int recvFd; + if ((recvFd = open_socket(port)) < 0) { + return EXIT_PORT_ERR; + } + + while (1) { + int fd = accept( + recvFd, (struct sockaddr *)&fromAddress, &fromAddressSize); + if (fd < 0) { + return 1; + } + + char hostname[NI_MAXHOST]; + int err; + // clang-format off + if ((err = getnameinfo((struct sockaddr *)&fromAddress, + fromAddressSize, hostname, NI_MAXHOST, NULL, 0, 0))) { + // clang-format on + return 1; + } + + struct ConnectionInfo ci; + memset(&ci, 0, sizeof(struct ConnectionInfo)); + ci.fd = fd; + ci.connections = connections; + ci.authKey = authKey; + ci.stringStores = stringStores; + + struct ConnectionInfo *ciPtr = malloc(sizeof(struct ConnectionInfo)); + *ciPtr = ci; + + pthread_t threadId; + pthread_create(&threadId, NULL, handle_connection, ciPtr); + pthread_detach(threadId); + } + + free(stringStores[0]); + free(stringStores[1]); + close(recvFd); + return 0; +} + +char *process_response_get(char *key, StringStore *stringStore) { + char *response = NULL; + char *value = NULL; + if ((value = (char *)stringstore_retrieve(stringStore, key)) == NULL) { + response = construct_HTTP_response(404, "Not Found", NULL, NULL); + } else { + response = construct_HTTP_response(200, "OK", NULL, value); + + pthread_mutex_lock(&(serverStats->connectionLock)); + serverStats->gets += 1; + pthread_mutex_unlock(&(serverStats->connectionLock)); + } + + return response; +} + +char *process_response_put(char *key, char *value, StringStore *stringStore) { + char *response = NULL; + if (stringstore_add(stringStore, key, value) == 0) { + response = construct_HTTP_response( + 500, "Internal Server Error", NULL, NULL); + } else { + response = construct_HTTP_response(200, "OK", NULL, NULL); + + pthread_mutex_lock(&(serverStats->connectionLock)); + serverStats->puts += 1; + pthread_mutex_unlock(&(serverStats->connectionLock)); + } + + return response; +} + +char *process_response_delete(char *key, StringStore *stringStore) { + char *response = NULL; + if (stringstore_delete(stringStore, key) == 0) { + response = construct_HTTP_response(404, "Not Found", NULL, NULL); + } else { + response = construct_HTTP_response(200, "OK", NULL, NULL); + + pthread_mutex_lock(&(serverStats->connectionLock)); + serverStats->deletes += 1; + pthread_mutex_unlock(&(serverStats->connectionLock)); + } + + return response; +} + +int check_auth_key(HttpHeader **headers, char *authKey) { + for (int i = 0; headers[i] != NULL; i++) { + if (strcmp(headers[i]->name, "Authorization") == 0 && + strcmp(headers[i]->value, authKey) == 0) { + return 1; + } + } + return 0; +} + +char *construct_response( + struct RequestParams req, StringStore **stringStores) { + char *response = NULL; + + // Default to public string store + StringStore *stringStore = stringStores[0]; + + if (req.path == NULL || req.key == NULL) { + return NULL; + } + + // If the client is trying to access a private path + if (strcmp(req.path, PRIVATE_PATH) == 0) { + if (!check_auth_key(req.headers, req.authKey)) { + // Client failed to auth + pthread_mutex_lock(&(serverStats->connectionLock)); + serverStats->authFails += 1; + pthread_mutex_unlock(&(serverStats->connectionLock)); + + return construct_HTTP_response(401, "Unauthorized", NULL, NULL); + } else { + stringStore = stringStores[1]; + } + } else if (strcmp(req.path, PUBLIC_PATH) != 0) { + return NULL; + } + + if (strcmp(req.method, "GET") == 0) { + response = process_response_get(req.key, stringStore); + } else if (strcmp(req.method, "PUT") == 0) { + response = process_response_put(req.key, req.value, stringStore); + } else if (strcmp(req.method, "DELETE") == 0) { + response = process_response_delete(req.key, stringStore); + } + return response; +} + +int get_path_key(char *address, char **path, char **key) { + int keyStartPos = get_nth_char_position(address, '/', 2) + 1; + if (keyStartPos <= 0) { + return 1; + } + + *path = (char *)malloc(sizeof(char) * (keyStartPos + 1)); + strncpy(*path, address, keyStartPos); + + *key = (char *)malloc( + sizeof(char) * ((strlen(address) - keyStartPos) + 1)); + strcpy(*key, &address[keyStartPos]); + + return 0; +} + +struct RequestParams request_params_init() { + struct RequestParams requestParams = {0}; + memset(&requestParams, 0, sizeof(struct RequestParams)); + requestParams.method = NULL; + requestParams.path = NULL; + requestParams.key = NULL; + requestParams.value = NULL; + requestParams.authKey = NULL; + + requestParams.headers = NULL; + memset(&(requestParams.headers), 0, sizeof(HttpHeader *)); + + return requestParams; +} + +int handle_connection_request( + FILE *recv, FILE *send, char *authKey, StringStore **stringStores) { + struct RequestParams req = request_params_init(); + + char *address = NULL; + // clang-format off + if (get_HTTP_request(recv, &(req.method), &address, &(req.headers), + &(req.value)) == 1) { + // clang-format on + get_path_key(address, &(req.path), &(req.key)); + + req.authKey = authKey; + + char *response = NULL; + if ((response = construct_response(req, stringStores)) == NULL) { + response = construct_HTTP_response(400, "Bad Request", NULL, NULL); + } + fprintf(send, response); + fflush(send); + + // Will only need to free in this branch + free(req.method); + free(address); + free(req.value); + + free(response); + free(req.path); + free(req.key); + free_array_of_headers(req.headers); + + return 0; + } + + return 1; +} + +void handle_client_connection( + int sendFd, char *authKey, StringStore **stringStores) { + int recvFd = dup(sendFd); + FILE *send = fdopen(sendFd, "w"); + FILE *recv = fdopen(recvFd, "r"); + + // Loop until EOF or invalid response + int err = 0; + // clang-format off + while ((err = handle_connection_request( + recv, send, authKey, stringStores)) == 0) { + // clang-format on + ; + } + fclose(recv); + fclose(send); + + close(recvFd); +} + +void *handle_connection(void *ciPtr) { + struct ConnectionInfo ci = *(struct ConnectionInfo *)ciPtr; + free(ciPtr); + + // Lock connection count variable while updating + pthread_mutex_lock(&(serverStats->connectionLock)); + serverStats->currentConnections += 1; + pthread_mutex_unlock(&(serverStats->connectionLock)); + + int illegalConnection = 0; + if (serverStats->currentConnections <= ci.connections || + ci.connections == 0) { + // if within the max connections + handle_client_connection(ci.fd, ci.authKey, ci.stringStores); + } else { + // send them away + write(ci.fd, UNAVALIABLE_RESPONSE, strlen(UNAVALIABLE_RESPONSE)); + illegalConnection = 1; + } + close(ci.fd); + + pthread_mutex_lock(&(serverStats->connectionLock)); + serverStats->currentConnections--; + if (!illegalConnection) { + serverStats->completedConnections++; + } + pthread_mutex_unlock(&(serverStats->connectionLock)); + return NULL; +} + +void print_server_stats(int s) { + fprintf(stderr, + "Connected clients:%d\nCompleted clients:%d\nAuth " + "failures:%d\nGET " + "operations:%d\nPUT operations:%d\nDELETE operations:%d\n", + serverStats->currentConnections, serverStats->completedConnections, + serverStats->authFails, serverStats->gets, serverStats->puts, + serverStats->deletes); +} + +void setup_signal_handler(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = print_server_stats; + sa.sa_flags = SA_RESTART; + + sigaction(SIGHUP, &sa, 0); +} + +int main(int argc, char *argv[]) { + serverstats_init(); + setup_signal_handler(); + + if (argc < 3 || argc > 4) { + fprintf(stderr, PROGRAM_USAGE); + exit(EXIT_ERR); + } + + int connections = 0; + if ((connections = get_valid_number(argv[2])) < 0) { + fprintf(stderr, PROGRAM_USAGE); + exit(EXIT_ERR); + } + + int portnum = 0; + if (argv[3] && strlen(argv[3]) > 0) { + portnum = get_valid_number(argv[3]); + if (portnum != 0 && (portnum < PORT_MIN || portnum > PORT_MAX)) { + fprintf(stderr, PROGRAM_USAGE); + exit(EXIT_ERR); + } + } + + char *authKey = get_authkey_from_file(argv[1]); + if (authKey == NULL) { + fprintf(stderr, "dbserver: unable to read authentication string\n"); + exit(EXIT_FILE_ERR); + } + + int err; + if ((err = handle_listen(portnum, connections, authKey)) > 0) { + fprintf(stderr, "dbserver: unable to open socket for listening\n"); + free(authKey); + exit(err); + } + + free(authKey); + free(serverStats); +} diff --git a/dbserver.h b/dbserver.h new file mode 100644 index 0000000..d3543c8 --- /dev/null +++ b/dbserver.h @@ -0,0 +1,270 @@ +#ifndef DBSERVER_H +#define DBSERVER_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT_MIN 1024 +#define PORT_MAX 65535 + +#define MAX_BACKLOG 10 + +#define UNAVALIABLE_RESPONSE \ + "HTTP/1.1 503 Service Unavaliable\r\nContent-Length: 0\r\n\r\n" +#define OK_RESPONSE "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" +#define ISR_RESPONSE \ + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n" + +#define PROGRAM_USAGE "Usage: dbserver authfile connections [portnum]\n" + +#define PUBLIC_PATH "/public/" +#define PRIVATE_PATH "/private/" + +// Program exit codes +enum ExitCodes { + EXIT_OK = 0, + EXIT_ERR = 1, + EXIT_FILE_ERR = 2, + EXIT_PORT_ERR = 3 +}; + +// Server Statistics +struct ServerStatistics { + int currentConnections; + int completedConnections; + int authFails; + int gets; + int puts; + int deletes; + pthread_mutex_t connectionLock; +}; + +// Connection information needed for threads +struct ConnectionInfo { + int fd; + int connections; + char *authKey; + struct ConnectionThread *connectionThread; + StringStore **stringStores; +}; + +// Information regarding a client request +struct RequestParams { + char *method; + char *path; + char *key; + char *value; + char *authKey; + HttpHeader **headers; +}; + +// The server statistic instance, global as needed to be called by SIGHUP +struct ServerStatistics *serverStats; + +/* get_nth_char_position() + * ----------------------- + * Get the character position of the nth occurrence of that character + * + * s: the string to search + * c: the character to look for + * n: the nth occurance to retrieve the position of + * + * Returns: the position of the nth ocurrance of the given character + */ +int get_nth_char_position(const char *s, int c, int n); + +/* get_valid_number() + * ------------------ + * Checks if a given number is an actual number, then returns it as an int + * + * rawNumber: the number to check and convert + * + * Returns: -1 if the number is not valid, the converted string if it is + * valid + */ +int get_valid_number(char *rawNumber); + +/* get_authkey_from_file() + * ----------------------- + * Gets the first line from a file + * + * filePath: the path to the file + * + * Returns: NULL if the file does not exist or the first line does not + * exist, the first line otherwise + */ +char *get_authkey_from_file(char *filePath); + +/* get_address_info() + * ------------------ + * Gets the required addrinfo for the given port + * + * port: the port to assign to + * + * Returns: the appropriate addrinfo + */ +struct addrinfo *get_address_info(int port); + +/* open_socket() + * ------------- + * Opens a socket for the given port holding the given connections + * + * port: the port to open a socket on + * connections: the amount of connections to support + * + * Returns: -1 if failed to connect to socket, 0 if success + */ +int open_socket(int port); + +/* serverstats_init() + * ----------------------- + * Initializes the server statistics + * + * Returns: the newly initialised server stats + */ +void serverstats_init(void); + +/* handle_listen() + * --------------- + * Opens a socket and deals with connections + * + * port: the port to open a socket on + * connections: the amount of connections to support + * authKey: the authorization key for the private db + * + * Returns: 3 if failed to connect to socket, 0 if success + */ +int handle_listen(int port, int connections, char *authKey); + +/* process_response_get() + * ----------------------- + * Gets the appropriate get response for the request from the client + * + * key: the key to get the value of from the db + * stringStore: the db + * + * Returns: the appropriate response (200 with value or 404) + */ +char *process_response_get(char *key, StringStore *stringStore); + +/* process_response_put() + * ----------------------- + * Adds the appropriate value to the db and then gets the appropriate + * response + * + * key: the key to update the value of in the db + * value: the value to update the key with + * stringStore: the db + * + * Returns: the appropiate response (200 or 500 if failed) + */ +char *process_response_put(char *key, char *value, StringStore *stringStore); + +/* process_response_delete() + * ----------------------- + * Deletes the given key from the db and returns the appropriate response + * + * key: the key to delete from the db + * stringStore: the db + * + * Returns: the appropriate response (200 or 404) + */ +char *process_response_delete(char *key, StringStore *stringStore); + +/* check_auth_key() + * ---------------- + * Check the given authentication key with the given headers + * + * headers: the headers that may contain the authorization header + * authKey: the auth key to check against the header + * + * Returns: 1 if the key matches, 0 otherwise + */ +int check_auth_key(HttpHeader **headers, char *authKey); + +/* construct_response() + * -------------------- + * Process the request and construct the needed response + * + * req: the request parameters + * stringStores: the db to update keys + * + * Returns: the required response + */ +char *construct_response(struct RequestParams req, StringStore **stringStores); + +/* get_path_key() + * -------------- + * Gets the needed path and key from the supplied address + * + * address: the address given by the HTTP request + * path: the pointer to the path variable to update + * key: the pointer to the key variable to update + */ +int get_path_key(char *address, char **path, char **key); + +/* request_params_init() + * --------------------- + * Initialise the requestParams struct + * + * Returns: the initialised requestParams struct + */ +struct RequestParams request_params_init(void); + +/* handle_connection_request() + * --------------------------- + * Handles the clients request + * + * recv: the connection file to read from + * send: the connection file to write to + * authKey: the authorization key for the private db + * stringStores: the arrays of dbs + */ +int handle_connection_request( + FILE *recv, FILE *send, char *authKey, StringStore **stringStores); + +/* handle_client_connection() + * --------------------------- + * Handles the connection through the given fd + * + * fd: the fd to send and recieve information for the connection on + * authKey: the authorization key for the private db + * stringStores: the array of dbs + */ +void handle_client_connection( + int fd, char *authKey, StringStore **stringStores); + +/* handle_connection() + * ------------------- + * Thread function for handling connections to the server, if there are too + * many connections the server will turn away the client + * + * ciPtr: the pointer to the connection information + */ +void *handle_connection(void *ciPtr); + +/* print_server_stats() + * -------------------- + * Prints the server statistics. used by SIGHUP handler + * + * s: the signal from the signal handler + */ +void print_server_stats(int s); + +/* setup_signal_handler() + * ---------------------- + * Sets up the signal handler for SIGHUP + */ +void setup_signal_handler(void); + +#endif diff --git a/stringstore.c b/stringstore.c new file mode 100644 index 0000000..6573da8 --- /dev/null +++ b/stringstore.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include + +typedef struct StringStoreContents { + char *key; + char *value; +} StringStoreContents; + +struct StringStore { + struct StringStoreContents *contents; + int size; +}; + +StringStore *stringstore_init(void) { + StringStore *stringStore = (StringStore *)malloc(sizeof(StringStore)); + memset(stringStore, 0, sizeof(StringStore)); + stringStore->size = 0; + stringStore->contents = + (StringStoreContents *)malloc(sizeof(StringStoreContents)); + return stringStore; +} + +StringStore *stringstore_free(StringStore *store) { + for (int i = 0; i < store->size; i++) { + free(store->contents[i].key); + free(store->contents[i].value); + } + free(store->contents); + free(store); + return NULL; +} + +int stringstore_add(StringStore *store, const char *key, const char *value) { + stringstore_delete(store, key); + + store->contents = (StringStoreContents *)realloc( + store->contents, sizeof(StringStoreContents) * (store->size + 1)); + + if ((store->contents[store->size].key = strdup(key)) == NULL || + (store->contents[store->size].value = strdup(value)) == NULL) { + return 0; + } + store->size++; + return 1; +} + +const char *stringstore_retrieve(StringStore *store, const char *key) { + for (int i = 0; i < store->size; i++) { + if (strcmp(store->contents[i].key, key) == 0) { + return store->contents[i].value; + } + } + return NULL; +} + +int stringstore_delete(StringStore *store, const char *key) { + for (int i = 0; i < store->size; i++) { + if (strcmp(store->contents[i].key, key) == 0) { + free(store->contents[i].key); + free(store->contents[i].value); + if ((i + 1) < store->size) { + store->contents[i] = store->contents[i + 1]; + } + store->size--; + return 1; + } + } + return 0; +}