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

Usage

Runtime library

All parsing code for C# generated by Kaitai Struct depends on the C# runtime library. You have to install it before you can parse data.

The C# runtime library is available in the NuGet Gallery. Installation instructions can also be found there.

Code

Parse a local file and get structure in memory:

var data = AndroidSuper.FromFile("path/to/local/file.img");

Or parse structure from a byte array:

byte[] someArray = new byte[] { ... };
var data = new AndroidSuper(new KaitaiStream(someArray));

After that, one can get various attributes from the structure by accessing properties like:

data.Root // => get root

C# source code to parse Android Dynamic Partitions metadata

AndroidSuper.cs

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

using System.Collections.Generic;

namespace Kaitai
{

    /// <summary>
    /// The metadata stored by Android at the beginning of a &quot;super&quot; 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.
    /// </summary>
    /// <remarks>
    /// Reference: <a href="https://source.android.com/devices/tech/ota/dynamic_partitions">Source</a>
    /// </remarks>
    /// <remarks>
    /// Reference: <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>
    /// </remarks>
    public partial class AndroidSuper : KaitaiStruct
    {
        public static AndroidSuper FromFile(string fileName)
        {
            return new AndroidSuper(new KaitaiStream(fileName));
        }

        public AndroidSuper(KaitaiStream p__io, KaitaiStruct p__parent = null, AndroidSuper p__root = null) : base(p__io)
        {
            m_parent = p__parent;
            m_root = p__root ?? this;
            f_root = false;
            _read();
        }
        private void _read()
        {
        }
        public partial class Root : KaitaiStruct
        {
            public static Root FromFile(string fileName)
            {
                return new Root(new KaitaiStream(fileName));
            }

            public Root(KaitaiStream p__io, AndroidSuper p__parent = null, AndroidSuper p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                __raw_primaryGeometry = m_io.ReadBytes(4096);
                var io___raw_primaryGeometry = new KaitaiStream(__raw_primaryGeometry);
                _primaryGeometry = new Geometry(io___raw_primaryGeometry, this, m_root);
                __raw_backupGeometry = m_io.ReadBytes(4096);
                var io___raw_backupGeometry = new KaitaiStream(__raw_backupGeometry);
                _backupGeometry = new Geometry(io___raw_backupGeometry, this, m_root);
                __raw_primaryMetadata = new List<byte[]>((int) (PrimaryGeometry.MetadataSlotCount));
                _primaryMetadata = new List<Metadata>((int) (PrimaryGeometry.MetadataSlotCount));
                for (var i = 0; i < PrimaryGeometry.MetadataSlotCount; i++)
                {
                    __raw_primaryMetadata.Add(m_io.ReadBytes(PrimaryGeometry.MetadataMaxSize));
                    var io___raw_primaryMetadata = new KaitaiStream(__raw_primaryMetadata[__raw_primaryMetadata.Count - 1]);
                    _primaryMetadata.Add(new Metadata(io___raw_primaryMetadata, this, m_root));
                }
                __raw_backupMetadata = new List<byte[]>((int) (PrimaryGeometry.MetadataSlotCount));
                _backupMetadata = new List<Metadata>((int) (PrimaryGeometry.MetadataSlotCount));
                for (var i = 0; i < PrimaryGeometry.MetadataSlotCount; i++)
                {
                    __raw_backupMetadata.Add(m_io.ReadBytes(PrimaryGeometry.MetadataMaxSize));
                    var io___raw_backupMetadata = new KaitaiStream(__raw_backupMetadata[__raw_backupMetadata.Count - 1]);
                    _backupMetadata.Add(new Metadata(io___raw_backupMetadata, this, m_root));
                }
            }
            private Geometry _primaryGeometry;
            private Geometry _backupGeometry;
            private List<Metadata> _primaryMetadata;
            private List<Metadata> _backupMetadata;
            private AndroidSuper m_root;
            private AndroidSuper m_parent;
            private byte[] __raw_primaryGeometry;
            private byte[] __raw_backupGeometry;
            private List<byte[]> __raw_primaryMetadata;
            private List<byte[]> __raw_backupMetadata;
            public Geometry PrimaryGeometry { get { return _primaryGeometry; } }
            public Geometry BackupGeometry { get { return _backupGeometry; } }
            public List<Metadata> PrimaryMetadata { get { return _primaryMetadata; } }
            public List<Metadata> BackupMetadata { get { return _backupMetadata; } }
            public AndroidSuper M_Root { get { return m_root; } }
            public AndroidSuper M_Parent { get { return m_parent; } }
            public byte[] M_RawPrimaryGeometry { get { return __raw_primaryGeometry; } }
            public byte[] M_RawBackupGeometry { get { return __raw_backupGeometry; } }
            public List<byte[]> M_RawPrimaryMetadata { get { return __raw_primaryMetadata; } }
            public List<byte[]> M_RawBackupMetadata { get { return __raw_backupMetadata; } }
        }
        public partial class Geometry : KaitaiStruct
        {
            public static Geometry FromFile(string fileName)
            {
                return new Geometry(new KaitaiStream(fileName));
            }

