APM (Apple Partition Map) partition table: Nim parsing library

This page hosts a formal specification of APM (Apple Partition Map) partition table 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 APM (Apple Partition Map) partition table

apm_partition_table.nim

import kaitai_struct_nim_runtime
import options

type
  ApmPartitionTable* = ref object of KaitaiStruct
    `parent`*: KaitaiStruct
    `rawPartitionLookupInst`*: seq[byte]
    `rawPartitionEntriesInst`*: seq[seq[byte]]
    `sectorSizeInst`: int
    `sectorSizeInstFlag`: bool
    `partitionLookupInst`: ApmPartitionTable_PartitionEntry
    `partitionLookupInstFlag`: bool
    `partitionEntriesInst`: seq[ApmPartitionTable_PartitionEntry]
    `partitionEntriesInstFlag`: bool
  ApmPartitionTable_PartitionEntry* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `reserved1`*: seq[byte]
    `numberOfPartitions`*: uint32
    `partitionStart`*: uint32
    `partitionSize`*: uint32
    `partitionName`*: string
    `partitionType`*: string
    `dataStart`*: uint32
    `dataSize`*: uint32
    `partitionStatus`*: uint32
    `bootCodeStart`*: uint32
    `bootCodeSize`*: uint32
    `bootLoaderAddress`*: uint32
    `reserved2`*: seq[byte]
    `bootCodeEntry`*: uint32
    `reserved3`*: seq[byte]
    `bootCodeCksum`*: uint32
    `processorType`*: string
    `parent`*: ApmPartitionTable
    `partitionInst`: seq[byte]
    `partitionInstFlag`: bool
    `dataInst`: seq[byte]
    `dataInstFlag`: bool
    `bootCodeInst`: seq[byte]
    `bootCodeInstFlag`: bool

proc read*(_: typedesc[ApmPartitionTable], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): ApmPartitionTable
proc read*(_: typedesc[ApmPartitionTable_PartitionEntry], io: KaitaiStream, root: KaitaiStruct, parent: ApmPartitionTable): ApmPartitionTable_PartitionEntry

proc sectorSize*(this: ApmPartitionTable): int
proc partitionLookup*(this: ApmPartitionTable): ApmPartitionTable_PartitionEntry
proc partitionEntries*(this: ApmPartitionTable): seq[ApmPartitionTable_PartitionEntry]
proc partition*(this: ApmPartitionTable_PartitionEntry): seq[byte]
proc data*(this: ApmPartitionTable_PartitionEntry): seq[byte]
proc bootCode*(this: ApmPartitionTable_PartitionEntry): seq[byte]


##[
@see <a href="https://en.wikipedia.org/wiki/Apple_Partition_Map">Source</a>
]##
proc read*(_: typedesc[ApmPartitionTable], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): ApmPartitionTable =
  template this: untyped = result
  this = new(ApmPartitionTable)
  let root = if root == nil: cast[ApmPartitionTable](this) else: cast[ApmPartitionTable](root)
  this.io = io
  this.root = root
  this.parent = parent


proc sectorSize(this: ApmPartitionTable): int = 

  ##[
  0x200 (512) bytes for disks, 0x1000 (4096) bytes is not supported by APM
0x800 (2048) bytes for CDROM

  ]##
  if this.sectorSizeInstFlag:
    return this.sectorSizeInst
  let sectorSizeInstExpr = int(512)
  this.sectorSizeInst = sectorSizeInstExpr
  this.sectorSizeInstFlag = true
  return this.sectorSizeInst

proc partitionLookup(this: ApmPartitionTable): ApmPartitionTable_PartitionEntry = 

  ##[
  Every partition entry contains the number of partition entries.
We parse the first entry, to know how many to parse, including the first one.
No logic is given what to do if other entries have a different number.

  ]##
  if this.partitionLookupInstFlag:
    return this.partitionLookupInst
  let io = ApmPartitionTable(this.root).io
  let pos = io.pos()
  io.seek(int(ApmPartitionTable(this.root).sectorSize))
  let rawPartitionLookupInstExpr = io.readBytes(int(this.sectorSize))
  this.rawPartitionLookupInst = rawPartitionLookupInstExpr
  let rawPartitionLookupInstIo = newKaitaiStream(rawPartitionLookupInstExpr)
  let partitionLookupInstExpr = ApmPartitionTable_PartitionEntry.read(rawPartitionLookupInstIo, this.root, this)
  this.partitionLookupInst = partitionLookupInstExpr
  io.seek(pos)
  this.partitionLookupInstFlag = true
  return this.partitionLookupInst

