Android Dynamic Partitions metadata: Go 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.

Go source code to parse Android Dynamic Partitions metadata

android_super.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"io"
	"bytes"
)


/**
 * 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/docs/core/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>
 */
type AndroidSuper struct {
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent interface{}
	_f_root bool
	root *AndroidSuper_Root
}
func NewAndroidSuper() *AndroidSuper {
	return &AndroidSuper{
	}
}

func (this *AndroidSuper) Read(io *kaitai.Stream, parent interface{}, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	return err
}
func (this *AndroidSuper) Root() (v *AndroidSuper_Root, err error) {
	if (this._f_root) {
		return this.root, nil
	}
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(4096), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp1 := NewAndroidSuper_Root()
	err = tmp1.Read(this._io, this, this._root)
	if err != nil {
		return nil, err
	}
	this.root = tmp1
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	this._f_root = true
	this._f_root = true
	return this.root, nil
}
type AndroidSuper_Root struct {
	PrimaryGeometry *AndroidSuper_Geometry
	BackupGeometry *AndroidSuper_Geometry
	PrimaryMetadata []*AndroidSuper_Metadata
	BackupMetadata []*AndroidSuper_Metadata
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper
	_raw_PrimaryGeometry []byte
	_raw_BackupGeometry []byte
	_raw_PrimaryMetadata [][]byte
	_raw_BackupMetadata [][]byte
}
func NewAndroidSuper_Root() *AndroidSuper_Root {
	return &AndroidSuper_Root{
	}
}

func (this *AndroidSuper_Root) Read(io *kaitai.Stream, parent *AndroidSuper, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp2, err := this._io.ReadBytes(int(4096))
	if err != nil {
		return err
	}
	tmp2 = tmp2
	this._raw_PrimaryGeometry = tmp2
	_io__raw_PrimaryGeometry := kaitai.NewStream(bytes.NewReader(this._raw_PrimaryGeometry))
	tmp3 := NewAndroidSuper_Geometry()
	err = tmp3.Read(_io__raw_PrimaryGeometry, this, this._root)
	if err != nil {
		return err
	}
	this.PrimaryGeometry = tmp3
	tmp4, err := this._io.ReadBytes(int(4096))
	if err != nil {
		return err
	}
	tmp4 = tmp4
	this._raw_BackupGeometry = tmp4
	_io__raw_BackupGeometry := kaitai.NewStream(bytes.NewReader(this._raw_BackupGeometry))
	tmp5 := NewAndroidSuper_Geometry()
	err = tmp5.Read(_io__raw_BackupGeometry, this, this._root)
	if err != nil {
		return err
	}
	this.BackupGeometry = tmp5
	for i := 0; i < int(this.PrimaryGeometry.MetadataSlotCount); i++ {
		_ = i
		tmp6, err := this._io.ReadBytes(int(this.PrimaryGeometry.MetadataMaxSize))
		if err != nil {
			return err
		}
		tmp6 = tmp6
		this._raw_PrimaryMetadata = append(this._raw_PrimaryMetadata, tmp6)
		_io__raw_PrimaryMetadata := kaitai.NewStream(bytes.NewReader(this._raw_PrimaryMetadata[i]))
		tmp7 := NewAndroidSuper_Metadata()
		err = tmp7.Read(_io__raw_PrimaryMetadata, this, this._root)
		if err != nil {
			return err
		}
		this.PrimaryMetadata = append(this.PrimaryMetadata, tmp7)
	}
	for i := 0; i < int(this.PrimaryGeometry.MetadataSlotCount); i++ {
		_ = i
		tmp8, err := this._io.ReadBytes(int(this.PrimaryGeometry.MetadataMaxSize))
		if err != nil {
			return err
		}
		tmp8 = tmp8
		this._raw_BackupMetadata = append(this._raw_BackupMetadata, tmp8)
		_io__raw_BackupMetadata := kaitai.NewStream(bytes.NewReader(this._raw_BackupMetadata[i]))
		tmp9 := NewAndroidSuper_Metadata()
		err = tmp9.Read(_io__raw_BackupMetadata, this, this._root)
		if err != nil {
			return err
		}
		this.BackupMetadata = append(this.BackupMetadata, tmp9)
	}
	return err
}
type AndroidSuper_Geometry struct {
	Magic []byte
	StructSize uint32
	Checksum []byte
	MetadataMaxSize uint32
	MetadataSlotCount uint32
	LogicalBlockSize uint32
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Root
}
func NewAndroidSuper_Geometry() *AndroidSuper_Geometry {
	return &AndroidSuper_Geometry{
	}
}

func (this *AndroidSuper_Geometry) Read(io *kaitai.Stream, parent *AndroidSuper_Root, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp10, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp10 = tmp10
	this.Magic = tmp10
	if !(bytes.Equal(this.Magic, []uint8{103, 68, 108, 97})) {
		return kaitai.NewValidationNotEqualError([]uint8{103, 68, 108, 97}, this.Magic, this._io, "/types/geometry/seq/0")
	}
	tmp11, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.StructSize = uint32(tmp11)
	tmp12, err := this._io.ReadBytes(int(32))
	if err != nil {
		return err
	}
	tmp12 = tmp12
	this.Checksum = tmp12
	tmp13, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.MetadataMaxSize = uint32(tmp13)
	tmp14, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.MetadataSlotCount = uint32(tmp14)
	tmp15, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LogicalBlockSize = uint32(tmp15)
	return err
}

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

type AndroidSuper_Metadata_TableKind int
const (
	AndroidSuper_Metadata_TableKind__Partitions AndroidSuper_Metadata_TableKind = 0
	AndroidSuper_Metadata_TableKind__Extents AndroidSuper_Metadata_TableKind = 1
	AndroidSuper_Metadata_TableKind__Groups AndroidSuper_Metadata_TableKind = 2
	AndroidSuper_Metadata_TableKind__BlockDevices AndroidSuper_Metadata_TableKind = 3
)
type AndroidSuper_Metadata struct {
	Magic []byte
	MajorVersion uint16
	MinorVersion uint16
	HeaderSize uint32
	HeaderChecksum []byte
	TablesSize uint32
	TablesChecksum []byte
	Partitions *AndroidSuper_Metadata_TableDescriptor
	Extents *AndroidSuper_Metadata_TableDescriptor
	Groups *AndroidSuper_Metadata_TableDescriptor
	BlockDevices *AndroidSuper_Metadata_TableDescriptor
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Root
}
func NewAndroidSuper_Metadata() *AndroidSuper_Metadata {
	return &AndroidSuper_Metadata{
	}
}

func (this *AndroidSuper_Metadata) Read(io *kaitai.Stream, parent *AndroidSuper_Root, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp16, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp16 = tmp16
	this.Magic = tmp16
	if !(bytes.Equal(this.Magic, []uint8{48, 80, 76, 65})) {
		return kaitai.NewValidationNotEqualError([]uint8{48, 80, 76, 65}, this.Magic, this._io, "/types/metadata/seq/0")
	}
	tmp17, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.MajorVersion = uint16(tmp17)
	tmp18, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.MinorVersion = uint16(tmp18)
	tmp19, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.HeaderSize = uint32(tmp19)
	tmp20, err := this._io.ReadBytes(int(32))
	if err != nil {
		return err
	}
	tmp20 = tmp20
	this.HeaderChecksum = tmp20
	tmp21, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.TablesSize = uint32(tmp21)
	tmp22, err := this._io.ReadBytes(int(32))
	if err != nil {
		return err
	}
	tmp22 = tmp22
	this.TablesChecksum = tmp22
	tmp23 := NewAndroidSuper_Metadata_TableDescriptor(AndroidSuper_Metadata_TableKind__Partitions)
	err = tmp23.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Partitions = tmp23
	tmp24 := NewAndroidSuper_Metadata_TableDescriptor(AndroidSuper_Metadata_TableKind__Extents)
	err = tmp24.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Extents = tmp24
	tmp25 := NewAndroidSuper_Metadata_TableDescriptor(AndroidSuper_Metadata_TableKind__Groups)
	err = tmp25.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Groups = tmp25
	tmp26 := NewAndroidSuper_Metadata_TableDescriptor(AndroidSuper_Metadata_TableKind__BlockDevices)
	err = tmp26.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.BlockDevices = tmp26
	return err
}

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

/**
 * SHA-256 hash of tables_size bytes from end of header
 */
type AndroidSuper_Metadata_BlockDevice struct {
	FirstLogicalSector uint64
	Alignment uint32
	AlignmentOffset uint32
	Size uint64
	PartitionName string
	FlagSlotSuffixed bool
	FlagsReserved uint64
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Metadata_TableDescriptor
}
func NewAndroidSuper_Metadata_BlockDevice() *AndroidSuper_Metadata_BlockDevice {
	return &AndroidSuper_Metadata_BlockDevice{
	}
}

func (this *AndroidSuper_Metadata_BlockDevice) Read(io *kaitai.Stream, parent *AndroidSuper_Metadata_TableDescriptor, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp27, err := this._io.ReadU8le()
	if err != nil {
		return err
	}
	this.FirstLogicalSector = uint64(tmp27)
	tmp28, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.Alignment = uint32(tmp28)
	tmp29, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.AlignmentOffset = uint32(tmp29)
	tmp30, err := this._io.ReadU8le()
	if err != nil {
		return err
	}
	this.Size = uint64(tmp30)
	tmp31, err := this._io.ReadBytes(int(36))
	if err != nil {
		return err
	}
	tmp31 = kaitai.BytesTerminate(tmp31, 0, false)
	this.PartitionName = string(tmp31)
	tmp32, err := this._io.ReadBitsIntLe(1)
	if err != nil {
		return err
	}
	this.FlagSlotSuffixed = tmp32 != 0
	tmp33, err := this._io.ReadBitsIntLe(31)
	if err != nil {
		return err
	}
	this.FlagsReserved = tmp33
	return err
}

type AndroidSuper_Metadata_Extent_TargetType int
const (
	AndroidSuper_Metadata_Extent_TargetType__Linear AndroidSuper_Metadata_Extent_TargetType = 0
	AndroidSuper_Metadata_Extent_TargetType__Zero AndroidSuper_Metadata_Extent_TargetType = 1
)
type AndroidSuper_Metadata_Extent struct {
	NumSectors uint64
	TargetType AndroidSuper_Metadata_Extent_TargetType
	TargetData uint64
	TargetSource uint32
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Metadata_TableDescriptor
}
func NewAndroidSuper_Metadata_Extent() *AndroidSuper_Metadata_Extent {
	return &AndroidSuper_Metadata_Extent{
	}
}

func (this *AndroidSuper_Metadata_Extent) Read(io *kaitai.Stream, parent *AndroidSuper_Metadata_TableDescriptor, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp34, err := this._io.ReadU8le()
	if err != nil {
		return err
	}
	this.NumSectors = uint64(tmp34)
	tmp35, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.TargetType = AndroidSuper_Metadata_Extent_TargetType(tmp35)
	tmp36, err := this._io.ReadU8le()
	if err != nil {
		return err
	}
	this.TargetData = uint64(tmp36)
	tmp37, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.TargetSource = uint32(tmp37)
	return err
}
type AndroidSuper_Metadata_TableDescriptor struct {
	Offset uint32
	NumEntries uint32
	EntrySize uint32
	Kind AndroidSuper_Metadata_TableKind
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Metadata
	_raw_table [][]byte
	_f_table bool
	table []interface{}
}
func NewAndroidSuper_Metadata_TableDescriptor(kind AndroidSuper_Metadata_TableKind) *AndroidSuper_Metadata_TableDescriptor {
	return &AndroidSuper_Metadata_TableDescriptor{
		Kind: kind,
	}
}

func (this *AndroidSuper_Metadata_TableDescriptor) Read(io *kaitai.Stream, parent *AndroidSuper_Metadata, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp38, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.Offset = uint32(tmp38)
	tmp39, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.NumEntries = uint32(tmp39)
	tmp40, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.EntrySize = uint32(tmp40)
	return err
}
func (this *AndroidSuper_Metadata_TableDescriptor) Table() (v []interface{}, err error) {
	if (this._f_table) {
		return this.table, nil
	}
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64((this._parent.HeaderSize + this.Offset)), io.SeekStart)
	if err != nil {
		return nil, err
	}
	for i := 0; i < int(this.NumEntries); i++ {
		_ = i
		switch (this.Kind) {
		case AndroidSuper_Metadata_TableKind__Partitions:
			tmp41, err := this._io.ReadBytes(int(this.EntrySize))
			if err != nil {
				return nil, err
			}
			tmp41 = tmp41
			this._raw_table = append(this._raw_table, tmp41)
			_io__raw_table := kaitai.NewStream(bytes.NewReader(this._raw_table[i]))
			tmp42 := NewAndroidSuper_Metadata_Partition()
			err = tmp42.Read(_io__raw_table, this, this._root)
			if err != nil {
				return nil, err
			}
			this.table = append(this.table, tmp42)
		case AndroidSuper_Metadata_TableKind__Extents:
			tmp43, err := this._io.ReadBytes(int(this.EntrySize))
			if err != nil {
				return nil, err
			}
			tmp43 = tmp43
			this._raw_table = append(this._raw_table, tmp43)
			_io__raw_table := kaitai.NewStream(bytes.NewReader(this._raw_table[i]))
			tmp44 := NewAndroidSuper_Metadata_Extent()
			err = tmp44.Read(_io__raw_table, this, this._root)
			if err != nil {
				return nil, err
			}
			this.table = append(this.table, tmp44)
		case AndroidSuper_Metadata_TableKind__Groups:
			tmp45, err := this._io.ReadBytes(int(this.EntrySize))
			if err != nil {
				return nil, err
			}
			tmp45 = tmp45
			this._raw_table = append(this._raw_table, tmp45)
			_io__raw_table := kaitai.NewStream(bytes.NewReader(this._raw_table[i]))
			tmp46 := NewAndroidSuper_Metadata_Group()
			err = tmp46.Read(_io__raw_table, this, this._root)
			if err != nil {
				return nil, err
			}
			this.table = append(this.table, tmp46)
		case AndroidSuper_Metadata_TableKind__BlockDevices:
			tmp47, err := this._io.ReadBytes(int(this.EntrySize))
			if err != nil {
				return nil, err
			}
			tmp47 = tmp47
			this._raw_table = append(this._raw_table, tmp47)
			_io__raw_table := kaitai.NewStream(bytes.NewReader(this._raw_table[i]))
			tmp48 := NewAndroidSuper_Metadata_BlockDevice()
			err = tmp48.Read(_io__raw_table, this, this._root)
			if err != nil {
				return nil, err
			}
			this.table = append(this.table, tmp48)
		default:
			tmp49, err := this._io.ReadBytes(int(this.EntrySize))
			if err != nil {
				return nil, err
			}
			tmp49 = tmp49
			this._raw_table = append(this._raw_table, tmp49)
		}
	}
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	this._f_table = true
	this._f_table = true
	return this.table, nil
}
type AndroidSuper_Metadata_Partition struct {
	Name string
	AttrReadonly bool
	AttrSlotSuffixed bool
	AttrUpdated bool
	AttrDisabled bool
	AttrsReserved uint64
	FirstExtentIndex uint32
	NumExtents uint32
	GroupIndex uint32
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Metadata_TableDescriptor
}
func NewAndroidSuper_Metadata_Partition() *AndroidSuper_Metadata_Partition {
	return &AndroidSuper_Metadata_Partition{
	}
}

