RPM package file: Lua parsing library

An RPM package consists of the lead, the signature (contains digests and signatures), the header (contains the package metadata) and the payload (a compressed archive of the package files).

This structure is shared by all package format versions supported by this Kaitai Struct implementation:

The versions differ mainly in the tags they use: v6 packages store all sizes as 64-bit integers, carry only cryptographic data in the signature and always use the stripped-down cpio archive format (see the payload instance).

The formats before v3, as well as the abandoned rpm5 fork, are not covered by this implementation.

Application

RPM Package Manager

File extension

["rpm", "srpm", "src.rpm", "drpm"]

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.11

References

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

Lua source code to parse RPM package file

rpm.lua

-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
--
-- This file is compatible with Lua 5.3

local class = require("class")
require("kaitaistruct")
local enum = require("enum")
local utils = require("utils")
local stringstream = require("string_stream")
local str_decode = require("string_decode")

-- 
-- An RPM package consists of the lead, the signature (contains digests and
-- signatures), the header (contains the package metadata) and the payload (a
-- compressed archive of the package files).
-- 
-- This structure is shared by all package format versions supported by this
-- Kaitai Struct implementation:
-- 
-- * v3, written by RPM 2.1 to 3.x.
-- * v4, written by RPM 4.x, and by RPM 6.x when the `%_rpmformat` macro is set
--   to 4 - see
--   <https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/man/rpmbuild-config.5.scd?plain=1#L189-L192>.
--   For example, Fedora 43 and 44 patch RPM 6.0 to keep producing v4 packages by
--   default - see
--   <https://src.fedoraproject.org/rpms/rpm/blob/7099d81c3b5ecf1777a43095be429cb198bcc566/f/rpm-6.0-rpmformat.patch>.
-- * v6, written by upstream RPM 6.0 by default - see
--   <https://github.com/rpm-software-management/rpm/commit/99d80a22d3d299bdc4418f7e61cd491731626d37>.
-- 
-- The versions differ mainly in the tags they use: v6 packages store all sizes
-- as 64-bit integers, carry only cryptographic data in the signature and always
-- use the stripped-down cpio archive format (see the `payload` instance).
-- 
-- The formats before v3, as well as the abandoned rpm5 fork, are not covered by
-- this implementation.
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_v6.md)
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_v4.md)
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_v3.md)
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/signatures_digests.md)
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/large_files.md)
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/tags.md)
-- See also: Source (https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html)
-- See also: Source (https://ftp.osuosl.org/pub/rpm/max-rpm/)
Rpm = class.class(KaitaiStruct)

Rpm.Architectures = enum.Enum {
  not_set = 0,
  x86 = 1,
  alpha = 2,
  sparc = 3,
  mips = 4,
  ppc = 5,
  m68k = 6,
  sgi = 7,
  rs6000 = 8,
  ia64 = 9,
  sparc64 = 10,
  mips64 = 11,
  arm = 12,
  m68k_mint = 13,
  s390 = 14,
  s390x = 15,
  ppc64 = 16,
  sh = 17,
  xtensa = 18,
  aarch64 = 19,
  mips_r6 = 20,
  mips64_r6 = 21,
  riscv = 22,
  loongarch64 = 23,
  e2k = 24,
  no_arch = 255,
}

Rpm.HeaderTags = enum.Enum {
  signatures = 62,
  header_immutable = 63,
  i18n_table = 100,
  name = 1000,
  version = 1001,
  release = 1002,
  epoch = 1003,
  summary = 1004,
  description = 1005,
  build_time = 1006,
  build_host = 1007,
  install_time = 1008,
  size = 1009,
  distribution = 1010,
  vendor = 1011,
  gif_obsolete = 1012,
  xpm_obsolete = 1013,
  license = 1014,
  packager = 1015,
  group = 1016,
  changelog_internal = 1017,
  source = 1018,
  patch = 1019,
  url = 1020,
  os = 1021,
  arch = 1022,
  pre_install_scriptlet = 1023,
  post_install_scriptlet = 1024,
  pre_uninstall_scriptlet = 1025,
  post_uninstall_scriptlet = 1026,
  old_file_names_obsolete = 1027,
  file_sizes = 1028,
  file_states = 1029,
  file_modes = 1030,
  file_uids_internal = 1031,
  file_gids_internal = 1032,
  device_number = 1033,
  mtimes = 1034,
  file_digests = 1035,
  link_tos = 1036,
  file_flags = 1037,
  root_internal = 1038,
  file_owner = 1039,
  file_group = 1040,
  exclude_internal = 1041,
  exclusive_internal = 1042,
  icon_obsolete = 1043,
  source_rpm = 1044,
  file_verify_flags = 1045,
  archive_size = 1046,
  provide_name = 1047,
  require_flags = 1048,
  require_name = 1049,
  require_version = 1050,
  no_source = 1051,
  no_patch = 1052,
  conflict_flags = 1053,
  conflict_name = 1054,
  conflict_version = 1055,
  default_prefix_internal = 1056,
  build_root_internal = 1057,
  install_prefix_internal = 1058,
  exclude_arch = 1059,
  exclude_os = 1060,
  exclusive_arch = 1061,
  exclusive_os = 1062,
  autoreqprov_internal = 1063,
  rpm_version = 1064,
  trigger_scripts = 1065,
  trigger_name = 1066,
  trigger_version = 1067,
  trigger_flags = 1068,
  trigger_index = 1069,
  verify_script = 1079,
  changelog_time = 1080,
  changelog_name = 1081,
  changelog_text = 1082,
  broken_md5_internal = 1083,
  prereq_internal = 1084,
  pre_install_interpreter = 1085,
  post_install_interpreter = 1086,
  pre_uninstall_interpreter = 1087,
  post_uninstall_interpreter = 1088,
  build_archs = 1089,
  obsolete_name = 1090,
  verify_script_prog = 1091,
  trigger_script_prog = 1092,
  doc_dir_internal = 1093,
  cookie = 1094,
  file_devices = 1095,
  file_inodes = 1096,
  file_langs = 1097,
  prefixes = 1098,
  install_prefixes = 1099,
  trigger_install_internal = 1100,
  trigger_uninstall_internal = 1101,
  trigger_post_uninstall_internal = 1102,
  autoreq_internal = 1103,
  autoprov_internal = 1104,
  capability_internal = 1105,
  source_package = 1106,
  old_orig_filenames_internal = 1107,
  build_prereq_internal = 1108,
  build_requires_internal = 1109,
  build_conflicts_internal = 1110,
  build_macros_internal = 1111,
  provide_flags = 1112,
  provide_version = 1113,
  obsolete_flags = 1114,
  obsolete_version = 1115,
  dir_indexes = 1116,
  base_names = 1117,
  dir_names = 1118,
  orig_dir_indexes = 1119,
  orig_base_names = 1120,
  orig_dir_names = 1121,
  opt_flags = 1122,
  dist_url = 1123,
  payload_format = 1124,
  payload_compressor = 1125,
  payload_flags = 1126,
  install_color = 1127,
  install_tid = 1128,
  remove_tid_obsolete = 1129,
  sha1_rhn_internal = 1130,
  rhn_platform_internal = 1131,
  platform = 1132,
  patches_name_obsolete = 1133,
  patches_flags_obsolete = 1134,
  patches_version_obsolete = 1135,
  cache_ctime_internal = 1136,
  cache_pkg_path_internal = 1137,
  cache_pkg_size_internal = 1138,
  cache_pkg_mtime_internal = 1139,
  file_colors = 1140,
  file_class = 1141,
  class_dict = 1142,
  file_depends_idx = 1143,
  file_depends_num = 1144,
  depends_dict = 1145,
  source_sig_md5 = 1146,
  file_contexts_obsolete = 1147,
  fs_contexts_obsolete = 1148,
  re_contexts_obsolete = 1149,
  policies = 1150,
  pre_trans = 1151,
  post_trans = 1152,
  pre_trans_prog = 1153,
  post_trans_prog = 1154,
  dist_tag = 1155,
  old_suggests_name_obsolete = 1156,
  old_suggests_version_obsolete = 1157,
  old_suggests_flags_obsolete = 1158,
  old_enhances_name_obsolete = 1159,
  old_enhances_version_obsolete = 1160,
  old_enhances_flags_obsolete = 1161,
  priority_unimplemented = 1162,
  cvsid_unimplemented = 1163,
  blink_pkgid_unimplemented = 1164,
  blink_hdrid_unimplemented = 1165,
  blink_nevra_unimplemented = 1166,
  flink_pkgid_unimplemented = 1167,
  flink_hdrid_unimplemented = 1168,
  flink_nevra_unimplemented = 1169,
  package_origin_unimplemented = 1170,
  trigger_pre_install_internal = 1171,
  build_suggests_unimplemented = 1172,
  build_enhances_unimplemented = 1173,
  script_states_unimplemented = 1174,
  script_metrics_unimplemented = 1175,
  build_cpu_clock_unimplemented = 1176,
  file_digest_algos_unimplemented = 1177,
  variants_unimplemented = 1178,
  xmajor_unimplemented = 1179,
  xminor_unimplemented = 1180,
  repo_tag_unimplemented = 1181,
  keywords_unimplemented = 1182,
  build_platforms_unimplemented = 1183,
  package_color_unimplemented = 1184,
  package_pref_color_unimplemented = 1185,
  xattrs_dict_unimplemented = 1186,
  filex_attrsx_unimplemented = 1187,
  dep_attrs_dict_unimplemented = 1188,
  conflict_attrsx_unimplemented = 1189,
  obsolete_attrsx_unimplemented = 1190,
  provide_attrsx_unimplemented = 1191,
  require_attrsx_unimplemented = 1192,
  build_provides_unimplemented = 1193,
  build_obsoletes_unimplemented = 1194,
  db_instance = 1195,
  nvra = 1196,
  file_names = 5000,
  file_provide = 5001,
  file_require = 5002,
  fs_names_unimplemented = 5003,
  fs_sizes_unimplemented = 5004,
  trigger_conds = 5005,
  trigger_type = 5006,
  orig_file_names = 5007,
  long_file_sizes = 5008,
  long_size = 5009,
  file_caps = 5010,
  file_digest_algo = 5011,
  bug_url = 5012,
  evr = 5013,
  nvr = 5014,
  nevr = 5015,
  nevra = 5016,
  header_color = 5017,
  verbose = 5018,
  epoch_num = 5019,
  pre_install_flags = 5020,
  post_install_flags = 5021,
  pre_uninstall_flags = 5022,
  post_uninstall_flags = 5023,
  pre_trans_flags = 5024,
  post_trans_flags = 5025,
  verify_script_flags = 5026,
  trigger_script_flags = 5027,
  collections_unimplemented = 5029,
  policy_names = 5030,
  policy_types = 5031,
  policy_types_indexes = 5032,
  policy_flags = 5033,
  vcs = 5034,
  order_name = 5035,
  order_version = 5036,
  order_flags = 5037,
  mssf_manifest_unimplemented = 5038,
  mssf_domain_unimplemented = 5039,
  inst_file_names = 5040,
  require_nevrs = 5041,
  provide_nevrs = 5042,
  obsolete_nevrs = 5043,
  conflict_nevrs = 5044,
  file_nlinks = 5045,
  recommend_name = 5046,
  recommend_version = 5047,
  recommend_flags = 5048,
  suggest_name = 5049,
  suggest_version = 5050,
  suggest_flags = 5051,
  supplement_name = 5052,
  supplement_version = 5053,
  supplement_flags = 5054,
  enhance_name = 5055,
  enhance_version = 5056,
  enhance_flags = 5057,
  recommend_nevrs = 5058,
  suggest_nevrs = 5059,
  supplement_nevrs = 5060,
  enhance_nevrs = 5061,
  encoding = 5062,
  file_trigger_install_internal = 5063,
  file_trigger_uninstall_internal = 5064,
  file_trigger_post_uninstall_internal = 5065,
  file_trigger_scripts = 5066,
  file_trigger_script_prog = 5067,
  file_trigger_script_flags = 5068,
  file_trigger_name = 5069,
  file_trigger_index = 5070,
  file_trigger_version = 5071,
  file_trigger_flags = 5072,
  trans_file_trigger_install_internal = 5073,
  trans_file_trigger_uninstall_internal = 5074,
  trans_file_trigger_post_uninstall_internal = 5075,
  trans_file_trigger_scripts = 5076,
  trans_file_trigger_script_prog = 5077,
  trans_file_trigger_script_flags = 5078,
  trans_file_trigger_name = 5079,
  trans_file_trigger_index = 5080,
  trans_file_trigger_version = 5081,
  trans_file_trigger_flags = 5082,
  remove_path_postfixes_internal = 5083,
  file_trigger_priorities = 5084,
  trans_file_trigger_priorities = 5085,
  file_trigger_conds = 5086,
  file_trigger_type = 5087,
  trans_file_trigger_conds = 5088,
  trans_file_trigger_type = 5089,
  file_signatures = 5090,
  file_signature_length = 5091,
  payload_sha256 = 5092,
  payload_sha256_algo_obsolete = 5093,
  auto_installed_unimplemented = 5094,
  identity_unimplemented = 5095,
  modularity_label = 5096,
  payload_sha256_alt = 5097,
  arch_suffix = 5098,
  spec = 5099,
  translation_url = 5100,
  upstream_releases = 5101,
  source_license_internal = 5102,
  pre_untrans = 5103,
  post_untrans = 5104,
  pre_untrans_prog = 5105,
  post_untrans_prog = 5106,
  pre_untrans_flags = 5107,
  post_untrans_flags = 5108,
  sys_users = 5109,
  build_system_internal = 5110,
  build_option_internal = 5111,
  payload_size = 5112,
  payload_size_alt = 5113,
  rpm_format = 5114,
  file_mime_index = 5115,
  mime_dict = 5116,
  file_mimes = 5117,
  package_digests = 5118,
  package_digest_algos = 5119,
  source_nevr = 5120,
  payload_sha512 = 5121,
  payload_sha512_alt = 5122,
  payload_sha3_256 = 5123,
  payload_sha3_256_alt = 5124,
}

Rpm.OperatingSystems = enum.Enum {
  not_set = 0,
  linux = 1,
  irix = 2,
  no_os = 255,
}

Rpm.RecordTypes = enum.Enum {
  char = 1,
  uint8 = 2,
  uint16 = 3,
  uint32 = 4,
  uint64 = 5,
  string = 6,
  bin = 7,
  string_array = 8,
  i18n_string = 9,
}

Rpm.RpmTypes = enum.Enum {
  binary = 0,
  source = 1,
}

Rpm.SignatureTags = enum.Enum {
  signatures = 62,
  header_immutable = 63,
  i18n_table = 100,
  bad_sha1_1_obsolete = 264,
  bad_sha1_2_obsolete = 265,
  dsa = 267,
  rsa = 268,
  sha1 = 269,
  long_size = 270,
  long_archive_size = 271,
  sha256 = 273,
  file_signatures = 274,
  file_signature_length = 275,
  verity_signatures = 276,
  verity_signature_algo = 277,
  openpgp = 278,
  sha3_256 = 279,
  reserved = 999,
  size = 1000,
  le_md5_1_obsolete = 1001,
  pgp = 1002,
  le_md5_2_obsolete = 1003,
  md5 = 1004,
  gpg = 1005,
  pgp5_obsolete = 1006,
  payload_size = 1007,
  reserved_space = 1008,
}

function Rpm:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Rpm:_read()
  self.lead = Rpm.Lead(self._io, self, self._root)
  self.signature = Rpm.Header(true, self._io, self, self._root)
  self.signature_padding = self._io:read_bytes(-(self._io:pos()) % 8)
  if self.ofs_header < 0 then
    self._unnamed3 = self._io:read_bytes(0)
  end
  self.header = Rpm.Header(false, self._io, self, self._root)
  if self.ofs_payload < 0 then
    self._unnamed5 = self._io:read_bytes(0)
  end
  self.signature_tags_steps = {}
  for i = 0, self.signature.header_record.num_index_records - 1 do
    self.signature_tags_steps[i + 1] = Rpm.SignatureTagsStep(i, utils.box_unwrap((i ~= 0) and utils.box_wrap(self.signature_tags_steps[(i - 1) + 1].size_tag_idx) or (-1)), utils.box_unwrap((i ~= 0) and utils.box_wrap(self.signature_tags_steps[(i - 1) + 1].long_size_tag_idx) or (-1)), self._io, self, self._root)
  end
  self.header_tags_steps = {}
  for i = 0, self.header.header_record.num_index_records - 1 do
    self.header_tags_steps[i + 1] = Rpm.HeaderTagsStep(i, utils.box_unwrap((i ~= 0) and utils.box_wrap(self.header_tags_steps[(i - 1) + 1].payload_size_tag_idx) or (-1)), self._io, self, self._root)
  end
end

Rpm.property.has_header_payload_size_tag = {}
function Rpm.property.has_header_payload_size_tag:get()
  if self._m_has_header_payload_size_tag ~= nil then
    return self._m_has_header_payload_size_tag
  end

  self._m_has_header_payload_size_tag = self.header_tags_steps[#self.header_tags_steps].payload_size_tag_idx ~= -1
  return self._m_has_header_payload_size_tag
end

Rpm.property.has_payload = {}
function Rpm.property.has_payload:get()
  if self._m_has_payload ~= nil then
    return self._m_has_payload
  end

  self._m_has_payload =  ((self.has_header_payload_size_tag) or (self.has_signature_long_size_tag) or (self.has_signature_size_tag)) 
  return self._m_has_payload
end

Rpm.property.has_signature_long_size_tag = {}
function Rpm.property.has_signature_long_size_tag:get()
  if self._m_has_signature_long_size_tag ~= nil then
    return self._m_has_signature_long_size_tag
  end

  self._m_has_signature_long_size_tag = self.signature_tags_steps[#self.signature_tags_steps].long_size_tag_idx ~= -1
  return self._m_has_signature_long_size_tag
end

Rpm.property.has_signature_size_tag = {}
function Rpm.property.has_signature_size_tag:get()
  if self._m_has_signature_size_tag ~= nil then
    return self._m_has_signature_size_tag
  end

  self._m_has_signature_size_tag = self.signature_tags_steps[#self.signature_tags_steps].size_tag_idx ~= -1
  return self._m_has_signature_size_tag
end

Rpm.property.header_payload_size_tag = {}
function Rpm.property.header_payload_size_tag:get()
  if self._m_header_payload_size_tag ~= nil then
    return self._m_header_payload_size_tag
  end

  if self.has_header_payload_size_tag then
    self._m_header_payload_size_tag = self.header.index_records[self.header_tags_steps[#self.header_tags_steps].payload_size_tag_idx + 1]
  end
  return self._m_header_payload_size_tag
end

Rpm.property.len_header = {}
function Rpm.property.len_header:get()
  if self._m_len_header ~= nil then
    return self._m_len_header
  end

  self._m_len_header = self.ofs_payload - self.ofs_header
  return self._m_len_header
end

-- 
-- Size of the (compressed) payload in bytes. v6 packages store it in
-- `header_tags::payload_size`, v4/v3 packages in `signature_tags::size`
-- (which also includes the size of the header).
-- 
-- If the header and payload together or the uncompressed payload reach
-- 4 GiB, v4 packages use `signature_tags::long_size` instead - see
-- <https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/lib/signature.cc#L182-L212>.
-- 
-- RPM never writes both (so this is just a hypothetical scenario), but if
-- both are present, `signature_tags::long_size` takes precedence over
-- `signature_tags::size`, just like in RPM's `printSize()` function:
-- <https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/lib/signature.cc#L36-L43>
Rpm.property.len_payload = {}
function Rpm.property.len_payload:get()
  if self._m_len_payload ~= nil then
    return self._m_len_payload
  end

  if self.has_payload then
    self._m_len_payload = utils.box_unwrap((self.has_header_payload_size_tag) and utils.box_wrap(self.header_payload_size_tag.body.values[0 + 1]) or (utils.box_unwrap((self.has_signature_long_size_tag) and utils.box_wrap(self.signature_long_size_tag.body.values[0 + 1] - self.len_header) or (self.signature_size_tag.body.values[0 + 1] - self.len_header))))
  end
  return self._m_len_payload
end

Rpm.property.ofs_header = {}
function Rpm.property.ofs_header:get()
  if self._m_ofs_header ~= nil then
    return self._m_ofs_header
  end

  self._m_ofs_header = self._io:pos()
  return self._m_ofs_header
end

Rpm.property.ofs_payload = {}
function Rpm.property.ofs_payload:get()
  if self._m_ofs_payload ~= nil then
    return self._m_ofs_payload
  end

  self._m_ofs_payload = self._io:pos()
  return self._m_ofs_payload
end

-- 
-- Archive of the package files, compressed using the method specified by
-- `header_tags::payload_compressor`. If this tag is missing, it's almost
-- certainly uncompressed (except for some very old v3 packages built by RPM
-- 3.0.3 or earlier, which didn't use the tag because the payload was always
-- gzipped; RPM 3.0.5 added support for bzip2 payloads and started writing
-- the tag). However, RPM reads the payload as gzip by default - see
-- <https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/lib/rpmte.cc#L643-L645>.
-- Since zlib's gzip reader passes data that is not in gzip format through
-- unchanged (see
-- <https://github.com/madler/zlib/blob/da607da739fa6047df13e66a2af6b8bec7c2a498/zlib.h#L1386-L1389>),
-- this also works for uncompressed payloads.
-- 
-- The archive format is given by `header_tags::payload_format`, which is
-- `"cpio"` for regular packages. In v4/v3 packages, it's a SVR4 cpio archive
-- without a checksum (the `070701` variant) - the [v4 format
-- documentation](https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_v4.md?plain=1#L106-L107)
-- claims "with a CRC checksum", but that's not true since RPM 2.4.4
-- (released in 1997).
-- 
-- v6 packages and v4 packages with a file over 4 GiB use a stripped-down
-- variant of cpio with the magic `07070X`. Its file headers only hold the
-- index of the file in the file lists of the RPM header, which is the only
-- place where the file names, sizes and other metadata are stored.
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_v6.md#payload)
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_v4.md#payload)
Rpm.property.payload = {}
function Rpm.property.payload:get()
  if self._m_payload ~= nil then
    return self._m_payload
  end

  if self.has_payload then
    local _pos = self._io:pos()
    self._io:seek(self.ofs_payload)
    self._m_payload = self._io:read_bytes(self.len_payload)
    self._io:seek(_pos)
  end
  return self._m_payload
end

Rpm.property.signature_long_size_tag = {}
function Rpm.property.signature_long_size_tag:get()
  if self._m_signature_long_size_tag ~= nil then
    return self._m_signature_long_size_tag
  end

  if self.has_signature_long_size_tag then
    self._m_signature_long_size_tag = self.signature.index_records[self.signature_tags_steps[#self.signature_tags_steps].long_size_tag_idx + 1]
  end
  return self._m_signature_long_size_tag
end

Rpm.property.signature_size_tag = {}
function Rpm.property.signature_size_tag:get()
  if self._m_signature_size_tag ~= nil then
    return self._m_signature_size_tag
  end

  if self.has_signature_size_tag then
    self._m_signature_size_tag = self.signature.index_records[self.signature_tags_steps[#self.signature_tags_steps].size_tag_idx + 1]
  end
  return self._m_signature_size_tag
end


Rpm.Dummy = class.class(KaitaiStruct)

function Rpm.Dummy:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Rpm.Dummy:_read()
end


-- 
-- header structure used for both the "header" and "signature", but some tag
-- values have different meanings in signature and header (hence they use
-- different enums)
Rpm.Header = class.class(KaitaiStruct)

function Rpm.Header:_init(is_signature, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.is_signature = is_signature
  self:_read()
end

function Rpm.Header:_read()
  self.header_record = Rpm.HeaderRecord(self._io, self, self._root)
  self.index_records = {}
  for i = 0, self.header_record.num_index_records - 1 do
    self.index_records[i + 1] = Rpm.HeaderIndexRecord(self._io, self, self._root)
  end
  self._raw_storage_section = self._io:read_bytes(self.header_record.len_storage_section)
  local _io = KaitaiStream(stringstream(self._raw_storage_section))
  self.storage_section = Rpm.Dummy(_io, self, self._root)
end

Rpm.Header.property.is_header = {}
function Rpm.Header.property.is_header:get()
  if self._m_is_header ~= nil then
    return self._m_is_header
  end

  self._m_is_header = not(self.is_signature)
  return self._m_is_header
end


Rpm.HeaderIndexRecord = class.class(KaitaiStruct)

function Rpm.HeaderIndexRecord:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Rpm.HeaderIndexRecord:_read()
  self.tag_raw = self._io:read_u4be()
  self.record_type = Rpm.RecordTypes(self._io:read_u4be())
  if self.record_type == nil then
    error("ValidationNotInEnumError")
  end
  self.ofs_body = self._io:read_u4be()
  self.count = self._io:read_u4be()
end

Rpm.HeaderIndexRecord.property.body = {}
function Rpm.HeaderIndexRecord.property.body:get()
  if self._m_body ~= nil then
    return self._m_body
  end

  local _io = self._parent.storage_section._io
  local _pos = _io:pos()
  _io:seek(self.ofs_body)
  local _on = self.record_type
  if _on == Rpm.RecordTypes.bin then
    self._m_body = Rpm.RecordTypeBin(self.len_value, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.char then
    self._m_body = Rpm.RecordTypeUint8(self.num_values, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.i18n_string then
    self._m_body = Rpm.RecordTypeStringArray(self.num_values, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.string then
    self._m_body = Rpm.RecordTypeString(_io, self, self._root)
  elseif _on == Rpm.RecordTypes.string_array then
    self._m_body = Rpm.RecordTypeStringArray(self.num_values, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.uint16 then
    self._m_body = Rpm.RecordTypeUint16(self.num_values, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.uint32 then
    self._m_body = Rpm.RecordTypeUint32(self.num_values, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.uint64 then
    self._m_body = Rpm.RecordTypeUint64(self.num_values, _io, self, self._root)
  elseif _on == Rpm.RecordTypes.uint8 then
    self._m_body = Rpm.RecordTypeUint8(self.num_values, _io, self, self._root)
  end
  _io:seek(_pos)
  return self._m_body
end

Rpm.HeaderIndexRecord.property.header_tag = {}
function Rpm.HeaderIndexRecord.property.header_tag:get()
  if self._m_header_tag ~= nil then
    return self._m_header_tag
  end

  if self._parent.is_header then
    self._m_header_tag = Rpm.HeaderTags(self.tag_raw)
  end
  return self._m_header_tag
end

Rpm.HeaderIndexRecord.property.len_value = {}
function Rpm.HeaderIndexRecord.property.len_value:get()
  if self._m_len_value ~= nil then
    return self._m_len_value
  end

  if self.record_type == Rpm.RecordTypes.bin then
    self._m_len_value = self.count
  end
  return self._m_len_value
end

Rpm.HeaderIndexRecord.property.num_values = {}
function Rpm.HeaderIndexRecord.property.num_values:get()
  if self._m_num_values ~= nil then
    return self._m_num_values
  end

  if self.record_type ~= Rpm.RecordTypes.bin then
    self._m_num_values = self.count
  end
  return self._m_num_values
end

Rpm.HeaderIndexRecord.property.signature_tag = {}
function Rpm.HeaderIndexRecord.property.signature_tag:get()
  if self._m_signature_tag ~= nil then
    return self._m_signature_tag
  end

  if self._parent.is_signature then
    self._m_signature_tag = Rpm.SignatureTags(self.tag_raw)
  end
  return self._m_signature_tag
end

-- 
-- prefer to access `signature_tag` and `header_tag` instead.
-- 
-- internal; access `num_values` and `len_value` instead.

Rpm.HeaderRecord = class.class(KaitaiStruct)

function Rpm.HeaderRecord:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Rpm.HeaderRecord:_read()
  self.magic = self._io:read_bytes(4)
  if not(self.magic == "\142\173\232\001") then
    error("not equal, expected " .. "\142\173\232\001" .. ", but got " .. self.magic)
  end
  self.reserved = self._io:read_bytes(4)
  if not(self.reserved == "\000\000\000\000") then
    error("not equal, expected " .. "\000\000\000\000" .. ", but got " .. self.reserved)
  end
  self.num_index_records = self._io:read_u4be()
  if not(self.num_index_records >= 1) then
    error("ValidationLessThanError")
  end
  if not(self.num_index_records <= utils.box_unwrap((self._parent.is_signature) and utils.box_wrap(32) or (65535))) then
    error("ValidationGreaterThanError")
  end
  self.len_storage_section = self._io:read_u4be()
  if not(self.len_storage_section <= utils.box_unwrap((self._parent.is_signature) and utils.box_wrap((64 * 1024) * 1024) or (268435455))) then
    error("ValidationGreaterThanError")
  end
end

-- 
-- Size of the storage area for the data
-- pointed to by the Index Records.

-- 
-- Like `signature_tags_step`, but looks for `header_tags::payload_size`,
-- which is where v6 packages store the payload size.
Rpm.HeaderTagsStep = class.class(KaitaiStruct)

function Rpm.HeaderTagsStep:_init(idx, prev_payload_size_tag_idx, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.idx = idx
  self.prev_payload_size_tag_idx = prev_payload_size_tag_idx
  self:_read()
end

function Rpm.HeaderTagsStep:_read()
end

Rpm.HeaderTagsStep.property.payload_size_tag_idx = {}
function Rpm.HeaderTagsStep.property.payload_size_tag_idx:get()
  if self._m_payload_size_tag_idx ~= nil then
    return self._m_payload_size_tag_idx
  end

  self._m_payload_size_tag_idx = utils.box_unwrap((self.prev_payload_size_tag_idx ~= -1) and utils.box_wrap(self.prev_payload_size_tag_idx) or (utils.box_unwrap(( ((self._parent.header.index_records[self.idx + 1].header_tag == Rpm.HeaderTags.payload_size) and (self._parent.header.index_records[self.idx + 1].record_type == Rpm.RecordTypes.uint64) and (self._parent.header.index_records[self.idx + 1].num_values >= 1)) ) and utils.box_wrap(self.idx) or (-1))))
  return self._m_payload_size_tag_idx
end


-- 
-- In 2021, Panu Matilainen (an RPM developer) [described this
-- structure](https://github.com/kaitai-io/kaitai_struct_formats/pull/469#discussion_r718288192)
-- as follows:
-- 
-- > The lead as a structure is 25 years obsolete, the data there is
-- > meaningless. Seriously. Except to check for the magic to detect that
-- > it's an rpm file in the first place, just ignore everything in it.
-- > Literally everything.
-- 
-- RPM 4.19 and older rejected packages that didn't meet the `valid`
-- constraints specified here, while RPM 4.20 and later only check the
-- `magic` - see
-- <https://github.com/rpm-software-management/rpm/commit/b3449a0774487a091bbe59e821b4004b06d4fa66>.
-- Nevertheless, RPM still writes values that pass these checks for backwards
-- compatibility, so any `.rpm` file should pass.
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/docs/manual/format_lead.md)
Rpm.Lead = class.class(KaitaiStruct)

function Rpm.Lead:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Rpm.Lead:_read()
  self.magic = self._io:read_bytes(4)
  if not(self.magic == "\237\171\238\219") then
    error("not equal, expected " .. "\237\171\238\219" .. ", but got " .. self.magic)
  end
  self.version = Rpm.RpmVersion(self._io, self, self._root)
  self.type = Rpm.RpmTypes(self._io:read_u2be())
  self.architecture = Rpm.Architectures(self._io:read_u2be())
  self.package_name = str_decode.decode(KaitaiStream.bytes_terminate(self._io:read_bytes(66), 0, false), "UTF-8")
  self.os = Rpm.OperatingSystems(self._io:read_u2be())
  self.signature_type = self._io:read_u2be()
  if not(self.signature_type == 5) then
    error("not equal, expected " .. 5 .. ", but got " .. self.signature_type)
  end
  self.reserved = self._io:read_bytes(16)
end

-- 
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/lib/rpmlead.cc#L20-L21)

Rpm.RecordTypeBin = class.class(KaitaiStruct)

function Rpm.RecordTypeBin:_init(len_value, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.len_value = len_value
  self:_read()
end

function Rpm.RecordTypeBin:_read()
  self.values = {}
  for i = 0, 1 - 1 do
    self.values[i + 1] = self._io:read_bytes(self.len_value)
  end
end


Rpm.RecordTypeString = class.class(KaitaiStruct)

function Rpm.RecordTypeString:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Rpm.RecordTypeString:_read()
  self.values = {}
  for i = 0, 1 - 1 do
    self.values[i + 1] = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "UTF-8")
  end
end


Rpm.RecordTypeStringArray = class.class(KaitaiStruct)

function Rpm.RecordTypeStringArray:_init(num_values, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.num_values = num_values
  self:_read()
end

function Rpm.RecordTypeStringArray:_read()
  self.values = {}
  for i = 0, self.num_values - 1 do
    self.values[i + 1] = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "UTF-8")
  end
end


Rpm.RecordTypeUint16 = class.class(KaitaiStruct)

function Rpm.RecordTypeUint16:_init(num_values, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.num_values = num_values
  self:_read()
end

function Rpm.RecordTypeUint16:_read()
  self.values = {}
  for i = 0, self.num_values - 1 do
    self.values[i + 1] = self._io:read_u2be()
  end
end


Rpm.RecordTypeUint32 = class.class(KaitaiStruct)

function Rpm.RecordTypeUint32:_init(num_values, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.num_values = num_values
  self:_read()
end

function Rpm.RecordTypeUint32:_read()
  self.values = {}
  for i = 0, self.num_values - 1 do
    self.values[i + 1] = self._io:read_u4be()
  end
end


Rpm.RecordTypeUint64 = class.class(KaitaiStruct)

function Rpm.RecordTypeUint64:_init(num_values, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.num_values = num_values
  self:_read()
end

function Rpm.RecordTypeUint64:_read()
  self.values = {}
  for i = 0, self.num_values - 1 do
    self.values[i + 1] = self._io:read_u8be()
  end
end


Rpm.RecordTypeUint8 = class.class(KaitaiStruct)

function Rpm.RecordTypeUint8:_init(num_values, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.num_values = num_values
  self:_read()
end

function Rpm.RecordTypeUint8:_read()
  self.values = {}
  for i = 0, self.num_values - 1 do
    self.values[i + 1] = self._io:read_u1()
  end
end


Rpm.RpmVersion = class.class(KaitaiStruct)

function Rpm.RpmVersion:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Rpm.RpmVersion:_read()
  self.major = self._io:read_u1()
  if not(self.major >= 3) then
    error("ValidationLessThanError")
  end
  if not(self.major <= 4) then
    error("ValidationGreaterThanError")
  end
  self.minor = self._io:read_u1()
end

-- 
-- 3 in v3 and v4 packages, 4 in v6 packages.
-- See also: Source (https://github.com/rpm-software-management/rpm/blob/ec9ea8c43808c346da4b6cb454cdc58aef8e506a/lib/rpmlead.cc#L51-L52)

-- 
-- Finds the first `signature_tags::size` and `signature_tags::long_size`
-- index record. Since Kaitai Struct doesn't have a built-in way to search an
-- array directly, each step receives the indexes found so far via
-- parameters.
Rpm.SignatureTagsStep = class.class(KaitaiStruct)

function Rpm.SignatureTagsStep:_init(idx, prev_size_tag_idx, prev_long_size_tag_idx, io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self.idx = idx
  self.prev_size_tag_idx = prev_size_tag_idx
  self.prev_long_size_tag_idx = prev_long_size_tag_idx
  self:_read()
end

function Rpm.SignatureTagsStep:_read()
end

Rpm.SignatureTagsStep.property.long_size_tag_idx = {}
function Rpm.SignatureTagsStep.property.long_size_tag_idx:get()
  if self._m_long_size_tag_idx ~= nil then
    return self._m_long_size_tag_idx
  end

  self._m_long_size_tag_idx = utils.box_unwrap((self.prev_long_size_tag_idx ~= -1) and utils.box_wrap(self.prev_long_size_tag_idx) or (utils.box_unwrap(( ((self._parent.signature.index_records[self.idx + 1].signature_tag == Rpm.SignatureTags.long_size) and (self._parent.signature.index_records[self.idx + 1].record_type == Rpm.RecordTypes.uint64) and (self._parent.signature.index_records[self.idx + 1].num_values >= 1)) ) and utils.box_wrap(self.idx) or (-1))))
  return self._m_long_size_tag_idx
end

Rpm.SignatureTagsStep.property.size_tag_idx = {}
function Rpm.SignatureTagsStep.property.size_tag_idx:get()
  if self._m_size_tag_idx ~= nil then
    return self._m_size_tag_idx
  end

  self._m_size_tag_idx = utils.box_unwrap((self.prev_size_tag_idx ~= -1) and utils.box_wrap(self.prev_size_tag_idx) or (utils.box_unwrap(( ((self._parent.signature.index_records[self.idx + 1].signature_tag == Rpm.SignatureTags.size) and (self._parent.signature.index_records[self.idx + 1].record_type == Rpm.RecordTypes.uint32) and (self._parent.signature.index_records[self.idx + 1].num_values >= 1)) ) and utils.box_wrap(self.idx) or (-1))))
  return self._m_size_tag_idx
end