/*
 * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp).
 * Copyright (c) 2022 Pol Henarejos.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "openpgp.h"
#include "asn1.h"

int parse_do(uint16_t *fids, int mode) {
    int len = 0;
    file_t *ef;
    for (int i = 0; i < fids[0]; i++) {
        if ((ef = search_by_fid(fids[i + 1], NULL, SPECIFY_EF))) {
            uint16_t data_len;
            if ((ef->type & FILE_DATA_FUNC) == FILE_DATA_FUNC) {
                data_len = ((int (*)(const file_t *, int))(ef->data))((const file_t *) ef, mode);
            }
            else {
                data_len = file_get_size(ef);
                if (mode == 1) {
                    if (fids[0] > 1 && res_APDU_size > 0) {
                        if (fids[i + 1] < 0x0100) {
                            res_APDU[res_APDU_size++] = fids[i + 1] & 0xff;
                        }
                        else {
                            res_APDU[res_APDU_size++] = fids[i + 1] >> 8;
                            res_APDU[res_APDU_size++] = fids[i + 1] & 0xff;
                        }
                        res_APDU_size += format_tlv_len(data_len, res_APDU + res_APDU_size);
                    }
                    if (file_has_data(ef)) {
                        memcpy(res_APDU + res_APDU_size, file_get_data(ef), data_len);
                    }
                    res_APDU_size += data_len;
                }
            }
            len += data_len;
        }
    }
    return len;
}

int parse_trium(uint16_t fid, uint8_t num, size_t size) {
    for (uint8_t i = 0; i < num; i++) {
        file_t *ef;
        if ((ef = search_by_fid(fid + i, NULL, SPECIFY_EF)) && ef->data) {
            uint16_t data_len = file_get_size(ef);
            memcpy(res_APDU + res_APDU_size, file_get_data(ef), data_len);
            res_APDU_size += data_len;
        }
        else {
            memset(res_APDU + res_APDU_size, 0, size);
            res_APDU_size += size;
        }
    }
    return num * size;
}

int parse_ch_data(const file_t *f, int mode) {
    uint16_t fids[] = {
        3,
        EF_CH_NAME, EF_LANG_PREF, EF_SEX,
    };
    res_APDU[res_APDU_size++] = EF_CH_DATA & 0xff;
    res_APDU[res_APDU_size++] = 0x82;
    uint8_t *lp = res_APDU + res_APDU_size;
    res_APDU_size += 2;
    parse_do(fids, mode);
    uint16_t lpdif = res_APDU + res_APDU_size - lp - 2;
    *lp++ = lpdif >> 8;
    *lp++ = lpdif & 0xff;
    return lpdif + 4;
}

int parse_sec_tpl(const file_t *f, int mode) {
    res_APDU[res_APDU_size++] = EF_SEC_TPL & 0xff;
    res_APDU[res_APDU_size++] = 5;
    file_t *ef = search_by_fid(EF_SIG_COUNT, NULL, SPECIFY_ANY);
    if (ef && ef->data) {
        res_APDU[res_APDU_size++] = EF_SIG_COUNT & 0xff;
        res_APDU[res_APDU_size++] = 3;
        memcpy(res_APDU + res_APDU_size, file_get_data(ef), 3);
        res_APDU_size += 3;
    }
    return 5 + 2;
}

int parse_ch_cert(const file_t *f, int mode) {
    return 0;
}

int parse_fp(const file_t *f, int mode) {
    res_APDU[res_APDU_size++] = EF_FP & 0xff;
    res_APDU[res_APDU_size++] = 60;
    return parse_trium(EF_FP_SIG, 3, 20) + 2;
}

int parse_cafp(const file_t *f, int mode) {
    res_APDU[res_APDU_size++] = EF_CA_FP & 0xff;
    res_APDU[res_APDU_size++] = 60;
    return parse_trium(EF_FP_CA1, 3, 20) + 2;
}

int parse_ts(const file_t *f, int mode) {
    res_APDU[res_APDU_size++] = EF_TS_ALL & 0xff;
    res_APDU[res_APDU_size++] = 12;
    return parse_trium(EF_TS_SIG, 3, 4) + 2;
}

int parse_keyinfo(const file_t *f, int mode) {
    int init_len = res_APDU_size;
    if (res_APDU_size > 0) {
        res_APDU[res_APDU_size++] = EF_KEY_INFO & 0xff;
        res_APDU[res_APDU_size++] = 6;
    }
    file_t *ef = search_by_fid(EF_PK_SIG, NULL, SPECIFY_ANY);
    res_APDU[res_APDU_size++] = 0x00;
    if (ef && ef->data) {
        res_APDU[res_APDU_size++] = 0x01;
    }
    else {
        res_APDU[res_APDU_size++] = 0x00;
    }

    ef = search_by_fid(EF_PK_DEC, NULL, SPECIFY_ANY);
    res_APDU[res_APDU_size++] = 0x01;
    if (ef && ef->data) {
        res_APDU[res_APDU_size++] = 0x01;
    }
    else {
        res_APDU[res_APDU_size++] = 0x00;
    }

    ef = search_by_fid(EF_PK_AUT, NULL, SPECIFY_ANY);
    res_APDU[res_APDU_size++] = 0x02;
    if (ef && ef->data) {
        res_APDU[res_APDU_size++] = 0x01;
    }
    else {
        res_APDU[res_APDU_size++] = 0x00;
    }
    return res_APDU_size - init_len;
}

int parse_pw_status(const file_t *f, int mode) {
    file_t *ef;
    int init_len = res_APDU_size;
    if (res_APDU_size > 0) {
        res_APDU[res_APDU_size++] = EF_PW_STATUS & 0xff;
        res_APDU[res_APDU_size++] = 7;
    }
    ef = search_by_fid(EF_PW_PRIV, NULL, SPECIFY_ANY);
    if (ef && ef->data) {
        memcpy(res_APDU + res_APDU_size, file_get_data(ef), 7);
        res_APDU_size += 7;
    }
    return res_APDU_size - init_len;
}

const uint8_t algorithm_attr_x448[] = {
    4,
    ALGO_ECDH,
    /* OID of X448 */
    0x2b, 0x65, 0x6f
};

