ruby_marshal: C++/STL parsing library

Ruby's Marshal module allows serialization and deserialization of many standard and arbitrary Ruby objects in a compact binary format. It is relatively fast, available in stdlibs standard and allows conservation of language-specific properties (such as symbols or encoding-aware strings).

Feature-wise, it is comparable to other language-specific implementations, such as:

From internal perspective, serialized stream consists of a simple magic header and a record.

KS implementation details

License: CC0-1.0

This page hosts a formal specification of ruby_marshal using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.ruby_marshal", std::ifstream::binary);
    #include <sstream>
    
    std::istringstream is(str);
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
  2. We need to wrap our input stream into Kaitai stream:
    #include <kaitai/kaitaistream.h>
    
    kaitai::kstream ks(&is);
  3. And finally, we can invoke the parsing:
    ruby_marshal_t data(&ks);

After that, one can get various attributes from the structure by invoking getter methods like:

data.version() // => get version

C++/STL source code to parse ruby_marshal

ruby_marshal.h

#ifndef RUBY_MARSHAL_H_
#define RUBY_MARSHAL_H_

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

#include "kaitai/kaitaistruct.h"

#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 7000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required"
#endif

/**
 * Ruby's Marshal module allows serialization and deserialization of
 * many standard and arbitrary Ruby objects in a compact binary
 * format. It is relatively fast, available in stdlibs standard and
 * allows conservation of language-specific properties (such as symbols
 * or encoding-aware strings).
 * 
 * Feature-wise, it is comparable to other language-specific
 * implementations, such as:
 * 
 * * Java's
 *   [Serializable](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html)
 * * .NET
 *   [BinaryFormatter](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter)
 * * Python's
 *   [marshal](https://docs.python.org/3/library/marshal.html),
 *   [pickle](https://docs.python.org/3/library/pickle.html) and
 *   [shelve](https://docs.python.org/3/library/shelve.html)
 * 
 * From internal perspective, serialized stream consists of a simple
 * magic header and a record.
 * \sa Source
 */

class ruby_marshal_t : public kaitai::kstruct {

public:
    class ruby_array_t;
    class bignum_t;
    class ruby_struct_t;
    class ruby_symbol_t;
    class packed_int_t;
    class pair_t;
    class instance_var_t;
    class record_t;
    class ruby_hash_t;
    class ruby_string_t;

    enum codes_t {
        CODES_RUBY_STRING = 34,
        CODES_CONST_NIL = 48,
        CODES_RUBY_SYMBOL = 58,
        CODES_RUBY_SYMBOL_LINK = 59,
        CODES_CONST_FALSE = 70,
        CODES_INSTANCE_VAR = 73,
        CODES_RUBY_STRUCT = 83,
        CODES_CONST_TRUE = 84,
        CODES_RUBY_ARRAY = 91,
        CODES_PACKED_INT = 105,
        CODES_BIGNUM = 108,
        CODES_RUBY_HASH = 123
    };

    ruby_marshal_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, ruby_marshal_t* p__root = 0);

private:
    void _read();

