/* * 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 Client, the program used to * send positioning requests to the infrastructure. */ #include #include #ifdef OWLPS_CLIENT_RECEIVES_POSITION # include #endif // OWLPS_CLIENT_RECEIVES_POSITION #include #include #include #include #include #include #include #include // for MAXHOSTNAMELEN /* 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:qs:t:vV" /* Function headers */ void parse_command_line(const int argc, char *const *argv) ; void parse_main_options(const int argc, char *const *argv) ; void check_destination_host(void) ; void parse_calibration_data(const int argc, char *const *argv) ; void check_configuration(void) ; void print_configuration(void) ; void create_socket(void) ; void send_request(void) ; void make_packet(void) ; void add_padding(void) ; uint_fast16_t initialise_common_fields(const uint_fast8_t packet_type) ; uint_fast16_t initialise_calibration_fields(uint_fast16_t offset) ; #ifdef OWLPS_CLIENT_RECEIVES_POSITION int receive_position(void) ; #endif // OWLPS_CLIENT_RECEIVES_POSITION void print_usage(void) ; void print_version(void) ; /* Options */ // TODO: the size of this structure could be reduced by reordering its fields. struct { bool daemon ; bool verbose ; char dest_host[MAXHOSTNAMELEN] ; // Destination host of the packets // 6 bytes alignment uint_fast16_t dest_port ; char iface[IFNAMSIZ + 1] ; // Source network interface // 7 bytes alignment 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 bool add_flood_delay ; // Add the delay to the transmission time? // 7 bytes alignment uint_fast16_t nb_requests ; // Number of requests to send uint_fast16_t listening_port ; // Calibration data: owl_direction direction ; // 3 bytes alignment float x ; float y ; float z ; } options = { false, // daemon true, // verbose "", // dest_host OWL_DEFAULT_REQUEST_PORT, // dest_port "", // iface -1, // delay 0, // nb_pkt 0, // pkt_size -1, // flood_delay false, // add_flood_delay 0, // nb_requests 0, // listening_port 0, 0, 0, 0 // Calibration data } ; char *program_name = NULL ; // true if the packet is a calibration request, false if it is // a simple positioning request: bool is_calibration_request = false ; int sockfd ; // Sending socket descriptor struct sockaddr 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 ; owl_timestamp start_time ; // Time of the transmission's start owl_run = true ; program_name = argv[0] ; parse_command_line(argc, argv) ; if (options.daemon) { if (options.verbose) 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() ; /* Transmit the first request */ if (! options.add_flood_delay) owl_timestamp_now(&start_time) ; send_request() ; if (options.nb_requests) nb_requests_left = options.nb_requests - 1 ; /* Transmit the next requests, if any */ while (owl_run && options.flood_delay >= 0 && nb_requests_left > 0) { uint_fast32_t trx_time, sleep_time ; owl_timestamp now ; if (options.verbose && options.nb_requests) printf("%"PRIuFAST16" more requests to transmit.\n", nb_requests_left) ; if (options.add_flood_delay) owl_msleep(options.flood_delay) ; else { owl_timestamp_now(&now) ; trx_time = owl_time_elapsed_ms(&start_time, &now) ; if (options.verbose) printf("Transmission time was %"PRIuFAST32" ms", trx_time) ; // Sleep only if the sleep delay is greater than the // transmission time if (trx_time < (uint_fast32_t) options.flood_delay) { sleep_time = options.flood_delay - trx_time ; if (options.verbose) printf(", sleeping for %"PRIuFAST32" ms...\n", sleep_time) ; owl_msleep(sleep_time) ; } else if (options.verbose) printf(" > %"PRIdFAST32" ms, no sleeping required.\n", options.flood_delay) ; owl_timestamp_now(&start_time) ; } 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 OWLPS_CLIENT_RECEIVES_POSITION if (options.listening_port > 0) ret = receive_position() ; #endif // OWLPS_CLIENT_RECEIVES_POSITION return ret ; } void parse_command_line(const int argc, char *const *argv) { parse_main_options(argc, argv) ; check_destination_host() ; parse_calibration_data(argc, argv) ; check_configuration() ; if (options.verbose) print_configuration() ; } void parse_main_options(const int argc, char *const *argv) { int opt ; long arg_long ; // Integer value of optarg char *endptr ; // Return value of strto*() while ((opt = getopt(argc, argv, OPTIONS)) != -1) { switch (opt) { case 'D' : options.daemon = true ; break ; case 'F' : /* If -F is present, first set the flood delay to its default * value (this can appear suboptimal when the user supplies a * value, but will simplify the operations). */ options.flood_delay = DEFAULT_FLOOD_DELAY ; /* Facultative getopt options do not handle separated values * (like -F ), so we have to test separately. */ if (optarg) // We got an option like -F, it's OK { arg_long = strtol(optarg, &endptr, 10) ; if (endptr != optarg) options.flood_delay = arg_long ; else fprintf(stderr, "Warning! Bad flood_delay:" " failing back to the default value.\n") ; if (optarg[0] == '+') options.add_flood_delay = true ; } else // We got -F alone or -F { /* If we are not at the end of the string and the next * optind is not an option, we have the option with a * separate argument. Otherwise we got the option alone, * but we have nothing to do since we already set the * default value. */ if (argv[optind] != NULL && argv[optind][0] != '-') { // Take the optind value: arg_long = strtol(argv[optind], &endptr, 10) ; if (endptr != argv[optind]) options.flood_delay = arg_long ; else fprintf(stderr, "Warning! Bad flood_delay:" " failing back to the default value.\n") ; if (argv[optind][0] == '+') options.add_flood_delay = true ; ++optind ; } } break ; case 'h' : print_usage() ; exit(0) ; case 'I' : strncpy(options.iface, optarg, IFNAMSIZ + 1) ; break ; case 'i' : strncpy(options.dest_host, optarg, MAXHOSTNAMELEN) ; break ; case 'l' : #ifdef OWLPS_CLIENT_RECEIVES_POSITION /* If -l is present, first set the listening port to its * default value (this can appear suboptimal when the user * supplies a port number, but will simplify the operations). */ options.listening_port = OWL_DEFAULT_RESULT_PORT ; /* Facultative getopt options do not handle separated values * (like -l ), so we have to test separately. */ if (optarg) // We got an option like -l, it's OK { unsigned long arg_ulong = strtoul(optarg, &endptr, 10) ; if (endptr != optarg) options.listening_port = arg_ulong ; else fprintf(stderr, "Warning! Bad listening_port:" " failing back to the default value.\n") ; } else // We got -l alone or -l { /* If we are not at the end of the string and the next * optind is not an option, we have the option with a * separate argument. Otherwise we got the option alone, * but we have nothing to do since we already set the * default value. */ if (argv[optind] != NULL && argv[optind][0] != '-') { // Take the optind value: unsigned long arg_ulong = strtoul(argv[optind], &endptr, 10) ; if (endptr != argv[optind]) options.listening_port = arg_ulong ; else fprintf(stderr, "Warning! Bad listening_port:" " failing back to the default value.\n") ; ++optind ; } } #else // OWLPS_CLIENT_RECEIVES_POSITION fprintf(stderr, "Warning! The program was compiled without" " enabling the -l option (receive the position from" " the positioning server).\n") ; #endif // OWLPS_CLIENT_RECEIVES_POSITION break ; case 'n' : options.nb_pkt = strtoul(optarg, NULL, 10) ; break ; case 'N' : options.nb_requests = strtoul(optarg, NULL, 10) ; break ; case 'p' : options.dest_port = strtoul(optarg, NULL, 10) ; break ; case 'q' : options.verbose = false ; break ; case 's' : options.pkt_size = strtoul(optarg, NULL, 10) ; break ; case 't' : arg_long = strtol(optarg, &endptr, 10) ; if (endptr != optarg) options.delay = arg_long ; else fprintf(stderr, "Warning! Bad delay:" " failing back to the default value.\n") ; break ; case 'v' : options.verbose = true ; break ; case 'V' : print_version() ; exit(0) ; default : print_usage() ; exit(OWL_ERR_BAD_USAGE) ; } } } void check_destination_host() { /* Check if we got a destination host */ if (options.dest_host[0] == '\0') { fprintf(stderr, "Error! You must specify a destination host" " (-i) .\n") ; print_usage() ; exit(OWL_ERR_BAD_USAGE) ; } } /* Parses remaining arguments (possible calibration data) */ void parse_calibration_data(const int argc, char *const *argv) { /* No more arguments to parse */ if (argc - optind == 0) return ; /* Exactly 4 more arguments */ if (argc - optind == 4) { char *endptr ; is_calibration_request = true ; options.direction = strtoul(argv[optind], &endptr, 10) ; if (endptr == argv[optind]) { fprintf(stderr, "Error in calibration data: wrong direction!\n") ; goto error ; } optind++ ; options.x = strtod(argv[optind], &endptr) ; if (endptr == argv[optind]) { fprintf(stderr, "Error in calibration data: wrong X coordinate!\n") ; goto error ; } optind++ ; options.y = strtod(argv[optind], &endptr) ; if (endptr == argv[optind]) { fprintf(stderr, "Error in calibration data: wrong Y coordinate!\n") ; goto error ; } optind++ ; options.z = strtod(argv[optind], &endptr) ; if (endptr == argv[optind]) { fprintf(stderr, "Error in calibration data: wrong Z coordinate!\n") ; goto error ; } return ; // No error occurred } /* Bad number of arguments or parse error */ error: print_usage() ; exit(OWL_ERR_BAD_USAGE) ; } void check_configuration() { // Delay not specified (or bad delay): if (options.delay < 0) { if (options.verbose) fprintf(stderr, "Warning! delay: failing back to default value.\n") ; 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) { if (options.verbose) fprintf(stderr, "Warning! nb_pkt: failing back to default value.\n") ; 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) { if (options.verbose) fprintf(stderr, "Warning! pkt_size cannot be greater than %d bytes:" " failing back to %d.\n", MAX_PKT_SIZE, MAX_PKT_SIZE) ; 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) { fprintf(stderr, "Warning! Bad dest_port:" " failing back to default value.\n") ; options.dest_port = OWL_DEFAULT_REQUEST_PORT ; } if (options.listening_port > 65535) { fprintf(stderr, "Warning! listening_port too high: ignored.\n") ; options.listening_port = 0 ; } // We want to send a calibration request AND to be located, which is // not allowed: if (is_calibration_request && options.listening_port > 0) { fprintf(stderr, "Warning! You cannot wait for a server answer when" " you calibrate. Option -l ignored.\n") ; 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) { fprintf(stderr, "Warning! You cannot wait for a server answer" " when you flood. Option -l ignored.\n") ; options.listening_port = 0 ; } } else // Flood is unactivated { if (options.nb_requests) { fprintf(stderr, "Warning! The -N option can be used only along" " with -F. Option -N ignored.\n") ; options.nb_requests = 0 ; } if (options.daemon) { fprintf(stderr, "Warning! It is useless to detach from" " the foreground if the flood mode is not activated" " Option -D ignored.\n") ; options.daemon = false ; } } } void print_configuration() { printf("Options:\n" "\tDaemon: %s\n" "\tVerbose: %s\n" "\tDestination host: %s\n" "\tDestination port: %"PRIuFAST16"\n" "\tInterface: %s\n" "\tDelay (ms): %"PRIdFAST32"\n" "\tNumber of packets: %"PRIuFAST16"\n" "\tPacket size: %"PRIuFAST16"\n" "\tFlood delay (ms): %"PRIdFAST32"\n" "\tCumulative flood delay: %s\n" "\tNumber of requests: %"PRIuFAST16"\n" "\tListening port: %"PRIuFAST16"\n" "\tDirection: %"PRIu8"\n" "\tX: %f\n" "\tY: %f\n" "\tZ: %f\n" , OWL_BOOL_TO_STRING(options.daemon), OWL_BOOL_TO_STRING(options.verbose), options.dest_host, options.dest_port, options.iface, options.delay, options.nb_pkt, options.pkt_size, options.flood_delay, OWL_BOOL_TO_STRING(options.add_flood_delay), options.nb_requests, options.listening_port, options.direction, options.x, options.y, options.z ) ; } void create_socket() { sockfd = owl_create_trx_socket(options.dest_host, 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, options.verbose) ; 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 { if (options.verbose) printf("\nPreparing 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) ; if (! packet) { perror("Cannot allocate memory") ; abort() ; } offset = initialise_common_fields(OWL_REQUEST_CALIBRATION) ; offset += initialise_calibration_fields(offset) ; } else // Standard packet { if (options.verbose) printf("\nPreparing request packet...\n") ; packet_size = sizeof(uint8_t) + sizeof(owl_timestamp) + sizeof(uint16_t) * 2 ; add_padding() ; packet = malloc(packet_size) ; if (! packet) { perror("Cannot allocate memory") ; abort() ; } 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 ; if (options.verbose) printf("Packet size: %"PRIuFAST16"\n", packet_size) ; } /* * Initialises the fields of a normal positioning request. */ uint_fast16_t initialise_common_fields(const 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 endianness: 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) ; if (options.verbose) 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 ; if (options.verbose) printf("Direction = %d, X = %f, Y = %f, Z = %f\n", packet[offset - 1], options.x, options.y, options.z) ; // Convert the coordinates to the network endianness: 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 OWLPS_CLIENT_RECEIVES_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 // OWLPS_CLIENT_RECEIVES_POSITION /* * 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" "Localisation request:\n" "\t%s" " [-v | -q]" " -i dest_host" " [-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" " [-v | -q]" " -i dest_host" " [-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-v\t\tTurn on verbose mode (default).\n" "\t-q\t\tDo not print informational messages and some (less\n" "\t\t\timportant) warnings.\n" "\t-i dest_host\tName or IP address of the destination host of" " the\n\t\t\tlocalisation 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 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 milliseconds (default:" " %d ms). If\n\t\t\t starts with a +, it is the time" " between two\n\t\t\trequests instead of the time between the" " start of the\n\t\t\ttransmission of two requests.\n" "\t-N nb_requests\tWith -F, stop after 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 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\tOWLPS_CLIENT_RECEIVES_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 OWLPS_CLIENT_RECEIVES_POSITION "YES" #else // OWLPS_CLIENT_RECEIVES_POSITION "NO" #endif // OWLPS_CLIENT_RECEIVES_POSITION ) ; }