            public Geometry(KaitaiStream p__io, AndroidSuper.Root p__parent = null, AndroidSuper p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _magic = m_io.ReadBytes(4);
                if (!((KaitaiStream.ByteArrayCompare(Magic, new byte[] { 103, 68, 108, 97 }) == 0)))
                {
                    throw new ValidationNotEqualError(new byte[] { 103, 68, 108, 97 }, Magic, M_Io, "/types/geometry/seq/0");
                }
                _structSize = m_io.ReadU4le();
                _checksum = m_io.ReadBytes(32);
                _metadataMaxSize = m_io.ReadU4le();
                _metadataSlotCount = m_io.ReadU4le();
                _logicalBlockSize = m_io.ReadU4le();
            }
            private byte[] _magic;
            private uint _structSize;
            private byte[] _checksum;
            private uint _metadataMaxSize;
            private uint _metadataSlotCount;
            private uint _logicalBlockSize;
            private AndroidSuper m_root;
            private AndroidSuper.Root m_parent;
            public byte[] Magic { get { return _magic; } }
            public uint StructSize { get { return _structSize; } }

            /// <summary>
            /// SHA-256 hash of struct_size bytes from beginning of geometry,
            /// calculated as if checksum were zeroed out
            /// </summary>
            public byte[] Checksum { get { return _checksum; } }
            public uint MetadataMaxSize { get { return _metadataMaxSize; } }
            public uint MetadataSlotCount { get { return _metadataSlotCount; } }
            public uint LogicalBlockSize { get { return _logicalBlockSize; } }
            public AndroidSuper M_Root { get { return m_root; } }
            public AndroidSuper.Root M_Parent { get { return m_parent; } }
        }
        public partial class Metadata : KaitaiStruct
        {
            public static Metadata FromFile(string fileName)
            {
                return new Metadata(new KaitaiStream(fileName));
            }


            public enum TableKind
            {
                Partitions = 0,
                Extents = 1,
                Groups = 2,
                BlockDevices = 3,
            }
            public Metadata(KaitaiStream p__io, AndroidSuper.Root p__parent = null, AndroidSuper p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _magic = m_io.ReadBytes(4);
                if (!((KaitaiStream.ByteArrayCompare(Magic, new byte[] { 48, 80, 76, 65 }) == 0)))
                {
                    throw new ValidationNotEqualError(new byte[] { 48, 80, 76, 65 }, Magic, M_Io, "/types/metadata/seq/0");
                }
                _majorVersion = m_io.ReadU2le();
                _minorVersion = m_io.ReadU2le();
                _headerSize = m_io.ReadU4le();
                _headerChecksum = m_io.ReadBytes(32);
                _tablesSize = m_io.ReadU4le();
                _tablesChecksum = m_io.ReadBytes(32);
                _partitions = new TableDescriptor(TableKind.Partitions, m_io, this, m_root);
                _extents = new TableDescriptor(TableKind.Extents, m_io, this, m_root);
                _groups = new TableDescriptor(TableKind.Groups, m_io, this, m_root);
                _blockDevices = new TableDescriptor(TableKind.BlockDevices, m_io, this, m_root);
            }
            public partial class BlockDevice : KaitaiStruct
            {
                public static BlockDevice FromFile(string fileName)
                {
                    return new BlockDevice(new KaitaiStream(fileName));
                }

