UF2 (USB Flashing Format): Java 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 Java code generated by Kaitai Struct depends on the Kaitai Struct runtime library for Java. You must add this dependency to your project before you can parse or serialize any data.

The Java runtime library is published in the Maven Central Repository. The artifact page provides snippets for various build tools that you can copy into your project.

Code

Parse a local file and get structure in memory:

Uf2 data = Uf2.fromFile("path/to/local/file.uf2");

Or parse structure from a byte array:

byte[] someArray = new byte[] { ... };
Uf2 data = new Uf2(new ByteBufferKaitaiStream(someArray));

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

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

Java source code to parse UF2 (USB Flashing Format)

Uf2.java

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

import io.kaitai.struct.ByteBufferKaitaiStream;
import io.kaitai.struct.KaitaiStruct;
import io.kaitai.struct.KaitaiStream;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.util.List;


/**
 * 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).
 * @see <a href="https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md">Source</a>
 */
public class Uf2 extends KaitaiStruct {
    public static Uf2 fromFile(String fileName) throws IOException {
        return new Uf2(new ByteBufferKaitaiStream(fileName));
    }

    public enum ExtensionTagType {
        END(0),
        PAGE_SIZE(780791),
        DESCRIPTION(6622621),
        RP2_IGNORE_BLOCK(10049507),
        VERSION(10471356),
        SHA2_CHECKSUM(11824560),
        DEVICE_TYPE_ID(13149993);

        private final long id;
        ExtensionTagType(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, ExtensionTagType> byId = new HashMap<Long, ExtensionTagType>(7);
        static {
            for (ExtensionTagType e : ExtensionTagType.values())
                byId.put(e.id(), e);
        }
        public static ExtensionTagType byId(long id) { return byId.get(id); }
    }

    public enum FamilyId {
        STM32L4(16738585),
        STM32L5(69471199),
        STM32F411XC(114362747),
        M0SENSE(299792458),
        ATMEGA32(374814231),
        SAML21(407992330),
        NRF52(458716255),
        ESP32(475996592),
        STM32L1(505365293),
        STM32L0(539900561),
        STM32WL(558239728),
        RTL8710B(585160444),
        LPC55(716994540),
        ESP32C2(730387100),
        STM32F411XE(767756741),
        STM32G0(806311475),
        ESP32S31(822212545),
        GD32F350(835856582),
        ESP32H2(858203894),
        RTL8720D(863621090),
        ESP32P4(1026592404),
        MAIXPLAY_U4(1265126769),
        STM32G4(1282483210),
        STM32H5(1318001757),
        CSK4(1332399698),
        MIMXRT10XX(1337120189),
        XR809(1374225320),
        STM32F7(1404571392),
        ESP32C6(1410195298),
        SAMD51(1427194976),
        STM32F4(1467308631),
        FX2(1511523995),
        STM32F2(1561987630),
        STM32F1(1591873650),
        NRF52833(1646171002),
        STM32F0(1685595318),
        BK7231U(1733968048),
        SAMD21(1760373640),
        CH32V(1771791084),
        BK7251(1786956866),
        STM32F3(1803837832),
        STM32F407(1829315322),
        STM32H7(1840668802),
        CSK6(1853049000),
        NRF52832XXAB(1869948536),
        STM32WB(1892771411),
        NRF52832XXAA(1920081230),
        MAX32690(1947226634),
        ESP32C61(2010665156),
        BK7231N(2067722800),
        RA4M1(2078840685),
        PY32F071_UVK5_V3(2105173743),
        ESP8266(2125160941),
        KL32L2(2139350931),
        NRF52820(2181929567L),
        STM32F407VG(2410701054L),
        MAX78002(2446589208L),
        RZA1LU(2501329455L),
        GD32VF103(2599435827L),
        ESP32H4(2651564682L),
        RTL8710A(2684343619L),
        AT32F415(2697558926L),
        NRF52840(2913282112L),
        ESP32H21(3067936943L),
        ESP32S2(3218951918L),
        ESP32S3(3296614247L),
        ESP32C3(3559628908L),
        MAX32650(3594487346L),
        BL602(3725750455L),
        RTL8720C(3767498084L),
        RP2040(3834380118L),
        RP2XXX_ABSOLUTE(3834380119L),
        RP2XXX_DATA(3834380120L),
        RP2350_ARM_S(3834380121L),
        RP2350_RISCV(3834380122L),
        RP2350_ARM_NS(3834380123L),
        MAX32666(4039314801L),
        ESP32C5(4145808195L);

