Compare commits

..

53 Commits
master ... dev

Author SHA1 Message Date
Barra Ó Catháin 19fa527d73 Added queues.h
* source/server/queues.h (QUEUES_H): Defined the interface and structures for universal queues.
2024-04-17 14:06:45 +01:00
Barra Ó Catháin bbcde81eaf Updated gitignore
* .gitignore: Added in tests and logs to be ignored.
2024-04-15 23:36:44 +01:00
Barra Ó Catháin ea76226760 Updated test to match new name for ListNode 2024-04-15 23:34:14 +01:00
Barra Ó Catháin 9b88d092c3 Merge branch 'worlds-and-queues' into dev 2024-04-15 23:31:56 +01:00
Barra Ó Catháin 80dce61058 Renamed Node to ListNode
* source/server/lists.c (appendToList): String replaced Node with ListNode.
(deleteNodeFromList): String replaced Node with ListNode.
(deleteListNodeFromList): String replaced Node with ListNode.
(indexOfFromList): String replaced Node with ListNode.
(getFirstFromList): String replaced Node with ListNode.
(isInList): String replaced Node with ListNode.
(isPointerInList): String replaced Node with ListNode.
* source/server/lists.h: String replaced Node with ListNode.
2024-04-14 23:47:03 +01:00
Barra Ó Catháin ef3cfb69c2 Removed tests directory. 2024-04-14 23:17:45 +01:00
Barra Ó Catháin 06b7f66f20 Added testing harness and test for lists
* Makefile.am (SilverMUDClient_SOURCES): Added testing into configuration.
* source/tests/lists-test.c: Initial list test.
* source/tests/tests.h (SILVERMUD_TESTS): Simple test harness.
2024-04-14 23:17:29 +01:00
Barra Ó Catháin cbec016039 Moved data type enum to own header
* source/server/data-type.h (DATATYPE_H): Moved from lists.h.
* source/server/lists.h (LISTS_H): Added include statement for data-type.h.
2024-04-14 23:16:10 +01:00
Barra Ó Catháin 9562cbfd21 Initial Implementation Of Lists
* lists.c (createList): Initial implementation.
(appendToList): Initial implementation.
(deleteNodeFromList): Initial implementation.
(indexOfFromList): Initial implementation.
(getFirstFromList): Initial implementation.
(isInList): Initial implementation.
(isPointerInList): Initial implementation.
* lists.h (LISTS_H): Defines the enum "DataType", and the structs "Node" and "List".
2024-04-14 23:08:37 +01:00
Barra Ó Catháin ce9f197a83 Accidental typo while moving around. 2024-03-30 22:47:01 +00:00
Barra Ó Catháin 060c637c08 Removed now unneeded placeholder. 2024-03-30 22:47:01 +00:00
Barra Ó Catháin c282fb20ad Changed player names to allocated strings to be friendly to Scheme! 2024-03-30 22:47:01 +00:00
Barra Ó Catháin aa382fefc2 Basic Scheme implementations of player structures and messaging. 2024-03-30 22:47:01 +00:00
Barra Ó Catháin fa3df0cc69 Update C Codebase to use modules for Scheme primitives 2024-03-30 22:47:01 +00:00
Barra Ó Catháin 4d13547ae6 Update output queue to not pin the CPU (proper waiting) 2024-03-30 22:47:01 +00:00
Barra Ó Catháin 751a734016 Added independent output thread. 2024-03-30 22:47:01 +00:00
Barra Ó Catháin 258fd49653 Basic global messaging functions for Scheme 2024-03-30 22:47:01 +00:00
Barra Ó Catháin 89d520dc15 Initial test implementation of output message queuing. 2024-03-30 22:47:01 +00:00
Barra Ó Catháin 6fc2e4d2b9 Close client if connection breaks.
* source/client/receiving-thread.c (receivingThreadHandler): Exit the process if the connection to the server breaks.
* source/server/main.c (main): Minor formatting tweak.
2024-03-19 23:28:12 +00:00
Barra Ó Catháin 0a2d03fdaa Added environment variable handling.
* source/server/main.c (main):
- Added environment variable handling.
- Moved "Using" messages to after both command line arguments and enviroment variables are checked and applied.
- Command-line arguments override enviroment variables.
2024-03-19 23:20:08 +00:00
Barra Ó Catháin 9660fd4c60 Fix config.h.
* source/client/client-drawing.c: Change to correct include directive for config.h.
* source/client/main.c: Change to correct include directive for config.h.
2024-03-19 22:55:45 +00:00
Barra Ó Catháin 2d6b194c26 Added basic command-line options to server.
* source/server/main.c
(checkRequestedHostname): Added function to check if client SNI hostname is the same as specified by the server.
(main): Added command-line options for binding to ports, hostnames, and interfaces.
2024-03-18 03:14:50 +00:00
Barra Ó Catháin 24f8e2688a Added address iteration and graceful TLS failures.
* source/client/main.c (main):
- Added iteration through found addresses from getaddrinfo.
- Added graceful failures for TLS errors.
2024-03-18 03:10:59 +00:00
Barra Ó Catháin 03ea201716 Make client use getopts and getaddrinfo
* source/client/main.c (main):
  Added command-line option handler.
  Refactored socket creation make use of getaddrinfo.
