UBIFS: Ruby parsing library

UBIFS is a file system for raw flash memory devices. It works on top of UBI (Unsorted Block Images), which works as an abstraction layer so UBIFS does not need to care about low level details. This specification only covers UBIFS, not UBI.

A UBIFS image is divided into several Logical Erase Blocks (LEB). The first LEB (LEB0) contains the superblock node, which includes information about LEB size, amongst others. The next two LEBs (LEB1 and LEB2) comprise the so called "master area" and both contain a copy of the master node, which includes information about where to find the root index node, which is then used to access all the files on the file system.

Limitations of this Kaitai Struct implementation:

  1. It only supports UBIFS images created by mkfs.ubifs (such as those in firmware images built by OpenWrt), not file systems modified after creation, e.g. by writing files to them on a running Linux system. The Linux kernel appends a new copy of the master node to LEB1 and LEB2 whenever it mounts the file system read-write, unmounts it, or commits changes (updates the index on flash to include them). When mounting, Linux uses the last copy, whereas this implementation reads the first one (master_1), which still describes the file system as it was created. Changes made since then are therefore not visible via index_root.
  2. Changes that have not been committed yet are not in the index at all, so they are not visible via index_root either. Until they are committed, UBIFS keeps them only in the journal. This only matters for file systems that were not unmounted cleanly, because unmounting commits all changes.
  3. Parsing doesn't advance the stream position. This implementation reads the image only through instances, so that parsing doesn't copy the whole image into memory. To find where the image ends, e.g. when carving it out of a larger file, use len_image. For the same reason, parsing doesn't fail if the image is truncated. To detect that, check that the stream is at least len_image bytes long (_io.size >= len_image).

KS implementation details

License: GPL-2.0-only
Minimal Kaitai Struct required: 0.11

References

This page hosts a formal specification of UBIFS using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All Ruby code generated by Kaitai Struct depends on the Kaitai Struct runtime library for Ruby. You must add this dependency to your project before you can parse or serialize any data.

The Ruby runtime library can be installed from RubyGems:

gem install kaitai-struct

Code

Parse a local file and get structure in memory:

data = Ubifs.from_file("path/to/local/file.bin")

Or parse structure from a string of bytes:

bytes = "\x00\x01\x02..."
data = Ubifs.new(Kaitai::Struct::Stream.new(bytes))

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

data.superblock # => The superblock node will always be at the start of
the first LEB.

Ruby source code to parse UBIFS

ubifs.rb

# 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


##
# UBIFS is a file system for raw flash memory devices. It works on top of
# UBI (Unsorted Block Images), which works as an abstraction layer so UBIFS
# does not need to care about low level details. This specification only covers
# UBIFS, not UBI.
# 
# A UBIFS image is divided into several Logical Erase Blocks (LEB). The first
# LEB (LEB0) contains the superblock node, which includes information about LEB
# size, amongst others. The next two LEBs (LEB1 and LEB2) comprise the so called
# "master area" and both contain a copy of the master node, which includes
# information about where to find the root index node, which is then used to
# access all the files on the file system.
# 
# **Limitations of this Kaitai Struct implementation:**
# 
# 1. It only supports UBIFS images created by `mkfs.ubifs` (such as those in
#    firmware images built by OpenWrt), not file systems modified after
#    creation, e.g. by writing files to them on a running Linux system. The
#    Linux kernel appends a new copy of the master node to LEB1 and LEB2
#    whenever it mounts the file system read-write, unmounts it, or commits
#    changes (updates the index on flash to include them). When mounting, Linux
#    uses the
#    [last copy](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L84-L165),
#    whereas this implementation reads the first one (`master_1`), which still
#    describes the file system as it was created. Changes made since then are
#    therefore not visible via `index_root`.
# 2. Changes that have not been committed yet are not in the index at all, so
#    they are not visible via `index_root` either. Until they are committed,
#    UBIFS keeps them only in the journal. This only matters for file systems
#    that were not unmounted cleanly, because unmounting
#    [commits](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/super.c#L486-L493)
#    all changes.
# 3. Parsing doesn't advance the stream position. This implementation reads
#    the image only through instances, so that parsing doesn't copy the
#    whole image into memory. To find where the image ends, e.g. when
#    carving it out of a larger file, use `len_image`. For the same reason,
#    parsing doesn't fail if the image is truncated. To detect that, check
#    that the stream is at least `len_image` bytes long
#    (`_io.size >= len_image`).
# @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h Git tag "v7.2"
# @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h Git tag "v7.2"
# @see http://www.linux-mtd.infradead.org/doc/ubifs.pdf Source
class Ubifs < Kaitai::Struct::Struct

  COMPRESSION = {
    0 => :compression_no_compression,
    1 => :compression_lzo,
    2 => :compression_zlib,
    3 => :compression_zstd,
  }
  I__COMPRESSION = COMPRESSION.invert

  HASH_ALGOS = {
    0 => :hash_algos_md4,
    1 => :hash_algos_md5,
    2 => :hash_algos_sha1,
    3 => :hash_algos_ripe_md_160,
    4 => :hash_algos_sha256,
    5 => :hash_algos_sha384,
    6 => :hash_algos_sha512,
    7 => :hash_algos_sha224,
    8 => :hash_algos_ripe_md_128,
    9 => :hash_algos_ripe_md_256,
    10 => :hash_algos_ripe_md_320,
    11 => :hash_algos_wp_256,
    12 => :hash_algos_wp_384,
    13 => :hash_algos_wp_512,
    14 => :hash_algos_tgr_128,
    15 => :hash_algos_tgr_160,
    16 => :hash_algos_tgr_192,
    17 => :hash_algos_sm3_256,
    18 => :hash_algos_streebog_256,
    19 => :hash_algos_streebog_512,
    20 => :hash_algos_sha3_256,
    21 => :hash_algos_sha3_384,
    22 => :hash_algos_sha3_512,
  }
  I__HASH_ALGOS = HASH_ALGOS.invert

  HASHES = {
    0 => :hashes_r5,
    1 => :hashes_test_hash,
  }
  I__HASHES = HASHES.invert

  INODE_TYPES = {
    0 => :inode_types_regular,
    1 => :inode_types_directory,
    2 => :inode_types_link,
    3 => :inode_types_block_device,
    4 => :inode_types_character_device,
    5 => :inode_types_fifo,
    6 => :inode_types_socket,
  }
  I__INODE_TYPES = INODE_TYPES.invert

  KEY_FORMATS = {
    0 => :key_formats_simple,
  }
  I__KEY_FORMATS = KEY_FORMATS.invert

  KEY_TYPES = {
    0 => :key_types_inode,
    1 => :key_types_data,
    2 => :key_types_directory,
    3 => :key_types_extended_attribute,
  }
  I__KEY_TYPES = KEY_TYPES.invert

  NODE_GROUP_TYPES = {
    0 => :node_group_types_no_node_group,
    1 => :node_group_types_in_node_group,
    2 => :node_group_types_last_of_node_group,
  }
  I__NODE_GROUP_TYPES = NODE_GROUP_TYPES.invert

  NODE_TYPES = {
    0 => :node_types_inode,
    1 => :node_types_data,
    2 => :node_types_directory,
    3 => :node_types_extended_attribute,
    4 => :node_types_truncation,
    5 => :node_types_padding,
    6 => :node_types_superblock,
    7 => :node_types_master,
    8 => :node_types_reference,
    9 => :node_types_index,
    10 => :node_types_commit_start,
    11 => :node_types_orphan,
    12 => :node_types_authentication,
    13 => :node_types_signature,
  }
  I__NODE_TYPES = NODE_TYPES.invert
  def initialize(_io, _parent = nil, _root = nil)
    super(_io, _parent, _root || self)
    _read
  end

  def _read
    self
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L774-L782 Git tag "v7.2"
  class AuthenticationHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @hmac = @_io.read_bytes(_root.superblock.node_header.len_hash)
      self
    end

    ##
    # HMAC authenticating the journal before this node, keyed with the
    # file system's authentication key (which is not stored on flash).
    # The kernel [uses](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L275-L276)
    # HMAC with the hash algorithm in `<superblock_header>.hash_algo`
    # ([checked when mounting](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L575-L580)),
    # so `hmac` is as long as its digest, e.g. 32 bytes for SHA-256.
    attr_reader :hmac
  end
  class Block < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @header = CommonHeader.new(@_io, self, @_root)
      case header.node_type
      when :node_types_authentication
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = AuthenticationHeader.new(_io_node_header, self, @_root)
      when :node_types_commit_start
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = CommitStartHeader.new(_io_node_header, self, @_root)
      when :node_types_data
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = DataHeader.new(_io_node_header, self, @_root)
      when :node_types_directory
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = DirectoryHeader.new(_io_node_header, self, @_root)
      when :node_types_extended_attribute
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = DirectoryHeader.new(_io_node_header, self, @_root)
      when :node_types_index
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = IndexHeader.new(_io_node_header, self, @_root)
      when :node_types_inode
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = InodeHeader.new(_io_node_header, self, @_root)
      when :node_types_master
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = MasterHeader.new(_io_node_header, self, @_root)
      when :node_types_orphan
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = OrphanHeader.new(_io_node_header, self, @_root)
      when :node_types_padding
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = PaddingHeader.new(_io_node_header, self, @_root)
      when :node_types_reference
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = ReferenceHeader.new(_io_node_header, self, @_root)
      when :node_types_signature
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = SignatureHeader.new(_io_node_header, self, @_root)
      when :node_types_superblock
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = SuperblockHeader.new(_io_node_header, self, @_root)
      when :node_types_truncation
        _io_node_header = @_io.substream(header.len_full_node - 24)
        @node_header = TruncationHeader.new(_io_node_header, self, @_root)
      else
        @node_header = @_io.read_bytes(header.len_full_node - 24)
      end
      self
    end
    attr_reader :header
    attr_reader :node_header
    attr_reader :_raw_node_header
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L801-L817 Git tag "v7.2"
  class Branch < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @target_leb = @_io.read_u4le
      @ofs_target = @_io.read_u4le
      @len_target = @_io.read_u4le
      _io_key = @_io.substream(8)
      @key = Key.new(_io_key, self, @_root)
      @hash = @_io.read_bytes(_root.superblock.node_header.len_hash)
      self
    end
    def target
      return @target unless @target.nil?
      io = _root._io
      _pos = io.pos
      io.seek(_root.superblock.node_header.leb_size * target_leb + ofs_target)
      _io_target = io.substream(len_target)
      @target = Block.new(_io_target, self, @_root)
      _ = @target
      raise Kaitai::Struct::ValidationExprError.new(@target, @_io, "/types/branch/instances/target") if not  ((_.header.len_full_node == len_target) && ((_parent.level != 0 ? _.header.node_type == :node_types_index : (Ubifs::I__NODE_TYPES[_.header.node_type] || _.header.node_type) == (Ubifs::I__KEY_TYPES[key.type] || key.type)))) 
      io.seek(_pos)
      @target
    end

    ##
    # LEB number of the target node
    attr_reader :target_leb

    ##
    # offset within the LEB `target_leb`
    attr_reader :ofs_target

    ##
    # target node length
    attr_reader :len_target
    attr_reader :key

    ##
    # Hash of the target node. It is empty in a file system without
    # authentication (see `<superblock_header>.len_hash`).
    # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L1642-L1654 Git tag "v7.2"
    attr_reader :hash
    attr_reader :_raw_key
    attr_reader :_raw_target
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L833-L841 Git tag "v7.2"
  class CommitStartHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @commit_number = @_io.read_u8le
      self
    end

    ##
    # commit number
    attr_reader :commit_number
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L443-L464 Git tag "v7.2"
  class CommonHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @magic = @_io.read_u4le
      raise Kaitai::Struct::ValidationNotEqualError.new(101718065, @magic, @_io, "/types/common_header/seq/0") if not @magic == 101718065
      @crc = @_io.read_u4le
      @sequence_number = @_io.read_u8le
      @len_full_node = @_io.read_u4le
      @node_type = Kaitai::Struct::Stream::resolve_enum(Ubifs::NODE_TYPES, @_io.read_u1)
      @group_type = Kaitai::Struct::Stream::resolve_enum(Ubifs::NODE_GROUP_TYPES, @_io.read_u1)
      @padding = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0].pack('C*'), @padding, @_io, "/types/common_header/seq/6") if not @padding == [0, 0].pack('C*')
      self
    end

    ##
    # UBIFS node magic number (`UBIFS_NODE_MAGIC`)
    attr_reader :magic

    ##
    # CRC-32 checksum of the whole node except `magic` and `crc` (i.e.
    # starting at offset 8). It is computed with an initial register value
    # of 0xFFFFFFFF and no final XOR, so it is the bitwise NOT of a standard
    # CRC-32 (e.g.
    # [`zlib.crc32()`](https://docs.python.org/3/library/zlib.html#zlib.crc32)
    # in Python).
    attr_reader :crc

    ##
    # sequence number
    attr_reader :sequence_number

    ##
    # full node length
    attr_reader :len_full_node

    ##
    # node type
    attr_reader :node_type

    ##
    # node group type
    attr_reader :group_type

    ##
    # reserved for future, zeroes
    attr_reader :padding
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L567-L584 Git tag "v7.2"
  class DataHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      _io_key = @_io.substream(16)
      @key = Key.new(_io_key, self, @_root)
      @len_uncompressed = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(1, @len_uncompressed, @_io, "/types/data_header/seq/1") if not @len_uncompressed >= 1
      raise Kaitai::Struct::ValidationGreaterThanError.new(4096, @len_uncompressed, @_io, "/types/data_header/seq/1") if not @len_uncompressed <= 4096
      @compression = Kaitai::Struct::Stream::resolve_enum(Ubifs::COMPRESSION, @_io.read_u2le)
      raise Kaitai::Struct::ValidationNotInEnumError.new(@compression, @_io, "/types/data_header/seq/2") if not Ubifs::I__COMPRESSION.key?(@compression)
      @len_compressed_data = @_io.read_u2le
      @data = @_io.read_bytes_full
      self
    end

    ##
    # node key
    attr_reader :key

    ##
    # uncompressed data size in bytes
    attr_reader :len_uncompressed

    ##
    # compression type (`compression::no_compression`, `compression::lzo`,
    # etc.)
    attr_reader :compression

    ##
    # compressed data size in bytes, only valid when data is encrypted
    attr_reader :len_compressed_data

    ##
    # One block (up to 4096 bytes) of the file, block number `key.value`,
    # compressed as given by `compression`. Blocks missing from the index
    # are holes (zeros). In an encrypted inode, the compressed data is
    # padded
    # and [encrypted](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/crypto.c#L38-L59),
    # and `len_compressed_data` is the length of the compressed data
    # before padding.
    attr_reader :data
    attr_reader :_raw_key
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L541-L565 Git tag "v7.2"
  class DirectoryHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      _io_key = @_io.substream(16)
      @key = Key.new(_io_key, self, @_root)
      _ = @key
      raise Kaitai::Struct::ValidationExprError.new(@key, @_io, "/types/directory_header/seq/0") if not  ((_.type == :key_types_directory) || (_.type == :key_types_extended_attribute)) 
      @inode_number = @_io.read_u8le
      raise Kaitai::Struct::ValidationGreaterThanError.new(4294967295, @inode_number, @_io, "/types/directory_header/seq/1") if not @inode_number <= 4294967295
      @padding1 = @_io.read_bytes(1)
      raise Kaitai::Struct::ValidationNotEqualError.new([0].pack('C*'), @padding1, @_io, "/types/directory_header/seq/2") if not @padding1 == [0].pack('C*')
      @inode_type = Kaitai::Struct::Stream::resolve_enum(Ubifs::INODE_TYPES, @_io.read_u1)
      raise Kaitai::Struct::ValidationNotInEnumError.new(@inode_type, @_io, "/types/directory_header/seq/3") if not Ubifs::I__INODE_TYPES.key?(@inode_type)
      @len_name = @_io.read_u2le
      @cookie = @_io.read_u4le
      @name = @_io.read_bytes(len_name)
      @name_terminator = @_io.read_bytes(1)
      raise Kaitai::Struct::ValidationNotEqualError.new([0].pack('C*'), @name_terminator, @_io, "/types/directory_header/seq/7") if not @name_terminator == [0].pack('C*')
      self
    end

    ##
    # node key
    attr_reader :key

    ##
    # target inode number
    attr_reader :inode_number

    ##
    # reserved for future, zeroes
    attr_reader :padding1

    ##
    # type of the target inode (`inode_types::regular`,
    # `inode_types::directory`, etc.)
    attr_reader :inode_type

    ##
    # name length
    attr_reader :len_name

    ##
    # A 32bits random number, used to construct a 64bits identifier.
    attr_reader :cookie

    ##
    # Name of the entry.
    # 
    # It is kept as bytes rather than decoded as a string: names in a
    # directory whose inode has `<inode_header>.encrypted` set are
    # ciphertext, which may contain any bytes, including zeros.
    # 
    # Unencrypted names, including all names of extended attributes, contain
    # no zero bytes, because Linux can't create such names. They aren't
    # necessarily valid UTF-8, though, as UBIFS doesn't enforce any encoding
    # of file names. A zero byte in such a name means that the image was
    # crafted, since an accidental corruption would most likely fail the
    # node's
    # [CRC check](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/io.c#L287-L293).
    attr_reader :name

    ##
    # Zero byte after `name`, [not counted in
    # `len_name`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L487-L489).
    attr_reader :name_terminator
    attr_reader :_raw_key
  end
  class Dummy < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      self
    end
  end
  class IndexBlock < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @header = CommonHeader.new(@_io, self, @_root)
      _ = @header
      raise Kaitai::Struct::ValidationExprError.new(@header, @_io, "/types/index_block/seq/0") if not _.node_type == :node_types_index
      _io_node_header = @_io.substream(header.len_full_node - 24)
      @node_header = IndexHeader.new(_io_node_header, self, @_root)
      self
    end
    attr_reader :header
    attr_reader :node_header
    attr_reader :_raw_node_header
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L819-L831 Git tag "v7.2"
  class IndexHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @num_branches = @_io.read_u2le
      @level = @_io.read_u2le
      @branches = []
      (num_branches).times { |i|
        @branches << Branch.new(@_io, self, @_root)
      }
      self
    end

    ##
    # number of child index nodes
    attr_reader :num_branches

    ##
    # tree level
    attr_reader :level

    ##
    # LEB number / offset / length / key branches
    # 
    # All branches have the same size, which is 20 bytes (with the simple
    # key format) plus the hash length in an authenticated file system.
    # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/misc.h#L186-L205 Git tag "v7.2"
    attr_reader :branches
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L480-L539 Git tag "v7.2"
  class InodeHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      _io_key = @_io.substream(16)
      @key = Key.new(_io_key, self, @_root)
      @creation_sequence_number = @_io.read_u8le
      @len_uncompressed = @_io.read_u8le
      @atime_sec = @_io.read_u8le
      @ctime_sec = @_io.read_u8le
      @mtime_sec = @_io.read_u8le
      @atime_nsec = @_io.read_u4le
      @ctime_nsec = @_io.read_u4le
      @mtime_nsec = @_io.read_u4le
      @num_links = @_io.read_u4le
      @uid = @_io.read_u4le
      @gid = @_io.read_u4le
      @mode = @_io.read_u4le
      @flags = @_io.read_u4le
      @len_data = @_io.read_u4le
      @num_xattrs = @_io.read_u4le
      @xattr_size = @_io.read_u4le
      @padding1 = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), @padding1, @_io, "/types/inode_header/seq/17") if not @padding1 == [0, 0, 0, 0].pack('C*')
      @xattr_names = @_io.read_u4le
      @compression = Kaitai::Struct::Stream::resolve_enum(Ubifs::COMPRESSION, @_io.read_u2le)
      raise Kaitai::Struct::ValidationNotInEnumError.new(@compression, @_io, "/types/inode_header/seq/19") if not Ubifs::I__COMPRESSION.key?(@compression)
      @padding2 = @_io.read_bytes(26)
      @data = @_io.read_bytes(len_data)
      self
    end

    ##
    # writes to the inode may only append data
    def append
      return @append unless @append.nil?
      @append = flags & 8 != 0
      @append
    end

    ##
    # use compression for this inode
    def compressed
      return @compressed unless @compressed.nil?
      @compressed = flags & 1 != 0
      @compressed
    end

    ##
    # I/O on this directory inode has to be synchronous
    def dirsync
      return @dirsync unless @dirsync.nil?
      @dirsync = flags & 16 != 0
      @dirsync
    end

    ##
    # use encryption for this inode
    def encrypted
      return @encrypted unless @encrypted.nil?
      @encrypted = flags & 64 != 0
      @encrypted
    end

    ##
    # inode is immutable
    def immutable
      return @immutable unless @immutable.nil?
      @immutable = flags & 4 != 0
      @immutable
    end
    def is_block_device
      return @is_block_device unless @is_block_device.nil?
      @is_block_device = mode & 61440 == 24576
      @is_block_device
    end
    def is_character_device
      return @is_character_device unless @is_character_device.nil?
      @is_character_device = mode & 61440 == 8192
      @is_character_device
    end
    def is_dir
      return @is_dir unless @is_dir.nil?
      @is_dir = mode & 61440 == 16384
      @is_dir
    end
    def is_fifo
      return @is_fifo unless @is_fifo.nil?
      @is_fifo = mode & 61440 == 4096
      @is_fifo
    end
    def is_link
      return @is_link unless @is_link.nil?
      @is_link = mode & 61440 == 40960
      @is_link
    end
    def is_regular
      return @is_regular unless @is_regular.nil?
      @is_regular = mode & 61440 == 32768
      @is_regular
    end
    def is_socket
      return @is_socket unless @is_socket.nil?
      @is_socket = mode & 61440 == 49152
      @is_socket
    end

    ##
    # I/O on this inode has to be synchronous
    def synchronous
      return @synchronous unless @synchronous.nil?
      @synchronous = flags & 2 != 0
      @synchronous
    end

    ##
    # this inode is the inode for an extended attribute value
    def xattr
      return @xattr unless @xattr.nil?
      @xattr = flags & 32 != 0
      @xattr
    end

    ##
    # node key
    attr_reader :key

    ##
    # sequence number at time of creation
    attr_reader :creation_sequence_number

    ##
    # inode size in bytes (amount of uncompressed data)
    attr_reader :len_uncompressed

    ##
    # access time seconds
    attr_reader :atime_sec

    ##
    # status change time seconds
    attr_reader :ctime_sec

    ##
    # modification time seconds
    attr_reader :mtime_sec

    ##
    # access time nanoseconds
    attr_reader :atime_nsec

    ##
    # status change time nanoseconds
    attr_reader :ctime_nsec

    ##
    # modification time nanoseconds
    attr_reader :mtime_nsec

    ##
    # number of hard links
    attr_reader :num_links

    ##
    # owner ID
    attr_reader :uid

    ##
    # group ID
    attr_reader :gid

    ##
    # file type and permissions, as in `st_mode` (see the `is_*`
    # instances for the file type)
    attr_reader :mode

    ##
    # per-inode flags (see the instances `compressed`, `synchronous`,
    # etc.)
    attr_reader :flags

    ##
    # inode data length
    attr_reader :len_data

    ##
    # count of extended attributes this inode has
    attr_reader :num_xattrs

    ##
    # summarized size of all extended attributes in bytes
    attr_reader :xattr_size
    attr_reader :padding1

    ##
    # sum of lengths of all extended attribute names belonging
    # to this inode
    attr_reader :xattr_names

    ##
    # compression type used for this inode
    attr_reader :compression

    ##
    # reserved for future, zeroes
    attr_reader :padding2

    ##
    # Target of a [symbolic link](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/dir.c#L1220-L1241),
    # (encrypted if the inode is encrypted), device number of a device node
    # ([`union ubifs_dev_desc`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L466-L478):
    # 4 bytes from Linux, 8 from `mkfs.ubifs`), or value of an
    # [extended attribute](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/xattr.c#L115-L121)
    # (in its own inode, see `xattr`). Empty otherwise.
    attr_reader :data
    attr_reader :_raw_key
  end

  ##
  # Inode nodes (`inode_header`), directory and extended attribute entry
  # nodes (`directory_header`) and data nodes (`data_header`) store keys in a
  # 16-byte field (`UBIFS_MAX_KEY_LEN`). The simple key format, the only one
  # there is (see `<superblock_header>.key_fmt`), uses only the first 8
  # bytes, and Linux (v7.2)
  # [zeroes](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h#L432-L440)
  # the rest. Branches of index nodes (`branch`)
  # [store only these 8 bytes](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h#L448-L455).
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h#L16-L22 Git tag "v7.2"
  class Key < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @inode_number = @_io.read_u4le
      @value = @_io.read_bits_int_le(29)
      @type = Kaitai::Struct::Stream::resolve_enum(Ubifs::KEY_TYPES, @_io.read_bits_int_le(3))
      raise Kaitai::Struct::ValidationNotInEnumError.new(@type, @_io, "/types/key/seq/2") if not Ubifs::I__KEY_TYPES.key?(@type)
      @_io.align_to_byte
      @unused = @_io.read_bytes_full
      self
    end

    ##
    # Number of the inode that the node belongs to: the parent directory
    # for a directory entry, the inode that the attribute belongs to for an
    # extended attribute entry.
    attr_reader :inode_number

    ##
    # Block number for a data node, hash of the name (see
    # `<superblock_header>.key_hash`) for a directory or extended
    # attribute entry, 0 for an inode node.
    attr_reader :value

    ##
    # Type of the key, which matches the type of the node.
    attr_reader :type

    ##
    # Rest of the field holding the key: 8 zero bytes in `inode_header`,
    # `directory_header` and `data_header`, empty in `branch`.
    attr_reader :unused
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L685-L756 Git tag "v7.2"
  class MasterHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @highest_inum = @_io.read_u8le
      @commit_number = @_io.read_u8le
      @flags = @_io.read_u4le
      @leb_log = @_io.read_u4le
      @leb_root = @_io.read_u4le
      @ofs_root = @_io.read_u4le
      @len_root = @_io.read_u4le
      @leb_garbage_collect = @_io.read_u4le
      @leb_index_head = @_io.read_u4le
      @ofs_index_head = @_io.read_u4le
      @len_index = @_io.read_u8le
      @total_free = @_io.read_u8le
      @total_dirty = @_io.read_u8le
      @total_used = @_io.read_u8le
      @total_dead = @_io.read_u8le
      @total_dark = @_io.read_u8le
      @leb_lpt = @_io.read_u4le
      @ofs_lpt = @_io.read_u4le
      @leb_nhead = @_io.read_u4le
      @ofs_nhead = @_io.read_u4le
      @leb_lprops_table = @_io.read_u4le
      @ofs_lprops_table = @_io.read_u4le
      @leb_save_table = @_io.read_u4le
      @ofs_save_table = @_io.read_u4le
      @leb_last_lpt_scan = @_io.read_u4le
      @num_empty_lebs = @_io.read_u4le
      @num_index_lebs = @_io.read_u4le
      @num_lebs = @_io.read_u4le
      @hash_root_index = @_io.read_bytes(64)
      @hash_lpt = @_io.read_bytes(64)
      @hmac = @_io.read_bytes(64)
      @padding = @_io.read_bytes(152)
      self
    end

    ##
    # master node is dirty
    def dirty
      return @dirty unless @dirty.nil?
      @dirty = flags & 1 != 0
      @dirty
    end

    ##
    # no orphan inodes present
    def no_orphans
      return @no_orphans unless @no_orphans.nil?
      @no_orphans = flags & 2 != 0
      @no_orphans
    end

    ##
    # written by recovery
    def recovery
      return @recovery unless @recovery.nil?
      @recovery = flags & 4 != 0
      @recovery
    end

    ##
    # highest inode number in the committed index
    attr_reader :highest_inum

    ##
    # commit number
    attr_reader :commit_number

    ##
    # various flags (see the instances `dirty`, `no_orphans` and
    # `recovery`)
    attr_reader :flags

    ##
    # start of the log
    attr_reader :leb_log

    ##
    # LEB number of the root indexing node
    attr_reader :leb_root

    ##
    # offset within the LEB `leb_root`
    attr_reader :ofs_root

    ##
    # root indexing node length
    attr_reader :len_root

    ##
    # LEB reserved for garbage collection, which must be in the main area.
    # 
    # The [comment above `struct ubifs_mst_node`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L695-L696)
    # says that -1 (`0xffff_ffff`) means the LEB was not reserved and should
    # be reserved on mount. However, Linux refuses to mount a file system
    # with -1. Linux
    # [stores](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L362)
    # the field in an
    # [`int`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L1424),
    # so `0xffff_ffff` becomes -1. Then -1 is rejected by the condition
    # `c->gc_lnum < c->main_first`, because the main area
    # [starts](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L775-L777)
    # after the superblock, master, log, LPT and orphan areas.
    # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L214-L217 Git tag "v7.2"
    attr_reader :leb_garbage_collect

    ##
    # LEB number of index head
    attr_reader :leb_index_head

    ##
    # offset of index head
    attr_reader :ofs_index_head

    ##
    # size of index on flash
    attr_reader :len_index

    ##
    # total free space in bytes
    attr_reader :total_free

    ##
    # total dirty space in bytes
    attr_reader :total_dirty

    ##
    # total used space in bytes (includes only data LEBs)
    attr_reader :total_used

    ##
    # total dead space in bytes (includes only data LEBs)
    attr_reader :total_dead

    ##
    # total dark space in bytes (includes only data LEBs)
    attr_reader :total_dark

    ##
    # LEB number of LPT root nnode
    attr_reader :leb_lpt

    ##
    # offset of LPT root nnode
    attr_reader :ofs_lpt

    ##
    # LEB number of LPT head
    attr_reader :leb_nhead

    ##
    # offset of LPT head
    attr_reader :ofs_nhead

    ##
    # LEB number of LPT's own lprops table
    attr_reader :leb_lprops_table

    ##
    # offset of LPT's own lprops table
    attr_reader :ofs_lprops_table

    ##
    # LEB number of LPT's save table. Present in every master node, but
    # only meaningful if `<superblock_header>.biglpt` is set: only then
    # does Linux [check the location of the save table](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L252-L256).
    attr_reader :leb_save_table

    ##
    # offset of LPT's save table (only meaningful if
    # `<superblock_header>.biglpt` is set, see `leb_save_table`)
    attr_reader :ofs_save_table

    ##
    # LEB number of last LPT scan
    attr_reader :leb_last_lpt_scan

    ##
    # number of empty logical eraseblocks
    attr_reader :num_empty_lebs

    ##
    # number of indexing logical eraseblocks
    attr_reader :num_index_lebs

    ##
    # count of LEBs used by file-system
    attr_reader :num_lebs

    ##
    # the hash of the root index node
    attr_reader :hash_root_index

    ##
    # the hash of the LPT
    attr_reader :hash_lpt

    ##
    # HMAC to authenticate the master node
    attr_reader :hmac

    ##
    # reserved for future, zeroes
    attr_reader :padding
  end
  class MasterblockNode < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @header = CommonHeader.new(@_io, self, @_root)
      _ = @header
      raise Kaitai::Struct::ValidationExprError.new(@header, @_io, "/types/masterblock_node/seq/0") if not _.node_type == :node_types_master
      _io_node_header = @_io.substream(header.len_full_node - 24)
      @node_header = MasterHeader.new(_io_node_header, self, @_root)
      self
    end
    attr_reader :header
    attr_reader :node_header
    attr_reader :_raw_node_header
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L843-L853 Git tag "v7.2"
  class OrphanHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @commit_number = @_io.read_u8le
      @inode_numbers = []
      i = 0
      while not @_io.eof?
        @inode_numbers << @_io.read_u8le
        i += 1
      end
      self
    end

    ##
    # commit number (also top bit is set on the last node of the commit)
    attr_reader :commit_number

    ##
    # inode numbers of orphans
    attr_reader :inode_numbers
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L606-L615 Git tag "v7.2"
  class PaddingHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @len_padding = @_io.read_u4le
      self
    end

    ##
    # how many bytes after this node are unused (because padded)
    attr_reader :len_padding
  end

  ##
  # logical eraseblock reference node.
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L758-L772 Git tag "v7.2"
  class ReferenceHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @leb_number = @_io.read_u4le
      @ofs_leb = @_io.read_u4le
      @journal_head_number = @_io.read_u4le
      @padding = @_io.read_bytes(28)
      self
    end

    ##
    # the referred logical eraseblock number
    attr_reader :leb_number

    ##
    # start offset in the referred LEB
    attr_reader :ofs_leb

    ##
    # journal head number
    attr_reader :journal_head_number

    ##
    # reserved for future, zeroes
    attr_reader :padding
  end

  ##
  # Signature of the superblock node of a file system signed when it was
  # created (`mkfs.ubifs` with a key). Linux
  # [expects it](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L198-L216)
  # as the first node after the superblock node in LEB0.
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L784-L799 Git tag "v7.2"
  class SignatureHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @type = @_io.read_u4le
      raise Kaitai::Struct::ValidationNotEqualError.new(1, @type, @_io, "/types/signature_header/seq/0") if not @type == 1
      @len_signature = @_io.read_u4le
      @padding = @_io.read_bytes(32)
      @signature = @_io.read_bytes(len_signature)
      self
    end

    ##
    # type of the signature
    attr_reader :type

    ##
    # length of `signature` in bytes
    attr_reader :len_signature

    ##
    # reserved for future, zeroes
    attr_reader :padding

    ##
    # [PKCS #7 signature](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L233-L236)
    # of the whole superblock node (4096 bytes, including the common
    # header)
    attr_reader :signature
  end

  ##
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L617-L683 Git tag "v7.2"
  class SuperblockHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @padding0 = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0].pack('C*'), @padding0, @_io, "/types/superblock_header/seq/0") if not @padding0 == [0, 0].pack('C*')
      @key_hash = Kaitai::Struct::Stream::resolve_enum(Ubifs::HASHES, @_io.read_u1)
      raise Kaitai::Struct::ValidationNotInEnumError.new(@key_hash, @_io, "/types/superblock_header/seq/1") if not Ubifs::I__HASHES.key?(@key_hash)
      @key_fmt = Kaitai::Struct::Stream::resolve_enum(Ubifs::KEY_FORMATS, @_io.read_u1)
      raise Kaitai::Struct::ValidationNotEqualError.new(:key_formats_simple, @key_fmt, @_io, "/types/superblock_header/seq/2") if not @key_fmt == :key_formats_simple
      @flags = @_io.read_u4le
      @min_io_size = @_io.read_u4le
      @leb_size = @_io.read_u4le
      @num_lebs = @_io.read_u4le
      @max_num_lebs = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(num_lebs, @max_num_lebs, @_io, "/types/superblock_header/seq/7") if not @max_num_lebs >= num_lebs
      @max_bud_bytes = @_io.read_u8le
      @num_log_lebs = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(2, @num_log_lebs, @_io, "/types/superblock_header/seq/9") if not @num_log_lebs >= 2
      @num_lpt_lebs = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(2, @num_lpt_lebs, @_io, "/types/superblock_header/seq/10") if not @num_lpt_lebs >= 2
      @num_orphan_lebs = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(1, @num_orphan_lebs, @_io, "/types/superblock_header/seq/11") if not @num_orphan_lebs >= 1
      @num_journal_heads = @_io.read_u4le
      @fanout = @_io.read_u4le
      @num_save_table_entries = @_io.read_u4le
      @fmt_version = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(4, @fmt_version, @_io, "/types/superblock_header/seq/15") if not @fmt_version >= 4
      @default_compression = Kaitai::Struct::Stream::resolve_enum(Ubifs::COMPRESSION, @_io.read_u2le)
      raise Kaitai::Struct::ValidationNotInEnumError.new(@default_compression, @_io, "/types/superblock_header/seq/16") if not Ubifs::I__COMPRESSION.key?(@default_compression)
      @padding1 = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([0, 0].pack('C*'), @padding1, @_io, "/types/superblock_header/seq/17") if not @padding1 == [0, 0].pack('C*')
      @reserve_pool_uid = @_io.read_u4le
      @reserve_pool_gid = @_io.read_u4le
      @reserve_pool_size = @_io.read_u8le
      @time_granularity = @_io.read_u4le
      raise Kaitai::Struct::ValidationLessThanError.new(1, @time_granularity, @_io, "/types/superblock_header/seq/21") if not @time_granularity >= 1
      raise Kaitai::Struct::ValidationGreaterThanError.new(1000000000, @time_granularity, @_io, "/types/superblock_header/seq/21") if not @time_granularity <= 1000000000
      @uuid = @_io.read_bytes(16)
      @ro_compat_version = @_io.read_u4le
      @hmac = @_io.read_bytes(64)
      @hmac_wkm = @_io.read_bytes(64)
      if authenticated
        @hash_algo = Kaitai::Struct::Stream::resolve_enum(Ubifs::HASH_ALGOS, @_io.read_u2le)
        raise Kaitai::Struct::ValidationNotInEnumError.new(@hash_algo, @_io, "/types/superblock_header/seq/26") if not Ubifs::I__HASH_ALGOS.key?(@hash_algo)
      end
      if !(authenticated)
        @hash_algo_unused = @_io.read_u2le
      end
      @hash_mst = @_io.read_bytes(64)
      @padding2 = @_io.read_bytes(3774)
      self
    end

    ##
    # this filesystem contains hashes for authentication
    def authenticated
      return @authenticated unless @authenticated.nil?
      @authenticated = flags & 32 != 0
      @authenticated
    end

    ##
    # if "big" LPT model is used if set
    def biglpt
      return @biglpt unless @biglpt.nil?
      @biglpt = flags & 2 != 0
      @biglpt
    end

    ##
    # store a 32bit cookie in directory entry nodes to
    # support 64bit cookies for lookups by hash
    def double_hash
      return @double_hash unless @double_hash.nil?
      @double_hash = flags & 8 != 0
      @double_hash
    end

    ##
    # filesystem contains encrypted files
    def encrypted
      return @encrypted unless @encrypted.nil?
      @encrypted = flags & 16 != 0
      @encrypted
    end

    ##
    # Length in bytes of the hash of a node (`c->hash_len` in Linux), e.g.
    # in each branch of an index node. In an authenticated file system, it
    # is determined by `hash_algo`. Otherwise, it is 0.
    def len_hash
      return @len_hash unless @len_hash.nil?
      @len_hash = (!(authenticated) ? 0 : ( ((hash_algo == :hash_algos_md4) || (hash_algo == :hash_algos_md5) || (hash_algo == :hash_algos_ripe_md_128) || (hash_algo == :hash_algos_tgr_128))  ? 16 : ( ((hash_algo == :hash_algos_sha1) || (hash_algo == :hash_algos_ripe_md_160) || (hash_algo == :hash_algos_tgr_160))  ? 20 : (hash_algo == :hash_algos_tgr_192 ? 24 : (hash_algo == :hash_algos_sha224 ? 28 : ( ((hash_algo == :hash_algos_sha256) || (hash_algo == :hash_algos_ripe_md_256) || (hash_algo == :hash_algos_wp_256) || (hash_algo == :hash_algos_sm3_256) || (hash_algo == :hash_algos_streebog_256) || (hash_algo == :hash_algos_sha3_256))  ? 32 : (hash_algo == :hash_algos_ripe_md_320 ? 40 : ( ((hash_algo == :hash_algos_sha384) || (hash_algo == :hash_algos_wp_384) || (hash_algo == :hash_algos_sha3_384))  ? 48 : ( ((hash_algo == :hash_algos_sha512) || (hash_algo == :hash_algos_wp_512) || (hash_algo == :hash_algos_streebog_512) || (hash_algo == :hash_algos_sha3_512))  ? 64 : -1)))))))))
      @len_hash
    end

    ##
    # first-mount "fixup" of free space within LEBs needed
    def space_fixup
      return @space_fixup unless @space_fixup.nil?
      @space_fixup = flags & 4 != 0
      @space_fixup
    end

    ##
    # reserved for future, zeroes
    attr_reader :padding0

    ##
    # type of hash function used in keys
    attr_reader :key_hash

    ##
    # format of the key
    attr_reader :key_fmt

    ##
    # file-system flags (see the instances `biglpt`, `space_fixup`, etc.)
    attr_reader :flags

    ##
    # minimal input/output unit size
    attr_reader :min_io_size

    ##
    # logical eraseblock size in bytes
    attr_reader :leb_size

    ##
    # count of LEBs used by file-system
    attr_reader :num_lebs

    ##
    # maximum count of LEBs used by file-system
    attr_reader :max_num_lebs

    ##
    # maximum amount of data stored in buds
    attr_reader :max_bud_bytes

    ##
    # log size in logical eraseblocks
    attr_reader :num_log_lebs

    ##
    # number of LEBs used for lprops table
    attr_reader :num_lpt_lebs

    ##
    # number of LEBs used for recording orphans
    attr_reader :num_orphan_lebs

    ##
    # count of journal heads
    attr_reader :num_journal_heads

    ##
    # tree fanout (max. number of links per indexing node)
    attr_reader :fanout

    ##
    # number of LEB numbers in LPT's save table
    attr_reader :num_save_table_entries

    ##
    # UBIFS on-flash format version
    attr_reader :fmt_version

    ##
    # default compression algorithm (`compression::lzo`, etc.)
    attr_reader :default_compression

    ##
    # reserved for future, zeroes
    attr_reader :padding1

    ##
    # reserve pool UID
    attr_reader :reserve_pool_uid

    ##
    # reserve pool GID
    attr_reader :reserve_pool_gid

    ##
    # size of the reserved pool in bytes
    attr_reader :reserve_pool_size

    ##
    # time granularity in nanoseconds
    attr_reader :time_granularity

    ##
    # UUID generated when the file system image was created
    attr_reader :uuid

    ##
    # UBIFS R/O compatibility version
    attr_reader :ro_compat_version

    ##
    # HMAC to authenticate the superblock node
    attr_reader :hmac

    ##
    # HMAC of a well known message (the string "UBIFS") as a convenience
    # to the user to check if the correct key is passed
    attr_reader :hmac_wkm

    ##
    # The hash algo used for this filesystem
    # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L549-L573 Git tag "v7.2"
    attr_reader :hash_algo

    ##
    # Unused field (would only be meaningful in an authenticated file
    # system, see `hash_algo`). The kernel writes it as 0xffff, `mkfs.ubifs`
    # writes it as 0.
    # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L183-L191 Git tag "v7.2"
    attr_reader :hash_algo_unused

    ##
    # hash of the master node, only valid for signed images in which the
    # master node does not contain a hmac
    attr_reader :hash_mst

    ##
    # reserved for future, zeroes
    attr_reader :padding2
  end
  class SuperblockNode < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @header = CommonHeader.new(@_io, self, @_root)
      _ = @header
      raise Kaitai::Struct::ValidationExprError.new(@header, @_io, "/types/superblock_node/seq/0") if not _.node_type == :node_types_superblock
      _io_node_header = @_io.substream(header.len_full_node - 24)
      @node_header = SuperblockHeader.new(_io_node_header, self, @_root)
      self
    end
    attr_reader :header
    attr_reader :node_header
    attr_reader :_raw_node_header
  end

  ##
  # This node exists only in the journal and never goes to the main area.
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L586-L604 Git tag "v7.2"
  class TruncationHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = nil)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @inode_number = @_io.read_u4le
      @padding = @_io.read_bytes(12)
      @old_size = @_io.read_u8le
      raise Kaitai::Struct::ValidationLessThanError.new(1, @old_size, @_io, "/types/truncation_header/seq/2") if not @old_size >= 1
      @new_size = @_io.read_u8le
      raise Kaitai::Struct::ValidationGreaterThanError.new(old_size - 1, @new_size, @_io, "/types/truncation_header/seq/3") if not @new_size <= old_size - 1
      self
    end

    ##
    # truncated inode number
    attr_reader :inode_number

    ##
    # reserved for future, zeroes
    attr_reader :padding

    ##
    # size before truncation
    attr_reader :old_size

    ##
    # size after truncation
    attr_reader :new_size
  end

  ##
  # The LEB containing the root index node, as given by `leb_root` in
  # `master_1`.
  # 
  # This instance only provides the substream for `index_root`, which can then
  # use `ofs_root` (an offset within the LEB) as its `pos` directly.
  def index
    return @index unless @index.nil?
    _pos = @_io.pos
    @_io.seek(superblock.node_header.leb_size * master_1.node_header.leb_root)
    _io_index = @_io.substream(superblock.node_header.leb_size)
    @index = Dummy.new(_io_index, self, @_root)
    @_io.seek(_pos)
    @index
  end

  ##
  # The root index node, in the `index` LEB at the offset given by
  # `ofs_root` in `master_1`. All other index nodes, and through them all
  # the files, are reached from it by recursively following the `target` of
  # each of the `branches`.
  def index_root
    return @index_root unless @index_root.nil?
    io = index._io
    _pos = io.pos
    io.seek(master_1.node_header.ofs_root)
    @index_root = IndexBlock.new(io, self, @_root)
    _ = @index_root
    raise Kaitai::Struct::ValidationExprError.new(@index_root, @_io, "/instances/index_root") if not _.header.len_full_node == master_1.node_header.len_root
    io.seek(_pos)
    @index_root
  end

  ##
  # All LEBs of the file system: `lebs[n]` is LEB number `n`, which starts
  # at offset `n * <superblock_header>.leb_size`.
  # 
  # Accessing this instance reads the whole image into memory. To get a
  # single LEB, it's cheaper to read `<superblock_header>.leb_size` bytes
  # at the offset of that LEB.
  def lebs
    return @lebs unless @lebs.nil?
    _pos = @_io.pos
    @_io.seek(0)
    @lebs = []
    (superblock.node_header.num_lebs).times { |i|
      @lebs << @_io.read_bytes(superblock.node_header.leb_size)
    }
    @_io.seek(_pos)
    @lebs
  end

  ##
  # Length of the file system image in bytes:
  # `<superblock_header>.num_lebs` LEBs of `<superblock_header>.leb_size`
  # bytes each. Use it to find where the image ends and to detect a
  # truncated image (see the top-level `doc`).
  def len_image
    return @len_image unless @len_image.nil?
    @len_image = superblock.node_header.leb_size * superblock.node_header.num_lebs
    @len_image
  end

  ##
  # The first copy of the master node in LEB1. This Kaitai Struct
  # implementation uses it to locate `index_root`. In an image created by
  # `mkfs.ubifs`, it is the only copy in LEB1.
  # @see https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L2627-L2633 Git tag "v2.3.1"
  def master_1
    return @master_1 unless @master_1.nil?
    _pos = @_io.pos
    @_io.seek(superblock.node_header.leb_size)
    _io_master_1 = @_io.substream(superblock.node_header.leb_size)
    @master_1 = MasterblockNode.new(_io_master_1, self, @_root)
    @_io.seek(_pos)
    @master_1
  end

  ##
  # The first copy of the master node in LEB2. In an image created by
  # `mkfs.ubifs`, it is the same as `master_1` except for the sequence
  # number and CRC in the common header. Linux keeps the master node in both
  # LEB1 and LEB2 for redundancy, and when mounting, it checks that the
  # copies in both match.
  # @see https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L2627-L2633 Git tag "v2.3.1"
  # @see https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L115-L130 Git tag "v7.2"
  def master_2
    return @master_2 unless @master_2.nil?
    _pos = @_io.pos
    @_io.seek(superblock.node_header.leb_size * 2)
    _io_master_2 = @_io.substream(superblock.node_header.leb_size)
    @master_2 = MasterblockNode.new(_io_master_2, self, @_root)
    @_io.seek(_pos)
    @master_2
  end

  ##
  # The superblock node will always be at the start of
  # the first LEB.
  def superblock
    return @superblock unless @superblock.nil?
    _pos = @_io.pos
    @_io.seek(0)
    @superblock = SuperblockNode.new(@_io, self, @_root)
    @_io.seek(_pos)
    @superblock
  end
  attr_reader :_raw_index
  attr_reader :_raw_master_1
  attr_reader :_raw_master_2
end