Broadcom devices .trx firmware packaging: Nim parsing library

.trx file format is widely used for distribution of firmware updates for Broadcom devices. The most well-known are ASUS routers.

Fundamentally, it includes a footer which acts as a safeguard against installing a firmware package on a wrong hardware model or version, and a header which list numerous partitions packaged inside a single .trx file.

trx files not necessarily contain all these headers.

File extension

trx

KS implementation details

License: GPL-2.0-or-later
Minimal Kaitai Struct required: 0.9

This page hosts a formal specification of Broadcom devices .trx firmware packaging 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 Broadcom devices .trx firmware packaging

broadcom_trx.nim

import kaitai_struct_nim_runtime
import options

type
  BroadcomTrx* = ref object of KaitaiStruct
    `parent`*: KaitaiStruct
    `headerInst`*: BroadcomTrx_Header
    `tailInst`*: BroadcomTrx_Tail
  BroadcomTrx_Revision* = ref object of KaitaiStruct
    `major`*: uint8
    `minor`*: uint8
    `parent`*: BroadcomTrx_Tail_HwCompInfo
  BroadcomTrx_Version* = ref object of KaitaiStruct
    `major`*: uint8
    `minor`*: uint8
    `patch`*: uint8
    `tweak`*: uint8
    `parent`*: BroadcomTrx_Tail
  BroadcomTrx_Tail* = ref object of KaitaiStruct
    `version`*: BroadcomTrx_Version
    `productId`*: string
    `compHw`*: seq[BroadcomTrx_Tail_HwCompInfo]
    `reserved`*: seq[byte]
    `parent`*: BroadcomTrx
  BroadcomTrx_Tail_HwCompInfo* = ref object of KaitaiStruct
    `min`*: BroadcomTrx_Revision
    `max`*: BroadcomTrx_Revision
    `parent`*: BroadcomTrx_Tail
  BroadcomTrx_Header* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `len`*: uint32
    `crc32`*: uint32
    `version`*: uint16
    `flags`*: BroadcomTrx_Header_Flags
    `partitions`*: seq[BroadcomTrx_Header_Partition]
    `parent`*: BroadcomTrx
  BroadcomTrx_Header_Partition* = ref object of KaitaiStruct
    `ofsBody`*: uint32
    `idx`*: uint8
    `parent`*: BroadcomTrx_Header
    `isPresentInst`*: bool
    `isLastInst`*: bool
    `lenBodyInst`*: int
    `bodyInst`*: seq[byte]
  BroadcomTrx_Header_Flags* = ref object of KaitaiStruct
    `flags`*: seq[bool]
    `parent`*: BroadcomTrx_Header

proc read*(_: typedesc[BroadcomTrx], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): BroadcomTrx
proc read*(_: typedesc[BroadcomTrx_Revision], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx_Tail_HwCompInfo): BroadcomTrx_Revision
proc read*(_: typedesc[BroadcomTrx_Version], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx_Tail): BroadcomTrx_Version
proc read*(_: typedesc[BroadcomTrx_Tail], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx): BroadcomTrx_Tail
proc read*(_: typedesc[BroadcomTrx_Tail_HwCompInfo], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx_Tail): BroadcomTrx_Tail_HwCompInfo
proc read*(_: typedesc[BroadcomTrx_Header], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx): BroadcomTrx_Header
proc read*(_: typedesc[BroadcomTrx_Header_Partition], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx_Header, idx: any): BroadcomTrx_Header_Partition
proc read*(_: typedesc[BroadcomTrx_Header_Flags], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx_Header): BroadcomTrx_Header_Flags

proc header*(this: BroadcomTrx): BroadcomTrx_Header
proc tail*(this: BroadcomTrx): BroadcomTrx_Tail
proc isPresent*(this: BroadcomTrx_Header_Partition): bool
proc isLast*(this: BroadcomTrx_Header_Partition): bool
proc lenBody*(this: BroadcomTrx_Header_Partition): int
proc body*(this: BroadcomTrx_Header_Partition): seq[byte]


##[
.trx file format is widely used for distribution of firmware
updates for Broadcom devices. The most well-known are ASUS routers.

Fundamentally, it includes a footer which acts as a safeguard
against installing a firmware package on a wrong hardware model or
version, and a header which list numerous partitions packaged inside
a single .trx file.

trx files not necessarily contain all these headers.

@see <a href="https://github.com/openwrt/openwrt/blob/3f5619f/tools/firmware-utils/src/trx.c">Source</a>
@see <a href="https://web.archive.org/web/20190127154419/https://openwrt.org/docs/techref/header">Source</a>
@see <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/mtd/partitions/brcm,trx.txt">Source</a>
]##
proc read*(_: typedesc[BroadcomTrx], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): BroadcomTrx =
  template this: untyped = result
  this = new(BroadcomTrx)
  let root = if root == nil: cast[BroadcomTrx](this) else: cast[BroadcomTrx](root)
  this.io = io
  this.root = root
  this.parent = parent


proc header(this: BroadcomTrx): BroadcomTrx_Header = 
  if this.headerInst != nil:
    return this.headerInst
  let pos = this.io.pos()
  this.io.seek(int(0))
  let headerInstExpr = BroadcomTrx_Header.read(this.io, this.root, this)
  this.headerInst = headerInstExpr
  this.io.seek(pos)
  if this.headerInst != nil:
    return this.headerInst

