#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lurk.h" #include "scolor.h" #include "looking_glass.h" /* * Features for next time / thorough test: * Client with long description (jabberwocky or hunting of the snark? More than 2^15 characters) * Sending a long message (same idea) * Open/close sockets at several points, maybe in the middle of sending of each struct * Try everything that shouldn't work before creating a character * Change rooms and then immediately disconnect * sync_send: synchronized-send a message from each bot to one bot and make sure the results are the same * Pack all bots into the same room at the same time, then have them all call fight as one, then leave then enter then disconnect * Fuzz test */ const char *argp_program_version = "4.4.2022"; static char doc[] = "LURK Server Test Program. Unless you specify a path, this program should return to the command line when finished. "; static char args_doc[] = "server port"; static struct argp_option options[] = { {"verbose", 'v', 0, 0, "Verbose Output" }, {"fight", 'f', 0, 0, "Fight after entering each room, default off"}, {"bots", 'b', "nbots", 0, "Number of bots to test with, default 5, 0 skips bot test"}, {"max-actions", 'm', "actions", 0, "Maximum number of actions per bot, default 20"}, {"path", 'p', "path", 0, "Path, separated by hyphens"}, {"thorough", 't', 0, 0, "More thorough test"}, {0} }; struct arguments { char *args[2]; char verbose, fight, thorough; int nbots, max_actions, pathlength; int path[128]; }; struct arguments arguments = {0, 0, 0, 0, 0, 5, 20, 0}; #define PDELAY 20000 size_t slow_write(int fd, const void *buf, size_t count){ size_t rval = 0; while(rval < count){ size_t next = write(fd, buf+rval, 1); rval += next; if(next != 1) return -1; usleep(PDELAY); } return rval; } static error_t parse_opt(int key, char *arg, struct argp_state *state){ switch(key){ case 'v': arguments.verbose = 1; break; case 'f': arguments.fight = 1; break; case 't': arguments.thorough = 1; break; case 'b': arguments.nbots = atoi(arg); break; case 'm': arguments.max_actions = atoi(arg); break; case 'p':;{ int prev_spot = 0; int arglen = strlen(arg); for(int i = 0; i < arglen; i++){ if(arg[i] == '-'){ arg[i] = 0; arguments.path[arguments.pathlength++] = atoi(arg + prev_spot); prev_spot = i+1; } } arguments.path[arguments.pathlength++] = atoi(arg + prev_spot); } break; case ARGP_KEY_ARG: if(state->arg_num >= 2) argp_usage(state); arguments.args[state->arg_num] = arg; break; case ARGP_KEY_END: if(state->arg_num < 2) argp_usage(state); break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = {options, parse_opt, args_doc, doc}; struct sockaddr_in connect_addr; struct client_data { struct player plr; int socket; int connections[512]; int nconnect; pthread_t read, write; pthread_mutex_t move; int nextroom; int done; }; #define BSIZE (64*1024+124) char *buffer; void print_lurk_struct(void* ls){ make_string(ls, buffer); puts(buffer); } void* client_read(void* param){ struct client_data *cd = (struct client_data*)param; for(;;){ char* type = (char*)read_lurk_struct(cd->socket); if(!type){ printf(RED("Bot %s failed to read\n"), cd->plr.name); cd->nconnect = -1; return 0; } /* Think about this: Should it change room if we receive a ROOM message? * It seems to work, but maybe it's because of servers not validating moves * What if the server sends ROOM, CONNECTION, then CHARACTER ? */ if(*type == CONNECTION){ struct room* c = (struct room*)type; pthread_mutex_lock(&cd->move); if(cd->nconnect < 32){ for(int i = 0; i < cd->nconnect; i++) if(cd->connections[i] == c->number){ pthread_mutex_unlock(&cd->move); goto next; } cd->connections[cd->nconnect++] = c->number; cd->connections[cd->nconnect] = -1; printf("New connection for %s in room %d, connects to %d\n", cd->plr.name, cd->plr.room, c->number); } else printf(YELLOW("Out of space for new connections: %s in room %d\n"), cd->plr.name, cd->plr.room); pthread_mutex_unlock(&cd->move); } else if(*type == CHARACTER){ struct player* p = (struct player*)type; if(!strcmp(p->name, cd->plr.name)){ cd->plr.health = p->health; cd->plr.flags = p->flags; cd->plr.room = p->room; if(!(p->flags & 0x80)){ cd->nconnect = -1; return 0; } } } else if(*type == ERROR && *(type+1) == 1){ struct error *e = (struct error*)type; printf(YELLOW("Movement Error: %s received %s, attempted %d to %d, Connections: "), cd->plr.name, e->message, cd->plr.room, cd->nextroom); for(int i = 0; i < 32 && cd->connections[i] != -1; i++) printf(" %d ", cd->connections[i]); puts(""); } next: if(arguments.verbose){ printf(PURPLE("Client %s received:"), cd->plr.name); print_lurk_struct((void*)type); } free_lurk_struct((void*)type); } } char* mkname(){ static char *name_storage; static size_t nsused = 0; static size_t namecount = 0; static char *names[6000]; static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&m); if(!nsused){ name_storage = malloc(40000); int namefd = open("/usr/bin/only_names", O_RDONLY); nsused = read(namefd, name_storage, 40000); close(namefd); names[0] = name_storage; namecount = 1; char firstletter = 1; for(char* place = name_storage; place < name_storage + nsused; place++){ if(*place == '\n'){ names[namecount] = place+1; *place = 0; namecount++; firstletter = 1; } else if(!firstletter) { *place = tolower(*place); } else firstletter = 0; } namecount--; srandom((long int)&nsused); } size_t name = random() % namecount; pthread_mutex_unlock(&m); return names[name]; } void* read_for_struct(char type, int socket){ void* curr = read_lurk_struct(socket); if(!curr){ return 0; } if(*((char*)curr) == type) return curr; if(arguments.verbose){ puts(PURPLE("Reading past struct:")); print_lurk_struct(curr); } return read_for_struct(type, socket); } void* client_write(void* param){ struct client_data *cd = (struct client_data*)param; cd->plr.room = 0; cd->socket = socket(AF_INET, SOCK_STREAM, 0); set_socklim(cd->socket); if(connect(cd->socket, (struct sockaddr*)&connect_addr, sizeof(struct sockaddr_in))){ printf(RED("Could not connect bot!\n")); return cd; } struct game* gm = read_lurk_struct(cd->socket); if(gm->type == VERSION) gm = read_lurk_struct(cd->socket); retry_name: strcpy(cd->plr.name, mkname()); while(strlen(cd->plr.name) == 0) strcpy(cd->plr.name, mkname()); printf(CYAN("Starting Bot %s (FD %d)\n"), cd->plr.name, cd->socket); cd->plr.type = 10; cd->plr.flags = 0xFF; cd->plr.attack = gm->init_points; cd->plr.defense = 0; cd->plr.regen = 0; cd->plr.description = "Auto-generated test player"; cd->plr.desclen = strlen(cd->plr.description); wls_retry(cd->socket, &cd->plr); char* next_struct = (char*)read_lurk_struct(cd->socket); if(*next_struct == ERROR){ printf(YELLOW("Bot %s received error: "), cd->plr.name); print_lurk_struct(next_struct); struct error* e = (struct error*)next_struct; if(e->errorcode == 2){ printf(YELLOW("Potential duplicate name: %s (FD %d), RETRY: "), cd->plr.name, cd->socket); goto retry_name; } printf(RED("Error wasn't a type 2\n")); return cd; } char fight = 3; fight = 6; wls_retry(cd->socket, &fight); printf("Bot %s started\n", cd->plr.name); fight = FIGHT; if(arguments.thorough) send_message(cd->socket, "TestScript", cd->plr.name, looking_glass); else send_message(cd->socket, "TestScript", cd->plr.name, "CheckIn"); // Just move about randomally cd->nconnect = 0; pthread_t t; pthread_mutex_init(&cd->move, 0); pthread_create(&t, 0, client_read, cd); int path_spot = 0; for(int i = 0; i < arguments.max_actions || arguments.pathlength; i++){ usleep(1000); if(arguments.fight){ usleep(500000); if(cd->nconnect != -1) wls_retry(cd->socket, &fight); } usleep(500000); pthread_mutex_lock(&cd->move); if(cd->nconnect == -1){ printf(CYAN("%s has died, room %d, final health %d\n"), cd->plr.name, cd->plr.room, cd->plr.health); pthread_mutex_unlock(&cd->move); goto end_dead; } struct change_room cm; cm.type = CHANGEROOM; if(!cd->nconnect){ printf(RED("%s is out of options in room %d\n"), cd->plr.name, cd->plr.room); pthread_mutex_unlock(&cd->move); goto end_dead; } if(arguments.pathlength){ if(path_spot < arguments.pathlength) cm.room_number = arguments.path[path_spot++]; else { pthread_mutex_unlock(&cd->move); printf(GREEN("%s has finished %d moves and remains alive, will now stand around forever\n"), cd->plr.name, i); for(;;){ sleep(1); } } } else cm.room_number = cd->connections[random() % cd->nconnect]; cd->nextroom = cm.room_number; cd->nconnect = 0; if(arguments.verbose) printf("%s attempting to move from %d to %d\n", cd->plr.name, cd->plr.room, cd->nextroom); wls_retry(cd->socket, &cm); pthread_mutex_unlock(&cd->move); usleep(500000); while(cd->nextroom != cd->plr.room){ if(cd->nconnect == -1) break; printf(YELLOW("Did not switch rooms within 0.5 seconds, %s from %d to %d\n"), cd->plr.name, cd->plr.room, cd->nextroom); usleep(500000); } } printf(GREEN("%s has finished %d moves and remains alive\n"), cd->plr.name, arguments.max_actions); end_dead: pthread_cancel(t); pthread_join(t, 0); fight = LEAVE; write(cd->socket, &fight, 1); close(cd->socket); cd->done = 1; printf(DYELLOW("End of the line for %s, room %d, health %d\n"), cd->plr.name, cd->plr.room, cd->plr.health); return cd; } void signal_die(int signal){ printf(RED("Signal %d received, terminating lurk_test\n"), signal); exit(1); } int main(int argc, char **argv){ argp_parse(&argp, argc, argv, 0, 0, &arguments); buffer = malloc(BSIZE); struct sigaction handler; handler.sa_handler = signal_die; sigaction(SIGPIPE, &handler, 0); sigaction(SIGTERM, &handler, 0); sigaction(SIGINT, &handler, 0); int sockfd = socket(AF_INET, SOCK_STREAM, 0); set_socklim(sockfd); short port = atoi(arguments.args[1]); connect_addr.sin_port = htons(port); connect_addr.sin_family = AF_INET; struct hostent* entry = gethostbyname(arguments.args[0]); if(!entry) goto err; struct in_addr **addr_list = (struct in_addr**)entry->h_addr_list; struct in_addr* c_addr = addr_list[0]; char* ip_string = inet_ntoa(*c_addr); connect_addr.sin_addr = *c_addr; if(connect(sockfd, (struct sockaddr*)&connect_addr, sizeof(struct sockaddr_in))) goto err; struct version* v = read_lurk_struct(sockfd); struct game* gm; /*A thought here: If the server advertises version 2.2, should we check for correct room-leaving behaviour? */ if(v->type != VERSION){ printf(PURPLE("No version sent. Perhaps server version is <2.2\n")); gm = (struct game*) v; } else { printf(GREEN("Server is version %d.%d\n"), v->major, v->minor); free_lurk_struct(v); gm = read_lurk_struct(sockfd); } make_string(gm, buffer); puts("Received game struct: "); puts(buffer); struct player p; p.type = 10; if(!arguments.thorough) memset(p.name, 0, 32); strcpy(p.name, "TestScript"); p.flags = 0xFF; p.attack = gm->init_points; p.defense = 1; p.regen = 0; p.description = "Auto-generated test player"; p.desclen = strlen(p.description); if(arguments.thorough){ printf(PURPLE("Testing piecemeal send with %d ms between bytes\n"), PDELAY/1000); size_t total = 0; total += slow_write(sockfd, &p, sizeof(struct player) - sizeof(char*)); if(total == sizeof(struct player) - sizeof(char*)) total += slow_write(sockfd, p.description, p.desclen); if(total != sizeof(struct player) - sizeof(char*) + p.desclen){ puts(BRED("FAILED slow send test")); goto broke; } printf(GREEN("Piecemeal send passed, sent %ld bytes \n"), total); } else write_lurk_struct(sockfd, &p); struct error* e = read_lurk_struct(sockfd); if(e->type != ERROR){ printf("\x1b[1;33mExpected error from too-high stats, received type %d \x1b[0m\n", e->type); if(e->type == ACCEPT){ struct player *ap = read_for_struct(CHARACTER, sockfd); if(ap->attack + ap->defense + ap->regen <= gm->init_points){ printf("\x1b[1;32mReceived corrected/reprised player: \x1b[0m\n"); print_lurk_struct(ap); memcpy(&p, ap, sizeof(struct player) - 8); free_lurk_struct(ap); goto skip_accept; } else goto broke; } else goto broke; } else printf("\x1b[1;32mRejects too-high stats with message: %s\x1b[0m\n", e->message); free_lurk_struct(e); p.attack -= 1; write_lurk_struct(sockfd, &p); struct accept* a = read_lurk_struct(sockfd); printf("Got struct type %d\n", *((char*)a)); if(a->type != ACCEPT){ printf("\x1b[1;31mExpected accept, received type %d \x1b[0m\n", a->type); goto broke; } else printf("\x1b[1;32mAccepts acceptable player\x1b[0m\n"); free_lurk_struct(a); char start; skip_accept: start = 6; write(sockfd, &start, 1); void* rm = read_for_struct(ROOM, sockfd); printf(CYAN("Currently in room:\n")); print_lurk_struct(rm); if(!arguments.nbots) return 0; struct client_data *cd = malloc(sizeof(struct client_data) * arguments.nbots); printf(BLUE("Starting test bot\n")); { int ts = arguments.nbots; pthread_t threads[arguments.nbots]; for(int i = 0; i < ts; i++){ if(!arguments.thorough) usleep(75000); cd[i].done = 0; pthread_create(threads + i, 0, client_write, cd + i); } printf(BLUE("Waiting for test bot\n")); struct message* m = read_for_struct(MESSAGE, sockfd); if(m->mlen < 128){ printf(GREEN("Received Message: \n")); print_lurk_struct(m); } else printf(GREEN("Received message from %s to %s of length %d\n"), m->sender, m->recipient, m->mlen); if(arguments.thorough){ while(strncmp(looking_glass, m->message, m->mlen)){ printf(YELLOW("Message of %d bytes received, but contents are not what we expected! (correct length %d)\n"), m->mlen, strlen(looking_glass)); printf(YELLOW("Maybe there will be another message. We will print out \"Correct message received!\" when we get the right one.\n")); free_lurk_struct(m); m = read_for_struct(MESSAGE, sockfd); } printf(GREEN("Correct message received!\n")); } free_lurk_struct(m); for(int i = 0; i < ts; i++) { struct client_data *r_cd; for(int next = 0; 1; next++){ if(cd[next].done == 1) { pthread_join(threads[next], (void**)&r_cd); cd[next].done = 2; break; } if(next == ts) next = -1; } printf(CYAN("Thread finished for %s. Bots Finished: %d/%d\n"), r_cd->plr.name, i+1, ts); if(ts-i < 10 && (ts - i) > 1){ char remainder[1024]; size_t sofar = 0; sofar += snprintf(remainder, 1024, "Remaining Bots: "); for(int j = 0; j < ts; j++){ if(cd[j].done != 2) sofar += snprintf(remainder + sofar, 1024 - sofar, " \"%s\" ", cd[j].plr.name); } sofar += snprintf(remainder + sofar, 1024 - sofar, "\n"); printf(CYAN("%s"), remainder); } } } start = LEAVE; write(cd->socket, &start, 1); close(sockfd); free(cd); free(buffer); return 0; broke: printf("\x1b[1;31mTest Terminated \x1b[0m\n", a->type); close(sockfd); return 2; err: perror(argv[0]); return 1; }