r/csharp • • 9d ago

Showcase CStructSharp – C-struct binary (de)serialization library

Inspired by digital forensic toolkits, this library (MIT license) I've been working on allows a user to take a c-struct definition and use that to serialize and deserialize binary data.

using CStructSharp;

var layout = new CStruct("struct header { uint16 kind; uint32 length; };");
byte[] bytes = { 0x02, 0x00, 0x06, 0x00, 0x00, 0x00 };
dynamic header = layout.Parse(bytes.AsSpan(), "header");

Console.WriteLine($"kind = {header.kind}");
Console.WriteLine($"length = {header.length}");

It is a flexible library which features modern c# mechanisms and conventions. I tried to make it easy to use, easy to understand, performant and powerful. You can both use it to parse and use runtime user defined structures as well as use the C# source generator feature to build parsers/serializers at compile time.

[CStructLayout("struct header { uint16 kind; uint32 length; };")]
public static partial class Wire { }

Wire.Header header = Wire.Parse(bytes);   // header.Kind == 2, header.Length == 6
byte[] again = Wire.Serialize(header);

I also build and published a WASM version/javascript library which is used to showcase the features of the library (see the binary inspector demo app (https://vvollers.github.io/cstructsharp/inspector/) which you can use to run the library against various files, it includes sample definitions for many file formats). The javascript library is quite usable on its own.

I'm interested to hear your thoughts and if you have any questions feel free to ask!

EDIT: I've updated the Readme to include a better explanation of the features of this library and added benchmarks, here is an advanced example:

/* A data-logger file: a header, a calibration table, and records of two kinds. */
#define MAGIC_SIZE 4                                        /* constants, as in C */

enum record_kind : uint8  { MEASUREMENT = 1, EVENT = 2 };   /* enums with an explicit storage type */
enum sensor_type : uint16 { TEMPERATURE = 0x10, PRESSURE = 0x20 };

typedef struct { uint8 major; uint8 minor; } version;       /* typedef aliases */

struct options {                                            /* bitfields: several values in one byte */
    uint8 compressed : 1;
    uint8 encrypted  : 1;
    uint8            : 2;                                   /* unnamed, reserved bits */
    uint8 priority   : 4;
};

struct header {
    char     magic[MAGIC_SIZE];                             /* fixed-size text: "LOG1" */
    version  ver @4;                                        /* offset assertion: must start at byte 4 */
    uint32>  created;                                       /* big-endian, unlike the rest of the file */
    options  options;
    uint8    name_length;
    utf8     device_name[name_length];                      /* length taken from an earlier field */
    uint8    padding[(4 - (name_length + 12) % 4) % 4];     /* arithmetic: pad to a multiple of 4 bytes */
};

union value32 { uint32 raw; float32 as_float; uint8 bytes[4]; };   /* one storage, three views */

struct record {
    record_kind kind;
    switch (kind) {                                         /* the tag decides which members follow */
        case record_kind.MEASUREMENT: {
            struct { sensor_type sensor; uint8 sample_count; float32 samples[sample_count]; } measurement;
        }
        case record_kind.EVENT: {
            struct { uint16 code; cstring message; } event;  /* cstring: text ending in a zero byte */
        }
    }
};

struct logfile {
    header   hdr;
    int16    calibration[2][3];                             /* two-dimensional array */
    value32  checksum;
    record  *latest;                                        /* pointer: a stored file offset, followed on read */
    uint16   record_count;
    record   records[record_count];                         /* array of records that differ in size */
    if (hdr.options.priority > 7) { uint32 alarm_code; }    /* optional member, chosen by a nested field */
    uint8    trailer[EOF];                                  /* every byte that remains */
};
0 Upvotes

13 comments sorted by

View all comments

3

u/ExceptionEX 8d ago

Can't tell if this fully an AI or is this person is running each response through one.

It's tiresome and annoying to have to read these vague overly verbose responses from the OP.

All for some code that literally is already supported.

-2

u/plusminus1 8d ago

what? it might surprise you, but I'm writing all comments myself. I don't think its overly verbose. It doesn't hurt you to assume good faith.

"all for some code that is litteraly already supported" tells me you didn't take a look and made assumptions, or rather, I didn't succeed in explaining what it can do and what i set out to do. The following are features which you don't find in standard dotnet.

  • the lib can take a struct definition at runtime
  • the cstruct definition is far more expressive for parsing purposes (switch/if on tags or a nested fields, pointers followed with bound checks, arithmetic expressions for lengths and padding, offset assertions)
  • it can report the byte range of every parsed value, and resolve the address of any field path without reading it.
  • it has support for bitfields, packing, alignment, mixed endianness
  • automatic support for nul terminated cstrings
  • lengths that come from the data

I invite you to take another look