        private final long id;
        FamilyId(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, FamilyId> byId = new HashMap<Long, FamilyId>(78);
        static {
            for (FamilyId e : FamilyId.values())
                byId.put(e.id(), e);
        }
        public static FamilyId byId(long id) { return byId.get(id); }
    }

    public Uf2(KaitaiStream _io) {
        this(_io, null, null);
    }

    public Uf2(KaitaiStream _io, KaitaiStruct _parent) {
        this(_io, _parent, null);
    }

    public Uf2(KaitaiStream _io, KaitaiStruct _parent, Uf2 _root) {
        super(_io);
        this._parent = _parent;
        this._root = _root == null ? this : _root;
        _read();
    }
    private void _read() {
        this.firstBlock = new Block(this._io, this, _root);
        this.blocks = new ArrayList<Block>();
        for (int i = 0; i < firstBlock().numBlocks() - 1; i++) {
            this.blocks.add(new Block(this._io, this, _root));
        }
    }

    public void _fetchInstances() {
        this.firstBlock._fetchInstances();
        for (int i = 0; i < this.blocks.size(); i++) {
            this.blocks.get(((Number) (i)).intValue())._fetchInstances();
        }
    }
    public static class Block extends KaitaiStruct {
        public static Block fromFile(String fileName) throws IOException {
            return new Block(new ByteBufferKaitaiStream(fileName));
        }

        public Block(KaitaiStream _io) {
            this(_io, null, null);
        }

        public Block(KaitaiStream _io, Uf2 _parent) {
            this(_io, _parent, null);
        }

        public Block(KaitaiStream _io, Uf2 _parent, Uf2 _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.magic = this._io.readBytes(4);
            if (!(Arrays.equals(this.magic, new byte[] { 85, 70, 50, 10 }))) {
                throw new KaitaiStream.ValidationNotEqualError(new byte[] { 85, 70, 50, 10 }, this.magic, this._io, "/types/block/seq/0");
            }
            this.secondMagic = this._io.readBytes(4);
            if (!(Arrays.equals(this.secondMagic, new byte[] { 87, 81, 93, -98 }))) {
                throw new KaitaiStream.ValidationNotEqualError(new byte[] { 87, 81, 93, -98 }, this.secondMagic, this._io, "/types/block/seq/1");
            }
            this.flags = new Flags(this._io, this, _root);
            this.targetAddress = this._io.readU4le();
            {
                long _it = this.targetAddress;
                if (!(KaitaiStream.mod(_it, 4) == 0)) {
                    throw new KaitaiStream.ValidationExprError(this.targetAddress, this._io, "/types/block/seq/3");
                }
            }
            this.lenPayload = this._io.readU4le();
            {
                long _it = this.lenPayload;
                if (!(KaitaiStream.mod(_it, 4) == 0)) {
                    throw new KaitaiStream.ValidationExprError(this.lenPayload, this._io, "/types/block/seq/4");
                }
            }
            this.blockNumber = this._io.readU4le();
            this.numBlocksRaw = this._io.readU4le();
            if (!(this.numBlocksRaw >= blockNumber() + 1)) {
                throw new KaitaiStream.ValidationLessThanError(blockNumber() + 1, this.numBlocksRaw, this._io, "/types/block/seq/6");
            }
            if (!(flags().hasFamilyId())) {
                this.fileSize = this._io.readU4le();
            }
            if (flags().hasFamilyId()) {
                this.familyId = Uf2.FamilyId.byId(this._io.readU4le());
            }
            KaitaiStream _io_data = this._io.substream(476);
            this.data = new BlockData(_io_data, this, _root);
            this.finalMagic = this._io.readBytes(4);
            if (!(Arrays.equals(this.finalMagic, new byte[] { 48, 111, -79, 10 }))) {
                throw new KaitaiStream.ValidationNotEqualError(new byte[] { 48, 111, -79, 10 }, this.finalMagic, this._io, "/types/block/seq/10");
            }
        }

