From 45eb0feb7864139b3e5109a0da2986b37fb25f1e Mon Sep 17 00:00:00 2001 From: Gene Stark Date: Sat, 16 Apr 2022 17:19:19 -0400 Subject: [PATCH 1/2] Bring in basecode from development repo. --- .gitignore | 7 + .gitlab-ci.yml | 29 ++ hw5/Makefile | 81 +++++ hw5/demo/pbx | Bin 0 -> 26872 bytes hw5/hw5.sublime-project | 46 +++ hw5/include/debug.h | 88 +++++ hw5/include/pbx.h | 52 +++ hw5/include/server.h | 49 +++ hw5/include/tu.h | 52 +++ hw5/lib/pbx.a | Bin 0 -> 23050 bytes hw5/src/globals.c | 35 ++ hw5/src/main.c | 44 +++ hw5/src/pbx.c | 91 +++++ hw5/src/server.c | 21 ++ hw5/src/tu.c | 205 ++++++++++++ hw5/tests/__test_includes.h | 41 +++ hw5/tests/basecode_tests.c | 185 +++++++++++ hw5/tests/script_tester.c | 642 ++++++++++++++++++++++++++++++++++++ 18 files changed, 1668 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 hw5/Makefile create mode 100755 hw5/demo/pbx create mode 100644 hw5/hw5.sublime-project create mode 100644 hw5/include/debug.h create mode 100644 hw5/include/pbx.h create mode 100644 hw5/include/server.h create mode 100644 hw5/include/tu.h create mode 100644 hw5/lib/pbx.a create mode 100644 hw5/src/globals.c create mode 100644 hw5/src/main.c create mode 100644 hw5/src/pbx.c create mode 100644 hw5/src/server.c create mode 100644 hw5/src/tu.c create mode 100644 hw5/tests/__test_includes.h create mode 100644 hw5/tests/basecode_tests.c create mode 100644 hw5/tests/script_tester.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3fbca3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*~ +#* +*.d +*.o +*.out +bin/* +build/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e8dd157 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,29 @@ +image: hwrunner:latest +variables: + GIT_SSL_NO_VERIFY: "true" + EXEC: pbx + HW_DIR: hw5 + CPU_LIMIT: 60 + FILE_LIMIT: 1000000 +before_script: + - make clean all -C ${HW_DIR} +stages: + - build + - run + - test +build: + stage: build + script: + - echo "Build done" +run: + stage: run + script: + - ulimit -t ${CPU_LIMIT} + - ulimit -f ${FILE_LIMIT} + - cd ${HW_DIR} && bin/${EXEC} +test: + stage: test + script: + - ulimit -t ${CPU_LIMIT} + - ulimit -f ${FILE_LIMIT} + - cd ${HW_DIR} && bin/${EXEC}_tests -S --verbose=0 -j1 --timeout 50 diff --git a/hw5/Makefile b/hw5/Makefile new file mode 100644 index 0000000..e31789a --- /dev/null +++ b/hw5/Makefile @@ -0,0 +1,81 @@ +CC := gcc +SRCD := src +TSTD := tests +BLDD := build +BIND := bin +INCD := include +LIBD := lib +UTILD := util + +MAIN := $(BLDD)/main.o +LIB := $(LIBD)/pbx.a +LIB_DB := $(LIBD)/pbx.a + +ALL_SRCF := $(shell find $(SRCD) -type f -name *.c) +ALL_OBJF := $(patsubst $(SRCD)/%,$(BLDD)/%,$(ALL_SRCF:.c=.o)) +ALL_FUNCF := $(filter-out $(MAIN), $(ALL_OBJF)) + +TEST_SRC := $(shell find $(TSTD) -type f -name *.c) + +INC := -I $(INCD) + +CFLAGS := -Wall -Werror -Wno-unused-function -Wno-error=switch -MMD +DFLAGS := -g -DDEBUG -DCOLOR +PRINT_STAMENTS := -DERROR -DSUCCESS -DWARN -DINFO + +STD := -std=gnu11 +TEST_LIB := -lcriterion +LIBS := $(LIB) -lpthread +LIBS_DB := $(LIB_DB) -lpthread +EXCLUDES := excludes.h + +CFLAGS += $(STD) -DTEST_CONFIG_C + +EXEC := pbx +TEST_EXEC := $(EXEC)_tests + +.PHONY: clean all setup debug + +all: setup $(BIND)/$(EXEC) $(INCD)/$(EXCLUDES) $(BIND)/$(TEST_EXEC) + +debug: CFLAGS += $(DFLAGS) $(PRINT_STAMENTS) +debug: all + +tester: $(UTILD)/tester + +setup: $(BIND) $(BLDD) +$(BIND): + mkdir -p $(BIND) +$(BLDD): + mkdir -p $(BLDD) + +$(UTILD)/tester: $(UTILD)/tester.c src/globals.c + $(CC) $(DFLAGS) $(INC) $^ -o $@ + +$(BIND)/$(EXEC): $(MAIN) $(ALL_FUNCF) + $(CC) $^ -o $@ $(LIBS) + +$(BIND)/$(TEST_EXEC): $(ALL_FUNCF) $(TEST_SRC) + $(CC) $(CFLAGS) $(INC) $(ALL_FUNCF) $(TEST_SRC) $(TEST_LIB) $(LIBS) -o $@ + +$(BLDD)/%.o: $(SRCD)/%.c + $(CC) $(CFLAGS) $(INC) -c -o $@ $< + +clean: + rm -rf $(BLDD) $(BIND) + +$(INCD)/$(EXCLUDES): $(BIND)/$(EXEC) + rm -f $@ + touch $@ + if nm $(BIND)/$(EXEC) | grep INSTRUCTOR_MAIN > /dev/null; then \ + echo "#define NO_MAIN" >> $@; \ + fi + if nm $(BIND)/$(EXEC) | grep INSTRUCTOR_SERVER > /dev/null; then \ + echo "#define NO_SERVER" >> $@; \ + fi + if nm $(BIND)/$(EXEC) | grep INSTRUCTOR_PBX > /dev/null; then \ + echo "#define NO_PBX" >> $@; \ + fi + +.PRECIOUS: $(BLDD)/*.d +-include $(BLDD)/*.d diff --git a/hw5/demo/pbx b/hw5/demo/pbx new file mode 100755 index 0000000000000000000000000000000000000000..ef31d4ad212fee17b1927f07508acb851ef47200 GIT binary patch literal 26872 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 E1)gXsy#N3J literal 0 HcmV?d00001 diff --git a/hw5/hw5.sublime-project b/hw5/hw5.sublime-project new file mode 100644 index 0000000..e7b6e29 --- /dev/null +++ b/hw5/hw5.sublime-project @@ -0,0 +1,46 @@ +{ + "folders": + [ + { + "path":".", + "name":"Project Base" + }, + { + "path": "src", + "name": "C Source", + "follow_symlinks": false, + "file_include_patterns":["*.c"], + }, + { + "path": "include", + "name": "C Headers", + "follow_symlinks": false, + "file_include_patterns":["*.h"], + }, + { + "path": "tests", + "name": "Tests", + } + ], + "settings": + { + }, + "build_systems": + [ + { + "name": "Release (full build)", + "working_dir":"$project_path", + "shell_cmd": "make clean all", + }, + { + "name": "Debug (full build)", + "working_dir":"$project_path", + "shell_cmd": "make clean debug", + }, + { + "name": "Test", + "working_dir":"$project_path", + "shell_cmd": "bin/${project_base_name}_tests}", + } + ] +} diff --git a/hw5/include/debug.h b/hw5/include/debug.h new file mode 100644 index 0000000..e8fc8b6 --- /dev/null +++ b/hw5/include/debug.h @@ -0,0 +1,88 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#include + +#define NL "\n" + +#ifdef COLOR +#define KNRM "\033[0m" +#define KRED "\033[1;31m" +#define KGRN "\033[1;32m" +#define KYEL "\033[1;33m" +#define KBLU "\033[1;34m" +#define KMAG "\033[1;35m" +#define KCYN "\033[1;36m" +#define KWHT "\033[1;37m" +#define KBWN "\033[0;33m" +#else +#define KNRM "" +#define KRED "" +#define KGRN "" +#define KYEL "" +#define KBLU "" +#define KMAG "" +#define KCYN "" +#define KWHT "" +#define KBWN "" +#endif + +#ifdef VERBOSE +#define DEBUG +#define INFO +#define WARN +#define ERROR +#define SUCCESS +#endif + +#ifdef DEBUG +#define debug(S, ...) \ + do { \ + fprintf(stderr, KMAG "DEBUG: %s:%s:%d " KNRM S NL, __FILE__, \ + __extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +#define debug(S, ...) +#endif + +#ifdef INFO +#define info(S, ...) \ + do { \ + fprintf(stderr, KBLU "INFO: %s:%s:%d " KNRM S NL, __FILE__, \ + __extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +#define info(S, ...) +#endif + +#ifdef WARN +#define warn(S, ...) \ + do { \ + fprintf(stderr, KYEL "WARN: %s:%s:%d " KNRM S NL, __FILE__, \ + __extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +#define warn(S, ...) +#endif + +#ifdef SUCCESS +#define success(S, ...) \ + do { \ + fprintf(stderr, KGRN "SUCCESS: %s:%s:%d " KNRM S NL, __FILE__, \ + __extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +#define success(S, ...) +#endif + +#ifdef ERROR +#define error(S, ...) \ + do { \ + fprintf(stderr, KRED "ERROR: %s:%s:%d " KNRM S NL, __FILE__, \ + __extension__ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + } while (0) +#else +#define error(S, ...) +#endif + +#endif /* DEBUG_H */ diff --git a/hw5/include/pbx.h b/hw5/include/pbx.h new file mode 100644 index 0000000..8157e73 --- /dev/null +++ b/hw5/include/pbx.h @@ -0,0 +1,52 @@ +/** + * === DO NOT MODIFY THIS FILE === + * If you need some other prototypes or constants in a header, please put them + * in another header file. + * + * When we grade, we will be replacing this file with our own copy. + * You have been warned. + * === DO NOT MODIFY THIS FILE === + */ +#ifndef PBX_H +#define PBX_H + +#include +#include + +#include "tu.h" + +/* + * Structure types representing objects manipulated by the PBX module. + * + * PBX: Represents the current state of a private branch exchange. + * TU: Represents the current state of a telephone unit. + * + * NOTE: These types are "opaque": the actual structure definitions are not + * given here and it is not intended that a client of the PBX module should + * know what they are. The actual structure definitions are local to the + * implementation of the PBX module and are not exported. + */ +typedef struct pbx PBX; + +/* + * Maximum number of extensions supported by a PBX. + */ +#define PBX_MAX_EXTENSIONS FD_SETSIZE + +/* + * End-of-line sequence used in communication with client. + */ +#define EOL "\r\n" + +/* + * Global variable that provides access to the PBX instance. + */ +extern PBX *pbx; + +PBX *pbx_init(); +void pbx_shutdown(PBX *pbx); +int pbx_register(PBX *pbx, TU *tu, int ext); +int pbx_unregister(PBX *pbx, TU *tu); +int pbx_dial(PBX *pbx, TU *tu, int ext); + +#endif diff --git a/hw5/include/server.h b/hw5/include/server.h new file mode 100644 index 0000000..0e26f78 --- /dev/null +++ b/hw5/include/server.h @@ -0,0 +1,49 @@ +/** + * === DO NOT MODIFY THIS FILE === + * If you need some other prototypes or constants in a header, please put them + * in another header file. + * + * When we grade, we will be replacing this file with our own copy. + * You have been warned. + * === DO NOT MODIFY THIS FILE === + */ +#ifndef SERVER_H +#define SERVER_H + +/* + * Definitions of the commands that can be issued by a client. + */ +typedef enum tu_command { + TU_PICKUP_CMD, TU_HANGUP_CMD, TU_DIAL_CMD, TU_CHAT_CMD, + // Below are special values used in grading tests. + TU_NO_CMD = 100, TU_CONNECT_CMD = 101, TU_DISCONNECT_CMD = 102, + TU_AWAIT_CMD = 103, TU_DELAY_CMD = 104, TU_EOF_CMD = 105 +} TU_COMMAND; + +/* + * Array that specifies a printable name for each of the commands that + * can be issued to a TU by a client. These names should be used when + * parsing commands received from a client. They may also be used for + * debugging purposes. + */ +extern char *tu_command_names[]; + +/* + * Thread function for the thread that handles a particular client. + * + * @param Pointer to a variable that holds the file descriptor for + * the client connection. This variable must be freed once the file + * descriptor has been retrieved. + * @return NULL + * + * This function executes a "service loop" that receives messages from + * the client and dispatches to appropriate functions to carry out + * the client's requests. The service loop ends when the network connection + * shuts down and EOF is seen. This could occur either as a result of the + * client explicitly closing the connection, a timeout in the network causing + * the connection to be closed, or the main thread of the server shutting + * down the connection as part of graceful termination. + */ +void *pbx_client_service(void *arg); + +#endif diff --git a/hw5/include/tu.h b/hw5/include/tu.h new file mode 100644 index 0000000..0b2e7f3 --- /dev/null +++ b/hw5/include/tu.h @@ -0,0 +1,52 @@ +/** + * === DO NOT MODIFY THIS FILE === + * If you need some other prototypes or constants in a header, please put them + * in another header file. + * + * When we grade, we will be replacing this file with our own copy. + * You have been warned. + * === DO NOT MODIFY THIS FILE === + */ +#ifndef TU_H +#define TU_H + +/* + * Structure types representing objects manipulated by the TU module. + * + * TU: Represents the current state of a telephone unit. + * + * NOTE: These types are "opaque": the actual structure definitions are not + * given here and it is not intended that a client of the TU module should + * know what they are. The actual structure definitions are local to the + * implementation of the TU module and are not exported. + */ +typedef struct tu TU; + +/* + * The possible states that a TU can be in. + */ +typedef enum tu_state { + TU_ON_HOOK, TU_RINGING, TU_DIAL_TONE, TU_RING_BACK, TU_BUSY_SIGNAL, + TU_CONNECTED, TU_ERROR +} TU_STATE; + +/* + * Array that specifies a printable name for each of the TU states. + * These names should be used when sending state-change notifications to + * the underlying network clients. They may also be used for debugging + * purposes. + */ +extern char *tu_state_names[]; + +TU *tu_init(int fd); +void tu_ref(TU *tu, char *reason); +void tu_unref(TU *tu, char *reason); +int tu_fileno(TU *tu); +int tu_extension(TU *tu); +int tu_set_extension(TU *tu, int ext); +int tu_pickup(TU *tu); +int tu_hangup(TU *tu); +int tu_dial(TU *tu, TU *target); +int tu_chat(TU *tu, char *msg); + +#endif diff --git a/hw5/lib/pbx.a b/hw5/lib/pbx.a new file mode 100644 index 0000000000000000000000000000000000000000..929ac31459f0582f4045b0b4935711db019b1db7 GIT binary patch literal 23050 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 F{||rM&z1lH literal 0 HcmV?d00001 diff --git a/hw5/src/globals.c b/hw5/src/globals.c new file mode 100644 index 0000000..3668061 --- /dev/null +++ b/hw5/src/globals.c @@ -0,0 +1,35 @@ +/** + * === DO NOT MODIFY THIS FILE === + * If you need some other prototypes or constants in a header, please put them + * in another header file. + * + * When we grade, we will be replacing this file with our own copy. + * You have been warned. + * === DO NOT MODIFY THIS FILE === + */ + +#include "pbx.h" +#include "server.h" + +char *tu_state_names[] = { + [TU_ON_HOOK] "ON HOOK", + [TU_RINGING] "RINGING", + [TU_DIAL_TONE] "DIAL TONE", + [TU_RING_BACK] "RING BACK", + [TU_BUSY_SIGNAL] "BUSY SIGNAL", + [TU_CONNECTED] "CONNECTED", + [TU_ERROR] "ERROR" +}; + +char *tu_command_names[] = { + [TU_PICKUP_CMD] "pickup", + [TU_HANGUP_CMD] "hangup", + [TU_DIAL_CMD] "dial", + [TU_CHAT_CMD] "chat" +}; + +/* + * Object that maintains the state of the Private Branch Exchange (PBX). + */ +PBX *pbx; + diff --git a/hw5/src/main.c b/hw5/src/main.c new file mode 100644 index 0000000..b32c9ab --- /dev/null +++ b/hw5/src/main.c @@ -0,0 +1,44 @@ +#include +#include + +#include "pbx.h" +#include "server.h" +#include "debug.h" + +static void terminate(int status); + +/* + * "PBX" telephone exchange simulation. + * + * Usage: pbx + */ +int main(int argc, char* argv[]){ + // Option processing should be performed here. + // Option '-p ' is required in order to specify the port number + // on which the server should listen. + + // Perform required initialization of the PBX module. + debug("Initializing PBX..."); + pbx = pbx_init(); + + // TODO: Set up the server socket and enter a loop to accept connections + // on this socket. For each connection, a thread should be started to + // run function pbx_client_service(). In addition, you should install + // a SIGHUP handler, so that receipt of SIGHUP will perform a clean + // shutdown of the server. + + fprintf(stderr, "You have to finish implementing main() " + "before the PBX server will function.\n"); + + terminate(EXIT_FAILURE); +} + +/* + * Function called to cleanly shut down the server. + */ +static void terminate(int status) { + debug("Shutting down PBX..."); + pbx_shutdown(pbx); + debug("PBX server terminating"); + exit(status); +} diff --git a/hw5/src/pbx.c b/hw5/src/pbx.c new file mode 100644 index 0000000..10232b3 --- /dev/null +++ b/hw5/src/pbx.c @@ -0,0 +1,91 @@ +/* + * PBX: simulates a Private Branch Exchange. + */ +#include + +#include "pbx.h" +#include "debug.h" + +/* + * Initialize a new PBX. + * + * @return the newly initialized PBX, or NULL if initialization fails. + */ +#if 0 +PBX *pbx_init() { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Shut down a pbx, shutting down all network connections, waiting for all server + * threads to terminate, and freeing all associated resources. + * If there are any registered extensions, the associated network connections are + * shut down, which will cause the server threads to terminate. + * Once all the server threads have terminated, any remaining resources associated + * with the PBX are freed. The PBX object itself is freed, and should not be used again. + * + * @param pbx The PBX to be shut down. + */ +#if 0 +void pbx_shutdown(PBX *pbx) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Register a telephone unit with a PBX at a specified extension number. + * This amounts to "plugging a telephone unit into the PBX". + * The TU is initialized to the TU_ON_HOOK state. + * The reference count of the TU is increased and the PBX retains this reference + *for as long as the TU remains registered. + * A notification of the assigned extension number is sent to the underlying network + * client. + * + * @param pbx The PBX registry. + * @param tu The TU to be registered. + * @param ext The extension number on which the TU is to be registered. + * @return 0 if registration succeeds, otherwise -1. + */ +#if 0 +int pbx_register(PBX *pbx, TU *tu, int ext) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Unregister a TU from a PBX. + * This amounts to "unplugging a telephone unit from the PBX". + * The TU is disassociated from its extension number. + * Then a hangup operation is performed on the TU to cancel any + * call that might be in progress. + * Finally, the reference held by the PBX to the TU is released. + * + * @param pbx The PBX. + * @param tu The TU to be unregistered. + * @return 0 if unregistration succeeds, otherwise -1. + */ +#if 0 +int pbx_unregister(PBX *pbx, TU *tu) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Use the PBX to initiate a call from a specified TU to a specified extension. + * + * @param pbx The PBX registry. + * @param tu The TU that is initiating the call. + * @param ext The extension number to be called. + * @return 0 if dialing succeeds, otherwise -1. + */ +#if 0 +int pbx_dial(PBX *pbx, TU *tu, int ext) { + // TO BE IMPLEMENTED + abort(); +} +#endif diff --git a/hw5/src/server.c b/hw5/src/server.c new file mode 100644 index 0000000..8dc6dd1 --- /dev/null +++ b/hw5/src/server.c @@ -0,0 +1,21 @@ +/* + * "PBX" server module. + * Manages interaction with a client telephone unit (TU). + */ +#include + +#include "debug.h" +#include "pbx.h" +#include "server.h" + +/* + * Thread function for the thread that handles interaction with a client TU. + * This is called after a network connection has been made via the main server + * thread and a new thread has been created to handle the connection. + */ +#if 0 +void *pbx_client_service(void *arg) { + // TO BE IMPLEMENTED + abort(); +} +#endif diff --git a/hw5/src/tu.c b/hw5/src/tu.c new file mode 100644 index 0000000..580b3cd --- /dev/null +++ b/hw5/src/tu.c @@ -0,0 +1,205 @@ +/* + * TU: simulates a "telephone unit", which interfaces a client with the PBX. + */ +#include + +#include "pbx.h" +#include "debug.h" + +/* + * Initialize a TU + * + * @param fd The file descriptor of the underlying network connection. + * @return The TU, newly initialized and in the TU_ON_HOOK state, if initialization + * was successful, otherwise NULL. + */ +#if 0 +TU *tu_init(int fd) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Increment the reference count on a TU. + * + * @param tu The TU whose reference count is to be incremented + * @param reason A string describing the reason why the count is being incremented + * (for debugging purposes). + */ +#if 0 +void tu_ref(TU *tu, char *reason) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Decrement the reference count on a TU, freeing it if the count becomes 0. + * + * @param tu The TU whose reference count is to be decremented + * @param reason A string describing the reason why the count is being decremented + * (for debugging purposes). + */ +#if 0 +void tu_unref(TU *tu, char *reason) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Get the file descriptor for the network connection underlying a TU. + * This file descriptor should only be used by a server to read input from + * the connection. Output to the connection must only be performed within + * the PBX functions. + * + * @param tu + * @return the underlying file descriptor, if any, otherwise -1. + */ +#if 0 +int tu_fileno(TU *tu) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Get the extension number for a TU. + * This extension number is assigned by the PBX when a TU is registered + * and it is used to identify a particular TU in calls to tu_dial(). + * The value returned might be the same as the value returned by tu_fileno(), + * but is not necessarily so. + * + * @param tu + * @return the extension number, if any, otherwise -1. + */ +#if 0 +int tu_extension(TU *tu) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Set the extension number for a TU. + * A notification is set to the client of the TU. + * This function should be called at most once one any particular TU. + * + * @param tu The TU whose extension is being set. + */ +#if 0 +int tu_set_extension(TU *tu, int ext) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Initiate a call from a specified originating TU to a specified target TU. + * If the originating TU is not in the TU_DIAL_TONE state, then there is no effect. + * If the target TU is the same as the originating TU, then the TU transitions + * to the TU_BUSY_SIGNAL state. + * If the target TU already has a peer, or the target TU is not in the TU_ON_HOOK + * state, then the originating TU transitions to the TU_BUSY_SIGNAL state. + * Otherwise, the originating TU and the target TU are recorded as peers of each other + * (this causes the reference count of each of them to be incremented), + * the target TU transitions to the TU_RINGING state, and the originating TU + * transitions to the TU_RING_BACK state. + * + * In all cases, a notification of the resulting state of the originating TU is sent to + * to the associated network client. If the target TU has changed state, then its client + * is also notified of its new state. + * + * If the caller of this function was unable to determine a target TU to be called, + * it will pass NULL as the target TU. In this case, the originating TU will transition + * to the TU_ERROR state if it was in the TU_DIAL_TONE state, and there will be no + * effect otherwise. This situation is handled here, rather than in the caller, + * because here we have knowledge of the current TU state and we do not want to introduce + * the possibility of transitions to a TU_ERROR state from arbitrary other states, + * especially in states where there could be a peer TU that would have to be dealt with. + * + * @param tu The originating TU. + * @param target The target TU, or NULL if the caller of this function was unable to + * identify a TU to be dialed. + * @return 0 if successful, -1 if any error occurs that results in the originating + * TU transitioning to the TU_ERROR state. + */ +#if 0 +int tu_dial(TU *tu, TU *target) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Take a TU receiver off-hook (i.e. pick up the handset). + * If the TU is in neither the TU_ON_HOOK state nor the TU_RINGING state, + * then there is no effect. + * If the TU is in the TU_ON_HOOK state, it goes to the TU_DIAL_TONE state. + * If the TU was in the TU_RINGING state, it goes to the TU_CONNECTED state, + * reflecting an answered call. In this case, the calling TU simultaneously + * also transitions to the TU_CONNECTED state. + * + * In all cases, a notification of the resulting state of the specified TU is sent to + * to the associated network client. If a peer TU has changed state, then its client + * is also notified of its new state. + * + * @param tu The TU that is to be picked up. + * @return 0 if successful, -1 if any error occurs that results in the originating + * TU transitioning to the TU_ERROR state. + */ +#if 0 +int tu_pickup(TU *tu) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * Hang up a TU (i.e. replace the handset on the switchhook). + * + * If the TU is in the TU_CONNECTED or TU_RINGING state, then it goes to the + * TU_ON_HOOK state. In addition, in this case the peer TU (the one to which + * the call is currently connected) simultaneously transitions to the TU_DIAL_TONE + * state. + * If the TU was in the TU_RING_BACK state, then it goes to the TU_ON_HOOK state. + * In addition, in this case the calling TU (which is in the TU_RINGING state) + * simultaneously transitions to the TU_ON_HOOK state. + * If the TU was in the TU_DIAL_TONE, TU_BUSY_SIGNAL, or TU_ERROR state, + * then it goes to the TU_ON_HOOK state. + * + * In all cases, a notification of the resulting state of the specified TU is sent to + * to the associated network client. If a peer TU has changed state, then its client + * is also notified of its new state. + * + * @param tu The tu that is to be hung up. + * @return 0 if successful, -1 if any error occurs that results in the originating + * TU transitioning to the TU_ERROR state. + */ +#if 0 +int tu_hangup(TU *tu) { + // TO BE IMPLEMENTED + abort(); +} +#endif + +/* + * "Chat" over a connection. + * + * If the state of the TU is not TU_CONNECTED, then nothing is sent and -1 is returned. + * Otherwise, the specified message is sent via the network connection to the peer TU. + * In all cases, the states of the TUs are left unchanged and a notification containing + * the current state is sent to the TU sending the chat. + * + * @param tu The tu sending the chat. + * @param msg The message to be sent. + * @return 0 If the chat was successfully sent, -1 if there is no call in progress + * or some other error occurs. + */ +#if 0 +int tu_chat(TU *tu, char *msg) { + // TO BE IMPLEMENTED + abort(); +} +#endif diff --git a/hw5/tests/__test_includes.h b/hw5/tests/__test_includes.h new file mode 100644 index 0000000..82441e1 --- /dev/null +++ b/hw5/tests/__test_includes.h @@ -0,0 +1,41 @@ +#include "pbx.h" +#include "server.h" + +#define QUOTE1(x) #x +#define QUOTE(x) QUOTE1(x) +#define SCRIPT1(x) x##_script +#define SCRIPT(x) SCRIPT1(x) + +#define SERVER_PORT 9999 +#define SERVER_PORT_STR "9999" +#define SERVER_HOSTNAME "localhost" + +#define NUM_STATES 7 +#define NUM_COMMANDS 5 +#define DELAY_COMMAND (NUM_COMMANDS-1) + +#define ZERO_SEC { 0, 0 } +#define ONE_USEC { 0, 1 } +#define ONE_MSEC { 0, 1000 } +#define TEN_MSEC { 0, 10000 } +#define FTY_MSEC { 0, 50000 } +#define HND_MSEC { 0, 100000 } +#define QTR_SEC { 0, 250000 } +#define ONE_SEC { 1, 0 } + +#define SERVER_STARTUP_SLEEP 1 +#define SERVER_SHUTDOWN_SLEEP 1 + +/* + * Structure describing a single step in a test script. + */ +typedef struct test_step { + int id; // Index of TU performing test, or -1 if end. + TU_COMMAND command; // Command to send. + int id_to_dial; // ID of TU to dial, for TU_DIAL command. + TU_STATE response; // Expected response. + struct timeval timeout; // Limit on time to wait for response (zero for no limit) + // or time to delay. +} TEST_STEP; + +int run_test_script(char *name, TEST_STEP *scr, int port); diff --git a/hw5/tests/basecode_tests.c b/hw5/tests/basecode_tests.c new file mode 100644 index 0000000..5de8bf9 --- /dev/null +++ b/hw5/tests/basecode_tests.c @@ -0,0 +1,185 @@ +/* + * Important: the Criterion tests in this file have to be run with -j1, + * because they each start a separate server instance and if they are + * run concurrently only one server will be able to bind the server port + * and the others will fail. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "__test_includes.h" + +static int server_pid; + +static void wait_for_server() { + int ret; + int i = 0; + do { + fprintf(stderr, "Waiting for server to start (i = %d)\n", i); + ret = system("netstat -an | grep 'LISTEN[ ]*$' | grep ':"SERVER_PORT_STR"'"); + sleep(SERVER_STARTUP_SLEEP); + } while(++i < 30 && WEXITSTATUS(ret)); +} + +static void wait_for_no_server() { + int ret; + do { + ret = system("netstat -an | grep 'LISTEN[ ]*$' | grep ':"SERVER_PORT_STR"'"); + if(WEXITSTATUS(ret) == 0) { + fprintf(stderr, "Waiting for server port to clear...\n"); + system("killall -s KILL pbx > /dev/null"); + sleep(1); + } else { + break; + } + } while(1); +} + +static void init() { + server_pid = 0; + wait_for_no_server(); + fprintf(stderr, "***Starting server..."); + if((server_pid = fork()) == 0) { + execlp("bin/pbx", "pbx", "-p", SERVER_PORT_STR, NULL); + fprintf(stderr, "Failed to exec server\n"); + abort(); + } + fprintf(stderr, "pid = %d\n", server_pid); + // Wait for server to start before returning + wait_for_server(); +} + +static void fini(int chk) { + int ret; + cr_assert(server_pid != 0, "No server was started!\n"); + fprintf(stderr, "***Sending SIGHUP to server pid %d\n", server_pid); + kill(server_pid, SIGHUP); + sleep(SERVER_SHUTDOWN_SLEEP); + kill(server_pid, SIGKILL); + wait(&ret); + fprintf(stderr, "***Server wait() returned = 0x%x\n", ret); + if(chk) { + if(WIFSIGNALED(ret)) + cr_assert_fail("***Server terminated ungracefully with signal %d\n", WTERMSIG(ret)); + cr_assert_eq(WEXITSTATUS(ret), 0, "Server exit status was not 0"); + } +} + +static void killall() { + system("killall -s KILL pbx /usr/lib/valgrind/memcheck-amd64-linux > /dev/null 2>&1"); +} + + +#define SUITE basecode_suite + +#define TEST_NAME connect_disconnect_test +static TEST_STEP SCRIPT(TEST_NAME)[] = { + // ID, COMMAND, ID_TO_DIAL, RESPONSE, TIMEOUT + { 0, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { -1, -1, -1, -1, ZERO_SEC } +}; + +Test(SUITE, TEST_NAME, .init = init, .fini = killall, .timeout = 30) +{ + char *name = QUOTE(SUITE)"/"QUOTE(TEST_NAME); + int ret = run_test_script(name, SCRIPT(TEST_NAME), SERVER_PORT); + cr_assert_eq(ret, 0, "expected %d, was %d\n", 0, ret); + fini(0); +} +#undef TEST_NAME + +#define TEST_NAME connect_disconnect2_test +static TEST_STEP SCRIPT(TEST_NAME)[] = { + // ID, COMMAND, ID_TO_DIAL, RESPONSE, TIMEOUT + { 0, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 1, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { 1, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { -1, -1, -1, -1, ZERO_SEC } +}; + +Test(SUITE, TEST_NAME, .init = init, .fini = killall, .timeout = 30) { + char *name = QUOTE(SUITE)"/"QUOTE(TEST_NAME); + int ret = run_test_script(name, SCRIPT(TEST_NAME), SERVER_PORT); + cr_assert_eq(ret, 0, "expected %d, was %d\n", 0, ret); + fini(0); +} +#undef TEST_NAME + +#define TEST_NAME pickup_hangup_test +static TEST_STEP SCRIPT(TEST_NAME)[] = { + // ID, COMMAND, ID_TO_DIAL, RESPONSE, TIMEOUT + { 0, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 0, TU_PICKUP_CMD, -1, TU_DIAL_TONE, TEN_MSEC }, + { 0, TU_HANGUP_CMD, -1, TU_ON_HOOK, TEN_MSEC }, + { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { -1, -1, -1, -1, ZERO_SEC } +}; + +Test(SUITE, TEST_NAME, .init = init, .fini = killall, .timeout = 30) { + char *name = QUOTE(SUITE)"/"QUOTE(TEST_NAME); + int ret = run_test_script(name, SCRIPT(TEST_NAME), SERVER_PORT); + cr_assert_eq(ret, 0, "expected %d, was %d\n", 0, ret); + fini(0); +} +#undef TEST_NAME + +#define TEST_NAME dial_answer_test +static TEST_STEP SCRIPT(TEST_NAME)[] = { + // ID, COMMAND, ID_TO_DIAL, RESPONSE, TIMEOUT + { 0, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 1, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 0, TU_PICKUP_CMD, -1, TU_DIAL_TONE, TEN_MSEC }, + { 0, TU_DIAL_CMD, 1, TU_RING_BACK, TEN_MSEC }, + { 1, TU_PICKUP_CMD, -1, TU_CONNECTED, TEN_MSEC }, + { 0, TU_HANGUP_CMD, -1, TU_ON_HOOK, FTY_MSEC }, + { 1, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { -1, -1, -1, -1, ZERO_SEC } +}; + +Test(SUITE, TEST_NAME, .init = init, .fini = killall, .timeout = 30) { + char *name = QUOTE(SUITE)"/"QUOTE(TEST_NAME); + int ret = run_test_script(name, SCRIPT(TEST_NAME), SERVER_PORT); + cr_assert_eq(ret, 0, "expected %d, was %d\n", 0, ret); + fini(0); +} +#undef TEST_NAME + +#define TEST_NAME dial_disconnect_test +static TEST_STEP SCRIPT(TEST_NAME)[] = { + // ID, COMMAND, ID_TO_DIAL, RESPONSE, TIMEOUT + { 0, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 1, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, + { 0, TU_PICKUP_CMD, -1, TU_DIAL_TONE, TEN_MSEC }, + { 0, TU_DIAL_CMD, 1, TU_RING_BACK, TEN_MSEC }, + { 1, TU_PICKUP_CMD, -1, TU_CONNECTED, TEN_MSEC }, + { 1, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { 0, TU_HANGUP_CMD, -1, TU_ON_HOOK, FTY_MSEC }, + { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, + { -1, -1, -1, -1, ZERO_SEC } +}; + +Test(SUITE, TEST_NAME, .init = init, .fini = killall, .timeout = 30) { + char *name = QUOTE(SUITE)"/"QUOTE(TEST_NAME); + int ret = run_test_script(name, SCRIPT(TEST_NAME), SERVER_PORT); + cr_assert_eq(ret, 0, "expected %d, was %d\n", 0, ret); + fini(0); +} +#undef TEST_NAME diff --git a/hw5/tests/script_tester.c b/hw5/tests/script_tester.c new file mode 100644 index 0000000..7b1f72f --- /dev/null +++ b/hw5/tests/script_tester.c @@ -0,0 +1,642 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pbx.h" +#include "server.h" +#include "__test_includes.h" +#include "debug.h" + +#define NUM_STATES 7 +#define NUM_COMMANDS 5 +#define DELAY_COMMAND (NUM_COMMANDS-1) + +/* + * Table of expected next states. + * Each entry is a bitmap that specifies a set of possible next states, given + * the current state and the last command that was issued. + * + * An issue that this tester has to handle is that commands to the server can + * "cross in transit" asynchronous state-change notifications coming back from the server. + * If we are currently in the TU_ON_HOOK state and we send a TU_PICKUP_CMD, it might + * be that the TU_PICKUP_CMD crosses in transit a TU_RINGING notification being sent + * back to us. What we will see is a next-state notification of TU_RINGING, rather + * than the TU_DIAL_TONE notification that we would otherwise expect. + * + * To handle this, there are two classes of expected states encoded in each entry of + * the table. The "normal case" encodes a TU_STATE s as the bit value 1<id != -1) { + if(ts->id >= MAX_TUS) { + fprintf(stderr, "Script error: TU ID %d too large (>= %d)\n", ts->id, MAX_TUS); + return -1; + } + int cmd = ts->command; + int ext = -1; + TU *tu = &tus[ts->id]; + + // First, deal with performing any explicit action. + switch(ts->command) { + // Meta-commands + case TU_NO_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) TU_NO_CMD\n", timestamp(), TU_ID(tu), ts - scr); + break; + case TU_CONNECT_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) TU_CONNECT_CMD\n", timestamp(), TU_ID(tu), ts - scr); + if(tu->infd) { + fprintf(stderr, "%s: [%ld] Test error: already connected\n", + timestamp(), TU_ID(tu)); + return -1; + } + // Otherwise connect to server and update state. + if(connect_command(tu, port) == -1) + return -1; + break; + case TU_DISCONNECT_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) TU_DISCONNECT_CMD\n", timestamp(), TU_ID(tu), ts - scr); + if(!tu->infd) { + fprintf(stderr, "%s: [%ld] Test error: not connected\n", timestamp(), TU_ID(tu)); + return -1; + } + disconnect_command(tu); + break; + case TU_DELAY_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) TU_DELAY_CMD\n", timestamp(), TU_ID(tu), ts - scr); + // Pause for the specified amount of time. + tms.tv_sec = ts->timeout.tv_sec; + tms.tv_nsec = ts->timeout.tv_usec * 1000l; + nanosleep(&tms, NULL); + break; + case TU_AWAIT_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) TU_AWAIT_CMD\n", timestamp(), TU_ID(tu), ts - scr); + // Process incoming messages until specified state seen + // or timeout occurs. + break; + + // Real commands + case TU_PICKUP_CMD: + case TU_HANGUP_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) %s\n", + timestamp(), TU_ID(tu), ts - scr, tu_command_names[cmd]); + fprintf(tu->out, "%s%s", tu_command_names[cmd], EOL); + fflush(tu->out); + break; + case TU_DIAL_CMD: + ext = tus[ts->id_to_dial].extension; + fprintf(stderr, "%s: [%ld] (step #%ld) %s extension %d (id %d)\n", + timestamp(), TU_ID(tu), ts - scr, tu_command_names[cmd], ext, ts->id_to_dial); + fprintf(tu->out, "%s %d%s", tu_command_names[cmd], ext, EOL); + fflush(tu->out); + break; + case TU_CHAT_CMD: + fprintf(stderr, "%s: [%ld] (step #%ld) %s\n", + timestamp(), TU_ID(tu), ts - scr, tu_command_names[cmd]); + fprintf(tu->out, "%s%s", tu_command_names[cmd], EOL); + fflush(tu->out); + break; + + // Unknown command + default: + fprintf(stderr, "%s: [%ld] (step #%ld) Test error: unknown command (%d)\n", + timestamp(), TU_ID(tu), ts - scr, cmd); + return -1; + } + if(cmd <= TU_CHAT_CMD) { + tu->last_command = cmd; + tu->expected_states = next_states[tu->current_state][cmd]; + } else if(cmd == TU_CONNECT_CMD) { + // This is to get the right set of expected commands on initial connect, + // when no previous command has actually been sent. + tu->last_command = TU_HANGUP_CMD; + tu->expected_states = next_states[tu->current_state][TU_HANGUP_CMD]; + } else if(cmd == TU_DISCONNECT_CMD) { + fprintf(stderr, "%s: [%ld] Disconnected, now expecting EOF\n", timestamp(), TU_ID(tu)); + tu->last_command = cmd; + tu->expected_states = ~0; // We allow anything to drain pending notifications. + } else { + // For pseudo-commands, just recalculate the expected states based on + // the last real command. + tu->expected_states = next_states[tu->current_state][tu->last_command]; + } + + // Next, read responses while keeping track of timeout. + // If expected response seen, go to next step. + // If unexpected response seen, fail. + // If timeout occurs, shutdown the connection so that read will fail. + if(tu->infd && read_responses(tu, ts->response, ts->timeout) == -1) + return -1; + + // Advance script to next test step. + ts++; + } + return 0; +} + +/* + * Connect a specified TU to the server. + * Returns 0 on success, -1 on error. + */ +static int connect_command(TU *tu, int port) { + char *hostname = "localhost"; + struct in_addr sa; + struct hostent *he; + int sfd; + + // Connect to server on specified port and set sfd. + if((he = gethostbyname(hostname)) == NULL) { + herror("gethostbyname"); + return -1; + } + memcpy(&sa, he->h_addr, sizeof(sa)); + if((sfd = connect_to_server(&sa, port)) == -1) { + fprintf(stdout, "%s [%ld]: Failed to connect to server %s:%d\n", + timestamp(), TU_ID(tu), hostname, port); + return -1; + } + struct sockaddr_in s; + socklen_t sl = sizeof(s); + getsockname(sfd, (struct sockaddr *)&s, &sl); + port = s.sin_port; + fprintf(stdout, "%s: [%ld] Connected to server %s:%d\n", + timestamp(), TU_ID(tu), hostname, port); + + // Save file descriptor and set up streams and initial test state. + memset(tu, 0, sizeof(*tu)); + tu->infd = sfd; + tu->outfd = dup(sfd); // So they can be closed independently. + tu->in = fdopen(tu->infd, "r"); + tu->out = fdopen(tu->outfd, "w"); + + // Initial expected state notification is TU_ON_HOOK + tu->expected_states = 1<last_command = TU_HANGUP_CMD; + + return 0; +} + +/* + * Disconnect a specified TU from the server. + */ +static void disconnect_command(TU *tu) { + if(tu->outfd) { + shutdown(tu->outfd, SHUT_WR); // This lets us see if the server notices. + tu->outfd = 0; + } + if(tu->out) { + fclose(tu->out); + tu->out = NULL; + } + // Closing the input should go where we detect EOF. +#if 0 + if(tu->in) { + fclose(tu->in); + tu->in = NULL; + } +#endif +} + +/* There isn't really a maximum message length, but this is just a test driver... */ +#define MAX_MESSAGE_LEN 256 + +static struct timeval current_timeout; + +static TU *tu_to_read; +static void alarm_handler(int sig) { + fprintf(stderr, "%s: [%ld] Timeout (%ld, %ld)\n", timestamp(), TU_ID(tu_to_read), + current_timeout.tv_sec, current_timeout.tv_usec); + shutdown(tu_to_read->infd, SHUT_RD); // Force return from fgets +} + +/* + * Read responses from the server for a specified TU until an expected state is reached. + */ +static int read_responses(TU *tu, TU_STATE exp, struct timeval tv) { + TU_STATE new; + char msg[MAX_MESSAGE_LEN]; + char *arg; + int ret = 0; + fprintf(stderr, "%s: [%ld] Read responses until %s\n", + timestamp(), TU_ID(tu), exp == -1 ? "EOF" : tu_state_names[exp]); + tu_to_read = tu; + struct itimerval itv = {0}; + struct sigaction sa = {0}, oa; + sa.sa_handler = alarm_handler; + sa.sa_flags = SA_RESTART; + itv.it_value = tv; + current_timeout = tv; + sigaction(SIGALRM, &sa, &oa); + setitimer(ITIMER_REAL, &itv, NULL); + memset(&itv, 0, sizeof(itv)); + itv.it_value = tv; + setitimer(ITIMER_REAL, &itv, NULL); + do { + fprintf(stderr, "%s: [%ld] Expecting: %s\n", timestamp(), TU_ID(tu), + unparse_state_set(tu->expected_states)); + + if(fgets(msg, MAX_MESSAGE_LEN, tu->in) == NULL) { + fprintf(stderr, "%s: [%ld] EOF reading message from server\n", timestamp(), TU_ID(tu)); + fclose(tu->in); + tu->infd = 0; + if(tu->resync) { + fprintf(stderr, "%s: [%ld] Premature disconnection during resync\n", + timestamp(), TU_ID(tu)); + ret = -1; + goto disarm; + } else { + if(tu->expected_states == ~0) { + fprintf(stderr, "%s: [%ld] Matched EOF after disconnect\n", + timestamp(), TU_ID(tu)); + goto disarm; + } else { + if(exp == -1) { + fprintf(stderr, "%s: [%ld] Expected EOF correctly seen\n", + timestamp(), TU_ID(tu)); + } else { + fprintf(stderr, "%s: [%ld] EOF seen when it shouldn't have been\n", + timestamp(), TU_ID(tu)); + ret = -1; + } + goto disarm; + } + } + } + trim_eol(msg); + fprintf(stderr, "%s: [%ld] Message from server: %s\n", timestamp(), TU_ID(tu), msg); + new = parse_message(msg, &arg); + if(new > NUM_STATES) { + // Tracing output already produced by parse_message. + ret = -1; + goto disarm; + } + if(new == NUM_STATES) { + // The message is chat. There is no state transition, but we must be + // in the connected state. + if(tu->current_state != TU_CONNECTED) { + fprintf(stderr, "%s: [%ld] Chat received when not in state %s\n", + timestamp(), TU_ID(tu), tu_state_names[tu->current_state]); + ret = -1; + goto disarm; + } + continue; + } + + // Check state transition to see if it is as expected. + if(1<expected_states) { + // OK + tu->resync = 0; + } else if(1<<(new+RESYNC) & tu->expected_states) { + // OK, but set resync because messages crossed in transit. + fprintf(stderr, "%s: [%ld] Resync: state %s, expecting %s\n", + timestamp(), TU_ID(tu), tu_state_names[new], + unparse_state_set(tu->expected_states)); + tu->resync = 1; + } else { + // New state is not one that is expected -- testing fails. + fprintf(stderr, "%s: [%ld] New state %s is not in expected set %s\n", + timestamp(), TU_ID(tu), tu_state_names[new], + unparse_state_set(tu->expected_states)); + ret = -1; + goto disarm; + } + + // Update current state to that specified in message + fprintf(stderr, "%s: [%ld] Change state: %s -> %s\n", + timestamp(), TU_ID(tu), tu_state_names[tu->current_state], tu_state_names[new]); + tu->current_state = new; + if(new == TU_ON_HOOK) { + int ext = atoi(arg); + if(tu->extension != ext) { + tu->extension = ext; + } + } + if(new == TU_CONNECTED) { + int ext = atoi(arg); + tu->peer = ext; + } else { + tu->peer = -1; + } + + if(tu->resync) { + // If resyncing, update expected states based on last command sent, + // unless we are draining to get EOF. + //fprintf(stderr, "%s: [%ld] Resync\n", timestamp(), TU_ID(tu)); + if(tu->expected_states != ~0) + tu->expected_states = next_states[new][tu->last_command]; + } + } while(tu->current_state != exp); + + disarm: + itv = (struct itimerval) {0}; + setitimer(ITIMER_REAL, &itv, NULL); + sigaction(SIGALRM, &oa, NULL); + tu_to_read = NULL; + return ret; +} + +/* + * Parse a message from the PBX, determining the new state. + */ +static TU_STATE parse_message(char *msg, char **arg) { + for(int i = 0; i < NUM_STATES; i++) { + if(strstr(msg, tu_state_names[i]) == msg) { + if(arg) + *arg = msg + strlen(tu_state_names[i]); + return i; + } + } + if(strstr(msg, "CHAT") == msg) { + if(arg) + *arg = msg + strlen("CHAT"); + return NUM_STATES; + } + fprintf(stderr, "%s: Unrecognized message: %s\n", timestamp(), msg); + return NUM_STATES+1; +} + +/* + * Connect to the server at a specified address. + * + * Returns: connection file descriptor in case of success. + * Returns -1 and sets errno in case of error. + */ +static int connect_to_server(struct in_addr *addr, int port) { + struct sockaddr_in sa; + int sfd; + + if((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return(-1); + } + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + memcpy(&sa.sin_addr.s_addr, addr, sizeof(struct in_addr)); + if(connect(sfd, (struct sockaddr *)(&sa), sizeof(sa)) < 0) { + close(sfd); + return(-1); + } + return sfd; +} + +/* + * Construct a string representation of an expected state bitmap. + */ +static char *unparse_state_set(int set) { + static char buf[100]; + buf[0] = '\0'; + strcat(buf, "{ "); + for(int i = 0; i < NUM_STATES; i++) { + if(set & (1< Date: Tue, 3 May 2022 18:43:03 -0400 Subject: [PATCH 2/2] Increase CONNECT timeouts from 10ms to 50ms. The 10ms timeout seems marginal. --- hw5/tests/basecode_tests.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw5/tests/basecode_tests.c b/hw5/tests/basecode_tests.c index 5de8bf9..b84d74d 100644 --- a/hw5/tests/basecode_tests.c +++ b/hw5/tests/basecode_tests.c @@ -147,7 +147,7 @@ static TEST_STEP SCRIPT(TEST_NAME)[] = { { 1, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, { 0, TU_PICKUP_CMD, -1, TU_DIAL_TONE, TEN_MSEC }, { 0, TU_DIAL_CMD, 1, TU_RING_BACK, TEN_MSEC }, - { 1, TU_PICKUP_CMD, -1, TU_CONNECTED, TEN_MSEC }, + { 1, TU_PICKUP_CMD, -1, TU_CONNECTED, FTY_MSEC }, { 0, TU_HANGUP_CMD, -1, TU_ON_HOOK, FTY_MSEC }, { 1, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, @@ -169,7 +169,7 @@ static TEST_STEP SCRIPT(TEST_NAME)[] = { { 1, TU_CONNECT_CMD, -1, TU_ON_HOOK, HND_MSEC }, { 0, TU_PICKUP_CMD, -1, TU_DIAL_TONE, TEN_MSEC }, { 0, TU_DIAL_CMD, 1, TU_RING_BACK, TEN_MSEC }, - { 1, TU_PICKUP_CMD, -1, TU_CONNECTED, TEN_MSEC }, + { 1, TU_PICKUP_CMD, -1, TU_CONNECTED, FTY_MSEC }, { 1, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC }, { 0, TU_HANGUP_CMD, -1, TU_ON_HOOK, FTY_MSEC }, { 0, TU_DISCONNECT_CMD, -1, -1, TEN_MSEC },