1

Recently I have been working with .FIT file format and SDK provided by garmin. In a nutshell I need to send Message Definition and Message Data that corresponds to Message Definition. There are many types of messages and almost all the time I need to use few of them - and SDK does not provide that, it sends all messages that are supported. I need to store variables of different data types and relate them to specific message. Currently variables are stored in structures that may look like this:

//Example
typedef struct
{
        uint32_t timestamp; // 1 * s + 0,
        uint16_t speed; // 1000 * m/s + 0,
        uint8_t heart_rate; // 1 * bpm + 0,
} FIT_RECORD_MESG;

However number of variables/arrays as well as data types is different for each FIT_(x)_MESG. This is example of FIT_RECORD_MESG.

I have created function that creates array which contains information which variables/arrays should be sent.

void write_field(FIT_UINT8 field)
{
    fields_mesg.fields_number++;    //number of messages
    fields_mesg.fields_array = (uint8_t*) realloc(fields_mesg.fields_array, sizeof(uint8_t)*fields_mesg.fields_number); //append message type
    fields_mesg.fields_array[fields_mesg.fields_number-1] = field;                                  //array of gloabl field types
}
/*
Example array:
[0,2]; 
0 - timestamp
2 - heart_rate
variable speed should not be sent
*/

Each structure can contain different number of variables/arrays as well as data types. If all data types in structure were the same type (and without arrays) I could create a pointer to the data structure and fields_mesg.fields_array that has been created in write_field() would be an offset from the structure pointer. Unfortunately in my case that cannot be done becouse data types may vary and structure can contain arrays. I was thinking about creating 3 arrays/structures of corresponding data type but it would be more complex (and would need to figure out how to store arrays from structure). I came up with something like lookup table where each position is offset (in bytes) to corresponding data from structure pointer.

#define FIT_RECORD_MESG_32_BIT_START 0+3    
#define FIT_RECORD_MESG_16_BIT_START 1+3   
#define FIT_RECORD_MESG_8_BIT_START 2+3    
#define FIT_RECORD_MESG_ARRAY_SIZE_CNT 51//48+3
#define offsetof(s,m) ((size_t)&(((s*)0)->m))

/*Table of offsets to structure*/
 const FIT_UINT8 FIT_RECORD_MESG_SIZES[FIT_RECORD_MESG_ARRAY_SIZE_CNT] =
{
    FIT_RECORD_MESG_32_BIT_START,
    FIT_RECORD_MESG_16_BIT_START,
    FIT_RECORD_MESG_8_BIT_START,
    offsetof(FIT_RECORD_MESG, timestamp), 
    offsetof(FIT_RECORD_MESG, speed), 
    offsetof(FIT_RECORD_MESG, heart_rate), 
}

First three positions are informations where data type is changing. Now by refering to fields_mesg.fields_array I can find pointer to variable in structure and print only 1/2/4bytes or array of 1/2/4bytes - depending on whats in structure FIT_(X)_MESG.

