WIP: Layer 2-Implementierung #6

Draft
thomas wants to merge 8 commits from layer2_dev into main
6 changed files with 429 additions and 0 deletions

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

@ -0,0 +1,182 @@
#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, sizeof(ham64->addr));
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++) {
rudi_s marked this conversation as resolved
Review

Off-by-one overflow (found by fsanitize) if ham64->length == 4 because sprintf NUL-terminates its output but there's not enough space for - and NUL. Possible fix:

 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]);
+               const char *fmt = i == 3 ? "%04X" : "%04X-";
+               sprintf(out + 5 * i, fmt, ham64->addr[i]);
        }

        out[5*ham64->length - 1] = '\0';

Off-by-one overflow (found by fsanitize) if `ham64->length == 4` because `sprintf` NUL-terminates its output but there's not enough space for `-` and NUL. Possible fix: ``` 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]); + const char *fmt = i == 3 ? "%04X" : "%04X-"; + sprintf(out + 5 * i, fmt, ham64->addr[i]); } out[5*ham64->length - 1] = '\0'; ```
const char *fmt = (i == 3) ? "%04X" : "%04X-";
sprintf(out + 5 * i, fmt, 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

@ -0,0 +1,64 @@
#include <assert.h>
#include <string.h>
#include "packet_structs.h"
size_t layer2_encode_packet_header(const layer2_packet_header_t *header, uint8_t *encoded)
{
assert(header->src_addr.length <= 4 && header->src_addr.length != 0);
assert(header->dst_addr.length <= 4 && header->dst_addr.length != 0);
assert(header->tx_seq_nr < 16);
assert(header->rx_seq_nr < 16);
size_t encoded_size = 2;
encoded[0] =
((uint8_t)header->msg_type & 0x7) << 5
| ((uint8_t)header->tx_request & 0x1) << 4
| ((header->src_addr.length-1) & 0x3) << 2
| ((header->dst_addr.length-1) & 0x3) << 0;
encoded[1] =
(header->tx_seq_nr & 0xF) << 4
| (header->rx_seq_nr & 0xF) << 0;
memcpy(encoded + encoded_size, header->src_addr.addr, 2 * header->src_addr.length);
encoded_size += 2 * header->src_addr.length;
memcpy(encoded + encoded_size, header->dst_addr.addr, 2 * header->dst_addr.length);
encoded_size += 2 * header->dst_addr.length;
return encoded_size;
}
bool layer2_decode_packet_header(const uint8_t *encoded, size_t encoded_len, layer2_packet_header_t *header)
{
// check if there are enough bytes for the minimum header size:
// - 1 byte packet info
// - 1 byte sequence numbers
// - 2 bytes source address
// - 2 bytes destination address
if(encoded_len < 6) {
return false;
}
header->msg_type = (encoded[0] >> 5) & 0x7;
header->tx_request = (encoded[0] & 0x10) != 0;
header->src_addr.length = (encoded[0] >> 2) & 0x3 + 1;
header->dst_addr.length = (encoded[0] >> 0) & 0x3 + 1;
// check for the actually needed size
if(encoded_len < (2 + 2*header->src_addr.length + 2*header->dst_addr.length)) {
return false;
}
header->tx_seq_nr = (encoded[1] >> 4) & 0xF;
header->tx_seq_nr = (encoded[1] >> 0) & 0xF;
memcpy(header->src_addr.addr, encoded + 2, 2 * header->src_addr.length);
memcpy(header->dst_addr.addr, encoded + 2 + 2 * header->src_addr.length, 2 * header->dst_addr.length);
return true;
}

View File

@ -0,0 +1,59 @@
#ifndef PACKET_STRUCTS_H
#define PACKET_STRUCTS_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "layer2/ham64.h"
/* Common Link-layer Header */
typedef enum {
L2_MSG_TYPE_DATA = 0x0,
L2_MSG_TYPE_CONN_MGMT = 0x1,
L2_MSG_TYPE_CONNECTIONLESS = 0x4
} layer2_message_type_t;
typedef struct layer2_packet_header_s {
layer2_message_type_t msg_type; //!< message type
bool tx_request; //!< transmission request (marks the end of the burst)
uint8_t tx_seq_nr; //!< sequence number (0 to 15) of this packet
uint8_t rx_seq_nr; //!< sequence number (0 to 15) expected next from he other side
ham64_t src_addr; //!< source HAM-64 address
ham64_t dst_addr; //!< destination HAM-64 address
} layer2_packet_header_t;
/*!\brief Encode a layer2 packet header to the transmitted form.
* \param header The header structure to encode.
* \param encoded A byte array where the encoded header will be stored. Must have space for 18 bytes.
* \returns The number of encoded bytes. Zero if an error occurred.
*/
size_t layer2_encode_packet_header(const layer2_packet_header_t *header, uint8_t *encoded);
/*!\brief Decode a layer2 packet header from the received form.
* \param encoded A byte array containing the received header.
* \param encoded_len Length (in bytes) of the given encoded data.
* \param header The header structure where the decoded information is stored.
* \returns Whether the header was decoded successfully.
*/
bool layer2_decode_packet_header(const uint8_t *encoded, size_t encoded_len, layer2_packet_header_t *header);
/* Data Packet Structs */
typedef enum {
L2_PAYLOAD_TYPE_IPV4 = 0x00,
L2_PAYLOAD_TYPE_IPV6 = 0x01
} layer2_payload_type_t;
typedef struct layer2_data_header_s {
layer2_payload_type_t payload_type; //!< Type of the contained layer 3 packet
} layer2_data_header_t;
/* Connection Management Structs */
// TODO
#endif // PACKET_STRUCTS_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
)

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

@ -0,0 +1,52 @@
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <layer2/ham64.h>
bool test_decode(const char *ref, const char *exp_fmt, const char *exp_type, const ham64_t *ham64)
{
char decoded[13] = {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);
rudi_s marked this conversation as resolved
Review

These printed results should be verified by the test as well.

These printed results should be verified by the test as well.
return strcmp(ref, decoded) == 0
&& strcmp(exp_fmt, ham64_format_buf) == 0
&& strcmp(exp_type, typestr) == 0;
}
bool test_call(const char *call, const char *exp_fmt)
{
ham64_t ham64;
ham64_encode(call, &ham64);
return test_decode(call, exp_fmt, "CALLSIGN", &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};
assert(test_call(call1, "8B05-0E89-7118-AEC8"));
assert(test_call(call2, "1B00-7EC4-EA60"));
assert(test_call(call3, "1EAB"));
assert(test_decode("", "0000", "EMPTY", &empty));
assert(test_decode("", "0001", "TMP_SHORT", &short_1));
assert(test_decode("", "063F", "TMP_SHORT", &short_last));
assert(test_decode("", "FFFF", "BROADCAST", &broadcast));
}