1

I'm currently facing a problem, where I'd like to merge unaligned bytes of each property of a class and therefore basically get a serialized byte array only representation (depth-first serialization). By unaligned, I mean the case, where not a full byte/int/short is needed, but only just a few bits of each property.

So, consider this entity class:

public class Data
{
    public byte Type { get; set; }
    [BitfieldLength(4)] public byte NumberOfOptions1 { get; set; } // nibble
    [BitfieldLength(4)] public byte NumberOfOptions2 { get; set; } // nibble
    public uint MajorVersion { get; set; }
    public byte MinorVersion { get; set; }
    [BitfieldLength(24)] public uint TTL { get; set; } // 3 bytes
    public ushort EventsCounter {get; set;}
    
    // 12 bytes total
}

The BitfieldLength is a custom attribute class:

[AttributeUsage(AttributeTargets.Property)]
public class BitfieldLengthAttribute : Attribute
{
    public BitfieldLengthAttribute(uint length)
    {
        this.Length = length;
    }

    public uint Length { get; }
}

Here is the extension code to extract the bytes from each property:

public static class Extensions
{
    public static byte[] GetBytesFromProperty(this PropertyInfo field, Data data)
    {
        Type propertyType = field.PropertyType;
        
        if (propertyType == typeof(ushort))
        {
            return BitConverter.GetBytes((ushort) field.GetValue(data));
        }
        else if (propertyType == typeof(uint))
        {
            return BitConverter.GetBytes((uint) field.GetValue(data));
        }
        else if (propertyType == typeof(byte))
        {
            return new[] {(byte) field.GetValue(data)};
        } 
        else
        {
            throw new Exception($"Unknown type: {propertyType}");
        }
    }
}

and finally the last part:

var data = new Data();
data.Type = 1;
data.NumberOfOptions1 = 2;
data.NumberOfOptions2 = 3;
data.MajorVersion = 5;
data.MinorVersion = 1;
data.TTL = 20;
data.EventsCounter = 120;

foreach (var prop in typeof(Data).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
    byte[] bytes = prop.GetBytesFromProperty(data);
    Console.WriteLine("Name: {0} => Value: {1} => Type: {2} => Bytes: {3}", prop.Name,
        prop.GetValue(data), prop.PropertyType,
        BitConverter.ToString(bytes));
}

If I'd concatenate each byte array from each property, the output would be 14 bytes instead of the expected 12 bytes...

And here is, where I'm stuck. How to twiddle this bits and bytes to the correct output?

EDIT:

Someone in the comments noted that the order of the properties is unpredictable. That's true, however, please take a look to this solution: https://stackoverflow.com/a/17998371/1362848.

Pythoneer
  • 87
  • 6
  • Can you post why 12 bytes are expected? And how you came up with that expectation? – mxmissile Jan 20 '21 at 21:48
  • Your code never takes in account of `BitFieldlength`. The smallest part you want to get is 4 bits, so less than a byte, so I would write a method `string GetBitsAsTextFromProperty(this PropertyInfo field, Data data)` that returns strings like e. g. "1011" for NumberOfOptions1, and later convert the whole string to a byte array. – BitLauncher Jan 20 '21 at 21:56
  • Of course my code didn't take the BitFieldLength into account, since I don't know how to proceed here. – Pythoneer Jan 20 '21 at 22:09
  • 1
    Important: the reflection APIs explicitly do not make any promises re order, so: without some order metadata that you add, this can never be deterministic. There is no such thing as field declaration order: it does not exist as a concept that you can query reliably. The bit twiddling should just be some masking and shifting: not trivial, but entirely doable. Side note: BitConverter is almost never a good solution to most problems (in particular, it is CPU-endian, and allocation-heavy) – Marc Gravell Jan 20 '21 at 22:24
  • You need to use `GetCustomAttribute` to get the value of `BitFieldLength`. You need something like `BitArray` to append the bits to, although that particular class is not very well written. You need to be able to append arbitrary bit lengths from `int`s directly to it, without looping. I would implement `GetBytesFromProperty` as just a `(long value, int bitMask)` tuple, which covers most basic types. – Charlieface Jan 20 '21 at 22:26
  • Definitely, you should use `StructLayout` with fields to ensure predictability. – aybe Jan 21 '21 at 08:24

0 Answers0