jefffhaynes / BinarySerializer

A declarative serialization framework for controlling formatting of data at the byte and bit level using field bindings, converters, and code.
MIT License
290 stars 62 forks source link

Crc32 built-in extension, serialization exception "Reverse binding not allowed on FieldValue attributes" when used on Subtype field combined with SerializeWhen attribute #223

Open abrasat opened 1 year ago

abrasat commented 1 year ago

Is it possible to use the Crc32 built-in extension to calculate the checksum over a subtype field? For example in this class from PcapNgNet, how can a new field be added to calculate the Crc32 for the "BlockBody Body" field?


public class Block
{
    [FieldOrder(0)]
    public BlockType Type { get; set; }

    [FieldOrder(1)]
    public int Length { get; set; }

    [FieldOrder(2)]
    [FieldAlignment(4)]
    [FieldLength("Length", ConverterType = typeof(SummingValueConverter), ConverterParameter = -12)]
    [FieldLength("Length2", ConverterType = typeof(SummingValueConverter), ConverterParameter = -12,
        BindingMode = BindingMode.OneWayToSource)]
    [Subtype("Type", BlockType.SectionHeader, typeof(SectionHeaderBlockBody))]
    [Subtype("Type", BlockType.InterfaceDescrption, typeof(InterfaceDescriptionBlockBody))]
    [Subtype("Type", BlockType.EnhancedPacket, typeof(EnhancedPacketBlockBody))]
    [Subtype("Type", BlockType.SimplePacket, typeof(SimplePacketBlockBody))]
    [Subtype("Type", BlockType.NameResolution, typeof(NameResolutionBlockBody))]
    [Subtype("Type", BlockType.InterfaceStatistics, typeof(InterfaceStatisticsBlockBody))]
    [SubtypeDefault(typeof(UnknownBlockBody))]
    public BlockBody Body { get; set; }

    [FieldOrder(3)]
    public int Length2 { get; set; }
}
jefffhaynes commented 1 year ago

It should work. Is it not?

abrasat commented 1 year ago

It works only if the Crc property is placed after the Subtype field. If I put it before (as I need in my application), it throws this exception:

Error serializing member 'Crc'. See inner exception for detail.

InvalidOperationException: Reverse binding not allowed on FieldValue attributes. Consider swapping source and target.

jefffhaynes commented 1 year ago

Yes, crc fields cannot appear before the data they are checking, as this is not strictly serialization.

abrasat commented 1 year ago

Any other way to solve my problem? Maybe using a ValueConverter?

abrasat commented 1 year ago

I tried to use the Crc32 built-in with my example using SerializeWhen and Subtypes (see #225 ). This is the code modification that I did:

    public class ValueDataInfo
    {
        [FieldOrder(0)]
        public ValueBlockType BlockType { get; set; } = ValueBlockType.PlainValue;

        [FieldOrder(1)]
        public UInt32 ParameterId { get; set; }

        [FieldOrder(2)]
        [FieldLength(8)]
        public string Name { get; set; }

        [FieldOrder(3)]
        public UInt32 NrValues { get; set; }

        [FieldOrder(4)]
        public ValueDataType DataTypeId { get; set; } = ValueDataType.Datatype_Invalid;

        [FieldOrder(5)]
        [SerializeWhen(nameof(BlockType), ValueBlockType.PlainValue)]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Double, typeof(DoublePlainValuesDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Float, typeof(FloatPlainValuesDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int16, typeof(Int16PlainValuesDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int32, typeof(Int32PlainValuesDataBody))]
        [SubtypeDefault(typeof(EmptyPlainValueDataBlock))]
        [FieldCrc32(nameof(Crc))]
        public PlainValueDataBlock Block { get; set; }

        [FieldOrder(6)]
        [SerializeWhen(nameof(BlockType), ValueBlockType.ValueWithDescriptor)]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Double, typeof(DoubleValuesWithDescriptorDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Float, typeof(FloatValuesWithDescriptorDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int16, typeof(Int16ValuesWithDescriptorDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int32, typeof(Int32ValuesWithDescriptorDataBody))]
        [SubtypeDefault(typeof(EmptyDescriptorDataBlock))]
        [FieldCrc32(nameof(Crc))]
        public ValueWithDescriptorDataBlock DescriptorBlock { get; set; }

        [FieldOrder(7)]
        public ulong Crc { get; set; }
    }

The following exception is thrown:

Error serializing member 'Crc'. See inner exception for detail. Inner exception: "Reverse binding not allowed on FieldValue attributes. Consider swapping source and target."

If I put the Crc32 attribute only on the first SerializeWhen option it seems to work, no excepion is generated:

...
        [FieldOrder(5)]
        [SerializeWhen(nameof(BlockType), ValueBlockType.PlainValue)]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Double, typeof(DoublePlainValuesDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Float, typeof(FloatPlainValuesDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int16, typeof(Int16PlainValuesDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int32, typeof(Int32PlainValuesDataBody))]
        [SubtypeDefault(typeof(EmptyPlainValueDataBlock))]
        [FieldCrc32(nameof(Crc))]
        public PlainValueDataBlock Block { get; set; }

        [FieldOrder(6)]
        [SerializeWhen(nameof(BlockType), ValueBlockType.ValueWithDescriptor)]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Double, typeof(DoubleValuesWithDescriptorDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Float, typeof(FloatValuesWithDescriptorDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int16, typeof(Int16ValuesWithDescriptorDataBody))]
        [Subtype(nameof(DataTypeId), ValueDataType.Datatype_Int32, typeof(Int32ValuesWithDescriptorDataBody))]
        [SubtypeDefault(typeof(EmptyDescriptorDataBlock))]
        //[FieldCrc32(nameof(Crc))]
        public ValueWithDescriptorDataBlock DescriptorBlock { get; set; }

        [FieldOrder(7)]
        public ulong Crc { get; set; }
...

@jefffhaynes could you please take a look, if related to #225 fix And also some questions: is there any method available to get the calculated Crc32 value after the serialization (for the cases it does not throw an exception)? Can the bound Crc property be marked with the [Ignore] attribute and still get calculated? And is it possible to get the byte-offset of a class property in the serialized byte-array?

jefffhaynes commented 11 months ago

I believe the issue is that the crc can only be used on a single field. If you need to calculate it over multiple fields, simply create a container class for those fields.