From 6cbce8e0eb1b62a16335d83a38465f6ec82f465e Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Sun, 13 Jun 2021 15:48:35 -0700 Subject: [PATCH 01/14] Add WSPR Type 2 and 3 support --- src/JTEncode.cpp | 303 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 252 insertions(+), 51 deletions(-) diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index 78d94a0..2156c28 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -1,7 +1,7 @@ /* * JTEncode.cpp - JT65/JT9/WSPR/FSQ encoder library for Arduino * - * Copyright (C) 2015-2018 Jason Milldrum + * Copyright (C) 2015-2021 Jason Milldrum * * Based on the algorithms presented in the WSJT software suite. * Thanks to Andy Talbot G4JNT for the whitepaper on the WSPR encoding @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -188,16 +189,17 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) /* * wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) * - * Takes an arbitrary message of up to 13 allowable characters and returns + * Takes a callsign, grid locator, and power level and returns a WSPR symbol + * table for a Type 1, 2, or 3 message. * - * call - Callsign (6 characters maximum). - * loc - Maidenhead grid locator (4 characters maximum). + * call - Callsign (11 characters maximum). + * loc - Maidenhead grid locator (6 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method. * */ -void JTEncode::wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) +void JTEncode::wspr_encode(const char * call, const char * loc, const int8_t dbm, uint8_t * symbols) { char call_[7]; char loc_[5]; @@ -533,7 +535,7 @@ uint8_t JTEncode::ft_code(char c) uint8_t JTEncode::wspr_code(char c) { // Validate the input then return the proper integer code. - // Return 255 as an error code if the char is not allowed. + // Change character to a space if the char is not allowed. if(isdigit(c)) { @@ -549,7 +551,7 @@ uint8_t JTEncode::wspr_code(char c) } else { - return 255; + return 36; } } @@ -612,53 +614,64 @@ void JTEncode::ft_message_prep(char * message) strcpy(message, temp_msg); } -void JTEncode::wspr_message_prep(char * call, char * loc, uint8_t dbm) +void JTEncode::wspr_message_prep(char * call, char * loc, int8_t dbm) { // Callsign validation and padding // ------------------------------- - - // If only the 2nd character is a digit, then pad with a space. - // If this happens, then the callsign will be truncated if it is - // longer than 5 characters. - if((call[1] >= '0' && call[1] <= '9') && (call[2] < '0' || call[2] > '9')) + + // Ensure that the only allowed characters are digits, uppercase letters, slash, and angle brackets + uint8_t i; + for(i = 0; i < 12; i++) { - memmove(call + 1, call, 5); - call[0] = ' '; + if(callsign[i] != '/' && callsign[i] != '<' && callsign[i] != '>') + { + callsign[i] = toupper(callsign[i]); + if(!(isdigit(callsign[i]) || isupper(callsign[i]))) + { + callsign[i] = ' '; + } + } } - // Now the 3rd charcter in the callsign must be a digit - if(call[2] < '0' || call[2] > '9') - { - // TODO: need a better way to handle this - call[2] = '0'; - } + strncpy(callsign, call, 12); - // Ensure that the only allowed characters are digits and - // uppercase letters - uint8_t i; - for(i = 0; i < 6; i++) + // Grid locator validation + if(strlen(loc) == 4 || strlen(loc) == 6) { - call[i] = toupper(call[i]); - if(!(isdigit(call[i]) || isupper(call[i]))) + for(i = 0; i <= 1; i++) { - call[i] = ' '; + loc[i] = toupper(loc[i]); + if((loc[i] < 'A' || loc[i] > 'R')) + { + strncpy(loc, "AA00AA", 7); + } + } + for(i = 2; i <= 3; i++) + { + if(!(isdigit(loc[i]))) + { + strncpy(loc, "AA00AA", 7); + } } } + else + { + strncpy(loc, "AA00AA", 7); + } - memcpy(callsign, call, 6); - - // Grid locator validation - for(i = 0; i < 4; i++) + if(strlen(loc) == 6) { - loc[i] = toupper(loc[i]); - if(!(isdigit(loc[i]) || (loc[i] >= 'A' && loc[i] <= 'R'))) + for(i = 4; i <= 5; i++) { - memcpy(loc, "AA00", 5); - //loc = "AA00"; + loc[i] = toupper(loc[i]); + if((loc[i] < 'A' || loc[i] > 'X')) + { + strncpy(loc, "AA00AA", 7); + } } } - memcpy(locator, loc, 4); + strncpy(locator, loc, 7); // Power level validation // Only certain increments are allowed @@ -666,10 +679,12 @@ void JTEncode::wspr_message_prep(char * call, char * loc, uint8_t dbm) { dbm = 60; } - const uint8_t valid_dbm[19] = - {0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40, + const uint8_t VALID_DBM_SIZE = 28; + const int8_t valid_dbm[VALID_DBM_SIZE] = + {-30, -27, -23, -20, -17, -13, -10, -7, -3, + 0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40, 43, 47, 50, 53, 57, 60}; - for(i = 0; i < 19; i++) + for(i = 0; i < VALID_DBM_SIZE; i++) { if(dbm == valid_dbm[i]) { @@ -677,7 +692,7 @@ void JTEncode::wspr_message_prep(char * call, char * loc, uint8_t dbm) } } // If we got this far, we have an invalid power level, so we'll round down - for(i = 1; i < 19; i++) + for(i = 1; i < VALID_DBM_SIZE; i++) { if(dbm < valid_dbm[i] && dbm >= valid_dbm[i - 1]) { @@ -794,18 +809,186 @@ void JTEncode::wspr_bit_packing(uint8_t * c) { uint32_t n, m; - n = wspr_code(callsign[0]); - n = n * 36 + wspr_code(callsign[1]); - n = n * 10 + wspr_code(callsign[2]); - n = n * 27 + (wspr_code(callsign[3]) - 10); - n = n * 27 + (wspr_code(callsign[4]) - 10); - n = n * 27 + (wspr_code(callsign[5]) - 10); + // Determine if type 1, 2 or 3 message + char* slash_avail = strchr(callsign, (int)'/'); + if(callsign[0] == '<') + { + // Type 3 message + char base_call[7]; + memset(base_call, 0, 7); + uint32_t init_val = 146; + char* bracket_avail = strchr(callsign, (int)'>'); + int call_len = bracket_avail - callsign - 1; + strncpy(base_call, callsign + 1, call_len); + uint32_t hash = nhash_(base_call, &call_len, &init_val); + uint16_t call_hash = hash & 32767; + + // Convert 6 char grid square to "callsign" format for transmission + // by putting the first character at the end + char temp_loc = locator[0]; + locator[0] = locator[1]; + locator[1] = locator[2]; + locator[2] = locator[3]; + locator[3] = locator[4]; + locator[4] = locator[5]; + locator[5] = temp_loc; + + n = wspr_code(locator[0]); + n = n * 36 + wspr_code(locator[1]); + n = n * 10 + wspr_code(locator[2]); + n = n * 27 + (wspr_code(locator[3]) - 10); + n = n * 27 + (wspr_code(locator[4]) - 10); + n = n * 27 + (wspr_code(locator[5]) - 10); + + m = (call_hash * 128) - (power + 1) + 64; + } + else if(slash_avail == (void *)0) + { + // Type 1 message + pad_callsign(callsign); + n = wspr_code(callsign[0]); + n = n * 36 + wspr_code(callsign[1]); + n = n * 10 + wspr_code(callsign[2]); + n = n * 27 + (wspr_code(callsign[3]) - 10); + n = n * 27 + (wspr_code(callsign[4]) - 10); + n = n * 27 + (wspr_code(callsign[5]) - 10); + + m = ((179 - 10 * (locator[0] - 'A') - (locator[2] - '0')) * 180) + + (10 * (locator[1] - 'A')) + (locator[3] - '0'); + m = (m * 128) + power + 64; + } + else if(slash_avail) + { + // Type 2 message + int slash_pos = slash_avail - callsign; + uint8_t i; - m = ((179 - 10 * (locator[0] - 'A') - (locator[2] - '0')) * 180) + - (10 * (locator[1] - 'A')) + (locator[3] - '0'); - m = (m * 128) + power + 64; + // Determine prefix or suffix + if(callsign[slash_pos + 2] == ' ' || callsign[slash_pos + 2] == 0) + { + // Single character suffix + char base_call[7]; + memset(base_call, 0, 7); + strncpy(base_call, callsign, slash_pos); + for(i = 0; i < 6; i++) + { + base_call[i] = toupper(base_call[i]); + if(!(isdigit(base_call[i]) || isupper(base_call[i]))) + { + base_call[i] = ' '; + } + } + pad_callsign(base_call); + + n = wspr_code(base_call[0]); + n = n * 36 + wspr_code(base_call[1]); + n = n * 10 + wspr_code(base_call[2]); + n = n * 27 + (wspr_code(base_call[3]) - 10); + n = n * 27 + (wspr_code(base_call[4]) - 10); + n = n * 27 + (wspr_code(base_call[5]) - 10); - // Callsign is 28 bits, locator/power is 22 bits. + char x = callsign[slash_pos + 1]; + if(x >= 48 && x <= 57) + { + x -= 48; + } + else if(x >= 65 && x <= 90) + { + x -= 55; + } + else + { + x = 38; + } + + m = 60000 - 32768 + x; + + m = (m * 128) + power + 2 + 64; + + } + else if(callsign[slash_pos + 3] == ' ' || callsign[slash_pos + 3] == 0) + { + // Two-digit numerical suffix + char base_call[7]; + memset(base_call, 0, 7); + strncpy(base_call, callsign, slash_pos); + for(i = 0; i < 6; i++) + { + base_call[i] = toupper(base_call[i]); + if(!(isdigit(base_call[i]) || isupper(base_call[i]))) + { + base_call[i] = ' '; + } + } + pad_callsign(base_call); + + n = wspr_code(base_call[0]); + n = n * 36 + wspr_code(base_call[1]); + n = n * 10 + wspr_code(base_call[2]); + n = n * 27 + (wspr_code(base_call[3]) - 10); + n = n * 27 + (wspr_code(base_call[4]) - 10); + n = n * 27 + (wspr_code(base_call[5]) - 10); + + // TODO: needs validation of digit + m = 10 * (callsign[slash_pos + 1] - 48) + callsign[slash_pos + 2] - 48; + m = 60000 + 26 + m; + m = (m * 128) + power + 2 + 64; + } + else + { + // Prefix + char prefix[4]; + char base_call[7]; + memset(prefix, 0, 4); + memset(base_call, 0, 7); + strncpy(prefix, callsign, slash_pos); + strncpy(base_call, callsign + slash_pos + 1, 7); + + if(prefix[2] == ' ' || prefix[2] == 0) + { + // Right align prefix + prefix[3] = 0; + prefix[2] = prefix[1]; + prefix[1] = prefix[0]; + prefix[0] = ' '; + } + + for(uint8_t i = 0; i < 6; i++) + { + base_call[i] = toupper(base_call[i]); + if(!(isdigit(base_call[i]) || isupper(base_call[i]))) + { + base_call[i] = ' '; + } + } + pad_callsign(base_call); + + n = wspr_code(base_call[0]); + n = n * 36 + wspr_code(base_call[1]); + n = n * 10 + wspr_code(base_call[2]); + n = n * 27 + (wspr_code(base_call[3]) - 10); + n = n * 27 + (wspr_code(base_call[4]) - 10); + n = n * 27 + (wspr_code(base_call[5]) - 10); + + m = 0; + for(uint8_t i = 0; i < 3; ++i) + { + m = 37 * m + wspr_code(prefix[i]); + } + + if(m >= 32768) + { + m -= 32768; + m = (m * 128) + power + 2 + 64; + } + else + { + m = (m * 128) + power + 1 + 64; + } + } + } + + // Callsign is 28 bits, locator/power is 22 bits. // A little less work to start with the least-significant bits c[3] = (uint8_t)((n & 0x0f) << 4); n = n >> 4; @@ -1387,3 +1570,21 @@ uint8_t JTEncode::crc8(const char * text) return crc; } + +void JTEncode::pad_callsign(char * call) +{ + // If only the 2nd character is a digit, then pad with a space. + // If this happens, then the callsign will be truncated if it is + // longer than 5 characters. + if(isdigit(call[1]) && isupper(call[2])) + { + memmove(call + 1, call, 5); + call[0] = ' '; + } + + // Now the 3rd charcter in the callsign must be a digit + // if(call[2] < '0' || call[2] > '9') + // { + // // return 1; + // } +} \ No newline at end of file From 9fedd042d4d523fca5cbe96e2f97541a06c0475a Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Sun, 13 Jun 2021 15:50:37 -0700 Subject: [PATCH 02/14] Add WSPR Type 2 and 3 message support --- README.md | 27 +++- library.properties | 2 +- src/JTEncode.h | 14 +- src/nhash.c | 384 +++++++++++++++++++++++++++++++++++++++++++++ src/nhash.h | 14 ++ 5 files changed, 430 insertions(+), 11 deletions(-) create mode 100644 src/nhash.c create mode 100644 src/nhash.h diff --git a/README.md b/README.md index 5530deb..30bcb20 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ JT65/JT9/JT4/FT8/WSPR/FSQ Encoder Library for Arduino ===================================================== -This library very simply generates a set of channel symbols for JT65, JT9, JT4, FT8, or WSPR based on the user providing a properly formatted Type 6 message for JT65, JT9, or JT4 (which is 13 valid characters), Type 0.0 or 0.5 message for FT8 (v2.0.0 protocol) or a callsign, Maidenhead grid locator, and power output for WSPR. It will also generate an arbitrary FSQ message of up to 200 characters in both directed and non-directed format. When paired with a synthesizer that can output frequencies in fine, phase-continuous tuning steps (such as the Si5351), then a beacon or telemetry transmitter can be created which can change the transmitted characters as needed from the Arduino. +This library very simply generates a set of channel symbols for JT65, JT9, JT4, FT8, or WSPR based on the user providing a properly formatted Type 6 message for JT65, JT9, or JT4 (which is 13 valid characters), Type 0.0 or 0.5 message for FT8 (v2.0.0 protocol) or a Type 1, Type 2, or Type 3 message for WSPR. It will also generate an arbitrary FSQ message of up to 200 characters in both directed and non-directed format. When paired with a synthesizer that can output frequencies in fine, phase-continuous tuning steps (such as the Si5351), then a beacon or telemetry transmitter can be created which can change the transmitted characters as needed from the Arduino. Please feel free to use the issues feature of GitHub if you run into problems or have suggestions for important features to implement. @@ -23,6 +23,20 @@ RAM Usage --------- Most of the encoding functions need to manipulate multiple arrays of symbols in RAM at the same time, and therefore are quite RAM intensive. Care has been taken to put as much data into program memory as is possible, but the encoding functions still can cause problems with the low RAM microcontrollers such as the ATmegaxx8 series. If you are using these, then please be sure to call them only once when a transmit buffer needs to be created or changed, and call them separately of other subroutine calls. When using other microcontrollers that have more RAM, such as most of the ARM ICs, this won't be as much of a problem. If you see unusual freezes, that almost certainly indicates a RAM shortage. +WSPR Messages +------------- +JTEncode includes support for all three WSPR message types. A brief listing of the three types is given below: + +| Message Type | Fields | Example | +|--------------|--------|---------| +| Type 1 | Callsign, Grid (4 digit), Power | NT7S CN85 30 | +| Type 2 | Callsign with prefix or suffix, Power | NT7S/P 30 | +| Type 3 | Callsign Hash, Grid (6 digit), Power | \ CN85NM 30 | + +Most WSPR messages are type 1, however sometimes type 2 and 3 messages are needed. Type 2 messages allow you to send a callsign with a prefix of up to three characters, a suffix of a single character, or a suffix consisting of two numerical digits. Type 3 messages are typically used in conjunction with type 2 messages since type 2 messages don't include a grid locator. The type 3 message sends a 15-bit hash of the included callsign, along with a 6 digit grid locator and the power. + +Type 2 messages can be sent in JTEncode simply by including the slashed prefix or suffix in the callsign field. A type 3 message can be sent by enclosing a callsign with angle brackets (as seen in the example above). + Example ------- There is a simple example that is placed in your examples menu under JTEncode. Open this to see how to incorporate this library with your code. The example provided with with the library is meant to be used in conjunction with the [Etherkit Si5351A Breakout Board](https://www.etherkit.com/rf-modules/si5351a-breakout-board.html), although it could be modified to use with other synthesizers which meet the technical requirements of the JT65/JT9/JT4/WSPR/FSQ modes. @@ -185,10 +199,11 @@ Public Methods /* * wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) * - * Takes an arbitrary message of up to 13 allowable characters and returns + * Takes a callsign, grid locator, and power level and returns a WSPR symbol + * table for a Type 1, 2, or 3 message. * - * call - Callsign (6 characters maximum). - * loc - Maidenhead grid locator (4 characters maximum). + * call - Callsign (11 characters maximum). + * loc - Maidenhead grid locator (6 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method. @@ -262,6 +277,10 @@ Also, a big thank you to Murray Greenman, ZL1BPU for working allowing me to pick Changelog --------- +* v1.3.0 + + * WSPR Type 2 and Type 3 message capability added + * v1.2.1 * Fix keywords.txt diff --git a/library.properties b/library.properties index 17645e2..d38f6c2 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Etherkit JTEncode -version=1.2.1 +version=1.3.0 author=Jason Milldrum maintainer=Jason Milldrum sentence=Generate JT65, JT9, JT4, FT8, WSPR, and FSQ symbols on your Arduino. diff --git a/src/JTEncode.h b/src/JTEncode.h index 132d6fa..fc8917e 100644 --- a/src/JTEncode.h +++ b/src/JTEncode.h @@ -1,7 +1,7 @@ /* * JTEncode.h - JT65/JT9/WSPR/FSQ encoder library for Arduino * - * Copyright (C) 2015-2018 Jason Milldrum + * Copyright (C) 2015-2021 Jason Milldrum * * Based on the algorithms presented in the WSJT software suite. * Thanks to Andy Talbot G4JNT for the whitepaper on the WSPR encoding @@ -26,6 +26,7 @@ #include "int.h" #include "rs_common.h" +#include "nhash.h" #include "Arduino.h" @@ -220,7 +221,7 @@ class JTEncode void jt65_encode(const char *, uint8_t *); void jt9_encode(const char *, uint8_t *); void jt4_encode(const char *, uint8_t *); - void wspr_encode(const char *, const char *, const uint8_t, uint8_t *); + void wspr_encode(const char *, const char *, const int8_t, uint8_t *); void fsq_encode(const char *, const char *, uint8_t *); void fsq_dir_encode(const char *, const char *, const char, const char *, uint8_t *); void ft8_encode(const char *, uint8_t *); @@ -232,7 +233,7 @@ class JTEncode int8_t hex2int(char); void jt_message_prep(char *); void ft_message_prep(char *); - void wspr_message_prep(char *, char *, uint8_t); + void wspr_message_prep(char *, char *, int8_t); void jt65_bit_packing(char *, uint8_t *); void jt9_bit_packing(char *, uint8_t *); void wspr_bit_packing(uint8_t *); @@ -254,10 +255,11 @@ class JTEncode void free_rs_int(void *); void * init_rs_int(int, int, int, int, int, int); uint8_t crc8(const char *); + void pad_callsign(char *); void * rs_inst; - char callsign[7]; - char locator[5]; - uint8_t power; + char callsign[12]; + char locator[7]; + int8_t power; }; #endif diff --git a/src/nhash.c b/src/nhash.c new file mode 100644 index 0000000..8aabb4f --- /dev/null +++ b/src/nhash.c @@ -0,0 +1,384 @@ +/* + *------------------------------------------------------------------------------- + * + * This file is part of the WSPR application, Weak Signal Propagation Reporter + * + * File Name: nhash.c + * Description: Functions to produce 32-bit hashes for hash table lookup + * + * Copyright (C) 2008-2014 Joseph Taylor, K1JT + * License: GPL-3 + * + * This program 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. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Files: lookup3.c + * Copyright: Copyright (C) 2006 Bob Jenkins + * License: public-domain + * You may use this code any way you wish, private, educational, or commercial. + * It's free. + * + *------------------------------------------------------------------------------- +*/ + +/* +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +*/ + +#define SELF_TEST 1 + +#include /* defines printf for tests */ +#include /* defines time_t for timings in the test */ +#ifdef Win32 +#include "win_stdint.h" /* defines uint32_t etc */ +#else +#include /* defines uint32_t etc */ +#endif +//#include /* attempt to define endianness */ +//#ifdef linux +//# include /* attempt to define endianness */ +//#endif + +#define HASH_LITTLE_ENDIAN 1 + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; /* fall through */ + case 11: c+=((uint32_t)k[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k[9])<<8; /* fall through */ + case 9 : c+=k[8]; /* fall through */ + case 8 : b+=((uint32_t)k[7])<<24; /* fall through */ + case 7 : b+=((uint32_t)k[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k[5])<<8; /* fall through */ + case 5 : b+=k[4]; /* fall through */ + case 4 : a+=((uint32_t)k[3])<<24; /* fall through */ + case 3 : a+=((uint32_t)k[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k[1])<<8; /* fall through */ + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + +//uint32_t __stdcall NHASH(const void *key, size_t length, uint32_t initval) diff --git a/src/nhash.h b/src/nhash.h new file mode 100644 index 0000000..d0b1aee --- /dev/null +++ b/src/nhash.h @@ -0,0 +1,14 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NHASH_H_ +#define NHASH_H_ + +uint32_t nhash_( const void *, int *, uint32_t *); + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 3fd4a163754acb49c1ef359bb4c01c7292a7fb2b Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 12:10:49 -0700 Subject: [PATCH 03/14] Fix wspr_encode buffer overflow --- src/JTEncode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index 2156c28..6bd78b2 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -201,8 +201,8 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) */ void JTEncode::wspr_encode(const char * call, const char * loc, const int8_t dbm, uint8_t * symbols) { - char call_[7]; - char loc_[5]; + char call_[12]; + char loc_[7]; uint8_t dbm_ = dbm; strcpy(call_, call); strcpy(loc_, loc); From 73e7aab8b2a4bcaa4d2f35585248dc7f6408420a Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 13:40:18 -0700 Subject: [PATCH 04/14] Add arduino-lint CI --- .github/workflows/arduino-lint.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/workflows/arduino-lint.yml diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml new file mode 100644 index 0000000..ec3d0df --- /dev/null +++ b/.github/workflows/arduino-lint.yml @@ -0,0 +1,12 @@ +name: arduino-lint + +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update + compliance: strict \ No newline at end of file From e58654ae5b7a0baa5711bd89596754bf314eff3f Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 13:52:14 -0700 Subject: [PATCH 05/14] tweak arduino-lint.yml --- .github/workflows/arduino-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml index ec3d0df..766af9c 100644 --- a/.github/workflows/arduino-lint.yml +++ b/.github/workflows/arduino-lint.yml @@ -9,4 +9,4 @@ jobs: - uses: arduino/arduino-lint-action@v1 with: library-manager: update - compliance: strict \ No newline at end of file + compliance: specification \ No newline at end of file From 9345bdee4bf412682a4d8a900d08b005a419e35b Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 13:52:29 -0700 Subject: [PATCH 06/14] Tweak arduino-lint.yml --- .github/workflows/arduino-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml index 766af9c..b957a83 100644 --- a/.github/workflows/arduino-lint.yml +++ b/.github/workflows/arduino-lint.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: arduino/arduino-lint-action@v1 + - uses: arduino/arduino-lint-action@v1.0.0 with: library-manager: update - compliance: specification \ No newline at end of file + compliance: specification From 0d1b0152fb78f4611a632ddac8417c56cdd276ba Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 13:58:11 -0700 Subject: [PATCH 07/14] Add lint badge to README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 30bcb20..9192ccd 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,10 @@ Changelog * Initial Release +Arduino Lint Status +=================== +[![YourActionName Actions Status](https://github.com/etherkit/JTEncode/workflows/arduino-lint.yml/badge.svg)](https://github.com/etherkit/JTEncode/actions) + License ------- From 8d297f4a432081dde8e992f32bbf862fea5a5b9c Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 13:59:45 -0700 Subject: [PATCH 08/14] Fix lint badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9192ccd..5f0faad 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ Changelog Arduino Lint Status =================== -[![YourActionName Actions Status](https://github.com/etherkit/JTEncode/workflows/arduino-lint.yml/badge.svg)](https://github.com/etherkit/JTEncode/actions) +[![arduino-lint Actions Status](https://github.com/etherkit/JTEncode/workflows/arduino-lint/badge.svg)](https://github.com/etherkit/JTEncode/actions) License From 39c2484b802258a3d0ef2b2986ce7b9836f88953 Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 14:00:31 -0700 Subject: [PATCH 09/14] Fix lint badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f0faad..e0bbf95 100644 --- a/README.md +++ b/README.md @@ -317,7 +317,7 @@ Changelog * Initial Release Arduino Lint Status -=================== +------------------- [![arduino-lint Actions Status](https://github.com/etherkit/JTEncode/workflows/arduino-lint/badge.svg)](https://github.com/etherkit/JTEncode/actions) From 053b4580d41205c0a1cc219a37676a2782929f3d Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 16:30:51 -0700 Subject: [PATCH 10/14] Fix Type 3 hash error --- src/JTEncode.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index 6bd78b2..9be9922 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -49,6 +49,7 @@ JTEncode::JTEncode(void) { // Initialize the Reed-Solomon encoder rs_inst = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); + memset(callsign, 0, 12); } /* @@ -189,11 +190,10 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) /* * wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) * - * Takes a callsign, grid locator, and power level and returns a WSPR symbol - * table for a Type 1, 2, or 3 message. + * Takes an arbitrary message of up to 13 allowable characters and returns * - * call - Callsign (11 characters maximum). - * loc - Maidenhead grid locator (6 characters maximum). + * call - Callsign (6 characters maximum). + * loc - Maidenhead grid locator (4 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method. @@ -814,14 +814,14 @@ void JTEncode::wspr_bit_packing(uint8_t * c) if(callsign[0] == '<') { // Type 3 message - char base_call[7]; - memset(base_call, 0, 7); + char base_call[12]; + memset(base_call, 0, 12); uint32_t init_val = 146; char* bracket_avail = strchr(callsign, (int)'>'); int call_len = bracket_avail - callsign - 1; strncpy(base_call, callsign + 1, call_len); uint32_t hash = nhash_(base_call, &call_len, &init_val); - uint16_t call_hash = hash & 32767; + hash &= 32767; // Convert 6 char grid square to "callsign" format for transmission // by putting the first character at the end @@ -840,7 +840,7 @@ void JTEncode::wspr_bit_packing(uint8_t * c) n = n * 27 + (wspr_code(locator[4]) - 10); n = n * 27 + (wspr_code(locator[5]) - 10); - m = (call_hash * 128) - (power + 1) + 64; + m = (hash * 128) - (power + 1) + 64; } else if(slash_avail == (void *)0) { @@ -870,7 +870,7 @@ void JTEncode::wspr_bit_packing(uint8_t * c) char base_call[7]; memset(base_call, 0, 7); strncpy(base_call, callsign, slash_pos); - for(i = 0; i < 6; i++) + for(i = 0; i < 7; i++) { base_call[i] = toupper(base_call[i]); if(!(isdigit(base_call[i]) || isupper(base_call[i]))) @@ -904,7 +904,6 @@ void JTEncode::wspr_bit_packing(uint8_t * c) m = 60000 - 32768 + x; m = (m * 128) + power + 2 + 64; - } else if(callsign[slash_pos + 3] == ' ' || callsign[slash_pos + 3] == 0) { From 9b8d6c468bfd1a1b3da2604610ef7c4c34e98082 Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Tue, 29 Jun 2021 16:35:03 -0700 Subject: [PATCH 11/14] Fix wspr_encode documentation error --- src/JTEncode.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index 9be9922..3a9b30f 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -190,10 +190,11 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) /* * wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols) * - * Takes an arbitrary message of up to 13 allowable characters and returns + * Takes a callsign, grid locator, and power level and returns a WSPR symbol + * table for a Type 1, 2, or 3 message. * - * call - Callsign (6 characters maximum). - * loc - Maidenhead grid locator (4 characters maximum). + * call - Callsign (11 characters maximum). + * loc - Maidenhead grid locator (6 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. * Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method. From 920358b223286080cd133aef65fae77aa5139477 Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Wed, 30 Jun 2021 16:49:46 -0700 Subject: [PATCH 12/14] Callsign validation fixes, code cleanup --- src/JTEncode.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index 3a9b30f..52ae11d 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -49,7 +49,7 @@ JTEncode::JTEncode(void) { // Initialize the Reed-Solomon encoder rs_inst = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); - memset(callsign, 0, 12); + // memset(callsign, 0, 13); } /* @@ -193,7 +193,7 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) * Takes a callsign, grid locator, and power level and returns a WSPR symbol * table for a Type 1, 2, or 3 message. * - * call - Callsign (11 characters maximum). + * call - Callsign (12 characters maximum). * loc - Maidenhead grid locator (6 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. @@ -202,7 +202,7 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols) */ void JTEncode::wspr_encode(const char * call, const char * loc, const int8_t dbm, uint8_t * symbols) { - char call_[12]; + char call_[13]; char loc_[7]; uint8_t dbm_ = dbm; strcpy(call_, call); @@ -624,15 +624,16 @@ void JTEncode::wspr_message_prep(char * call, char * loc, int8_t dbm) uint8_t i; for(i = 0; i < 12; i++) { - if(callsign[i] != '/' && callsign[i] != '<' && callsign[i] != '>') + if(call[i] != '/' && call[i] != '<' && call[i] != '>') { - callsign[i] = toupper(callsign[i]); - if(!(isdigit(callsign[i]) || isupper(callsign[i]))) + call[i] = toupper(call[i]); + if(!(isdigit(call[i]) || isupper(call[i]))) { - callsign[i] = ' '; + call[i] = ' '; } } } + call[12] = 0; strncpy(callsign, call, 12); @@ -815,8 +816,8 @@ void JTEncode::wspr_bit_packing(uint8_t * c) if(callsign[0] == '<') { // Type 3 message - char base_call[12]; - memset(base_call, 0, 12); + char base_call[13]; + memset(base_call, 0, 13); uint32_t init_val = 146; char* bracket_avail = strchr(callsign, (int)'>'); int call_len = bracket_avail - callsign - 1; @@ -1575,10 +1576,15 @@ void JTEncode::pad_callsign(char * call) { // If only the 2nd character is a digit, then pad with a space. // If this happens, then the callsign will be truncated if it is - // longer than 5 characters. + // longer than 6 characters. if(isdigit(call[1]) && isupper(call[2])) { - memmove(call + 1, call, 5); + // memmove(call + 1, call, 6); + call[5] = call[4]; + call[4] = call[3]; + call[3] = call[2]; + call[2] = call[1]; + call[1] = call[0]; call[0] = ' '; } From b95463a730ba581d9926668fda9a418ddf16f83a Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Wed, 30 Jun 2021 17:46:49 -0700 Subject: [PATCH 13/14] README fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0bbf95..f9d911b 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ Public Methods * Takes a callsign, grid locator, and power level and returns a WSPR symbol * table for a Type 1, 2, or 3 message. * - * call - Callsign (11 characters maximum). + * call - Callsign (12 characters maximum). * loc - Maidenhead grid locator (6 characters maximum). * dbm - Output power in dBm. * symbols - Array of channel symbols to transmit returned by the method. From 6b9b78d8b1d116134343dc7947e82dec4462ba07 Mon Sep 17 00:00:00 2001 From: Jason Milldrum Date: Wed, 30 Jun 2021 17:54:52 -0700 Subject: [PATCH 14/14] Fix buffer terminator overwrite in jt_message_prep --- src/JTEncode.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/JTEncode.cpp b/src/JTEncode.cpp index 52ae11d..52d6d62 100644 --- a/src/JTEncode.cpp +++ b/src/JTEncode.cpp @@ -578,12 +578,10 @@ void JTEncode::jt_message_prep(char * message) // Pad the message with trailing spaces uint8_t len = strlen(message); - if(len < 13) + + for(i = len; i < 13; i++) { - for(i = len; i <= 13; i++) - { - message[i] = ' '; - } + message[i] = ' '; } // Convert all chars to uppercase