UF2 (USB Flashing Format): C++11/STL parsing library

UF2 is a file format, developed by Microsoft for PXT (also known as Microsoft MakeCode), that is particularly suitable for flashing microcontrollers over MSC (Mass Storage Class; aka removable flash drive).

The list of family IDs is stored in a separate JSON file:

https://github.com/microsoft/uf2/blob/master/utils/uf2families.json

This JSON file is regularly updated. The family_id enum should be kept in sync with it. For simplicity and consistency, it's strongly recommended to auto-generate this enum from uf2families.json directly. This was last done using the following commands:

$ curl -fsSLO https://github.com/microsoft/uf2/raw/90e9741f217f5a40c98ba74d663e408041037578/utils/uf2families.json
$ jq -r '
  .[]
  | "    \(.id | ascii_downcase):\n"
  + "      id: \(.short_name | ascii_downcase | gsub("-"; "_"))\n"
  + "      doc: \(.description | tojson)"
' uf2families.json

Test files, picked to cover as many different shapes as possible:

File extension

uf2

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of UF2 (USB Flashing Format) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All C++11/STL code generated by Kaitai Struct depends on the Kaitai Struct runtime library for C++/STL. You must add this dependency to your project before you can parse or serialize any 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.uf2", 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:
    uf2_t data(&ks);
    

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

data.first_block() // => get first block

C++11/STL source code to parse UF2 (USB Flashing Format)

uf2.h

#pragma once

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

class uf2_t;

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <memory>
#include <set>
#include <vector>

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

/**
 * UF2 is a file format, developed by Microsoft for PXT (also known as
 * Microsoft MakeCode), that is particularly suitable for flashing
 * microcontrollers over MSC (Mass Storage Class; aka removable flash drive).
 * 
 * The list of family IDs is stored in a separate JSON file:
 * 
 * <https://github.com/microsoft/uf2/blob/master/utils/uf2families.json>
 * 
 * This JSON file is regularly updated. The `family_id` enum should be kept in
 * sync with it. For simplicity and consistency, it's strongly recommended to
 * auto-generate this enum from `uf2families.json` directly. This was last done
 * using the following commands:
 * 
 * ```bash
 * $ curl -fsSLO https://github.com/microsoft/uf2/raw/90e9741f217f5a40c98ba74d663e408041037578/utils/uf2families.json
 * $ jq -r '
 *   .[]
 *   | "    \(.id | ascii_downcase):\n"
 *   + "      id: \(.short_name | ascii_downcase | gsub("-"; "_"))\n"
 *   + "      doc: \(.description | tojson)"
 * ' uf2families.json
 * ```
 * 
 * Test files, picked to cover as many different shapes as possible:
 * 
 * * <https://micropython.org/download/RPI_PICO/> - a typical case: all blocks
 *   have the same family ID and `num_blocks`.
 * * <https://micropython.org/download/RPI_PICO2/> - these .uf2 files are
 *   actually two UF2 files concatenated. The first UF2 file is the standalone
 *   `family_id::rp2xxx_absolute` block that `picotool` prepends (see the
 *   `is_rp2350_e10_block` value instance in the `block` type).
 * * <https://circuitpython.org/downloads> - the builds for SAMD boards (for
 *   example
 *   [Feather M0 Express](https://circuitpython.org/board/feather_m0_express/))
 *   set no flags at all, so the field at offset 28 is read as `file_size` rather
 *   than `family_id`.
 * * <https://github.com/raspberrypi/pico-sdk-prebuilts/releases> -
 *   [Universal UF2](https://github.com/raspberrypi/pico-examples/blob/c81c855ffdedc825975a40ba357723a71358ddf0/universal/README.md#universal-binary-vs-universal-uf2)
 *   files, which are again two UF2 files concatenated. What's interesting about
 *   these is that the last block of the first file (with the family ID
 *   `family_id::rp2040`) is moved to the end of the second file, which was
 *   intended for `family_id::rp2xxx_absolute` (see
 *   <https://github.com/raspberrypi/pico-examples/blob/c81c855ffdedc825975a40ba357723a71358ddf0/universal/CMakeLists.txt#L159-L169>).
 * * <https://github.com/microsoft/pxt-microsoft-boot-sequence/releases> - some
 *   files contain blocks with the `flags.is_file_container` flag set (for
 *   example
 *   <https://github.com/microsoft/pxt-microsoft-boot-sequence/releases/download/v0.0.4/arcade-p0.uf2>
 *   is a pure file container for
 *   `file_name == "Projects/microsoft-boot-sequence.elf"`). Generated by
 *   MakeCode, the only known producer of file container UF2s.
 * * <https://github.com/umi-eng/uftwo/tree/35bccf75b4f81c43f088696a8c4a9912f1f4104e/uftwo/tests> -
 *   synthetic test files for features that real firmware doesn't seem to use
 *   (e.g. MD5 checksums).
 * \sa https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md Source
 */

class uf2_t : public kaitai::kstruct {

public:
    class block_t;
    class block_data_t;
    class extension_tag_t;
    class flags_t;
    class md5_checksum_t;

