Broadcom devices .trx firmware packaging: C++98/STL parsing library

.trx file format is widely used for distribution of firmware updates for Broadcom devices. The most well-known are ASUS routers.

Fundamentally, it includes a footer which acts as a safeguard against installing a firmware package on a wrong hardware model or version, and a header which list numerous partitions packaged inside a single .trx file.

trx files not necessarily contain all these headers.

File extension

trx

KS implementation details

License: GPL-2.0-or-later
Minimal Kaitai Struct required: 0.9

This page hosts a formal specification of Broadcom devices .trx firmware packaging using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

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.trx", 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:
    broadcom_trx_t data(&ks);
    

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

data.header() // => get header

C++98/STL source code to parse Broadcom devices .trx firmware packaging

broadcom_trx.h

#ifndef BROADCOM_TRX_H_
#define BROADCOM_TRX_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 < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * .trx file format is widely used for distribution of firmware
 * updates for Broadcom devices. The most well-known are ASUS routers.
 * 
 * Fundamentally, it includes a footer which acts as a safeguard
 * against installing a firmware package on a wrong hardware model or
 * version, and a header which list numerous partitions packaged inside
 * a single .trx file.
 * 
 * trx files not necessarily contain all these headers.
 * \sa https://github.com/openwrt/firmware-utils/blob/a2c80c5/src/trx.c Source
 * \sa https://web.archive.org/web/20190127154419/https://openwrt.org/docs/techref/header Source
 * \sa https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/mtd/partitions/brcm,trx.txt Source
 */

class broadcom_trx_t : public kaitai::kstruct {

public:
    class revision_t;
    class version_t;
    class tail_t;
    class header_t;

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

private:
    void _read();
    void _clean_up();

public:
    ~broadcom_trx_t();

    class revision_t : public kaitai::kstruct {

    public:

        revision_t(kaitai::kstream* p__io, broadcom_trx_t::tail_t::hw_comp_info_t* p__parent = 0, broadcom_trx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~revision_t();

    private:
        uint8_t m_major;
        uint8_t m_minor;
        broadcom_trx_t* m__root;
        broadcom_trx_t::tail_t::hw_comp_info_t* m__parent;

    public:
        uint8_t major() const { return m_major; }
        uint8_t minor() const { return m_minor; }
        broadcom_trx_t* _root() const { return m__root; }
        broadcom_trx_t::tail_t::hw_comp_info_t* _parent() const { return m__parent; }
    };

    class version_t : public kaitai::kstruct {

    public:

        version_t(kaitai::kstream* p__io, broadcom_trx_t::tail_t* p__parent = 0, broadcom_trx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~version_t();

    private:
        uint8_t m_major;
        uint8_t m_minor;
        uint8_t m_patch;
        uint8_t m_tweak;
        broadcom_trx_t* m__root;
        broadcom_trx_t::tail_t* m__parent;

    public:
        uint8_t major() const { return m_major; }
        uint8_t minor() const { return m_minor; }
        uint8_t patch() const { return m_patch; }
        uint8_t tweak() const { return m_tweak; }
        broadcom_trx_t* _root() const { return m__root; }
        broadcom_trx_t::tail_t* _parent() const { return m__parent; }
    };

    /**
     * A safeguard against installation of firmware to an incompatible device
     */

    class tail_t : public kaitai::kstruct {

    public:
        class hw_comp_info_t;

