Merging HW5_CODE
This commit is contained in:
commit
dcdadfc215
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,5 +3,5 @@
|
||||||
*.d
|
*.d
|
||||||
*.o
|
*.o
|
||||||
*.out
|
*.out
|
||||||
bin/
|
bin/*
|
||||||
build/
|
build/*
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
image: hwrunner:latest
|
image: hwrunner:latest
|
||||||
variables:
|
variables:
|
||||||
GIT_SSL_NO_VERIFY: "true"
|
GIT_SSL_NO_VERIFY: "true"
|
||||||
EXEC: mush
|
EXEC: pbx
|
||||||
HW_DIR: hw4
|
HW_DIR: hw5
|
||||||
|
CPU_LIMIT: 60
|
||||||
|
FILE_LIMIT: 1000000
|
||||||
before_script:
|
before_script:
|
||||||
- make clean all -C ${HW_DIR}
|
- make clean all -C ${HW_DIR}
|
||||||
stages:
|
stages:
|
||||||
|
@ -16,8 +18,12 @@ build:
|
||||||
run:
|
run:
|
||||||
stage: run
|
stage: run
|
||||||
script:
|
script:
|
||||||
- cd ${HW_DIR} && bin/${EXEC} < rsrc/run_test.mush
|
- ulimit -t ${CPU_LIMIT}
|
||||||
|
- ulimit -f ${FILE_LIMIT}
|
||||||
|
- cd ${HW_DIR} && bin/${EXEC}
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- cd ${HW_DIR} && bin/${EXEC}_tests -S --verbose=0 --timeout 5
|
- ulimit -t ${CPU_LIMIT}
|
||||||
|
- ulimit -f ${FILE_LIMIT}
|
||||||
|
- cd ${HW_DIR} && bin/${EXEC}_tests -S --verbose=0 -j1 --timeout 50
|
||||||
|
|
81
hw5/Makefile
Normal file
81
hw5/Makefile
Normal file
|
@ -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
|
BIN
hw5/demo/pbx
Executable file
BIN
hw5/demo/pbx
Executable file
Binary file not shown.
46
hw5/hw5.sublime-project
Normal file
46
hw5/hw5.sublime-project
Normal file
|
@ -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}",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
88
hw5/include/debug.h
Normal file
88
hw5/include/debug.h
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#ifndef DEBUG_H
|
||||||
|
#define DEBUG_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#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 */
|
52
hw5/include/pbx.h
Normal file
52
hw5/include/pbx.h
Normal file
|
@ -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 <unistd.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
#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
|
49
hw5/include/server.h
Normal file
49
hw5/include/server.h
Normal file
|
@ -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
|
52
hw5/include/tu.h
Normal file
52
hw5/include/tu.h
Normal file
|
@ -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
|
BIN
hw5/lib/pbx.a
Normal file
BIN
hw5/lib/pbx.a
Normal file
Binary file not shown.
35
hw5/src/globals.c
Normal file
35
hw5/src/globals.c
Normal file
|
@ -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;
|
||||||
|
|
44
hw5/src/main.c
Normal file
44
hw5/src/main.c
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "pbx.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
static void terminate(int status);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "PBX" telephone exchange simulation.
|
||||||
|
*
|
||||||
|
* Usage: pbx <port>
|
||||||
|
*/
|
||||||
|
int main(int argc, char* argv[]){
|
||||||
|
// Option processing should be performed here.
|
||||||
|
// Option '-p <port>' 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);
|
||||||
|
}
|
91
hw5/src/pbx.c
Normal file
91
hw5/src/pbx.c
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* PBX: simulates a Private Branch Exchange.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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
|
21
hw5/src/server.c
Normal file
21
hw5/src/server.c
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* "PBX" server module.
|
||||||
|
* Manages interaction with a client telephone unit (TU).
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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
|
205
hw5/src/tu.c
Normal file
205
hw5/src/tu.c
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
* TU: simulates a "telephone unit", which interfaces a client with the PBX.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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
|
41
hw5/tests/__test_includes.h
Normal file
41
hw5/tests/__test_includes.h
Normal file
|
@ -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);
|
185
hw5/tests/basecode_tests.c
Normal file
185
hw5/tests/basecode_tests.c
Normal file
|
@ -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 <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include <criterion/criterion.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#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, 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 },
|
||||||
|
{ -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, 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 },
|
||||||
|
{ -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
|
642
hw5/tests/script_tester.c
Normal file
642
hw5/tests/script_tester.c
Normal file
|
@ -0,0 +1,642 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#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<<s, and it
|
||||||
|
* indicates a state that we would expect to see if there were no "crossing in transit".
|
||||||
|
* The "abnormal case" encodes additional states that we might see when messages
|
||||||
|
* cross in transit. These are encoded as 1<<(s+RESYNC), where RESYNC is larger than
|
||||||
|
* any TU_STATE value. When we receive a state notification, it is checked against
|
||||||
|
* the expected state bitmap. If we find that state among the "normal case" states,
|
||||||
|
* then nothing special happens and we proceed on to selecting the next command to send.
|
||||||
|
* On the other hand, if we find that state among the "abnormal case" states, then
|
||||||
|
* a "resync" flag is set and we do not immediately select a new command to send.
|
||||||
|
* Instead, we assume that what we have just received is an asynchronous state-change
|
||||||
|
* notification that crossed in transit our last command, and that the response to
|
||||||
|
* our last command is still forthcoming. In this situation, we redetermine the set
|
||||||
|
* of expected events based on the new state, but the last command that we sent.
|
||||||
|
* When we finally do receive a "normal case" response, then the resynchronization is
|
||||||
|
* over and we proceed to send another command.
|
||||||
|
*
|
||||||
|
* A deficiency in the current implementation is that there ought to be a timeout after
|
||||||
|
* which we declare failure if a resynchronization has not completed within a short
|
||||||
|
* period of time.
|
||||||
|
*
|
||||||
|
* Another deficiency at the moment is that the tester tests that "bad things don't happen",
|
||||||
|
* but it doesn't really check that "good things do happen" (e.g. that calls get connected).
|
||||||
|
*
|
||||||
|
* One other deficiency is in the treatment of delays. When the action chosen from a state
|
||||||
|
* is to delay, the delays will continue until a non-delay action is chosen, without reading
|
||||||
|
* any notifications from the server until the delay period is over. It would be better if
|
||||||
|
* the arrival of notifications from the server was checked after each basic delay, but that
|
||||||
|
* would further complicate the program and it has not been implemented at this time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define RESYNC NUM_STATES
|
||||||
|
|
||||||
|
int next_states[NUM_STATES][NUM_COMMANDS] = {
|
||||||
|
[TU_ON_HOOK] {
|
||||||
|
1<<TU_DIAL_TONE | 1<<(TU_RINGING+RESYNC) | 1<<(TU_ON_HOOK+RESYNC), // TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_RINGING+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_RINGING+RESYNC), // TU_DIAL_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_RINGING+RESYNC), // TU_CHAT_CMD
|
||||||
|
1<<(TU_ON_HOOK+RESYNC) | 1<<(TU_RINGING+RESYNC) // DELAY
|
||||||
|
},
|
||||||
|
[TU_RINGING] {
|
||||||
|
1<<TU_CONNECTED | 1<<(TU_ON_HOOK+RESYNC) | 1<<(TU_RINGING+RESYNC), // TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_RINGING+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_RINGING | 1<<(TU_ON_HOOK+RESYNC), // TU_DIAL_CMD
|
||||||
|
1<<TU_RINGING | 1<<(TU_ON_HOOK+RESYNC), // TU_CHAT_CMD
|
||||||
|
1<<(TU_RINGING+RESYNC) | 1<<(TU_ON_HOOK+RESYNC) // DELAY
|
||||||
|
},
|
||||||
|
[TU_DIAL_TONE] {
|
||||||
|
1<<TU_DIAL_TONE, // TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_DIAL_TONE+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_RING_BACK | 1<<TU_BUSY_SIGNAL | 1<<TU_ERROR
|
||||||
|
| 1<<(TU_DIAL_TONE+RESYNC), // TU_DIAL_CMD
|
||||||
|
1<<TU_DIAL_TONE, // TU_CHAT_CMD
|
||||||
|
1<<(TU_DIAL_TONE+RESYNC) // DELAY
|
||||||
|
},
|
||||||
|
[TU_RING_BACK] {
|
||||||
|
1<<TU_RING_BACK | 1<<(TU_CONNECTED+RESYNC) | 1<<(TU_DIAL_TONE+RESYNC),// TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_CONNECTED+RESYNC) | 1<<(TU_DIAL_TONE+RESYNC)
|
||||||
|
| 1<<(TU_RING_BACK+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_RING_BACK | 1<<(TU_CONNECTED+RESYNC) | 1<<(TU_DIAL_TONE+RESYNC),// TU_DIAL_CMD
|
||||||
|
1<<TU_RING_BACK | 1<<(TU_CONNECTED+RESYNC) | 1<<(TU_DIAL_TONE+RESYNC),// TU_CHAT_CMD
|
||||||
|
1<<(TU_RING_BACK+RESYNC) | 1<<(TU_CONNECTED+RESYNC) | 1<<(TU_DIAL_TONE+RESYNC) // DELAY
|
||||||
|
},
|
||||||
|
[TU_BUSY_SIGNAL] {
|
||||||
|
1<<TU_BUSY_SIGNAL, // TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_BUSY_SIGNAL+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_BUSY_SIGNAL, // TU_DIAL_CMD
|
||||||
|
1<<TU_BUSY_SIGNAL, // TU_CHAT_CMD
|
||||||
|
1<<(TU_BUSY_SIGNAL+RESYNC) // DELAY
|
||||||
|
},
|
||||||
|
[TU_CONNECTED] {
|
||||||
|
1<<TU_CONNECTED | 1<<(TU_DIAL_TONE+RESYNC) | 1<<(TU_CONNECTED+RESYNC),// TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_DIAL_TONE+RESYNC) | 1<<(TU_CONNECTED+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_CONNECTED | 1<<(TU_DIAL_TONE+RESYNC), // TU_DIAL_CMD
|
||||||
|
1<<TU_CONNECTED | 1<<(TU_DIAL_TONE+RESYNC), // TU_CHAT_CMD
|
||||||
|
1<<(TU_CONNECTED+RESYNC) | 1<<(TU_DIAL_TONE+RESYNC) // DELAY
|
||||||
|
},
|
||||||
|
[TU_ERROR] {
|
||||||
|
1<<TU_ERROR, // TU_PICKUP_CMD
|
||||||
|
1<<TU_ON_HOOK | 1<<(TU_ERROR+RESYNC), // TU_HANGUP_CMD
|
||||||
|
1<<TU_ERROR, // TU_DIAL_CMD
|
||||||
|
1<<TU_ERROR, // TU_CHAT_CMD
|
||||||
|
1<<(TU_ERROR+RESYNC) // DELAY
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure that records the state of a single TU under test.
|
||||||
|
*/
|
||||||
|
typedef struct tu {
|
||||||
|
/* File descriptor for input from server connection, or 0 if not connected. */
|
||||||
|
int infd;
|
||||||
|
|
||||||
|
/* File descriptor for output to server connection, or 0 if not connected. */
|
||||||
|
int outfd;
|
||||||
|
|
||||||
|
/* Input stream from the server. */
|
||||||
|
FILE *in;
|
||||||
|
|
||||||
|
/* Output stream to the server. */
|
||||||
|
FILE *out;
|
||||||
|
|
||||||
|
/* Extension number we have been assigned by the server. */
|
||||||
|
int extension;
|
||||||
|
|
||||||
|
/* Extension to which we are connnected. */
|
||||||
|
int peer;
|
||||||
|
|
||||||
|
/* The current state of the TU simulated by the tester. */
|
||||||
|
TU_STATE current_state;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A bitmap that specifies the set of next states we expect, as described above.
|
||||||
|
* A transition to a state not in this set results in a test failure.
|
||||||
|
*/
|
||||||
|
int expected_states;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flag that indicates whether we are currently resynchronizing, as a result of
|
||||||
|
* due to messages that "crossed in transit".
|
||||||
|
*/
|
||||||
|
int resync;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The last command sent to the server. This is used during resynchronization,
|
||||||
|
* to determine a new set of expected states as each successive asynchronous
|
||||||
|
* notification is received.
|
||||||
|
*/
|
||||||
|
TU_COMMAND last_command;
|
||||||
|
} TU;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Table giving the states of all TUs under test.
|
||||||
|
*/
|
||||||
|
#define MAX_TUS 20
|
||||||
|
TU tus[MAX_TUS];
|
||||||
|
|
||||||
|
#define TU_ID(tu) ((tu) - &tus[0])
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Meta-commands" for the test script.
|
||||||
|
* These are not actual TU commands, but rather specify other actions to
|
||||||
|
* be performed during a test script.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
#define TU_NO_CMD 100
|
||||||
|
#define TU_CONNECT_CMD 101
|
||||||
|
#define TU_DISCONNECT_CMD 102
|
||||||
|
#define TU_AWAIT_CMD 103 // Await a specified TU state (e.g. TU_RINGING)
|
||||||
|
#define TU_DELAY_CMD 104 // Timeout specifies time delay
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sample test script.
|
||||||
|
* Later these will be provided by individual Criterion tests.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
TEST_STEP sample_test_script[] = {
|
||||||
|
// ID, COMMAND, ID_TO_DIAL, RESPONSE, TIMEOUT
|
||||||
|
{ 0, TU_CONNECT_CMD, -1, TU_ON_HOOK, { 1, 0 }},
|
||||||
|
{ 1, TU_CONNECT_CMD, -1, TU_ON_HOOK, { 1, 0 }},
|
||||||
|
{ 1, TU_PICKUP_CMD, -1, TU_DIAL_TONE, { 1, 0 }},
|
||||||
|
{ 1, TU_DIAL_CMD, 0, TU_RING_BACK, { 1, 0 }},
|
||||||
|
{ 0, TU_AWAIT_CMD, -1, TU_RINGING, { 1, 0 }},
|
||||||
|
{ 0, TU_PICKUP_CMD, -1, TU_CONNECTED, { 1, 0 }},
|
||||||
|
{ 0, TU_DISCONNECT_CMD, -1, -1, { 1, 0 }},
|
||||||
|
{ 1, TU_DISCONNECT_CMD, -1, -1, { 1, 0 }},
|
||||||
|
{ -1, -1, -1, -1, { 0, 0 }}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Prototypes for functions that appear below. */
|
||||||
|
static void test(FILE *in, FILE *out, int cmds);
|
||||||
|
static int choose_action(void);
|
||||||
|
static TU_STATE parse_message(char *msg, char **arg);
|
||||||
|
static char *unparse_state_set(int set);
|
||||||
|
static void trim_eol(char *msg);
|
||||||
|
static char *timestamp(void);
|
||||||
|
static int connect_command(TU *tu, int port);
|
||||||
|
static void disconnect_command(TU *tu);
|
||||||
|
static int connect_to_server(struct in_addr *addr, int port);
|
||||||
|
static int read_responses(TU *tu, TU_STATE exp, struct timeval tv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Temporary main until this is fleshed out.
|
||||||
|
* Then it will be hooked to Criterion.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if(run_test_script(sample_test_script, SERVER_PORT) == -1) {
|
||||||
|
debug("Script failed");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
} else {
|
||||||
|
debug("Script succeeded");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void alert(int sig) {
|
||||||
|
fprintf(stderr, "Unexpected signal: %d\n", sig);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run a test script provided as a parameter.
|
||||||
|
* Returns 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int run_test_script(char *name, TEST_STEP *scr, int port) {
|
||||||
|
fprintf(stderr, "Running test %s\n", name);
|
||||||
|
signal(SIGPIPE, alert);
|
||||||
|
signal(SIGSEGV, alert);
|
||||||
|
signal(SIGHUP, alert);
|
||||||
|
signal(SIGINT, alert);
|
||||||
|
TEST_STEP *ts = scr;
|
||||||
|
struct timespec tms = {0};
|
||||||
|
while(ts->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<<TU_ON_HOOK;
|
||||||
|
|
||||||
|
// Set initial last command to TU_HANGUP so that updating expected states
|
||||||
|
// works properly.
|
||||||
|
tu->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<<new & tu->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<<i) || set & (1<<(i+RESYNC))) {
|
||||||
|
strcat(buf, tu_state_names[i]);
|
||||||
|
if(set & (1<<(i+RESYNC)))
|
||||||
|
strcat(buf, "*");
|
||||||
|
strcat(buf, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strcat(buf, "}");
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trim EOL characters from the end of a message.
|
||||||
|
*/
|
||||||
|
static void trim_eol(char *msg) {
|
||||||
|
for(char *mp = msg; *mp != '\0'; mp++) {
|
||||||
|
if(*mp == '\n' || *mp == '\r')
|
||||||
|
*mp = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a timestamp string for tracing printout.
|
||||||
|
*/
|
||||||
|
static char *timestamp() {
|
||||||
|
static char buf[100];
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
sprintf(buf, "%ld.%06ld", tv.tv_sec, tv.tv_usec);
|
||||||
|
return buf;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user