    enum extension_tag_type_t {
        EXTENSION_TAG_TYPE_END = 0,
        EXTENSION_TAG_TYPE_PAGE_SIZE = 780791,
        EXTENSION_TAG_TYPE_DESCRIPTION = 6622621,
        EXTENSION_TAG_TYPE_RP2_IGNORE_BLOCK = 10049507,
        EXTENSION_TAG_TYPE_VERSION = 10471356,
        EXTENSION_TAG_TYPE_SHA2_CHECKSUM = 11824560,
        EXTENSION_TAG_TYPE_DEVICE_TYPE_ID = 13149993
    };
    static bool _is_defined_extension_tag_type_t(extension_tag_type_t v);

private:
    static const std::set<extension_tag_type_t> _values_extension_tag_type_t;

public:

    enum family_id_t {
        FAMILY_ID_STM32L4 = 16738585,
        FAMILY_ID_STM32L5 = 69471199,
        FAMILY_ID_STM32F411XC = 114362747,
        FAMILY_ID_M0SENSE = 299792458,
        FAMILY_ID_ATMEGA32 = 374814231,
        FAMILY_ID_SAML21 = 407992330,
        FAMILY_ID_NRF52 = 458716255,
        FAMILY_ID_ESP32 = 475996592,
        FAMILY_ID_STM32L1 = 505365293,
        FAMILY_ID_STM32L0 = 539900561,
        FAMILY_ID_STM32WL = 558239728,
        FAMILY_ID_RTL8710B = 585160444,
        FAMILY_ID_LPC55 = 716994540,
        FAMILY_ID_ESP32C2 = 730387100,
        FAMILY_ID_STM32F411XE = 767756741,
        FAMILY_ID_STM32G0 = 806311475,
        FAMILY_ID_ESP32S31 = 822212545,
        FAMILY_ID_GD32F350 = 835856582,
        FAMILY_ID_ESP32H2 = 858203894,
        FAMILY_ID_RTL8720D = 863621090,
        FAMILY_ID_ESP32P4 = 1026592404,
        FAMILY_ID_MAIXPLAY_U4 = 1265126769,
        FAMILY_ID_STM32G4 = 1282483210,
        FAMILY_ID_STM32H5 = 1318001757,
        FAMILY_ID_CSK4 = 1332399698,
        FAMILY_ID_MIMXRT10XX = 1337120189,
        FAMILY_ID_XR809 = 1374225320,
        FAMILY_ID_STM32F7 = 1404571392,
        FAMILY_ID_ESP32C6 = 1410195298,
        FAMILY_ID_SAMD51 = 1427194976,
        FAMILY_ID_STM32F4 = 1467308631,
        FAMILY_ID_FX2 = 1511523995,
        FAMILY_ID_STM32F2 = 1561987630,
        FAMILY_ID_STM32F1 = 1591873650,
        FAMILY_ID_NRF52833 = 1646171002,
        FAMILY_ID_STM32F0 = 1685595318,
        FAMILY_ID_BK7231U = 1733968048,
        FAMILY_ID_SAMD21 = 1760373640,
        FAMILY_ID_CH32V = 1771791084,
        FAMILY_ID_BK7251 = 1786956866,
        FAMILY_ID_STM32F3 = 1803837832,
        FAMILY_ID_STM32F407 = 1829315322,
        FAMILY_ID_STM32H7 = 1840668802,
        FAMILY_ID_CSK6 = 1853049000,
        FAMILY_ID_NRF52832XXAB = 1869948536,
        FAMILY_ID_STM32WB = 1892771411,
        FAMILY_ID_NRF52832XXAA = 1920081230,
        FAMILY_ID_MAX32690 = 1947226634,
        FAMILY_ID_ESP32C61 = 2010665156,
        FAMILY_ID_BK7231N = 2067722800,
        FAMILY_ID_RA4M1 = 2078840685,
        FAMILY_ID_PY32F071_UVK5_V3 = 2105173743,
        FAMILY_ID_ESP8266 = 2125160941,
        FAMILY_ID_KL32L2 = 2139350931,
        FAMILY_ID_NRF52820 = 2181929567UL,
        FAMILY_ID_STM32F407VG = 2410701054UL,
        FAMILY_ID_MAX78002 = 2446589208UL,
        FAMILY_ID_RZA1LU = 2501329455UL,
        FAMILY_ID_GD32VF103 = 2599435827UL,
        FAMILY_ID_ESP32H4 = 2651564682UL,
        FAMILY_ID_RTL8710A = 2684343619UL,
        FAMILY_ID_AT32F415 = 2697558926UL,
        FAMILY_ID_NRF52840 = 2913282112UL,
        FAMILY_ID_ESP32H21 = 3067936943UL,
        FAMILY_ID_ESP32S2 = 3218951918UL,
        FAMILY_ID_ESP32S3 = 3296614247UL,
        FAMILY_ID_ESP32C3 = 3559628908UL,
        FAMILY_ID_MAX32650 = 3594487346UL,
        FAMILY_ID_BL602 = 3725750455UL,
        FAMILY_ID_RTL8720C = 3767498084UL,
        FAMILY_ID_RP2040 = 3834380118UL,
        FAMILY_ID_RP2XXX_ABSOLUTE = 3834380119UL,
        FAMILY_ID_RP2XXX_DATA = 3834380120UL,
        FAMILY_ID_RP2350_ARM_S = 3834380121UL,
        FAMILY_ID_RP2350_RISCV = 3834380122UL,
        FAMILY_ID_RP2350_ARM_NS = 3834380123UL,
        FAMILY_ID_MAX32666 = 4039314801UL,
        FAMILY_ID_ESP32C5 = 4145808195UL
    };
    static bool _is_defined_family_id_t(family_id_t v);

private:
    static const std::set<family_id_t> _values_family_id_t;

public:

