/* * * This file is part of Maemo Mapper. * * Maemo Mapper is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Maemo Mapper 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Maemo Mapper. If not, see . * * * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008): * * * XASTIR, Amateur Station Tracking and Information Reporting * Copyright (C) 1999,2000 Frank Giannandrea * Copyright (C) 2000-2007 The Xastir Group * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef INCLUDE_APRS #include "aprs_message.h" #include "data.h" #include "aprs.h" /////////////////////////////////////////// Messages /////////////////////////////////////////// long *msg_index; long msg_index_end; static long msg_index_max; int log_wx_alert_data = 0; Message *msg_data; // Array containing all messages, // including ones we've transmitted (via // loopback in the code) time_t last_message_update = 0; ack_record *ack_list_head = NULL; // Head of linked list storing most recent ack's int satellite_ack_mode; int new_message_data; time_t last_message_remove; // last time we did a check for message removing // How often update_messages() will run, in seconds. // This is necessary because routines like UpdateTime() // call update_messages() VERY OFTEN. // // Actually, we just changed the code around so that we only call // update_messages() with the force option, and only when we receive a // message. message_update_delay is no longer used, and we don't call // update_messages() from UpdateTime() anymore. static int message_update_delay = 300; char *remove_trailing_spaces(char *data); void update_messages(int force); void clear_acked_message(char *from, char *to, char *seq) { // TODO - replace stub } int check_popup_window(char *from_call_sign, int group) { // TODO - replace stub return 1; } void bulletin_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, TAprsPort port) { // TODO - replace stub } int look_for_open_group_data(char *to) { // TODO - replace stub return 0; } void get_send_message_path(char *callsign, char *path, int path_size) { // TODO - replace stub } void transmit_message_data_delayed(char *to, char *message, char *path, time_t when) { // TODO - replace stub } int process_directed_query(char *call,char *path,char *message,TAprsPort port) { // TODO - replace stub return 0; } void transmit_message_data(char *to, char *message, char *path) { // TODO - replace stub } void popup_message(char *banner, char *message) { } // Saves latest ack in a linked list. We need this value in order // to use Reply/Ack protocol when sending out messages. void store_most_recent_ack(char *callsign, char *ack) { ack_record *p; int done = 0; char call[MAX_CALLSIGN+1]; char new_ack[5+1]; snprintf(call, sizeof(call), "%s", callsign); remove_trailing_spaces(call); // Get a copy of "ack". We might need to change it. snprintf(new_ack, sizeof(new_ack), "%s", ack); // If it's more than 2 characters long, we can't use it for // Reply/Ack protocol as there's only space enough for two. // In this case we need to make sure that we blank out any // former ack that was 1 or 2 characters, so that communications // doesn't stop. if ( strlen(new_ack) > 2 ) { // It's too long, blank it out so that gets saved as "", // which will overwrite any previously saved ack's that were // short enough to use. new_ack[0] = '\0'; } // Search for matching callsign through linked list p = ack_list_head; while ( !done && (p != NULL) ) { if (strcasecmp(call,p->callsign) == 0) { done++; } else { p = p->next; } } if (done) { // Found it. Update the ack field. //fprintf(stderr,"Found callsign %s on recent ack list, Old:%s, New:%s\n",call,p->ack,new_ack); snprintf(p->ack,sizeof(p->ack),"%s",new_ack); } else { // Not found. Add a new record to the beginning of the // list. //fprintf(stderr,"New callsign %s, adding to list. Ack: %s\n",call,new_ack); p = (ack_record *)malloc(sizeof(ack_record)); CHECKMALLOC(p); snprintf(p->callsign,sizeof(p->callsign),"%s",call); snprintf(p->ack,sizeof(p->ack),"%s",new_ack); p->next = ack_list_head; ack_list_head = p; } } // Gets latest ack by callsign char *get_most_recent_ack(char *callsign) { ack_record *p; int done = 0; char call[MAX_CALLSIGN+1]; snprintf(call, sizeof(call), "%s", callsign); remove_trailing_spaces(call); // Search for matching callsign through linked list p = ack_list_head; while ( !done && (p != NULL) ) { if (strcasecmp(call,p->callsign) == 0) { done++; } else { p = p->next; } } if (done) { // Found it. Return pointer to ack string. //fprintf(stderr,"Found callsign %s on linked list, returning ack: %s\n",call,p->ack); return(&p->ack[0]); } else { //fprintf(stderr,"Callsign %s not found\n",call); return(NULL); } } void init_message_data(void) { // called at start of main new_message_data = 0; last_message_remove = sec_now(); } void msg_clear_data(Message *clear) { int size; int i; unsigned char *data_ptr; data_ptr = (unsigned char *)clear; size=sizeof(Message); for(i=0;i (last_message_update + message_update_delay) ) return(1); else return(0); } int msg_comp_active(const void *a, const void *b) { char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2]; char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2]; snprintf(temp_a, sizeof(temp_a), "%c%s%s%s", ((Message*)a)->active, ((Message*)a)->call_sign, ((Message*)a)->from_call_sign, ((Message*)a)->seq); snprintf(temp_b, sizeof(temp_b), "%c%s%s%s", ((Message*)b)->active, ((Message*)b)->call_sign, ((Message*)b)->from_call_sign, ((Message*)b)->seq); return(strcmp(temp_a, temp_b)); } int msg_comp_data(const void *a, const void *b) { char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1]; char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1]; snprintf(temp_a, sizeof(temp_a), "%s%s%s", msg_data[*(long*)a].call_sign, msg_data[*(long *)a].from_call_sign, msg_data[*(long *)a].seq); snprintf(temp_b, sizeof(temp_b), "%s%s%s", msg_data[*(long*)b].call_sign, msg_data[*(long *)b].from_call_sign, msg_data[*(long *)b].seq); return(strcmp(temp_a, temp_b)); } void msg_input_database(Message *m_fill) { void *m_ptr; long i; // fprintf(stderr, "DEBUG: Message: %s %s\n", m_fill->call_sign, m_fill->message_line); if (msg_index_end == msg_index_max) { for (i = 0; i < msg_index_end; i++) { // Check for a record that is marked RECORD_NOTACTIVE. // If found, use that record instead of malloc'ing a new // one. if (msg_data[msg_index[i]].active == RECORD_NOTACTIVE) { // Found an unused record. Fill it in. memcpy(&msg_data[msg_index[i]], m_fill, sizeof(Message)); // Sort msg_data qsort(msg_data, (size_t)msg_index_end, sizeof(Message), msg_comp_active); for (i = 0; i < msg_index_end; i++) { msg_index[i] = i; if (msg_data[i].active == RECORD_NOTACTIVE) { msg_index_end = i; break; } } // Sort msg_index qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data); // All done with this message. return; } } // Didn't find free message record. Fetch some more space. // Get more msg_data space. m_ptr = realloc(msg_data, (msg_index_max+MSG_INCREMENT)*sizeof(Message)); if (m_ptr) { msg_data = m_ptr; // Get more msg_index space m_ptr = realloc(msg_index, (msg_index_max+MSG_INCREMENT)*sizeof(Message *)); if (m_ptr) { msg_index = m_ptr; msg_index_max += MSG_INCREMENT; //fprintf(stderr, "Max Message Array: %ld\n", msg_index_max); } else { // TODO //XtWarning("Unable to allocate more space for message index.\n"); } } else { // TODO //XtWarning("Unable to allocate more space for message database.\n"); } } if (msg_index_end < msg_index_max) { msg_index[msg_index_end] = msg_index_end; // Copy message data into new message record. memcpy(&msg_data[msg_index_end++], m_fill, sizeof(Message)); // Sort msg_index qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data); } } // Does a binary search through a sorted message database looking // for a string match. // // If two or more messages match, this routine _should_ return the // message with the latest timestamp. This will ensure that earlier // messages don't get mistaken for current messages, for the case // where the remote station did a restart and is using the same // sequence numbers over again. // long msg_find_data(Message *m_fill) { long record_start, record_mid, record_end, return_record, done; char tempfile[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1]; char tempfill[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1]; snprintf(tempfill, sizeof(tempfill), "%s%s%s", m_fill->call_sign, m_fill->from_call_sign, m_fill->seq); return_record = -1L; if (msg_index && msg_index_end >= 1) { /* more than one record */ record_start=0L; record_end = (msg_index_end - 1); record_mid=(record_end-record_start)/2; done=0; while (!done) { /* get data for record start */ snprintf(tempfile, sizeof(tempfile), "%s%s%s", msg_data[msg_index[record_start]].call_sign, msg_data[msg_index[record_start]].from_call_sign, msg_data[msg_index[record_start]].seq); if (strcmp(tempfill, tempfile) < 0) { /* filename comes before */ /*fprintf(stderr,"Before No data found!!\n");*/ done=1; break; } else { /* get data for record end */ snprintf(tempfile, sizeof(tempfile), "%s%s%s", msg_data[msg_index[record_end]].call_sign, msg_data[msg_index[record_end]].from_call_sign, msg_data[msg_index[record_end]].seq); if (strcmp(tempfill,tempfile)>=0) { /* at end or beyond */ if (strcmp(tempfill, tempfile) == 0) { return_record = record_end; //fprintf(stderr,"record %ld",return_record); } done=1; break; } else if ((record_mid == record_start) || (record_mid == record_end)) { /* no mid for compare check to see if in the middle */ done=1; snprintf(tempfile, sizeof(tempfile), "%s%s%s", msg_data[msg_index[record_mid]].call_sign, msg_data[msg_index[record_mid]].from_call_sign, msg_data[msg_index[record_mid]].seq); if (strcmp(tempfill,tempfile)==0) { return_record = record_mid; //fprintf(stderr,"record: %ld",return_record); } } } if (!done) { /* get data for record mid */ snprintf(tempfile, sizeof(tempfile), "%s%s%s", msg_data[msg_index[record_mid]].call_sign, msg_data[msg_index[record_mid]].from_call_sign, msg_data[msg_index[record_mid]].seq); if (strcmp(tempfill, tempfile) == 0) { return_record = record_mid; //fprintf(stderr,"record %ld",return_record); done = 1; break; } if(strcmp(tempfill, tempfile)<0) record_end = record_mid; else record_start = record_mid; record_mid = record_start+(record_end-record_start)/2; } } } return(return_record); } void msg_replace_data(Message *m_fill, long record_num) { memcpy(&msg_data[msg_index[record_num]], m_fill, sizeof(Message)); } void msg_get_data(Message *m_fill, long record_num) { memcpy(m_fill, &msg_data[msg_index[record_num]], sizeof(Message)); } void msg_update_ack_stamp(long record_num) { //fprintf(stderr,"Attempting to update ack stamp: %ld\n",record_num); if ( (record_num >= 0) && (record_num < msg_index_end) ) { msg_data[msg_index[record_num]].last_ack_sent = sec_now(); //fprintf(stderr,"Ack stamp: %ld\n",msg_data[msg_index[record_num]].last_ack_sent); } //fprintf(stderr,"\n\n\n*** Record: %ld ***\n\n\n",record_num); } // Called when we receive an ACK. Sets the "acked" field in a // Message which gets rid of the highlighting in the Send Message // dialog for that message line. This lets us know which messages // have been acked and which have not. If timeout is non-zero, then // set acked to 2: We use this in update_messages() to flag that // "*TIMEOUT*" should prefix the string. If cancelled is non-zero, // set acked to 3: We use this in update_messages() to flag that // "*CANCELLED*" should prefix the string. // void msg_record_ack(char *to_call_sign, char *my_call, char *seq, int timeout, int cancel) { Message m_fill; long record; int do_update = 0; // Find the corresponding message in msg_data[i], set the // "acked" field to one. substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.from_call_sign); substr(m_fill.seq, seq, MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); // Look for a message with the same to_call_sign, my_call, // and seq number record = msg_find_data(&m_fill); if (record == -1L) { // No match yet, try another tactic. if (seq[2] == '}' && strlen(seq) == 3) { // Try it again without the trailing '}' character m_fill.from_call_sign[2] = '\0'; // Look for a message with the same to_call_sign, // my_call, and seq number (minus the trailing '}') record = msg_find_data(&m_fill); } } if(record != -1L) { // Found a match! // Only cause an update if this is the first ack. This // reduces dialog "flashing" a great deal if ( msg_data[msg_index[record]].acked == 0 ) { // Check for my callsign (including SSID). If found, // update any open message dialogs if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) { //fprintf(stderr,"From: %s\tTo: %s\n", // msg_data[msg_index[record]].from_call_sign, // msg_data[msg_index[record]].call_sign); do_update++; } } else { // This message has already been acked. } if (cancel) msg_data[msg_index[record]].acked = (char)3; else if (timeout) msg_data[msg_index[record]].acked = (char)2; else msg_data[msg_index[record]].acked = (char)1; // Set the interval to zero so that we don't display it // anymore in the dialog. Same for tries. msg_data[msg_index[record]].interval = 0; msg_data[msg_index[record]].tries = 0; } if (do_update) { update_messages(1); // Force an update // Call check_popup_messages() here in order to pop up any // closed Send Message dialogs. For first ack's or // CANCELLED messages it is less important, but for TIMEOUT // messages it is very important. // // TODO // (void)check_popup_window(m_fill.call_sign, 2); // Calls update_messages() } } // Called when we receive a REJ packet (reject). Sets the "acked" // field in a Message to 4 to indicate that the message has been // rejected by the remote station. This gets rid of the // highlighting in the Send Message dialog for that message line. // This lets us know which messages have been rejected and which // have not. We use this in update_messages() to flag that // "*REJECTED*" should prefix the string. // // The most common source of REJ packets would be from sending to a // D700A who's buffers are full, so that it can't take another // message. // void msg_record_rej(char *to_call_sign, char *my_call, char *seq) { Message m_fill; long record; int do_update = 0; // Find the corresponding message in msg_data[i], set the // "acked" field to four. substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.from_call_sign); substr(m_fill.seq, seq, MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); // Look for a message with the same to_call_sign, my_call, // and seq number record = msg_find_data(&m_fill); if (record == -1L) { // No match yet, try another tactic. if (seq[2] == '}' && strlen(seq) == 3) { // Try it again without the trailing '}' character m_fill.from_call_sign[2] = '\0'; // Look for a message with the same to_call_sign, // my_call, and seq number (minus the trailing '}') record = msg_find_data(&m_fill); } } if(record != -1L) { // Found a match! // Only cause an update if this is the first rej. This // reduces dialog "flashing" a great deal if ( msg_data[msg_index[record]].acked == 0 ) { // Check for my callsign (including SSID). If found, // update any open message dialogs if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) { //fprintf(stderr,"From: %s\tTo: %s\n", // msg_data[msg_index[record]].from_call_sign, // msg_data[msg_index[record]].call_sign); do_update++; } } else { // This message has already been acked. } // Actually record the REJ here msg_data[msg_index[record]].acked = (char)4; // Set the interval to zero so that we don't display it // anymore in the dialog. Same for tries. msg_data[msg_index[record]].interval = 0; msg_data[msg_index[record]].tries = 0; } if (do_update) { update_messages(1); // Force an update // Call check_popup_messages() here in order to pop up any // closed Send Message dialogs. For first ack's or // CANCELLED messages it is less important, but for TIMEOUT // messages it is very important. // // TODO // (void)check_popup_window(m_fill.call_sign, 2); // Calls update_messages() } } // Called from check_and_transmit_messages(). Updates the interval // field in our message record for the message currently being // transmitted. We'll use this in the Send Message dialog to // display the current message interval. // void msg_record_interval_tries(char *to_call_sign, char *my_call, char *seq, time_t interval, int tries) { Message m_fill; long record; // Find the corresponding message in msg_data[i] substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.from_call_sign); substr(m_fill.seq, seq, MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); // Look for a message with the same to_call_sign, my_call, // and seq number record = msg_find_data(&m_fill); if(record != -1L) { // Found a match! msg_data[msg_index[record]].interval = interval; msg_data[msg_index[record]].tries = tries; } update_messages(1); // Force an update } // Returns: time_t for last_ack_sent // -1 if the message doesn't pass our tests // 0 if it is a new message. // // Also returns the record number found if not passed a NULL pointer // in record_out or -1L if it's a new record. // time_t msg_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, TAprsPort port, long *record_out) { Message m_fill; long record; char time_data[MAX_TIME]; int do_msg_update = 0; time_t last_ack_sent; int distance = -1; char temp[10]; int group_message = 0; //fprintf(stderr,"from:%s, to:%s, seq:%s\n", from_call, call_sign, seq); // Set the default output condition. We'll change this later if // we need to. if (record_out != NULL) *record_out = -1l; // Check for some reasonable string in call_sign parameter if (call_sign == NULL || strlen(call_sign) == 0) { return((time_t)-1l); } //else //fprintf(stderr,"msg_data_add():call_sign: %s\n", call_sign); if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) { return((time_t)-1l); } substr(m_fill.call_sign, call_sign, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.seq, seq, MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); // If the sequence number is blank, then it may have been a query, // directed query, or group message. Assume it is a new message in // each case and add it. if (seq[0] != '\0') { // Normal station->station messaging or // bulletins // Look for a message with the same call_sign, // from_call_sign, and seq number record = msg_find_data(&m_fill); //fprintf(stderr,"RECORD %ld \n",record); //fprintf(stderr,"Normal station->station message\n"); } else { // Group message/query/etc. record = -1L; group_message++; // Flag it as a group message //fprintf(stderr,"Group message/query/etc\n"); } msg_clear_data(&m_fill); if(record != -1L) { /* fill old data */ msg_get_data(&m_fill, record); last_ack_sent = m_fill.last_ack_sent; //fprintf(stderr,"Found: last_ack_sent: %ld\n",m_fill.last_ack_sent); //fprintf(stderr,"Found a duplicate message. Updating fields, seq %s\n",seq); // If message is different this time, do an update to the // send message window and update the sec_heard field. The // remote station must have restarted and is re-using the // sequence numbers. What a pain! if (strcmp(m_fill.message_line,data) != 0) { m_fill.sec_heard = sec_now(); last_ack_sent = (time_t)0; //fprintf(stderr,"Message is different this time: Setting last_ack_sent to 0\n"); if (type != MESSAGE_BULLETIN) { // Not a bulletin do_msg_update++; } } // If message is the same, but the sec_heard field is quite // old (more than 8 hours), the remote station must have // restarted, is re-using the sequence numbers, and just // happened to send the same message with the same sequence // number. Again, what a pain! Either that, or we // connected to a spigot with a _really_ long queue! if (m_fill.sec_heard < (sec_now() - (8 * 60 * 60) )) { m_fill.sec_heard = sec_now(); last_ack_sent = (time_t)0; //fprintf(stderr,"Found >8hrs old: Setting last_ack_sent to 0\n"); if (type != MESSAGE_BULLETIN) { // Not a bulletin do_msg_update++; } } // Check for zero time if (m_fill.sec_heard == (time_t)0) { m_fill.sec_heard = sec_now(); fprintf(stderr,"Zero time on a previous message.\n"); } } else { // Only do this if it's a new message. This keeps things // more in sequence by not updating the time stamps // constantly on old messages that don't get ack'ed. m_fill.sec_heard = sec_now(); last_ack_sent = (time_t)0; //fprintf(stderr,"New msg: Setting last_ack_sent to 0\n"); if (type != MESSAGE_BULLETIN) { // Not a bulletin //fprintf(stderr,"Found new message\n"); do_msg_update++; // Always do an update to the // message window for new messages } } /* FROM */ m_fill.port =port; m_fill.active=RECORD_ACTIVE; m_fill.type=type; if (m_fill.heard_via_tnc != VIA_TNC) m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC; distance = (int)(distance_from_my_station(from_call,temp, sizeof(temp)) + 0.9999); if (distance != 0) { // Have a posit from the sending station m_fill.position_known = 1; //fprintf(stderr,"Position known: %s\n",from_call); } else { //fprintf(stderr,"Position not known: %s\n",from_call); } substr(m_fill.call_sign,call_sign,MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.from_call_sign); // Update the message field substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH); substr(m_fill.seq,seq,MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); // Create a timestamp from the current time snprintf(m_fill.packet_time, sizeof(m_fill.packet_time), "%s", get_time(time_data)); if(record == -1L) { // No old record found if (group_message) m_fill.acked = 1; // Group msgs/queries need no ack else m_fill.acked = 0; // We can't have been acked yet m_fill.interval = 0; m_fill.tries = 0; // We'll be sending an ack right away if this is a new // message, so might as well set the time now so that we // don't care about failing to set it in // msg_update_ack_stamp due to the record number being -1. m_fill.last_ack_sent = sec_now(); msg_input_database(&m_fill); // Create a new entry //fprintf(stderr,"No record found: Setting last_ack_sent to sec_now()00\n"); } else { // Old record found //fprintf(stderr,"Replacing the message in the database, seq %s\n",seq); msg_replace_data(&m_fill, record); // Copy fields from m_fill to record } /* display messages */ // TODO // if (type == MESSAGE_MESSAGE) // all_messages(from,call_sign,from_call,data); // Check for my callsign (including SSID). If found, update any // open message dialogs if ( is_my_call(m_fill.from_call_sign, 1) || is_my_call(m_fill.call_sign, 1) ) { if (do_msg_update) { update_messages(1); // Force an update } } // Return the important variables we'll need if (record_out != NULL) *record_out = record; //fprintf(stderr,"\nrecord_out:%ld record %ld\n",*record_out,record); return(last_ack_sent); } // End of msg_data_add() // alert_data_add: Function which adds NWS weather alerts to the // alert hash. // // This function adds alerts directly to the alert hash, bypassing // the message list and associated message-scan functions. // void alert_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, TAprsPort port) { Message m_fill; char time_data[MAX_TIME]; /* if (log_wx_alert_data && from != DATA_VIA_FILE) { char temp_msg[MAX_MESSAGE_LENGTH+1]; // Attempt to reconstruct the original weather alert packet // here, minus the path. snprintf(temp_msg, sizeof(temp_msg), "%s>APRS::%-9s:%s{%s", from_call, call_sign, data, seq); log_data( get_user_base_dir(LOGFILE_WX_ALERT), temp_msg); // fprintf(stderr, "%s\n", temp_msg); } */ if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) { return; } substr(m_fill.call_sign, call_sign, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.seq, seq, MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); m_fill.sec_heard = sec_now(); /* FROM */ m_fill.port=port; m_fill.active=RECORD_ACTIVE; m_fill.type=type; // We don't have a value filled in yet here! //if (m_fill.heard_via_tnc != VIA_TNC) m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC; substr(m_fill.call_sign,call_sign,MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.call_sign); substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN); (void)remove_trailing_asterisk(m_fill.from_call_sign); // Update the message field substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH); substr(m_fill.seq,seq,MAX_MESSAGE_ORDER); (void)remove_trailing_spaces(m_fill.seq); (void)remove_leading_spaces(m_fill.seq); // Create a timestamp from the current time snprintf(m_fill.packet_time, sizeof(m_fill.packet_time), "%s", get_time(time_data)); // Go try to add it to our alert hash. alert_build_list() will // check for duplicates before adding it. // TODO // alert_build_list(&m_fill); // This function fills in the Shapefile filename and index // so that we can later draw it. // TODO // fill_in_new_alert_entries(); } // End of alert_data_add() // What I'd like to do for the following routine: Use // XmTextGetInsertionPosition() or XmTextGetCursorPosition() to // find the last of the text. Could also save the position for // each SendMessage window. Compare the timestamps of messages // found with the last update time. If newer, then add them to // the end. This should stop the incessant scrolling. // Another idea, easier method: Create a buffer. Snag out the // messages from the array and sort by time. Put them into a // buffer. Figure out the length of the text widget, and append // the extra length of the buffer onto the end of the text widget. // Once the message data is turned into a linked list, it might // be sorted already by time, so this window will look better // anyway. // Calling update_messages with force == 1 will cause an update // no matter what message_update_time() says. void update_messages(int force) { // TODO } /* void update_messages(int force) { static XmTextPosition pos; char temp1[MAX_CALLSIGN+1]; char temp2[500]; char stemp[20]; long i; int mw_p; char *temp_ptr; if ( message_update_time() || force) { //fprintf(stderr,"update_messages()\n"); //fprintf(stderr,"Um %d\n",(int)sec_now() ); // go through all mw_p's! // Perform this for each message window for (mw_p=0; msg_index && mw_p < MAX_MESSAGE_WINDOWS; mw_p++) { //pos=0; begin_critical_section(&send_message_dialog_lock, "db.c:update_messages" ); if (mw[mw_p].send_message_dialog!=NULL) { //fprintf(stderr,"\n"); //fprintf(stderr,"found send_message_dialog\n"); // Clear the text from message window XmTextReplace(mw[mw_p].send_message_text, (XmTextPosition) 0, XmTextGetLastPosition(mw[mw_p].send_message_text), ""); // Snag the callsign you're dealing with from the message dialogue if (mw[mw_p].send_message_call_data != NULL) { temp_ptr = XmTextFieldGetString(mw[mw_p].send_message_call_data); snprintf(temp1, sizeof(temp1), "%s", temp_ptr); XtFree(temp_ptr); new_message_data--; if (new_message_data<0) new_message_data=0; if(strlen(temp1)>0) { // We got a callsign from the dialog so // create a linked list of the message indexes in time-sorted order typedef struct _index_record { int index; time_t sec_heard; struct _index_record *next; } index_record; index_record *head = NULL; index_record *p_prev = NULL; index_record *p_next = NULL; // Allocate the first record (a dummy record) head = (index_record *)malloc(sizeof(index_record)); CHECKMALLOC(head); head->index = -1; head->sec_heard = (time_t)0; head->next = NULL; (void)remove_trailing_spaces(temp1); (void)to_upper(temp1); pos = 0; // Loop through looking for messages to/from // that callsign (including SSID) for (i = 0; i < msg_index_end; i++) { if (msg_data[msg_index[i]].active == RECORD_ACTIVE && (strcmp(temp1, msg_data[msg_index[i]].from_call_sign) == 0 || strcmp(temp1,msg_data[msg_index[i]].call_sign) == 0) && (is_my_call(msg_data[msg_index[i]].from_call_sign, 1) || is_my_call(msg_data[msg_index[i]].call_sign, 1) || mw[mw_p].message_group ) ) { int done = 0; // Message matches our parameters so // save the relevant data about the // message in our linked list. Compare // the sec_heard field to see whether // we're higher or lower, and insert the // record at the correct spot in the // list. We end up with a time-sorted // list. p_prev = head; p_next = p_prev->next; while (!done && (p_next != NULL)) { // Loop until end of list or record inserted //fprintf(stderr,"Looping, looking for insertion spot\n"); if (p_next->sec_heard <= msg_data[msg_index[i]].sec_heard) { // Advance one record p_prev = p_next; p_next = p_prev->next; } else { // We found the correct insertion spot done++; } } //fprintf(stderr,"Inserting\n"); // Add the record in between p_prev and // p_next, even if we're at the end of // the list (in that case p_next will be // NULL. p_prev->next = (index_record *)malloc(sizeof(index_record)); CHECKMALLOC(p_prev->next); p_prev->next->next = p_next; // Link to rest of records or NULL p_prev->next->index = i; p_prev->next->sec_heard = msg_data[msg_index[i]].sec_heard; // Remember to free this entire linked list before exiting the loop for // this message window! } } // Done processing the entire list for this // message window. //fprintf(stderr,"Done inserting/looping\n"); if (head->next != NULL) { // We have messages to display int done = 0; //fprintf(stderr,"We have messages to display\n"); // Run through the linked list and dump the // info out. It's now in time-sorted order. // Another optimization would be to keep a count of records added, then // later when we were dumping it out to the window, only dump the last // XX records out. p_prev = head->next; // Skip the first dummy record p_next = p_prev->next; while (!done && (p_prev != NULL)) { // Loop until end of list int j = p_prev->index; // Snag the index out of the record char prefix[50]; char interval_str[50]; int offset = 22; // Offset for highlighting //fprintf(stderr,"\nLooping through, reading messages\n"); //fprintf(stderr,"acked: %d\n",msg_data[msg_index[j]].acked); // Message matches so snag the important pieces into a string snprintf(stemp, sizeof(stemp), "%c%c/%c%c %c%c:%c%c", msg_data[msg_index[j]].packet_time[0], msg_data[msg_index[j]].packet_time[1], msg_data[msg_index[j]].packet_time[2], msg_data[msg_index[j]].packet_time[3], msg_data[msg_index[j]].packet_time[8], msg_data[msg_index[j]].packet_time[9], msg_data[msg_index[j]].packet_time[10], msg_data[msg_index[j]].packet_time[11] ); // Somewhere in here we appear to be losing the first message. It // doesn't get written to the window later in the QSO. Same for // closing the window and re-opening it, putting the same callsign // in and pressing "New Call" button. First message is missing. // Label the message line with who sent it. // If acked = 2 a timeout has occurred // If acked = 3 a cancel has occurred if (msg_data[msg_index[j]].acked == 2) { snprintf(prefix, sizeof(prefix), "%s ", langcode("WPUPMSB016") ); // "*TIMEOUT*" } else if (msg_data[msg_index[j]].acked == 3) { snprintf(prefix, sizeof(prefix), "%s ", langcode("WPUPMSB017") ); // "*CANCELLED*" } else if (msg_data[msg_index[j]].acked == 4) { snprintf(prefix, sizeof(prefix), "%s ", langcode("WPUPMSB018") ); // "*REJECTED*" } else prefix[0] = '\0'; if (msg_data[msg_index[j]].interval) { snprintf(interval_str, sizeof(interval_str), ">%d/%lds", msg_data[msg_index[j]].tries + 1, (long)msg_data[msg_index[j]].interval); // Don't highlight the interval // value offset = offset + strlen(interval_str); } else { interval_str[0] = '\0'; } snprintf(temp2, sizeof(temp2), "%s %-9s%s>%s%s\n", // Debug code. Trying to find sorting error //"%ld %s %-9s>%s\n", //msg_data[msg_index[j]].sec_heard, stemp, msg_data[msg_index[j]].from_call_sign, interval_str, prefix, msg_data[msg_index[j]].message_line); //fprintf(stderr,"message: %s\n", msg_data[msg_index[j]].message_line); //fprintf(stderr,"update_messages: %s|%s", temp1, temp2); // Replace the text from pos to pos+strlen(temp2) by the string "temp2" if (mw[mw_p].send_message_text != NULL) { // Insert the text at the end // XmTextReplace(mw[mw_p].send_message_text, // pos, // pos+strlen(temp2), // temp2); XmTextInsert(mw[mw_p].send_message_text, pos, temp2); // Set highlighting based on the // "acked" field. Callsign // match here includes SSID. //fprintf(stderr,"acked: %d\t",msg_data[msg_index[j]].acked); if ( (msg_data[msg_index[j]].acked == 0) // Not acked yet && ( is_my_call(msg_data[msg_index[j]].from_call_sign, 1)) ) { //fprintf(stderr,"Setting underline\t"); XmTextSetHighlight(mw[mw_p].send_message_text, pos+offset, pos+strlen(temp2), //XmHIGHLIGHT_SECONDARY_SELECTED); // Underlining XmHIGHLIGHT_SELECTED); // Reverse Video } else { // Message was acked, get rid of highlighting //fprintf(stderr,"Setting normal\t"); XmTextSetHighlight(mw[mw_p].send_message_text, pos+offset, pos+strlen(temp2), XmHIGHLIGHT_NORMAL); } //fprintf(stderr,"Text: %s\n",temp2); pos += strlen(temp2); } // Advance to the next record in the list p_prev = p_next; if (p_next != NULL) p_next = p_prev->next; } // End of while } // End of if else { // No messages matched, list is empty } // What does this do? Move all of the text? // if (pos > 0) { // if (mw[mw_p].send_message_text != NULL) { // XmTextReplace(mw[mw_p].send_message_text, // --pos, // XmTextGetLastPosition(mw[mw_p].send_message_text), // ""); // } // } //fprintf(stderr,"Free'ing list\n"); // De-allocate the linked list p_prev = head; while (p_prev != NULL) { //fprintf(stderr,"You're free!\n"); p_next = p_prev->next; free(p_prev); p_prev = p_next; } // Show the last added message in the window XmTextShowPosition(mw[mw_p].send_message_text, pos); } } } end_critical_section(&send_message_dialog_lock, "db.c:update_messages" ); } last_message_update = sec_now(); //fprintf(stderr,"Message index end: %ld\n",msg_index_end); } } */ void mdelete_messages_from(char *from) { long i; // Mark message records with RECORD_NOTACTIVE. This will mark // them for re-use. for (i = 0; msg_index && i < msg_index_end; i++) if (strcmp(msg_data[i].call_sign, _aprs_mycall) == 0 && strcmp(msg_data[i].from_call_sign, from) == 0) msg_data[i].active = RECORD_NOTACTIVE; } void mdelete_messages_to(char *to) { long i; // Mark message records with RECORD_NOTACTIVE. This will mark // them for re-use. for (i = 0; msg_index && i < msg_index_end; i++) if (strcmp(msg_data[i].call_sign, to) == 0) msg_data[i].active = RECORD_NOTACTIVE; } void mdelete_messages(char *to_from) { long i; // Mark message records with RECORD_NOTACTIVE. This will mark // them for re-use. for (i = 0; msg_index && i < msg_index_end; i++) if (strcmp(msg_data[i].call_sign, to_from) == 0 || strcmp(msg_data[i].from_call_sign, to_from) == 0) msg_data[i].active = RECORD_NOTACTIVE; } void mdata_delete_type(const char msg_type, const time_t reference_time) { long i; // Mark message records with RECORD_NOTACTIVE. This will mark // them for re-use. for (i = 0; msg_index && i < msg_index_end; i++) if ((msg_type == '\0' || msg_type == msg_data[i].type) && msg_data[i].active == RECORD_ACTIVE && msg_data[i].sec_heard < reference_time) msg_data[i].active = RECORD_NOTACTIVE; } void check_message_remove(time_t curr_sec) { // called in timing loop // Time to check for old messages again? (Currently every ten // minutes) #ifdef EXPIRE_DEBUG if ( last_message_remove < (curr_sec - DEBUG_MESSAGE_REMOVE_CYCLE) ) { #else // EXPIRE_DEBUG if ( last_message_remove < (curr_sec - MESSAGE_REMOVE_CYCLE) ) { #endif // Yes it is. Mark all messages that are older than // sec_remove with the RECORD_NOTACTIVE flag. This will // mark them for re-use. #ifdef EXPIRE_DEBUG mdata_delete_type('\0', curr_sec-DEBUG_MESSAGE_REMOVE); #else // EXPIRE_DEBUG mdata_delete_type('\0', curr_sec-_aprs_sec_remove); #endif last_message_remove = curr_sec; } // Should we sort them at this point so that the unused ones are // near the end? It looks like the message input functions do // this, so I guess we don't need to do it here. } void mscan_file(char msg_type, void (*function)(Message *)) { long i; for (i = 0; msg_index && i < msg_index_end; i++) if ((msg_type == '\0' || msg_type == msg_data[msg_index[i]].type) && msg_data[msg_index[i]].active == RECORD_ACTIVE) function(&msg_data[msg_index[i]]); } void mprint_record(Message *m_fill) { /* fprintf(stderr, "%-9s>%-9s %s:%5s %s:%c :%s\n", m_fill->from_call_sign, m_fill->call_sign, langcode("WPUPMSB013"), // "seq" m_fill->seq, langcode("WPUPMSB014"), // "type" m_fill->type, m_fill->message_line); */ } void mdisplay_file(char msg_type) { fprintf(stderr,"\n\n"); mscan_file(msg_type, mprint_record); fprintf(stderr,"\tmsg_index_end %ld, msg_index_max %ld\n", msg_index_end, msg_index_max); } int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party) { char *temp_ptr; char ipacket_message[300]; char message_plus_acks[MAX_MESSAGE_LENGTH + 10]; char from_call[MAX_CALLSIGN+1]; char ack[20]; int ok, len; char addr[9+1]; char addr9[9+1]; char msg_id[5+1]; char orig_msg_id[5+1]; char ack_string[6]; int done; int reply_ack = 0; int to_my_call = 0; int to_my_base_call = 0; int from_my_call = 0; // :xxxxxxxxx:____0-67____ message printable, except '|', '~', '{' // :BLNn :____0-67____ general bulletin printable, except '|', '~' // :BLNnxxxxx:____0-67____ + Group Bulletin // :BLNX :____0-67____ Announcement // :NWS-xxxxx:____0-67____ NWS Service Bulletin // :NWS_xxxxx:____0-67____ NWS Service Bulletin // :xxxxxxxxx:ackn1-5n + ack // :xxxxxxxxx:rejn1-5n + rej // :xxxxxxxxx:____0-67____{n1-5n + message // :NTS.... // 01234567890123456 // 01234567890123456 old // we get message with already extracted data ID if (is_my_call(call, 1) ) { // Check SSID also from_my_call++; } ack_string[0] = '\0'; // Clear out the Reply/Ack result string len = (int)strlen(message); ok = (int)(len > 9 && message[9] == ':'); if (ok) { //fprintf(stderr,"DEBUG: decode_message: from %s: %s\n", call, message); //return 0; substr(addr9,message,9); // extract addressee snprintf(addr, sizeof(addr), "%s", addr9); (void)remove_trailing_spaces(addr); if (is_my_call(addr,1)) { // Check includes SSID to_my_call++; } if (is_my_call(addr,0)) { // Check ignores SSID. We use // this to catch messages to some // of our other SSID's to_my_base_call++; } message = message + 10; // pointer to message text // Save the message text and the acks/reply-acks before we // extract the acks below. snprintf(message_plus_acks, sizeof(message_plus_acks), "%s", message); temp_ptr = strrchr(message,'{'); // look for message ID after //*last* { in message. msg_id[0] = '\0'; if (temp_ptr != NULL) { substr(msg_id,temp_ptr+1,5); // extract message ID, could be non-digit temp_ptr[0] = '\0'; // adjust message end (chops off message ID) } // Save the original msg_id away. snprintf(orig_msg_id, sizeof(orig_msg_id), "%s", msg_id); // Check for Reply/Ack protocol in msg_id, which looks like // this: "{XX}BB", where XX is the sequence number for the // message, and BB is the ack for the previous message from // my station. I've also seen this from APRS+: "{XX}B", so // perhaps this is also possible "{X}B" or "{X}BB}". We can // also get auto-reply responses from APRS+ that just have // "}X" or "}XX" at the end. We decode those as well. // temp_ptr = strstr(msg_id,"}"); // look for Reply Ack in msg_id if (temp_ptr != NULL) { // Found Reply/Ack protocol! reply_ack++; // Put this code into the UI message area as well (if applicable). // Separate out the extra ack so that we can deal with // it properly. snprintf(ack_string, sizeof(ack_string), "%s", temp_ptr+1); // After the '}' character! // Terminate it here so that rest of decode works // properly. We can get duplicate messages // otherwise. // // Note that we modify msg_id here. Use orig_msg_id if we need the // unmodified version (full REPLY-ACK version) later. // temp_ptr[0] = '\0'; // adjust msg_id end } else { // Look for Reply Ack in message without sequence // number temp_ptr = strstr(message,"}"); if (temp_ptr != NULL) { int yy = 0; reply_ack++; // Put this code into the UI message area as well (if applicable). snprintf(ack_string, sizeof(ack_string), "%s", temp_ptr+1); // After the '}' character! ack_string[yy] = '\0'; // Terminate the string // Terminate it here so that rest of decode works // properly. We can get duplicate messages // otherwise. temp_ptr[0] = '\0'; // adjust message end } } done = 0; } else { done = 1; // fall through... } len = (int)strlen(message); //-------------------------------------------------------------------------- if (!done && len > 3 && strncmp(message,"ack",3) == 0) { // ACK // Received an ACK packet. Note that these can carry the // REPLY-ACK protocol or a single ACK sequence number plus // perhaps an extra '}' on the end. They should have one of // these formats: // ack1 Normal ACK // ackY Normal ACK // ack23 Normal ACK // ackfH Normal ACK // ack23{ REPLY-ACK Protocol // ack2Q}3d REPLY-ACK Protocol substr(msg_id,message+3,5); // fprintf(stderr,"ACK: %s: |%s| |%s|\n",call,addr,msg_id); if (to_my_call) { // Check SSID also // Note: This function handles REPLY-ACK protocol just // fine, stripping off the 2nd ack if present. It uses // only the first sequence number. clear_acked_message(call,addr,msg_id); // got an ACK for me // This one also handles REPLY-ACK protocol just fine. msg_record_ack(call,addr,msg_id,0,0); // Record the ack for this message } else { // ACK is for another station // Now if I have Igate on and I allow to retransmit station data // check if this message is to a person I have heard on my TNC within an X // time frame. If if is a station I heard and all the conditions are ok // spit the ACK out on the TNC -FG /* * TODO - Add igate support if (operate_as_an_igate>1 && from==DATA_VIA_NET && !from_my_call // Check SSID also && port != -1) { // Not from a log file char short_path[100]; shorten_path(path,short_path,sizeof(short_path)); // Only send '}' and the ack_string if it's not // empty, else just end the packet with the message // string. This keeps us from appending a '}' when // it's not called for. snprintf(ipacket_message, sizeof(ipacket_message), "}%s>%s,TCPIP,%s*::%s:%s", call, short_path, my_callsign, addr9, message_plus_acks); output_igate_rf(call, addr, path, ipacket_message, port, third_party, NULL); igate_msgs_tx++; } */ } done = 1; } //-------------------------------------------------------------------------- if (!done && len > 3 && strncmp(message,"rej",3) == 0) { // REJ substr(msg_id,message+3,5); if (to_my_call) { // Check SSID also // Note: This function handles REPLY-ACK protocol just // fine, stripping off the 2nd ack if present. It uses // only the first sequence number. clear_acked_message(call,addr,msg_id); // got an REJ for me // This one also handles REPLY-ACK protocol just fine. msg_record_rej(call,addr,msg_id); // Record the REJ for this message } else { // REJ is for another station /* Now if I have Igate on and I allow to retransmit station data */ /* check if this message is to a person I have heard on my TNC within an X */ /* time frame. If if is a station I heard and all the conditions are ok */ /* spit the REJ out on the TNC */ /* * TODO - Add igate support if (operate_as_an_igate>1 && from==DATA_VIA_NET && !from_my_call // Check SSID also && port != -1) { // Not from a log file char short_path[100]; shorten_path(path,short_path,sizeof(short_path)); // Only send '}' and the rej_string if it's not // empty, else just end the packet with the message // string. This keeps us from appending a '}' when // it's not called for. snprintf(ipacket_message, sizeof(ipacket_message), "}%s>%s,TCPIP,%s*::%s:%s", call, short_path, my_callsign, addr9, message_plus_acks); output_igate_rf(call, addr, path, ipacket_message, port, third_party, NULL); igate_msgs_tx++; } */ } done = 1; } //-------------------------------------------------------------------------- if (!done && strncmp(addr,"BLN",3) == 0) { // Bulletin // fprintf(stderr,"found BLN: |%s| |%s|\n",addr,message); bulletin_data_add(addr,call,message,"",MESSAGE_BULLETIN,port); done = 1; } //-------------------------------------------------------------------------- if (!done && strlen(msg_id) > 0 && to_my_call) { // Message for me (including SSID check) // with msg_id (sequence number) time_t last_ack_sent; long record; // Remember to put this code into the UI message area as well (if // applicable). // Check for Reply/Ack if (reply_ack && strlen(ack_string) != 0) { // Have a free-ride ack to deal with clear_acked_message(call,addr,ack_string); // got an ACK for me msg_record_ack(call,addr,ack_string,0,0); // Record the ack for this message } // Save the ack 'cuz we might need it while talking to this // station. We need it to implement Reply/Ack protocol. // Note that msg_id has already been truncated by this point. // orig_msg_id contains the full REPLY-ACK text. //fprintf(stderr, "store_most_recent_ack()\n"); store_most_recent_ack(call,msg_id); // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id); last_ack_sent = msg_data_add(addr, call, message, msg_id, MESSAGE_MESSAGE, port, &record); // id_fixed // Here we need to know if it is a new message or an old. // If we've already received it, we don't want to kick off // the alerts or pop up the Send Message dialog again. If // last_ack_sent == (time_t)0, then it is a new message. // if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before new_message_data += 1; // Note that the check_popup_window() function will // re-create a Send Message dialog if one doesn't exist // for this QSO. Only call it for the first message // line or the first ack, not for any repeats. // (void)check_popup_window(call, 2); // Calls update_messages() //update_messages(1); // Force an update } // Try to only send an ack out once per 30 seconds at the // fastest. //WE7U // Does this 30-second check work? // if ( (last_ack_sent != (time_t)-1l) // Not an error && (last_ack_sent + 30 ) < sec_now() && !satellite_ack_mode // Disable separate ack's for satellite work && port != -1 ) { // Not from a log file char path[MAX_LINE_SIZE+1]; // Update the last_ack_sent field for the message msg_update_ack_stamp(record); pad_callsign(from_call,call); /* ack the message */ // Attempt to snag a custom path out of the Send Message // dialog, if set. If not set, path will contain '\0'; get_send_message_path(call, path, MAX_LINE_SIZE+1); //fprintf(stderr,"Path: %s\n", path); // In this case we want to send orig_msg_id back, not // the (possibly) truncated msg_id. This is per Bob B's // Reply/Ack spec, sent to xastir-dev on Nov 14, 2001. snprintf(ack, sizeof(ack), ":%s:ack%s",from_call,orig_msg_id); //WE7U // Need to figure out the reverse path for this one instead of // passing a NULL for the path? Probably not, as auto-calculation // of paths isn't a good idea. // // What we need to do here is check whether we have a custom path // set for this QSO. If so, pass that path along as the transmit // path. messages.h:Message_Window struct has the send_message_path // variable in it. If a Message_Window still exists for this QSO // then we can snag the user-entered path from there. If the struct // has already been destroyed then we have nowhere to snag the // custom path from and have to rely on the default paths in each // interface properties dialog instead. Then again, we _could_ snag // the path out of the last received message in the message database // for that case. Might be better to disable the Close button, or // warn the user that the custom path will be lost if they close the // Send Message dialog. // Send out the immediate ACK if (path[0] == '\0') transmit_message_data(call,ack,NULL); else transmit_message_data(call,ack,path); if (record != -1l) { // Msg we've received before // It's a message that we've received before, // consider sending an extra ACK in about 30 seconds // to try to get it to the remote station. Perhaps // another one in 60 seconds as well. if (path[0] == '\0') { transmit_message_data_delayed(call,ack,NULL,sec_now()+30); transmit_message_data_delayed(call,ack,NULL,sec_now()+60); transmit_message_data_delayed(call,ack,NULL,sec_now()+120); } else { transmit_message_data_delayed(call,ack,path,sec_now()+30); transmit_message_data_delayed(call,ack,path,sec_now()+60); transmit_message_data_delayed(call,ack,path,sec_now()+120); } } /* * TODO if (auto_reply == 1) { snprintf(ipacket_message, sizeof(ipacket_message), "AA:%s", auto_reply_message); if (!from_my_call) // Check SSID also output_message(my_callsign, call, ipacket_message, ""); } */ } done = 1; } //-------------------------------------------------------------------------- if (!done && strlen(msg_id) == 0 && to_my_call) { // Message for me (including SSID check) // but without message-ID. // These should appear in a Send Message dialog and should // NOT get ack'ed. Kenwood radios send this message type as // an auto-answer or a buffer-full message. They look // something like: // // :WE7U-13 :Not at keyboard. // time_t last_ack_sent; long record; if (len > 2 && message[0] == '?' && port != -1 // Not from a log file && to_my_call) { // directed query (check SSID also) // Smallest query known is "?WX". done = process_directed_query(call,path,message+1,port); } // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id); last_ack_sent = msg_data_add(addr, call, message, msg_id, MESSAGE_MESSAGE, port, &record); // id_fixed // Here we need to know if it is a new message or an old. // If we've already received it, we don't want to kick off // the alerts or pop up the Send Message dialog again. If // last_ack_sent == (time_t)0, then it is a new message. // if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before new_message_data += 1; // Note that the check_popup_window() function will // re-create a Send Message dialog if one doesn't exist // for this QSO. Only call it for the first message // line or the first ack, not for any repeats. // //fprintf(stderr,"***check_popup_window 1\n"); (void)check_popup_window(call, 2); // Calls update_messages() //update_messages(1); // Force an update } // Update the last_ack_sent field for the message, even // though we won't be sending an ack in response. msg_update_ack_stamp(record); done = 1; } //-------------------------------------------------------------------------- if (!done && ( (strncmp(addr,"NWS-",4) == 0) // NWS weather alert || (strncmp(addr,"NWS_",4) == 0) ) ) { // NWS weather alert compressed // could have sort of line number //fprintf(stderr,"found NWS: |%s| |%s| |%s|\n",addr,message,msg_id); (void)alert_data_add(addr, call, message, msg_id, MESSAGE_NWS, port); done = 1; /* * TODO - add igate support if (operate_as_an_igate>1 && from==DATA_VIA_NET && !from_my_call // Check SSID also && port != -1) { // Not from a log file char short_path[100]; shorten_path(path,short_path,sizeof(short_path)); snprintf(ipacket_message, sizeof(ipacket_message), "}%s>%s,TCPIP,%s*::%s:%s", call, short_path, my_callsign, addr9, message); output_nws_igate_rf(call, path, ipacket_message, port, third_party); } */ } //-------------------------------------------------------------------------- if (!done && strncmp(addr,"SKY",3) == 0) { // NWS weather alert additional info // could have sort of line number //fprintf(stderr,"found SKY: |%s| |%s| |%s|\n",addr,message,msg_id); // We don't wish to record these in memory. They cause an infinite // loop in the current code and a massive memory leak. return(1); // Tell the calling program that the packet was ok so // that it doesn't add it with data_add() itself! done = 1; /* * TODO - add igate support if (operate_as_an_igate>1 && from==DATA_VIA_NET && !from_my_call // Check SSID also && port != -1) { // Not from a log file char short_path[100]; shorten_path(path,short_path,sizeof(short_path)); snprintf(ipacket_message, sizeof(ipacket_message), "}%s>%s,TCPIP,%s*::%s:%s", call, short_path, my_callsign, addr9, message); output_nws_igate_rf(call, path, ipacket_message, port, third_party); } */ } //-------------------------------------------------------------------------- if (!done && strlen(msg_id) > 0) { // Other message with linenumber. This // is either a message for someone else // or a message for another one of my // SSID's. long record_out; time_t last_ack_sent; char message_plus_note[MAX_MESSAGE_LENGTH + 30]; if (to_my_base_call && !from_my_call) { // Special case: We saw a message w/msg_id that was to // one of our other SSID's, but it was not from // ourselves. That last bit (!from_my_call) is // important in the case where we're working an event // with several stations using the same callsign. // // Store as if it came to my callsign, with a zeroed-out // msg_id so we can't try to ack it. We also need some // other indication in the "Send Message" dialog as to // what's happening. Perhaps add the original callsign // to the message itself in a note at the start? // snprintf(message_plus_note, sizeof(message_plus_note), "(Sent to:%s) %s", addr, message); last_ack_sent = msg_data_add(_aprs_mycall, call, message_plus_note, "", MESSAGE_MESSAGE, port, &record_out); } else { // Normal case, messaging between other people last_ack_sent = msg_data_add(addr, call, message, msg_id, MESSAGE_MESSAGE, port, &record_out); } new_message_data += look_for_open_group_data(addr); // Note that the check_popup_window() function will // re-create a Send Message dialog if one doesn't exist for // this QSO. Only call it for the first message line or the // first ack, not for any repeats. // if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before // Callsign check here also checks SSID for exact match // We need to do an SSID-non-specific check here so that we can pick // up messages intended for other stations of ours. if ((to_my_base_call && check_popup_window(call, 2) != -1) || check_popup_window(call, 0) != -1 || check_popup_window(addr, 1) != -1) { update_messages(1); // Force an update } } /* Now if I have Igate on and I allow to retransmit station data */ /* check if this message is to a person I have heard on my TNC within an X */ /* time frame. If if is a station I heard and all the conditions are ok */ /* spit the message out on the TNC -FG */ /* * TODO - add igate support if (operate_as_an_igate>1 && last_ack_sent != (time_t)-1l && from==DATA_VIA_NET && !from_my_call // Check SSID also && !to_my_call // Check SSID also && port != -1) { // Not from a log file char short_path[100]; shorten_path(path,short_path,sizeof(short_path)); snprintf(ipacket_message, sizeof(ipacket_message), "}%s>%s,TCPIP,%s*::%s:%s", call, short_path, my_callsign, addr9, message_plus_acks); output_igate_rf(call, addr, path, ipacket_message, port, third_party, NULL); igate_msgs_tx++; } */ done = 1; } //-------------------------------------------------------------------------- if (!done) { // message without line number long record_out; time_t last_ack_sent; last_ack_sent = msg_data_add(addr, call, message, "", MESSAGE_MESSAGE, port, &record_out); new_message_data++; // ?????? // Note that the check_popup_window() function will // re-create a Send Message dialog if one doesn't exist for // this QSO. Only call it for the first message line or the // first ack, not for any repeats. // if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before if (check_popup_window(addr, 1) != -1) { //update_messages(1); // Force an update } } // Could be response to a query. Popup a messsage. // Check addr for my_call and !third_party, then check later in the // packet for my_call if it is a third_party message? Depends on // what the packet looks like by this point. if ( last_ack_sent != (time_t)-1l && (message[0] != '?') && to_my_call ) { // Check SSID also // We no longer wish to have both popups and the Send // Group Message dialogs come up for every query // response, so we use popup_message() here instead of // popup_message_always() so that by default we'll see // the below message in STDERR. If --with-errorpopups // has been configured in, we'll get a popup as well. // Send Group Message dialogs work well for multi-line // query responses, so we'll leave it that way. // // TODO // popup_message(langcode("POPEM00018"),message); // Check for Reply/Ack. APRS+ sends an AA: response back // for auto-reply, with an embedded free-ride Ack. if (strlen(ack_string) != 0) { // Have an extra ack to deal with clear_acked_message(call,addr,ack_string); // got an ACK for me msg_record_ack(call,addr,ack_string,0,0); // Record the ack for this message } } // done = 1; } //-------------------------------------------------------------------------- if (ok) (void)data_add(STATION_CALL_DATA, call, path, message, port, NULL, third_party, 0, // Not a packet from my station 0); // Not my object/item return(ok); } #endif //INCLUDE_APRS