                public BlockDevice(KaitaiStream p__io, AndroidSuper.Metadata.TableDescriptor p__parent = null, AndroidSuper p__root = null) : base(p__io)
                {
                    m_parent = p__parent;
                    m_root = p__root;
                    _read();
                }
                private void _read()
                {
                    _firstLogicalSector = m_io.ReadU8le();
                    _alignment = m_io.ReadU4le();
                    _alignmentOffset = m_io.ReadU4le();
                    _size = m_io.ReadU8le();
                    _partitionName = System.Text.Encoding.GetEncoding("UTF-8").GetString(KaitaiStream.BytesTerminate(m_io.ReadBytes(36), 0, false));
                    _flagSlotSuffixed = m_io.ReadBitsIntLe(1) != 0;
                    _flagsReserved = m_io.ReadBitsIntLe(31);
                }
                private ulong _firstLogicalSector;
                private uint _alignment;
                private uint _alignmentOffset;
                private ulong _size;
                private string _partitionName;
                private bool _flagSlotSuffixed;
                private ulong _flagsReserved;
                private AndroidSuper m_root;
                private AndroidSuper.Metadata.TableDescriptor m_parent;
                public ulong FirstLogicalSector { get { return _firstLogicalSector; } }
                public uint Alignment { get { return _alignment; } }
                public uint AlignmentOffset { get { return _alignmentOffset; } }
                public ulong Size { get { return _size; } }
                public string PartitionName { get { return _partitionName; } }
                public bool FlagSlotSuffixed { get { return _flagSlotSuffixed; } }
                public ulong FlagsReserved { get { return _flagsReserved; } }
                public AndroidSuper M_Root { get { return m_root; } }
                public AndroidSuper.Metadata.TableDescriptor M_Parent { get { return m_parent; } }
            }
            public partial class Extent : KaitaiStruct
            {
                public static Extent FromFile(string fileName)
                {
                    return new Extent(new KaitaiStream(fileName));
                }


                public enum TargetType
                {
                    Linear = 0,
                    Zero = 1,
                }
                public Extent(KaitaiStream p__io, AndroidSuper.Metadata.TableDescriptor p__parent = null, AndroidSuper p__root = null) : base(p__io)
                {
                    m_parent = p__parent;
                    m_root = p__root;
                    _read();
                }
                private void _read()
                {
                    _numSectors = m_io.ReadU8le();
                    _targetType = ((TargetType) m_io.ReadU4le());
                    _targetData = m_io.ReadU8le();
                    _targetSource = m_io.ReadU4le();
                }
                private ulong _numSectors;
                private TargetType _targetType;
                private ulong _targetData;
                private uint _targetSource;
                private AndroidSuper m_root;
                private AndroidSuper.Metadata.TableDescriptor m_parent;
                public ulong NumSectors { get { return _numSectors; } }
                public TargetType TargetType { get { return _targetType; } }
                public ulong TargetData { get { return _targetData; } }
                public uint TargetSource { get { return _targetSource; } }
                public AndroidSuper M_Root { get { return m_root; } }
                public AndroidSuper.Metadata.TableDescriptor M_Parent { get { return m_parent; } }
            }
            public partial class TableDescriptor : KaitaiStruct
            {
                public TableDescriptor(TableKind p_kind, KaitaiStream p__io, AndroidSuper.Metadata p__parent = null, AndroidSuper p__root = null) : base(p__io)
                {
                    m_parent = p__parent;
                    m_root = p__root;
                    _kind = p_kind;
                    f_table = false;
                    _read();
                }
                private void _read()
                {
                    _offset = m_io.ReadU4le();
                    _numEntries = m_io.ReadU4le();
                    _entrySize = m_io.ReadU4le();
                }
                private bool f_table;
                private List<object> _table;
                public List<object> Table
                {
                    get
                    {
                        if (f_table)
                            return _table;
                        long _pos = m_io.Pos;
                        m_io.Seek((M_Parent.HeaderSize + Offset));
                        __raw_table = new List<byte[]>((int) (NumEntries));
                        _table = new List<object>((int) (NumEntries));
                        for (var i = 0; i < NumEntries; i++)
                        {
                            switch (Kind) {
                            case AndroidSuper.Metadata.TableKind.Partitions: {
                                __raw_table.Add(m_io.ReadBytes(EntrySize));
                                var io___raw_table = new KaitaiStream(__raw_table[__raw_table.Count - 1]);
                                _table.Add(new Partition(io___raw_table, this, m_root));
                                break;
                            }
                            case AndroidSuper.Metadata.TableKind.Extents: {
                                __raw_table.Add(m_io.ReadBytes(EntrySize));
                                var io___raw_table = new KaitaiStream(__raw_table[__raw_table.Count - 1]);
                                _table.Add(new Extent(io___raw_table, this, m_root));
                                break;
                            }
                            case AndroidSuper.Metadata.TableKind.Groups: {
                                __raw_table.Add(m_io.ReadBytes(EntrySize));
                                var io___raw_table = new KaitaiStream(__raw_table[__raw_table.Count - 1]);
                                _table.Add(new Group(io___raw_table, this, m_root));
                                break;
                            }
                            case AndroidSuper.Metadata.TableKind.BlockDevices: {
                                __raw_table.Add(m_io.ReadBytes(EntrySize));
                                var io___raw_table = new KaitaiStream(__raw_table[__raw_table.Count - 1]);
                                _table.Add(new BlockDevice(io___raw_table, this, m_root));
                                break;
                            }
                            default: {
                                _table.Add(m_io.ReadBytes(EntrySize));
                                break;
                            }
                            }
                        }
                        m_io.Seek(_pos);
                        f_table = true;
                        return _table;
                    }
                }
                private uint _offset;
                private uint _numEntries;
                private uint _entrySize;
                private TableKind _kind;
                private AndroidSuper m_root;
                private AndroidSuper.Metadata m_parent;
                private List<byte[]> __raw_table;
                public uint Offset { get { return _offset; } }
                public uint NumEntries { get { return _numEntries; } }
                public uint EntrySize { get { return _entrySize; } }
                public TableKind Kind { get { return _kind; } }
                public AndroidSuper M_Root { get { return m_root; } }
                public AndroidSuper.Metadata M_Parent { get { return m_parent; } }
                public List<byte[]> M_RawTable { get { return __raw_table; } }
            }
            public partial class Partition : KaitaiStruct
            {
                public static Partition FromFile(string fileName)
                {
                    return new Partition(new KaitaiStream(fileName));
                }

                public Partition(KaitaiStream p__io, AndroidSuper.Metadata.TableDescriptor p__parent = null, AndroidSuper p__root = null) : base(p__io)
                {
                    m_parent = p__parent;
                    m_root = p__root;
                    _read();
                }
                private void _read()
                {
                    _name = System.Text.Encoding.GetEncoding("UTF-8").GetString(KaitaiStream.BytesTerminate(m_io.ReadBytes(36), 0, false));
                    _attrReadonly = m_io.ReadBitsIntLe(1) != 0;
                    _attrSlotSuffixed = m_io.ReadBitsIntLe(1) != 0;
                    _attrUpdated = m_io.ReadBitsIntLe(1) != 0;
                    _attrDisabled = m_io.ReadBitsIntLe(1) != 0;
                    _attrsReserved = m_io.ReadBitsIntLe(28);
                    m_io.AlignToByte();
                    _firstExtentIndex = m_io.ReadU4le();
                    _numExtents = m_io.ReadU4le();
                    _groupIndex = m_io.ReadU4le();
                }
                private string _name;
                private bool _attrReadonly;
                private bool _attrSlotSuffixed;
                private bool _attrUpdated;
                private bool _attrDisabled;
                private ulong _attrsReserved;
                private uint _firstExtentIndex;
                private uint _numExtents;
                private uint _groupIndex;
                private AndroidSuper m_root;
                private AndroidSuper.Metadata.TableDescriptor m_parent;
                public string Name { get { return _name; } }
                public bool AttrReadonly { get { return _attrReadonly; } }
                public bool AttrSlotSuffixed { get { return _attrSlotSuffixed; } }
                public bool AttrUpdated { get { return _attrUpdated; } }
                public bool AttrDisabled { get { return _attrDisabled; } }
                public ulong AttrsReserved { get { return _attrsReserved; } }
                public uint FirstExtentIndex { get { return _firstExtentIndex; } }
                public uint NumExtents { get { return _numExtents; } }
                public uint GroupIndex { get { return _groupIndex; } }
                public AndroidSuper M_Root { get { return m_root; } }
                public AndroidSuper.Metadata.TableDescriptor M_Parent { get { return m_parent; } }
            }
            public partial class Group : KaitaiStruct
            {
                public static Group FromFile(string fileName)
                {
                    return new Group(new KaitaiStream(fileName));
                }

