From 0fc63f5f6967c352db5959a2af37c4506c0519de Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Mon, 1 Jul 2024 20:58:20 +0200 Subject: [PATCH] HAM-64 address format implementation --- impl/src/layer2/ham64.c | 180 +++++++++++++++++++++++++++++++++++++++ impl/src/layer2/ham64.h | 59 +++++++++++++ impl/test/CMakeLists.txt | 13 +++ impl/test/test_ham64.c | 50 +++++++++++ 4 files changed, 302 insertions(+) create mode 100644 impl/src/layer2/ham64.c create mode 100644 impl/src/layer2/ham64.h create mode 100644 impl/test/test_ham64.c diff --git a/impl/src/layer2/ham64.c b/impl/src/layer2/ham64.c new file mode 100644 index 0000000..5895adc --- /dev/null +++ b/impl/src/layer2/ham64.c @@ -0,0 +1,180 @@ +#include +#include + +#include "ham64.h" + +static uint16_t char_to_ham64(char c) +{ + if(c == 0) { + return 0; + } else if(c >= 'A' && c <= 'Z') { + return 1 + c - 'A'; + } else if(c >= 'a' && c <= 'z') { + return 1 + c - 'a'; + } else if(c >= '0' && c <= '9') { + return 27 + c - '0'; + } else if(c == '/') { + return 37; + } else if(c == '-') { + return 38; + } else if(c == '^') { + return 39; + } else { + return 0xFFFF; // undefined character + } +} + + +static char ham64_to_char(uint16_t v) { + if(v == 0) { + return 0; + } else if(v >= 1 && v <= 26) { + return 'A' + v - 1; + } else if(v >= 27 && v <= 36) { + return '0' + v - 27; + } else if(v == 37) { + return '/'; + } else if(v == 38) { + return '-'; + } else if(v == 39) { + return '^'; + } else { + return '?'; + } +} + + +size_t ham64_encode(const char *call, ham64_t *ham64) +{ + size_t chars_in_tmp = 0; + size_t ham64_len = 0; + + uint16_t *tmp = ham64->addr; + + memset(ham64->addr, 0, 4*sizeof(uint16_t)); + + while(*call) { + uint16_t encoded = char_to_ham64(*call); + if(encoded == 0xFFFF) { + // drop non-encodable characters + continue; + } + + *tmp *= 40; + *tmp += encoded; + chars_in_tmp++; + + if(chars_in_tmp == 3) { + ham64_len++; + tmp++; + + chars_in_tmp = 0; + } + + call++; + } + + // finalize a partially filled word + if(chars_in_tmp != 0) { + while(chars_in_tmp < 3) { + *tmp *= 40; + chars_in_tmp++; + } + + ham64_len++; + } + + ham64->length = ham64_len; + return ham64_len; +} + + +size_t ham64_decode_callsign(const ham64_t *ham64, char *call) +{ + if(ham64->length > 4 || ham64->length == 0) { + // unsupported length + return 0; + } + + if(ham64->addr[0] < 0x0640 || ham64->addr[0] >= 0xFA00) { + // special address, must not be decoded here + return 0; + } + + size_t call_len = 0; + + for(size_t i = 0; i < ham64->length; i++) { + // big endian decoding + uint16_t chunk = ham64->addr[i]; + + if(chunk < 0x0640 || chunk >= 0xFA00) { + // invalid word + return 0; + } + + for(uint16_t div = 1600; div != 0; div /= 40) { + *call = ham64_to_char((chunk / div) % 40); + if(!*call) { + return call_len; + } + + call_len++; + call++; + } + } + + *call = '\0'; + return call_len; +} + + +const char *ham64_addr_type_to_string(ham64_addr_type_t addr_type) +{ + static const char *type_strings[HAM64_NUM_ADDR_TYPES] = { + "EMPTY", + "TMP_SHORT", + "CALLSIGN", + "BROADCAST", + "IPV6_MULTICAST", + "IPV4_MULTICAST", + "RESERVED"}; + + if(addr_type >= HAM64_NUM_ADDR_TYPES) { + return NULL; + } + + return type_strings[addr_type]; +} + + +ham64_addr_type_t ham64_get_addr_type(const ham64_t *ham64) +{ + uint16_t first_word = ham64->addr[0]; + uint8_t first_byte = first_word >> 8; + + if(first_word >= 0x0640 && first_word < 0xFA00) { + return HAM64_ADDR_TYPE_CALLSIGN; + } else if(first_word == 0x0000) { + return HAM64_ADDR_TYPE_EMPTY; + } else if(first_word < 0x0640) { + return HAM64_ADDR_TYPE_TMP_SHORT; + } else if(first_word == 0xFFFF) { + return HAM64_ADDR_TYPE_BROADCAST; + } else if(first_byte == 0xFA) { + return HAM64_ADDR_TYPE_IPV6_MULTICAST; + } else if(first_byte == 0xFB) { + return HAM64_ADDR_TYPE_IPV4_MULTICAST; + } else { + return HAM64_ADDR_TYPE_RESERVED; + } +} + + +void ham64_format(const ham64_t *ham64, char *out) +{ + for(uint8_t i = 0; i < ham64->length; i++) { + sprintf(out + 5 * i, "%04X-", ham64->addr[i]); + } + + out[5*ham64->length - 1] = '\0'; +} diff --git a/impl/src/layer2/ham64.h b/impl/src/layer2/ham64.h new file mode 100644 index 0000000..4670d98 --- /dev/null +++ b/impl/src/layer2/ham64.h @@ -0,0 +1,59 @@ +#ifndef HAM64_H +#define HAM64_H + +#include +#include + +// buffer size required for the string representation of a maximum-length HAM64 +// address, including terminating zero. +#define HAM64_FMT_MAX_LEN (4*4 + 3 + 1) + +typedef enum { + HAM64_ADDR_TYPE_EMPTY, + HAM64_ADDR_TYPE_TMP_SHORT, + HAM64_ADDR_TYPE_CALLSIGN, + HAM64_ADDR_TYPE_BROADCAST, + HAM64_ADDR_TYPE_IPV6_MULTICAST, + HAM64_ADDR_TYPE_IPV4_MULTICAST, + HAM64_ADDR_TYPE_RESERVED, + + HAM64_NUM_ADDR_TYPES +} ham64_addr_type_t; + +typedef struct ham64_s { + uint16_t addr[4]; + uint8_t length; +} ham64_t; + +/*!\brief Encode a call sign as HAM64 address. + * \param call The call sign as null-terminated string. + * \param ham64 Pointer to the memory for the output address. + * \returns The length in words of the encoded address. Zero if an error occurred. + */ +size_t ham64_encode(const char *call, ham64_t *ham64); + +/*!\brief Decode a ham64 address to a readable call sign. + * \param ham64 Pointer to the address to be decoded. + * \param call Pointer to the output buffer (at least 13 characters in size). + * \returns The length of the output string or zero in case of errors. + */ +size_t ham64_decode_callsign(const ham64_t *ham64, char *call); + +/*!\brief Determine the type of a HAM64 address. + * \param ham64 Pointer to the address to identify. + * \returns The type of the address. + */ +ham64_addr_type_t ham64_get_addr_type(const ham64_t *ham64); + +/*!\brief Get a string representation of the given address type. + */ +const char *ham64_addr_type_to_string(ham64_addr_type_t addr_type); + +/*!\brief Format a ham64 address into a string for printing. + * \param ham64 Pointer to the address to be formatted. + * \param out Pointer to the buffer where the string representation will + * be stored. Must be at least \ref HAM64_FMT_MAX_LEN bytes long. + */ +void ham64_format(const ham64_t *ham64, char *out); + +#endif // HAM64_H diff --git a/impl/test/CMakeLists.txt b/impl/test/CMakeLists.txt index 47e9de8..7e6a7a4 100644 --- a/impl/test/CMakeLists.txt +++ b/impl/test/CMakeLists.txt @@ -132,3 +132,16 @@ target_link_libraries( m liquid ) + +#------------------------------------ + +add_executable( + test_ham64 + ../src/layer2/ham64.c + ../src/layer2/ham64.h + test_ham64.c +) + +target_link_libraries( + test_ham64 +) diff --git a/impl/test/test_ham64.c b/impl/test/test_ham64.c new file mode 100644 index 0000000..96945d7 --- /dev/null +++ b/impl/test/test_ham64.c @@ -0,0 +1,50 @@ +#include +#include +#include + +#include + +bool test_decode(const char *ref, const ham64_t *ham64) +{ + char decoded[13]; + decoded[12] = 0; + + char ham64_format_buf[HAM64_FMT_MAX_LEN]; + + size_t decoded_len = ham64_decode_callsign(ham64, decoded); + + ham64_format(ham64, ham64_format_buf); + const char *typestr = ham64_addr_type_to_string(ham64_get_addr_type(ham64)); + printf("»%s« → [%u] %s (%s) → [%zd] »%s«\n", ref, ham64->length, ham64_format_buf, typestr, decoded_len, decoded); + + return strcmp(ref, decoded) == 0; +} + +bool test_call(const char *call) +{ + ham64_t ham64; + + ham64_encode(call, &ham64); + return test_decode(call, &ham64); +} + +int main(void) +{ + char *call1 = "VI2BMARC50-X"; + char *call2 = "DL5TKL/T"; + char *call3 = "D9K"; + + ham64_t empty = {{0x0000, 0x0, 0x0, 0x0}, 1}; + ham64_t short_1 = {{0x0001, 0x0, 0x0, 0x0}, 1}; + ham64_t short_last = {{0x063F, 0x0, 0x0, 0x0}, 1}; + ham64_t broadcast = {{0xFFFF, 0x0, 0x0, 0x0}, 1}; + + test_call(call1); + test_call(call2); + test_call(call3); + + test_decode("", &empty); + test_decode("", &short_1); + test_decode("", &short_last); + test_decode("", &broadcast); +}