SP8EBC-ParaTNC/system/src/aprs/message.c

571 wiersze
19 KiB
C

/*
* message.c
*
* Created on: Apr 20, 2024
* Author: mateusz
*/
#include "message.h"
#include "variant.h"
#include "string.h"
#include "stdio.h"
#ifdef UNIT_TEST
#define STATIC
#else
#define STATIC static
#endif
#define MESSAGE_RECIPIENT_FIELD_SIZE 9
#define MESSAGE_SSID_CHARS_LN 2
#define MESSAGE_SENDER_CALL_SSID_MAXLEN 9
#define MESSAGE_COUNTER_MAXLEN 4
#define MESSAGE_MINIMUM_SENSEFUL_LN (MESSAGE_SENDER_CALL_SSID_MAXLEN + MESSAGE_RECIPIENT_FIELD_SIZE + MESSAGE_COUNTER_MAXLEN)
#define MESSAGE_CURRENT_CHAR *(message + content_position + i)
#define MESSAGE_CURRENT_SENDER_CHAR *(message + i)
#define MESSAGE_IS_HEX_DIGIT(c) ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
#define MESSAGE_IS_DIGIT(c) ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
#define MESSAGE_IS_DIGIT_OR_LETTER(c) ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
#define MESSAGE_ACK_REMAINING_BUF (out_buffer_ln - current_pos)
#define MESSAGE_ACK_CURRENT_POS (char*)(out_buffer + current_pos)
static uint8_t message_tx_msg_couter = 0;
/**
* Lookup table used to string-to-int conversion for HEX strings. Value of 0x30 should be
* subtracted from ASCII scancode and this value should be used to intex this table
*/
static const int8_t message_atoi_lookup[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-1, -1, -1, -1, -1, -1, -1,
10, 11, 12, 13, 14, 15,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1,
10, 11, 12, 13, 14, 15};
/**
* Convert string with message number to integer, with automatic detection of decimal or hex base. It operates
* only on positive integers up to 32 bit wide.
* @param string
* @param string_ln
* @param output optional pointer to message structure when output will be also put
* @return unsigned integer with decoded number
*/
STATIC uint32_t message_atoi_message_counter(const uint8_t const * string, uint8_t string_ln, message_t * output)
{
// sum to be returned
uint32_t sum = 0u;
// temporary buffer
int8_t conversion_table[6];
// set to non zero if any digit from A fo F was found in input string
int8_t is_hex = 0;
// checck if input string is located in legit RAM memory
if (variant_validate_is_within_ram(string) == 1 && string_ln <= 6) {
// clear intermediate convertion buffer
memset(conversion_table, 0x00, 6);
// iterator over conversion_table array
int8_t conversion_it = string_ln;
// iterate from the end of a string
for (int8_t string_it = string_ln; string_it >= 0; string_it--) {
// currently processed character
const uint8_t current_char = *(string + string_it - 1);
// check if current character is hex or dec base digit
if (MESSAGE_IS_DIGIT(current_char)) {
// calculate index to lookup table basing on current character
const uint8_t index = current_char - 0x30u;
// check lookup table for converting character with a digit to a number
conversion_table[string_ln - conversion_it] = message_atoi_lookup[index];
// decrement an interator
conversion_it--;
if (MESSAGE_IS_HEX_DIGIT(current_char)) {
is_hex = 1;
}
}
}
if (is_hex == 1) {
sum = (conversion_table[0]);
sum += (conversion_table[1] * 0x10);
sum += (conversion_table[2] * 0x100);
sum += (conversion_table[3] * 0x1000);
sum += (conversion_table[4] * 0x10000);
sum += (conversion_table[5] * 0x100000);
}
else {
sum = (conversion_table[0]);
sum += (conversion_table[1] * 10);
sum += (conversion_table[2] * 100);
sum += (conversion_table[3] * 1000);
sum += (conversion_table[4] * 10000);
sum += (conversion_table[5] * 100000);
}
}
if (variant_validate_is_within_ram(output) == 1) {
output->number = sum & 0xFFu;
if (is_hex == 1) {
if (output->source == MESSAGE_SOURCE_APRSIS) {
output->source = MESSAGE_SOURCE_APRSIS_HEXCNTR;
}
else if (output->source == MESSAGE_SOURCE_RADIO) {
output->source = MESSAGE_SOURCE_RADIO_HEXCNTR;
}
}
}
return sum;
}
/**
* Decode received data to look for an APRS message and put it into a structure
* @param message pointer to data received from APRS-IS
* @param message_ln lenght of a buffer content
* @param content_position optional position of an APRS message content (recipient callsign). if set to zero function will look for it
* @param output parsed APRS message content
* @return zero if message has been found and decoded, non zero if parsing failed
*/
uint8_t message_decode(const uint8_t * const message, const uint16_t message_ln, uint16_t content_position, message_source_t src, message_t * output) {
// example message:: SP8EBC>APX216,TCPIP*,qAC,NINTH::SR9WXZ :tedt{0s}\r\n
// result returned by the function, although shamesly it is also used as local aux variable
// in few places of this function :( this is what You sometimes do to save some stack.
uint8_t result = 0xFF;
uint16_t i = 0;
if (output == 0x00) {
return result;
}
if (message_ln < (MESSAGE_SENDER_CALL_SSID_MAXLEN + MESSAGE_RECIPIENT_FIELD_SIZE)) {
return result;
}
memset(output->number_str, 0x00, MESSAGE_NUMBER_STRING_BUFFER);
// if start position of APRS message (position of recipient callsign) has not been provided
if ((src == MESSAGE_SOURCE_APRSIS) && (content_position == 0)) {
// look for it
for (i = 0; i < message_ln; i++) {
const uint8_t * this_character = message + i;
const uint8_t * next_character = message + i + 1;
// break on an end of input string
if (*this_character == 0x00) {
break;
}
// check if double semicolon has been found
if ((*this_character == ':') && (*next_character == ':')) {
// APRS message starts after second semicolong
content_position = i + 2;
break;
}
}
}
// clear the iterator, it will be used across this function
i = 0;
// check content position one more time to verify
// if input data contains APRS message at all
if (
((src == MESSAGE_SOURCE_APRSIS) && (content_position != 0)) ||
(src == MESSAGE_SOURCE_RADIO)
) {
// set this variable to zero as now it will be used as a local
result = 0;
// clear output structure to make room for new data
memset(output, 0x00, sizeof(message_t));
//extract sender call and SSID only if this message has been received from APRS-IS in pure text form
if (src == MESSAGE_SOURCE_APRSIS) {
// fast forward any potential whitespace at the begining
while (MESSAGE_CURRENT_SENDER_CHAR == ' ') {
i++;
if (i >= message_ln) {
break;
}
}
// extract sender callsign
for (; i < MESSAGE_SENDER_CALL_SSID_MAXLEN; i++) {
// if SSID separator or sender end character ('>') has been reached
if (MESSAGE_CURRENT_SENDER_CHAR == '-' || MESSAGE_CURRENT_SENDER_CHAR == '>') {
break;
}
output->from.call[result++] = MESSAGE_CURRENT_SENDER_CHAR;
}
// check if sender has SSID
if (MESSAGE_CURRENT_SENDER_CHAR == '-') {
// jumps to next character. otherwise separating '-'
// will be interpreted as a minus/negitive sign
i++;
result = 0; // here used as a local iterator
// extract SSID
for (; i < MESSAGE_RECIPIENT_FIELD_SIZE; i++) {
// copy characters to aux buffer
output->number_str[result++] = MESSAGE_CURRENT_SENDER_CHAR;
// check if there isn't enough characters
if (result == MESSAGE_NUMBER_STRING_BUFFER) {
break;
}
}
// convert SSID to int
output->from.ssid = atoi(output->number_str);
}
// clear the iterator, it will be used across this function
i = 0;
}
else {
// clear content_position iterator
content_position = 0;
// find a begining of the message in data from radio channel
while (MESSAGE_CURRENT_CHAR != ':') {
content_position++;
if (content_position >= message_ln) {
break;
}
}
// jump over ':'
content_position++;
}
// extract recipient
for (; i < MESSAGE_RECIPIENT_FIELD_SIZE; i++) {
// look if end of callsign or separating '-' has been found
if (MESSAGE_CURRENT_CHAR == ' ' || MESSAGE_CURRENT_CHAR == '-') {
break; // and go for SSID reading
}
// copy recipient callsign character per character
output->to.call[i] = MESSAGE_CURRENT_CHAR;
}
// check if callsign has SSID set
if (MESSAGE_CURRENT_CHAR == '-') {
// jumps to next character. otherwise separating '-'
// will be interpreted as a minus/negitive sign
i++;
result = 0; // here used as a local iterator
// extract SSID
for (; i < MESSAGE_RECIPIENT_FIELD_SIZE; i++) {
// copy characters to aux buffer
output->number_str[result++] = MESSAGE_CURRENT_CHAR;
// check if there isn't enough characters
if (result == MESSAGE_NUMBER_STRING_BUFFER) {
break;
}
}
// convert SSID to int
output->to.ssid = atoi(output->number_str);
}
if (result != MESSAGE_NUMBER_STRING_BUFFER && /* if SSID extraction was OK and end of a buffer hasn't been reached */
(
(i < MESSAGE_RECIPIENT_FIELD_SIZE) || /* if recipient and callsign has been read before ':' separating from message content */
((i == MESSAGE_RECIPIENT_FIELD_SIZE) && (MESSAGE_CURRENT_CHAR == ':')) /* if a position of separating ':' was reached and ':' is there */
)
) {
// reinitialize buffer before next usage
memset(output->number_str, 0x00, MESSAGE_NUMBER_STRING_BUFFER);
// check if the iterator is set now to position of ':' separating
// recipient an the message itself
while(MESSAGE_CURRENT_CHAR != ':' && i <= MESSAGE_RECIPIENT_FIELD_SIZE) {
i++;
}
// one more incrementation to jump over ':' and land on the first character of the message
i++;
result = 0;
output->content_ln = 0;
// then copy message, which ends on a counter, something like '{1'
while(MESSAGE_CURRENT_CHAR != ':' && i + content_position < message_ln) {
output->content[result++] = MESSAGE_CURRENT_CHAR;
output->content_ln++;
i++;
// break on message counter separator
if (MESSAGE_CURRENT_CHAR == '{') {
i++; // move to first digit of a counter
break;
}
}
// check which condition has ended previous 'while' loop and if an end of the buffer has been reached
if (i + content_position < message_ln) {
result = 0;
// now iterator is set (should be set) on a first digit of message counter
// copy everything until first non digit character is found
while (MESSAGE_IS_DIGIT_OR_LETTER(MESSAGE_CURRENT_CHAR)) {
output->number_str[result++] = MESSAGE_CURRENT_CHAR;
i++;
// check if there isn't enough characters
if (result == MESSAGE_NUMBER_STRING_BUFFER) {
break;
}
}
output->source = src;
// convert message counter from string to int
message_atoi_message_counter(output->number_str, result, output);
if (result < MESSAGE_NUMBER_STRING_BUFFER) {
// new we are done (??)
result = 0;
}
}
else {
result = 0xFF;
}
}
else {
result = 0xFF;
}
}
return result;
}
/**
*
* @param input
* @param output
* @param output_ln
* @param encode_for
* @return
*/
uint16_t message_encode(message_t * input, uint8_t * output, uint16_t output_ln, message_source_t encode_for)
{
uint16_t current_pos = 0;
uint8_t recipient_pos = 0;
if( variant_validate_is_within_ram(input) &&
variant_validate_is_within_ram(output) &&
output_ln > (MESSAGE_MINIMUM_SENSEFUL_LN + input->content_ln)) {
// check the lenght of sender (from) callsign
const uint8_t from_call_ln = (input->from.call[5] != 0x00) ? 6 : strlen(input->from.call);
// check the lenght of recipient (to) callsign
const uint8_t to_call_ln = (input->to.call[5] != 0x00) ? 6 : strlen(input->from.call);
if (encode_for == MESSAGE_SOURCE_APRSIS) {
// put my callsign
memcpy(output, input->from.call, from_call_ln);
current_pos += from_call_ln;
// check if SSID is set
if (input->from.ssid > 0 && input->from.ssid < 16) {
current_pos += sprintf(output + current_pos, "-%d>AKLPRZ:", input->from.ssid);
}
else {
current_pos += sprintf(output + current_pos, ">AKLPRZ:");
}
}
*(output + current_pos) = ':';
current_pos++;
// put recipient callsign
memcpy(output + current_pos, input->to.call, to_call_ln);
recipient_pos = to_call_ln;
// and optional SSID
if (input->to.ssid > 0 && input->to.ssid < 16) {
recipient_pos += sprintf(output + current_pos + recipient_pos, "-%d", input->to.ssid);
}
else {
;
}
current_pos += recipient_pos;
// check how many characters were put and if padding needs to be applied
while (recipient_pos < MESSAGE_RECIPIENT_FIELD_SIZE) {
*(output + current_pos) = ' ';
current_pos++;
recipient_pos++;
}
if (current_pos + input->content_ln < output_ln) {
// put message content itself
current_pos += sprintf(output + current_pos, ":%s", input->content);
// put message counter
if (current_pos + MESSAGE_COUNTER_MAXLEN < output_ln) {
current_pos += sprintf(output + current_pos, "{%x}", message_tx_msg_couter);
message_tx_msg_couter++;
*(output + current_pos) = 0x00;
}
else {
current_pos = 0;
}
}
else {
current_pos = 0;
}
}
return current_pos;
}
/**
*
* @param config_data
* @param message
* @return zero if this is a message to us, non zero otherwise
*/
uint8_t message_is_for_me(const char * const callsign, const uint8_t ssid, const message_t * const message)
{
const int _callsign = strncmp(callsign, message->to.call, 6);
if (_callsign == 0 && (ssid == message->to.ssid)) {
return 0;
}
else {
return 1;
}
}
/**
*
* @param out_buffer
* @param out_buffer_ln
* @param message
* @param src how this message has been received
*/
int message_create_ack_for(uint8_t * out_buffer, const uint16_t out_buffer_ln, const message_t * const message)
{
int current_pos = 0;
uint8_t call_position = 0;
const message_source_t src = message->source;
// clear output buffer
memset(out_buffer, 0x00, out_buffer_ln);
if (src == MESSAGE_SOURCE_APRSIS || src == MESSAGE_SOURCE_APRSIS_HEXCNTR) {
// put my callsign
for (; call_position < 6; call_position++) {
// break on null character
if (message->to.call[call_position] == 0x00) {
break;
}
// copy callsign data
out_buffer[current_pos + call_position] = message->to.call[call_position];
}
current_pos += call_position;
call_position = 0;
// check if I have a SSID
if (message->to.ssid != 0) {
current_pos += snprintf(MESSAGE_ACK_CURRENT_POS, MESSAGE_ACK_REMAINING_BUF, "-%d", message->to.ssid);
}
// constant part
current_pos += snprintf(MESSAGE_ACK_CURRENT_POS, MESSAGE_ACK_REMAINING_BUF, ">AKLPRZ::");
// put sender callsign, station I received this message from
for (; call_position < 6; call_position++) {
// break on null character
if (message->from.call[call_position] == 0x00) {
break;
}
// copy callsign data
out_buffer[current_pos + call_position] = message->from.call[call_position];
}
// put sender SSID, station I received this message from
if (message->from.ssid != 0) {
call_position += snprintf(MESSAGE_ACK_CURRENT_POS + call_position, MESSAGE_ACK_REMAINING_BUF, "-%d", message->from.ssid);
}
// check if callsign was shorter than 6 characters
while (call_position < 9) {
// copy callsign data
out_buffer[current_pos + call_position] = ' ';
call_position++;
}
// callsign + ssid + padding must be exactly 9 characters long
current_pos += 9;
// then put 'ackXX' where X is message number
current_pos += snprintf(MESSAGE_ACK_CURRENT_POS, MESSAGE_ACK_REMAINING_BUF, ":ack%s\r\n", message->number_str);
}
else {
}
return current_pos;
}