proc partitionEntries(this: ApmPartitionTable): seq[ApmPartitionTable_PartitionEntry] = 
  if this.partitionEntriesInstFlag:
    return this.partitionEntriesInst
  let io = ApmPartitionTable(this.root).io
  let pos = io.pos()
  io.seek(int(ApmPartitionTable(this.root).sectorSize))
  for i in 0 ..< int(ApmPartitionTable(this.root).partitionLookup.numberOfPartitions):
    let buf = io.readBytes(int(this.sectorSize))
    this.rawPartitionEntriesInst.add(buf)
    let rawPartitionEntriesInstIo = newKaitaiStream(buf)
    let it = ApmPartitionTable_PartitionEntry.read(rawPartitionEntriesInstIo, this.root, this)
    this.partitionEntriesInst.add(it)
  io.seek(pos)
  this.partitionEntriesInstFlag = true
  return this.partitionEntriesInst

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

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

  let magicExpr = this.io.readBytes(int(2))
  this.magic = magicExpr
  let reserved1Expr = this.io.readBytes(int(2))
  this.reserved1 = reserved1Expr
  let numberOfPartitionsExpr = this.io.readU4be()
  this.numberOfPartitions = numberOfPartitionsExpr

  ##[
  First sector
  ]##
  let partitionStartExpr = this.io.readU4be()
  this.partitionStart = partitionStartExpr

  ##[
  Number of sectors
  ]##
  let partitionSizeExpr = this.io.readU4be()
  this.partitionSize = partitionSizeExpr
  let partitionNameExpr = encode(this.io.readBytes(int(32)).bytesTerminate(0, false), "ascii")
  this.partitionName = partitionNameExpr
  let partitionTypeExpr = encode(this.io.readBytes(int(32)).bytesTerminate(0, false), "ascii")
  this.partitionType = partitionTypeExpr

  ##[
  First sector
  ]##
  let dataStartExpr = this.io.readU4be()
  this.dataStart = dataStartExpr

  ##[
  Number of sectors
  ]##
  let dataSizeExpr = this.io.readU4be()
  this.dataSize = dataSizeExpr
  let partitionStatusExpr = this.io.readU4be()
  this.partitionStatus = partitionStatusExpr

  ##[
  First sector
  ]##
  let bootCodeStartExpr = this.io.readU4be()
  this.bootCodeStart = bootCodeStartExpr

  ##[
  Number of bytes
  ]##
  let bootCodeSizeExpr = this.io.readU4be()
  this.bootCodeSize = bootCodeSizeExpr

  ##[
  Address of bootloader code
  ]##
  let bootLoaderAddressExpr = this.io.readU4be()
  this.bootLoaderAddress = bootLoaderAddressExpr
  let reserved2Expr = this.io.readBytes(int(4))
  this.reserved2 = reserved2Expr

  ##[
  Boot code entry point
  ]##
  let bootCodeEntryExpr = this.io.readU4be()
  this.bootCodeEntry = bootCodeEntryExpr
  let reserved3Expr = this.io.readBytes(int(4))
  this.reserved3 = reserved3Expr

  ##[
  Boot code checksum
  ]##
  let bootCodeCksumExpr = this.io.readU4be()
  this.bootCodeCksum = bootCodeCksumExpr
  let processorTypeExpr = encode(this.io.readBytes(int(16)).bytesTerminate(0, false), "ascii")
  this.processorType = processorTypeExpr

proc partition(this: ApmPartitionTable_PartitionEntry): seq[byte] = 
  if this.partitionInstFlag:
    return this.partitionInst
  if (this.partitionStatus and 1) != 0:
    let io = ApmPartitionTable(this.root).io
    let pos = io.pos()
    io.seek(int((this.partitionStart * ApmPartitionTable(this.root).sectorSize)))
    let partitionInstExpr = io.readBytes(int((this.partitionSize * ApmPartitionTable(this.root).sectorSize)))
    this.partitionInst = partitionInstExpr
    io.seek(pos)
  this.partitionInstFlag = true
  return this.partitionInst

proc data(this: ApmPartitionTable_PartitionEntry): seq[byte] = 
  if this.dataInstFlag:
    return this.dataInst
  let io = ApmPartitionTable(this.root).io
  let pos = io.pos()
  io.seek(int((this.dataStart * ApmPartitionTable(this.root).sectorSize)))
  let dataInstExpr = io.readBytes(int((this.dataSize * ApmPartitionTable(this.root).sectorSize)))
  this.dataInst = dataInstExpr
  io.seek(pos)
  this.dataInstFlag = true
  return this.dataInst

proc bootCode(this: ApmPartitionTable_PartitionEntry): seq[byte] = 
  if this.bootCodeInstFlag:
    return this.bootCodeInst
  let io = ApmPartitionTable(this.root).io
  let pos = io.pos()
  io.seek(int((this.bootCodeStart * ApmPartitionTable(this.root).sectorSize)))
  let bootCodeInstExpr = io.readBytes(int(this.bootCodeSize))
  this.bootCodeInst = bootCodeInstExpr
  io.seek(pos)
  this.bootCodeInstFlag = true
  return this.bootCodeInst

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