proc tail(this: BroadcomTrx): BroadcomTrx_Tail = 
  if this.tailInst != nil:
    return this.tailInst
  let pos = this.io.pos()
  this.io.seek(int((this.io.size - 64)))
  let tailInstExpr = BroadcomTrx_Tail.read(this.io, this.root, this)
  this.tailInst = tailInstExpr
  this.io.seek(pos)
  if this.tailInst != nil:
    return this.tailInst

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

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

  let majorExpr = this.io.readU1()
  this.major = majorExpr
  let minorExpr = this.io.readU1()
  this.minor = minorExpr

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

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

  let majorExpr = this.io.readU1()
  this.major = majorExpr
  let minorExpr = this.io.readU1()
  this.minor = minorExpr
  let patchExpr = this.io.readU1()
  this.patch = patchExpr
  let tweakExpr = this.io.readU1()
  this.tweak = tweakExpr

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


##[
A safeguard against installation of firmware to an incompatible device
]##
proc read*(_: typedesc[BroadcomTrx_Tail], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx): BroadcomTrx_Tail =
  template this: untyped = result
  this = new(BroadcomTrx_Tail)
  let root = if root == nil: cast[BroadcomTrx](this) else: cast[BroadcomTrx](root)
  this.io = io
  this.root = root
  this.parent = parent


  ##[
  1.9.2.7 by default
  ]##
  let versionExpr = BroadcomTrx_Version.read(this.io, this.root, this)
  this.version = versionExpr
  let productIdExpr = encode(this.io.readBytes(int(12)).bytesTerminate(0, false), "utf-8")
  this.productId = productIdExpr

  ##[
  0.02 - 2.99
  ]##
  for i in 0 ..< int(4):
    let it = BroadcomTrx_Tail_HwCompInfo.read(this.io, this.root, this)
    this.compHw.add(it)
  let reservedExpr = this.io.readBytes(int(32))
  this.reserved = reservedExpr

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

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

  let minExpr = BroadcomTrx_Revision.read(this.io, this.root, this)
  this.min = minExpr
  let maxExpr = BroadcomTrx_Revision.read(this.io, this.root, this)
  this.max = maxExpr

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

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

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

  ##[
  Length of file including header
  ]##
  let lenExpr = this.io.readU4le()
  this.len = lenExpr

  ##[
  CRC from `version` (??? todo: see the original and disambiguate) to end of file
  ]##
  let crc32Expr = this.io.readU4le()
  this.crc32 = crc32Expr
  let versionExpr = this.io.readU2le()
  this.version = versionExpr
  let flagsExpr = BroadcomTrx_Header_Flags.read(this.io, this.root, this)
  this.flags = flagsExpr

  ##[
  Offsets of partitions from start of header
  ]##
  block:
    var i: int
    while true:
      let it = BroadcomTrx_Header_Partition.read(this.io, this.root, this, i)
      this.partitions.add(it)
      if  ((i >= 4) or (not(it.isPresent))) :
        break
      inc i

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

proc read*(_: typedesc[BroadcomTrx_Header_Partition], io: KaitaiStream, root: KaitaiStruct, parent: BroadcomTrx_Header, idx: any): BroadcomTrx_Header_Partition =
  template this: untyped = result
  this = new(BroadcomTrx_Header_Partition)
  let root = if root == nil: cast[BroadcomTrx](this) else: cast[BroadcomTrx](root)
  this.io = io
  this.root = root
  this.parent = parent
  let idxExpr = uint8(idx)
  this.idx = idxExpr

  let ofsBodyExpr = this.io.readU4le()
  this.ofsBody = ofsBodyExpr

proc isPresent(this: BroadcomTrx_Header_Partition): bool = 
  if this.isPresentInst != nil:
    return this.isPresentInst
  let isPresentInstExpr = bool(this.ofsBody != 0)
  this.isPresentInst = isPresentInstExpr
  if this.isPresentInst != nil:
    return this.isPresentInst

proc isLast(this: BroadcomTrx_Header_Partition): bool = 
  if this.isLastInst != nil:
    return this.isLastInst
  if this.isPresent:
    let isLastInstExpr = bool( ((this.idx == (len(this.parent.partitions) - 1)) or (not(this.parent.partitions[(this.idx + 1)].isPresent))) )
    this.isLastInst = isLastInstExpr
  if this.isLastInst != nil:
    return this.isLastInst

proc lenBody(this: BroadcomTrx_Header_Partition): int = 
  if this.lenBodyInst != nil:
    return this.lenBodyInst
  if this.isPresent:
    let lenBodyInstExpr = int((if this.isLast: (BroadcomTrx(this.root).io.size - this.ofsBody) else: this.parent.partitions[(this.idx + 1)].ofsBody))
    this.lenBodyInst = lenBodyInstExpr
  if this.lenBodyInst != nil:
    return this.lenBodyInst

proc body(this: BroadcomTrx_Header_Partition): seq[byte] = 
  if this.bodyInst.len != 0:
    return this.bodyInst
  if this.isPresent:
    let io = BroadcomTrx(this.root).io
    let pos = io.pos()
    io.seek(int(this.ofsBody))
    let bodyInstExpr = io.readBytes(int(this.lenBody))
    this.bodyInst = bodyInstExpr
    io.seek(pos)
  if this.bodyInst.len != 0:
    return this.bodyInst

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

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

  for i in 0 ..< int(16):
    let it = this.io.readBitsIntLe(1) != 0
    this.flags.add(it)

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