owlps/owlps-client/owlps-client.c

768 lines
22 KiB
C

/*
* This file is part of the Owl Positioning System (OwlPS).
* OwlPS is a project of the University of Franche-Comte
* (Université de Franche-Comté), France.
*
* Copyright © Université de Franche-Comté 2007-2012.
*
* Corresponding author: Matteo Cypriani <mcy@lm7.fr>
*
***********************************************************************
*
* This software is governed by the CeCILL license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL:
* http://www.cecill.info
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided
* only with a limited warranty and the software's authors, the holder
* of the economic rights, and the successive licensors have only
* limited liability.
*
* In this respect, the user's attention is drawn to the risks
* associated with loading, using, modifying and/or developing or
* reproducing the software by the user in light of its specific status
* of free software, that may mean that it is complicated to manipulate,
* and that also therefore means that it is reserved for developers and
* experienced professionals having in-depth computer knowledge. Users
* are therefore encouraged to load and test the software's suitability
* as regards their requirements in conditions enabling the security of
* their systems and/or data to be ensured and, more generally, to use
* and operate it in the same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
*
***********************************************************************
*
* This is the main source file of OwlPS Client, the program used to
* send positioning requests to the infrastructure.
*/
#include <owlps-client.h>
#ifdef ENABLE_RECEIVE_POSITION
# include <owlps-resultreader.h>
#endif // ENABLE_RECEIVE_POSITION
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <string.h>
#include <signal.h>
#include <net/if.h>
/* Number of packets to send */
#define DEFAULT_NBPKT_CALIB 20 // 20 packets when calibrating
#define DEFAULT_NBPKT_NORMAL 10 // 10 packets when requesting the position
/* Delay between two packet transmissions (in milliseconds) */
#define DEFAULT_DELAY_CALIB 50 // Calibration request
#define DEFAULT_DELAY_NORMAL 25 // Localisation request
/* Delay between two requests in loop mode (in milliseconds) */
#define DEFAULT_FLOOD_DELAY 1000
/* Maximal size of a packet */
#define MAX_PKT_SIZE 1450u
/* Program arguments (getopt string) */
#define OPTIONS "DF::hi:I:l::n:N:p:s:t:V"
/* Function headers */
void parse_command_line(int argc, char **argv) ;
void parse_main_options(int argc, char **argv) ;
void check_destination_ip(void) ;
void parse_calibration_data(int argc, char **argv) ;
void check_configuration(void) ;
#ifdef DEBUG
void print_configuration(void) ;
#endif // DEBUG
void create_socket(void) ;
void send_request(void) ;
void make_packet(void) ;
void add_padding(void) ;
uint_fast16_t initialise_common_fields(uint_fast8_t packet_type) ;
uint_fast16_t initialise_calibration_fields(uint_fast16_t offset) ;
#ifdef ENABLE_RECEIVE_POSITION
int receive_position(void) ;
#endif // ENABLE_RECEIVE_POSITION
void print_usage(void) ;
void print_version(void) ;
/* Options */
struct {
owl_bool daemon ;
char dest_ip[INET_ADDRSTRLEN] ; // Destination IP of the packets
uint_fast16_t dest_port ;
char iface[IFNAMSIZ + 1] ; // Source network interface
int_fast32_t delay ; // Time between two packet transmissions
uint_fast16_t nb_pkt ; // Number of packets to send
uint_fast16_t pkt_size ; // Size of the packet to send
int_fast32_t flood_delay ; // Time between two request transmissions
uint_fast16_t nb_requests ; // Number of requests to send
uint_fast16_t listening_port ;
// Calibration data:
owl_direction direction ;
float x ;
float y ;
float z ;
} options = {
owl_false, // daemon
"", // dest_ip
OWL_DEFAULT_REQUEST_PORT, // dest_port
"", // iface
-1, // delay
0, // nb_pkt
0, // pkt_size
-1, // flood_delay
0, // nb_requests
0, // listening_port
0, 0, 0, 0 // Calibration data
} ;
char *program_name = NULL ;
// owl_true if the packet is a calibration request, owl_false if it is
// a simple positioning request:
owl_bool is_calibration_request = owl_false ;
int sockfd ; // Sending socket descriptor
struct sockaddr_in server ; // Server info
uint8_t *packet = NULL ; // Packet to send
uint_fast16_t packet_size ; // Packet size
int main(int argc, char *argv[])
{
struct sigaction action ; // Signal handler structure
int ret = 0 ;
// Number of requests we still have to transmit:
uint_fast16_t nb_requests_left = 1 ;
program_name = argv[0] ;
parse_command_line(argc, argv) ;
if (options.daemon)
{
fprintf(stderr, "Detaching to background...\n") ;
if (daemon(0, 0))
perror("Cannot daemonize") ;
}
/* 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) ;
create_socket() ;
send_request() ;
if (options.nb_requests)
nb_requests_left = options.nb_requests - 1 ;
while (owl_run && options.flood_delay >= 0 && nb_requests_left > 0)
{
owl_msleep(options.flood_delay) ;
if (owl_run) // owl_run can have been set to false during the sleep
{
send_request() ;
if (options.nb_requests)
--nb_requests_left ;
}
}
close(sockfd) ;
#ifdef ENABLE_RECEIVE_POSITION
if (options.listening_port > 0)
ret = receive_position() ;
#endif // ENABLE_RECEIVE_POSITION
return ret ;
}
void parse_command_line(int argc, char **argv)
{
parse_main_options(argc, argv) ;
check_destination_ip() ;
parse_calibration_data(argc, argv) ;
check_configuration() ;
#ifdef DEBUG
print_configuration() ;
#endif // DEBUG
}
void parse_main_options(int argc, char **argv)
{
int opt ;
while ((opt = getopt(argc, argv, OPTIONS)) != -1)
{
switch (opt)
{
case 'D' :
options.daemon = owl_true ;
break ;
case 'F' :
/* Facultative getopt options do not handle separated values
* (like -F <delay>), so we have to test separately.
*/
if (optarg) // We got an option like -F<delay>, it's OK
options.flood_delay = strtoul(optarg, NULL, 0) ;
else // We got -F alone or -F <port>
{
/* If we are at the end of the string, or the next optind
* is an option, we have the option without argument */
if (argv[optind] == NULL || argv[optind][0] == '-')
// Take the default value:
options.flood_delay = DEFAULT_FLOOD_DELAY ;
else
{
// Take the optind value:
options.flood_delay =
strtoul(argv[optind], NULL, 0) ;
++optind ;
}
}
break ;
case 'h' :
print_usage() ;
exit(0) ;
case 'I' :
strncpy(options.iface, optarg, IFNAMSIZ + 1) ;
break ;
case 'i' :
strncpy(options.dest_ip, optarg, INET_ADDRSTRLEN) ;
break ;
case 'l' :
#ifdef ENABLE_RECEIVE_POSITION
/* Facultative getopt options do not handle separated values
* (like -l <port>), so we have to test separately.
*/
if (optarg) // We got an option like -l<port>, it's OK
options.listening_port = strtoul(optarg, NULL, 0) ;
else // We got -l alone or -l <port>
{
/* If we are at the end of the string, or the next optind
* is an option, we have -l without a port number */
if (argv[optind] == NULL || argv[optind][0] == '-')
// Take the default value:
options.listening_port = OWL_DEFAULT_RESULT_PORT ;
else
{
// Take the optind value:
options.listening_port =
strtoul(argv[optind], NULL, 0) ;
++optind ;
}
}
#else // ENABLE_RECEIVE_POSITION
fprintf(stderr, "Warning! The program was compiled without"
" enabling the -l option (receive the position from"
" the positioning server).\n") ;
#endif // ENABLE_RECEIVE_POSITION
break ;
case 'n' :
options.nb_pkt = strtoul(optarg, NULL, 0) ;
break ;
case 'N' :
options.nb_requests = strtoul(optarg, NULL, 0) ;
break ;
case 'p' :
options.dest_port = strtoul(optarg, NULL, 0) ;
break ;
case 's' :
options.pkt_size = strtoul(optarg, NULL, 0) ;
break ;
case 't' :
options.delay = strtol(optarg, NULL, 0) ;
break ;
case 'V' :
print_version() ;
exit(0) ;
default :
print_usage() ;
exit(OWL_ERR_BAD_USAGE) ;
}
}
}
void check_destination_ip()
{
/* Check if we got a destination IP address */
if (options.dest_ip[0] == '\0')
{
fprintf(stderr, "Error! You must specify a destination IP address"
" (-i) .\n") ;
print_usage() ;
exit(OWL_ERR_BAD_USAGE) ;
}
}
void parse_calibration_data(int argc, char **argv)
{
/* Parse remaining arguments (possible calibration data) */
if (argc - optind != 0)
{
if (argc - optind == 4)
{
is_calibration_request = owl_true ;
options.direction = strtoul(argv[optind++], NULL, 0) ;
options.x = strtod(argv[optind++], NULL) ;
options.y = strtod(argv[optind++], NULL) ;
options.z = strtod(argv[optind], NULL) ;
}
else // Bad number of arguments
{
print_usage() ;
exit(OWL_ERR_BAD_USAGE) ;
}
}
}
void check_configuration()
{
// Delay not specified (or bad delay):
if (options.delay < 0)
{
#ifdef DEBUG
fprintf(stderr,
"Warning! delay: failing back to default value.\n") ;
#endif // DEBUG
if (is_calibration_request)
options.delay = DEFAULT_DELAY_CALIB ;
else
options.delay = DEFAULT_DELAY_NORMAL ;
}
// Number of packet not specified (or bad number)
if (options.nb_pkt < 1)
{
#ifdef DEBUG
fprintf(stderr,
"Warning! nb_pkt: failing back to default value.\n") ;
#endif // DEBUG
if (is_calibration_request)
options.nb_pkt = DEFAULT_NBPKT_CALIB ;
else
options.nb_pkt = DEFAULT_NBPKT_NORMAL ;
}
// Packet size too big
if (options.pkt_size > MAX_PKT_SIZE)
{
#ifdef DEBUG
fprintf(stderr,
"Warning! pkt_size cannot be greater than %d bytes:"
" failing back to %d.\n", MAX_PKT_SIZE, MAX_PKT_SIZE) ;
#endif // DEBUG
options.pkt_size = MAX_PKT_SIZE ;
}
// Calibration request but bad direction
if (is_calibration_request)
if (options.direction < OWL_DIRECTION_MIN ||
options.direction > OWL_DIRECTION_MAX)
{
fprintf(stderr, "Error! \"%"PRIu8"\" is not a valid"
" direction.\n", options.direction) ;
exit(OWL_ERR_BAD_USAGE) ;
}
// Check port numbers
if (options.dest_port < 1 || options.dest_port > 65535)
{
#ifdef DEBUG
fprintf(stderr, "Warning! Bad dest_port:"
" failing back to default value.\n") ;
options.dest_port = OWL_DEFAULT_REQUEST_PORT ;
#endif // DEBUG
}
if (options.listening_port > 65535)
{
#ifdef DEBUG
fprintf(stderr, "Warning! listening_port too high: ignored.\n") ;
options.listening_port = 0 ;
#endif // DEBUG
}
// We want to send a calibration request AND to be located, which is
// not allowed:
if (is_calibration_request && options.listening_port > 0)
{
#ifdef DEBUG
fprintf(stderr, "Warning! You cannot wait for a server answer when"
" you calibrate. Option -l ignored.\n") ;
#endif // DEBUG
options.listening_port = 0 ;
}
if (options.flood_delay >= 0)
{
// We want to flood AND to be located, which is not allowed:
if (options.listening_port > 0)
{
#ifdef DEBUG
fprintf(stderr, "Warning! You cannot wait for a server answer"
" when you flood. Option -l ignored.\n") ;
#endif // DEBUG
options.listening_port = 0 ;
}
}
else // Flood is unactivated
{
if (options.nb_requests)
{
#ifdef DEBUG
fprintf(stderr, "Warning! The -N option can be used only along"
" with -F. Option -N ignored.\n") ;
#endif // DEBUG
options.nb_requests = 0 ;
}
if (options.daemon)
{
#ifdef DEBUG
fprintf(stderr, "Warning! It is useless to detach from"
" the foreground if the flood mode is not activated"
" Option -D ignored.\n") ;
#endif // DEBUG
options.daemon = owl_false ;
}
}
}
#ifdef DEBUG
void print_configuration()
{
fprintf(stderr, "Options:\n"
"\tDestination IP: %s\n"
"\tDestination port: %"PRIuFAST16"\n"
"\tInterface: %s\n"
"\tDelay (ms): %"PRIdFAST32"\n"
"\tNumber of packets: %"PRIuFAST16"\n"
"\tFlood delay (ms): %"PRIdFAST32"\n"
"\tNumber of requests: %"PRIuFAST16"\n"
"\tListening port: %"PRIuFAST16"\n"
"\tDirection: %"PRIu8"\n"
"\tX: %f\n"
"\tY: %f\n"
"\tZ: %f\n"
,
options.dest_ip,
options.dest_port,
options.iface,
options.delay,
options.nb_pkt,
options.flood_delay,
options.nb_requests,
options.listening_port,
options.direction,
options.x,
options.y,
options.z
) ;
}
#endif // DEBUG
void create_socket()
{
sockfd =
owl_create_trx_socket(options.dest_ip, options.dest_port,
&server, options.iface) ;
}
/*
* Prepares a new request and sends it.
*/
void send_request()
{
make_packet() ;
owl_send_request(sockfd, &server, packet, packet_size,
options.nb_pkt, options.delay) ;
free(packet) ;
}
/*
* Creates the packet to send.
*/
void make_packet()
{
uint_fast16_t offset ; // Index used to create the packet
if (is_calibration_request) // Calibration packet
{
printf("Preparing calibration request packet...\n") ;
packet_size =
sizeof(uint8_t) * 2 + sizeof(owl_timestamp) + sizeof(float) * 3 +
sizeof(uint16_t) * 2 ;
add_padding() ;
packet = malloc(packet_size) ;
offset = initialise_common_fields(OWL_REQUEST_CALIBRATION) ;
offset += initialise_calibration_fields(offset) ;
}
else // Standard packet
{
printf("Preparing request packet...\n") ;
packet_size =
sizeof(uint8_t) + sizeof(owl_timestamp) + sizeof(uint16_t) * 2 ;
add_padding() ;
packet = malloc(packet_size) ;
offset = initialise_common_fields(OWL_REQUEST_NORMAL) ;
}
// Initialize padding bytes with 0xFF:
while (offset < packet_size)
packet[offset++] = 0xFF ;
}
/*
* Increases the packet size to add padding.
*/
void add_padding()
{
if (options.pkt_size > packet_size)
packet_size = options.pkt_size ;
printf("Packet size: %"PRIuFAST16"\n", packet_size) ;
}
/*
* Initialises the fields of a normal positioning request.
*/
uint_fast16_t initialise_common_fields(uint_fast8_t packet_type)
{
uint_fast16_t offset = 0 ;
uint16_t npkt ;
owl_timestamp request_time ;
char request_time_str[OWL_TIMESTAMP_STRLEN] ;
// Get the current time and copy it as a string before to switch it to
// network endianess:
owl_timestamp_now(&request_time) ;
owl_timestamp_to_string(&request_time, request_time_str) ;
owl_hton_timestamp(&request_time) ;
// Packet type:
memset(&packet[offset++], packet_type, 1) ;
// Number of the current packet (1 for the first):
npkt = htons(1u) ;
memcpy(&packet[offset], &npkt, sizeof(uint16_t)) ;
offset += sizeof(uint16_t) ;
// Number of packets:
npkt = htons(options.nb_pkt) ;
memcpy(&packet[offset], &npkt, sizeof(uint16_t)) ;
offset += sizeof(uint16_t) ;
// Request time:
memcpy(&packet[offset], &request_time, sizeof(request_time)) ;
offset += sizeof(request_time) ;
printf("Packet timestamp: %s\n", request_time_str) ;
return offset ;
}
/*
* Initialises the calibration data fields.
*/
uint_fast16_t initialise_calibration_fields(uint_fast16_t offset)
{
float x, y, z ;
// Direction:
packet[offset++] = options.direction ;
#ifdef DEBUG
printf("Direction = %d, X = %f, Y = %f, Z = %f\n",
packet[offset - 1], options.x, options.y, options.z) ;
#endif // DEBUG
// Convert the coordinates to the network endianess:
x = owl_htonf(options.x) ;
y = owl_htonf(options.y) ;
z = owl_htonf(options.z) ;
// Copy the coordinates to the packet:
memcpy(&packet[offset], &x, sizeof(float)) ;
offset += sizeof(float) ;
memcpy(&packet[offset], &y, sizeof(float)) ;
offset += sizeof(float) ;
memcpy(&packet[offset], &z, sizeof(float)) ;
offset += sizeof(float) ;
return offset ;
}
#ifdef ENABLE_RECEIVE_POSITION
/*
* Receives a position computed by the infrastructure.
* Note that it is currently not guaranteed that the received result
* correspond to the request sent.
* Returns 0, or a non-zero value in case of error.
*/
int receive_position()
{
owl_result *result ;
printf("Waiting for the result from the infrastructure...\n") ;
sockfd = owl_create_udp_listening_socket(options.listening_port) ;
if (sockfd < 0)
return OWL_ERR_SOCKET_CREATE ;
result = owl_receive_position(sockfd) ;
if (result == NULL)
return OWL_ERR_SOCKET_RECV ;
close(sockfd) ;
owl_print_result(result) ;
owl_free_result(result) ;
return 0 ;
}
#endif // ENABLE_RECEIVE_POSITION
void print_usage()
{
printf("Usage:\n"
"Localisation request:\n"
"\t%s"
" -i dest_ip"
" [-p dest_port]"
" [-I iface]"
" [-t delay]"
"\n\t"
" [-n nb_packets]"
" [-s packet_size]"
" [-F [delay] [-N nb_requests] [-D]]"
"\n\t"
" [-l [port]]\n"
"Calibration request:\n"
"\t%s"
" -i dest_ip"
" [-p dest_port]"
" [-I iface]"
" [-t delay]"
"\n\t"
" [-n nb_packets]"
" [-s packet_size]"
" [-F [delay] [-N nb_requests] [-D]]"
"\n\t"
" direction x y z\n"
"Options:\n"
"\t-h\t\tPrint this help.\n"
"\t-V\t\tPrint version information.\n"
"\t-i dest_ip\tDestination IP address of the localisation"
" request.\n"
"\t-p dest_port\tDestination port of the localisation request"
" (default:\n\t\t\t%d).\n"
"\t-t delay\tTime between each packet transmission in"
" milliseconds\n\t\t\t(default: %d ms for a normal request,"
" %d ms for a\n\t\t\tcalibration request).\n"
"\t-n nb_packets\tNumber of packet transmitted for the request"
" (default:\n\t\t\t%d for a normal request, %d for a"
" calibration request).\n"
"\t-s packet_size\tData size of the transmitted packets. The"
" minimal value\n\t\t\tis the size of the request's data"
" fields; if\n\t\t\t<packet_size> is less than this size, it is"
" ignored.\n\t\t\tNote that this size does not take into"
" account the\n\t\t\theaders, so the whole 802.11 frame will be"
" bigger.\n"
"\t-I iface\tName of the network interface used to transmit the"
"\n\t\t\trequest (e.g. \"eth2\"). If this option is absent, the"
"\n\t\t\tinterface is selected automatically. You must be root"
" to\n\t\t\tuse this option.\n"
"\t-F [delay]\t\"Flood mode\": loop indefinitely, sending a"
" new request\n\t\t\tevery <delay> milliseconds (default:"
" %d ms).\n"
"\t-N nb_requests\tWith -F, stop after <nb_requests> requests"
" transmitted\n\t\t\tinstead of looping indefinitely.\n"
"\t-D\t\tDaemon mode. Useful only in flood mode.\n"
"\t-l [port]\tWait for the computed position and display it."
" The\n\t\t\toptional argument <port> allows to specify the"
" listening\n\t\t\tport (default: %d). Available only if the"
" program was\n\t\t\tcompiled with the compilation-time option"
"\n\t\t\tENABLE_RECEIVE_POSITION.\n",
program_name,
program_name,
OWL_DEFAULT_REQUEST_PORT,
DEFAULT_DELAY_NORMAL,
DEFAULT_DELAY_CALIB,
DEFAULT_NBPKT_NORMAL,
DEFAULT_NBPKT_CALIB,
DEFAULT_FLOOD_DELAY,
OWL_DEFAULT_RESULT_PORT
) ;
}
void print_version()
{
printf("This is OwlPS Client, part of the Owl"
" Positioning System project.\n"
"Version: %s.\n"
"Compilation-time options:\n"
"\tOption -l: %s.\n",
#ifdef OWLPS_VERSION
OWLPS_VERSION
#else // OWLPS_VERSION
"unknown version"
#endif // OWLPS_VERSION
,
#ifdef ENABLE_RECEIVE_POSITION
"YES"
#else // ENABLE_RECEIVE_POSITION
"NO"
#endif // ENABLE_RECEIVE_POSITION
) ;
}