        tail_t(kaitai::kstream* p__io, broadcom_trx_t* p__parent = 0, broadcom_trx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~tail_t();

        class hw_comp_info_t : public kaitai::kstruct {

        public:

            hw_comp_info_t(kaitai::kstream* p__io, broadcom_trx_t::tail_t* p__parent = 0, broadcom_trx_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~hw_comp_info_t();

        private:
            revision_t* m_min;
            revision_t* m_max;
            broadcom_trx_t* m__root;
            broadcom_trx_t::tail_t* m__parent;

        public:
            revision_t* min() const { return m_min; }
            revision_t* max() const { return m_max; }
            broadcom_trx_t* _root() const { return m__root; }
            broadcom_trx_t::tail_t* _parent() const { return m__parent; }
        };

    private:
        version_t* m_version;
        std::string m_product_id;
        std::vector<hw_comp_info_t*>* m_comp_hw;
        std::string m_reserved;
        broadcom_trx_t* m__root;
        broadcom_trx_t* m__parent;

    public:

        /**
         * 1.9.2.7 by default
         */
        version_t* version() const { return m_version; }
        std::string product_id() const { return m_product_id; }

        /**
         * 0.02 - 2.99
         */
        std::vector<hw_comp_info_t*>* comp_hw() const { return m_comp_hw; }
        std::string reserved() const { return m_reserved; }
        broadcom_trx_t* _root() const { return m__root; }
        broadcom_trx_t* _parent() const { return m__parent; }
    };

    class header_t : public kaitai::kstruct {

    public:
        class partition_t;
        class flags_t;

        header_t(kaitai::kstream* p__io, broadcom_trx_t* p__parent = 0, broadcom_trx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

        class partition_t : public kaitai::kstruct {

        public:

            partition_t(uint8_t p_idx, kaitai::kstream* p__io, broadcom_trx_t::header_t* p__parent = 0, broadcom_trx_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~partition_t();

        private:
            bool f_is_present;
            bool m_is_present;

        public:
            bool is_present();

        private:
            bool f_is_last;
            bool m_is_last;
            bool n_is_last;

        public:
            bool _is_null_is_last() { is_last(); return n_is_last; };

        private:

        public:
            bool is_last();

        private:
            bool f_len_body;
            int32_t m_len_body;
            bool n_len_body;

        public:
            bool _is_null_len_body() { len_body(); return n_len_body; };

        private:

        public:
            int32_t len_body();

        private:
            bool f_body;
            std::string m_body;
            bool n_body;

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

        private:

        public:
            std::string body();

        private:
            uint32_t m_ofs_body;
            uint8_t m_idx;
            broadcom_trx_t* m__root;
            broadcom_trx_t::header_t* m__parent;

        public:
            uint32_t ofs_body() const { return m_ofs_body; }
            uint8_t idx() const { return m_idx; }
            broadcom_trx_t* _root() const { return m__root; }
            broadcom_trx_t::header_t* _parent() const { return m__parent; }
        };

        class flags_t : public kaitai::kstruct {

        public:

            flags_t(kaitai::kstream* p__io, broadcom_trx_t::header_t* p__parent = 0, broadcom_trx_t* p__root = 0);

        private:
            void _read();
            void _clean_up();

        public:
            ~flags_t();

        private:
            std::vector<bool>* m_flags;
            broadcom_trx_t* m__root;
            broadcom_trx_t::header_t* m__parent;

        public:
            std::vector<bool>* flags() const { return m_flags; }
            broadcom_trx_t* _root() const { return m__root; }
            broadcom_trx_t::header_t* _parent() const { return m__parent; }
        };

    private:
        std::string m_magic;
        uint32_t m_len;
        uint32_t m_crc32;
        uint16_t m_version;
        flags_t* m_flags;
        std::vector<partition_t*>* m_partitions;
        broadcom_trx_t* m__root;
        broadcom_trx_t* m__parent;

    public:
        std::string magic() const { return m_magic; }

        /**
         * Length of file including header
         */
        uint32_t len() const { return m_len; }

        /**
         * CRC from `version` (??? todo: see the original and disambiguate) to end of file
         */
        uint32_t crc32() const { return m_crc32; }
        uint16_t version() const { return m_version; }
        flags_t* flags() const { return m_flags; }

        /**
         * Offsets of partitions from start of header
         */
        std::vector<partition_t*>* partitions() const { return m_partitions; }
        broadcom_trx_t* _root() const { return m__root; }
        broadcom_trx_t* _parent() const { return m__parent; }
    };

private:
    bool f_header;
    header_t* m_header;

public:
    header_t* header();

private:
    bool f_tail;
    tail_t* m_tail;

public:
    tail_t* tail();

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

public:
    broadcom_trx_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // BROADCOM_TRX_H_

broadcom_trx.cpp

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

#include "broadcom_trx.h"
#include "kaitai/exceptions.h"

broadcom_trx_t::broadcom_trx_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = 0;
    m_tail = 0;
    f_header = false;
    f_tail = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::_read() {
}

broadcom_trx_t::~broadcom_trx_t() {
    _clean_up();
}

void broadcom_trx_t::_clean_up() {
    if (f_header) {
        if (m_header) {
            delete m_header; m_header = 0;
        }
    }
    if (f_tail) {
        if (m_tail) {
            delete m_tail; m_tail = 0;
        }
    }
}

broadcom_trx_t::revision_t::revision_t(kaitai::kstream* p__io, broadcom_trx_t::tail_t::hw_comp_info_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::revision_t::_read() {
    m_major = m__io->read_u1();
    m_minor = m__io->read_u1();
}

broadcom_trx_t::revision_t::~revision_t() {
    _clean_up();
}

void broadcom_trx_t::revision_t::_clean_up() {
}

broadcom_trx_t::version_t::version_t(kaitai::kstream* p__io, broadcom_trx_t::tail_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::version_t::_read() {
    m_major = m__io->read_u1();
    m_minor = m__io->read_u1();
    m_patch = m__io->read_u1();
    m_tweak = m__io->read_u1();
}

broadcom_trx_t::version_t::~version_t() {
    _clean_up();
}

void broadcom_trx_t::version_t::_clean_up() {
}

broadcom_trx_t::tail_t::tail_t(kaitai::kstream* p__io, broadcom_trx_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_version = 0;
    m_comp_hw = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::tail_t::_read() {
    m_version = new version_t(m__io, this, m__root);
    m_product_id = kaitai::kstream::bytes_to_str(kaitai::kstream::bytes_terminate(m__io->read_bytes(12), 0, false), std::string("utf-8"));
    m_comp_hw = new std::vector<hw_comp_info_t*>();
    const int l_comp_hw = 4;
    for (int i = 0; i < l_comp_hw; i++) {
        m_comp_hw->push_back(new hw_comp_info_t(m__io, this, m__root));
    }
    m_reserved = m__io->read_bytes(32);
}

broadcom_trx_t::tail_t::~tail_t() {
    _clean_up();
}

void broadcom_trx_t::tail_t::_clean_up() {
    if (m_version) {
        delete m_version; m_version = 0;
    }
    if (m_comp_hw) {
        for (std::vector<hw_comp_info_t*>::iterator it = m_comp_hw->begin(); it != m_comp_hw->end(); ++it) {
            delete *it;
        }
        delete m_comp_hw; m_comp_hw = 0;
    }
}

broadcom_trx_t::tail_t::hw_comp_info_t::hw_comp_info_t(kaitai::kstream* p__io, broadcom_trx_t::tail_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_min = 0;
    m_max = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::tail_t::hw_comp_info_t::_read() {
    m_min = new revision_t(m__io, this, m__root);
    m_max = new revision_t(m__io, this, m__root);
}

broadcom_trx_t::tail_t::hw_comp_info_t::~hw_comp_info_t() {
    _clean_up();
}

void broadcom_trx_t::tail_t::hw_comp_info_t::_clean_up() {
    if (m_min) {
        delete m_min; m_min = 0;
    }
    if (m_max) {
        delete m_max; m_max = 0;
    }
}

broadcom_trx_t::header_t::header_t(kaitai::kstream* p__io, broadcom_trx_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = 0;
    m_partitions = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::header_t::_read() {
    m_magic = m__io->read_bytes(4);
    if (!(magic() == std::string("\x48\x44\x52\x30", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x48\x44\x52\x30", 4), magic(), _io(), std::string("/types/header/seq/0"));
    }
    m_len = m__io->read_u4le();
    m_crc32 = m__io->read_u4le();
    m_version = m__io->read_u2le();
    m_flags = new flags_t(m__io, this, m__root);
    m_partitions = new std::vector<partition_t*>();
    {
        int i = 0;
        partition_t* _;
        do {
            _ = new partition_t(i, m__io, this, m__root);
            m_partitions->push_back(_);
            i++;
        } while (!( ((i >= 4) || (!(_->is_present()))) ));
    }
}

broadcom_trx_t::header_t::~header_t() {
    _clean_up();
}

void broadcom_trx_t::header_t::_clean_up() {
    if (m_flags) {
        delete m_flags; m_flags = 0;
    }
    if (m_partitions) {
        for (std::vector<partition_t*>::iterator it = m_partitions->begin(); it != m_partitions->end(); ++it) {
            delete *it;
        }
        delete m_partitions; m_partitions = 0;
    }
}

broadcom_trx_t::header_t::partition_t::partition_t(uint8_t p_idx, kaitai::kstream* p__io, broadcom_trx_t::header_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_idx = p_idx;
    f_is_present = false;
    f_is_last = false;
    f_len_body = false;
    f_body = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::header_t::partition_t::_read() {
    m_ofs_body = m__io->read_u4le();
}

broadcom_trx_t::header_t::partition_t::~partition_t() {
    _clean_up();
}

void broadcom_trx_t::header_t::partition_t::_clean_up() {
    if (f_body && !n_body) {
    }
}

bool broadcom_trx_t::header_t::partition_t::is_present() {
    if (f_is_present)
        return m_is_present;
    m_is_present = ofs_body() != 0;
    f_is_present = true;
    return m_is_present;
}

bool broadcom_trx_t::header_t::partition_t::is_last() {
    if (f_is_last)
        return m_is_last;
    n_is_last = true;
    if (is_present()) {
        n_is_last = false;
        m_is_last =  ((idx() == (_parent()->partitions()->size() - 1)) || (!(_parent()->partitions()->at((idx() + 1))->is_present()))) ;
    }
    f_is_last = true;
    return m_is_last;
}

int32_t broadcom_trx_t::header_t::partition_t::len_body() {
    if (f_len_body)
        return m_len_body;
    n_len_body = true;
    if (is_present()) {
        n_len_body = false;
        m_len_body = ((is_last()) ? ((_root()->_io()->size() - ofs_body())) : (_parent()->partitions()->at((idx() + 1))->ofs_body()));
    }
    f_len_body = true;
    return m_len_body;
}

std::string broadcom_trx_t::header_t::partition_t::body() {
    if (f_body)
        return m_body;
    n_body = true;
    if (is_present()) {
        n_body = false;
        kaitai::kstream *io = _root()->_io();
        std::streampos _pos = io->pos();
        io->seek(ofs_body());
        m_body = io->read_bytes(len_body());
        io->seek(_pos);
        f_body = true;
    }
    return m_body;
}

broadcom_trx_t::header_t::flags_t::flags_t(kaitai::kstream* p__io, broadcom_trx_t::header_t* p__parent, broadcom_trx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void broadcom_trx_t::header_t::flags_t::_read() {
    m_flags = new std::vector<bool>();
    const int l_flags = 16;
    for (int i = 0; i < l_flags; i++) {
        m_flags->push_back(m__io->read_bits_int_le(1));
    }
}

broadcom_trx_t::header_t::flags_t::~flags_t() {
    _clean_up();
}

void broadcom_trx_t::header_t::flags_t::_clean_up() {
    if (m_flags) {
        delete m_flags; m_flags = 0;
    }
}

broadcom_trx_t::header_t* broadcom_trx_t::header() {
    if (f_header)
        return m_header;
    std::streampos _pos = m__io->pos();
    m__io->seek(0);
    m_header = new header_t(m__io, this, m__root);
    m__io->seek(_pos);
    f_header = true;
    return m_header;
}

broadcom_trx_t::tail_t* broadcom_trx_t::tail() {
    if (f_tail)
        return m_tail;
    std::streampos _pos = m__io->pos();
    m__io->seek((_io()->size() - 64));
    m_tail = new tail_t(m__io, this, m__root);
    m__io->seek(_pos);
    f_tail = true;
    return m_tail;
}