Skip to content

LTP Layer

The Lists, Tables, and Properties layer (pkg/ltp/) provides structured data access.

Components

Heap-on-Node (heap.go)

Manages allocations within a node's data blocks:

heap, err := ltp.NewHeapOnNode(node)
if err != nil {
    log.Fatal(err)
}

// Read an allocation
data, err := heap.Read(heapID)

// Get root allocation
rootID := heap.RootID()

Heap Structure

Each node's data blocks contain:

Block 0:
┌─────────────────────────────────┐
│ Heap First Header (12 bytes)   │
│ - Page map offset              │
│ - Signature (0xEC)             │
│ - Client signature             │
│ - Root HID                     │
├─────────────────────────────────┤
│ Allocations...                 │
├─────────────────────────────────┤
│ Page Map                       │
│ - Num allocations              │
│ - Allocation offsets           │
└─────────────────────────────────┘

Client Signatures

The client signature identifies the heap's purpose:

Signature Value Description
TC 0x7C Table Context
PC 0xBC Property Context
BTH 0xB5 BTree-on-Heap

BTree-on-Heap (bth.go)

A B-tree structure stored within heap allocations:

bth, err := ltp.NewBTH(heap, rootHID)
if err != nil {
    log.Fatal(err)
}

// Lookup by key
value, err := bth.LookupUint16(key)

// Iterate all entries
for entry, err := range bth.Entries() {
    // entry.Key, entry.Value
}

BTH Structure

┌─────────────────────┐
│ BTH Header          │
│ - Signature (0xB5)  │
│ - Key size          │
│ - Entry size        │
│ - Num levels        │
│ - Root HID          │
└─────────┬───────────┘
┌─────────────────────┐
│ Non-leaf Node       │
│ Key1 → HID1         │
│ Key2 → HID2         │
└─────────┬───────────┘
┌─────────────────────┐
│ Leaf Node           │
│ Key1 → Value1       │
│ Key2 → Value2       │
└─────────────────────┘

Property Context (propbag.go)

Key-value storage for properties:

bag, err := ltp.NewPropertyBag(node)
if err != nil {
    log.Fatal(err)
}

// Check existence
if bag.Exists(ltp.PidTagSubject) {
    subject, _ := bag.GetString(ltp.PidTagSubject)
}

// List all properties
for _, propID := range bag.Properties() {
    propType, _ := bag.GetType(propID)
}

Property Types

Type Value Size Description
Int16 0x0002 2 Signed 16-bit
Int32 0x0003 4 Signed 32-bit
Bool 0x000B 2 Boolean
Int64 0x0014 8 Signed 64-bit
String8 0x001E Variable ANSI string
String 0x001F Variable Unicode string
SysTime 0x0040 8 FILETIME
Binary 0x0102 Variable Binary data

Table Context (table.go)

Tabular data with rows and columns:

table, err := ltp.NewTable(node)
if err != nil {
    log.Fatal(err)
}

// Get column info
for _, col := range table.Columns() {
    fmt.Printf("Column: 0x%04X\n", col.PropID)
}

// Iterate rows
for row, err := range table.Rows() {
    rowID := row.RowID()
    if row.HasProperty(ltp.PidTagDisplayName) {
        name, _ := row.GetString(ltp.PidTagDisplayName)
    }
}

Table Structure

┌─────────────────────────────────┐
│ TC Header                       │
│ - Signature (0x7C)              │
│ - Num columns                   │
│ - Size offsets                  │
│ - Row BTH ID                    │
│ - Row matrix HNID               │
│ - Column descriptors            │
└─────────────────────────────────┘
         │               │
         │               │
         ▼               ▼
┌─────────────┐  ┌─────────────────┐
│ Row Index   │  │ Row Matrix       │
│ BTH         │  │ (row data)       │
│ RowID → Idx │  │                  │
└─────────────┘  └─────────────────┘

Heap IDs vs Node IDs

The HNID (HeapNodeID) type can reference either:

  • Heap ID: Allocation within the current heap
  • Node ID: Subnode with its own data
if hnid.IsHeapID() {
    data, _ := heap.Read(hnid.ToHeapID())
} else {
    subnode, _ := node.LookupSubnode(hnid.ToNodeID())
    data, _ := subnode.ReadAll()
}

This allows properties to be stored inline (small values) or in subnodes (large values).

Property Access

Properties can be accessed by type:

// Fixed-size properties
int16Val, _ := bag.GetInt16(propID)
int32Val, _ := bag.GetInt32(propID)
int64Val, _ := bag.GetInt64(propID)
boolVal, _ := bag.GetBool(propID)

// Variable-size properties
stringVal, _ := bag.GetString(propID)
binaryVal, _ := bag.GetBinary(propID)

// Time properties
fileTime, _ := bag.GetTime(propID)
goTime := ltp.FileTimeToTime(fileTime)