#include "helpers/kmem.h"
#include "kern_utils.h"
#include "sandbox.h"
#include "helpers/patchfinder64.h"
#include "helpers/kexecute.h"

typedef uint64_t extension_hdr_t;
typedef uint64_t extension_t;

struct extension_hdr {
    /* 0x00 */    extension_hdr_t next;
    /* 0x08 */    uint64_t desc;
    /* 0x10 */    extension_t ext_lst;
    /* 0x18 */
};

struct extension {
    /* 0x00 */    extension_t next;
    /* 0x08 */    uint64_t desc; // always 0xffffffffffffffff
    /* 0x10 */    uint64_t ext_lst; // zero, since it's extension and not a header
    /* 0x18 */    uint8_t something[32]; // zeroed from what I've seen
    /* 0x38 */    uint32_t type; // see ext_type enum
    /* 0x3c */    uint32_t subtype; // either 0 or 4 (or whatever unhex gave?..)
    /* 0x40 */    uint64_t data; // a c string, meaning depends on type and hdr which had this extension
    /* 0x48 */    uint64_t data_len; // strlen(data)
    /* 0x50 */    uint64_t unk0; // always 0
    /* 0x58 */    uint64_t unk1; // always 0xdeadbeefdeadbeef
    /* 0x60 */
};

uint64_t _smalloc(uint64_t size) {
    return kexecute(find_smalloc(), size, 0, 0, 0, 0, 0, 0);
}

uint64_t smalloc(uint64_t size) {
    uint64_t ret = _smalloc(size);
    
    if (ret != 0) {
        // IOAlloc's of small size go to zalloc
        ret = zm_fix_addr(ret);
    }
    
    return ret;
}

uint64_t sstrdup(const char* s) {
    size_t slen = strlen(s) + 1;
    
    uint64_t ks = smalloc(slen);
    if (ks) {
        kwrite(ks, s, slen);
    }
    
    return ks;
}

// Notice: path should *not* end with '/' !
uint64_t extension_create_file(const char* path, uint64_t nextptr) {
    size_t slen = strlen(path);
    
    if (path[slen - 1] == '/') {
        fprintf(stderr, "No traling slash in path pls \n");
        return 0;
    }
    
    uint64_t ext_p = smalloc(sizeof(struct extension));
    uint64_t ks = sstrdup(path);
    
    if (ext_p && ks) {
        struct extension ext;
        bzero(&ext, sizeof(ext));
        ext.next = nextptr;
        ext.desc = 0xffffffffffffffff;
        
        ext.data = ks;
        ext.data_len = slen;
        
        kwrite(ext_p, &ext, sizeof(ext));
    } else {
        // XXX oh no a leak
    }
    
    return ext_p;
}

// get 64 higher bits of 64bit int multiplication
// https://stackoverflow.com/a/28904636
// ofc in asm it's done with 1 instruction huh
// XXX there has to be a cleaner way utilizing hardware support
uint64_t mulhi(uint64_t a, uint64_t b) {
    uint64_t    a_lo = (uint32_t)a;
    uint64_t    a_hi = a >> 32;
    uint64_t    b_lo = (uint32_t)b;
    uint64_t    b_hi = b >> 32;
    
    uint64_t    a_x_b_hi =  a_hi * b_hi;
    uint64_t    a_x_b_mid = a_hi * b_lo;
    uint64_t    b_x_a_mid = b_hi * a_lo;
    uint64_t    a_x_b_lo =  a_lo * b_lo;
    
    uint64_t    carry_bit = ((uint64_t)(uint32_t)a_x_b_mid +
                             (uint64_t)(uint32_t)b_x_a_mid +
                             (a_x_b_lo >> 32) ) >> 32;
    
    uint64_t    multhi = a_x_b_hi +
    (a_x_b_mid >> 32) + (b_x_a_mid >> 32) +
    carry_bit;
    
    return multhi;
}

int hashing_magic(const char *desc) {
    // inlined into exception_add
    uint64_t hashed = 0x1505;
    
    // if desc == NULL, then returned value would be 8
    // APPL optimizes it for some reason
    // but meh, desc should never be NULL or you get
    // null dereference in exception_add
    // if (desc == NULL) return 8;
    
    if (desc != NULL) {
        for (const char* dp = desc; *dp != '\0'; ++dp) {
            hashed += hashed << 5;
            hashed += (int64_t) *dp;
        }
    }
    
    uint64_t magic = 0xe38e38e38e38e38f;
    
    uint64_t hi = mulhi(hashed, magic);
    hi >>= 3;
    hi = (hi<<3) + hi;
    
    hashed -= hi;
    
    return hashed;
}

static const char *ent_key = "com.apple.security.exception.files.absolute-path.read-only";

uint64_t make_ext_hdr(const char* key, uint64_t ext_lst) {
    struct extension_hdr hdr;
    
    uint64_t khdr = smalloc(sizeof(hdr));
    
    if (khdr) {
        // we add headers to end
        hdr.next = 0;
        hdr.desc = sstrdup(key);
        if (hdr.desc == 0) {
            // XXX leak
            return 0;
        }
        
        hdr.ext_lst = ext_lst;
        kwrite(khdr, &hdr, sizeof(hdr));
    }
    
    return khdr;
}

void extension_add(uint64_t ext, uint64_t sb, const char* desc) {
    // XXX patchfinder + kexecute would be way better
    
    int slot = hashing_magic(ent_key);
    uint64_t insert_at_p = sb + sizeof(void*) + slot * sizeof(void*);
    uint64_t insert_at = rk64(insert_at_p);
    
    while (insert_at != 0) {
        uint64_t kdsc = rk64(insert_at + offsetof(struct extension_hdr, desc));
        
        if (kstrcmp(kdsc, desc) == 0) {
            break;
        }
        
        insert_at_p = insert_at;
        insert_at = rk64(insert_at);
    }
    
    if (insert_at == 0) {
        insert_at = make_ext_hdr(ent_key, ext);
        wk64(insert_at_p, insert_at);
    } else {
        // XXX no duplicate check
        uint64_t ext_lst_p = insert_at + offsetof(struct extension_hdr, ext_lst);
        uint64_t ext_lst = rk64(ext_lst_p);
        
        while (ext_lst != 0) {
            fprintf(stderr, "ext_lst_p = 0x%llx ext_lst = 0x%llx\n", ext_lst_p, ext_lst);
            ext_lst_p = ext_lst + offsetof(struct extension, next);
            ext_lst = rk64(ext_lst_p);
        }
        
        fprintf(stderr, "ext_lst_p = 0x%llx ext_lst = 0x%llx\n", ext_lst_p, ext_lst);
        
        wk64(ext_lst_p, ext);
    }
}

// 1 if yes
int has_file_extension(uint64_t sb, const char* path) {
    const char* desc = ent_key;
    int found = 0;
    
    int slot = hashing_magic(ent_key);
    uint64_t insert_at_p = sb + sizeof(void*) + slot * sizeof(void*);
    uint64_t insert_at = rk64(insert_at_p);
    
    while (insert_at != 0) {
        uint64_t kdsc = rk64(insert_at + offsetof(struct extension_hdr, desc));
        
        if (kstrcmp(kdsc, desc) == 0) {
            break;
        }
        
        insert_at_p = insert_at;
        insert_at = rk64(insert_at);
    }
    
    if (insert_at != 0) {
        uint64_t ext_lst = rk64(insert_at + offsetof(struct extension_hdr, ext_lst));
        
        uint64_t plen = strlen(path);
        char *exist = malloc(plen + 1);
        exist[plen] = '\0';
        
        while (ext_lst != 0) {
            // XXX no type/subtype check
            uint64_t data_len = rk64(ext_lst + offsetof(struct extension, data_len));
            if (data_len == plen) {
                uint64_t data = rk64(ext_lst + offsetof(struct extension, data));
                kread(data, exist, plen);
                
                if (strcmp(path, exist) == 0) {
                    found = 1;
                    break;
                }
            }
            
            ext_lst = rk64(ext_lst);
        }
        
        
        free(exist);
    }
    
    return found;
}
