Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize a struct with random data in C?

Assuming we have the following complex struct (in C). It includes fixed-length arrays, members of varying sizes, enums and other structs. This struct is not packed, and cannot be packed.

struct {
    uint8_t smallNum;
    /* uint8_t align0 */
    /* uint8_t align1 */
    /* uint8_t align2 */
    uint32_t arrayOfBigNums[5];
    bool isFalse;
    /* uint8_t align0 */
    /* uint8_t align1 */
    /* uint8_t align2 */
    struct myOtherStruct;
    /* uint8_t align0 */
    enum mySmallEnum;
...
    uint8_t aByte;
    /* uint8_t align0 */
    /* uint8_t align1 */
} myStruct;

We would like to initialize this struct with random data for unit testing. The test includes taking this struct, serializing it, writing it to flash, reading it, deserializing it, and then checking that the data hasn't changed.

Either generate all possible values, or generate a small randomized subset and test it. So how would one go about doing this?

The immediate solution would be the following:

myStruct testData = {0};
myStruct readData = {0};
testData.smallNum = rand();
testData.arrayOfBigNums[0] = rand();
testData.arrayOfBigNums[1] = rand();
....
testData.aByte = rand();
save(&testData);
load(&readData);
int ret = memcmp(&readData, &testData, sizeof(myStruct)); 
// ret == 0. Good!

But this solution isn't scalable. Adding another field to the struct, or increasing the array would require changing the unit test. Also, it'd be a long init function, prone to human error.

Another solution would be to generate a random array of bytes, and then memcpy it into our struct. Although this is a great idea, in practice it won't work because our struct isn't packed. Some bytes are and always will be 0.

myStruct testData = {0};
myStruct readData = {0};
uint8_t* randomData = genRandomData(sizeof(myStruct));
memcpy((uint8_t*)&testData, randomData, sizeof(myStruct));
save(&testData);
load(&readData);
int ret = memcmp(&readData, &testData, sizeof(myStruct)); 
// ret != 0 because deserialization will fill the struct properly. 
// Ignoring junk bytes in padding

My current working solution is to iteratively improve on the previous one. Initialize a struct with 0xff, cast it to a byte array and mask it with everything. You get a compiler warning but to my knowledge it's harmless.

myStruct structMask = {MACRO_TO_FILL_WITH_MANY(-1)};
myStruct testData = {0};
myStruct readData = {0};
uint8_t* randomData = genRandomData(sizeof(myStruct));
memcpy((uint8_t*)&testData, randomData, sizeof(myStruct));
for(size_t i = 0; i < sizeof(myStruct); i++){
    ((uint8_t*)&testData)[i] &= ((uint8_t*)&structMask)[i];
}
save(&testData);
load(&readData);
int ret = memcmp(&readData, &testData, sizeof(myStruct)); 
// ret != 0 because deserialization will fill the struct properly. 
// Ignoring junk bytes in padding

This solution is also not optimal, because it doesn't take into account enum limitations. Types like bool (from stdbool.h) are stored as 0/1 which is nice, but enums can still have undefined values. So we'd need to manually modulo all enum values.

Looking for more generic / robust solutions.

like image 973
Alex Osheter Avatar asked Aug 04 '26 06:08

Alex Osheter


1 Answers

Considering you want the random data to obey by the rules of the types, there is no way to make that work in a scalable manner.

What I would do is to just copy random bytes into the struct. Yes, some data is going to be invalid, and that too should be a test case to see how you handle corrupted/invalid data:

#include <stdio.h>
#include <string.h>

struct Foo {
    int a;
    double d;
    char ch;
    _Bool b;
};

int main() {
    FILE* random = fopen("/dev/urandom", "rb");
    struct Foo foo;

    if (!random || fread(&foo, sizeof(foo), 1, random) != 1) {
        fputs("Failed to generate random data", stderr);
        return -1;
    }
    
    fclose(random);

    printf("foo.d = %d\n", foo.d);
}
like image 155
Ayxan Haqverdili Avatar answered Aug 06 '26 23:08

Ayxan Haqverdili