const uint8_t algorithm_attr_rsa1k[] = {
    6,
    ALGO_RSA,
    0x04, 0x00,       /* Length modulus (in bit): 1024 */
    0x00, 0x20,       /* Length exponent (in bit): 32  */
    0x00          /* 0: Acceptable format is: P and Q */
};

const uint8_t algorithm_attr_rsa2k[] = {
    6,
    ALGO_RSA,
    0x08, 0x00,       /* Length modulus (in bit): 2048 */
    0x00, 0x20,       /* Length exponent (in bit): 32  */
    0x00          /* 0: Acceptable format is: P and Q */
};

const uint8_t algorithm_attr_rsa3k[] = {
    6,
    ALGO_RSA,
    0x0C, 0x00,       /* Length modulus (in bit): 3072 */
    0x00, 0x20,       /* Length exponent (in bit): 32  */
    0x00          /* 0: Acceptable format is: P and Q */
};

const uint8_t algorithm_attr_rsa4k[] = {
    6,
    ALGO_RSA,
    0x10, 0x00,       /* Length modulus (in bit): 4096 */
    0x00, 0x20,       /* Length exponent (in bit): 32  */
    0x00          /* 0: Acceptable format is: P and Q */
};

const uint8_t algorithm_attr_p256k1[] = {
    6,
    ALGO_ECDSA,
    0x2b, 0x81, 0x04, 0x00, 0x0a
};

const uint8_t algorithm_attr_p256r1[] = {
    9,
    ALGO_ECDSA,
    0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07
};

const uint8_t algorithm_attr_p384r1[] = {
    6,
    ALGO_ECDSA,
    0x2B, 0x81, 0x04, 0x00, 0x22
};

const uint8_t algorithm_attr_p521r1[] = {
    6,
    ALGO_ECDSA,
    0x2B, 0x81, 0x04, 0x00, 0x23
};

const uint8_t algorithm_attr_bp256r1[] = {
    10,
    ALGO_ECDSA,
    0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07
};

const uint8_t algorithm_attr_bp384r1[] = {
    10,
    ALGO_ECDSA,
    0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B
};

const uint8_t algorithm_attr_bp512r1[] = {
    10,
    ALGO_ECDSA,
    0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D
};

const uint8_t algorithm_attr_cv25519[] = {
    11,
    ALGO_ECDH,
    0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01
};

#ifdef MBEDTLS_EDDSA_C
const uint8_t algorithm_attr_ed25519[] = {
    10,
    ALGO_EDDSA,
    0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01
};

const uint8_t algorithm_attr_ed448[] = {
    4,
    ALGO_EDDSA,
    0x2b, 0x65, 0x71
};
#endif

int parse_algo(const uint8_t *algo, uint16_t tag) {
    res_APDU[res_APDU_size++] = tag & 0xff;
    memcpy(res_APDU + res_APDU_size, algo, algo[0] + 1);
    res_APDU_size += algo[0] + 1;
    return algo[0] + 2;
}

int parse_algoinfo(const file_t *f, int mode) {
    int datalen = 0;
    if (f->fid == EF_ALGO_INFO) {
        res_APDU[res_APDU_size++] = EF_ALGO_INFO & 0xff;
        res_APDU[res_APDU_size++] = 0x82;
        uint8_t *lp = res_APDU + res_APDU_size;
        res_APDU_size += 2;
        datalen += parse_algo(algorithm_attr_rsa1k, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_rsa2k, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_rsa3k, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_rsa4k, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_p256k1, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_p256r1, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_p384r1, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_p521r1, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_bp256r1, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_bp384r1, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_bp512r1, EF_ALGO_SIG);
#ifdef MBEDTLS_EDDSA_C
        datalen += parse_algo(algorithm_attr_ed25519, EF_ALGO_SIG);
        datalen += parse_algo(algorithm_attr_ed448, EF_ALGO_SIG);
#endif

        datalen += parse_algo(algorithm_attr_rsa1k, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_rsa2k, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_rsa3k, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_rsa4k, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_p256k1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_p256r1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_p384r1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_p521r1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_bp256r1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_bp384r1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_bp512r1, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_cv25519, EF_ALGO_DEC);
        datalen += parse_algo(algorithm_attr_x448, EF_ALGO_DEC);

        datalen += parse_algo(algorithm_attr_rsa1k, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_rsa2k, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_rsa3k, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_rsa4k, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_p256k1, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_p256r1, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_p384r1, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_p521r1, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_bp256r1, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_bp384r1, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_bp512r1, EF_ALGO_AUT);
#ifdef MBEDTLS_EDDSA_C
        datalen += parse_algo(algorithm_attr_ed25519, EF_ALGO_AUT);
        datalen += parse_algo(algorithm_attr_ed448, EF_ALGO_AUT);
#endif
        uint16_t lpdif = res_APDU + res_APDU_size - lp - 2;
        *lp++ = lpdif >> 8;
        *lp++ = lpdif & 0xff;
        datalen = lpdif + 4;
    }
    else if (f->fid == EF_ALGO_SIG || f->fid == EF_ALGO_DEC || f->fid == EF_ALGO_AUT) {
        uint16_t fid = 0x1000 | f->fid;
        file_t *ef;
        if (!(ef = search_by_fid(fid, NULL, SPECIFY_EF)) || !ef->data) {
            datalen += parse_algo(algorithm_attr_rsa2k, f->fid);
        }
        else {
            uint16_t len = file_get_size(ef);
            if (res_APDU_size > 0) {
                res_APDU[res_APDU_size++] = f->fid & 0xff;
                res_APDU[res_APDU_size++] = len & 0xff;
                datalen += 2;
            }
            memcpy(res_APDU + res_APDU_size, file_get_data(ef), len);
            res_APDU_size += len;
            datalen += len;
        }
    }
    return datalen;
}

int parse_app_data(const file_t *f, int mode) {
    uint16_t fids[] = {
        6,
        EF_FULL_AID, EF_HIST_BYTES, EF_EXLEN_INFO, EF_GFM, EF_DISCRETE_DO, EF_KEY_INFO
    };
    res_APDU[res_APDU_size++] = EF_APP_DATA & 0xff;
    res_APDU[res_APDU_size++] = 0x82;
    uint8_t *lp = res_APDU + res_APDU_size;
    res_APDU_size += 2;
    parse_do(fids, mode);
    uint16_t lpdif = res_APDU + res_APDU_size - lp - 2;
    *lp++ = lpdif >> 8;
    *lp++ = lpdif & 0xff;
    return lpdif + 4;
}

int parse_discrete_do(const file_t *f, int mode) {
    uint16_t fids[] = {
        11,
        EF_EXT_CAP, EF_ALGO_SIG, EF_ALGO_DEC, EF_ALGO_AUT, EF_PW_STATUS, EF_FP, EF_CA_FP, EF_TS_ALL,
        EF_UIF_SIG, EF_UIF_DEC, EF_UIF_AUT
    };
    res_APDU[res_APDU_size++] = EF_DISCRETE_DO & 0xff;
    res_APDU[res_APDU_size++] = 0x82;
    uint8_t *lp = res_APDU + res_APDU_size;
    res_APDU_size += 2;
    parse_do(fids, mode);
    uint16_t lpdif = res_APDU + res_APDU_size - lp - 2;
    *lp++ = lpdif >> 8;
    *lp++ = lpdif & 0xff;
    return lpdif + 4;
}
