owlps/owlps-udp-to-http/owlps-udp-to-http.c

658 lines
17 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.
*
* This program listens for results sent by OwlPS Positioner on a UDP
* socket, and listens for a client on a TCP socket. The client is
* expected to send an HTTP GET request, with the request string as the
* first variable value (the variable name does not matter).
* For example, a correct request is detected if you load
* http://localhost:8080/?request=ReadSimpleResults
* (assuming the host running this program is localhost).
*
* The default UDP listening port is the port on which OwlPS Positioner
* sends the result by default, and the default TCP (HTTP) listening
* port is 8080.
*
* Only the last result read from the positioning server, for each
* mobile, is memorised and provided to the HTTP client.
*
* The HTTP requests currently implemented are listed bellow.
*
* ** Request "ReadResults" **
* Answer in case of error:
* Results;NOK;Explanation
* Normal answer:
* Results;OK;Nb_results;Result_1;…;Result_n
* Nb_results is the number of results in the answer (number of mobiles).
* Result_i follows this format:
* Mobile_MAC;Request_type;Request_timestamp;Nb_algo;Algo_1;…;Algo_n
* Nb_algo is the number of algorithms in the result.
* Algo_i follows this format:
* Algorithm_name;X;Y;Z;Error;Area_name
* Error is the distance from the true coordinates of the mobile, if
* known; if unknown, Error is set to -1.
* Area_name is the name of the area or room in which the mobile is (may
* be empty).
*
* ** Unknown request **
* If a unknown request is received, the answer format is:
* UnknownRequest;NOK
*
* ** Request "ReadSimpleResults" **
* Answer in case of error:
* SimpleResults;NOK;Explanation
* Normal answer:
* SimpleResults;OK;Nb_results;Result_1;…;Result_n
* Nb_results is the number of results in the answer (number of mobiles).
* Result_i follows this format:
* Mobile_MAC;X;Y;Z;Area_name
* Area_name is the name of the area or room in which the mobile is (may
* be empty).
*/
#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 <assert.h>
struct {
uint_fast16_t result_port ;
uint_fast16_t http_port ;
int nb_connections ;
} options = {
OWL_DEFAULT_RESULT_PORT, // result_port
DEFAULT_HTTP_PORT, // http_port
DEFAULT_NB_CONNECTIONS // nb_connections
} ;
char *program_name = NULL ;
// This needs to become an option when we introduce options
#define verbose true
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 (verbose)
fprintf(stderr, "%s: exiting.\n", program_name) ;
return ret ;
}
void parse_command_line(int argc, char **argv)
{
parse_options(argc, argv) ;
check_configuration() ;
}
void parse_options(int argc, char **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, 0) ;
break ;
case 't' :
options.http_port = strtoul(optarg, NULL, 0) ;
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 ;
}
}
/*
* 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 *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()
{
struct sockaddr_in server_addr ;
tcp_sockfd = socket(AF_INET, SOCK_STREAM, 0) ;
if (tcp_sockfd < 0)
{
perror("Error opening the TCP socket") ;
return OWL_ERR_SOCKET_CREATE ;
}
bzero((char *) &server_addr, sizeof(server_addr)) ;
server_addr.sin_family = AF_INET ;
server_addr.sin_addr.s_addr = INADDR_ANY ;
server_addr.sin_port = htons(options.http_port) ;
if (bind(tcp_sockfd, (struct sockaddr *) &server_addr,
sizeof(server_addr)) < 0)
{
perror("Error binding the TCP socket") ;
return OWL_ERR_SOCKET_CREATE ;
}
return 0 ;
}
/*
* Waits for requests from HTTP clients.
*/
void* tcp_server(void *NULL_value)
{
int newsockfd ;
socklen_t client_len ;
struct sockaddr_in client_addr ;
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) ;
client_len = sizeof(client_addr) ;
// 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,
(struct sockaddr*) &client_addr, &client_len) ;
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 (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 (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.
*/
int
extract_request_from_message(char client_request[CLIENT_REQUEST_STRLEN],
char *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(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;NoResult" ;
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;NoResult" ;
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(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-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
) ;
}