From 32ff91db7e57fc07012c95a0fbffd938cbad8610 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sat, 20 Jul 2024 14:28:14 -0500 Subject: [PATCH 1/5] introduce skeleton for table coding system If the message structure is compatible, the DSDL compiler generates a table which describes its structure. If enabled, an interpreter is then used to follow the table description and encode or decode the message. This occupies substantially fewer bytes per message as the table is much smaller than the C function previously used. This translates to a large reduction in program memory usage, especially if many different message types need to be coded. This commit sets up all the hooks to enable and disable the system and defines the interpreter functions. However, they don't do anything as the system currently doesn't support any types. --- canard.c | 33 +++++++++++++++++++++++++++++++ canard.h | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/canard.c b/canard.c index 5a27dee..045bed1 100644 --- a/canard.c +++ b/canard.c @@ -994,6 +994,39 @@ void canardEncodeScalar(void* destination, copyBitArray(&storage.bytes[0], 0, bit_length, (uint8_t*) destination, bit_offset); } +#if CANARD_ENABLE_TABLE_DECODING +bool canardTableDecodeMessage(const CanardCodingTable* table, + const CanardRxTransfer* transfer, + void* msg) +{ + (void)table; + (void)transfer; + (void)msg; + + return true; +} +#endif + +#if CANARD_ENABLE_TABLE_ENCODING +uint32_t canardTableEncodeMessage(const CanardCodingTable* table, + uint8_t* buffer, + const void* msg +#if CANARD_ENABLE_TAO_OPTION + , bool tao +#endif + ) +{ + (void)table; + (void)buffer; + (void)msg; +#if CANARD_ENABLE_TAO_OPTION + (void)tao; +#endif + + return 0; +} +#endif + void canardReleaseRxTransferPayload(CanardInstance* ins, CanardRxTransfer* transfer) { while (transfer->payload_middle != NULL) diff --git a/canard.h b/canard.h index 78b738a..a2fb5f6 100644 --- a/canard.h +++ b/canard.h @@ -66,6 +66,18 @@ extern "C" { #endif #endif +#ifndef CANARD_ENABLE_TABLE_CODING +#define CANARD_ENABLE_TABLE_CODING 1 +#endif + +#ifndef CANARD_ENABLE_TABLE_ENCODING +#define CANARD_ENABLE_TABLE_ENCODING CANARD_ENABLE_TABLE_CODING +#endif + +#ifndef CANARD_ENABLE_TABLE_DECODING +#define CANARD_ENABLE_TABLE_DECODING CANARD_ENABLE_TABLE_CODING +#endif + /// By default this macro resolves to the standard assert(). The user can redefine this if necessary. #ifndef CANARD_ASSERT #ifdef CANARD_ENABLE_ASSERTS @@ -451,6 +463,24 @@ struct CanardRxTransfer #endif }; +#if CANARD_ENABLE_TABLE_DECODING || CANARD_ENABLE_TABLE_ENCODING +/** + * This structure describes the encoded form of part of a particular message. It + * can be contained in ROM. It should be generated using dronecan_dsdlc. + */ +typedef struct { +} CanardCodingTableEntry; + +/** + * This structure describes the encoded form of a particular message. It can be + * contained in ROM. It should be generated using dronecan_dsdlc. + */ +typedef struct { + size_t entries_max; + CanardCodingTableEntry entries[]; +} CanardCodingTable; +#endif + /** * Initializes a library instance. * Local node ID will be set to zero, i.e. the node will be anonymous. @@ -682,6 +712,35 @@ void canardEncodeScalar(void* destination, ///< Destination buffer where th uint8_t bit_length, ///< Length of the value, in bits; see the table const void* value); ///< Pointer to the value; see the table +#if CANARD_ENABLE_TABLE_DECODING +/** + * This function can be used to extract a message structure from a transfer + * using a coding table that describes the message layout. + * + * Returns true if there was an error during the decoding. + */ +bool canardTableDecodeMessage(const CanardCodingTable* table, ///< Table describing message layout + const CanardRxTransfer* transfer, ///< Transfer containing the message data + void* msg); ///< Pointer to the destination message structure +#endif + +#if CANARD_ENABLE_TABLE_ENCODING +/** + * This function can be used to encode a message structure into a buffer using a + * coding table that describes the message layout. For a message type named + * msg.type, the buffer must be at least MSG_TYPE_MAX_SIZE bytes. + * + * Returns the actual number of bytes stored into the buffer. + */ +uint32_t canardTableEncodeMessage(const CanardCodingTable* table, ///< Table describing message layout + uint8_t* buffer, ///< Pointer to the destination buffer + const void* msg ///< Pointer to message structure to be encoded +#if CANARD_ENABLE_TAO_OPTION + , bool tao ///< True if encoding should use tail array optimization (TAO) +#endif + ); +#endif + /** * This function can be invoked by the application to release pool blocks that are used * to store the payload of the transfer. From b9e5198853e4e97ea0ad417e01672e0d134ee6c1 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sat, 20 Jul 2024 15:15:06 -0500 Subject: [PATCH 2/5] table code types containing primitive and void fields Each table has a four byte header, plus some number of entries (4 bytes). Each field consumes one entry. The maximum encoded message length in bytes, size of the C message struct in chars, and number of table entries all are limited to 65535 for table coding to be possible. Also supports nested compound types containing these fields. --- canard.c | 121 ++++++++++++++++++++++++++++++++++++++++----- canard.h | 33 ++++++++++++- canard_internals.h | 19 +++++++ 3 files changed, 161 insertions(+), 12 deletions(-) diff --git a/canard.c b/canard.c index 045bed1..7c982ae 100644 --- a/canard.c +++ b/canard.c @@ -999,11 +999,28 @@ bool canardTableDecodeMessage(const CanardCodingTable* table, const CanardRxTransfer* transfer, void* msg) { - (void)table; - (void)transfer; - (void)msg; +#if CANARD_ENABLE_TAO_OPTION + if (transfer->tao && (transfer->payload_len > table->max_size)) { + return true; // invalid length + } +#endif + + uint32_t bit_ofs = 0; + const CanardCodingTableEntry* entry = &table->entries[0]; + const CanardCodingTableEntry* entry_last = &table->entries[table->entries_max]; + if (tableDecodeCore(entry, entry_last, transfer, &bit_ofs, msg)) { + return true; // decode failure + } - return true; + const uint32_t byte_len = (bit_ofs+7U)/8U; +#if CANARD_ENABLE_TAO_OPTION + // if this could be CANFD then the dlc could indicating more bytes than + // we actually have + if (!transfer->tao) { + return byte_len > transfer->payload_len; + } +#endif + return byte_len != transfer->payload_len; } #endif @@ -1016,14 +1033,14 @@ uint32_t canardTableEncodeMessage(const CanardCodingTable* table, #endif ) { - (void)table; - (void)buffer; - (void)msg; -#if CANARD_ENABLE_TAO_OPTION - (void)tao; -#endif + memset(buffer, 0, table->max_size); + + uint32_t bit_ofs = 0; + const CanardCodingTableEntry* entry = &table->entries[0]; + const CanardCodingTableEntry* entry_last = &table->entries[table->entries_max]; + tableEncodeCore(entry, entry_last, buffer, &bit_ofs, msg); - return 0; + return ((bit_ofs+7)/8); } #endif @@ -1858,6 +1875,88 @@ CANARD_INTERNAL void swapByteOrder(void* data, unsigned size) } } +/** + * Table coding functions + */ +#if CANARD_ENABLE_TABLE_DECODING +CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, + const CanardCodingTableEntry* entry_last, + const CanardRxTransfer* transfer, + uint32_t* bit_ofs, + void* msg) +{ + do { + void* p = (char*)msg + entry->offset; + uint8_t type = entry->type; + uint8_t bitlen = entry->bitlen; + + switch (type) { + case CANARD_TABLE_CODING_UNSIGNED: + case CANARD_TABLE_CODING_SIGNED: + case CANARD_TABLE_CODING_FLOAT: { + canardDecodeScalar(transfer, *bit_ofs, bitlen, type != CANARD_TABLE_CODING_UNSIGNED, p); + if (type == CANARD_TABLE_CODING_FLOAT && bitlen == 16) { + uint16_t float16_val = (uint16_t)*(int16_t*)p; + *(float*)p = canardConvertFloat16ToNativeFloat(float16_val); + } + + *bit_ofs += bitlen; + break; + } + + case CANARD_TABLE_CODING_VOID: + // nothing to decode for void + *bit_ofs += bitlen; + break; + + default: + return true; // invalid type + } + } while (++entry <= entry_last); + + return false; // success +} +#endif + +#if CANARD_ENABLE_TABLE_ENCODING +CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, + const CanardCodingTableEntry* entry_last, + uint8_t* buffer, + uint32_t* bit_ofs, + const void* msg) +{ + do { + const void* p = (const char*)msg + entry->offset; + uint8_t type = entry->type; + uint8_t bitlen = entry->bitlen; + + switch (type) { + case CANARD_TABLE_CODING_UNSIGNED: + case CANARD_TABLE_CODING_SIGNED: + case CANARD_TABLE_CODING_FLOAT: { + uint16_t float16_val; + if (type == CANARD_TABLE_CODING_FLOAT && bitlen == 16) { + float16_val = canardConvertNativeFloatToFloat16(*(const float*)p); + p = &float16_val; + } + canardEncodeScalar(buffer, *bit_ofs, bitlen, p); + + *bit_ofs += bitlen; + break; + } + + case CANARD_TABLE_CODING_VOID: + // void encoding is taken care of by memset to 0 in the wrapper + *bit_ofs += bitlen; + break; + + default: + return; // invalid type + } + } while (++entry <= entry_last); +} +#endif + /* * CRC functions */ diff --git a/canard.h b/canard.h index a2fb5f6..c0e39a0 100644 --- a/canard.h +++ b/canard.h @@ -464,21 +464,52 @@ struct CanardRxTransfer }; #if CANARD_ENABLE_TABLE_DECODING || CANARD_ENABLE_TABLE_ENCODING + +#define CANARD_TABLE_CODING_UNSIGNED (0) +#define CANARD_TABLE_CODING_SIGNED (1) +#define CANARD_TABLE_CODING_FLOAT (2) +#define CANARD_TABLE_CODING_VOID (3) + /** * This structure describes the encoded form of part of a particular message. It * can be contained in ROM. It should be generated using dronecan_dsdlc. */ typedef struct { + uint16_t offset; + uint8_t type; + uint8_t bitlen; } CanardCodingTableEntry; +/** + * Coding table entry for primitive types (unsigned, signed, float). + * + * offset: offset, in chars, to the storage in the message struct + * type: 0, 1, or 2 for unsigned, signed, float + * bitlen: number of bits the primitive is encoded into + */ +#define CANARD_TABLE_CODING_ENTRY_PRIMITIVE(offset, type, bitlen) \ + {offset, type, bitlen} + +/** + * Coding table entry for void type. + * + * offset: always 0 + * type: 3 for void + * bitlen: number of bits of padding in the encoded output + */ +#define CANARD_TABLE_CODING_ENTRY_VOID(bitlen) \ + {0, CANARD_TABLE_CODING_VOID, bitlen} + /** * This structure describes the encoded form of a particular message. It can be * contained in ROM. It should be generated using dronecan_dsdlc. */ typedef struct { - size_t entries_max; + uint16_t max_size; // must be > 0 + uint16_t entries_max; CanardCodingTableEntry entries[]; } CanardCodingTable; + #endif /** diff --git a/canard_internals.h b/canard_internals.h index c147c94..cdd63f4 100644 --- a/canard_internals.h +++ b/canard_internals.h @@ -150,6 +150,25 @@ CANARD_INTERNAL bool isBigEndian(void); CANARD_INTERNAL void swapByteOrder(void* data, unsigned size); +/** + * Table coding functions + */ +#if CANARD_ENABLE_TABLE_DECODING +CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, + const CanardCodingTableEntry* entry_end, + const CanardRxTransfer* transfer, + uint32_t* bit_ofs, + void* msg); +#endif + +#if CANARD_ENABLE_TABLE_ENCODING +CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, + const CanardCodingTableEntry* entry_end, + uint8_t* buffer, + uint32_t* bit_ofs, + const void* msg); +#endif + /* * Transfer CRC */ From 5ccd48499b8b7fb9c46d956739e31c0b4cfbec14 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sat, 20 Jul 2024 16:38:58 -0500 Subject: [PATCH 3/5] table code types containing static arrays Static arrays have a 2-entry header (8 bytes), plus the entries describing the contents of the array. Array contents cannot consist of more than 256 entries (including header entries and entries describing nested types). The array length cannot exceed 65535. --- canard.c | 32 ++++++++++++++++++++++++++++++++ canard.h | 19 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/canard.c b/canard.c index 7c982ae..1f42bec 100644 --- a/canard.c +++ b/canard.c @@ -1909,6 +1909,23 @@ CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, *bit_ofs += bitlen; break; + case CANARD_TABLE_CODING_ARRAY_STATIC: { + const CanardCodingTableEntry* aux = ++entry; + const CanardCodingTableEntry* array_entry = ++entry; + const CanardCodingTableEntry* array_entry_last = array_entry + bitlen; + entry = array_entry_last; // point entry to last for ++entry at end of loop + + uint16_t elems = aux->type | ((uint16_t)aux->bitlen << 8); + while (elems--) { + if (tableDecodeCore(array_entry, array_entry_last, transfer, bit_ofs, p)) { + return true; + } + p = (char*)p + aux->offset; + } + + break; + } + default: return true; // invalid type } @@ -1950,6 +1967,21 @@ CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, *bit_ofs += bitlen; break; + case CANARD_TABLE_CODING_ARRAY_STATIC: { + const CanardCodingTableEntry* aux = ++entry; + const CanardCodingTableEntry* array_entry = ++entry; + const CanardCodingTableEntry* array_entry_last = array_entry + bitlen; + entry = array_entry_last; // point entry to last for ++entry at end of loop + + uint16_t elems = aux->type | ((uint16_t)aux->bitlen << 8); + while (elems--) { + tableEncodeCore(array_entry, array_entry_last, buffer, bit_ofs, p); + p = (const char*)p + aux->offset; + } + + break; + } + default: return; // invalid type } diff --git a/canard.h b/canard.h index c0e39a0..a1bf5f7 100644 --- a/canard.h +++ b/canard.h @@ -469,6 +469,7 @@ struct CanardRxTransfer #define CANARD_TABLE_CODING_SIGNED (1) #define CANARD_TABLE_CODING_FLOAT (2) #define CANARD_TABLE_CODING_VOID (3) +#define CANARD_TABLE_CODING_ARRAY_STATIC (4) /** * This structure describes the encoded form of part of a particular message. It @@ -500,6 +501,24 @@ typedef struct { #define CANARD_TABLE_CODING_ENTRY_VOID(bitlen) \ {0, CANARD_TABLE_CODING_VOID, bitlen} +/** + * Coding table entries (2 total) for array type with a static length. + * + * first entry: + * offset: offset, in chars, to the storage of the first element in the message struct + * type: 4 for static array + * bitlen: total number of entries after these which describe the array contents (may encompass e.g. other arrays), minus one + * second entry: + * offset: size, in chars, of one array element, i.e. sizeof(arr[0]) + * type: low 8 bits of array length + * bitlen: high 8 bits of array length + * + * note: entries which describe the array contents have offsets relative to the start of the array storage + */ +#define CANARD_TABLE_CODING_ENTRIES_ARRAY_STATIC(offset, num_entries, elem_size, array_len) \ + {offset, CANARD_TABLE_CODING_ARRAY_STATIC, (num_entries)-1}, \ + {elem_size, (array_len)&0xFF, (array_len)>>8} + /** * This structure describes the encoded form of a particular message. It can be * contained in ROM. It should be generated using dronecan_dsdlc. From e8d1797bdc8c1c4e55c53a464ba21765b2e9cfc0 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sat, 20 Jul 2024 17:33:48 -0500 Subject: [PATCH 4/5] table code dynamic arrays, including tail array optimization Dynamic arrays have a 3-entry header (12 bytes), plus the entries describing the contents of the array. Array contents cannot consist of more than 256 entries (including header entries and entries describing nested types). The maximum array length cannot exceed 65535. --- canard.c | 124 ++++++++++++++++++++++++++++++++++++++++++--- canard.h | 25 +++++++++ canard_internals.h | 6 ++- 3 files changed, 147 insertions(+), 8 deletions(-) diff --git a/canard.c b/canard.c index 1f42bec..e067185 100644 --- a/canard.c +++ b/canard.c @@ -1008,7 +1008,13 @@ bool canardTableDecodeMessage(const CanardCodingTable* table, uint32_t bit_ofs = 0; const CanardCodingTableEntry* entry = &table->entries[0]; const CanardCodingTableEntry* entry_last = &table->entries[table->entries_max]; - if (tableDecodeCore(entry, entry_last, transfer, &bit_ofs, msg)) { + if (tableDecodeCore(entry, entry_last, transfer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + transfer->tao +#else + true +#endif + )) { return true; // decode failure } @@ -1038,7 +1044,13 @@ uint32_t canardTableEncodeMessage(const CanardCodingTable* table, uint32_t bit_ofs = 0; const CanardCodingTableEntry* entry = &table->entries[0]; const CanardCodingTableEntry* entry_last = &table->entries[table->entries_max]; - tableEncodeCore(entry, entry_last, buffer, &bit_ofs, msg); + tableEncodeCore(entry, entry_last, buffer, &bit_ofs, msg, +#if CANARD_ENABLE_TAO_OPTION + tao +#else + true +#endif + ); return ((bit_ofs+7)/8); } @@ -1883,7 +1895,8 @@ CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, const CanardCodingTableEntry* entry_last, const CanardRxTransfer* transfer, uint32_t* bit_ofs, - void* msg) + void* msg, + bool tao) { do { void* p = (char*)msg + entry->offset; @@ -1917,7 +1930,7 @@ CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, uint16_t elems = aux->type | ((uint16_t)aux->bitlen << 8); while (elems--) { - if (tableDecodeCore(array_entry, array_entry_last, transfer, bit_ofs, p)) { + if (tableDecodeCore(array_entry, array_entry_last, transfer, bit_ofs, p, !elems && tao)) { return true; } p = (char*)p + aux->offset; @@ -1926,6 +1939,60 @@ CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, break; } + case CANARD_TABLE_CODING_ARRAY_DYNAMIC: + case CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO: { + const CanardCodingTableEntry* aux = ++entry; + const CanardCodingTableEntry* aux2 = ++entry; + const CanardCodingTableEntry* array_entry = ++entry; + const CanardCodingTableEntry* array_entry_last = array_entry + bitlen; + entry = array_entry_last; // point entry to last for ++entry at end of loop + + uint16_t max_len = aux->type | ((uint16_t)aux->bitlen << 8); + void* len_p = ((char*)msg + aux2->offset); + uint8_t len_bitlen = aux2->bitlen; + if (type != CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO || !tao) { + // not using TAO + canardDecodeScalar(transfer, *bit_ofs, len_bitlen, false, len_p); + *bit_ofs += len_bitlen; + + uint16_t elems; + if (len_bitlen <= 8) { + elems = *(uint8_t*)len_p; + } else { // 16 bits is max supported len + elems = *(uint16_t*)len_p; + } + if (elems > max_len) { + return true; // invalid value + } + + while (elems--) { + if (tableDecodeCore(array_entry, array_entry_last, transfer, bit_ofs, p, !elems && tao)) { + return true; + } + p = (char*)p + aux->offset; + } + } else { + // TAO optimization in play + uint16_t elems = 0; + // TAO requires the element size to be at least 8 bits so if we have less we know we are done + uint32_t max_bits = (transfer->payload_len*8)-7; + while (max_bits > *bit_ofs) { + if (!max_len-- || tableDecodeCore(array_entry, array_entry_last, transfer, bit_ofs, p, false)) { + return true; + } + p = (char*)p + aux->offset; + elems++; + } + if (len_bitlen <= 8) { + *(uint8_t*)len_p = (uint8_t)elems; + } else { // 16 bits is max supported len + *(uint16_t*)len_p = elems; + } + } + + break; + } + default: return true; // invalid type } @@ -1940,7 +2007,8 @@ CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, const CanardCodingTableEntry* entry_last, uint8_t* buffer, uint32_t* bit_ofs, - const void* msg) + const void* msg, + bool tao) { do { const void* p = (const char*)msg + entry->offset; @@ -1975,7 +2043,51 @@ CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, uint16_t elems = aux->type | ((uint16_t)aux->bitlen << 8); while (elems--) { - tableEncodeCore(array_entry, array_entry_last, buffer, bit_ofs, p); + tableEncodeCore(array_entry, array_entry_last, buffer, bit_ofs, p, !elems && tao); + p = (const char*)p + aux->offset; + } + + break; + } + + case CANARD_TABLE_CODING_ARRAY_DYNAMIC: + case CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO: { + const CanardCodingTableEntry* aux = ++entry; + const CanardCodingTableEntry* aux2 = ++entry; + const CanardCodingTableEntry* array_entry = ++entry; + const CanardCodingTableEntry* array_entry_last = array_entry + bitlen; + entry = array_entry_last; // point entry to last for ++entry at end of loop + + uint16_t max_len = aux->type | ((uint16_t)aux->bitlen << 8); + const void* len_p = (const char*)msg + aux2->offset; + uint8_t len_bitlen = aux2->bitlen; + + uint16_t elems; + if (len_bitlen <= 8) { + elems = *(const uint8_t*)len_p; + } else { // 16 bits is max supported len + elems = *(const uint16_t*)len_p; + } + if (elems > max_len) { + elems = max_len; + } + + if (type != CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO || !tao) { + // encode the length value we have clamped to the maximum + uint8_t elems_8; + if (len_bitlen <= 8) { + elems_8 = (uint8_t)elems; + len_p = &elems_8; + } else { // 16 bits is max supported len + len_p = &elems; + } + canardEncodeScalar(buffer, *bit_ofs, len_bitlen, len_p); + *bit_ofs += len_bitlen; + } + + int element_tao = type != CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO && tao; + while (elems--) { + tableEncodeCore(array_entry, array_entry_last, buffer, bit_ofs, p, !elems && element_tao); p = (const char*)p + aux->offset; } diff --git a/canard.h b/canard.h index a1bf5f7..bd7d4d5 100644 --- a/canard.h +++ b/canard.h @@ -470,6 +470,8 @@ struct CanardRxTransfer #define CANARD_TABLE_CODING_FLOAT (2) #define CANARD_TABLE_CODING_VOID (3) #define CANARD_TABLE_CODING_ARRAY_STATIC (4) +#define CANARD_TABLE_CODING_ARRAY_DYNAMIC (5) +#define CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO (6) /** * This structure describes the encoded form of part of a particular message. It @@ -519,6 +521,29 @@ typedef struct { {offset, CANARD_TABLE_CODING_ARRAY_STATIC, (num_entries)-1}, \ {elem_size, (array_len)&0xFF, (array_len)>>8} +/** + * Coding table entries (3 total) for array type with a dynamic length. + * + * first entry: + * offset: offset, in chars, to the storage of the first element in the message struct + * type: 5 for dynamic array, 6 for dynamic array eligible for TAO + * bitlen: total number of entries after these which describe the array contents (may encompass e.g. other arrays), minus one + * second entry: + * offset: size, in chars, of one array element, i.e. sizeof(arr[0]) + * type: low 8 bits of array length + * bitlen: high 8 bits of array length + * third entry: + * offset: offset, in chars, to the storage in the message struct containing the array length + * type: always 0 + * bitlen: number of bits the array length is encoded into + * + * note: entries which describe the array contents have offsets relative to the start of the array storage + */ +#define CANARD_TABLE_CODING_ENTRIES_ARRAY_DYNAMIC(offset, tao, num_entries, elem_size, array_len, len_bitlen, len_offset) \ + {offset, (tao) ? CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO : CANARD_TABLE_CODING_ARRAY_DYNAMIC, (num_entries)-1}, \ + {elem_size, (array_len)&0xFF, (array_len)>>8}, \ + {len_offset, 0, len_bitlen} + /** * This structure describes the encoded form of a particular message. It can be * contained in ROM. It should be generated using dronecan_dsdlc. diff --git a/canard_internals.h b/canard_internals.h index cdd63f4..5356edb 100644 --- a/canard_internals.h +++ b/canard_internals.h @@ -158,7 +158,8 @@ CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, const CanardCodingTableEntry* entry_end, const CanardRxTransfer* transfer, uint32_t* bit_ofs, - void* msg); + void* msg, + bool tao); #endif #if CANARD_ENABLE_TABLE_ENCODING @@ -166,7 +167,8 @@ CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, const CanardCodingTableEntry* entry_end, uint8_t* buffer, uint32_t* bit_ofs, - const void* msg); + const void* msg, + bool tao); #endif /* From dd2aca9e3bf44fc00e5b1ec70dea86ce5ab2b7b1 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sat, 20 Jul 2024 22:43:29 -0500 Subject: [PATCH 5/5] table code types containing unions Unions have a 2-entry header (8 bytes), plus 1 entry for each field, plus the entries describing each field. The maximum number of fields is 255. Each field cannot consist of more than 255 entries (including header entries and entries describing nested types). This completes the list of DSDL types. --- canard.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ canard.h | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/canard.c b/canard.c index e067185..0bfaece 100644 --- a/canard.c +++ b/canard.c @@ -1993,6 +1993,46 @@ CANARD_INTERNAL bool tableDecodeCore(const CanardCodingTableEntry* entry, break; } + case CANARD_TABLE_CODING_UNION: { + uint8_t num_tags = bitlen; + const CanardCodingTableEntry* aux = ++entry; + const CanardCodingTableEntry* union_header = ++entry; + const CanardCodingTableEntry* union_entry = union_header + num_tags; + + uint8_t union_tag; + uint8_t tag_bitlen = aux->bitlen; + canardDecodeScalar(transfer, *bit_ofs, tag_bitlen, false, &union_tag); + *bit_ofs += tag_bitlen; + if (union_tag >= num_tags) { + return true; // invalid value + } + + void* tag_p = (char*)msg + aux->offset; + // we know 254 is the max tag value, but the chars taken is compiler dependent + if (sizeof(CanardCodingTableUnionEnum) == sizeof(uint8_t)) { + *(uint8_t*)tag_p = union_tag; + } else if (sizeof(CanardCodingTableUnionEnum) == sizeof(uint16_t)) { + *(uint16_t*)tag_p = union_tag; + } else if (sizeof(CanardCodingTableUnionEnum) == sizeof(uint32_t)) { + *(uint32_t*)tag_p = union_tag; + } else { + *(uint64_t*)tag_p = union_tag; + } + + // check each tag so we can keep track of the entry pointers + for (uint8_t tag=0; tagbitlen; + if (num_entries && tag == union_tag) { // can't decode 0 entries, so check that in addition to match + tableDecodeCore(union_entry, union_entry+num_entries-1, transfer, bit_ofs, p, tao); + } + union_entry += num_entries; + } + + entry = union_entry - 1; // point entry to last for ++entry at end of loop + + break; + } + default: return true; // invalid type } @@ -2094,6 +2134,44 @@ CANARD_INTERNAL void tableEncodeCore(const CanardCodingTableEntry* entry, break; } + case CANARD_TABLE_CODING_UNION: { + uint8_t num_fields = bitlen; + const CanardCodingTableEntry* aux = ++entry; + const CanardCodingTableEntry* union_header = ++entry; + const CanardCodingTableEntry* union_entry = union_header + num_fields; + + const void* tag_p = (const char*)msg + aux->offset; + uint8_t union_tag; + // we know 254 is the max tag value, but the chars taken is compiler dependent + if (sizeof(CanardCodingTableUnionEnum) == sizeof(uint8_t)) { + union_tag = (uint8_t)*(const uint8_t*)tag_p; + } else if (sizeof(CanardCodingTableUnionEnum) == sizeof(uint16_t)) { + union_tag = (uint16_t)*(const uint16_t*)tag_p; + } else if (sizeof(CanardCodingTableUnionEnum) == sizeof(uint32_t)) { + union_tag = (uint32_t)*(const uint32_t*)tag_p; + } else { + union_tag = (uint64_t)*(const uint64_t*)tag_p; + } + // the native type is an enum so assume it can't be out of range + + uint8_t tag_bitlen = aux->bitlen; + canardEncodeScalar(buffer, *bit_ofs, tag_bitlen, &union_tag); + *bit_ofs += tag_bitlen; + + // check each tag so we can keep track of the entry pointers + for (uint8_t tag=0; tagbitlen; + if (num_entries && tag == union_tag) { // can't encode 0 entries, so check that in addition to match + tableEncodeCore(union_entry, union_entry+num_entries-1, buffer, bit_ofs, p, tao); + } + union_entry += num_entries; + } + + entry = union_entry - 1; // point entry to last for ++entry at end of loop + + break; + } + default: return; // invalid type } diff --git a/canard.h b/canard.h index bd7d4d5..7e3acb1 100644 --- a/canard.h +++ b/canard.h @@ -472,6 +472,17 @@ struct CanardRxTransfer #define CANARD_TABLE_CODING_ARRAY_STATIC (4) #define CANARD_TABLE_CODING_ARRAY_DYNAMIC (5) #define CANARD_TABLE_CODING_ARRAY_DYNAMIC_TAO (6) +#define CANARD_TABLE_CODING_UNION (7) + +/** + * Structure representing the enum for the union tag field for the maximum + * supported structure size in the table. We assume it is the same size as any + * union tag enum with the same span of tag values or less. + */ +typedef enum { + CanardCodingTableUnionEnumMin = 0, + CanardCodingTableUnionEnumMax = 254, +} CanardCodingTableUnionEnum; /** * This structure describes the encoded form of part of a particular message. It @@ -544,9 +555,44 @@ typedef struct { {elem_size, (array_len)&0xFF, (array_len)>>8}, \ {len_offset, 0, len_bitlen} +/** + * Coding table entries (2 total) for union type header. + * + * first entry: + * offset: offset, in chars, to the storage of an arbitrary union member + * type: 7 for union + * bitlen: number of fields in the union + * second entry: + * offset: offset, in chars, to the storage of the tag + * type: always 0 + * bitlen: number of bits the tag is encoded into + * + * note: entries which describe the union contents have offsets relative to the start of the array storage + */ +#define CANARD_TABLE_CODING_ENTRIES_UNION(offset, num_fields, tag_bitlen, tag_offset) \ + {offset, CANARD_TABLE_CODING_UNION, num_fields}, \ + {tag_offset, 0, tag_bitlen} + +/** + * Coding table entry for union type field. + * + * offset: always 0 + * type: always 0 + * bitlen: total number of entries after these which describe the field contents (may encompass e.g. arrays) + */ +#define CANARD_TABLE_CODING_ENTRY_UNION_FIELD(num_entries) \ + {0, 0, num_entries} + /** * This structure describes the encoded form of a particular message. It can be * contained in ROM. It should be generated using dronecan_dsdlc. + * + * The table can describe any structure of message supported by DSDL, however + * the quantities and sizes of specific aspects are limited by the datatypes + * chosen. For example, the total number of entries is limited to 65536 due to + * the maximum entry index being stored in a uint16_t. The description for each + * type of table entry explains the field types and meanings, from which the + * maximum supported values can be derived. */ typedef struct { uint16_t max_size; // must be > 0