How should I structure a C# application that reads & writes binary data?

I have to read and write binary “chunks” of approximately 1Mb each. The data can come in the form of a stream or a in-memory byte[].

Normally I would use a Struct with a fixed layout, but there are a few properties of this binary data that are making this harder:

  1. Byte 4 describes the “version” of the chunk
  2. Byte 5 starts either as variable array of sub-chunks or as a human readable blob
  3. Each sub-chunk has a version, and has a variable array of data within it.
  4. The endian-ness of the data is inconsistent

How should I structure a program that needs to handle binary data thats in this format?

4

I once had to implement a versioned de/serialization abstractions similarly to what you’re describing before, I’ll detail what I (can remember I) did:

I created an unversioned form of the objects that were to be serialized/deserialized which just decorated the latest version so all consuming code always pointed to the versionless ones which were always the latest versions; this way when new versions came out I didn’t have to change any consuming code, just those versionless abstractions had to be pointed to the new versions.

So let’s say an example like..

public class Person : V4.Person { }

Then in the implementation, I had each versioned object upconvert on deserialization by passing down to older until one of them recognized the version so it was something like..

public class V4.Person : V3.Person
{
    public string SomethingNewInV4 { get; set; }

    public static Person FromBytes(BinaryReader personReader)
    {
        Person person = base.FromBytes(personReader);

        if (person.Version >= 4) // If this version or newer, it will have the SomethingNewInV4 property, deserialize it.
        {
            int stringLength = personReader.ReadInt();
            person.SomethingNewInV4 = personReader.ReadString(stringlength);
        }
        else
        {
            person.SomethingNewInV4 = "default string used when upconverting from V3";
        }

        return person;
    }
}

In this way, each new version is just a modification of the previous, inheriting the previous version and adding (or hiding) parts from it, and using it’s serialization/deserialization which it then adds onto it’s particular parts special to it’s version. There is the possibility for a breaking change in the chain but that just means that particular version may have to reimplement serialization/deserialization from scratch including all the intelligence for branching if previous versions should do it or not, but scenarios like that are likely minimal.

The versionless facade in front ensures all new serializations are serialized in the latest format, and all old deserializations start at the top of the stack which will call down to the bottom through inheritance, and each version will upconvert from there, so that versionless facade redirecting to the latest is necessary (unless each time a new version comes out you want to update all consuming code).

V1 will have to do the version picking and full deserialization etc, but above that each version will check which version and choose whether the data for it is there or it needs to fake it up.

When I did this, the subchunking you’re referring to was totally there as well and I handled this by having this same layout for each subchunk type, so you could have Person’s deserialization call to Arm’s deserializer when it got to the Arm chunk, and if it had a numeral in front counting the Arms it would use that to deserialize in a loop. Each versioned subchunk needs a versionless front piece where all the main chunks call to, so V4.Person would call to the versionless Arm’s deserializer which may point to V3 if that’s the latest Arm, or etc.

Last point: I strongly suggest memory stream for this, I remember going back and forth on this and realizing the byte array was terrible because the forward movement meant I had to pass pointers around between versions, with the memory stream version 1 takes all the initial V1 data, V2 takes all the V2 data which is layered after that, and so on and so forth without needing to maintain the pointer to your current position in the array.

Your problem is almost step by step identical to the one I faced and solved in this manner, it worked pretty well, hope this helps! To make things a little easier I made extension methods on BinaryReader for some of the types that pointed to the versionless stuff so there would be like

public Arm ReadArm(this BinaryReader target) { return Arm.FromBytes(target); }

then in person you would have..

int numberOfArms = personReader.ReadInt();
List<Arm> arms = new List<Arm>();
for(int i = 0; i < 1; i++) arms.Add(personReader.ReadArm());

I made it fit with BinaryReader’s normal naming, don’t remember if it’s Read or To.. Either way it was a handy shortcut.

I wonder if some sort of lexer/parser set-up would be in order, since:

  • it seems like you’re going from an input stream (of some sort) to a tree-like structure (if I’m reading things right)

  • it would be a way to do the back-tracking you mentioned you might need to do

  • it could probably handle some of the endianness uncertainty/switching you might encounter

I don’t know if you could use one that’s pre-written in this use case, but it might be a place to start with thinking about structuring things.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật