Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust support #22

Open
LogicAndTrick opened this issue Aug 16, 2016 · 98 comments
Open

Rust support #22

LogicAndTrick opened this issue Aug 16, 2016 · 98 comments

Comments

@LogicAndTrick
Copy link
Collaborator

Adding this so it can be tracked, but I plan on doing most of this myself. I've not yet had a need for Rust but I've been wanting to try it out anyway, figured this is a good excuse.

I've created a runtime for Rust: https://github.com/kaitai-io/kaitai_struct_rust_runtime

I'll be adding matching tests and a compiler for Rust in the future.

Note that I don't know Rust at all so I'm learning as I go. If anybody has coded with it before I would very much appreciate advice, suggestions, or even a code review of the runtime!

@GreyCat
Copy link
Member

GreyCat commented Aug 16, 2016

Wow, that's cool :) May be you should post it to some Rust-related mailing lists / communities, to attract more peer review? I don't know a thing about Rust myself ;)

@LogicAndTrick
Copy link
Collaborator Author

After working with the language a bit, I've come to realise how difficult of a task this would be. Rust is certainly an interesting language to work with, but it comes with a huge list of new challenges that don't exist in other languages. I don't see myself being able to overcome them, so I'm going to quit.

There's a whole stack of architecture questions that are easy to answer in other languages, but actually very complex in Rust. The design of code generated by KS follows the easy patterns because that's how pretty much every other language works. But Rust is unique in many areas.

Rust has what they call a foreign function interface which lets you interact with C libraries, perhaps that's a more reasonable approach for Rust. But for now, I'm going to close this because I'm not able to make any more progress.

(I enjoyed experimenting with Rust: it sort of feels like a modern approach to C. The memory safety syntax is very complex for beginners, but the rest of language is quite simple and expressive. The tools are a pleasure to use as well. Would definitely recommend anyone to spend a weekend with it if they've ever been curious about it.)

@GreyCat
Copy link
Member

GreyCat commented Aug 18, 2016

Maybe someone would return to Rust implementation in future, so I thought it might be interesting to document your findings or at least map out possible obstacles on the path. What does the language lack that impedes KS implementation? Something about the type system? Memory management? Strict compile-time checks? Lack of "everything is an expression"? Something else?

@LogicAndTrick
Copy link
Collaborator Author

The main pain point I discovered is around the references to io, root, and parent in each object. If they could be removed somehow, I think I could probably get everything else going. From what I've found online, root/parent/self references require some pretty complex type chains, and the generated code would probably be pretty crazy as well. I don't see myself ever becoming proficient enough in Rust to be able to get that going correctly, and even if you could do it, the result would probably not be something that anybody would want to actually use.

If instance methods required these three pointers to be passed as arguments, then it might be more possible. For example:

// hard to do
fn get_calculated_instance(self) { ... }

// probably easier, but consumer must pass root/parent manually
fn get_calculated_instance(self, io: Stream, parent: Struct, root: Struct) { ... }

From what it seems, Rust is best approached by creating an architecture that specifically fits Rust's "memory safety first" style. Trying to modify an existing architecture so that it works in Rust is very difficult.

@GreyCat
Copy link
Member

GreyCat commented Aug 19, 2016

That's kind of strange. I guess that Rust has something that resembles an object (i.e. bundle of data + methods) and I don't see any difference in storing some "useful" data, i.e. attributes / instances vs storing a reference (pointer?) to io object that should be used for structure parsing, or parent, or root.

But that said, I know almost nothing about Rust, so I can only make theories.

@GreyCat
Copy link
Member

GreyCat commented Jun 8, 2017

I'm reorganizing all "new language support" issues, adding relevant tag. I'll reopen this one, as a few people asked for Rust support again, and it's better to keep it open, so it would be easier to find and people won't create duplicates again and again.

@GreyCat GreyCat reopened this Jun 8, 2017
@berkus
Copy link

berkus commented Jul 24, 2017

Yes, please, very much need Rust support from Kaitai!

Happy to help with discussions and possible implementation if needed.

@GreyCat
Copy link
Member

GreyCat commented Jul 24, 2017

@berkus You probably know of http://doc.kaitai.io/new_language.html — let's just follow. I'd be happy to launch a Rust-targeting branch as soon as we'll get basic hello_world.rs and basic tests for it.

@berkus
Copy link

berkus commented Jul 24, 2017

No, this is new to me - I just started actually using Kaitai today, loving it so far.

Will take a look!

@LogicAndTrick
Copy link
Collaborator Author

I'm going to start tentatively taking a second look at this one because the second edition of the Rust book has a dedicated chapter on reference cycles which might help me solve the roadblocks I was hitting last year. I still have no experience with actual proper Rust code so if anybody has more experience with the language, I'd love some assistance!

Question for @GreyCat: I've manually assembled the code for a hello_world.rs file which passes basic tests, where would I put it? I don't think it's ready for CI scripts at this point, I still have to figure out the best way to separate the test project from the code project.

For the time being, it'd be good to get some feedback so here's what I've got so far for hello_world:

extern crate kaitai_struct;

use kaitai_struct::{Stream, Struct};

use std::io::{Cursor, Read, Seek};
use std::fs::File;

struct HelloWorld {
    one: u8,
}

impl Struct for HelloWorld {

    // Rust requires all fields to be initialised so we auto-generate an empty ctor
    fn empty() -> HelloWorld {
        HelloWorld { one: 0 }
    }

    // Passes any io::Error through to the caller using the ? operator
    fn from_file(path: &str) -> std::result::Result<HelloWorld, std::io::Error> {
        let mut buf = File::open(path)?;
        Ok(HelloWorld::new(
            &mut buf,
            &None::<Box<Struct>>,
            &None::<Box<Struct>>,
        ))
    }

    // Constructor from a stream
    // Box<> References to parent and root are temporary and the types will change.
    fn new<S: Stream>(
        stream: &mut S,
        parent: &Option<Box<Struct>>,
        root: &Option<Box<Struct>>,
    ) -> HelloWorld {
        let mut this = HelloWorld::empty();
        this.read(stream, &parent, &root);
        this
    }

    // Read takes a reference to stream/parent/root, this may change later.
    fn read<S: Stream>(
        &mut self,
        stream: &mut S,
        parent: &Option<Box<Struct>>,
        root: &Option<Box<Struct>>,
    ) {
        self.one = stream.read_u1().unwrap();
    }
}

#[cfg(test)]
mod tests {
    use kaitai_struct::Struct;
    use HelloWorld;

    #[test]
    fn test_hello_world() {
        let hw = HelloWorld::from_file("./src/fixed_struct.bin")
            .expect("File not found!");
        assert_eq!(hw.one, 0x50);
    }
}

I'm going to start working out the parent and root references, if I can figure that out then I'm hoping that it should be pretty basic stuff after that.

@GreyCat
Copy link
Member

GreyCat commented Sep 2, 2017

Looks really cool :)

As for code location, I'd just leave it here at the moment. It's not like putting it somewhere into the tests repo would help anything — we need test specs, we need the parser code to be generated, etc, so the hand-written generation result doesn't really help much in terms of CI or stuff like that.

May be we could ask some Rust online communities for some input / suggestions on this one?

@berkus
Copy link

berkus commented Sep 25, 2017

I've implemented some parts in Rust, inspired by kaitai cpp generator here.

Maybe this gives you new ideas (matching .ksy file is here)

@LogicAndTrick
Copy link
Collaborator Author

Neat, you wouldn't by any chance know a good way to represent the parent/root references in Rust? I have looked at it a few times and it just seems to create a painful chain of types like Option<Weak<RefCell<Rc<T>>>> and the interface it creates seems pretty unreasonable for somebody to want to use.

@berkus
Copy link

berkus commented Sep 26, 2017

It's pretty painful because of borrow checker. Usually peeps do it via a Vec and integer indices pointing to parent or children from a T.

Probably some rust tree package like ego_tree or idtree may give you help?

Otherwise I believe RefCell<Rc<T>> is the only way (but i'm no expert - been doing rust for barely two weeks now)

@CWood1
Copy link

CWood1 commented Apr 5, 2018

Since I don't know the status of this, I decided to start an alternate implementation. Still very much a WIP, but hello world now successfully compiles to this:

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

use std::{
    option::Option,
    boxed::Box,
    io::Result
};

use kaitai_struct::{
    KaitaiStream,
    KaitaiStruct
};

pub struct HelloWorld {
    pub one: u8,
}

impl KaitaiStruct for HelloWorld {
    fn new<S: KaitaiStream>(stream: &mut S,
                            _parent: &Option<Box<KaitaiStruct>>,
                            _root: &Option<Box<KaitaiStruct>>)
                            -> Result<Self>
        where Self: Sized {
        let mut s = Self {
            one: 0,
        };

        s.read(stream, _parent, _root)?;

        Ok(s)
    }

    fn read<S: KaitaiStream>(&mut self,
                             stream: &mut S,
                             _parent: &Option<Box<KaitaiStruct>>,
                             _root: &Option<Box<KaitaiStruct>>)
                             -> Result<()>
        where Self: Sized {
        self.one = stream.read_u1()?;

        Ok(())
    }
}

I've tested this and confirmed it all working. My next step is to port some of the test suite over, writing test harnesses for Rust. From there, I can get them all passing, one by one.

@GreyCat
Copy link
Member

GreyCat commented Apr 5, 2018

@CWood1 Thanks, looks really cool! Note that:

  • Nowadays there is a roadmap for implementation of new languages: looks like this, and can be generated using these scripts.

  • There is really no need to port majority of test specs manually: one can implement expression support for target language, and then harness the power of KST (see Generating tests with KS expressions (KST) #253). The translator project is here and once you'll add support for the new language there, porting many tests is a breeze, as you get it to generate specs automatically.

@CWood1
Copy link

CWood1 commented Apr 5, 2018

Oh wow, that all looks much easier than I'd anticipated! Is there any documentation on generating and running the tests for a given language automatically? Tried to figure it out from travis.yml, but that does strange things with packages which won't work on my Arch installation.

Thanks for the help, I'll take a look at getting the test translator ported next.

@GreyCat
Copy link
Member

GreyCat commented Apr 5, 2018

Unfortunately, not now, as this whole thing was literally implemented a month or so ago. Travis yaml won't help much here, as specs are to autogenerated on the fly during normal build process.

To run test translator, you'd generally want to:

  1. Go to compiler dir
  2. sbt publishLocal to build compiler jars and "publish" it into local repo where translator project could find it
  3. Go to translator dir
  4. sbt run (or load that project into IDE and run it there) to try autogenerating all possible specs:
    • Sources for KST specs are in spec/kst
    • Results would be in spec/kst/out/$LANG
  5. Check generated specs and move it manually to spec/$LANG after checking that it actually does the job

@CWood1
Copy link

CWood1 commented Apr 7, 2018

So, I've got the translator outputting tests now. Few compilation issues here and there due to incorrect syntax, nothing too extreme.

Next question: how exactly does one run the compiler against the test suite, then the tests against the compiled specs? Once I get these compiler errors resolved, that's my next goal is to have 50something failing tests I can then make pass.

@GreyCat
Copy link
Member

GreyCat commented Apr 8, 2018

@CWood1 Could you show me what exactly have you achieved so far?

@CWood1
Copy link

CWood1 commented Apr 8, 2018

Okay, where we're at (I'll be pushing all this shortly, soon as I've got a couple compiler errors ironed out), I've got a rust library project under spec/rust. Inside of there, tests/ houses all of the tests.

Therefore, open questions:

  • How do I get the compiler to run, and output the results into spec/rust/src?
  • Can I script updating spec/rust/src/lib.rs, to publicly declare all of the compiled modules in a way the tests can access?
  • Is there a framework by which I can automatically run the above, and then invoke cargo test?

Doing the above manually is relatively easy, though tedious. Are there ways by which I can script them, under existing frameworks? Or would that need to be developed from scratch?

@GreyCat
Copy link
Member

GreyCat commented Apr 8, 2018

How do I get the compiler to run, and output the results into spec/rust/src?

There is a huge mess with terminology we have here ;)

If by "compiler", you mean ksc (i.e. app that translates ksy files into parsing source code in target language), then normally, it is run with build-formats script, it runs the compiler on all formats for all target languages, and outputs results into compiled/$LANG.

If by "compiler", you mean kst (i.e. app that translates kst files into test specs in target languages), then it is right now runnable by invoking sbt run in its dir (or, equivalently, launching its main class in IDE).

I suspect there's slight misunderstanding on what needs to be done. Actually we have 3 "places" to update:

That's why I asked you to show what you've got, so I could understand which of these 3 things you were working on :)

Can I script updating spec/rust/src/lib.rs, to publicly declare all of the compiled modules in a way the tests can access?

No equivalent of these exists so far in any other languages. C++ might benefit from similar automation, but so far we've just added all tests manually to CMake project. This is actually a somewhat sloppy moment, but for compiled languages you don't usually have an option to go with "build everything that would build, and run the tests with everything's that we've built". Normally (for C#, C++, Java, Go), a single broken file ruins the build, so we employ various tricks to reduce that risk. Having a manually maintained list of tests that "should run ok" is a first step to mitigate that problem.

Is there a framework by which I can automatically run the above, and then invoke cargo test?

ci-all script invokes all ci-* scripts in sequence. We should probably create ci-rust, which would run thte tests and deliver some kind of (ideally, JUnit-like XML) report to test_out/.

@bspeice
Copy link

bspeice commented Apr 24, 2019

Architecture question: current implementations don't seem to handle "we ran out of data" well. Is it just up to the user to reset the stream position in these scenarios?

@CWood1
Copy link

CWood1 commented Apr 24, 2019 via email

@bspeice
Copy link

bspeice commented Apr 24, 2019

On stream recovery: had a quick conversation with @GreyCat on Gitter, and that was where I came to; it's probably up to the user to save the stream position (using _io.pos or similar) before parsing, and then seeking back to that if desired. Think we're both agreed.

And I spoke incorrectly earlier on lifetimes, the borrow checker thoroughly convinced me of the error of my ways. What I should have said was the lifetime of the struct must be bounded by the lifetime of the stream (that is, the stream must live at least as long as the struct). I'm against _root owning the stream, as very often the same stream will be used to parse multiple root structures (network streams). In general though, my argument would be that if you want to separate the struct and stream lifetimes, we're better off deriving Clone and allowing users to worry about the heap when necessary, the struct run-time should be zero-copy as much as possible (unavoidable exceptions are things like conditional-repeat blocks)

@CWood1
Copy link

CWood1 commented Apr 24, 2019 via email

@bspeice
Copy link

bspeice commented Apr 24, 2019

Yeah, returning across lib boundaries is going to be painful because the structs I've been creating don't own their data (unlike other Kaitai implementations). And now that I think about it, #[derive(Clone)] won't work because they'd just copy the _parent and _root references.

My personal use case is interested in having the zero-copy style, but maybe we make that a compile option to choose between owning and non-owning codegen?

EDIT: It should be noted that structs owning their data would likely still have issues with lib boundaries because of _parent and _root (roots can own children, but children still need references back to parents and root), but that may be something not worth worrying about yet.

@CWood1
Copy link

CWood1 commented Apr 24, 2019 via email

@bspeice
Copy link

bspeice commented Apr 24, 2019

On compile options, baked into the Kaitai compiler, it basically switches structs from using &[u8] in the non-owning case to Vec<u8> in the owning case.

For nom's no_std support - sure looks like it. no_std would be difficult (both process_* functions in the runtime and conditional repeats in read need heap allocation), but we can feature-gate those and let users deal with the compilation issues.

Finally, having learned my lesson on the borrow checker coming out with some nasty surprises, I've got a pretty basic test case available here that doesn't yet compile; if we can get this working (currently has issues with proving validity of _parent and _root) then I think we're in the clear. It may require some API changes (giving _root and _parent references to read instead of storing in the struct seems like a good idea), but still making progress.

@CWood1
Copy link

CWood1 commented Apr 24, 2019 via email

@bspeice
Copy link

bspeice commented Apr 25, 2019

The more I work with it, the more I'm convinced we have to initialize members in the parent read. Because self-referencing is such an issue (to the point where I'm not sure it's possible) I think the current class needs to own its children, and the read() signature becomes something along the lines of read(stream: &S, parent: Option<&P>, root: Option<&R>) -> Result<Self>, which looks strangely like how nom handles things (:wink:). That said, adding a read(stream: &S) that sets things up for the root-level structs and then calls each child read would likely be pretty easy to implement on top.

Should have some interesting stuff drawn up tomorrow.

@bspeice
Copy link

bspeice commented Apr 25, 2019

Alright, new issues. Because instance definitions can use _parent and _root, and we compute instances lazily, we've got difficulties with lifetimes. It's not a pretty API, but my plan for now is to always require _root and _parent being passed into the instance retrieval method, but we'll need to figure out a better API for the future.

@CWood1
Copy link

CWood1 commented Apr 25, 2019 via email

@bspeice
Copy link

bspeice commented Apr 25, 2019

The example I posted yesterday is good enough to demonstrate the issue. There's two things that are problematic:

  1. In the version where parents only store references to children (ex. websocket message storing a vector of its dataframes), we can't create new child structs during read() because they'll be dropped at the end. Fairly straight-forward, nobody owns the children so they disappear.

  2. If you modify it such that parents do own their children, there are a ton of issues that show up during read() that I admittedly don't quite understand.

The example read method:

    fn read<'s: 'a, S: KStream>(&mut self, stream: &'s mut S) -> Result<(), KError<'s>> {
        self.bytes = stream.read_bytes(1)?;
        let mut child = TestChildStruct::new(Some(self), Some(self.root()));
        child.read(stream)?;
        self.child = Some(&child);
        Ok(())
    }

The first issue has to do with stream being delivered to the child, which I admit I'm a bit surprised by, since I would've expected this is legal:

error[E0499]: cannot borrow `*stream` as mutable more than once at a time
  --> tests/lifetime_validation.rs:38:20
   |
34 |     fn read<'s: 'a, S: KStream>(&'a mut self, stream: &'s mut S) -> Result<(), KError<'s>> {
   |             -- lifetime `'s` defined here
35 |         self.bytes = stream.read_bytes(1)?;
   |                      ---------------------
   |                      |
   |                      first mutable borrow occurs here
   |                      returning this value requires that `*stream` is borrowed for `'s`
...
38 |         child.read(stream)?;
   |                    ^^^^^^ second mutable borrow occurs here

The second definitely makes sense as being illegal. When we create a new child struct, we borrow self and give it to the child (which also effectively borrows self.child). We then assign self.child later with the owned value which causes issues, because that assignment needs to borrow self as well.

error[E0506]: cannot assign to `self.child` because it is borrowed
  --> tests/lifetime_validation.rs:39:9
   |
20 | impl<'a> KStruct<'a> for TestRootStruct<'a> {
   |      -- lifetime `'a` defined here
...
37 |         let mut child = TestChildStruct::new(Some(self), self.root);
   |                         -------------------------------------------
   |                         |                         |
   |                         |                         borrow of `self.child` occurs here
   |                         argument requires that `*self` is borrowed for `'a`
38 |         child.read(stream)?;
39 |         self.child = Some(child);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `self.child` occurs here

There's a total of seven errors that show up in the mini example I gave. I'm simultaneously glad because that example is catching a lot of issues early, but they're still problematic.

@bspeice
Copy link

bspeice commented Apr 26, 2019

OK, lots of updates and some good news. I have a definition that passes the borrow checker, works for all the simple structures I can think of, and lets us scale into the future. I'm documenting how I went through the process in case anyone else is crazy enough to try re-building this with a different structure in the future.

First, a super-simple sketch of what we're ultimately after. We need to read things in, have nested structures (and thus deal with ownership) while maintain zero-copy access to the underlying stream.

From there, we try to add a KStream trait, and pass that as mutable to the readers. That doesn't go so well because we can't give the child struct access to the stream while we maintain a mutable borrow ourselves, and fancy scoping doesn't help at all.

So, we need to make the streams immutable. Thankfully, Rust has a trick for dealing with this - the RefCell type. If we move the stream state into the cell, we can stay no-std compliant (and no heap allocation needed). We'll have to do a lot of borrow and borrow_mut calls in the stream definition, but I'd rather pay that cost than have structs own their data and have to allocate everywhere (no actual benchmarking was done to support this conclusion, I'm just imitating other well-known libraries).

Finally, we add in the definition of KStruct so that all structs have a common language for instantiating/reading each other.

The result is the API available here: kaitai_rust. It includes a couple things not available in the Playground (a definition of KError for example), and a fixed version of the example I posted previously.

Now, there are a couple of issues that need to be addressed before it's considered done.

  1. How do we handle instance definitions? I need to actually prove it out, but as long as we provide _root and _parent references the same way as in our read definition, I don't expect we'll run into issues.

  2. How do we handle process directives? I'd like the structs to keep an owned buffer that can be provided, rather than how existing implementations allocate new buffers. This is a codegen issue though, not lifetimes, so not terribly worrisome.

  3. Support for _root.io directives in instance definitions? I'm not totally familiar with what's going on here, so not sure how hard it'd be, but it's also not super-prevalent so I'm not treating as a big concern at the moment.

Those concerns aside, it's not unreasonable to think that there's a Rust implementation on the horizon.

@bspeice
Copy link

bspeice commented May 1, 2019

Big news! A simple Websockets-based example is now running, so at the very least, a PoC compiler is ready. Following up from the last post, instance definitions are indeed handled by passing in _root and _parent, I need to see if there's a better API we can put on top of that, but they're functional. process directives are still up in the air. And _root.io shouldn't be too bad - from what I can tell, it's just some extra function calls around pos(), seek(), etc. so I don't think there will be too many worries.

Now, it's time for a couple last cleanup things. First, the code isn't something I'm super proud of, so a refactoring pass is in order. Second, we don't have a good way of handling calculated endianness, similar to Go. Finally, I have no idea what else will break as soon as we start trying other types, the Websocket message is a reasonably simple format.

Still, progress is progress.

@GreyCat
Copy link
Member

GreyCat commented May 1, 2019

@CWood1 @bspeice Can I ask you guys to still devote some time to get us visibility of how well we fare in tests counts in CI?

Right now I have a feeling like a big chunk of tremendous work done by @bspeice is going virtually unnoticed due to (1) no consensus on merging, (2) no visibility in CI.

@bspeice
Copy link

bspeice commented May 1, 2019

Definitely. Now that I have a design that I know will work well, I think getting CI on it makes a ton of sense. For merge consensus, I'll delegate to you guys - is it worth looking to integrate the compiler/runtime I've been working on and then fix things in CI as we go? Or would we rather have testing done beforehand?

@bspeice
Copy link

bspeice commented May 10, 2019

@CWood1 / @GreyCat - Can you guys run me through how to generate the tests? I need to make some changes to the translator as part of the new API, but I'm unable to figure out how to run it locally.

@bspeice
Copy link

bspeice commented May 10, 2019

@GreyCat followed up via chat; sbt publishLocal for the compiler, then sbt run for the translator.

@sealmove
Copy link
Member

@CWood1 / @GreyCat - Can you guys run me through how to generate the tests? I need to make some changes to the translator as part of the new API, but I'm unable to figure out how to run it locally.

#626

Mingun added a commit to Mingun/kaitai_struct that referenced this issue Aug 14, 2020
@ShadowJonathan
Copy link

Why hasn't this been implemented yet? Seeing rust absent from the gallery of other languages makes me not exactly inclined to use this for anything.

krisutofu pushed a commit to krisutofu/kaitai_struct that referenced this issue Jan 2, 2022
@rljacobson
Copy link

Why hasn't this been implemented yet?

It looks to me like it has. Maybe @CWood1 or @GreyCat can tell us the status. When I tried to run it I got the following:

$ ksc -t rust png.ksy
png: /:
	error: AnyType (of class io.kaitai.struct.datatype.DataType$AnyType$)

@orangecms
Copy link

In case anyone seeks to improve the Rust support or its documentation, funding is available:
https://nlnet.nl/project/Kaitai-Rust/

@jonstewart
Copy link

jonstewart commented May 6, 2024 via email

@generalmimon
Copy link
Member

The most recent efforts to add Rust were from @Agile86 and @revitalyr in these pull requests:

The grant from NLnet has multiple parts, one of them is to follow up on these efforts with the goal of integrating them to Kaitai Struct. This includes adding missing pieces of test infrastructure (partial builder), reviewing the code, aligning it with KS standards and conventions, fixing issues etc.

@jonstewart
Copy link

Yes, @Agile86 and @revitalyr are the developers I'm thinking about. :-D I'm sure they would appreciate the funding opportunity to complete their work on Rust support, and the reasons we've pursued it over the years are largely the same as stated by NGIO Entrust. We're using Kaitai for building digital forensics parsers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests