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

require 'kaitai/struct/struct'

unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.11')
  raise "Incompatible Kaitai Struct Ruby API: 0.11 or later is required, but you have #{Kaitai::Struct::VERSION}"
end


##
# 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 https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md Source
class Uf2 < Kaitai::Struct::Struct

  EXTENSION_TAG_TYPE = {
    0 => :extension_tag_type_end,
    780791 => :extension_tag_type_page_size,
    6622621 => :extension_tag_type_description,
    10049507 => :extension_tag_type_rp2_ignore_block,
    10471356 => :extension_tag_type_version,
    11824560 => :extension_tag_type_sha2_checksum,
    13149993 => :extension_tag_type_device_type_id,
  }
  I__EXTENSION_TAG_TYPE = EXTENSION_TAG_TYPE.invert

  FAMILY_ID = {
    16738585 => :family_id_stm32l4,
    69471199 => :family_id_stm32l5,
    114362747 => :family_id_stm32f411xc,
    299792458 => :family_id_m0sense,
    374814231 => :family_id_atmega32,
    407992330 => :family_id_saml21,
    458716255 => :family_id_nrf52,
    475996592 => :family_id_esp32,
    505365293 => :family_id_stm32l1,
    539900561 => :family_id_stm32l0,
    558239728 => :family_id_stm32wl,
    585160444 => :family_id_rtl8710b,
    716994540 => :family_id_lpc55,
    730387100 => :family_id_esp32c2,
    767756741 => :family_id_stm32f411xe,
    806311475 => :family_id_stm32g0,
    822212545 => :family_id_esp32s31,
    835856582 => :family_id_gd32f350,
    858203894 => :family_id_esp32h2,
    863621090 => :family_id_rtl8720d,
    1026592404 => :family_id_esp32p4,
    1265126769 => :family_id_maixplay_u4,
    1282483210 => :family_id_stm32g4,
    1318001757 => :family_id_stm32h5,
    1332399698 => :family_id_csk4,
    1337120189 => :family_id_mimxrt10xx,
    1374225320 => :family_id_xr809,
    1404571392 => :family_id_stm32f7,
    1410195298 => :family_id_esp32c6,
    1427194976 => :family_id_samd51,
    1467308631 => :family_id_stm32f4,
    1511523995 => :family_id_fx2,
    1561987630 => :family_id_stm32f2,
    1591873650 => :family_id_stm32f1,
    1646171002 => :family_id_nrf52833,
    1685595318 => :family_id_stm32f0,
    1733968048 => :family_id_bk7231u,
    1760373640 => :family_id_samd21,
    1771791084 => :family_id_ch32v,
    1786956866 => :family_id_bk7251,
    1803837832 => :family_id_stm32f3,
    1829315322 => :family_id_stm32f407,
    1840668802 => :family_id_stm32h7,
    1853049000 => :family_id_csk6,
    1869948536 => :family_id_nrf52832xxab,
    1892771411 => :family_id_stm32wb,
    1920081230 => :family_id_nrf52832xxaa,
    1947226634 => :family_id_max32690,
    2010665156 => :family_id_esp32c61,
    2067722800 => :family_id_bk7231n,
    2078840685 => :family_id_ra4m1,
    2105173743 => :family_id_py32f071_uvk5_v3,
    2125160941 => :family_id_esp8266,
    2139350931 => :family_id_kl32l2,
    2181929567 => :family_id_nrf52820,
    2410701054 => :family_id_stm32f407vg,
    2446589208 => :family_id_max78002,
    2501329455 => :family_id_rza1lu,
    2599435827 => :family_id_gd32vf103,
    2651564682 => :family_id_esp32h4,
    2684343619 => :family_id_rtl8710a,
    2697558926 => :family_id_at32f415,
    2913282112 => :family_id_nrf52840,
    3067936943 => :family_id_esp32h21,
    3218951918 => :family_id_esp32s2,
    3296614247 => :family_id_esp32s3,
    3559628908 => :family_id_esp32c3,
    3594487346 => :family_id_max32650,
    3725750455 => :family_id_bl602,
    3767498084 => :family_id_rtl8720c,
    3834380118 => :family_id_rp2040,
    3834380119 => :family_id_rp2xxx_absolute,
    3834380120 => :family_id_rp2xxx_data,
    3834380121 => :family_id_rp2350_arm_s,
    3834380122 => :family_id_rp2350_riscv,
    3834380123 => :family_id_rp2350_arm_ns,
    4039314801 => :family_id_max32666,
    4145808195 => :family_id_esp32c5,
  }
  I__FAMILY_ID = FAMILY_ID.invert
  def initialize(_io, _parent = nil, _root = nil)
    super(_io, _parent, _root || self)
    _read
  end

  def _read
    @first_block = Block.new(@_io, self, @_root)
    @blocks = []
    (first_block.num_blocks - 1).times { |i|
      @blocks << Block.new(@_io, self, @_root)
    }
    self
  end
  class Block < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @magic = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([85, 70, 50, 10].pack('C*'), @magic, @_io, "/types/block/seq/0") if not @magic == [85, 70, 50, 10].pack('C*')
      @second_magic = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([87, 81, 93, 158].pack('C*'), @second_magic, @_io, "/types/block/seq/1") if not @second_magic == [87, 81, 93, 158].pack('C*')
      @flags = Flags.new(@_io, self, @_root)
      @target_address = @_io.read_u4le
      _ = @target_address
      raise Kaitai::Struct::ValidationExprError.new(@target_address, @_io, "/types/block/seq/3") if not _ % 4 == 0
      @len_payload = @_io.read_u4le
      _ = @len_payload
      raise Kaitai::Struct::ValidationExprError.new(@len_payload, @_io, "/types/block/seq/4") if not _ % 4 == 0
      @block_number = @_io.read_u4le
      @num_blocks_raw = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(block_number + 1, @num_blocks_raw, @_io, "/types/block/seq/6") if not @num_blocks_raw >= block_number + 1
      if !(flags.has_family_id)
        @file_size = @_io.read_u4le
      end
      if flags.has_family_id
        @family_id = Kaitai::Struct::Stream::resolve_enum(Uf2::FAMILY_ID, @_io.read_u4le)
      end
      _io_data = @_io.substream(476)
      @data = BlockData.new(_io_data, self, @_root)
      @final_magic = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([48, 111, 177, 10].pack('C*'), @final_magic, @_io, "/types/block/seq/10") if not @final_magic == [48, 111, 177, 10].pack('C*')
      self
    end

    ##
    # 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 https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147 Git tag "2.3.0"
    # @see https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f Source
    def is_rp2350_e10_block
      return @is_rp2350_e10_block unless @is_rp2350_e10_block.nil?
      @is_rp2350_e10_block =  (( ((flags.value == 8192) || (flags.value == 40960)) ) && (family_id == :family_id_rp2xxx_absolute) && (num_blocks_raw == 2) && (block_number == 0) && (len_payload == 256) && (data.payload[0].ord == 239) && (data.payload[-1].ord == 239) && ( ((!(flags.has_extension_tags)) || (data.extension_tags[0].len_tag == 0) || ( ((data.extension_tags[0].len_tag == 4) && (data.extension_tags[0].tag_type == :extension_tag_type_rp2_ignore_block)) )) )) 
      @is_rp2350_e10_block
    end
    def num_blocks
      return @num_blocks unless @num_blocks.nil?
      @num_blocks = (is_rp2350_e10_block ? 1 : num_blocks_raw)
      @num_blocks
    end
    attr_reader :magic
    attr_reader :second_magic
    attr_reader :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.
    attr_reader :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.
    attr_reader :len_payload
    attr_reader :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).
    attr_reader :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.
    attr_reader :file_size
    attr_reader :family_id
    attr_reader :data
    attr_reader :final_magic
    attr_reader :_raw_data
  end
  class BlockData < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @payload = @_io.read_bytes(_parent.len_payload)
      if _parent.flags.is_file_container
        @file_name = (@_io.read_bytes_term(0, false, true, true)).force_encoding("UTF-8")
      end
      if _parent.flags.has_extension_tags
        @extension_tags = []
        i = 0
        begin
          _ = ExtensionTag.new(@_io, self, @_root)
          @extension_tags << _
          i += 1
        end until _.len_tag == 0
      end
      self
    end

    ##
    # 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>.
    def md5_checksum
      return @md5_checksum unless @md5_checksum.nil?
      if _parent.flags.has_md5_checksum
        _pos = @_io.pos
        @_io.seek(_io.size - 24)
        @md5_checksum = Md5Checksum.new(@_io, self, @_root)
        @_io.seek(_pos)
      end
      @md5_checksum
    end

    ##
    # 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.
    attr_reader :payload
    attr_reader :file_name
    attr_reader :extension_tags
  end

  ##
  # @see https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#extension-tags Source
  class ExtensionTag < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @len_tag = @_io.read_u1
      _ = @len_tag
      raise Kaitai::Struct::ValidationExprError.new(@len_tag, @_io, "/types/extension_tag/seq/0") if not  ((_ == 0) || (_ >= min_len_tag)) 
      @tag_type = Kaitai::Struct::Stream::resolve_enum(Uf2::EXTENSION_TAG_TYPE, @_io.read_bits_int_le(24))
      @_io.align_to_byte
      if len_tag != 0
        @value = @_io.read_bytes(len_value)
      end
      @padding = @_io.read_bytes(-(len_tag) % 4)
      self
    end
    def len_value
      return @len_value unless @len_value.nil?
      @len_value = (len_tag >= min_len_tag ? len_tag - min_len_tag : 0)
      @len_value
    end
    def min_len_tag
      return @min_len_tag unless @min_len_tag.nil?
      @min_len_tag = 1 + 3
      @min_len_tag
    end

    ##
    # 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.
    attr_reader :len_tag
    attr_reader :tag_type
    attr_reader :value

    ##
    # Tags are 4-byte aligned, so a tag whose size is not a multiple
    # of 4 is followed by padding.
    attr_reader :padding
  end

  ##
  # @see https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/uf2.h#L43-L47 Source
  # @see 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 < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @value = @_io.read_u4le
      _ = @value
      raise Kaitai::Struct::ValidationExprError.new(@value, @_io, "/types/flags/seq/0") if not  ((_ & ~61441 == 0) && (!( ((is_file_container) && (has_extension_tags)) ))) 
      self
    end

    ##
    # Indicates whether extension tags are present after the payload.
    def has_extension_tags
      return @has_extension_tags unless @has_extension_tags.nil?
      @has_extension_tags = value & 32768 != 0
      @has_extension_tags
    end

    ##
    # The field at offset 28 in the block is `family_id` instead of
    # `file_size`.
    def has_family_id
      return @has_family_id unless @has_family_id.nil?
      @has_family_id = value & 8192 != 0
      @has_family_id
    end

    ##
    # Indicates whether `md5_checksum` is present at the end of `data`.
    def has_md5_checksum
      return @has_md5_checksum unless @has_md5_checksum.nil?
      @has_md5_checksum = value & 16384 != 0
      @has_md5_checksum
    end

    ##
    # 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`.
    def is_file_container
      return @is_file_container unless @is_file_container.nil?
      @is_file_container = value & 4096 != 0
      @is_file_container
    end

    ##
    # 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.
    def not_main_flash
      return @not_main_flash unless @not_main_flash.nil?
      @not_main_flash = value & 1 != 0
      @not_main_flash
    end

    ##
    # 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.
    attr_reader :value
  end

  ##
  # @see https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum Source
  class Md5Checksum < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @start_address = @_io.read_u4le
      @len_region = @_io.read_u4le
      @md5 = @_io.read_bytes(16)
      self
    end
    attr_reader :start_address
    attr_reader :len_region
    attr_reader :md5
  end
  attr_reader :first_block
  attr_reader :blocks
end