public:
    ~ruby_marshal_t();

    class ruby_array_t : public kaitai::kstruct {

    public:

        ruby_array_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~ruby_array_t();

    private:
        packed_int_t* m_num_elements;
        std::vector<record_t*>* m_elements;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:
        packed_int_t* num_elements() const { return m_num_elements; }
        std::vector<record_t*>* elements() const { return m_elements; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * \sa Source
     */

    class bignum_t : public kaitai::kstruct {

    public:

        bignum_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~bignum_t();

    private:
        uint8_t m_sign;
        packed_int_t* m_len_div_2;
        std::string m_body;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:

        /**
         * A single byte containing `+` for a positive value or `-` for a negative value.
         */
        uint8_t sign() const { return m_sign; }

        /**
         * Length of bignum body, divided by 2.
         */
        packed_int_t* len_div_2() const { return m_len_div_2; }

        /**
         * Bytes that represent the number, see ruby-lang.org docs for reconstruction algorithm.
         */
        std::string body() const { return m_body; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * \sa Source
     */

    class ruby_struct_t : public kaitai::kstruct {

    public:

        ruby_struct_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~ruby_struct_t();

    private:
        record_t* m_name;
        packed_int_t* m_num_members;
        std::vector<pair_t*>* m_members;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:

        /**
         * Symbol containing the name of the struct.
         */
        record_t* name() const { return m_name; }

        /**
         * Number of members in a struct
         */
        packed_int_t* num_members() const { return m_num_members; }
        std::vector<pair_t*>* members() const { return m_members; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * \sa Source
     */

    class ruby_symbol_t : public kaitai::kstruct {

    public:

        ruby_symbol_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~ruby_symbol_t();

    private:
        packed_int_t* m_len;
        std::string m_name;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:
        packed_int_t* len() const { return m_len; }
        std::string name() const { return m_name; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * Ruby uses sophisticated system to pack integers: first `code`
     * byte either determines packing scheme or carries encoded
     * immediate value (thus allowing smaller values from -123 to 122
     * (inclusive) to take only one byte. There are 11 encoding schemes
     * in total:
     * 
     * * 0 is encoded specially (as 0)
     * * 1..122 are encoded as immediate value with a shift
     * * 123..255 are encoded with code of 0x01 and 1 extra byte
     * * 0x100..0xffff are encoded with code of 0x02 and 2 extra bytes
     * * 0x10000..0xffffff are encoded with code of 0x03 and 3 extra
     *   bytes
     * * 0x1000000..0xffffffff are encoded with code of 0x04 and 4
     *   extra bytes
     * * -123..-1 are encoded as immediate value with another shift
     * * -256..-124 are encoded with code of 0xff and 1 extra byte
     * * -0x10000..-257 are encoded with code of 0xfe and 2 extra bytes
     * * -0x1000000..0x10001 are encoded with code of 0xfd and 3 extra
     *    bytes
     * * -0x40000000..-0x1000001 are encoded with code of 0xfc and 4
     *    extra bytes
     * 
     * Values beyond that are serialized as bignum (even if they
     * technically might be not Bignum class in Ruby implementation,
     * i.e. if they fit into 64 bits on a 64-bit platform).
     * \sa Source
     */

    class packed_int_t : public kaitai::kstruct {

    public:

        packed_int_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~packed_int_t();

    private:
        bool f_is_immediate;
        bool m_is_immediate;

    public:
        bool is_immediate();

    private:
        bool f_value;
        int32_t m_value;

    public:
        int32_t value();

    private:
        uint8_t m_code;
        uint32_t m_encoded;
        bool n_encoded;

    public:
        bool _is_null_encoded() { encoded(); return n_encoded; };

    private:
        uint8_t m_encoded2;
        bool n_encoded2;

    public:
        bool _is_null_encoded2() { encoded2(); return n_encoded2; };

    private:
        ruby_marshal_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        uint8_t code() const { return m_code; }
        uint32_t encoded() const { return m_encoded; }

        /**
         * One extra byte for 3-byte integers (0x03 and 0xfd), as
         * there is no standard `u3` type in KS.
         */
        uint8_t encoded2() const { return m_encoded2; }
        ruby_marshal_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    class pair_t : public kaitai::kstruct {

    public:

        pair_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~pair_t();

    private:
        record_t* m_key;
        record_t* m_value;
        ruby_marshal_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        record_t* key() const { return m_key; }
        record_t* value() const { return m_value; }
        ruby_marshal_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    /**
     * \sa Source
     */

    class instance_var_t : public kaitai::kstruct {

    public:

        instance_var_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~instance_var_t();

    private:
        record_t* m_obj;
        packed_int_t* m_num_vars;
        std::vector<pair_t*>* m_vars;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:
        record_t* obj() const { return m_obj; }
        packed_int_t* num_vars() const { return m_num_vars; }
        std::vector<pair_t*>* vars() const { return m_vars; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * Each record starts with a single byte that determines its type
     * (`code`) and contents. If necessary, additional info as parsed
     * as `body`, to be determined by `code`.
     */

    class record_t : public kaitai::kstruct {

    public:

        record_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~record_t();

    private:
        codes_t m_code;
        kaitai::kstruct* m_body;
        bool n_body;

    public:
        bool _is_null_body() { body(); return n_body; };

    private:
        ruby_marshal_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        codes_t code() const { return m_code; }
        kaitai::kstruct* body() const { return m_body; }
        ruby_marshal_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    /**
     * \sa Source
     */

    class ruby_hash_t : public kaitai::kstruct {

    public:

        ruby_hash_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~ruby_hash_t();

    private:
        packed_int_t* m_num_pairs;
        std::vector<pair_t*>* m_pairs;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:
        packed_int_t* num_pairs() const { return m_num_pairs; }
        std::vector<pair_t*>* pairs() const { return m_pairs; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * \sa Source
     */

    class ruby_string_t : public kaitai::kstruct {

    public:

        ruby_string_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent = 0, ruby_marshal_t* p__root = 0);

    private:
        void _read();

    public:
        ~ruby_string_t();

    private:
        packed_int_t* m_len;
        std::string m_body;
        ruby_marshal_t* m__root;
        ruby_marshal_t::record_t* m__parent;

    public:
        packed_int_t* len() const { return m_len; }
        std::string body() const { return m_body; }
        ruby_marshal_t* _root() const { return m__root; }
        ruby_marshal_t::record_t* _parent() const { return m__parent; }
    };

private:
    std::string m_version;
    record_t* m_records;
    ruby_marshal_t* m__root;
    kaitai::kstruct* m__parent;

public:
    std::string version() const { return m_version; }
    record_t* records() const { return m_records; }
    ruby_marshal_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // RUBY_MARSHAL_H_

ruby_marshal.cpp

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

#include "ruby_marshal.h"



ruby_marshal_t::ruby_marshal_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    _read();
}

void ruby_marshal_t::_read() {
    m_version = m__io->ensure_fixed_contents(std::string("\x04\x08", 2));
    m_records = new record_t(m__io, this, m__root);
}

ruby_marshal_t::~ruby_marshal_t() {
    delete m_records;
}

ruby_marshal_t::ruby_array_t::ruby_array_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::ruby_array_t::_read() {
    m_num_elements = new packed_int_t(m__io, this, m__root);
    int l_elements = num_elements()->value();
    m_elements = new std::vector<record_t*>();
    m_elements->reserve(l_elements);
    for (int i = 0; i < l_elements; i++) {
        m_elements->push_back(new record_t(m__io, this, m__root));
    }
}

ruby_marshal_t::ruby_array_t::~ruby_array_t() {
    delete m_num_elements;
    for (std::vector<record_t*>::iterator it = m_elements->begin(); it != m_elements->end(); ++it) {
        delete *it;
    }
    delete m_elements;
}

ruby_marshal_t::bignum_t::bignum_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::bignum_t::_read() {
    m_sign = m__io->read_u1();
    m_len_div_2 = new packed_int_t(m__io, this, m__root);
    m_body = m__io->read_bytes((len_div_2()->value() * 2));
}

ruby_marshal_t::bignum_t::~bignum_t() {
    delete m_len_div_2;
}

ruby_marshal_t::ruby_struct_t::ruby_struct_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::ruby_struct_t::_read() {
    m_name = new record_t(m__io, this, m__root);
    m_num_members = new packed_int_t(m__io, this, m__root);
    int l_members = num_members()->value();
    m_members = new std::vector<pair_t*>();
    m_members->reserve(l_members);
    for (int i = 0; i < l_members; i++) {
        m_members->push_back(new pair_t(m__io, this, m__root));
    }
}

ruby_marshal_t::ruby_struct_t::~ruby_struct_t() {
    delete m_name;
    delete m_num_members;
    for (std::vector<pair_t*>::iterator it = m_members->begin(); it != m_members->end(); ++it) {
        delete *it;
    }
    delete m_members;
}

ruby_marshal_t::ruby_symbol_t::ruby_symbol_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::ruby_symbol_t::_read() {
    m_len = new packed_int_t(m__io, this, m__root);
    m_name = kaitai::kstream::bytes_to_str(m__io->read_bytes(len()->value()), std::string("UTF-8"));
}

ruby_marshal_t::ruby_symbol_t::~ruby_symbol_t() {
    delete m_len;
}

ruby_marshal_t::packed_int_t::packed_int_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_is_immediate = false;
    f_value = false;
    _read();
}

void ruby_marshal_t::packed_int_t::_read() {
    m_code = m__io->read_u1();
    n_encoded = true;
    switch (code()) {
    case 4: {
        n_encoded = false;
        m_encoded = m__io->read_u4le();
        break;
    }
    case 1: {
        n_encoded = false;
        m_encoded = m__io->read_u1();
        break;
    }
    case 252: {
        n_encoded = false;
        m_encoded = m__io->read_u4le();
        break;
    }
    case 253: {
        n_encoded = false;
        m_encoded = m__io->read_u2le();
        break;
    }
    case 3: {
        n_encoded = false;
        m_encoded = m__io->read_u2le();
        break;
    }
    case 2: {
        n_encoded = false;
        m_encoded = m__io->read_u2le();
        break;
    }
    case 255: {
        n_encoded = false;
        m_encoded = m__io->read_u1();
        break;
    }
    case 254: {
        n_encoded = false;
        m_encoded = m__io->read_u2le();
        break;
    }
    }
    n_encoded2 = true;
    switch (code()) {
    case 3: {
        n_encoded2 = false;
        m_encoded2 = m__io->read_u1();
        break;
    }
    case 253: {
        n_encoded2 = false;
        m_encoded2 = m__io->read_u1();
        break;
    }
    }
}

ruby_marshal_t::packed_int_t::~packed_int_t() {
    if (!n_encoded) {
    }
    if (!n_encoded2) {
    }
}

bool ruby_marshal_t::packed_int_t::is_immediate() {
    if (f_is_immediate)
        return m_is_immediate;
    m_is_immediate =  ((code() > 4) && (code() < 252)) ;
    f_is_immediate = true;
    return m_is_immediate;
}

int32_t ruby_marshal_t::packed_int_t::value() {
    if (f_value)
        return m_value;
    m_value = ((is_immediate()) ? (((code() < 128) ? ((code() - 5)) : ((4 - (~(code()) & 127))))) : (((code() == 0) ? (0) : (((code() == 255) ? ((encoded() - 256)) : (((code() == 254) ? ((encoded() - 65536)) : (((code() == 253) ? ((((encoded2() << 16) | encoded()) - 16777216)) : (((code() == 3) ? (((encoded2() << 16) | encoded())) : (encoded()))))))))))));
    f_value = true;
    return m_value;
}

ruby_marshal_t::pair_t::pair_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::pair_t::_read() {
    m_key = new record_t(m__io, this, m__root);
    m_value = new record_t(m__io, this, m__root);
}

ruby_marshal_t::pair_t::~pair_t() {
    delete m_key;
    delete m_value;
}

ruby_marshal_t::instance_var_t::instance_var_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::instance_var_t::_read() {
    m_obj = new record_t(m__io, this, m__root);
    m_num_vars = new packed_int_t(m__io, this, m__root);
    int l_vars = num_vars()->value();
    m_vars = new std::vector<pair_t*>();
    m_vars->reserve(l_vars);
    for (int i = 0; i < l_vars; i++) {
        m_vars->push_back(new pair_t(m__io, this, m__root));
    }
}

ruby_marshal_t::instance_var_t::~instance_var_t() {
    delete m_obj;
    delete m_num_vars;
    for (std::vector<pair_t*>::iterator it = m_vars->begin(); it != m_vars->end(); ++it) {
        delete *it;
    }
    delete m_vars;
}

ruby_marshal_t::record_t::record_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::record_t::_read() {
    m_code = static_cast<ruby_marshal_t::codes_t>(m__io->read_u1());
    n_body = true;
    switch (code()) {
    case CODES_BIGNUM: {
        n_body = false;
        m_body = new bignum_t(m__io, this, m__root);
        break;
    }
    case CODES_RUBY_HASH: {
        n_body = false;
        m_body = new ruby_hash_t(m__io, this, m__root);
        break;
    }
    case CODES_RUBY_ARRAY: {
        n_body = false;
        m_body = new ruby_array_t(m__io, this, m__root);
        break;
    }
    case CODES_RUBY_SYMBOL: {
        n_body = false;
        m_body = new ruby_symbol_t(m__io, this, m__root);
        break;
    }
    case CODES_INSTANCE_VAR: {
        n_body = false;
        m_body = new instance_var_t(m__io, this, m__root);
        break;
    }
    case CODES_RUBY_STRING: {
        n_body = false;
        m_body = new ruby_string_t(m__io, this, m__root);
        break;
    }
    case CODES_PACKED_INT: {
        n_body = false;
        m_body = new packed_int_t(m__io, this, m__root);
        break;
    }
    case CODES_RUBY_STRUCT: {
        n_body = false;
        m_body = new ruby_struct_t(m__io, this, m__root);
        break;
    }
    case CODES_RUBY_SYMBOL_LINK: {
        n_body = false;
        m_body = new packed_int_t(m__io, this, m__root);
        break;
    }
    }
}

ruby_marshal_t::record_t::~record_t() {
    if (!n_body) {
        delete m_body;
    }
}

ruby_marshal_t::ruby_hash_t::ruby_hash_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::ruby_hash_t::_read() {
    m_num_pairs = new packed_int_t(m__io, this, m__root);
    int l_pairs = num_pairs()->value();
    m_pairs = new std::vector<pair_t*>();
    m_pairs->reserve(l_pairs);
    for (int i = 0; i < l_pairs; i++) {
        m_pairs->push_back(new pair_t(m__io, this, m__root));
    }
}

ruby_marshal_t::ruby_hash_t::~ruby_hash_t() {
    delete m_num_pairs;
    for (std::vector<pair_t*>::iterator it = m_pairs->begin(); it != m_pairs->end(); ++it) {
        delete *it;
    }
    delete m_pairs;
}

ruby_marshal_t::ruby_string_t::ruby_string_t(kaitai::kstream* p__io, ruby_marshal_t::record_t* p__parent, ruby_marshal_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ruby_marshal_t::ruby_string_t::_read() {
    m_len = new packed_int_t(m__io, this, m__root);
    m_body = m__io->read_bytes(len()->value());
}

ruby_marshal_t::ruby_string_t::~ruby_string_t() {
    delete m_len;
}