657 lines
16 KiB
C
657 lines
16 KiB
C
/*
|
|
* 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
|
|
* http://code.lm7.fr/p/owlps/source/tree/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 <signal.h>
|
|
#include <semaphore.h>
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
#include <netdb.h>
|
|
|
|
#include <assert.h>
|
|
|
|
struct {
|
|
bool verbose ;
|
|
uint_fast16_t result_port ;
|
|
uint_fast16_t http_port ;
|
|
int nb_connections ;
|
|
} 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_strlen<answer_buflen) ;
|
|
strncpy(answer + answer_strlen, result_str,
|
|
result_len) ;
|
|
answer_strlen += result_len ;
|
|
result = result->next ;
|
|
}
|
|
}
|
|
|
|
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
|
|
) ;
|
|
}
|