/* * Copyright (c) 2019 Clementine Computing LLC. * * This file is part of PopuFare. * * PopuFare is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PopuFare is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with PopuFare. If not, see . * */ #include #include #include #include #include #include #include #include #include #include "../common/common_defs.h" #include "commhub.h" // This function gets the number of microseconds since epoch for storing in message headers and // comparison to determine how timely a message is. // long long int get_usec_time() { struct timeval tv; // Get the number of seconds (and remainder microseconds) since epoch. // gettimeofday(&tv,NULL); // Return those in a single sane 64 bit integer containing the total number // of microseconds since epoch by multiplying tv_sec by a million and summing // the two values // return (long long int)tv.tv_usec + (1000000 * (long long int)tv.tv_sec); } // This function does a non-blocking poll to determine if the message passing socket is // ready for input or output (depending on // since it is much more efficient to block on the entire list of your I/O file descriptors // and service any that cause you to unblock. // // Returns: MSG_NO_ACTION = no message // MSG_ERROR = fatal error (passed fd is no good) // // If the socket is ready, the return value will be // whatever combination of events specified in mask (MSG_SEND, MSG_RECV) // that is actually ready. // int message_socket_status(int message_socket_fd, int mask) { struct pollfd fds[1]; int retval; fds[0].fd = message_socket_fd; do { fds[0].events = mask; retval = poll(fds,1,0); //poll the 1 file descriptor, returning immediately. if (retval < 0) { // if we were interrupted by a signal, try again... // if(errno == EINTR) { continue; } // otherwise poll() has failed, which is BAD news. // return MSG_ERROR; } if (retval == 0) { return MSG_NO_ACTION; } // If poll tells us that the fd is invalid or has encountered a serious error, return -1. // if(fds[0].revents & (POLLERR | POLLNVAL)) { return MSG_ERROR; } // If any of the events supplied in mask have occurred, report them. // if(fds[0].revents & mask) { return fds[0].revents & mask; } // if there are no more message to drain (see above) and a hangup has occurred, // let the user know the socket is done for. if (fds[0].revents & POLLHUP) { return MSG_ERROR; } // Otherwise, by process of elimination we have no message and no error condition to report return MSG_NO_ACTION; } while(1); } // this function will pack a mailbox address and a payload into the supplied message structure and // set the correct timestamp. // int prepare_message(struct message_record *target, char *mailbox, void *payload, unsigned int payload_len) { if (target == NULL) { return -1; } if (mailbox == NULL) { return -1; } if (payload == NULL) { return -1; } if (payload_len > MAX_PAYLOAD_LENGTH) { return -1; } memset(target, 0, sizeof(struct message_record)); strncpy(target->header.mailbox_name, mailbox, MAILBOX_NAME_MAX); target->header.mailbox_name[MAILBOX_NAME_MAX] = '\0'; target->header.usec_time = get_usec_time(); target->header.payload_length = payload_len; target->header.sender = getpid(); target->header.from_fd = 0; memmove(target->payload, payload, payload_len); return 0; } // the send_message function will transmit a message and its header (trimmed to the specified payload length). // zero will be returned on success, -1 on failure. // int send_message(int message_socket_fd, struct message_record *message) { int txlen; int retval; if (message == NULL) { return -1; } // If we are trying to send a message that is longer than allowed, throw an error! // if (message->header.payload_length > MAX_PAYLOAD_LENGTH) { return -1; } // Calculate the total message length (header + payload) so that we don't send // any extra padding that we don't need. txlen = sizeof(struct message_header_record) + message->header.payload_length; do { retval = send(message_socket_fd, message, txlen, MSG_EOR); // if we were interrupted by a signal, try again. // if ( (retval == -1) && (errno == EINTR) ) { continue; } // if we tried to send a message and it got truncated, return failure. // if (retval != txlen) { return -1; } return 0; } while(1); } // the get_message function will receive a message from the opposite end of the socket and place it in the // supplied message structure incliding its header. -1 is returned on failure, 0 on success. // int get_message(int message_socket_fd, struct message_record *message) { int retval; if (message == NULL) { return -1; } // clear our buffer of any garbage before using it to read in the message // memset(message, 0, sizeof(struct message_record)); do { // try and receive our message // retval = recv(message_socket_fd, message, sizeof(struct message_record), 0); // if we were interrupted by a signal, try again. // if ( (retval == -1) && (errno == EINTR) ) { continue; } // zero is a special case for recv which indicates that the other side has shut down the connection. if (retval == 0) { return -1; } // if the received message size does not add up (between header and payload) // if (retval != (sizeof(struct message_header_record) + message->header.payload_length)) { // throw an error! // return -1; } // Record which IPC socket we got this from (for things like PING replies) message->header.from_fd = message_socket_fd; // otherwise, return success // return 0; } while(1); } // This function connects to the communication hub and returns the file descriptor of the connection. // If the connection failed, -1 is returned. The progname parameter (if not NULL) will be used to // register a module name (snagged from argv[0]) with the server for ease of debugging. // int connect_to_message_server(char *progname) { int fd; int retval; int len; char *message_text; struct sockaddr_un addr; struct message_record msg; fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); if (fd < 0) { return -1; } // construct our address structure (pointing to the agreed upon server address) // to pass to connect. addr.sun_family = AF_UNIX; strncpy(addr.sun_path, COMMHUB_ADDRESS, sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1]='\0'; len=strlen(COMMHUB_ADDRESS) + sizeof(addr.sun_family); // attempt to connect to said server // retval = connect(fd,(struct sockaddr *)&addr,len); if (retval) { fprintf(stderr, "Cannot connect to IPC hub at %s\n", COMMHUB_ADDRESS); close(fd); return -1; } // if we have been passed a module name to register as, use that, otherwise default to "ANONYMOUS" // if (progname) { message_text = progname; } else { message_text = "ANONYMOUS"; } len = strlen(message_text); // if for some inconceivable reason our module name is longer than the server allows, truncate it. // if (len > MAX_MODULE_NAME_LENGTH) { len = MAX_MODULE_NAME_LENGTH; } // prepare and send our registation message // prepare_message(&msg, MAILBOX_HELLO, message_text, len); send_message(fd, &msg); return fd; } //--------------------------------------------------------------------- // Read the `state_info_t` from the on disk text file. // Format is field (in caps) followed by the field value // separated by a single space. // // return: // 0 on success // non-zero on error // int get_state_info(state_info_t *_state) { FILE *fp; int ch = 1, input_idx=0, n=0, field_len=0; char buffer[LINE_BUFFER_SIZE], *chp; if (access(STATE_INFO_FILE, R_OK)!=0) { return -1; } memset(buffer, 0, sizeof(char)*LINE_BUFFER_SIZE); if ((fp = fopen(STATE_INFO_FILE, "r")) == NULL) { return -1; } while (!feof(fp)) { ch = fgetc(fp); if ((ch == '\n') || (ch == EOF)) { // ignore blank lines and comment lines // (begining with '#') // if (input_idx == 0) { continue; } if (buffer[0] == '#') { buffer[0] = '\0'; input_idx = 0; continue; } chp = strchr(buffer, ' '); if (chp != NULL) { chp++; n = chp - buffer; if (strncmp(buffer, "LAT ", n)==0) { _state->lat = atof(chp); } else if (strncmp(buffer, "LON ", n)==0) { _state->lon = atof(chp); } else if (strncmp(buffer, "HEADING ", n)==0) { _state->heading = atof(chp); } else if (strncmp(buffer, "VELOCITY ", n)==0) { _state->velocity = atof(chp); } else if (strncmp(buffer, "NUM_SATS ", n)==0) { _state->num_sats = atoi(chp); } else if (strncmp(buffer, "GPS_GOOD ", n)==0) { _state->gps_good = atoi(chp); } else if (strncmp(buffer, "STAMP ", n)==0) { _state->stamp = (time_t)atoi(chp); } else if (strncmp(buffer, "GPSTAMP ", n)==0) { _state->gpstime = (time_t)atoi(chp); } else if (strncmp(buffer, "PADDLE ", n)==0) { _state->paddle = atoi(chp); } else if (strncmp(buffer, "ROUTE ", n)==0) { _state->route = atoi(chp); } else if (strncmp(buffer, "TRIP ", n)==0) { _state->trip = atoi(chp); } else if (strncmp(buffer, "STOP ", n)==0) { _state->stop = atoi(chp); } else if (strncmp(buffer, "STOPNAME ", n)==0) { field_len = input_idx - n; if (field_len >= STATE_INFO_FIELD_SIZE) { field_len = STATE_INFO_FIELD_SIZE-1; } memcpy(_state->stopname, chp, field_len); _state->stopname[field_len] = '\0'; } else if (strncmp(buffer, "LOGGED_IN_DRIVER ", n)==0) { _state->logged_in_driver = atoi(chp); } else if (strncmp(buffer, "DRIVER_NAME ", n)==0) { field_len = input_idx - n; if (field_len >= STATE_INFO_FIELD_SIZE) { field_len = STATE_INFO_FIELD_SIZE-1; } memcpy(_state->driver_name, chp, input_idx - n + 1); _state->driver_name[field_len] = '\0'; } else if (strncmp(buffer, "EQUIP_NUM ", n)==0) { _state->equip_num = atoi(chp); } } buffer[0] = '\0'; input_idx = 0; continue; } buffer[input_idx] = ch; input_idx++; if (input_idx >= LINE_BUFFER_SIZE) { input_idx = LINE_BUFFER_SIZE-1; } buffer[input_idx] = '\0'; } fclose(fp); return 0; } // Save `state_info_t` on disk. // Format it field name (in caps) separated by // a single space followed by the field value. // // Constructs the text state file as a temporary file // (specified by STATE_INFO_TEMPFILE) then moves it // after it's finished. // // return: // 0 on success // non-zero on error // int set_state_info(state_info_t *_state) { FILE *fp; if ((fp = fopen(STATE_INFO_TEMPFILE, "w")) == NULL) { return -1; } fprintf(fp, "# updated %lli\n", get_usec_time()); fprintf(fp, "# %s\n", _state->comment); fprintf(fp, "LAT %f\n", _state->lat); fprintf(fp, "LON %f\n", _state->lon); fprintf(fp, "HEADING %f\n", _state->heading); fprintf(fp, "VELOCITY %f\n", _state->velocity); fprintf(fp, "NUM_SATS %i\n", _state->num_sats); fprintf(fp, "GPS_GOOD %i\n", _state->gps_good); fprintf(fp, "STAMP %i\n", (int)(_state->stamp)); fprintf(fp, "GPSTAMP %i\n", (int)(_state->gpstime)); fprintf(fp, "PADDLE %i\n", _state->paddle); fprintf(fp, "ROUTE %i\n", _state->route); fprintf(fp, "TRIP %i\n", _state->trip); fprintf(fp, "STOP %i\n", _state->stop); fprintf(fp, "STOPNAME %s\n", _state->stopname); fprintf(fp, "LOGGED_IN_DRIVER %i\n", _state->logged_in_driver); fprintf(fp, "DRIVER_NAME %s\n", _state->driver_name); fprintf(fp, "EQUIP_NUM %i\n", _state->equip_num); fclose(fp); return rename(STATE_INFO_TEMPFILE, STATE_INFO_FILE); } // print out the state information int print_state_info(state_info_t *_state) { FILE *fp = stdout; fprintf(fp, "LAT %f\n", _state->lat); fprintf(fp, "LON %f\n", _state->lon); fprintf(fp, "HEADING %f\n", _state->heading); fprintf(fp, "VELOCITY %f\n", _state->velocity); fprintf(fp, "NUM_SATS %i\n", _state->num_sats); fprintf(fp, "GPS_GOOD %i\n", _state->gps_good); fprintf(fp, "STAMP %i\n", (int)(_state->stamp)); fprintf(fp, "GPSTAMP %i\n", (int)(_state->gpstime)); fprintf(fp, "PADDLE %i\n", _state->paddle); fprintf(fp, "ROUTE %i\n", _state->route); fprintf(fp, "TRIP %i\n", _state->trip); fprintf(fp, "STOP %i\n", _state->stop); fprintf(fp, "STOPNAME %s\n", _state->stopname); fprintf(fp, "LOGGED_IN_DRIVER %i\n", _state->logged_in_driver); fprintf(fp, "DRIVER_NAME %s\n", _state->driver_name); fprintf(fp, "EQUIP_NUM %i\n", _state->equip_num); return 0; }