2024-03-15 01:29:34 +00:00
Barra Ó Catháin 7fa1fadc12 Accidental typo while moving around. 2023-11-21 10:51:38 +00:00
Barra Ó Catháin 3c8ed9994b Removed now unneeded placeholder. 2023-11-20 21:34:06 +00:00
Barra Ó Catháin 30e63d36b5 Changed player names to allocated strings to be friendly to Scheme! 2023-11-20 21:33:45 +00:00
Barra Ó Catháin 3873192547 Basic Scheme implementations of player structures and messaging. 2023-11-20 21:32:53 +00:00
Barra Ó Catháin 4fa677c09a Update C Codebase to use modules for Scheme primitives 2023-11-20 21:30:30 +00:00
Barra Ó Catháin 3309c034c4 Update output queue to not pin the CPU (proper waiting) 2023-11-20 21:26:21 +00:00
Barra Ó Catháin 2e813ae29c Added independent output thread. 2023-11-09 23:51:50 +00:00
Barra Ó Catháin e11a7b3a76 Basic global messaging functions for Scheme 2023-11-07 22:46:15 +00:00
Barra Ó Catháin 2acbe5e19b Initial test implementation of output message queuing. 2023-11-07 11:52:20 +00:00
Barra Ó Catháin 442a9319e8 Removed unneeded check, added temporary name command 2023-10-30 16:57:15 +00:00
Barra Ó Catháin a66a07c897 Properly remove disconnected players 2023-10-29 20:15:41 +00:00
Barra Ó Catháin 81fc72a1d7 Added system messages to client and added welcome message from server. 2023-10-29 17:20:14 +00:00
Barra Ó Catháin a1b1b80449 Removed now unneeded placeholders, added stubs for "rulebooks" in documentation. 2023-09-12 23:25:44 +01:00
Barra Ó Catháin 54b613befe Basic implementation of player lists and tying connections to players 2023-09-12 22:32:19 +01:00
Barra Ó Catháin 5a53e9f197 Added basic player type containing a name, and made the server echo messages with player name. 2023-09-10 17:24:46 +01:00
Barra Ó Catháin 3fc75ef30f Basic message receiver, server now echoes messages to all clients. 2023-08-31 01:44:17 +01:00
Barra Ó Catháin b292966588 Fixed window height calculations. 2023-08-28 02:53:31 +01:00
Barra Ó Catháin 50dcddfc56 Initial ncurses setup, and layout of client. 2023-08-28 02:29:21 +01:00
Barra Ó Catháin c043da64a2 Modify server and client to begin using ClientToServer messages. 2023-08-26 00:48:28 +01:00
Barra Ó Catháin 0104a11a7e Added basic client capable of connecting to the server. 2023-08-25 00:34:05 +01:00
Barra Ó Catháin 080e46fe99 Set up GNU Autotools as build system. 2023-08-24 00:12:27 +01:00
Barra Ó Catháin 0814e437cd Basic connection handling (using previous version of client) 2023-08-22 02:02:29 +01:00
Barra Ó Catháin 9801be3622 Renamed src back to source, because I liked it better 2023-08-19 16:00:57 +01:00
Barra Ó Catháin e2ef744e87 Moved scheme initialization to main thread, added basic networking
The server can now listen on a port and send data to a client.
2023-08-19 00:18:03 +01:00
Barra Ó Catháin 8b0920c35d Added basic implemantation of message structures. 2023-08-18 00:45:24 +01:00
Barra Ó Catháin 6ed532c368 Added structure section. 2023-08-17 00:21:20 +01:00
Barra Ó Catháin 48f0858735 Began implementation planning document. 2023-08-17 00:14:40 +01:00
Barra Ó Catháin 32503cdbca Rename source/ to src/ for Autotools. 2023-08-14 03:15:37 +01:00
Barra Ó Catháin 2b488477f5 Added initial stubs for server.
- Server now launches a thread to initialize Scheme, and drops into a REPL.
2023-08-14 02:46:43 +01:00
23 changed files with 1185 additions and 51 deletions

8
.gitignore vendored
View File

@ -108,4 +108,10 @@ SilverMUDServer
SilverMUDClient
config.h
config.h.in
stamp-h1
stamp-h1
build/
# Tests:
*.log
*_test*

View File

@ -1,5 +1,7 @@
bin_PROGRAMS = SilverMUDServer SilverMUDClient
dist_doc_DATA = README.org
schemedir = $(pkgdatadir)/scheme
dist_scheme_DATA = lisp
SilverMUDServer_CFLAGS = -lgnutls -g $(GUILE_CFLAGS) $(GUILE_LIBS)
SilverMUDClient_CFLAGS = -lgnutls -g -lncurses $(GUILE_CFLAGS) $(GUILE_LIBS)
@ -8,10 +10,18 @@ SilverMUDServer_SOURCES = \
source/server/player-data.c \
source/server/connections.c \
source/server/scheme-integration.c \
source/server/main.c
source/server/output-queue.c \
source/server/main.c
SilverMUDClient_SOURCES = \
source/messages.c \
source/client/client-drawing.c \
source/client/receiving-thread.c \
source/client/main.c
check_PROGRAMS = lists_test
lists_test_SOURCES = \
source/tests/lists-test.c \
source/server/lists.c
TESTS = $(check_PROGRAMS)

View File

@ -2,8 +2,6 @@ AC_INIT([SilverMUD], [0.0.1], [barra@ocathain.ie])
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
AC_PROG_CC
AC_CONFIG_HEADERS([source/config.h])
AC_CONFIG_FILES([
Makefile
])
AC_CONFIG_FILES([Makefile])
PKG_CHECK_MODULES([GUILE], [guile-3.0])
AC_OUTPUT

View File

18
lisp/messaging.scm Normal file
View File