                public Group(KaitaiStream p__io, AndroidSuper.Metadata.TableDescriptor p__parent = null, AndroidSuper p__root = null) : base(p__io)
                {
                    m_parent = p__parent;
                    m_root = p__root;
                    _read();
                }
                private void _read()
                {
                    _name = System.Text.Encoding.GetEncoding("UTF-8").GetString(KaitaiStream.BytesTerminate(m_io.ReadBytes(36), 0, false));
                    _flagSlotSuffixed = m_io.ReadBitsIntLe(1) != 0;
                    _flagsReserved = m_io.ReadBitsIntLe(31);
                    m_io.AlignToByte();
                    _maximumSize = m_io.ReadU8le();
                }
                private string _name;
                private bool _flagSlotSuffixed;
                private ulong _flagsReserved;
                private ulong _maximumSize;
                private AndroidSuper m_root;
                private AndroidSuper.Metadata.TableDescriptor m_parent;
                public string Name { get { return _name; } }
                public bool FlagSlotSuffixed { get { return _flagSlotSuffixed; } }
                public ulong FlagsReserved { get { return _flagsReserved; } }
                public ulong MaximumSize { get { return _maximumSize; } }
                public AndroidSuper M_Root { get { return m_root; } }
                public AndroidSuper.Metadata.TableDescriptor M_Parent { get { return m_parent; } }
            }
            private byte[] _magic;
            private ushort _majorVersion;
            private ushort _minorVersion;
            private uint _headerSize;
            private byte[] _headerChecksum;
            private uint _tablesSize;
            private byte[] _tablesChecksum;
            private TableDescriptor _partitions;
            private TableDescriptor _extents;
            private TableDescriptor _groups;
            private TableDescriptor _blockDevices;
            private AndroidSuper m_root;
            private AndroidSuper.Root m_parent;
            public byte[] Magic { get { return _magic; } }
            public ushort MajorVersion { get { return _majorVersion; } }
            public ushort MinorVersion { get { return _minorVersion; } }
            public uint HeaderSize { get { return _headerSize; } }

            /// <summary>
            /// SHA-256 hash of header_size bytes from beginning of metadata,
            /// calculated as if header_checksum were zeroed out
            /// </summary>
            public byte[] HeaderChecksum { get { return _headerChecksum; } }
            public uint TablesSize { get { return _tablesSize; } }

            /// <summary>
            /// SHA-256 hash of tables_size bytes from end of header
            /// </summary>
            public byte[] TablesChecksum { get { return _tablesChecksum; } }
            public TableDescriptor Partitions { get { return _partitions; } }
            public TableDescriptor Extents { get { return _extents; } }
            public TableDescriptor Groups { get { return _groups; } }
            public TableDescriptor BlockDevices { get { return _blockDevices; } }
            public AndroidSuper M_Root { get { return m_root; } }
            public AndroidSuper.Root M_Parent { get { return m_parent; } }
        }
        private bool f_root;
        private Root _root;
        public Root Root
        {
            get
            {
                if (f_root)
                    return _root;
                long _pos = m_io.Pos;
                m_io.Seek(4096);
                _root = new Root(m_io, this, m_root);
                m_io.Seek(_pos);
                f_root = true;
                return _root;
            }
        }
        private AndroidSuper m_root;
        private KaitaiStruct m_parent;
        public AndroidSuper M_Root { get { return m_root; } }
        public KaitaiStruct M_Parent { get { return m_parent; } }
    }
}