/*
    local_mesg_number - used by FIT protocol
    *array_sizes - pointer to array of offsets from structure address (example. FIT_RECORD_MESG_SIZES)
    *mesg_pointer - pointer to data structure (example. FIT_RECORD_MESG)
    *fields_mesg - pointer to structure which contains information which messages to send
*/
void WriteMessageRecord(FIT_UINT8 local_mesg_number, FIT_UINT8* array_sizes, const void* mesg_pointer, FIELDS_TO_SEND* fields_mesg, FILE* fp)
{
    void *pointer_offset = mesg_pointer; //begining of the structure
    uint16_t message_to_send;
    uint16_t offset_diference=0;
    
    /*Write begining of the message*/
    WriteData(&local_mesg_number, FIT_HDR_SIZE, fp);

    /*Go through every message in fields_mesg.fields_array*/
    for (uint16_t cnt = 0; cnt < fields_mesg->fields_number;cnt++)
    {
        message_to_send = fields_mesg->fields_array[cnt];
        /*If data type is 1 byte*/
        if (message_to_send >= (array_sizes[2]-3))
        {
            offset_diference = array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3];
            /*Check if this isn't array and print as many as array length*/
            if (offset_diference > sizeof(uint8_t))
            {               
                for (uint8_t x = 0;x < offset_diference; x++)
                {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3]+x;
                    WriteData(((uint8_t*)pointer_offset), 1, fp);

                    printf("\nARRAY: %d, %d", (*(uint8_t*)pointer_offset), message_to_send);
                }
            }
            else 
            {
                pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
                WriteData(((uint16_t*)pointer_offset), 1, fp);

                printf("\nNOT   : %d, %d", (*(uint8_t*)pointer_offset), message_to_send);

            }
        }
        /*If data type is 2 bytes*/
        else if (message_to_send >= (array_sizes[1] - 3))
        {
            offset_diference = (array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3]);

            /*Check if this isn't array and print as many as array length*/
            if (offset_diference > sizeof(uint16_t))
            {
                for (uint8_t x = 0;x < offset_diference; x+=2)
                {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3]+x;
                    WriteData(((uint16_t*)pointer_offset), 2, fp);

                    printf("\nARRAY   : %d, %d", (*(uint16_t*)pointer_offset), message_to_send);
                }
            }
            else //Print only 2 bytes
            {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
                    WriteData(((uint16_t*)pointer_offset), 2, fp);

                    printf("\nNOT   : %d, %d", (*(uint16_t*)pointer_offset), message_to_send);
            }

        }
        /*If data type is 4 bytes*/
        else if (message_to_send >= (array_sizes[0]-3))
        {
            offset_diference = (array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3]);
            /*Check if this isn't array and print as many as array length*/
            if (offset_diference > sizeof(uint32_t))
            {
                for (uint8_t x = 0;x < offset_diference; x += 4)
                {
                    pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3] + x;
                    WriteData(((uint32_t*)pointer_offset), 4, fp);
                    printf("\nARRAY   : %d, %d", (*(uint32_t*)pointer_offset), message_to_send);
                }
            }
            else //Print only 4 bytes
            {
                pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
                WriteData(((uint32_t*)pointer_offset), 4, fp);
                printf("\nNOT   : %d, %d", (*(uint32_t*)pointer_offset), message_to_send);
            }
        }
    }
}

And in main function:

// Write message of type: Record
        FIT_RECORD_MESG record_mesg;
        write_field(FIT_RECORD_MESG_TIMESTAMP); //will be sending timestamp variable
        write_field(FIT_RECORD_MESG_ENHANCED_SPEED);  //will be sending enhanced_speed variable
        record_mesg.timestamp = 5;
        record_mesg.enhanced_speed = 6;
        WriteMessageDefinitionSelection(0, fit_mesg_defs[FIT_MESG_RECORD], &fields_mesg, fp); //send message definition
        WriteMessageRecord(0, FIT_RECORD_MESG_SIZES ,&record_mesg, &fields_mesg, fp); //send message data
        clear_fields();

This works but I don't like few things about it:

  • I need to create additional offset arrays which takes extra memory in ROM.
  • I need to calculate where each data type begins.
  • Any changes to structure would create an error.

So.. Is there any more elegant way to store variables/arrays of different data types and easily access them?