        public void _fetchInstances() {
            this.flags._fetchInstances();
            if (!(flags().hasFamilyId())) {
            }
            if (flags().hasFamilyId()) {
            }
            this.data._fetchInstances();
        }
        private Boolean isRp2350E10Block;

        /**
         * 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.
         * @see <a href="https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147">Git tag "2.3.0"</a>
         * @see <a href="https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f">Source</a>
         */
        public Boolean isRp2350E10Block() {
            if (this.isRp2350E10Block != null)
                return this.isRp2350E10Block;
            this.isRp2350E10Block =  (( ((flags().value() == 8192) || (flags().value() == 40960)) ) && (familyId() == Uf2.FamilyId.RP2XXX_ABSOLUTE) && (numBlocksRaw() == 2) && (blockNumber() == 0) && (lenPayload() == 256) && ((data().payload()[((int) 0)] & 0xff) == 239) && ((data().payload()[((Number) (data().payload().length - 1)).intValue()] & 0xff) == 239) && ( ((!(flags().hasExtensionTags())) || (data().extensionTags().get(((int) 0)).lenTag() == 0) || ( ((data().extensionTags().get(((int) 0)).lenTag() == 4) && (data().extensionTags().get(((int) 0)).tagType() == Uf2.ExtensionTagType.RP2_IGNORE_BLOCK)) )) )) ;
            return this.isRp2350E10Block;
        }
        private Long numBlocks;
        public Long numBlocks() {
            if (this.numBlocks != null)
                return this.numBlocks;
            this.numBlocks = ((Number) ((isRp2350E10Block() ? 1 : numBlocksRaw()))).longValue();
            return this.numBlocks;
        }
        private byte[] magic;
        private byte[] secondMagic;
        private Flags flags;
        private long targetAddress;
        private long lenPayload;
        private long blockNumber;
        private long numBlocksRaw;
        private Long fileSize;
        private FamilyId familyId;
        private BlockData data;
        private byte[] finalMagic;
        private Uf2 _root;
        private Uf2 _parent;
        public byte[] magic() { return magic; }
        public byte[] secondMagic() { return secondMagic; }
        public Flags flags() { return flags; }

        /**
         * 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.
         */
        public long targetAddress() { return targetAddress; }

        /**
         * 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.
         */
        public long lenPayload() { return lenPayload; }
        public long blockNumber() { return blockNumber; }

        /**
         * 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).
         */
        public long numBlocksRaw() { return numBlocksRaw; }

        /**
         * 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.
         */
        public Long fileSize() { return fileSize; }
        public FamilyId familyId() { return familyId; }
        public BlockData data() { return data; }
        public byte[] finalMagic() { return finalMagic; }
        public Uf2 _root() { return _root; }
        public Uf2 _parent() { return _parent; }
    }
    public static class BlockData extends KaitaiStruct {
        public static BlockData fromFile(String fileName) throws IOException {
            return new BlockData(new ByteBufferKaitaiStream(fileName));
        }

        public BlockData(KaitaiStream _io) {
            this(_io, null, null);
        }

        public BlockData(KaitaiStream _io, Uf2.Block _parent) {
            this(_io, _parent, null);
        }

        public BlockData(KaitaiStream _io, Uf2.Block _parent, Uf2 _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.payload = this._io.readBytes(_parent().lenPayload());
            if (_parent().flags().isFileContainer()) {
                this.fileName = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.UTF_8);
            }
            if (_parent().flags().hasExtensionTags()) {
                this.extensionTags = new ArrayList<ExtensionTag>();
                {
                    ExtensionTag _it;
                    int i = 0;
                    do {
                        _it = new ExtensionTag(this._io, this, _root);
                        this.extensionTags.add(_it);
                        i++;
                    } while (!(_it.lenTag() == 0));
                }
            }
        }

        public void _fetchInstances() {
            if (_parent().flags().isFileContainer()) {
            }
            if (_parent().flags().hasExtensionTags()) {
                for (int i = 0; i < this.extensionTags.size(); i++) {
                    this.extensionTags.get(((Number) (i)).intValue())._fetchInstances();
                }
            }
            md5Checksum();
            if (this.md5Checksum != null) {
                this.md5Checksum._fetchInstances();
            }
        }
        private Md5Checksum md5Checksum;

        /**
         * 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>.
         */
        public Md5Checksum md5Checksum() {
            if (this.md5Checksum != null)
                return this.md5Checksum;
            if (_parent().flags().hasMd5Checksum()) {
                long _pos = this._io.pos();
                this._io.seek(_io().size() - 24);
                this.md5Checksum = new Md5Checksum(this._io, this, _root);
                this._io.seek(_pos);
            }
            return this.md5Checksum;
        }
        private byte[] payload;
        private String fileName;
        private List<ExtensionTag> extensionTags;
        private Uf2 _root;
        private Uf2.Block _parent;

        /**
         * 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.
         */
        public byte[] payload() { return payload; }
        public String fileName() { return fileName; }
        public List<ExtensionTag> extensionTags() { return extensionTags; }
        public Uf2 _root() { return _root; }
        public Uf2.Block _parent() { return _parent; }
    }

    /**
     * @see <a href="https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#extension-tags">Source</a>
     */
    public static class ExtensionTag extends KaitaiStruct {
        public static ExtensionTag fromFile(String fileName) throws IOException {
            return new ExtensionTag(new ByteBufferKaitaiStream(fileName));
        }

        public ExtensionTag(KaitaiStream _io) {
            this(_io, null, null);
        }

        public ExtensionTag(KaitaiStream _io, Uf2.BlockData _parent) {
            this(_io, _parent, null);
        }

        public ExtensionTag(KaitaiStream _io, Uf2.BlockData _parent, Uf2 _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.lenTag = this._io.readU1();
            {
                int _it = this.lenTag;
                if (!( ((_it == 0) || (_it >= minLenTag())) )) {
                    throw new KaitaiStream.ValidationExprError(this.lenTag, this._io, "/types/extension_tag/seq/0");
                }
            }
            this.tagType = Uf2.ExtensionTagType.byId(this._io.readBitsIntLe(24));
            if (lenTag() != 0) {
                this.value = this._io.readBytes(lenValue());
            }
            this.padding = this._io.readBytes(KaitaiStream.mod(-(lenTag()), 4));
        }

        public void _fetchInstances() {
            if (lenTag() != 0) {
            }
        }
        private Integer lenValue;
        public Integer lenValue() {
            if (this.lenValue != null)
                return this.lenValue;
            this.lenValue = ((Number) ((lenTag() >= minLenTag() ? lenTag() - minLenTag() : 0))).intValue();
            return this.lenValue;
        }
        private Integer minLenTag;
        public Integer minLenTag() {
            if (this.minLenTag != null)
                return this.minLenTag;
            this.minLenTag = ((Number) (1 + 3)).intValue();
            return this.minLenTag;
        }
        private int lenTag;
        private ExtensionTagType tagType;
        private byte[] value;
        private byte[] padding;
        private Uf2 _root;
        private Uf2.BlockData _parent;

        /**
         * 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.
         */
        public int lenTag() { return lenTag; }
        public ExtensionTagType tagType() { return tagType; }
        public byte[] value() { return value; }

        /**
         * Tags are 4-byte aligned, so a tag whose size is not a multiple
         * of 4 is followed by padding.
         */
        public byte[] padding() { return padding; }
        public Uf2 _root() { return _root; }
        public Uf2.BlockData _parent() { return _parent; }
    }

    /**
     * @see <a href="https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/uf2.h#L43-L47">Source</a>
     * @see <a href="https://github.com/raspberrypi/pico-sdk/blob/98a542c1a62fb549ffb5d66a3e5892b06276b670/src/common/boot_uf2_headers/include/boot/uf2.h#L23-L27">Git tag "2.3.0"</a>
     */
    public static class Flags extends KaitaiStruct {
        public static Flags fromFile(String fileName) throws IOException {
            return new Flags(new ByteBufferKaitaiStream(fileName));
        }

