Kaitai Struct: A new way to develop parsers for binary structures
kaitai.io
kaitai.io
Paired with C FFI available in most languages, this seems like the nicer solution. It's simpler than generating code for a bunch of high level languages, and more performant
wuffs is much more powerful: it's essentially a safe C dialect that compiles to C, that lets you write an entire codec and know that there aren't any overflows.
The linked-to page says: "Version 0.2. The API and ABI aren't stabilized yet. The compiler undoubtedly has bugs."
There are not many recent commits, and mostly by one developer.
This is not an official Google product, it is just code that happens to be owned by Google> Kaitai Struct is in a similar space, generating safe parsers for multiple target programming languages from one declarative specification. Again, Wuffs differs in that it is a complete (and performant) end to end implementation, not just for the structured parts of a file format. Repeating a point in the previous paragraph, the difficulty in decoding the GIF format isn't in the regularly-expressible part of the format, it's in the LZW compression. Kaitai's GIF parser returns the compressed LZW data as an opaque blob.
> However, unlike hand-written C, Wuffs the Language is safe with respect to buffer overflows, integer arithmetic overflows and null pointer dereferences. A key difference between Wuffs and other memory-safe languages is that all such checks are done at compile time, not at run time. If it compiles, it is safe, with respect to those three bug classes.
Sounds a lot like ZZ. [0] It also sounds much like SPARK Ada, except for transpiling to C.
I’ve also taken some inspiration with a Go library I wrote, restruct:
https://github.com/go-restruct/restruct
… which is a bit like Go’s JSON encoding/decoding library, but with kaitai-like annotations for binary encoding. Check the PNG example to see some of what can be done with it:
https://github.com/go-restruct/restruct/blob/master/formats/...
The main advantage it has over Kaitai at the moment is that it can serialize, which is still not a feature of mainline Kaitai Struct, though it will hopefully be some day.
Actually could not figure out how to parse a binary file/stream containing heterogeneous binary structures from reading the docs. I suppose it is possible?
The problem I'm stuck on now is that since my "Unmarshal" function returns interface{}, I have to type assert for every type of packet in the protocol. There's probably thousands in total. I need some magic that can automatically Unmarshal to a concrete type and pass it to an appropriate handler. gRPC's codegen does this for you. Still working out how best to do this in the constraints of Go.
This is somewhat akin to ASN.1.
My personal feature wish list:
- support writing as well as reading;
- support generating Rust, Julia and Swift code.
- upload button to let users add to a contrib/ folder of existing format descriptions
-define(IP_VERSION, 4).
-define(IP_MIN_HDR_LEN, 5).
DgramSize = byte_size(Dgram),
case Dgram of
<<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
ID:16, Flgs:3, FragOff:13,
TTL:8, Proto:8, HdrChkSum:16,
SrcIP:32,
DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
<<Opts:OptsLen/binary,Data/binary>> = RestDgram,
...
end.
[0] https://www.erlang.org/doc/programming_examples/bit_syntax.h... <<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>
This bit syntax binds `Sz` as an 8-bit length value, `Payload` as `Sz` 8-bit bytes with the remainder in `Rest`.[0] https://www.erlang.org/doc/programming_examples/bit_syntax.h...
decode(<<16#01, Size:8, Payload:Size/binary-unit:8, Rest/binary>>) ->
[{field1, Payload}|decode(Rest)];
decode(<<16#02, Flags:16, Rest/binary>>) when Flags =:= 1 ->
[{field2, true}|decode(Rest)];
decode(<<16#02, Flags:16, Rest/binary>>) ->
[{field2, false}|decode(Rest)].
Anything after the when keyword is a guard condition allowing for some manner of positive/negative “look ahead” (Contrived example, but maybe you get the point).Lib: https://github.com/jam1garner/binrw
Example level (a little slow to load): https://www.smm2-viewer.com/courses/BMV-CN5-4DG
That you reverse engineered Nintendo's API and data model, that you built a lovely and retro visualizer, that you made it a social tool. The technical details. Rust. Wasm.
Everything about this rocks.
Keep it up!
I can't claim credit for the API and reverse engineering of the data model (https://github.com/0Liam/smm2-documentation), standing on the shoulder of giants here. But the original level viewer was a windows app which I couldn't use (there are since a handful of web ports of it); it seemed like an opportunity to learn a few technologies (Rust, WASM). There were a lot of interesting problems, i'll do a proper write-up one day.
I was sad at "[ ] PDF" because I consider that the torture test of any binary parsing framework
- Very old and currently obsolete project target
- As a result, does not use modern data types such as Span<T>
- No utilisation of ArrayPool<T> which is important for things like serialisers where you expect to deal with buffers a lot
- Appears to be a blind Java port given provided code style
This is not acceptable when working with low-level and binary structures which this standard is focused on. Yes, I know, this is an OSS project and therefore instead of complaining here I should have been working on contributing a PR to fix those issues. However, my main concern is that this standard and set of libraries in the current form work against the performance-sensitive nature of working with binary data.
Looking at the code-gen for utf8_string [0] and it's a case of 'thanks, but no thanks'
> std::unique_ptr<std::vector<std::unique_ptr<utf8_codepoint_t>>> m_codepoints;
This is a solution looking for a problem, but I bet it was fun to write.
my $s = Struct("foo",
UBInt8("a"),
UBInt16("b"),
Struct("bar",
UBInt8("a"),
UBInt16("b"),
)
);
my $data = $s->parse("ABBabb");
# $data is { a => 65, b => 16962, bar => { a => 97, b => 25186 } } length u32
data u32[length]
This is a huge problem for traditional parsers since the input modifies the grammar dynamically.It doesn't supported bit per bit reading which I needed, so I had to stop when I realised that.
Do you mean mapping meaning from bit fields?
Kaitai Struct does support bit fields but they have to be aligned on 8 bit boundaries[3]. I can't think of any instance of a binary format anyone would still be using that would fail this condition to align on 8 bit boundaries.
[1] http://www.catb.org/esr/structure-packing/#_bitfields
[2] https://retrocomputing.stackexchange.com/questions/15512/did...
[3] https://doc.kaitai.io/user_guide.html#_bit_sized_integers
Unreal engine encode the network packet like it's a BitStream, when it want to write a boolean for example, it will write a single bit. The following integer won't be aligned.
The poke and other folks had an online conference recently called Binary Tools Summit:
Overall I'm very happy with Kaitai. I've found it to be effective and enjoyable to use.
I can't recommend it enough, even (or especially)if you are a novice. It looks daunting, but I found it surprisingly nice.
Would be cool if you could generate a protocol diagram from this.
Also the structure defenition seems to needs compiling, what if there is change of structure at runtime? Can it handle it?
Is it possible to for an application to have editable structures and KaiTai to parse them without needing to recompile the whole application (I'm speaking here in a java context).
It is great that the spec is YAML because that's quick to get up and running. It was just unfortunate that the expression language is custom string, i.e. not YAML.
It may have improved, but at the time the custom language wasn't unambiguously specified either.
1. It's not possible to read from the file until a multiple byte termination sequence is detected. [1]
2. You can't read sections of a file where the termination condition is the presence of a sequence of bytes denoting the next unrelated section of the file (and you don't want to consume/read these bytes) [2]
3. The WebIDE at the time couldn't handle very large file format specifications such as Photoshop (PSD) [3]
4. Files containing compressed or encrypted sections require a compression/encryption algorithm to be hardcoded into Kaitai struct libraries for each programming language it can output to.
The WebIDE I particularly liked as it makes it easy to get started and share results. I also liked how Kaitai Struct allows easy definition of constraints (simple ones at least) into the file format specification so that you can say "this section of the file shall have a size not exceeding header.length * 2 bytes".
Some alternative binary file format specification attempts for those interested in seeing alternatives, each with their own set of problems/pros/cons:
1. 010 Editor [4]
2. Synalysis [5]
3. hachoir [6]
4. DFDL [7]
[1] https://github.com/kaitai-io/kaitai_struct/issues/158
[2] https://github.com/kaitai-io/kaitai_struct/issues/156
[3] https://raw.githubusercontent.com/davidhicks/kaitai_struct_f...
[4] https://www.sweetscape.com/010editor/repository/templates/
[5] https://github.com/synalysis/Grammars
[6] https://github.com/vstinner/hachoir/tree/main/hachoir/parser
What does this refer to:
> 4. Files containing compressed or encrypted sections require a compression/encryption algorithm to be hardcoded into Kaitai struct libraries for each programming language it can output to.
Text mentions "Kaitai", but references
4. DFDL [7]
or
[4] https://www.sweetscape.com/010editor/repository/templates/
is unrelated.
The comment about compressed and encrypted sections of files relates to the limited built-in algorithms that can be found at [1]. Any additional algorithms, for example, AES-XTS, would need to be added as custom processes and a corresponding function added for each language runtime Kaitai supports.
"DFDL [7]" refers to the reference "[7] https://github.com/DFDLSchemas/".
"4. ..." doesn't have any special meeting
[1] https://raw.githubusercontent.com/kaitai-io/kaitai_struct_co...