kronikary
  • 13
  • 3
  • All of this is totally wrong. (1) If you need to store several things of different types, then an array is not what you want. Use a struct. (2) If you need to write a struct, just write a struct, don't write each field separately. Throw away `write_field` and everything around it, you don't need it. Use `FIT_RECORD_MESG record_mesg; ... WriteData(&record_mesg, sizeof(record_mesg), fp);` – n. m. could be an AI Jan 20 '21 at 12:05
  • What you have shown has been implemented in SDK. But the problem is I don't want to write whole structure "record_mesg", becouse many times I need to write only a few variables/arrays from structure. – kronikary Jan 20 '21 at 12:11
  • Code such as `(*(uint16_t*)pointer_offset)` is a [strict aliasing violation](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) and therefore undefined behavior. It also risks violating any alignment restrictions and can be undefined behavior for that reason in addition to that from the strict aliasing violation. – Andrew Henle Jan 20 '21 at 13:04
  • 1
    If you want to write a variable, write a variable. `WriteData(&record_mesg.timestamp, sizeof(record_mesg.timestamp), fp);`. Why is it important that this particular variable sits in a struct? – n. m. could be an AI Jan 20 '21 at 13:24
  • @n.'pronouns'm it is only a matter of fact that number of messages/variables to write may change during application work and I just didn't want to make list of `if` statements to check which variable to send. And I would need to do that for each message type which are plenty :P – kronikary Jan 20 '21 at 13:50
  • @AndrewHenle Thanks, didn't notice that - i used this casting only for debugging. Now changed casting in WriteData() to *char. – kronikary Jan 20 '21 at 14:45
  • You are trying to create an interpreted mini-language that writes your data, encode its statement as an array of descriptors, and interpret the resulting program (the interpreter is WriteMessageRecord and the program is the two array parameters). Instead of all this, just write your program in C. You have the same amount of code to write, minus the interpreter. – n. m. could be an AI Jan 20 '21 at 19:00
  • Having said that, if you really feel that you need an array of descriptors, do them properly. A descriptor is a pair (offset, size), nothing more. You write it like this: `{offsetof(my_struct, my_field), sizeof(dummy_struct_ptr->my_field)}`. – n. m. could be an AI Jan 20 '21 at 19:10

1 Answers1

1

Here's a basic working example of how this could be built. Note there is no need to have separate code for each field type.

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

typedef struct 
{
    size_t size;
    size_t offset;
} field_descriptor;

#define FIELD_SIZE(type, field) (sizeof(((type*)0)->field))

#define FIELD(type, field) { FIELD_SIZE(type, field), offsetof(type, field)}

#define LENGTH(x) (sizeof(x)/sizeof(x[0]))

typedef struct
{
    int a;
    float b;
    int c[3];
} example;

field_descriptor example_descriptors[] = 
{
    FIELD(example, a), 
    FIELD(example, b), 
    FIELD(example, c)
};


example x = { 1, 2.34, {-1, -2, 42 }};

int write_struct(FILE* f, void* s, field_descriptor* fields, size_t nfields)
{
    for (size_t i = 0; i < nfields; ++i)
    {
        size_t c = fwrite((char*)s+fields[i].offset, fields[i].size, 1, f);
        if (c != 1) 
        {
            return c;
        }
    }
    return nfields;
}

int read_struct(FILE* f, void* s, field_descriptor* fields, size_t nfields)
{
    for (size_t i = 0; i < nfields; ++i)
    {
        size_t c = fread((char*)s+fields[i].offset, fields[i].size, 1, f);
        if (c != 1) 
        {
            return c;
        }
    }
    return nfields;
}

// example use, error checking omitted
int main()
{
    example x = { 1, 2.34, {-1, -2, 42 }};
    example y = {0, 0, {0, 0, 0}};
    FILE* f = fopen("test.dat", "w");
    write_struct(f, &x, example_descriptors, LENGTH(example_descriptors));
    fclose(f);
    f = fopen("test.dat", "r");
    read_struct(f, &y, example_descriptors, LENGTH(example_descriptors));
    printf ("%d %f %d %d %d\n", y.a, y.b, y.c[0], y.c[1], y.c[2]);
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Ok I see, I screw up way to create descriptors. Overall it seems like descriptors will take me some not so small piece of memory but I think thats the best way I can make it. Thanks :) – kronikary Jan 20 '21 at 23:49