HAM-64 address format implementation

This commit is contained in:
Thomas Kolb 2024-07-01 20:58:20 +02:00
parent 546d86ffb0
commit 4bb4017623
4 changed files with 302 additions and 0 deletions

180
impl/src/layer2/ham64.c Normal file
View File

@ -0,0 +1,180 @@
#include <string.h>
#include <stdio.h>
#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';
}

59
impl/src/layer2/ham64.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef HAM64_H
#define HAM64_H
#include <stddef.h>
#include <stdint.h>
// 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

View File

@ -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
)

50
impl/test/test_ham64.c Normal file
View File

@ -0,0 +1,50 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <layer2/ham64.h>
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);
}