@ -0,0 +1,18 @@
;;;; This file is part of SilverMUD.
;;;; structures.scm defines various variables and functions used for interacting with the player's
;;;; via chat output from the Scheme enviroment of SilverMUD.
(define-module (silvermud messaging))
(use-modules (silvermud primitives))
(define message-everyone (lambda (name content)
(push-output-message *global-output-queue* #f *global-player-list*
8 name content)))
(define system-message (lambda (content)
(push-output-message *global-output-queue* #f *global-player-list*
0 "" content)))
(define message-expression (lambda (expression)
(system-message (format #f "~a" expression))))
;; Export everything!
(export message-everyone system-message message-expression)

67
lisp/structures.scm Normal file
View File

@ -0,0 +1,67 @@
;;;; This file is part of SilverMUD.
;;;; structures.scm defines various variables and functions used for interacting with C structures
;;;; from the Scheme enviroment of SilverMUD.
(define-module (silvermud structures))
(use-modules (system foreign)
(silvermud primitives))
;;; struct PlayerList:
;; The layout of the struct PlayerList:
(define *player-list-structure* (list size_t '* '*))
;; Pretty-format the player list header:
(define (player-list-header->string player-list-pointer)
"Format a struct PlayerList pointer into a string."
(if (not (null-pointer? player-list-pointer))
(let ((structure (parse-c-struct player-list-pointer *player-list-structure*)))
(format #f
"Players in list: ~d.\nHead: ~a. \nTail: ~a.\n"
(list-ref structure 0) (list-ref structure 1) (list-ref structure 2)))))
;; Create a list of strings representing all players in a list:
(define (list-players player-list-pointer)
"List all players in a given C PlayerList as a list of strings."
(if (not (null-pointer? player-list-pointer)) ; Check we're not dereferencing a null pointer.
(build-list-players
(list-ref (parse-c-struct player-list-pointer *player-list-node-structure*) 2))))
(define (build-list-players pointer)
(if (not (null-pointer? pointer))
(let* ((node (parse-c-struct pointer *player-list-node-structure*))
(player (parse-c-struct (list-ref node 0) *player-structure*)))
(cons (pointer->string (list-ref player 1)) (build-list-players (list-ref node 1))))
'()))
;;; struct PlayerListNode:
;; Used to interact with struct PlayerListNode:
(define *player-list-node-structure* (list '* '* '*))
;; Pretty-format the player list node:
(define (player-list-node->string player-list-node-pointer)
"Format a struct PlayerListNode pointer into a string."
(if (not (null-pointer? player-list-node-pointer))
(let ((structure (parse-c-struct player-list-node-pointer *player-list-node-structure*)))
(format #f
"Player pointer: ~a.\nNext: ~a. \nPrevious: ~a.\n"
(list-ref structure 0) (list-ref structure 1) (list-ref structure 2)))))
;;; struct Player:
; Used to interact with struct Player:
(define *player-structure* (list '* '*))
(define (player->string player-info-pointer)
"Format a struct Player pointer into a string."
(if (not (null-pointer? player-info-pointer))
(let ((structure (parse-c-struct player-info-pointer *player-structure*)))
(display (null-pointer? (list-ref structure 1)))
(format #f
"Player Name: ~a\n" (if (null-pointer? (list-ref structure 1))
(pointer->bytevector (list-ref structure 1) 64)
#f)))))
;; Export everything!
(export *player-list-structure* *player-list-node-structure* *player-structure*
player->string player-list-header->string player-list-node->string list-players)

View File

@ -3,7 +3,7 @@
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// ==========================================
#include "../config.h"
#include <config.h>
#include "client-drawing.h"
void redrawClientLayout(WINDOW * gameWindow, WINDOW * chatWindow, WINDOW * inputWindow)

View File

@ -4,7 +4,11 @@
// | See end of file for copyright notice. |
// =========================================
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <config.h>
#include <stdlib.h>
#include <getopt.h>
#include <limits.h>
#include <pthread.h>
#include <ncurses.h>
@ -13,17 +17,53 @@
#include <sys/socket.h>
#include <gnutls/gnutls.h>
#include "../config.h"
#include "../messages.h"
#include "client-drawing.h"
#include "receiving-thread.h"
static char serverPort[HOST_NAME_MAX] = "5050";
static char serverHostname[HOST_NAME_MAX] = "127.0.0.1";
static bool hostSpecified = false, portSpecified = false;
int main (int argc, char ** argv)
{
// Print a welcome message:
printf("SilverMUD Client - Starting Now.\n"
"================================\n");
struct addrinfo * serverInformation;
// Configure command-line options:
static struct option longOptions[] =
{
{"host", required_argument, 0, 'h' },
{"port", required_argument, 0, 'p' }
};
// Parse command-line options:
int selectedOption = 0, optionIndex = 0;
while ((selectedOption = getopt_long(argc, argv, "h:p:", longOptions, &optionIndex)) != -1)
{
switch (selectedOption)
{
case 'h':
{
printf("Connecting to host: %s\n", optarg);
hostSpecified = true;
strncpy(serverHostname, optarg, HOST_NAME_MAX);
break;
}
case 'p':
{
printf("Connecting to port: %s\n", optarg);
portSpecified = true;
strncpy(serverPort, optarg, HOST_NAME_MAX);
break;
}
}
}
// Create a socket for communicating with the server:
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1)
@ -33,18 +73,28 @@ int main (int argc, char ** argv)
}
// Set up the server address structure to point to the server:
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddress.sin_port = htons(5000);
// Connect to the server:
if (connect(serverSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)) != 0)
if (getaddrinfo(serverHostname, serverPort, NULL, &serverInformation) != 0)
{
printf("Server lookup failed. Aborting.\n");
exit(EXIT_FAILURE);
}
// Connect to the server, iterating through addresses until we get SilverMUD:
struct addrinfo * currentAddress;
for (currentAddress = serverInformation; currentAddress != NULL; currentAddress = currentAddress->ai_next)
{
if (connect(serverSocket, serverInformation->ai_addr, serverInformation->ai_addrlen) != -1)
{
break;
}
}
if (currentAddress == NULL)
{
printf("Failed to connect to the server. Aborting.\n");
exit(EXIT_FAILURE);
}
freeaddrinfo(serverInformation);
// Set up a GnuTLS session and handshake with the server:
gnutls_session_t tlsSession = NULL;
if (gnutls_init(&tlsSession, GNUTLS_CLIENT) < 0)
@ -54,20 +104,31 @@ int main (int argc, char ** argv)
gnutls_anon_client_credentials_t clientKey = NULL;
gnutls_anon_allocate_client_credentials(&clientKey);
gnutls_credentials_set(tlsSession, GNUTLS_CRD_ANON, &clientKey);
gnutls_transport_set_int(tlsSession, serverSocket);
gnutls_priority_set_direct(tlsSession, "PERFORMANCE:+ANON-ECDH:+ANON-DH", NULL);
gnutls_credentials_set(tlsSession, GNUTLS_CRD_ANON, &clientKey);
gnutls_handshake_set_timeout(tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
gnutls_priority_set_direct(tlsSession, "PERFORMANCE:+ANON-ECDH:+ANON-DH", NULL);
gnutls_server_name_set(tlsSession, GNUTLS_NAME_DNS, serverHostname, strlen(serverHostname));
int returnValue = -1;
int returnValue = -1, connectionAttempts = 0;
do
{
returnValue = gnutls_handshake(tlsSession);
connectionAttempts++;
if (connectionAttempts == 50)
{
printf("Failed to establish a TLS session. Aborting.\n");
exit(EXIT_FAILURE);
}
}
while (returnValue < 0 && gnutls_error_is_fatal(returnValue) == 0);
if (returnValue < 0)
{
printf("Failed to establish a TLS session. Aborting.\n");
exit(EXIT_FAILURE);
}
// Initialize ncurses:
initscr();
keypad(stdscr, TRUE);

View File

@ -34,10 +34,16 @@ void * receivingThreadHandler(void * threadArguments)
wattrset(gameWindow, A_NORMAL);
struct ServerToClientMessage currentMessage;
int returnValue = 0;
while (true)
{
gnutls_record_recv(session, &currentMessage, sizeof(struct ServerToClientMessage));
returnValue = gnutls_record_recv(session, &currentMessage, sizeof(struct ServerToClientMessage));
if (gnutls_error_is_fatal(returnValue))
{
exit(EXIT_SUCCESS);
}
switch (currentMessage.type)
{
case SYSTEM:

32
source/server/data-type.h Normal file
View File

@ -0,0 +1,32 @@
// =========================================
// | SilverMUD Server - data-type.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef DATATYPE_H
#define DATATYPE_H
enum DataType
{
AREA,
PLAYER,
CONNECTION
};
#endif
// =================================================
// | End of data-type.h, copyright notice follows. |
// =================================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

230
source/server/lists.c Normal file
View File

@ -0,0 +1,230 @@
// =========================================
// | SilverMUD Server - lists.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "lists.h"
// Functions:
// ==========
struct List * createList(enum DataType type)
{
struct List * newList = calloc(1, sizeof(struct List));
newList->itemCount = 0;
newList->head = NULL;
newList->tail = NULL;
newList->type = type;
return newList;
}
size_t appendToList(enum DataType type, struct List * list, void * data)
{
// First check that you're adding the correct type:
assert(type == list->type);
struct ListNode * newListNode = calloc(1, sizeof(struct ListNode));
newListNode->next = NULL;
newListNode->previous = list->tail;
newListNode->data = data;
if (list->itemCount == 0)
{
list->head = newListNode;
}
else
{
list->tail->next = newListNode;
}
list->tail = newListNode;
list->itemCount++;
return list->itemCount;
}
void * deleteListNodeFromList(size_t index, struct List * list)
{
void * toReturn;
if ((list->itemCount - 1) < index)
{
return NULL;
}
struct ListNode * currentListNode = NULL;
if (index < (list->itemCount / 2))
{
currentListNode = list->head;
// Get to the correct point in the linked list:
for (int currentIndex = 0; currentIndex < index; currentIndex++)
{
currentListNode = currentListNode->next;
}
}
else
{
currentListNode = list->tail;
// Get to the correct point in the linked list:
for (int currentIndex = list->itemCount - 1; currentIndex > index; currentIndex--)
{
currentListNode = currentListNode->previous;
}
}
if (currentListNode == list->head)
{
list->head = list->head->next;
if (list->head)
{
list->head->previous = NULL;
}
}
if (currentListNode == list->tail)
{
list->tail = list->tail->previous;
if (list->tail)
{
list->tail->next = NULL;
}
}
if (currentListNode->next != NULL)
{
currentListNode->next->previous = currentListNode->previous;
}
if (currentListNode->previous != NULL)
{
currentListNode->previous->next = currentListNode->next;
}
toReturn = currentListNode->data;
free(currentListNode);
list->itemCount--;
return toReturn;
}
ssize_t indexOfFromList(bool (*comparisonFunction)(void *, void *), void * data, struct List * list)
{
size_t index = 0;
if (list->head == NULL)
{
return -1;
}
else
{
struct ListNode * currentListNode = list->head;
do
{
if (comparisonFunction(currentListNode->data, data) == true)
{
return index;
}
index++;
currentListNode = currentListNode->next;
}
while (currentListNode != NULL);
return -1;
}
}
void * getFirstFromList(bool (*comparisonFunction)(void *, void *), void * data, struct List * list)
{
size_t index = 0;
if (list->head == NULL)
{
return NULL;
}
else
{
struct ListNode * currentListNode = list->head;
do
{
if (comparisonFunction(currentListNode->data, data) == true)
{
return currentListNode;
}
index++;
currentListNode = currentListNode->next;
}
while (currentListNode != NULL);
return NULL;
}
}
bool isInList(bool (*comparisonFunction)(void *, void *), void * data, struct List * list)
{
if (list->head == NULL)
{
return false;
}
else
{
struct ListNode * currentListNode = list->head;
do
{
if (comparisonFunction(currentListNode->data, data) == 0)
{
return true;
}
currentListNode = currentListNode->next;
}
while (currentListNode != NULL);
return false;
}
}
bool isPointerInList(void * data, struct List * list)
{
if (list->head == NULL)
{
return false;
}
else
{
struct ListNode * currentListNode = list->head;
do
{
if (currentListNode->data == data)
{
return true;
}
currentListNode = currentListNode->next;
}
while (currentListNode != NULL);
return false;
}
}
// =============================================
// | End of lists.h, copyright notice follows. |
// =============================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

54
source/server/lists.h Normal file
View File

@ -0,0 +1,54 @@
// =========================================
// | SilverMUD Server - lists.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef LISTS_H
#define LISTS_H
#include "data-type.h"
#include <stdbool.h>
struct List
{
size_t itemCount;
enum DataType type;
struct ListNode * head;
struct ListNode * tail;
};
struct ListNode
{
struct ListNode * next;
struct ListNode * previous;
void * data;
};
// Functions:
// ==========
struct List * createList(enum DataType type);
size_t appendToList(enum DataType type, struct List * list, void * data);
void * deleteListNodeFromList(size_t index, struct List * list);
ssize_t indexOfFromList(bool (*comparisonFunction)(void *, void *), void * data, struct List * list);
void * getFirstFromList(bool (*comparisonFunction)(void *, void *), void * data, struct List * list);
bool isInList(bool (*comparisonFunction)(void *, void *), void * data, struct List * list);
bool isPointerInList(void * data, struct List * list);
#endif
// =============================================
// | End of lists.h, copyright notice follows. |
// =============================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -9,6 +9,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <pthread.h>
#include <libguile.h>
@ -19,13 +20,37 @@
#include <netinet/in.h>
#include <gnutls/gnutls.h>
#include "output-queue.h"
#include "player-data.h"
#include "connections.h"
#include "../messages.h"
#include "scheme-integration.h"
static const int PORT = 5000;
static const int CONCURRENT_PLAYER_COUNT = 256;
static char serverPort[HOST_NAME_MAX] = "5050";
static char serverHostname[HOST_NAME_MAX] = "";
static char serverInterface[HOST_NAME_MAX] = "";
static char clientRequestedHost[HOST_NAME_MAX] = "";
static size_t clientRequestedHostLength = HOST_NAME_MAX;
static bool portSpecified = false, hostSpecified = false, interfaceSpecified = false;
// Check what the client intends to connect to:
int checkRequestedHostname(gnutls_session_t session)
{
// Get the hostname the client is using to connect:
clientRequestedHostLength = HOST_NAME_MAX;
gnutls_server_name_get(session, (void *)clientRequestedHost, &clientRequestedHostLength, &(unsigned int){GNUTLS_NAME_DNS}, 0);
clientRequestedHost[HOST_NAME_MAX - 1] = '\0';
printf("Client is connecting to: %s\n", clientRequestedHost);
// Check that it's a valid hostname for SilverMUD:
if (hostSpecified == true && strncmp(serverHostname, clientRequestedHost, HOST_NAME_MAX) != 0)
{
return GNUTLS_E_UNRECOGNIZED_NAME;
}
return 0;
}
int main (int argc, char ** argv)
{
@ -33,14 +58,78 @@ int main (int argc, char ** argv)
printf("SilverMUD Server - Starting Now.\n"
"================================\n");
// Configure command-line options:
static struct option longOptions[] =
{
{"port", required_argument, 0, 'p' },
{"host", required_argument, 0, 'h' },
{"interface", required_argument, 0, 'i' }
};
// Check environment variables:
if (getenv("SILVERMUD_SERVER_PORT") != NULL)
{
portSpecified = true;
strncpy(serverPort, getenv("SILVERMUD_SERVER_HOST"), HOST_NAME_MAX);
}
if (getenv("SILVERMUD_SERVER_HOST") != NULL)
{
hostSpecified = true;
strncpy(serverHostname, getenv("SILVERMUD_SERVER_HOST"), HOST_NAME_MAX);
}
if (getenv("SILVERMUD_SERVER_INTERFACE") != NULL)
{
interfaceSpecified = true;
strncpy(serverInterface, getenv("SILVERMUD_SERVER_INTERFACE"), HOST_NAME_MAX);
}
// Parse command-line options:
int selectedOption = 0, optionIndex = 0;
while ((selectedOption = getopt_long(argc, argv, "p:h:i:", longOptions, &optionIndex)) != -1)
{
switch (selectedOption)
{
case 'p':
{
portSpecified = true;
strncpy(serverPort, optarg, HOST_NAME_MAX);
break;
}
case 'h':
{
hostSpecified = true;
strncpy(serverHostname, optarg, HOST_NAME_MAX);
break;
}
case 'i':
{
printf("Using interface address: %s\n", optarg);
interfaceSpecified = true;
strncpy(serverInterface, optarg, HOST_NAME_MAX);
break;
}
}
}
if (portSpecified)
{
printf("Using port: %s\n", serverPort);
}
if (hostSpecified)
{
printf("Using hostname: %s\n", serverHostname);
}
if (interfaceSpecified)
{
printf("Using interface: %s\n", serverInterface);
}
// Initialize Scheme:
scm_init_guile();
// Start the REPL server on a UNIX socket:
scm_c_eval_string("(begin (use-modules (system repl server))"
"(if (file-exists? \"silvermud-repl\") (delete-file \"silvermud-repl\"))"
"(spawn-server (make-unix-domain-server-socket #:path \"silvermud-repl\")))");
// Create a socket to listen for connections on:
int masterSocket = socket(AF_INET, SOCK_STREAM, 0);
if (masterSocket < 0)
@ -59,8 +148,10 @@ int main (int argc, char ** argv)
// Assign the IP address and port to the server address struct:
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(PORT);
serverAddress.sin_addr.s_addr = (interfaceSpecified) ?
inet_addr(serverInterface) : htonl(INADDR_ANY);
serverAddress.sin_port = (portSpecified) ?
htons(atoi(serverPort)) : htons(5050);
// Bind the master socket to the server address:
if ((bind(masterSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in))) != 0)
@ -104,11 +195,31 @@ int main (int argc, char ** argv)
// Create some structures needed to store global state:
struct PlayerList * globalPlayerList = createPlayerList();
struct OutputQueue * globalOutputQueue = createOutputQueue();
// Define a module for use in the REPL containing our needed primitives:
SchemeModulePointers schemePointers;
schemePointers.globalPlayerList = globalPlayerList;
schemePointers.globalOutputQueue = globalOutputQueue;
scm_c_define_module("silvermud primitives", initialize_silvermud_primitives, &schemePointers);
scm_c_use_module("silvermud primitives");
// Start the REPL server on a UNIX socket:
scm_c_eval_string("(begin (use-modules (system repl server))"
"(if (file-exists? \"silvermud-repl\") (delete-file \"silvermud-repl\"))"
"(spawn-server (make-unix-domain-server-socket #:path \"silvermud-repl\")))");
// Start an output thread:
pthread_t outputThread;
pthread_create(&outputThread, NULL, outputThreadHandler, (void *)globalOutputQueue);
// Start a REPL thread:
//pthread_t schemeREPLThread;
//pthread_create(&schemeREPLThread, NULL, schemeREPLHandler, NULL);
size_t * clientRequestedHostLength = calloc(1, sizeof(size_t));
while (true)
{
do
@ -133,22 +244,23 @@ int main (int argc, char ** argv)
gnutls_priority_set_direct(*tlsSession, "NORMAL:+ANON-ECDH:+ANON-DH", NULL);
gnutls_credentials_set(*tlsSession, GNUTLS_CRD_ANON, serverKey);
gnutls_handshake_set_timeout(*tlsSession, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
gnutls_handshake_set_post_client_hello_function(*tlsSession, checkRequestedHostname);
// Accept the connection:
int newSocket = accept(masterSocket, NULL, NULL);
gnutls_transport_set_int(*tlsSession, newSocket);
// Perform a TLS handshake:
int handshakeReturnValue = 0;
do
{
handshakeReturnValue = gnutls_handshake(*tlsSession);
} while (handshakeReturnValue < 0 && gnutls_error_is_fatal(handshakeReturnValue) == 0);
// If the handshake was unsuccessful, close the connection:
if (handshakeReturnValue < 0)
{
printf("%d", handshakeReturnValue);
fprintf(stderr, "TLS Failure: %d\n", handshakeReturnValue);
fflush(stdout);
gnutls_bye(*tlsSession, 2);
shutdown(newSocket, 2);
@ -213,23 +325,10 @@ int main (int argc, char ** argv)
continue;
}
// ONLY FOR DEMO
struct ServerToClientMessage outputMessage;
// Copy the message to the output format:
outputMessage.type = LOCAL_CHAT;
strncpy(outputMessage.name, connection->player->name, 64);
strncpy(outputMessage.content, message.content, MESSAGE_CONTENT_LENGTH);
// Echo the message into all other clients: (Temporary)
struct ClientConnectionNode * currentClient = clientConnections.head;
while (currentClient != NULL)
{
gnutls_record_send(*currentClient->connection->tlsSession, &outputMessage,
sizeof(struct ServerToClientMessage));
currentClient = currentClient->next;
}
pushOutputMessage(globalOutputQueue, false, globalPlayerList, LOCAL_CHAT,
connection->player->name, message.content,
MESSAGE_NAME_LENGTH, MESSAGE_CONTENT_LENGTH);
}
}
else
@ -241,7 +340,7 @@ int main (int argc, char ** argv)
close(events[index].data.fd);
removeConnectionByFileDescriptor(&clientConnections, events[index].data.fd);
}
}
}
}
}

View File

@ -0,0 +1,168 @@
// =========================================
// | SilverMUD Server - output-queue.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#include "player-data.h"
#include "output-queue.h"
// A thread handler for constantly outputting messages from an output queue:
void * outputThreadHandler(void * outputQueue)
{
struct OutputQueue * queue = (struct OutputQueue *)outputQueue;
struct OutputMessage * currentMessage = NULL;
while (true)
{
if (queue->count == 0)
{
pthread_cond_wait(&queue->updated, &queue->waitMutex);
}
currentMessage = popOutputMessage(queue);
if (currentMessage != NULL)
{
struct PlayerListNode * currentPlayerNode = currentMessage->recepients->head;
while (currentPlayerNode != NULL)
{
gnutls_record_send(*currentPlayerNode->player->connection->tlsSession,
currentMessage->message, sizeof(struct ServerToClientMessage));
currentPlayerNode = currentPlayerNode->next;
}
if (currentMessage->deallocatePlayerList == true)
{
deallocatePlayerList(&currentMessage->recepients);
}
deallocateOutputMessage(&currentMessage);
}
}
}
struct OutputQueue * const createOutputQueue()
{
// Allocate a new queue:
struct OutputQueue * const newQueue = calloc(1, sizeof(struct OutputQueue));
// Initialize it:
pthread_mutex_init(&newQueue->mutex, NULL);
pthread_cond_init(&newQueue->updated, NULL);
newQueue->count = 0;
newQueue->front = NULL;
newQueue->back = NULL;
// Return the new queue:
return newQueue;
}
size_t pushOutputMessage(struct OutputQueue * const queue,
const bool deallocatePlayerList,
struct PlayerList * const recepients,
const enum MessageTypes type,
const char const * name, const char const * content,
const size_t nameLength, const size_t contentLength)
{
// Allocate the appropriate memory for the queued message:
struct OutputMessage * newMessage = calloc(1, sizeof(struct OutputMessage));
newMessage->message = calloc(1, sizeof(struct ServerToClientMessage));
// Copy in the appropriate values to the ServerToClientMessage:
newMessage->message->type = type;
strncpy(newMessage->message->name, name, (nameLength < MESSAGE_NAME_LENGTH) ?
nameLength : MESSAGE_NAME_LENGTH);
newMessage->message->name[MESSAGE_NAME_LENGTH - 1] = '\0';
strncpy(newMessage->message->content, content, (contentLength < MESSAGE_CONTENT_LENGTH) ?
contentLength : MESSAGE_CONTENT_LENGTH);
newMessage->message->content[MESSAGE_CONTENT_LENGTH - 1] = '\0';
// Copy in the appropriate values to the OutputMessage:
newMessage->deallocatePlayerList = deallocatePlayerList;
newMessage->recepients = recepients;
// Entering critical section - Lock the queue:
pthread_mutex_lock(&queue->mutex);
// Add it to the queue:
if (queue->back != NULL)
{
queue->back->next = newMessage;
queue->back = newMessage;
}
if (queue->front == NULL)
{
queue->front = newMessage;
queue->back = newMessage;
}
queue->count++;
// Leaving critical section - Unlock the queue:
pthread_mutex_unlock(&queue->mutex);
pthread_cond_signal(&queue->updated);
return queue->count;
}
struct OutputMessage * popOutputMessage(struct OutputQueue * queue)
{
if (queue->count == 0)
{
return NULL;
}
// Entering the critical section - Lock the queue:
pthread_mutex_lock(&queue->mutex);
struct OutputMessage * message = queue->front;
queue->count--;
if(queue->count == 0)
{
queue->front = NULL;
queue->back = NULL;
}
else
{
queue->front = queue->front->next;
}
// Leaving the critical section - Unlock the queue:
pthread_mutex_unlock(&queue->mutex);
return message;
}
void deallocateOutputMessage(struct OutputMessage ** message)
{
// Free and set the pointer to NULL:
free(*message);
message = NULL;
}
// ====================================================
// | End of output-queue.c, copyright notice follows. |
// ====================================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,65 @@
// =========================================
// | SilverMUD Server - output-queue.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef OUTPUT_QUEUE_H
#define OUTPUT_QUEUE_H
#include <stdbool.h>
#include <pthread.h>
#include "../messages.h"
struct OutputMessage
{
// Allows for easy reuse of existing player lists, such as the global list
// or an area's playerlist:
bool deallocatePlayerList;
struct OutputMessage * next;
struct PlayerList * recepients;
struct ServerToClientMessage * message;
};
struct OutputQueue
{
pthread_mutex_t mutex;
pthread_mutex_t waitMutex;
pthread_cond_t updated;
size_t count;
struct OutputMessage * front, * back;
};
void * outputThreadHandler(void * outputQueue);
struct OutputQueue * const createOutputQueue();
size_t pushOutputMessage(struct OutputQueue * const queue,
const bool deallocatePlayerList,
struct PlayerList * const recepients,
const enum MessageTypes type,
const char const * name, const char const * content,
const size_t nameLength, const size_t contentLength);
struct OutputMessage * popOutputMessage(struct OutputQueue * queue);
void deallocateOutputMessage(struct OutputMessage ** message);
#endif
// ====================================================
// | End of output-queue.h, copyright notice follows. |
// ====================================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -31,6 +31,7 @@ struct Player * createNewPlayer(struct ClientConnection * connection)
{
struct Player * newPlayer = calloc(1, sizeof(struct Player));
newPlayer->connection = connection;
newPlayer->name = calloc(PLAYER_NAME_LENGTH, sizeof(char));
return newPlayer;
}
@ -38,6 +39,7 @@ struct Player * createNewPlayer(struct ClientConnection * connection)
// Deallocates a player:
void deallocatePlayer(struct Player ** player)
{
free((*player)->name);
free(*player);
*player = NULL;
}

View File

@ -5,6 +5,7 @@
// =========================================
#ifndef PLAYER_DATA_H
#define PLAYER_DATA_H
#define PLAYER_NAME_LENGTH 64
#include <stdbool.h>
#include "connections.h"
@ -14,7 +15,7 @@
struct Player
{
struct ClientConnection * connection;
char name[64];
char * name;
};
// Functions:

54
source/server/queues.h Normal file
View File

@ -0,0 +1,54 @@
// =========================================
// | SilverMUD Server - queues.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef QUEUES_H
#define QUEUES_H
#include "data-type.h"
#include <stdbool.h>
struct Queue
{
size_t itemCount;
enum DataType type;
struct QueueNode * front;
struct QueueNode * back;
};
struct QueueNode
{
struct QueueNode * next;
void * data;
};
// Functions:
// ==========
struct Queue * createQueue(enum DataType type);
int destroyQueue(struct Queue * queue);
int destroyQueueAndContents(void (*deallocationFunction)(void *), struct Queue * queue);
void * peekFromQueue(struct Queue * queue);
size_t popFromQueue(struct Queue * queue);
size_t pushToQueue(enum DataType type, void * data, struct Queue * queue);
size_t popFromQueueAndDestroy(void (*deallocationFunction)(void *), struct Queue * queue);
#endif
// ==============================================
// | End of queues.h, copyright notice follows. |
// ==============================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -3,9 +3,47 @@
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// ===========================================
#include <stdbool.h>
#include <libguile.h>
SCM scheme_get_player_by_name(SCM name);
#include "../messages.h"
#include "output-queue.h"
#include "scheme-integration.h"
void initialize_silvermud_primitives (void * gameState)
{
SchemeModulePointers * pointers = (SchemeModulePointers *)gameState;
scm_c_define_gsubr("push-output-message", 6, 0, 0, &push_output_message);
scm_c_define("*global-player-list*", scm_from_pointer(pointers->globalPlayerList, NULL));
scm_c_define("*global-output-queue*", scm_from_pointer(pointers->globalOutputQueue, NULL));
scm_c_export("push-output-message", "*global-player-list*", "*global-output-queue*", NULL);
}
//SCM scheme_get_player_by_name(SCM name, SCM queue)
SCM push_output_message(SCM queue, SCM deallocate_list, SCM recepients, SCM type, SCM name, SCM content)
{
// Convert our scheme values into appropriate data types:
struct OutputQueue * queue_c = scm_to_pointer(queue);
bool deallocate_list_c = scm_to_bool(deallocate_list);
struct PlayerList * recepients_c = scm_to_pointer(recepients);
enum MessageTypes type_c = scm_to_int(type);
// Turn the Scheme strings into C strings:
size_t nameLength, contentLength;
char * name_c = scm_to_locale_stringn(name, &nameLength);
char * content_c = scm_to_locale_stringn(content, &contentLength);
// Call the C function:
pushOutputMessage(queue_c, deallocate_list_c, recepients_c, type_c, name_c, content_c,
nameLength, contentLength);
// Free the created C strings:
free(name_c);
free(content_c);
return SCM_BOOL_T;
}
// ==========================================================
// | End of scheme-integration.c, copyright notice follows. |

View File

@ -6,8 +6,18 @@
#ifndef SCHEME_INTEGRATION_H
#define SCHEME_INTEGRATION_H
typedef struct SchemeModulePointers
{
struct PlayerList * globalPlayerList;
struct OutputQueue * globalOutputQueue;
} SchemeModulePointers;
void initialize_silvermud_primitives (void * gameState);
SCM scheme_get_player_by_name(SCM name);
SCM push_output_message(SCM queue, SCM deallocate_list, SCM recepients, SCM type, SCM name, SCM content);
#endif
// ==========================================================
// | End of scheme-integration.h, copyright notice follows. |

172
source/tests/lists-test.c Normal file
View File

@ -0,0 +1,172 @@
// =========================================
// | SilverMUD Tests - lists-test.c |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "tests.h"
#include "../server/lists.h"
SETUP_TESTS;
bool compareChar (void * a, void * b)
{
return *(char *)a == *(char *)b;
}
void printListHeader(struct List * list)
{
printf("Items in list: %zu\nType: %d\nHead: %p\nTail: %p\n\n",
list->itemCount, list->type, list->head, list->tail);
}
void printListNode(struct ListNode * node)
{
printf("%p\nNext: %p\nPrevious: %p\nData: %c\n\n",
node, node->next, node->previous, *(char *)(node->data));
}
int main (int argc, char ** argv)
{
char testDataA = 'A', testDataB = 'B', testDataC = 'C';
// Test 1:
struct List * testList = createList(PLAYER);
TEST("Creating list",
(testList->head == NULL &&
testList->tail == NULL &&
testList->itemCount == 0));
// Test 2:
appendToList(PLAYER, testList, &testDataA);
TEST("Adding a single item to the list",
(testList->itemCount == 1 &&
testList->head != NULL &&
testList->tail != NULL &&
testList->tail == testList->head &&
*(char *)(testList->head->data) == 'A'));
// Test 3:
deleteListNodeFromList(0, testList);
TEST("Deleting a single item from the list",
(testList->head == NULL &&
testList->tail == NULL &&
testList->itemCount == 0));
// Test 4:
appendToList(PLAYER, testList, &testDataA);
appendToList(PLAYER, testList, &testDataB);
TEST("Adding two items to the list",
(testList->itemCount == 2 &&
testList->head != NULL &&
testList->tail != NULL &&
*(char *)testList->head->data == 'A' &&
*(char *)testList->tail->data == 'B' &&
testList->tail != testList->head &&
testList->head->next == testList->tail &&
testList->tail->previous == testList->head));
// Test 5:
deleteListNodeFromList(1, testList);
TEST("Deleting tail from the list",
(testList->itemCount == 1 &&
testList->head != NULL &&
testList->tail != NULL &&
testList->tail == testList->head &&
*(char *)(testList->head->data) == 'A'));
// Test 6:
appendToList(PLAYER, testList, &testDataB);
deleteListNodeFromList(0, testList);
TEST("Deleting head from the list",
(testList->itemCount == 1 &&
testList->head != NULL &&
testList->tail != NULL &&
testList->tail == testList->head &&
*(char *)(testList->head->data) == 'B'));
// Test 7:
deleteListNodeFromList(0, testList);
appendToList(PLAYER, testList, &testDataA);
appendToList(PLAYER, testList, &testDataB);
appendToList(PLAYER, testList, &testDataC);
TEST("Adding three items to the list",
(testList->itemCount == 3 &&
testList->head != NULL &&
testList->tail != NULL &&
*(char *)testList->head->data == 'A' &&
*(char *)testList->tail->data == 'C' &&
*(char *)testList->head->next->data == 'B' &&
*(char *)testList->tail->previous->data == 'B' &&
testList->tail != testList->head &&
testList->tail->previous == testList->head->next));
// Test 8:
deleteListNodeFromList(1, testList);
TEST("Deleting an item in middle of the list",
(testList->itemCount == 2 &&
testList->head != NULL &&
testList->tail != NULL &&
*(char *)testList->head->data == 'A' &&
*(char *)testList->tail->data == 'C' &&
testList->tail != testList->head &&
testList->head->next == testList->tail &&
testList->tail->previous == testList->head));
// Test 9:
deleteListNodeFromList(1, testList);
appendToList(PLAYER, testList, &testDataB);
appendToList(PLAYER, testList, &testDataC);
TEST("Checking for data in the list using comparison function",
(isInList(compareChar, &testDataB, testList) == true));
// Test 10:
TEST("Checking for data NOT in the list using comparison function",
(isInList(compareChar, &(char){'D'}, testList) == true));
// Test 11:
TEST("Checking for data in the list using pointer",
(isPointerInList(&testDataB, testList) == true));
// Test 12:
TEST("Checking for data NOT in the list using pointer",
(isPointerInList(&(char){'D'}, testList) == false));
// Test 13:
TEST("Checking index of data in the list",
(indexOfFromList(compareChar, &testDataB, testList) == 1));
// Test 14:
TEST("Checking index of data NOT in the list",
(indexOfFromList(compareChar, &(char){'D'}, testList) == -1));
// Test 15:
appendToList(PLAYER, testList, &testDataA);
TEST("Checking first of data in the list",
(getFirstFromList(compareChar, &testDataA, testList) == testList->head));
// Test 16:
TEST("Checking first of data NOT in the list",
(getFirstFromList(compareChar, &(char){'D'}, testList) == NULL));
FINISH_TESTING;
}
// ==================================================
// | End of lists-test.c, copyright notice follows. |
// ==================================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

43
source/tests/tests.h Normal file
View File

@ -0,0 +1,43 @@
// =========================================
// | SilverMUD Tests - tests.h |
// | Copyright (C) 2023, Barra Ó Catháin |
// | See end of file for copyright notice. |
// =========================================
#ifndef SILVERMUD_TESTS
#define SILVERMUD_TESTS
#define SETUP_TESTS \
size_t TEST_SUCCESSES = 0, TEST_FAILURES = 0, TEST_COUNT;
#define TEST(STRING, CONDITION) \
printf("Test %zu: ", ++TEST_COUNT); \
printf(STRING); \
if (CONDITION) { printf("... SUCCESS!\n"); TEST_SUCCESSES++; } \
else { printf("... FAILED.\n"); TEST_FAILURES++; }
#define FINISH_TESTING \
printf("=====\n\nSuccesses: %zu | Failures: %zu\n", TEST_SUCCESSES, TEST_FAILURES); \
if (TEST_FAILURES == 0) \
{ \
printf("All good! We're done here.\n"); \
exit(EXIT_SUCCESS); \
} \
exit(EXIT_FAILURE);
#endif
// =============================================
// | End of tests.h, copyright notice follows. |
// =============================================
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

View File