Android Dynamic Partitions metadata: Nim parsing library

The metadata stored by Android at the beginning of a "super" partition, which is what it calls a disk partition that holds one or more Dynamic Partitions. Dynamic Partitions do more or less the same thing that LVM does on Linux, allowing Android to map ranges of non-contiguous extents to a single logical device. This metadata holds that mapping.

Application

Android

File extension

img

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.9

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

Nim source code to parse Android Dynamic Partitions metadata

android_super.nim

import kaitai_struct_nim_runtime
import options

type
  AndroidSuper* = ref object of KaitaiStruct
    `parent`*: KaitaiStruct
    `rootInst`*: AndroidSuper_Root
  AndroidSuper_Root* = ref object of KaitaiStruct
    `primaryGeometry`*: AndroidSuper_Geometry
    `backupGeometry`*: AndroidSuper_Geometry
    `primaryMetadata`*: seq[AndroidSuper_Metadata]
    `backupMetadata`*: seq[AndroidSuper_Metadata]
    `parent`*: AndroidSuper
    `rawPrimaryGeometry`*: seq[byte]
    `rawBackupGeometry`*: seq[byte]
    `rawPrimaryMetadata`*: seq[seq[byte]]
    `rawBackupMetadata`*: seq[seq[byte]]
  AndroidSuper_Geometry* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `structSize`*: uint32
    `checksum`*: seq[byte]
    `metadataMaxSize`*: uint32
    `metadataSlotCount`*: uint32
    `logicalBlockSize`*: uint32
    `parent`*: AndroidSuper_Root
  AndroidSuper_Metadata* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `majorVersion`*: uint16
    `minorVersion`*: uint16
    `headerSize`*: uint32
    `headerChecksum`*: seq[byte]
    `tablesSize`*: uint32
    `tablesChecksum`*: seq[byte]
    `partitions`*: AndroidSuper_Metadata_TableDescriptor
    `extents`*: AndroidSuper_Metadata_TableDescriptor
    `groups`*: AndroidSuper_Metadata_TableDescriptor
    `blockDevices`*: AndroidSuper_Metadata_TableDescriptor
    `parent`*: AndroidSuper_Root
  AndroidSuper_Metadata_TableKind* = enum
    partitions = 0
    extents = 1
    groups = 2
    block_devices = 3
  AndroidSuper_Metadata_BlockDevice* = ref object of KaitaiStruct
    `firstLogicalSector`*: uint64
    `alignment`*: uint32
    `alignmentOffset`*: uint32
    `size`*: uint64
    `partitionName`*: string
    `flagSlotSuffixed`*: bool
    `flagsReserved`*: uint64
    `parent`*: AndroidSuper_Metadata_TableDescriptor
  AndroidSuper_Metadata_Extent* = ref object of KaitaiStruct
    `numSectors`*: uint64
    `targetType`*: AndroidSuper_Metadata_Extent_TargetType
    `targetData`*: uint64
    `targetSource`*: uint32
    `parent`*: AndroidSuper_Metadata_TableDescriptor
  AndroidSuper_Metadata_Extent_TargetType* = enum
    linear = 0
    zero = 1
  AndroidSuper_Metadata_TableDescriptor* = ref object of KaitaiStruct
    `offset`*: uint32
    `numEntries`*: uint32
    `entrySize`*: uint32
    `kind`*: AndroidSuper_Metadata_TableKind
    `parent`*: AndroidSuper_Metadata
    `rawTableInst`*: seq[seq[byte]]
    `tableInst`*: seq[KaitaiStruct]
  AndroidSuper_Metadata_Partition* = ref object of KaitaiStruct
    `name`*: string
    `attrReadonly`*: bool
    `attrSlotSuffixed`*: bool
    `attrUpdated`*: bool
    `attrDisabled`*: bool
    `attrsReserved`*: uint64
    `firstExtentIndex`*: uint32
    `numExtents`*: uint32
    `groupIndex`*: uint32
    `parent`*: AndroidSuper_Metadata_TableDescriptor
  AndroidSuper_Metadata_Group* = ref object of KaitaiStruct
    `name`*: string
    `flagSlotSuffixed`*: bool
    `flagsReserved`*: uint64
    `maximumSize`*: uint64
    `parent`*: AndroidSuper_Metadata_TableDescriptor

proc read*(_: typedesc[AndroidSuper], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): AndroidSuper
proc read*(_: typedesc[AndroidSuper_Root], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper): AndroidSuper_Root
proc read*(_: typedesc[AndroidSuper_Geometry], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Root): AndroidSuper_Geometry
proc read*(_: typedesc[AndroidSuper_Metadata], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Root): AndroidSuper_Metadata
proc read*(_: typedesc[AndroidSuper_Metadata_BlockDevice], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_BlockDevice
proc read*(_: typedesc[AndroidSuper_Metadata_Extent], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_Extent
proc read*(_: typedesc[AndroidSuper_Metadata_TableDescriptor], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata, kind: any): AndroidSuper_Metadata_TableDescriptor
proc read*(_: typedesc[AndroidSuper_Metadata_Partition], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_Partition
proc read*(_: typedesc[AndroidSuper_Metadata_Group], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_Group

proc root*(this: AndroidSuper): AndroidSuper_Root
proc table*(this: AndroidSuper_Metadata_TableDescriptor): seq[KaitaiStruct]


##[
The metadata stored by Android at the beginning of a "super" partition, which
is what it calls a disk partition that holds one or more Dynamic Partitions.
Dynamic Partitions do more or less the same thing that LVM does on Linux,
allowing Android to map ranges of non-contiguous extents to a single logical
device. This metadata holds that mapping.

@see <a href="https://source.android.com/devices/tech/ota/dynamic_partitions">Source</a>
@see <a href="https://android.googlesource.com/platform/system/core/+/refs/tags/android-11.0.0_r8/fs_mgr/liblp/include/liblp/metadata_format.h">Source</a>
]##
proc read*(_: typedesc[AndroidSuper], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): AndroidSuper =
  template this: untyped = result
  this = new(AndroidSuper)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent


proc root(this: AndroidSuper): AndroidSuper_Root = 
  if this.rootInst != nil:
    return this.rootInst
  let pos = this.io.pos()
  this.io.seek(int(4096))
  let rootInstExpr = AndroidSuper_Root.read(this.io, this.root, this)
  this.rootInst = rootInstExpr
  this.io.seek(pos)
  if this.rootInst != nil:
    return this.rootInst

proc fromFile*(_: typedesc[AndroidSuper], filename: string): AndroidSuper =
  AndroidSuper.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Root], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper): AndroidSuper_Root =
  template this: untyped = result
  this = new(AndroidSuper_Root)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let rawPrimaryGeometryExpr = this.io.readBytes(int(4096))
  this.rawPrimaryGeometry = rawPrimaryGeometryExpr
  let rawPrimaryGeometryIo = newKaitaiStream(rawPrimaryGeometryExpr)
  let primaryGeometryExpr = AndroidSuper_Geometry.read(rawPrimaryGeometryIo, this.root, this)
  this.primaryGeometry = primaryGeometryExpr
  let rawBackupGeometryExpr = this.io.readBytes(int(4096))
  this.rawBackupGeometry = rawBackupGeometryExpr
  let rawBackupGeometryIo = newKaitaiStream(rawBackupGeometryExpr)
  let backupGeometryExpr = AndroidSuper_Geometry.read(rawBackupGeometryIo, this.root, this)
  this.backupGeometry = backupGeometryExpr
  for i in 0 ..< int(this.primaryGeometry.metadataSlotCount):
    let buf = this.io.readBytes(int(this.primaryGeometry.metadataMaxSize))
    this.rawPrimaryMetadata.add(buf)
    let rawPrimaryMetadataIo = newKaitaiStream(buf)
    let it = AndroidSuper_Metadata.read(rawPrimaryMetadataIo, this.root, this)
    this.primaryMetadata.add(it)
  for i in 0 ..< int(this.primaryGeometry.metadataSlotCount):
    let buf = this.io.readBytes(int(this.primaryGeometry.metadataMaxSize))
    this.rawBackupMetadata.add(buf)
    let rawBackupMetadataIo = newKaitaiStream(buf)
    let it = AndroidSuper_Metadata.read(rawBackupMetadataIo, this.root, this)
    this.backupMetadata.add(it)

proc fromFile*(_: typedesc[AndroidSuper_Root], filename: string): AndroidSuper_Root =
  AndroidSuper_Root.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Geometry], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Root): AndroidSuper_Geometry =
  template this: untyped = result
  this = new(AndroidSuper_Geometry)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(4))
  this.magic = magicExpr
  let structSizeExpr = this.io.readU4le()
  this.structSize = structSizeExpr

  ##[
  SHA-256 hash of struct_size bytes from beginning of geometry,
calculated as if checksum were zeroed out

  ]##
  let checksumExpr = this.io.readBytes(int(32))
  this.checksum = checksumExpr
  let metadataMaxSizeExpr = this.io.readU4le()
  this.metadataMaxSize = metadataMaxSizeExpr
  let metadataSlotCountExpr = this.io.readU4le()
  this.metadataSlotCount = metadataSlotCountExpr
  let logicalBlockSizeExpr = this.io.readU4le()
  this.logicalBlockSize = logicalBlockSizeExpr

proc fromFile*(_: typedesc[AndroidSuper_Geometry], filename: string): AndroidSuper_Geometry =
  AndroidSuper_Geometry.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Metadata], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Root): AndroidSuper_Metadata =
  template this: untyped = result
  this = new(AndroidSuper_Metadata)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(4))
  this.magic = magicExpr
  let majorVersionExpr = this.io.readU2le()
  this.majorVersion = majorVersionExpr
  let minorVersionExpr = this.io.readU2le()
  this.minorVersion = minorVersionExpr
  let headerSizeExpr = this.io.readU4le()
  this.headerSize = headerSizeExpr

  ##[
  SHA-256 hash of header_size bytes from beginning of metadata,
calculated as if header_checksum were zeroed out

  ]##
  let headerChecksumExpr = this.io.readBytes(int(32))
  this.headerChecksum = headerChecksumExpr
  let tablesSizeExpr = this.io.readU4le()
  this.tablesSize = tablesSizeExpr

  ##[
  SHA-256 hash of tables_size bytes from end of header
  ]##
  let tablesChecksumExpr = this.io.readBytes(int(32))
  this.tablesChecksum = tablesChecksumExpr
  let partitionsExpr = AndroidSuper_Metadata_TableDescriptor.read(this.io, this.root, this, android_super.partitions)
  this.partitions = partitionsExpr
  let extentsExpr = AndroidSuper_Metadata_TableDescriptor.read(this.io, this.root, this, android_super.extents)
  this.extents = extentsExpr
  let groupsExpr = AndroidSuper_Metadata_TableDescriptor.read(this.io, this.root, this, android_super.groups)
  this.groups = groupsExpr
  let blockDevicesExpr = AndroidSuper_Metadata_TableDescriptor.read(this.io, this.root, this, android_super.block_devices)
  this.blockDevices = blockDevicesExpr

proc fromFile*(_: typedesc[AndroidSuper_Metadata], filename: string): AndroidSuper_Metadata =
  AndroidSuper_Metadata.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Metadata_BlockDevice], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_BlockDevice =
  template this: untyped = result
  this = new(AndroidSuper_Metadata_BlockDevice)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let firstLogicalSectorExpr = this.io.readU8le()
  this.firstLogicalSector = firstLogicalSectorExpr
  let alignmentExpr = this.io.readU4le()
  this.alignment = alignmentExpr
  let alignmentOffsetExpr = this.io.readU4le()
  this.alignmentOffset = alignmentOffsetExpr
  let sizeExpr = this.io.readU8le()
  this.size = sizeExpr
  let partitionNameExpr = encode(this.io.readBytes(int(36)).bytesTerminate(0, false), "UTF-8")
  this.partitionName = partitionNameExpr
  let flagSlotSuffixedExpr = this.io.readBitsIntLe(1) != 0
  this.flagSlotSuffixed = flagSlotSuffixedExpr
  let flagsReservedExpr = this.io.readBitsIntLe(31)
  this.flagsReserved = flagsReservedExpr

proc fromFile*(_: typedesc[AndroidSuper_Metadata_BlockDevice], filename: string): AndroidSuper_Metadata_BlockDevice =
  AndroidSuper_Metadata_BlockDevice.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Metadata_Extent], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_Extent =
  template this: untyped = result
  this = new(AndroidSuper_Metadata_Extent)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let numSectorsExpr = this.io.readU8le()
  this.numSectors = numSectorsExpr
  let targetTypeExpr = AndroidSuper_Metadata_Extent_TargetType(this.io.readU4le())
  this.targetType = targetTypeExpr
  let targetDataExpr = this.io.readU8le()
  this.targetData = targetDataExpr
  let targetSourceExpr = this.io.readU4le()
  this.targetSource = targetSourceExpr

proc fromFile*(_: typedesc[AndroidSuper_Metadata_Extent], filename: string): AndroidSuper_Metadata_Extent =
  AndroidSuper_Metadata_Extent.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Metadata_TableDescriptor], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata, kind: any): AndroidSuper_Metadata_TableDescriptor =
  template this: untyped = result
  this = new(AndroidSuper_Metadata_TableDescriptor)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent
  let kindExpr = AndroidSuper_Metadata_TableKind(kind)
  this.kind = kindExpr

  let offsetExpr = this.io.readU4le()
  this.offset = offsetExpr
  let numEntriesExpr = this.io.readU4le()
  this.numEntries = numEntriesExpr
  let entrySizeExpr = this.io.readU4le()
  this.entrySize = entrySizeExpr

proc table(this: AndroidSuper_Metadata_TableDescriptor): seq[KaitaiStruct] = 
  if this.tableInst.len != 0:
    return this.tableInst
  let pos = this.io.pos()
  this.io.seek(int((this.parent.headerSize + this.offset)))
  for i in 0 ..< int(this.numEntries):
    block:
      let on = this.kind
      if on == android_super.partitions:
        let buf = this.io.readBytes(int(this.entrySize))
        this.rawTableInst.add(buf)
        let rawTableInstIo = newKaitaiStream(buf)
        let it = AndroidSuper_Metadata_Partition.read(rawTableInstIo, this.root, this)
        this.tableInst.add(it)
      elif on == android_super.extents:
        let buf = this.io.readBytes(int(this.entrySize))
        this.rawTableInst.add(buf)
        let rawTableInstIo = newKaitaiStream(buf)
        let it = AndroidSuper_Metadata_Extent.read(rawTableInstIo, this.root, this)
        this.tableInst.add(it)
      elif on == android_super.groups:
        let buf = this.io.readBytes(int(this.entrySize))
        this.rawTableInst.add(buf)
        let rawTableInstIo = newKaitaiStream(buf)
        let it = AndroidSuper_Metadata_Group.read(rawTableInstIo, this.root, this)
        this.tableInst.add(it)
      elif on == android_super.block_devices:
        let buf = this.io.readBytes(int(this.entrySize))
        this.rawTableInst.add(buf)
        let rawTableInstIo = newKaitaiStream(buf)
        let it = AndroidSuper_Metadata_BlockDevice.read(rawTableInstIo, this.root, this)
        this.tableInst.add(it)
      else:
        let it = this.io.readBytes(int(this.entrySize))
        this.tableInst.add(it)
  this.io.seek(pos)
  if this.tableInst.len != 0:
    return this.tableInst

proc fromFile*(_: typedesc[AndroidSuper_Metadata_TableDescriptor], filename: string): AndroidSuper_Metadata_TableDescriptor =
  AndroidSuper_Metadata_TableDescriptor.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Metadata_Partition], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_Partition =
  template this: untyped = result
  this = new(AndroidSuper_Metadata_Partition)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let nameExpr = encode(this.io.readBytes(int(36)).bytesTerminate(0, false), "UTF-8")
  this.name = nameExpr
  let attrReadonlyExpr = this.io.readBitsIntLe(1) != 0
  this.attrReadonly = attrReadonlyExpr
  let attrSlotSuffixedExpr = this.io.readBitsIntLe(1) != 0
  this.attrSlotSuffixed = attrSlotSuffixedExpr
  let attrUpdatedExpr = this.io.readBitsIntLe(1) != 0
  this.attrUpdated = attrUpdatedExpr
  let attrDisabledExpr = this.io.readBitsIntLe(1) != 0
  this.attrDisabled = attrDisabledExpr
  let attrsReservedExpr = this.io.readBitsIntLe(28)
  this.attrsReserved = attrsReservedExpr
  alignToByte(this.io)
  let firstExtentIndexExpr = this.io.readU4le()
  this.firstExtentIndex = firstExtentIndexExpr
  let numExtentsExpr = this.io.readU4le()
  this.numExtents = numExtentsExpr
  let groupIndexExpr = this.io.readU4le()
  this.groupIndex = groupIndexExpr

proc fromFile*(_: typedesc[AndroidSuper_Metadata_Partition], filename: string): AndroidSuper_Metadata_Partition =
  AndroidSuper_Metadata_Partition.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[AndroidSuper_Metadata_Group], io: KaitaiStream, root: KaitaiStruct, parent: AndroidSuper_Metadata_TableDescriptor): AndroidSuper_Metadata_Group =
  template this: untyped = result
  this = new(AndroidSuper_Metadata_Group)
  let root = if root == nil: cast[AndroidSuper](this) else: cast[AndroidSuper](root)
  this.io = io
  this.root = root
  this.parent = parent

  let nameExpr = encode(this.io.readBytes(int(36)).bytesTerminate(0, false), "UTF-8")
  this.name = nameExpr
  let flagSlotSuffixedExpr = this.io.readBitsIntLe(1) != 0
  this.flagSlotSuffixed = flagSlotSuffixedExpr
  let flagsReservedExpr = this.io.readBitsIntLe(31)
  this.flagsReserved = flagsReservedExpr
  alignToByte(this.io)
  let maximumSizeExpr = this.io.readU8le()
  this.maximumSize = maximumSizeExpr

proc fromFile*(_: typedesc[AndroidSuper_Metadata_Group], filename: string): AndroidSuper_Metadata_Group =
  AndroidSuper_Metadata_Group.read(newKaitaiFileStream(filename), nil, nil)