        public Flags(KaitaiStream _io) {
            this(_io, null, null);
        }

        public Flags(KaitaiStream _io, Uf2.Block _parent) {
            this(_io, _parent, null);
        }

        public Flags(KaitaiStream _io, Uf2.Block _parent, Uf2 _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.value = this._io.readU4le();
            {
                long _it = this.value;
                if (!( (((_it & ~61441) == 0) && (!( ((isFileContainer()) && (hasExtensionTags())) ))) )) {
                    throw new KaitaiStream.ValidationExprError(this.value, this._io, "/types/flags/seq/0");
                }
            }
        }

        public void _fetchInstances() {
        }
        private Boolean hasExtensionTags;

        /**
         * Indicates whether extension tags are present after the payload.
         */
        public Boolean hasExtensionTags() {
            if (this.hasExtensionTags != null)
                return this.hasExtensionTags;
            this.hasExtensionTags = (value() & 32768) != 0;
            return this.hasExtensionTags;
        }
        private Boolean hasFamilyId;

        /**
         * The field at offset 28 in the block is `family_id` instead of
         * `file_size`.
         */
        public Boolean hasFamilyId() {
            if (this.hasFamilyId != null)
                return this.hasFamilyId;
            this.hasFamilyId = (value() & 8192) != 0;
            return this.hasFamilyId;
        }
        private Boolean hasMd5Checksum;

        /**
         * Indicates whether `md5_checksum` is present at the end of `data`.
         */
        public Boolean hasMd5Checksum() {
            if (this.hasMd5Checksum != null)
                return this.hasMd5Checksum;
            this.hasMd5Checksum = (value() & 16384) != 0;
            return this.hasMd5Checksum;
        }
        private Boolean isFileContainer;

        /**
         * 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`.
         */
        public Boolean isFileContainer() {
            if (this.isFileContainer != null)
                return this.isFileContainer;
            this.isFileContainer = (value() & 4096) != 0;
            return this.isFileContainer;
        }
        private Boolean notMainFlash;

        /**
         * 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.
         */
        public Boolean notMainFlash() {
            if (this.notMainFlash != null)
                return this.notMainFlash;
            this.notMainFlash = (value() & 1) != 0;
            return this.notMainFlash;
        }
        private long value;
        private Uf2 _root;
        private Uf2.Block _parent;

        /**
         * 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.
         */
        public long value() { return value; }
        public Uf2 _root() { return _root; }
        public Uf2.Block _parent() { return _parent; }
    }

    /**
     * @see <a href="https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum">Source</a>
     */
    public static class Md5Checksum extends KaitaiStruct {
        public static Md5Checksum fromFile(String fileName) throws IOException {
            return new Md5Checksum(new ByteBufferKaitaiStream(fileName));
        }

        public Md5Checksum(KaitaiStream _io) {
            this(_io, null, null);
        }

        public Md5Checksum(KaitaiStream _io, Uf2.BlockData _parent) {
            this(_io, _parent, null);
        }

        public Md5Checksum(KaitaiStream _io, Uf2.BlockData _parent, Uf2 _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.startAddress = this._io.readU4le();
            this.lenRegion = this._io.readU4le();
            this.md5 = this._io.readBytes(16);
        }

        public void _fetchInstances() {
        }
        private long startAddress;
        private long lenRegion;
        private byte[] md5;
        private Uf2 _root;
        private Uf2.BlockData _parent;
        public long startAddress() { return startAddress; }
        public long lenRegion() { return lenRegion; }
        public byte[] md5() { return md5; }
        public Uf2 _root() { return _root; }
        public Uf2.BlockData _parent() { return _parent; }
    }
    private Block firstBlock;
    private List<Block> blocks;
    private Uf2 _root;
    private KaitaiStruct _parent;
    public Block firstBlock() { return firstBlock; }
    public List<Block> blocks() { return blocks; }
    public Uf2 _root() { return _root; }
    public KaitaiStruct _parent() { return _parent; }
}