/* * 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 libowlps-resultreader. */ #define _GNU_SOURCE // for strndup() #include "owlps-resultreader.h" #include #include #include #include #define CSV_DELIMITER ";" /** * Receives a result from the socket of file descriptor `sockfd`, fills * an `owl_result` structure with the received information, and returns a * pointer to it. * * Note that the new owl_result is allocated with `malloc()` and must be * deleted using `owl_free_result()`. * * In case of error, a message is printed, except if `owl_run` (from * owlps.h) is `false`, and `NULL` is returned. * * @returns A pointer to a new `owl_result`. * @returns `NULL` in case of error. */ owl_result* owl_receive_position(const int sockfd) { ssize_t nread ; // recvfrom return value char csv[OWL_CSV_RESULT_STRLEN] ; // Read string nread = recvfrom(sockfd, &csv, OWL_CSV_RESULT_STRLEN, 0, NULL, NULL) ; if (nread <= 0) { if (owl_run) perror("No request received from listener") ; return NULL ; } return owl_fill_result(csv) ; } /** * Splits the `csv` string received from OwlPS Positioner and stores * the fields in a new `owl_result`, and returns a pointer to it (or * `NULL` in case of error). Because this function uses strsep(), the * `csv` string is modified; it should be copied in the calling function * if preservation of the original value is desired. * * Note that the new `owl_result` is allocated with `malloc()` and must * be deleted using `owl_free_result()`. * * Handled CSV format: * * Mobile_MAC;Request_type;Request_timestamp;Algorithm;X;Y;Z;Error;Area * * The Request_timestamp format is: * * seconds.nanoseconds * * The *Area* field can be empty. * * @returns A pointer to a new `owl_result`. * @returns `NULL` in case of error. */ owl_result* owl_fill_result(char *csv) { char *csv_field = NULL ; long longfield ; // Return value of owl_read_long_field() owl_result *result = NULL ; result = malloc(sizeof(*result)) ; if (! result) { perror("Cannot allocate memory") ; return NULL ; } memset(result, 0, sizeof(*result)) ; /* Mobile MAC address */ csv_field = strsep(&csv, CSV_DELIMITER) ; if (! csv_field) { fprintf(stderr, "Error reading the mobile's MAC address from the CSV" " string (empty string?)!\n") ; goto error ; } if (strnlen(csv_field, OWL_ETHER_ADDR_STRLEN) != OWL_ETHER_ADDR_STRLEN - 1) { csv_field[OWL_ETHER_ADDR_STRLEN-1] = '\0' ; fprintf(stderr, "The string \"%s\" is not a valid MAC address!\n", csv_field) ; goto error ; } strncpy(result->mobile_mac_addr, csv_field, OWL_ETHER_ADDR_STRLEN) ; /* Request type */ if (owl_read_long_field(&csv, CSV_DELIMITER, &longfield)) { fprintf(stderr, "Error reading the request type from the CSV string!\n") ; goto error ; } if (! OWL_IS_REQUEST_TYPE(longfield)) { fprintf(stderr, "The request type read (%ld) is invalid!\n", longfield) ; goto error ; } result->request_type = longfield ; /* Timestamp */ // Seconds if (owl_read_long_field(&csv, ".", &longfield)) { fprintf(stderr, "Error reading the timestamp (seconds) from the CSV" " string!\n") ; goto error ; } result->mobile_timestamp.tv_sec = longfield ; // Nanoseconds if (owl_read_long_field(&csv, CSV_DELIMITER, &longfield)) { fprintf(stderr, "Error reading the timestamp (nanoseconds) from the CSV" " string!\n") ; goto error ; } result->mobile_timestamp.tv_nsec = longfield ; /* Algorithm results */ do { owl_algorithm_result *current_algo = owl_fill_algorithm_result(&csv) ; if (current_algo == NULL) { fprintf(stderr, "Error reading the algorithm #%d!\n", result->nb_results + 1) ; break ; } // Insert the current algorithm at the begining of the list current_algo->next = result->results ; result->results = current_algo ; ++result->nb_results ; } while (csv) ; return result ; // Success error: owl_free_result(result) ; return NULL ; } /** * Splits the `csv` string, stores the fields in a new * `owl_algorithm_result`, and returns a pointer to it (or `NULL` * in case of error). Because this function uses strsep(), the * `csv` string is modified; it should be copied in the calling function * if preservation of the original value is desired. * * Note that the new owl_algorithm_result is allocated with `malloc()` * and must be deleted using `owl_free_algorithm_result()`. * * Handled CSV format: * * Algorithm;X;Y;Z;Error;Area * * The *Area* field can be empty. * * @returns A pointer to a new `owl_algorithm_result`. * @returns `NULL` in case of error. */ owl_algorithm_result* owl_fill_algorithm_result(char **csv) { owl_algorithm_result *algo ; char *csv_field = NULL ; algo = malloc(sizeof(owl_algorithm_result)) ; if (! algo) { perror("Cannot allocate memory") ; return NULL ; } memset(algo, 0, sizeof(*algo)) ; // Algorithm name csv_field = strsep(csv, CSV_DELIMITER) ; if (! csv_field) { fprintf(stderr, "Error reading the algorithm name from the CSV string!\n") ; goto error ; } algo->algorithm = strndup(csv_field, OWL_ALGORITHM_STRLEN) ; // X coordinate if (owl_read_float_field(csv, &algo->x)) { fprintf(stderr, "Error reading the X coordinate from the CSV string!\n") ; goto error ; } // Y coordinate if (owl_read_float_field(csv, &algo->y)) { fprintf(stderr, "Error reading the Y coordinate from the CSV string!\n") ; goto error ; } // Z coordinate if (owl_read_float_field(csv, &algo->z)) { fprintf(stderr, "Error reading the Z coordinate from the CSV string!\n") ; goto error ; } // Distance error if (owl_read_float_field(csv, &algo->error)) { fprintf(stderr, "Error reading the distance error from the CSV string!\n") ; goto error ; } // Area name (optional) csv_field = strsep(csv, CSV_DELIMITER) ; if (csv_field) algo->area = strndup(csv_field, OWL_AREA_STRLEN) ; return algo ; // Success error: owl_free_algorithm_result(algo) ; return NULL ; } /** * Reads the first field from the `csv` string as a long integer and * stores it in `ret`. The fields of `csv` are separated by the symbols * in the `delim` string. `csv` will be modified to point to the next * field. * Upon error, the value pointed by `ret` may or may not be modified. * * @param[in,out] csv The CSV string. * @param[in] delim The characters delimiting the fields in the string. * @param[out] ret The integer value read from the field. * * @returns 0 in case of success. * @returns 1 if the field could not be read. * @returns 2 if the field could be read but did not contain a valid * value. */ int owl_read_long_field(char **const csv, const char *const delim, long *const ret) { char *endptr = NULL, *csv_field = NULL ; csv_field = strsep(csv, delim) ; if (! csv_field) return 1 ; *ret = strtol(csv_field, &endptr, 10) ; if (endptr == csv_field) return 2 ; return 0 ; } /** * Reads the first field from the `csv` string as a float and stores it * in `ret`. `csv` will be modified to point to the next field. * Upon error, the value pointed by `ret` may or may not be modified. * * @param[in,out] csv The CSV string. * @param[out] ret The float value read from the field. * * @returns 0 in case of success. * @returns 1 if the field could not be read. * @returns 2 if the field could be read but did not contain a valid * value. */ int owl_read_float_field(char **const csv, float *const ret) { char *endptr = NULL, *csv_field = NULL ; csv_field = strsep(csv, CSV_DELIMITER) ; if (! csv_field) return 1 ; *ret = strtof(csv_field, &endptr) ; if (endptr == csv_field) return 2 ; return 0 ; } /** * Converts an `owl_result` back to a CSV string. * `dst` must be an allocated string of at least `OWL_CSV_RESULT_STRLEN` * characters. * * CSV format: * * Mobile_MAC;Request_type;Request_timestamp;Nb_algo;Algo_1;…;Algo_n * * *Nb_algo* is the number of algorithms in the result. * The format of *Algo_i* is documented in the documentation of the * owl_algorithm_result_to_csv() function. */ void owl_result_to_csv(char dst[OWL_CSV_RESULT_STRLEN], const owl_result *const src) { size_t dst_len ; char timestamp_str[OWL_TIMESTAMP_STRLEN] ; assert(src) ; owl_timestamp_to_string(&src->mobile_timestamp, timestamp_str) ; snprintf(dst, OWL_CSV_RESULT_REQUEST_STRLEN, "%s;%"PRIu8";%s;%u", src->mobile_mac_addr, src->request_type, timestamp_str, src->nb_results) ; dst_len = strlen(dst) ; owl_algorithm_result *algo = src->results ; while (algo) { char algo_str[OWL_CSV_ALGORITHM_RESULT_STRLEN] ; owl_algorithm_result_to_csv(algo_str, algo) ; dst[dst_len++] = ';' ; strncpy(dst + dst_len, algo_str, OWL_CSV_ALGORITHM_RESULT_STRLEN) ; dst_len += strlen(algo_str) ; algo = algo->next ; } } /** * Converts an `owl_algorithm_result` back to a CSV string. * `dst` must be an allocated string of at least * `OWL_CSV_ALGORITHM_RESULT_STRLEN` characters. * * CSV 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). */ void owl_algorithm_result_to_csv(char dst[OWL_CSV_ALGORITHM_RESULT_STRLEN], const owl_algorithm_result *const src) { assert(src) ; snprintf(dst, OWL_CSV_ALGORITHM_RESULT_STRLEN, "%s;%f;%f;%f;%f;%s", src->algorithm, src->x, src->y, src->z, src->error, src->area ? src->area : "") ; } /** * Converts an `owl_result` back to a CSV string, in a simplified format: * only the *first* algorithm in the result's algorithm list will be * included in the string. * `dst` must be an allocated string of at least * `OWL_CSV_RESULT_SIMPLE_STRLEN` characters. * * CSV format: * * Mobile_MAC;First_algorithm * * *First_algorithm* is the first algorithm in `src->results`. Its format * is documented in the documentation of the * owl_algorithm_result_to_csv_simple() function. */ void owl_result_to_csv_simple(char dst[OWL_CSV_RESULT_SIMPLE_STRLEN], const owl_result *const src) { size_t dst_len ; char algo_str[OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN] ; assert(src) ; strncpy(dst, src->mobile_mac_addr, OWL_ETHER_ADDR_STRLEN) ; dst[OWL_ETHER_ADDR_STRLEN - 1] = ';' ; dst_len = OWL_ETHER_ADDR_STRLEN ; if (! src->results) return ; owl_algorithm_result_to_csv_simple(algo_str, src->results) ; strncpy(dst + dst_len, algo_str, OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN) ; } /** * Converts an `owl_algorithm_result` back to a CSV string, in a * simplified format. * `dst` must be an allocated string of at least * `OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN` characters. * * CSV format: * * X;Y;Z;Area_name * * *Area_name* is the name of the area or room in which the mobile is * (may be empty). */ void owl_algorithm_result_to_csv_simple (char dst[OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN], const owl_algorithm_result *const src) { assert(src) ; snprintf(dst, OWL_CSV_ALGORITHM_RESULT_SIMPLE_STRLEN, "%f;%f;%f;%s", src->x, src->y, src->z, src->area ? src->area : "") ; } /** * Prints an `owl_result` to the given stream, using `fprintf()`. * `src` must not be `NULL`. */ void owl_fprint_result(FILE *const stream, const owl_result *const src) { char timestamp_str[OWL_TIMESTAMP_STRLEN] ; assert(src) ; owl_timestamp_to_string(&src->mobile_timestamp, timestamp_str) ; fprintf(stream, "Mobile MAC: %s\n" "Request type: %"PRIu8"\n" "Mobile timestamp: %s\n" "%u results:\n" , src->mobile_mac_addr, src->request_type, timestamp_str, src->nb_results) ; owl_algorithm_result *algo = src->results ; while (algo) { owl_fprint_algorithm_result(stream, algo) ; algo = algo->next ; } } /** * Prints an `owl_algorithm_result` to the given stream, using * `fprintf()`. * `src` must not be `NULL`. */ void owl_fprint_algorithm_result(FILE *const stream, const owl_algorithm_result *const src) { assert(src) ; fprintf(stream, "* Algorithm: %s\n" " X: %f\n" " Y: %f\n" " Z: %f\n" " Error: %f\n" " Area: %s\n" , src->algorithm, src->x, src->y, src->z, src->error, src->area ? src->area : "" ) ; } /** * Frees the memory allocated for an `owl_result`. The `results` field * of `result` *must* be defined, either to `NULL` or to a valid memory * block allocated with `malloc()`. * Note that `result` will not be set to `NULL`. */ void owl_free_result(owl_result *const result) { if (! result) return ; while (result->results) { owl_algorithm_result *algo = result->results ; result->results = algo->next ; owl_free_algorithm_result(algo) ; } free(result) ; } /** * Frees the memory allocated for a single `owl_algorithm_result` (*not* * recursively). The `algorithm` and `area` fields of `algo` *must* be * defined, either to `NULL` or to a valid memory block allocated with * `malloc()`. * Note that `algo` will not be set to `NULL`. */ void owl_free_algorithm_result(owl_algorithm_result *const algo) { if (! algo) return ; free(algo->algorithm) ; free(algo->area) ; free(algo) ; }