    uf2_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, uf2_t* p__root = nullptr);

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

public:
    ~uf2_t();

    class block_t : public kaitai::kstruct {

    public:

        block_t(kaitai::kstream* p__io, uf2_t* p__parent = nullptr, uf2_t* p__root = nullptr);

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

    public:
        ~block_t();

    private:
        bool f_is_rp2350_e10_block;
        bool m_is_rp2350_e10_block;

    public:

        /**
         * Determines whether this is a block that `picotool` prepends to RP2350
         * flash images as a workaround for erratum RP2350-E10 (i.e. a hardware
         * bug in the A2 version of the RP2350 boot ROM).
         * 
         * Such a block is always written on its own, but its `num_blocks_raw` is
         * set to 2. If we trusted this value, we would attempt to read one block
         * too many (which would most likely fail). Therefore, we must correct it
         * to 1 before using it.
         * 
         * The conditions for detecting this block come from the
         * [`check_abs_block()`](https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147)
         * function in `picotool`. However, there are some differences:
         * 
         * 1. In Kaitai Struct, we cannot easily check whether all 256 payload
         *    bytes are set to `0xef`, so we only check the first and last bytes.
         * 2. There are .uf2 files in the wild where `flags.has_extension_tags`
         *    is true, but there are actually no extension tags (the first and
         *    only tag has a size of 0, which is just a terminator), so our
         *    condition allows for this case. You can download an example of such
         *    a .uf2 file here:
         *    <https://github.com/neednotapply/DC32-cfw/releases/tag/1.69.13.37>
         * 
         * It's worth noting that we cannot require the presence of the
         * `extension_tag_type::rp2_ignore_block`
         * (`UF2_EXTENSION_RP2_IGNORE_BLOCK`) tag because the UF2 files generated
         * by `picotool` prior to
         * <https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f>
         * don't have it. Therefore, we check whether this tag is present only if
         * `flags.has_extension_tags` is set.
         * 
         * Test .uf2 files with this special block can be downloaded from
         * <https://micropython.org/download/RPI_PICO2/>. Note that all the .uf2
         * files there are actually two UF2 (sub)files concatenated, and this
         * Kaitai Struct implementation parses only one at a time (so in order to
         * parse both, you need something like the helper spec `uf2_files.ksy`
         * from
         * <https://github.com/kaitai-io/kaitai_struct_formats/pull/542#discussion_r3906386820>).
         * v1.24.x releases predate the `extension_tag_type::rp2_ignore_block`
         * tag, while releases v1.25.0 and later include it.
         * \sa https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147 Git tag "2.3.0"
         * \sa https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f Source
         */
        bool is_rp2350_e10_block();

    private:
        bool f_num_blocks;
        uint32_t m_num_blocks;

    public:
        uint32_t num_blocks();

    private:
        std::string m_magic;
        std::string m_second_magic;
        std::unique_ptr<flags_t> m_flags;
        uint32_t m_target_address;
        uint32_t m_len_payload;
        uint32_t m_block_number;
        uint32_t m_num_blocks_raw;
        uint32_t m_file_size;
        bool n_file_size;

    public:
        bool _is_null_file_size() { file_size(); return n_file_size; };

    private:
        family_id_t m_family_id;
        bool n_family_id;

    public:
        bool _is_null_family_id() { family_id(); return n_family_id; };

    private:
        std::unique_ptr<block_data_t> m_data;
        std::string m_final_magic;
        uf2_t* m__root;
        uf2_t* m__parent;
        std::string m__raw_data;
        std::unique_ptr<kaitai::kstream> m__io__raw_data;

    public:
        std::string magic() const { return m_magic; }
        std::string second_magic() const { return m_second_magic; }
        flags_t* flags() const { return m_flags.get(); }

        /**
         * Address in flash where `data.payload` should be written, or an offset
         * in the file specified by `data.file_name` if `flags.is_file_container`
         * is set.
         * 
         * The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#payload-sizes)
         * says:
         * 
         * > In any event, payload size and target address should always be
         * > 4-byte aligned.
         */
        uint32_t target_address() const { return m_target_address; }

        /**
         * The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#payload-sizes)
         * says:
         * 
         * > In any event, payload size and target address should always be
         * > 4-byte aligned.
         */
        uint32_t len_payload() const { return m_len_payload; }
        uint32_t block_number() const { return m_block_number; }

        /**
         * Number of blocks that make up the UF2 file to which this block
         * belongs. Every block of a file has the same value. It is at least 1
         * and every `block_number` in the file must be less than this value
         * (which is validated by this Kaitai Struct implementation).
         */
        uint32_t num_blocks_raw() const { return m_num_blocks_raw; }

        /**
         * Size of the file this block belongs to, but only if
         * `flags.is_file_container` is true. Otherwise, the official spec allows
         * this field to be set to anything - though in practice, it's always
         * zero.
         */
        uint32_t file_size() const { return m_file_size; }
        family_id_t family_id() const { return m_family_id; }
        block_data_t* data() const { return m_data.get(); }
        std::string final_magic() const { return m_final_magic; }
        uf2_t* _root() const { return m__root; }
        uf2_t* _parent() const { return m__parent; }
        std::string _raw_data() const { return m__raw_data; }
        kaitai::kstream* _io__raw_data() const { return m__io__raw_data.get(); }
    };

    class block_data_t : public kaitai::kstruct {

    public:

        block_data_t(kaitai::kstream* p__io, uf2_t::block_t* p__parent = nullptr, uf2_t* p__root = nullptr);

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

    public:
        ~block_data_t();

    private:
        bool f_md5_checksum;
        std::unique_ptr<md5_checksum_t> m_md5_checksum;
        bool n_md5_checksum;

    public:
        bool _is_null_md5_checksum() { md5_checksum(); return n_md5_checksum; };

    private:

    public:

        /**
         * Describes a region that doesn't need to be flashed again if the
         * checksum matches.
         * 
         * The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum)
         * says that "This is currently only used on ESP32", but no real-world
         * firmware sample has been found (at least none of the .uf2 files from
         * <https://github.com/adafruit/tinyuf2/releases>,
         * [MicroPython](https://micropython.org/download/) or
         * [CircuitPython](https://circuitpython.org/downloads) contain it). It
         * was found only in some synthetic test files, e.g.
         * <https://github.com/umi-eng/uftwo/blob/35bccf75b4f81c43f088696a8c4a9912f1f4104e/uftwo/tests/checksum_256.uf2>.
         */
        md5_checksum_t* md5_checksum();

    private:
        std::string m_payload;
        std::string m_file_name;
        bool n_file_name;

    public:
        bool _is_null_file_name() { file_name(); return n_file_name; };

    private:
        std::unique_ptr<std::vector<std::unique_ptr<extension_tag_t>>> m_extension_tags;
        bool n_extension_tags;

    public:
        bool _is_null_extension_tags() { extension_tags(); return n_extension_tags; };

    private:
        uf2_t* m__root;
        uf2_t::block_t* m__parent;

    public:

        /**
         * The bytes to be written to `_parent.target_address`, which is either
         * an address in flash, or an offset in the file specified by `file_name`
         * if `_parent.flags.is_file_container` is set.
         */
        std::string payload() const { return m_payload; }
        std::string file_name() const { return m_file_name; }
        std::vector<std::unique_ptr<extension_tag_t>>* extension_tags() const { return m_extension_tags.get(); }
        uf2_t* _root() const { return m__root; }
        uf2_t::block_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#extension-tags Source
     */

    class extension_tag_t : public kaitai::kstruct {

    public:

        extension_tag_t(kaitai::kstream* p__io, uf2_t::block_data_t* p__parent = nullptr, uf2_t* p__root = nullptr);

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

    public:
        ~extension_tag_t();

    private:
        bool f_len_value;
        int32_t m_len_value;

    public:
        int32_t len_value();

    private:
        bool f_min_len_tag;
        int32_t m_min_len_tag;

    public:
        int32_t min_len_tag();

    private:
        uint8_t m_len_tag;
        extension_tag_type_t m_tag_type;
        std::string m_value;
        bool n_value;

    public:
        bool _is_null_value() { value(); return n_value; };

    private:
        std::string m_padding;
        uf2_t* m__root;
        uf2_t::block_data_t* m__parent;

    public:

        /**
         * Total size of the tag in bytes, including this byte and `tag_type`, so
         * at least 4. The exception is the last tag which terminates the list -
         * it specifies a total size of 0.
         */
        uint8_t len_tag() const { return m_len_tag; }
        extension_tag_type_t tag_type() const { return m_tag_type; }
        std::string value() const { return m_value; }

        /**
         * Tags are 4-byte aligned, so a tag whose size is not a multiple
         * of 4 is followed by padding.
         */
        std::string padding() const { return m_padding; }
        uf2_t* _root() const { return m__root; }
        uf2_t::block_data_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/uf2.h#L43-L47 Source
     * \sa https://github.com/raspberrypi/pico-sdk/blob/98a542c1a62fb549ffb5d66a3e5892b06276b670/src/common/boot_uf2_headers/include/boot/uf2.h#L23-L27 Git tag "2.3.0"
     */

    class flags_t : public kaitai::kstruct {

    public:

        flags_t(kaitai::kstream* p__io, uf2_t::block_t* p__parent = nullptr, uf2_t* p__root = nullptr);

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

    public:
        ~flags_t();

    private:
        bool f_has_extension_tags;
        bool m_has_extension_tags;

    public:

        /**
         * Indicates whether extension tags are present after the payload.
         */
        bool has_extension_tags();

    private:
        bool f_has_family_id;
        bool m_has_family_id;

    public:

        /**
         * The field at offset 28 in the block is `family_id` instead of
         * `file_size`.
         */
        bool has_family_id();

    private:
        bool f_has_md5_checksum;
        bool m_has_md5_checksum;

    public:

        /**
         * Indicates whether `md5_checksum` is present at the end of `data`.
         */
        bool has_md5_checksum();

    private:
        bool f_is_file_container;
        bool m_is_file_container;

    public:

        /**
         * When set, the UF2 format is used as a container for regular files
         * (akin to a TAR file, or ZIP archive without compression).
         * 
         * `target_address` is the offset in the file where the payload is to be
         * written, and `file_size` is the size of that file. The name of the
         * destination file is stored in `data.file_name`.
         */
        bool is_file_container();

    private:
        bool f_not_main_flash;
        bool m_not_main_flash;

    public:

        /**
         * Indicates that this block should be skipped when writing the device
         * flash. It can be used to store data that does not fit on the device,
         * typically embedded source code or debug info.
         */
        bool not_main_flash();

    private:
        uint32_t m_value;
        uf2_t* m__root;
        uf2_t::block_t* m__parent;

    public:

        /**
         * Only the five bits that we cover in value instances below are defined,
         * and no other bit may be set. The [official
         * spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#flags)
         * says "Currently, there are five flags defined". If any other flags are
         * added in the future, this .ksy spec will need to be updated.
         * 
         * The `is_file_container` and `has_extension_tags` flags disagree about
         * what follows the payload in `data` (a file name or a list of extension
         * tags), so this Kaitai Struct implementation treats them as mutually
         * exclusive and will reject a block that sets both. The official spec
         * does not specify how this should be handled, but logically there's a
         * conflict, so we've decided to strictly treat it as an error.
         */
        uint32_t value() const { return m_value; }
        uf2_t* _root() const { return m__root; }
        uf2_t::block_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum Source
     */

    class md5_checksum_t : public kaitai::kstruct {

    public:

        md5_checksum_t(kaitai::kstream* p__io, uf2_t::block_data_t* p__parent = nullptr, uf2_t* p__root = nullptr);

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

    public:
        ~md5_checksum_t();

    private:
        uint32_t m_start_address;
        uint32_t m_len_region;
        std::string m_md5;
        uf2_t* m__root;
        uf2_t::block_data_t* m__parent;

    public:
        uint32_t start_address() const { return m_start_address; }
        uint32_t len_region() const { return m_len_region; }
        std::string md5() const { return m_md5; }
        uf2_t* _root() const { return m__root; }
        uf2_t::block_data_t* _parent() const { return m__parent; }
    };

private:
    std::unique_ptr<block_t> m_first_block;
    std::unique_ptr<std::vector<std::unique_ptr<block_t>>> m_blocks;
    uf2_t* m__root;
    kaitai::kstruct* m__parent;

public:
    block_t* first_block() const { return m_first_block.get(); }
    std::vector<std::unique_ptr<block_t>>* blocks() const { return m_blocks.get(); }
    uf2_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

uf2.cpp

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

#include "uf2.h"
#include "kaitai/exceptions.h"
const std::set<uf2_t::extension_tag_type_t> uf2_t::_values_extension_tag_type_t{
    uf2_t::EXTENSION_TAG_TYPE_END,
    uf2_t::EXTENSION_TAG_TYPE_PAGE_SIZE,
    uf2_t::EXTENSION_TAG_TYPE_DESCRIPTION,
    uf2_t::EXTENSION_TAG_TYPE_RP2_IGNORE_BLOCK,
    uf2_t::EXTENSION_TAG_TYPE_VERSION,
    uf2_t::EXTENSION_TAG_TYPE_SHA2_CHECKSUM,
    uf2_t::EXTENSION_TAG_TYPE_DEVICE_TYPE_ID,
};
bool uf2_t::_is_defined_extension_tag_type_t(uf2_t::extension_tag_type_t v) {
    return uf2_t::_values_extension_tag_type_t.find(v) != uf2_t::_values_extension_tag_type_t.end();
}
const std::set<uf2_t::family_id_t> uf2_t::_values_family_id_t{
    uf2_t::FAMILY_ID_STM32L4,
    uf2_t::FAMILY_ID_STM32L5,
    uf2_t::FAMILY_ID_STM32F411XC,
    uf2_t::FAMILY_ID_M0SENSE,
    uf2_t::FAMILY_ID_ATMEGA32,
    uf2_t::FAMILY_ID_SAML21,
    uf2_t::FAMILY_ID_NRF52,
    uf2_t::FAMILY_ID_ESP32,
    uf2_t::FAMILY_ID_STM32L1,
    uf2_t::FAMILY_ID_STM32L0,
    uf2_t::FAMILY_ID_STM32WL,
    uf2_t::FAMILY_ID_RTL8710B,
    uf2_t::FAMILY_ID_LPC55,
    uf2_t::FAMILY_ID_ESP32C2,
    uf2_t::FAMILY_ID_STM32F411XE,
    uf2_t::FAMILY_ID_STM32G0,
    uf2_t::FAMILY_ID_ESP32S31,
    uf2_t::FAMILY_ID_GD32F350,
    uf2_t::FAMILY_ID_ESP32H2,
    uf2_t::FAMILY_ID_RTL8720D,
    uf2_t::FAMILY_ID_ESP32P4,
    uf2_t::FAMILY_ID_MAIXPLAY_U4,
    uf2_t::FAMILY_ID_STM32G4,
    uf2_t::FAMILY_ID_STM32H5,
    uf2_t::FAMILY_ID_CSK4,
    uf2_t::FAMILY_ID_MIMXRT10XX,
    uf2_t::FAMILY_ID_XR809,
    uf2_t::FAMILY_ID_STM32F7,
    uf2_t::FAMILY_ID_ESP32C6,
    uf2_t::FAMILY_ID_SAMD51,
    uf2_t::FAMILY_ID_STM32F4,
    uf2_t::FAMILY_ID_FX2,
    uf2_t::FAMILY_ID_STM32F2,
    uf2_t::FAMILY_ID_STM32F1,
    uf2_t::FAMILY_ID_NRF52833,
    uf2_t::FAMILY_ID_STM32F0,
    uf2_t::FAMILY_ID_BK7231U,
    uf2_t::FAMILY_ID_SAMD21,
    uf2_t::FAMILY_ID_CH32V,
    uf2_t::FAMILY_ID_BK7251,
    uf2_t::FAMILY_ID_STM32F3,
    uf2_t::FAMILY_ID_STM32F407,
    uf2_t::FAMILY_ID_STM32H7,
    uf2_t::FAMILY_ID_CSK6,
    uf2_t::FAMILY_ID_NRF52832XXAB,
    uf2_t::FAMILY_ID_STM32WB,
    uf2_t::FAMILY_ID_NRF52832XXAA,
    uf2_t::FAMILY_ID_MAX32690,
    uf2_t::FAMILY_ID_ESP32C61,
    uf2_t::FAMILY_ID_BK7231N,
    uf2_t::FAMILY_ID_RA4M1,
    uf2_t::FAMILY_ID_PY32F071_UVK5_V3,
    uf2_t::FAMILY_ID_ESP8266,
    uf2_t::FAMILY_ID_KL32L2,
    uf2_t::FAMILY_ID_NRF52820,
    uf2_t::FAMILY_ID_STM32F407VG,
    uf2_t::FAMILY_ID_MAX78002,
    uf2_t::FAMILY_ID_RZA1LU,
    uf2_t::FAMILY_ID_GD32VF103,
    uf2_t::FAMILY_ID_ESP32H4,
    uf2_t::FAMILY_ID_RTL8710A,
    uf2_t::FAMILY_ID_AT32F415,
    uf2_t::FAMILY_ID_NRF52840,
    uf2_t::FAMILY_ID_ESP32H21,
    uf2_t::FAMILY_ID_ESP32S2,
    uf2_t::FAMILY_ID_ESP32S3,
    uf2_t::FAMILY_ID_ESP32C3,
    uf2_t::FAMILY_ID_MAX32650,
    uf2_t::FAMILY_ID_BL602,
    uf2_t::FAMILY_ID_RTL8720C,
    uf2_t::FAMILY_ID_RP2040,
    uf2_t::FAMILY_ID_RP2XXX_ABSOLUTE,
    uf2_t::FAMILY_ID_RP2XXX_DATA,
    uf2_t::FAMILY_ID_RP2350_ARM_S,
    uf2_t::FAMILY_ID_RP2350_RISCV,
    uf2_t::FAMILY_ID_RP2350_ARM_NS,
    uf2_t::FAMILY_ID_MAX32666,
    uf2_t::FAMILY_ID_ESP32C5,
};
bool uf2_t::_is_defined_family_id_t(uf2_t::family_id_t v) {
    return uf2_t::_values_family_id_t.find(v) != uf2_t::_values_family_id_t.end();
}

uf2_t::uf2_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, uf2_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root ? p__root : this;
    m_first_block = nullptr;
    m_blocks = nullptr;
    _read();
}

void uf2_t::_read() {
    m_first_block = std::unique_ptr<block_t>(new block_t(m__io, this, m__root));
    m_blocks = std::unique_ptr<std::vector<std::unique_ptr<block_t>>>(new std::vector<std::unique_ptr<block_t>>());
    const int l_blocks = first_block()->num_blocks() - 1;
    for (int i = 0; i < l_blocks; i++) {
        m_blocks->push_back(std::move(std::unique_ptr<block_t>(new block_t(m__io, this, m__root))));
    }
}

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

void uf2_t::_clean_up() {
}

uf2_t::block_t::block_t(kaitai::kstream* p__io, uf2_t* p__parent, uf2_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = nullptr;
    m_data = nullptr;
    m__io__raw_data = nullptr;
    f_is_rp2350_e10_block = false;
    f_num_blocks = false;
    _read();
}

void uf2_t::block_t::_read() {
    m_magic = m__io->read_bytes(4);
    if (!(m_magic == std::string("\x55\x46\x32\x0A", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x55\x46\x32\x0A", 4), m_magic, m__io, std::string("/types/block/seq/0"));
    }
    m_second_magic = m__io->read_bytes(4);
    if (!(m_second_magic == std::string("\x57\x51\x5D\x9E", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x57\x51\x5D\x9E", 4), m_second_magic, m__io, std::string("/types/block/seq/1"));
    }
    m_flags = std::unique_ptr<flags_t>(new flags_t(m__io, this, m__root));
    m_target_address = m__io->read_u4le();
    {
        uint32_t _ = m_target_address;
        if (!(kaitai::kstream::mod(_, 4) == 0)) {
            throw kaitai::validation_expr_error<uint32_t>(m_target_address, m__io, std::string("/types/block/seq/3"));
        }
    }
    m_len_payload = m__io->read_u4le();
    {
        uint32_t _ = m_len_payload;
        if (!(kaitai::kstream::mod(_, 4) == 0)) {
            throw kaitai::validation_expr_error<uint32_t>(m_len_payload, m__io, std::string("/types/block/seq/4"));
        }
    }
    m_block_number = m__io->read_u4le();
    m_num_blocks_raw = m__io->read_u4le();
    if (!(m_num_blocks_raw >= block_number() + 1)) {
        throw kaitai::validation_less_than_error<uint32_t>(block_number() + 1, m_num_blocks_raw, m__io, std::string("/types/block/seq/6"));
    }
    n_file_size = true;
    if (!(flags()->has_family_id())) {
        n_file_size = false;
        m_file_size = m__io->read_u4le();
    }
    n_family_id = true;
    if (flags()->has_family_id()) {
        n_family_id = false;
        m_family_id = static_cast<uf2_t::family_id_t>(m__io->read_u4le());
    }
    m__raw_data = m__io->read_bytes(476);
    m__io__raw_data = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_data));
    m_data = std::unique_ptr<block_data_t>(new block_data_t(m__io__raw_data.get(), this, m__root));
    m_final_magic = m__io->read_bytes(4);
    if (!(m_final_magic == std::string("\x30\x6F\xB1\x0A", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x30\x6F\xB1\x0A", 4), m_final_magic, m__io, std::string("/types/block/seq/10"));
    }
}

uf2_t::block_t::~block_t() {
    _clean_up();
}

void uf2_t::block_t::_clean_up() {
    if (!n_file_size) {
    }
    if (!n_family_id) {
    }
}

bool uf2_t::block_t::is_rp2350_e10_block() {
    if (f_is_rp2350_e10_block)
        return m_is_rp2350_e10_block;
    f_is_rp2350_e10_block = true;
    m_is_rp2350_e10_block =  (( ((flags()->value() == 8192) || (flags()->value() == 40960)) ) && (family_id() == uf2_t::FAMILY_ID_RP2XXX_ABSOLUTE) && (num_blocks_raw() == 2) && (block_number() == 0) && (len_payload() == 256) && (data()->payload().front() == 239) && (data()->payload().back() == 239) && ( ((!(flags()->has_extension_tags())) || (data()->extension_tags()->at(0)->len_tag() == 0) || ( ((data()->extension_tags()->at(0)->len_tag() == 4) && (data()->extension_tags()->at(0)->tag_type() == uf2_t::EXTENSION_TAG_TYPE_RP2_IGNORE_BLOCK)) )) )) ;
    return m_is_rp2350_e10_block;
}

uint32_t uf2_t::block_t::num_blocks() {
    if (f_num_blocks)
        return m_num_blocks;
    f_num_blocks = true;
    m_num_blocks = ((is_rp2350_e10_block()) ? (1) : (num_blocks_raw()));
    return m_num_blocks;
}

uf2_t::block_data_t::block_data_t(kaitai::kstream* p__io, uf2_t::block_t* p__parent, uf2_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_extension_tags = nullptr;
    m_md5_checksum = nullptr;
    f_md5_checksum = false;
    _read();
}

void uf2_t::block_data_t::_read() {
    m_payload = m__io->read_bytes(_parent()->len_payload());
    n_file_name = true;
    if (_parent()->flags()->is_file_container()) {
        n_file_name = false;
        m_file_name = kaitai::kstream::bytes_to_str(m__io->read_bytes_term(0, false, true, true), "UTF-8");
    }
    n_extension_tags = true;
    if (_parent()->flags()->has_extension_tags()) {
        n_extension_tags = false;
        m_extension_tags = std::unique_ptr<std::vector<std::unique_ptr<extension_tag_t>>>(new std::vector<std::unique_ptr<extension_tag_t>>());
        {
            int i = 0;
            extension_tag_t* _;
            do {
                _ = new extension_tag_t(m__io, this, m__root);
                m_extension_tags->push_back(std::move(std::unique_ptr<extension_tag_t>(_)));
                i++;
            } while (!(_->len_tag() == 0));
        }
    }
}

uf2_t::block_data_t::~block_data_t() {
    _clean_up();
}

void uf2_t::block_data_t::_clean_up() {
    if (!n_file_name) {
    }
    if (!n_extension_tags) {
    }
    if (f_md5_checksum && !n_md5_checksum) {
    }
}

uf2_t::md5_checksum_t* uf2_t::block_data_t::md5_checksum() {
    if (f_md5_checksum)
        return m_md5_checksum.get();
    f_md5_checksum = true;
    n_md5_checksum = true;
    if (_parent()->flags()->has_md5_checksum()) {
        n_md5_checksum = false;
        std::streampos _pos = m__io->pos();
        m__io->seek(_io()->size() - 24);
        m_md5_checksum = std::unique_ptr<md5_checksum_t>(new md5_checksum_t(m__io, this, m__root));
        m__io->seek(_pos);
    }
    return m_md5_checksum.get();
}

uf2_t::extension_tag_t::extension_tag_t(kaitai::kstream* p__io, uf2_t::block_data_t* p__parent, uf2_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_len_value = false;
    f_min_len_tag = false;
    _read();
}

void uf2_t::extension_tag_t::_read() {
    m_len_tag = m__io->read_u1();
    {
        uint8_t _ = m_len_tag;
        if (!( ((_ == 0) || (_ >= min_len_tag())) )) {
            throw kaitai::validation_expr_error<uint8_t>(m_len_tag, m__io, std::string("/types/extension_tag/seq/0"));
        }
    }
    m_tag_type = static_cast<uf2_t::extension_tag_type_t>(m__io->read_bits_int_le(24));
    m__io->align_to_byte();
    n_value = true;
    if (len_tag() != 0) {
        n_value = false;
        m_value = m__io->read_bytes(len_value());
    }
    m_padding = m__io->read_bytes(kaitai::kstream::mod(-(len_tag()), 4));
}

uf2_t::extension_tag_t::~extension_tag_t() {
    _clean_up();
}

void uf2_t::extension_tag_t::_clean_up() {
    if (!n_value) {
    }
}

int32_t uf2_t::extension_tag_t::len_value() {
    if (f_len_value)
        return m_len_value;
    f_len_value = true;
    m_len_value = ((len_tag() >= min_len_tag()) ? (len_tag() - min_len_tag()) : (0));
    return m_len_value;
}

int32_t uf2_t::extension_tag_t::min_len_tag() {
    if (f_min_len_tag)
        return m_min_len_tag;
    f_min_len_tag = true;
    m_min_len_tag = 1 + 3;
    return m_min_len_tag;
}

uf2_t::flags_t::flags_t(kaitai::kstream* p__io, uf2_t::block_t* p__parent, uf2_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_has_extension_tags = false;
    f_has_family_id = false;
    f_has_md5_checksum = false;
    f_is_file_container = false;
    f_not_main_flash = false;
    _read();
}

void uf2_t::flags_t::_read() {
    m_value = m__io->read_u4le();
    {
        uint32_t _ = m_value;
        if (!( (((_ & ~61441) == 0) && (!( ((is_file_container()) && (has_extension_tags())) ))) )) {
            throw kaitai::validation_expr_error<uint32_t>(m_value, m__io, std::string("/types/flags/seq/0"));
        }
    }
}

uf2_t::flags_t::~flags_t() {
    _clean_up();
}

void uf2_t::flags_t::_clean_up() {
}

bool uf2_t::flags_t::has_extension_tags() {
    if (f_has_extension_tags)
        return m_has_extension_tags;
    f_has_extension_tags = true;
    m_has_extension_tags = (value() & 32768) != 0;
    return m_has_extension_tags;
}

bool uf2_t::flags_t::has_family_id() {
    if (f_has_family_id)
        return m_has_family_id;
    f_has_family_id = true;
    m_has_family_id = (value() & 8192) != 0;
    return m_has_family_id;
}

bool uf2_t::flags_t::has_md5_checksum() {
    if (f_has_md5_checksum)
        return m_has_md5_checksum;
    f_has_md5_checksum = true;
    m_has_md5_checksum = (value() & 16384) != 0;
    return m_has_md5_checksum;
}

bool uf2_t::flags_t::is_file_container() {
    if (f_is_file_container)
        return m_is_file_container;
    f_is_file_container = true;
    m_is_file_container = (value() & 4096) != 0;
    return m_is_file_container;
}

bool uf2_t::flags_t::not_main_flash() {
    if (f_not_main_flash)
        return m_not_main_flash;
    f_not_main_flash = true;
    m_not_main_flash = (value() & 1) != 0;
    return m_not_main_flash;
}

uf2_t::md5_checksum_t::md5_checksum_t(kaitai::kstream* p__io, uf2_t::block_data_t* p__parent, uf2_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void uf2_t::md5_checksum_t::_read() {
    m_start_address = m__io->read_u4le();
    m_len_region = m__io->read_u4le();
    m_md5 = m__io->read_bytes(16);
}

uf2_t::md5_checksum_t::~md5_checksum_t() {
    _clean_up();
}

void uf2_t::md5_checksum_t::_clean_up() {
}