HAM-64 address format implementation
This commit is contained in:
parent
b6f040ebc7
commit
0fc63f5f69
180
impl/src/layer2/ham64.c
Normal file
180
impl/src/layer2/ham64.c
Normal 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
59
impl/src/layer2/ham64.h
Normal 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
|
|
@ -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
50
impl/test/test_ham64.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue