/* * 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 "../common/common_defs.h" #include "commhub.h" #include "client_utils.h" gps_status gps_stat = {0}; stop_status stop_stat = {0}; driver_status driver_stat = {0}; pass_status pass_stat = {0}; bill_status bill_stat = {0}; state_info_t state_info = {0}; //----------------------------------------------------------------- float effective_lat() { if (gps_stat.gps_good) { return (float)gps_stat.lat; } else { return (float)stop_stat.lat; } } float effective_lon() { if (gps_stat.gps_good) { return (float)gps_stat.lon; } else { return (float)stop_stat.lon; } } //----------------------------------------------------------------- typedef struct message_callback_struct { char mailbox[MAILBOX_NAME_MAX + 1]; int ident; message_notify_func callback; void *param; struct message_callback_struct *next; } message_callback; static message_callback *dispatch_list_head = NULL; //This function registers a callback to happen when a certain message is received. //It takes four parameters: // mailbox = string containing name of mailbox to trigger this callback // ident = a numeric identifier specifying both a unique identifier for this callback, and processing order // func = a message handling callback function // param = an untyped pointer used to pass any outside contect to the callback // int register_dispatch_callback(char *mailbox, int ident, message_notify_func func, void *param) { message_callback *p, *q, *n; if (!func || !mailbox) { return -1; } q = NULL; p = dispatch_list_head; // Scan our existing list // while (p) { // stop when we find a node with an identifier greater than ident or run off the end // if (p->ident > ident) { break; } q = p; p = p->next; } n = (message_callback *)malloc(sizeof(message_callback)); if (n == NULL) { return -1; } // clear our new node // memset(n, 0, sizeof(message_callback)); // copy our match string // strncpy(n->mailbox, mailbox, MAILBOX_NAME_MAX); // null terminate // n->mailbox[MAILBOX_NAME_MAX] = '\0'; // record our identifier // n->ident = ident; // callback // n->callback = func; // and parameter // n->param = param; // register our next pointer as per above scan's determined insert point // n->next = p; // If we're inserting at the head of the lsit // if (q == NULL) { // set the list head to n // dispatch_list_head = n; } else { // tweak the next pointer of the previous node to perform the insertion // q->next = n; } return 0; } // This function unregisters a message callback function by its mailbox name and identifier // this allows for easy selective removal of a specific callback while still keeping the rest of the chain intact // int unregister_dispatch_callback(char *mailbox, int ident) { message_callback *p, *q; // if we have a bogus mailbox, give up // if (!mailbox) { return -1; } q = NULL; p = dispatch_list_head; // Scan our callback list looking for the identifier in question // while (p) { // if we find a mailbox with both a matching identifier and mailbox name, that's the one... // if ( (p->ident == ident) && !strncmp(mailbox, p->mailbox, MAILBOX_NAME_MAX) ) { break; } q = p; p = p->next; } // if we didn't find it, that is reported by the < 0 return value // if (p == NULL) { return -1; } // If we are removing at the head... // if (q == NULL) { // advance the head pointer down the list // dispatch_list_head = p->next; } else { // snip the node in question out mid-list // q->next = p->next; } // free the node in question // free(p); return 0; } // This function unregisters ALL callbacks in preparation for a clean exit // int unregister_all_callbacks() { message_callback *p, *q; q = NULL; p = dispatch_list_head; // traverse the list freeing all nodes // while (p) { q = p; p = p->next; free(q); } // reset our list pointers to empty state // dispatch_list_head = NULL; return 0; } // This function is used to translate any special "Command" messages (things addressed // by module or PID rather than mailbox, where the payload is a mailbox name for special // notifications (primarily MAILBOX_PING and MAILBOX_EXIT). // static int process_addressed_message(struct message_record *msg) { struct message_record translated_msg; // If this message is special... // if ( (!strncmp((char *)msg->payload, MAILBOX_PING, MAILBOX_NAME_MAX)) || (!strncmp((char *)msg->payload, MAILBOX_HUP, MAILBOX_NAME_MAX)) || (!strncmp((char *)msg->payload, MAILBOX_EXIT, MAILBOX_NAME_MAX)) ) { // Copy the message as a whole // memcpy(&translated_msg, msg, sizeof(translated_msg)); // Then change its mailbox name to the special mailbox specified in the payload // strncpy(translated_msg.header.mailbox_name, (char *)msg->payload, MAILBOX_NAME_MAX); translated_msg.header.mailbox_name[MAILBOX_NAME_MAX] = '\0'; //this is not a bug, since mailbox_name is defined as MAILBOX_NAME_MAX + 1 bytes long // Go and feed this through the callback chain as if it originally came on on the actual mailbox... // process_message(&translated_msg); return 0; } return 0; } //--- // Update _state_info with _gps_stat // int update_state_info_with_gps(state_info_t *_state_info, gps_status *_gps_stat) { _state_info->lat = _gps_stat->lat; _state_info->lon = _gps_stat->lon; _state_info->heading = _gps_stat->heading; _state_info->velocity = _gps_stat->velocity; _state_info->num_sats = _gps_stat->num_sats; _state_info->gps_good = _gps_stat->gps_good; _state_info->stamp = _gps_stat->stamp; _state_info->gpstime = _gps_stat->gpstime; return 0; } // Update _state_info with _stop_stat // int update_state_info_with_stop(state_info_t *_state_info, stop_status *_stop_stat) { _state_info->paddle = _stop_stat->paddle; _state_info->route = _stop_stat->route; _state_info->trip = _stop_stat->trip; _state_info->stop = _stop_stat->stop; _state_info->stop_lat = _stop_stat->lat; _state_info->stop_lon = _stop_stat->lon; memcpy(_state_info->stopname, _stop_stat->stopname, ( STATE_INFO_FIELD_SIZE < STOP_NAME_LEN ) ? STATE_INFO_FIELD_SIZE : STOP_NAME_LEN ); _state_info->stopname[STATE_INFO_FIELD_SIZE-1] = '\0'; return 0; } // Update _state_info with _driver_stat // int update_state_info_with_driver(state_info_t *_state_info, driver_status *_driver_stat) { _state_info->logged_in_driver = _driver_stat->logged_in_driver; memcpy(_state_info->driver_name, _driver_stat->driver_name, ( STATE_INFO_FIELD_SIZE < DRIVER_NAME_LEN) ? STATE_INFO_FIELD_SIZE : DRIVER_NAME_LEN ); _state_info->driver_name[STATE_INFO_FIELD_SIZE-1] = '\0'; _state_info->equip_num = _driver_stat->equip_num; return 0; } // Load global variable, state_info, with stat information, // stored in local file. // Other global variables might need to be initalized based // on the state_info so this function was created to help // facilitate that in the future, if needed. // int init_state_info(void) { get_state_info(&state_info); return 0; } //--- // This function actually processes a message through the dispatch list // using the return values of the callbacks to determine whether to stop or continue // the return value is the return value of the last handler to have read the message. // a return value of MESSAGE_UNHANDLED means that no handler successfully handled the message. message_callback_return process_message(struct message_record *msg) { message_callback *p = dispatch_list_head; message_callback_return ret = MESSAGE_UNHANDLED; // if there is no message // if (msg == NULL) { // we can't handle it // return ret; } // Walk through this list of handlers (which is maintained sorted by ident) // while (p) { // if the message's mailbox matches the callback's mailbox // if (!strncmp(msg->header.mailbox_name, p->mailbox, MAILBOX_NAME_MAX)) { // go and run the callback // ret = p->callback(msg, p->param); // if this callback requested a STOP, do so // if (ret == MESSAGE_HANDLED_STOP) { break; } } // otherwise, keep going... // p = p->next; } // If nobody has said STOP yet, see if this is an individually addressed message // if ( (msg->header.mailbox_name[0] == ':') || (msg->header.mailbox_name[0] == '>') ) { process_addressed_message(msg); } return ret; } //------------------------------------ SOME USEFUL DEFAULT CALLBACKS ------------------------------------------- message_callback_return ignore_message(struct message_record *msg, void *param) { return MESSAGE_HANDLED_STOP; } //------------------------------------ SYSTEM CALLBACKS ------------------------------------------------------- static message_callback_return update_gps_status(struct message_record *msg, void *param) { // guard against version mismatch // if (msg->header.payload_length != sizeof(gps_status)) { return MESSAGE_HANDLED_STOP; } memcpy(&gps_stat, msg->payload, sizeof(gps_status)); //otherwise, update our structure return MESSAGE_HANDLED_CONT; } static message_callback_return update_stop_status(struct message_record *msg, void *param) { // guard against version mismatch // if (msg->header.payload_length != sizeof(stop_status)) { return MESSAGE_HANDLED_STOP; } // otherwise, update our structure // memcpy(&stop_stat, msg->payload, sizeof(stop_status)); return MESSAGE_HANDLED_CONT; } static message_callback_return update_driver_status(struct message_record *msg, void *param) { // guard against version mismatch // if (msg->header.payload_length != sizeof(driver_status)) { return MESSAGE_HANDLED_STOP; } // otherwise, update our structure // memcpy(&driver_stat, msg->payload, sizeof(driver_stat)); return MESSAGE_HANDLED_CONT; } static message_callback_return update_bill_status(struct message_record *msg, void *param) { // guard against version mismatch // if (msg->header.payload_length != sizeof(bill_status)) { return MESSAGE_HANDLED_STOP; } // otherwise, update our structure // memcpy(&bill_stat, msg->payload, sizeof(bill_stat)); return MESSAGE_HANDLED_CONT; } static message_callback_return update_pass_status(struct message_record *msg, void *param) { // guard against version mismatch // if (msg->header.payload_length != sizeof(pass_status)) { return MESSAGE_HANDLED_STOP; } memcpy(&pass_stat, msg->payload, sizeof(pass_stat)); //otherwise, update our structure return MESSAGE_HANDLED_CONT; } static message_callback_return polite_exit_request_message(struct message_record *msg, void *param) { request_polite_exit(EXIT_REQUEST_POLITE, "%s", (char *)msg->payload); return MESSAGE_HANDLED_CONT; } static message_callback_return hup_request_message(struct message_record *msg, void *param) { request_hup("HUP(%s)", (char *)msg->payload); return MESSAGE_HANDLED_CONT; } static message_callback_return ping_request_message(struct message_record *msg, void *param) { struct message_record outgoing; prepare_message(&outgoing, MAILBOX_PONG, msg->payload, msg->header.payload_length); send_message(msg->header.from_fd, &outgoing); return MESSAGE_HANDLED_CONT; } // This function unregisters the default system status callbacks // int unregister_system_status_callbacks() { unregister_dispatch_callback(MAILBOX_GPS_STATUS, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_STOP_STATUS, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_DRIVER_STATUS, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_PASS_STATUS, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_BILL_STATUS, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_EXIT, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_HUP, CALLBACK_SYSTEM); unregister_dispatch_callback(MAILBOX_PING, CALLBACK_SYSTEM); return 0; } // This function registers the default system status callbacks int register_system_status_callbacks() { unregister_system_status_callbacks(); register_dispatch_callback(MAILBOX_GPS_STATUS, CALLBACK_SYSTEM, update_gps_status, NULL); register_dispatch_callback(MAILBOX_STOP_STATUS, CALLBACK_SYSTEM, update_stop_status, NULL); register_dispatch_callback(MAILBOX_DRIVER_STATUS, CALLBACK_SYSTEM, update_driver_status, NULL); register_dispatch_callback(MAILBOX_PASS_STATUS, CALLBACK_SYSTEM, update_pass_status, NULL); register_dispatch_callback(MAILBOX_BILL_STATUS, CALLBACK_SYSTEM, update_bill_status, NULL); register_dispatch_callback(MAILBOX_EXIT, CALLBACK_SYSTEM, polite_exit_request_message, NULL); register_dispatch_callback(MAILBOX_HUP, CALLBACK_SYSTEM, hup_request_message, NULL); register_dispatch_callback(MAILBOX_PING, CALLBACK_SYSTEM, ping_request_message, NULL); return 0; } // This function subscribes to a handful of essential messages // int subscribe_to_default_messages(int fd) { struct message_record outgoing_msg; if (fd < 0) { return -1; } //------------- things that keep us informed about the other modules prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_GPS_STATUS, strlen(MAILBOX_GPS_STATUS)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_STOP_STATUS, strlen(MAILBOX_STOP_STATUS)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_DRIVER_STATUS, strlen(MAILBOX_DRIVER_STATUS)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PASS_STATUS, strlen(MAILBOX_PASS_STATUS)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_BILL_STATUS, strlen(MAILBOX_BILL_STATUS)); send_message(fd, &outgoing_msg); //------------- things that every process should have a crack at prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_STATUS_REQUEST, strlen(MAILBOX_STATUS_REQUEST)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_EXIT, strlen(MAILBOX_EXIT)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_HUP, strlen(MAILBOX_HUP)); send_message(fd, &outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PING, strlen(MAILBOX_PING)); send_message(fd, &outgoing_msg); return 0; } //-------------------------------------------------------------------------------------------------------------- int compress_tabs_to_spaces(char *dest, char *src, int n) { int i; for (i=0; i < n; i++) { switch (src[i]) { case '\t': case '\r': case '\n': dest[i] = ' '; break; default: dest[i] = src[i]; break; } if (src[i] == '\0') { break; } } return 0; } // This function is a printf-line interface to format user display messages: // // target = blank IPC message to fill with this log entry // line = which line of the display to draw on (0 or 1) // priority = what message priority? // duration = how many seconds to display this (0 makes it the default message) // fmt = printf format // ... = printf args // int format_piu_message(struct message_record *target, int line, int priority, int duration, const char *fmt, ...) { piu_message pmsg = {0}; va_list ap; pmsg.line = line; pmsg.priority = priority; pmsg.seconds = duration; va_start(ap, fmt); vsnprintf(pmsg.message, PIU_MESSAGE_LEN, fmt, ap); va_end(ap); return prepare_message(target, MAILBOX_PIU_MESSAGE, &pmsg, sizeof(pmsg)); } // This function takes a buffer and a buffer size and puts a // log prefix in that contains the equipment number and the local time // int make_log_prefix(char *prefix, int max) { int eqnum; time_t foo; struct tm t; foo = time(NULL); localtime_r(&foo, &t); // If the driver interface module has a current EQ num // if (driver_stat.equip_num > 0) { // use it so as not to thrash the file system // eqnum = driver_stat.equip_num; } else { // otherwise, get it off disk // eqnum = get_equip_num(); } return snprintf(prefix, max, "EQ# %d %04d-%02d-%02d %02d:%02d:%02d ", eqnum, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); } // This function is a printf-line interface to format log messages for the server: // // target = blank IPC message to fill with this log entry // loglevel = one of the following: LOGLEVEL_DEBUG, LOGLEVEL_WARN, LOGLEVEL_ERROR // fmt = printf format // ... = printf args // // NOTE: DO NOT UNDER ANY CIRCUMSTANCES TERMINATE A LOG MESSAGE WITH A '\n' CHARACTER! // int format_log_message(struct message_record *target, char loglevel, const char *fmt, ...) { int len = 0; char payload[MAX_PAYLOAD_LENGTH] = {0}; va_list ap; if (!target || !fmt) { return -1; } switch (loglevel) { case LOGLEVEL_DEBUG: case LOGLEVEL_WARN: case LOGLEVEL_ERROR: payload[0] = loglevel; len += 1; break; default: payload[0] = LOGLEVEL_ERROR; len += 1; break; } len += make_log_prefix(payload + len, MAX_PAYLOAD_LENGTH - len); va_start(ap, fmt); len += vsnprintf(payload + len, MAX_PAYLOAD_LENGTH - len, fmt, ap); va_end(ap); return prepare_message(target, MAILBOX_BILLING_LOG, payload, len); } // This function printf-line interface to format messages for debug and trace purposes // int format_trace_message(struct message_record *target, const char *fmt, ...) { int len = 0; char payload[MAX_PAYLOAD_LENGTH] = {0}; va_list ap; if (!target || !fmt) { return -1; } va_start(ap, fmt); len += vsnprintf(payload + len, MAX_PAYLOAD_LENGTH - len, fmt, ap); va_end(ap); return prepare_message(target, "DEBUG_TRACE", payload, len); } // This function is a printf-line interface to format messages for the driver: // // target = blank IPC message to fill with this log entry // loglevel = one of the following: LOGLEVEL_DEBUG, LOGLEVEL_WARN, LOGLEVEL_ERROR // fmt = printf format // ... = printf args // // NOTE: DO NOT UNDER ANY CIRCUMSTANCES TERMINATE A LOG MESSAGE WITH A '\n' CHARACTER! // int format_driver_message(struct message_record *target, char loglevel, const char *fmt, ...) { int len = 0; char payload[MAX_PAYLOAD_LENGTH] = {0}; va_list ap; if (!target || !fmt) { return -1; } switch(loglevel) { case LOGLEVEL_DEBUG: case LOGLEVEL_WARN: case LOGLEVEL_ERROR: case LOGLEVEL_ACCEPT: case LOGLEVEL_REJECT: case LOGLEVEL_EVENT: payload[0] = loglevel; len += 1; break; default: payload[0] = LOGLEVEL_ERROR; len += 1; break; } va_start(ap, fmt); len += vsnprintf(payload + len, MAX_PAYLOAD_LENGTH - len, fmt, ap); va_end(ap); return prepare_message(target, MAILBOX_DRIVER_NOTIFY, payload, len); } // This function formats a billing log entry into a form ready to pass off to the billdb process: // // target = blank IPC message to fill with this log entry // action = string specifying billing action (ACCEPT, REJECT, PASSBACK, etc...) // rule = string specifying which rule (if any) generated this billing entry // ruleparam = string specifying the parameter passed to the above rule // reason = human readable reason for the reject or accept of this rider // credential = string containing the magnetic or RFID credential used to identify the rider // logical_card_id = rider ID from pass table // cash_value = number of cents paid for this fare if it is a cash fare // // All other required values are supplied by the system status structures maintained by the default status callbacks: // // equipment number, GPS locaction, driver, paddle, route, trip, stop, and stop name // int format_billing_message(struct message_record *target, char *action, char *rule, char *ruleparam, char *reason, char *credential, unsigned long long logical_card_id, int cash_value) { int len = 0; char foo[MAX_PAYLOAD_LENGTH]; char payload[MAX_PAYLOAD_LENGTH] = {0}; if (!target) return -1; if (!action) action = ""; if (!rule) rule = ""; if (!ruleparam) ruleparam = ""; if (!reason) reason = ""; if (!credential) credential = ""; // equipment number (which bus) // len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", driver_stat.equip_num); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", driver_stat.logged_in_driver); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.paddle); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.route); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.trip); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", stop_stat.stop); // the UTC timestamp of now... // len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", (int)time(NULL)); // if we have a valid GPS fix, put that into the structure // if (gps_stat.gps_good) { len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", gps_stat.lat); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", gps_stat.lon); } // otherwise, use the location recorded in the paddle // else { len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", stop_stat.lat); len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%f\t", stop_stat.lon); } // billing action code // compress_tabs_to_spaces(foo, action, BILLING_ACTION_LEN); foo[BILLING_ACTION_LEN] = '\0'; len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo); // billing rule code // compress_tabs_to_spaces(foo, rule, BILLING_RULE_LEN); foo[BILLING_RULE_LEN] = '\0'; len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo); // billing rule parameter // compress_tabs_to_spaces(foo, ruleparam, BILLING_RULEPARAM_LEN); foo[BILLING_RULEPARAM_LEN] = '\0'; len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo); // billing reason code // compress_tabs_to_spaces(foo, reason, BILLING_REASON_LEN); foo[BILLING_REASON_LEN] = '\0'; len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo); // billing credential code // compress_tabs_to_spaces(foo, credential, BILLING_CREDENTIAL_LEN); foo[BILLING_CREDENTIAL_LEN] = '\0'; len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo); // rider ID from pass table // len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%lld\t", logical_card_id); // cash fare in pennies // len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%d\t", cash_value); // billing stop name // compress_tabs_to_spaces(foo, stop_stat.stopname, STOP_NAME_LEN); foo[STOP_NAME_LEN] = '\0'; len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%s\t", foo); // This field is not used by the database, but distinguishes multiple identical cash fares per second // // NOTE: This field ends without a newline OR tab, the newline is added by the billdb module before being sent // len += snprintf(payload + len, MAX_PAYLOAD_LENGTH - len, "%llu", get_usec_time()); return prepare_message(target, MAILBOX_BILLING_LOG, payload, len); } /* int main(int argc, char **argv) { struct message_record msg; stop_stat.lat = 9999.99999f; stop_stat.lon = 9999.99999f; stop_stat.route = 99999; stop_stat.paddle = 99999; stop_stat.trip = 99; stop_stat.stop = 99; strcpy(stop_stat.stopname, "ABCDEFGIJKABCDEFGIJKABCDEFGIJKABCDEFGIJKABCDEFGIJKABCDEFGIJK123"); driver_stat.logged_in_driver = 12345; driver_stat.equip_num = 12345; format_billing_message(&msg, "ACCEPT1ACCEPT2_", "RULE0123456789012345678", "PARA0123456789012345678", "R012345678900123456789001234567890", "C012345678900123456789001234567890", 9999); printf("%s", msg.payload); return 0; } */