/* * This file is part of the Owl Positioning System (OwlPS) project. * It is subject to the copyright notice and license terms in the * COPYRIGHT.t2t file found in the top-level directory of this * distribution and at * https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t * No part of the OwlPS Project, including this file, may be copied, * modified, propagated, or distributed except according to the terms * contained in the COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be * distributed along with this file, either separately or by replacing * this notice by the COPYRIGHT.t2t file's contents. * *********************************************************************** * * This is the main source file of OwlPS UDP-to-HTTP, a program that * allows a client to retrieve results from OwlPS Positioner through * HTTP. */ #include "owlps-udp-to-http.h" #include #include #include #include #include #include #include #include #include /* Options */ // TODO: the size of this structure could be reduced by reordering its fields. struct { bool verbose ; // 7 bytes alignment uint_fast16_t result_port ; uint_fast16_t http_port ; int nb_connections ; // 4 bytes alignment } options = { true, // verbose OWL_DEFAULT_RESULT_PORT, // result_port DEFAULT_HTTP_PORT, // http_port DEFAULT_NB_CONNECTIONS // nb_connections } ; char *program_name = NULL ; results_list *results = NULL ; unsigned int nb_results = 0 ; sem_t lock_results ; int udp_sockfd = -1 ; int tcp_sockfd = -1 ; char *answer = NULL ; // Answer to send to the client size_t answer_strlen = 0 ; // Total size of the answer string size_t answer_buflen = 0 ; // Size of the answer allocated buffer int main(int argc, char *argv[]) { struct sigaction action ; // Signal handler structure int ret = 0 ; // Program return value pthread_t tcp_server_thread ; owl_run = true ; program_name = argv[0] ; parse_command_line(argc, argv) ; /* Set up signal handlers */ action.sa_flags = 0 ; sigemptyset(&action.sa_mask) ; action.sa_handler = owl_sigint_handler ; sigaction(SIGINT, &action, NULL) ; action.sa_handler = owl_sigterm_handler ; sigaction(SIGTERM, &action, NULL) ; /* Set up the semaphore */ sem_init(&lock_results, 0, 1) ; /* Prepare the TCP socket */ ret = init_tcp_socket() ; if (ret) goto exit ; /* Launch the TCP thread */ ret = pthread_create(&tcp_server_thread, NULL, &tcp_server, NULL) ; if (ret) { perror("Cannot create the TCP server thread") ; ret = OWL_ERR_THREAD_CREATE ; goto exit ; } /* Main loop */ ret = receive_udp() ; /* Stop the TCP thread */ // We must cancel the thread because it can be blocked on the // recv() call: if (pthread_cancel(tcp_server_thread)) perror("Cannot cancel the TCP server thread") ; if (pthread_join(tcp_server_thread, NULL)) perror("Cannot join the TCP server thread") ; exit: /* Close sockets */ if (tcp_sockfd >= 0) if (close(tcp_sockfd)) perror("Error closing the TCP socket") ; if (udp_sockfd >= 0) if (close(udp_sockfd)) perror("Error closing the UDP socket") ; /* Last cleaning */ free(answer) ; free_results_list() ; sem_destroy(&lock_results) ; if (options.verbose) fprintf(stderr, "%s: exiting.\n", program_name) ; return ret ; } void parse_command_line(const int argc, char *const *argv) { parse_options(argc, argv) ; check_configuration() ; if (options.verbose) print_configuration() ; } void parse_options(const int argc, char *const *argv) { int opt ; while ((opt = getopt(argc, argv, OPTIONS)) != -1) { switch (opt) { case 'h' : print_usage() ; exit(0) ; case 'l' : options.result_port = strtoul(optarg, NULL, 10) ; break ; case 't' : options.http_port = strtoul(optarg, NULL, 10) ; break ; case 'q' : options.verbose = false ; break ; case 'v' : options.verbose = true ; break ; case 'V' : print_version() ; exit(0) ; default : print_usage() ; exit(OWL_ERR_BAD_USAGE) ; } } } void check_configuration() { if (options.result_port > 65535) { fprintf(stderr, "Warning! result_port too high: using default" " value (%d).\n", OWL_DEFAULT_RESULT_PORT) ; options.result_port = OWL_DEFAULT_RESULT_PORT ; } if (options.http_port > 65535) { fprintf(stderr, "Warning! http_port too high: using default" " value (%d).\n", DEFAULT_HTTP_PORT) ; options.http_port = DEFAULT_HTTP_PORT ; } } void print_configuration() { fprintf(stderr, "Options:\n" "\tVerbose: %s\n" "\tResult port: %"PRIuFAST16"\n" "\tHTTP port: %"PRIuFAST16"\n" , OWL_BOOL_TO_STRING(options.verbose), options.result_port, options.http_port ) ; } /* * Opens the UDP socket and reads from it the results sent by OwlPS * Positioning. * Returns a non-zero value in case of error. */ int receive_udp() { owl_result *result ; /* Open the UDP socket */ udp_sockfd = owl_create_udp_listening_socket(options.result_port) ; if (udp_sockfd < 0) return OWL_ERR_SOCKET_CREATE ; /* UDP read loop */ while (owl_run) { result = owl_receive_position(udp_sockfd) ; if (result == NULL) return OWL_ERR_SOCKET_RECV ; owl_print_result(result) ; store_result(result) ; printf("--------------\n") ; } return 0 ; } /* * Adds a new owl_result to the results' list. */ void store_result(owl_result *const new_result) { sem_wait(&lock_results) ; // The results' list does not exist yet if (! results) { results = malloc(sizeof(results_list)) ; if (! results) { perror("Cannot allocate memory") ; owl_run = false ; goto end ; } ++nb_results ; results->result = new_result ; results->next = NULL ; } // The results' list contains at least 1 element else { // Search for an existing result with the same mobile's MAC results_list *res = results ; while (res != NULL) { char *mac = res->result->mobile_mac_addr ; if (strncmp(mac, new_result->mobile_mac_addr, OWL_ETHER_ADDR_STRLEN) == 0) break ; res = res->next ; } if (res == NULL) // Not found, adding an element { res = malloc(sizeof(results_list)) ; if (! res) { perror("Cannot allocate memory") ; owl_run = false ; goto end ; } ++nb_results ; res->next = results ; results = res ; } else // Found, clearing it owl_free_result(res->result) ; res->result = new_result ; } end: sem_post(&lock_results) ; } /* * Opens the TCP socket. * Returns a non-zero value in case of error. */ int init_tcp_socket() { char http_port_str[6] ; struct addrinfo gai_hints, *gai_results = NULL, *gai_res = NULL ; int gai_ret ; // Return value of getaddrinfo() /* Get the server information */ sprintf(http_port_str, "%"PRIuFAST16, options.http_port) ; memset(&gai_hints, 0, sizeof(struct addrinfo)) ; gai_hints.ai_family = AF_UNSPEC ; // IPv4 or IPv6 gai_hints.ai_socktype = SOCK_STREAM ; gai_hints.ai_flags = AI_PASSIVE ; gai_ret = getaddrinfo(NULL, http_port_str, &gai_hints, &gai_results) ; if (gai_ret) { fprintf(stderr, "TCP socket creation failed: getaddrinfo(): %s\n", gai_strerror(gai_ret)) ; return OWL_ERR_SOCKET_CREATE ; } /* Create the TCP socket: * loop until both socket() and bind() succeed */ for (gai_res = gai_results ; gai_res != NULL ; gai_res = gai_res->ai_next) { tcp_sockfd = socket(gai_res->ai_family, gai_res->ai_socktype, gai_res->ai_protocol) ; if (tcp_sockfd == -1) continue ; if (! bind(tcp_sockfd, gai_res->ai_addr, gai_res->ai_addrlen)) break ; // Success! close(tcp_sockfd) ; tcp_sockfd = -1 ; } if (gai_res == NULL) { fprintf(stderr, "TCP socket creation failed: socket() or bind().\n") ; return OWL_ERR_SOCKET_CREATE ; } return 0 ; } /* * Waits for requests from HTTP clients. */ void* tcp_server(void *const NULL_value) { int newsockfd ; ssize_t nbytes ; // recv/send return value char client_message[CLIENT_MESSAGE_STRLEN] ; char client_request[CLIENT_REQUEST_STRLEN] ; int request_id ; listen(tcp_sockfd, options.nb_connections) ; // Prepare the answer, assuming there is only 1 full result (an error // message will also fit) answer_buflen = ANSWER_HDR_STRLEN + OWL_CSV_RESULT_STRLEN ; answer = malloc(answer_buflen) ; if (! answer_buflen) { perror("Cannot allocate memory") ; owl_run = false ; return NULL ; } strncpy(answer, ANSWER_HDR, ANSWER_HDR_STRLEN) ; while (owl_run) { newsockfd = accept(tcp_sockfd, NULL, NULL) ; if (newsockfd < 0) { perror("Error accepting a connection on the TCP socket") ; continue ; } nbytes = recv(newsockfd, client_message, CLIENT_MESSAGE_STRLEN, 0) ; if (nbytes < 0) { perror("Error reading from the TCP socket") ; close(newsockfd) ; continue ; } client_message[nbytes] = '\0' ; if (options.verbose) printf("Got a message from the client:\n" "\"%s\"\n", client_message) ; request_id = extract_request_from_message(client_request, client_message) ; prepare_answer(request_id) ; if (options.verbose) printf("Answer to send:\n\"%s\"\n", answer) ; /* Send the answer */ nbytes = send(newsockfd, answer, answer_strlen, 0) ; if (nbytes < 0) perror("Error sending answer to the TCP socket") ; close(newsockfd) ; } pthread_exit(NULL_value) ; } /* * Reads a message and search for a request in it. * Returns the identifier of a request if found, or 0 if not found. * The text of the request is stored in 'client_request'. */ int extract_request_from_message(char client_request[CLIENT_REQUEST_STRLEN], const char *const client_message) { char *token ; token = strchr(client_message, '=') ; if (! token) return 0 ; ++token ; token = strsep(&token, " ") ; if (strncmp(SIMPLE_RESULTS_REQUEST, token, strlen(SIMPLE_RESULTS_REQUEST)) == 0) { strncpy(client_request, SIMPLE_RESULTS_REQUEST, CLIENT_REQUEST_STRLEN) ; return SIMPLE_RESULTS_ID ; } if (strncmp(RESULTS_REQUEST, token, strlen(RESULTS_REQUEST)) == 0) { strncpy(client_request, RESULTS_REQUEST, CLIENT_REQUEST_STRLEN) ; return RESULTS_ID ; } return 0 ; // No known request found } /* * Prepare the answer string to send to the TCP client. */ void prepare_answer(const int request_id) { // Reset the answer's length: answer_strlen = ANSWER_HDR_STRLEN ; switch (request_id) { case RESULTS_ID: strncpy(answer + answer_strlen, RESULTS_ANSWER, strlen(RESULTS_ANSWER)) ; answer_strlen += strlen(RESULTS_ANSWER) ; sem_wait(&lock_results) ; if (! results) { char answer_end[] = ";NOK;NoResults" ; strncpy(answer + answer_strlen, answer_end, strlen(answer_end)) ; answer_strlen += strlen(answer_end) ; } else { results_list *result ; char answer_begin[10] ; size_t answer_begin_len ; snprintf(answer_begin, 10, ";OK;%u", nb_results) ; answer_begin_len = strlen(answer_begin) ; strncpy(answer + answer_strlen, answer_begin, answer_begin_len) ; answer_strlen += answer_begin_len ; realloc_answer(answer_strlen + nb_results * OWL_CSV_RESULT_STRLEN) ; result = results ; while (result != NULL) { char result_str[OWL_CSV_RESULT_STRLEN] ; size_t result_len ; owl_result_to_csv(result_str, result->result) ; result_len = strlen(result_str) ; answer[answer_strlen++] = ';' ; assert(answer_strlennext ; } } sem_post(&lock_results) ; break ; case SIMPLE_RESULTS_ID: strncpy(answer + answer_strlen, SIMPLE_RESULTS_ANSWER, strlen(SIMPLE_RESULTS_ANSWER)) ; answer_strlen += strlen(SIMPLE_RESULTS_ANSWER) ; sem_wait(&lock_results) ; if (! results) { char answer_end[] = ";NOK;NoResults" ; strncpy(answer + answer_strlen, answer_end, strlen(answer_end)) ; answer_strlen += strlen(answer_end) ; } else { results_list *result ; char answer_begin[10] ; size_t answer_begin_len ; snprintf(answer_begin, 10, ";OK;%u", nb_results) ; answer_begin_len = strlen(answer_begin) ; strncpy(answer + answer_strlen, answer_begin, answer_begin_len) ; answer_strlen += answer_begin_len ; realloc_answer(answer_strlen + nb_results * OWL_CSV_RESULT_SIMPLE_STRLEN) ; result = results ; while (result != NULL) { char result_str[OWL_CSV_RESULT_SIMPLE_STRLEN] ; size_t result_len ; owl_result_to_csv_simple(result_str, result->result) ; result_len = strlen(result_str) ; answer[answer_strlen++] = ';' ; strncpy(answer + answer_strlen, result_str, result_len) ; answer_strlen += result_len ; result = result->next ; } } sem_post(&lock_results) ; break ; default: { char answer_end[] = "UnknownRequest;NOK" ; strncpy(answer + ANSWER_HDR_STRLEN, answer_end, strlen(answer_end)) ; answer_strlen += strlen(answer_end) ; } } answer[answer_strlen] = '\0' ; } /* * Realloc the answer buffer to the if needed: grows it if new_size is * greater than the current size, shrink it if the current size is * greater than the double of new_size. */ void realloc_answer(const size_t new_size) { if (new_size > answer_buflen) answer_buflen = new_size ; else if (answer_buflen / 2 >= new_size) answer_buflen /= 2 ; else return ; answer = realloc(answer, answer_buflen) ; if (! answer) abort() ; } /* * Frees the memory allocated for the results' list. */ void free_results_list() { results_list *tmp_res ; while (results != NULL) { owl_free_result(results->result) ; tmp_res = results ; results = results->next ; free(tmp_res) ; } nb_results = 0 ; } /* * Prints the usage message on the standard output. * * /!\ Don't forget to update the documentation when modifying something * here! */ void print_usage() { printf("Usage:\n" "\t%s" " [-v | -q]" " [-l result_port]" " [-t http_port]\n" "Options:\n" "\t-h\t\tPrint this help.\n" "\t-V\t\tPrint version information.\n" "\t-v\t\tTurn on verbose mode (default).\n" "\t-q\t\tDo not print informational messages.\n" "\t-l result_port\tPort on which the results are received" " (default: %d).\n" "\t-t http_port\tPort on which the HTTP server listens" " (default: %d).\n" , program_name, OWL_DEFAULT_RESULT_PORT, DEFAULT_HTTP_PORT ) ; } void print_version() { printf("This is OwlPS UDP-to-HTTP, part of the Owl Positioning System" " project.\n" "Version: %s.\n", #ifdef OWLPS_VERSION OWLPS_VERSION #else // OWLPS_VERSION "unknown version" #endif // OWLPS_VERSION ) ; }