// 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.Objects;
import io.kaitai.struct.ConsistencyError;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
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.ReadWrite {
    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() {
        this(null, null, null);
    }

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

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

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

    public void _fetchInstances() {
        this.firstBlock._fetchInstances();
        for (int i = 0; i < this.blocks.size(); i++) {
            this.blocks.get(((Number) (i)).intValue())._fetchInstances();
        }
    }

    public void _write_Seq() {
        _assertNotDirty();
        this.firstBlock._write_Seq(this._io);
        for (int i = 0; i < this.blocks.size(); i++) {
            this.blocks.get(((Number) (i)).intValue())._write_Seq(this._io);
        }
    }

    public void _check() {
        if (!Objects.equals(this.firstBlock._root(), _root()))
            throw new ConsistencyError("first_block", _root(), this.firstBlock._root());
        if (!Objects.equals(this.firstBlock._parent(), this))
            throw new ConsistencyError("first_block", this, this.firstBlock._parent());
        if (this.blocks.size() != firstBlock().numBlocks() - 1)
            throw new ConsistencyError("blocks", firstBlock().numBlocks() - 1, this.blocks.size());
        for (int i = 0; i < this.blocks.size(); i++) {
            if (!Objects.equals(this.blocks.get(((Number) (i)).intValue())._root(), _root()))
                throw new ConsistencyError("blocks", _root(), this.blocks.get(((Number) (i)).intValue())._root());
            if (!Objects.equals(this.blocks.get(((Number) (i)).intValue())._parent(), this))
                throw new ConsistencyError("blocks", this, this.blocks.get(((Number) (i)).intValue())._parent());
        }
        _dirty = false;
    }
    public static class Block extends KaitaiStruct.ReadWrite {
        public static Block fromFile(String fileName) throws IOException {
            return new Block(new ByteBufferKaitaiStream(fileName));
        }
        public Block() {
            this(null, null, null);
        }

        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;
        }
        public 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.flags._read();
            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());
            }
            this._raw_data = this._io.readBytes(476);
            KaitaiStream _io__raw_data = new ByteBufferKaitaiStream(this._raw_data);
            this.data = new BlockData(_io__raw_data, this, _root);
            this.data._read();
            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");
            }
            _dirty = false;
        }

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

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeBytes(this.magic);
            this._io.writeBytes(this.secondMagic);
            this.flags._write_Seq(this._io);
            this._io.writeU4le(this.targetAddress);
            this._io.writeU4le(this.lenPayload);
            this._io.writeU4le(this.blockNumber);
            this._io.writeU4le(this.numBlocksRaw);
            if (!(flags().hasFamilyId())) {
                this._io.writeU4le(this.fileSize);
            }
            if (flags().hasFamilyId()) {
                this._io.writeU4le(((Number) (this.familyId.id())).longValue());
            }
            final KaitaiStream _io__raw_data = new ByteBufferKaitaiStream(476);
            this._io.addChildStream(_io__raw_data);
            {
                long _pos2 = this._io.pos();
                this._io.seek(this._io.pos() + (476));
                final Block _this = this;
                _io__raw_data.setWriteBackHandler(new KaitaiStream.WriteBackHandler(_pos2) {
                    @Override
                    protected void write(KaitaiStream parent) {
                        _this._raw_data = _io__raw_data.toByteArray();
                        if (_this._raw_data.length != 476)
                            throw new ConsistencyError("raw(data)", 476, _this._raw_data.length);
                        parent.writeBytes(_this._raw_data);
                    }
                });
            }
            this.data._write_Seq(_io__raw_data);
            this._io.writeBytes(this.finalMagic);
        }

        public void _check() {
            if (this.magic.length != 4)
                throw new ConsistencyError("magic", 4, this.magic.length);
            if (!(Arrays.equals(this.magic, new byte[] { 85, 70, 50, 10 }))) {
                throw new KaitaiStream.ValidationNotEqualError(new byte[] { 85, 70, 50, 10 }, this.magic, null, "/types/block/seq/0");
            }
            if (this.secondMagic.length != 4)
                throw new ConsistencyError("second_magic", 4, this.secondMagic.length);
            if (!(Arrays.equals(this.secondMagic, new byte[] { 87, 81, 93, -98 }))) {
                throw new KaitaiStream.ValidationNotEqualError(new byte[] { 87, 81, 93, -98 }, this.secondMagic, null, "/types/block/seq/1");
            }
            if (!Objects.equals(this.flags._root(), _root()))
                throw new ConsistencyError("flags", _root(), this.flags._root());
            if (!Objects.equals(this.flags._parent(), this))
                throw new ConsistencyError("flags", this, this.flags._parent());
            {
                long _it = this.targetAddress;
                if (!(KaitaiStream.mod(_it, 4) == 0)) {
                    throw new KaitaiStream.ValidationExprError(this.targetAddress, null, "/types/block/seq/3");
                }
            }
            {
                long _it = this.lenPayload;
                if (!(KaitaiStream.mod(_it, 4) == 0)) {
                    throw new KaitaiStream.ValidationExprError(this.lenPayload, null, "/types/block/seq/4");
                }
            }
            if (!(this.numBlocksRaw >= blockNumber() + 1)) {
                throw new KaitaiStream.ValidationLessThanError(blockNumber() + 1, this.numBlocksRaw, null, "/types/block/seq/6");
            }
            if (!(flags().hasFamilyId())) {
            }
            if (flags().hasFamilyId()) {
            }
            if (!Objects.equals(this.data._root(), _root()))
                throw new ConsistencyError("data", _root(), this.data._root());
            if (!Objects.equals(this.data._parent(), this))
                throw new ConsistencyError("data", this, this.data._parent());
            if (this.finalMagic.length != 4)
                throw new ConsistencyError("final_magic", 4, this.finalMagic.length);
            if (!(Arrays.equals(this.finalMagic, new byte[] { 48, 111, -79, 10 }))) {
                throw new KaitaiStream.ValidationNotEqualError(new byte[] { 48, 111, -79, 10 }, this.finalMagic, null, "/types/block/seq/10");
            }
            _dirty = false;
        }
        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;
        }
        public void _invalidateIsRp2350E10Block() { this.isRp2350E10Block = null; }
        private Long numBlocks;
        public Long numBlocks() {
            if (this.numBlocks != null)
                return this.numBlocks;
            this.numBlocks = ((Number) ((isRp2350E10Block() ? 1 : numBlocksRaw()))).longValue();
            return this.numBlocks;
        }
        public void _invalidateNumBlocks() { this.numBlocks = null; }
        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;
        private byte[] _raw_data;
        public byte[] magic() { return magic; }
        public void setMagic(byte[] _v) { _dirty = true; magic = _v; }
        public byte[] secondMagic() { return secondMagic; }
        public void setSecondMagic(byte[] _v) { _dirty = true; secondMagic = _v; }
        public Flags flags() { return flags; }
        public void setFlags(Flags _v) { _dirty = true; flags = _v; }

        /**
         * 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; }
        public void setTargetAddress(long _v) { _dirty = true; targetAddress = _v; }

        /**
         * 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 void setLenPayload(long _v) { _dirty = true; lenPayload = _v; }
        public long blockNumber() { return blockNumber; }
        public void setBlockNumber(long _v) { _dirty = true; blockNumber = _v; }

        /**
         * 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; }
        public void setNumBlocksRaw(long _v) { _dirty = true; numBlocksRaw = _v; }

        /**
         * 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 void setFileSize(Long _v) { _dirty = true; fileSize = _v; }
        public FamilyId familyId() { return familyId; }
        public void setFamilyId(FamilyId _v) { _dirty = true; familyId = _v; }
        public BlockData data() { return data; }
        public void setData(BlockData _v) { _dirty = true; data = _v; }
        public byte[] finalMagic() { return finalMagic; }
        public void setFinalMagic(byte[] _v) { _dirty = true; finalMagic = _v; }
        public Uf2 _root() { return _root; }
        public void set_root(Uf2 _v) { _dirty = true; _root = _v; }
        public Uf2 _parent() { return _parent; }
        public void set_parent(Uf2 _v) { _dirty = true; _parent = _v; }
        public byte[] _raw_data() { return _raw_data; }
        public void set_raw_Data(byte[] _v) { _dirty = true; _raw_data = _v; }
    }
    public static class BlockData extends KaitaiStruct.ReadWrite {
        public static BlockData fromFile(String fileName) throws IOException {
            return new BlockData(new ByteBufferKaitaiStream(fileName));
        }
        public BlockData() {
            this(null, null, null);
        }

        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;
        }
        public 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 {
                        ExtensionTag _t_extensionTags = new ExtensionTag(this._io, this, _root);
                        try {
                            _t_extensionTags._read();
                        } finally {
                            _it = _t_extensionTags;
                            this.extensionTags.add(_it);
                        }
                        i++;
                    } while (!(_it.lenTag() == 0));
                }
            }
            _dirty = false;
        }

        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();
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            _shouldWriteMd5Checksum = _enabledMd5Checksum;
            this._io.writeBytes(this.payload);
            if (_parent().flags().isFileContainer()) {
                this._io.writeBytes((this.fileName).getBytes(Charset.forName("UTF-8")));
                this._io.writeU1(0);
            }
            if (_parent().flags().hasExtensionTags()) {
                for (int i = 0; i < this.extensionTags.size(); i++) {
                    this.extensionTags.get(((Number) (i)).intValue())._write_Seq(this._io);
                }
            }
        }

        public void _check() {
            if (this.payload.length != _parent().lenPayload())
                throw new ConsistencyError("payload", _parent().lenPayload(), this.payload.length);
            if (_parent().flags().isFileContainer()) {
                if (KaitaiStream.byteArrayIndexOf((this.fileName).getBytes(Charset.forName("UTF-8")), ((byte) 0)) != -1)
                    throw new ConsistencyError("file_name", -1, KaitaiStream.byteArrayIndexOf((this.fileName).getBytes(Charset.forName("UTF-8")), ((byte) 0)));
            }
            if (_parent().flags().hasExtensionTags()) {
                if (this.extensionTags.size() == 0)
                    throw new ConsistencyError("extension_tags", 0, this.extensionTags.size());
                for (int i = 0; i < this.extensionTags.size(); i++) {
                    if (!Objects.equals(this.extensionTags.get(((Number) (i)).intValue())._root(), _root()))
                        throw new ConsistencyError("extension_tags", _root(), this.extensionTags.get(((Number) (i)).intValue())._root());
                    if (!Objects.equals(this.extensionTags.get(((Number) (i)).intValue())._parent(), this))
                        throw new ConsistencyError("extension_tags", this, this.extensionTags.get(((Number) (i)).intValue())._parent());
                    {
                        ExtensionTag _it = this.extensionTags.get(((Number) (i)).intValue());
                        if ((_it.lenTag() == 0) != (i == this.extensionTags.size() - 1))
                            throw new ConsistencyError("extension_tags", i == this.extensionTags.size() - 1, _it.lenTag() == 0);
                    }
                }
            }
            if (_enabledMd5Checksum) {
                if (_parent().flags().hasMd5Checksum()) {
                    if (!Objects.equals(this.md5Checksum._root(), _root()))
                        throw new ConsistencyError("md5_checksum", _root(), this.md5Checksum._root());
                    if (!Objects.equals(this.md5Checksum._parent(), this))
                        throw new ConsistencyError("md5_checksum", this, this.md5Checksum._parent());
                }
            }
            _dirty = false;
        }
        private Md5Checksum md5Checksum;
        private boolean _shouldWriteMd5Checksum = false;
        private boolean _enabledMd5Checksum = true;

        /**
         * 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 (_shouldWriteMd5Checksum)
                _writeMd5Checksum();
            if (this.md5Checksum != null)
                return this.md5Checksum;
            if (!_enabledMd5Checksum)
                return null;
            if (_parent().flags().hasMd5Checksum()) {
                long _pos = this._io.pos();
                this._io.seek(_io().size() - 24);
                this.md5Checksum = new Md5Checksum(this._io, this, _root);
                this.md5Checksum._read();
                this._io.seek(_pos);
            }
            return this.md5Checksum;
        }
        public void setMd5Checksum(Md5Checksum _v) { _dirty = true; md5Checksum = _v; }
        public void setMd5Checksum_Enabled(boolean _v) { _dirty = true; _enabledMd5Checksum = _v; }

        private void _writeMd5Checksum() {
            _shouldWriteMd5Checksum = false;
            if (_parent().flags().hasMd5Checksum()) {
                long _pos = this._io.pos();
                this._io.seek(_io().size() - 24);
                this.md5Checksum._write_Seq(this._io);
                this._io.seek(_pos);
            }
        }
        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 void setPayload(byte[] _v) { _dirty = true; payload = _v; }
        public String fileName() { return fileName; }
        public void setFileName(String _v) { _dirty = true; fileName = _v; }
        public List<ExtensionTag> extensionTags() { return extensionTags; }
        public void setExtensionTags(List<ExtensionTag> _v) { _dirty = true; extensionTags = _v; }
        public Uf2 _root() { return _root; }
        public void set_root(Uf2 _v) { _dirty = true; _root = _v; }
        public Uf2.Block _parent() { return _parent; }
        public void set_parent(Uf2.Block _v) { _dirty = true; _parent = _v; }
    }

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

        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;
        }
        public 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));
            _dirty = false;
        }

        public void _fetchInstances() {
            if (lenTag() != 0) {
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeU1(this.lenTag);
            this._io.writeBitsIntLe(24, ((Number) (this.tagType.id())).longValue());
            if (lenTag() != 0) {
                this._io.writeBytes(this.value);
            }
            this._io.writeBytes(this.padding);
        }

        public void _check() {
            {
                int _it = this.lenTag;
                if (!( ((_it == 0) || (_it >= minLenTag())) )) {
                    throw new KaitaiStream.ValidationExprError(this.lenTag, null, "/types/extension_tag/seq/0");
                }
            }
            if (lenTag() != 0) {
                if (this.value.length != lenValue())
                    throw new ConsistencyError("value", lenValue(), this.value.length);
            }
            if (this.padding.length != KaitaiStream.mod(-(lenTag()), 4))
                throw new ConsistencyError("padding", KaitaiStream.mod(-(lenTag()), 4), this.padding.length);
            _dirty = false;
        }
        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;
        }
        public void _invalidateLenValue() { this.lenValue = null; }
        private Integer minLenTag;
        public Integer minLenTag() {
            if (this.minLenTag != null)
                return this.minLenTag;
            this.minLenTag = ((Number) (1 + 3)).intValue();
            return this.minLenTag;
        }
        public void _invalidateMinLenTag() { this.minLenTag = null; }
        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 void setLenTag(int _v) { _dirty = true; lenTag = _v; }
        public ExtensionTagType tagType() { return tagType; }
        public void setTagType(ExtensionTagType _v) { _dirty = true; tagType = _v; }
        public byte[] value() { return value; }
        public void setValue(byte[] _v) { _dirty = true; value = _v; }

        /**
         * 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 void setPadding(byte[] _v) { _dirty = true; padding = _v; }
        public Uf2 _root() { return _root; }
        public void set_root(Uf2 _v) { _dirty = true; _root = _v; }
        public Uf2.BlockData _parent() { return _parent; }
        public void set_parent(Uf2.BlockData _v) { _dirty = true; _parent = _v; }
    }

    /**
     * @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.ReadWrite {
        public static Flags fromFile(String fileName) throws IOException {
            return new Flags(new ByteBufferKaitaiStream(fileName));
        }
        public Flags() {
            this(null, null, null);
        }

        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;
        }
        public 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");
                }
            }
            _dirty = false;
        }

        public void _fetchInstances() {
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeU4le(this.value);
        }

        public void _check() {
            {
                long _it = this.value;
                if (!( (((_it & ~61441) == 0) && (!( ((isFileContainer()) && (hasExtensionTags())) ))) )) {
                    throw new KaitaiStream.ValidationExprError(this.value, null, "/types/flags/seq/0");
                }
            }
            _dirty = false;
        }
        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;
        }
        public void _invalidateHasExtensionTags() { this.hasExtensionTags = null; }
        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;
        }
        public void _invalidateHasFamilyId() { this.hasFamilyId = null; }
        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;
        }
        public void _invalidateHasMd5Checksum() { this.hasMd5Checksum = null; }
        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;
        }
        public void _invalidateIsFileContainer() { this.isFileContainer = null; }
        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;
        }
        public void _invalidateNotMainFlash() { this.notMainFlash = null; }
        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 void setValue(long _v) { _dirty = true; value = _v; }
        public Uf2 _root() { return _root; }
        public void set_root(Uf2 _v) { _dirty = true; _root = _v; }
        public Uf2.Block _parent() { return _parent; }
        public void set_parent(Uf2.Block _v) { _dirty = true; _parent = _v; }
    }

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

        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;
        }
        public void _read() {
            this.startAddress = this._io.readU4le();
            this.lenRegion = this._io.readU4le();
            this.md5 = this._io.readBytes(16);
            _dirty = false;
        }

        public void _fetchInstances() {
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeU4le(this.startAddress);
            this._io.writeU4le(this.lenRegion);
            this._io.writeBytes(this.md5);
        }

        public void _check() {
            if (this.md5.length != 16)
                throw new ConsistencyError("md5", 16, this.md5.length);
            _dirty = false;
        }
        private long startAddress;
        private long lenRegion;
        private byte[] md5;
        private Uf2 _root;
        private Uf2.BlockData _parent;
        public long startAddress() { return startAddress; }
        public void setStartAddress(long _v) { _dirty = true; startAddress = _v; }
        public long lenRegion() { return lenRegion; }
        public void setLenRegion(long _v) { _dirty = true; lenRegion = _v; }
        public byte[] md5() { return md5; }
        public void setMd5(byte[] _v) { _dirty = true; md5 = _v; }
        public Uf2 _root() { return _root; }
        public void set_root(Uf2 _v) { _dirty = true; _root = _v; }
        public Uf2.BlockData _parent() { return _parent; }
        public void set_parent(Uf2.BlockData _v) { _dirty = true; _parent = _v; }
    }
    private Block firstBlock;
    private List<Block> blocks;
    private Uf2 _root;
    private KaitaiStruct.ReadWrite _parent;
    public Block firstBlock() { return firstBlock; }
    public void setFirstBlock(Block _v) { _dirty = true; firstBlock = _v; }
    public List<Block> blocks() { return blocks; }
    public void setBlocks(List<Block> _v) { _dirty = true; blocks = _v; }
    public Uf2 _root() { return _root; }
    public void set_root(Uf2 _v) { _dirty = true; _root = _v; }
    public KaitaiStruct.ReadWrite _parent() { return _parent; }
    public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
}