func (this *AndroidSuper_Metadata_Partition) Read(io *kaitai.Stream, parent *AndroidSuper_Metadata_TableDescriptor, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp50, err := this._io.ReadBytes(int(36))
	if err != nil {
		return err
	}
	tmp50 = kaitai.BytesTerminate(tmp50, 0, false)
	this.Name = string(tmp50)
	tmp51, err := this._io.ReadBitsIntLe(1)
	if err != nil {
		return err
	}
	this.AttrReadonly = tmp51 != 0
	tmp52, err := this._io.ReadBitsIntLe(1)
	if err != nil {
		return err
	}
	this.AttrSlotSuffixed = tmp52 != 0
	tmp53, err := this._io.ReadBitsIntLe(1)
	if err != nil {
		return err
	}
	this.AttrUpdated = tmp53 != 0
	tmp54, err := this._io.ReadBitsIntLe(1)
	if err != nil {
		return err
	}
	this.AttrDisabled = tmp54 != 0
	tmp55, err := this._io.ReadBitsIntLe(28)
	if err != nil {
		return err
	}
	this.AttrsReserved = tmp55
	this._io.AlignToByte()
	tmp56, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.FirstExtentIndex = uint32(tmp56)
	tmp57, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.NumExtents = uint32(tmp57)
	tmp58, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.GroupIndex = uint32(tmp58)
	return err
}
type AndroidSuper_Metadata_Group struct {
	Name string
	FlagSlotSuffixed bool
	FlagsReserved uint64
	MaximumSize uint64
	_io *kaitai.Stream
	_root *AndroidSuper
	_parent *AndroidSuper_Metadata_TableDescriptor
}
func NewAndroidSuper_Metadata_Group() *AndroidSuper_Metadata_Group {
	return &AndroidSuper_Metadata_Group{
	}
}

func (this *AndroidSuper_Metadata_Group) Read(io *kaitai.Stream, parent *AndroidSuper_Metadata_TableDescriptor, root *AndroidSuper) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp59, err := this._io.ReadBytes(int(36))
	if err != nil {
		return err
	}
	tmp59 = kaitai.BytesTerminate(tmp59, 0, false)
	this.Name = string(tmp59)
	tmp60, err := this._io.ReadBitsIntLe(1)
	if err != nil {
		return err
	}
	this.FlagSlotSuffixed = tmp60 != 0
	tmp61, err := this._io.ReadBitsIntLe(31)
	if err != nil {
		return err
	}
	this.FlagsReserved = tmp61
	this._io.AlignToByte()
	tmp62, err := this._io.ReadU8le()
	if err != nil {
		return err
	}
	this.MaximumSize = uint64(tmp62)
	return err
}