/*!
 * Copyrights
 *
 * Portions created or assigned to Cisco Systems, Inc. are
 * Copyright (c) 2014-2016 Cisco Systems, Inc.  All Rights Reserved.
 */

#include <stdlib.h>
#include <string.h>
#include <jansson.h>
#include "cjose/header.h"
#include "include/header_int.h"

const char *CJOSE_HDR_ALG = "alg";
const char *CJOSE_HDR_ALG_NONE = "none";
const char *CJOSE_HDR_ALG_ECDH_ES = "ECDH-ES";
const char *CJOSE_HDR_ALG_RSA_OAEP = "RSA-OAEP";
const char *CJOSE_HDR_ALG_RSA1_5 = "RSA1_5";
const char *CJOSE_HDR_ALG_A128KW = "A128KW";
const char *CJOSE_HDR_ALG_A192KW = "A192KW";
const char *CJOSE_HDR_ALG_A256KW = "A256KW";
const char *CJOSE_HDR_ALG_DIR = "dir";
const char *CJOSE_HDR_ALG_PS256 = "PS256";
const char *CJOSE_HDR_ALG_PS384 = "PS384";
const char *CJOSE_HDR_ALG_PS512 = "PS512";
const char *CJOSE_HDR_ALG_RS256 = "RS256";
const char *CJOSE_HDR_ALG_RS384 = "RS384";
const char *CJOSE_HDR_ALG_RS512 = "RS512";
const char *CJOSE_HDR_ALG_HS256 = "HS256";
const char *CJOSE_HDR_ALG_HS384 = "HS384";
const char *CJOSE_HDR_ALG_HS512 = "HS512";
const char *CJOSE_HDR_ALG_ES256 = "ES256";
const char *CJOSE_HDR_ALG_ES384 = "ES384";
const char *CJOSE_HDR_ALG_ES512 = "ES512";

const char *CJOSE_HDR_ENC = "enc";
const char *CJOSE_HDR_ENC_A128GCM = "A128GCM";
const char *CJOSE_HDR_ENC_A192GCM = "A192GCM";
const char *CJOSE_HDR_ENC_A256GCM = "A256GCM";
const char *CJOSE_HDR_ENC_A128CBC_HS256 = "A128CBC-HS256";
const char *CJOSE_HDR_ENC_A192CBC_HS384 = "A192CBC-HS384";
const char *CJOSE_HDR_ENC_A256CBC_HS512 = "A256CBC-HS512";

const char *CJOSE_HDR_CTY = "cty";

const char *CJOSE_HDR_KID = "kid";

const char *CJOSE_HDR_EPK = "epk";

const char *CJOSE_HDR_APU = "apu";
const char *CJOSE_HDR_APV = "apv";

static const char *CJOSE_HDR_CRIT = "crit";

////////////////////////////////////////////////////////////////////////////////
bool _cjose_header_validate_crit(cjose_header_t *header, const char *const *supported, size_t supported_len, cjose_err *err)
{
    if (NULL == header)
    {
        return true;
    }

    json_t *crit = json_object_get((json_t *)header, CJOSE_HDR_CRIT);
    if (NULL == crit)
    {
        return true;
    }

    if (!json_is_array(crit))
    {
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return false;
    }

    // RFC 7515 section 4.1.11: if present, the "crit" list MUST NOT be empty
    if (0 == json_array_size(crit))
    {
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return false;
    }

    size_t index = 0;
    json_t *entry = NULL;
    json_array_foreach(crit, index, entry)
    {
        if (!json_is_string(entry))
        {
            CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
            return false;
        }

        const char *name = json_string_value(entry);
        bool found = false;
        for (size_t i = 0; i < supported_len; i++)
        {
            if (0 == strcmp(name, supported[i]))
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
            return false;
        }
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////////
cjose_header_t *cjose_header_new(cjose_err *err)
{
    cjose_header_t *retval = (cjose_header_t *)json_object();
    if (NULL == retval)
    {
        CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY);
    }
    return retval;
}

////////////////////////////////////////////////////////////////////////////////
cjose_header_t *cjose_header_retain(cjose_header_t *header)
{
    if (NULL != header)
    {
        header = (cjose_header_t *)json_incref((json_t *)header);
    }
    return header;
}

////////////////////////////////////////////////////////////////////////////////
void cjose_header_release(cjose_header_t *header)
{
    if (NULL != header)
    {
        json_decref((json_t *)header);
    }
}

////////////////////////////////////////////////////////////////////////////////
bool cjose_header_set(cjose_header_t *header, const char *attr, const char *value, cjose_err *err)
{
    if (NULL == header || NULL == attr || NULL == value)
    {
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return false;
    }

    json_t *value_obj = json_string(value);
    if (NULL == value_obj)
    {
        CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY);
        return false;
    }

    // json_object_set_new fails on OOM or an invalid attr key, and releases
    // value_obj either way; don't report success with the attribute unset
    if (0 != json_object_set_new((json_t *)header, attr, value_obj))
    {
        CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY);
        return false;
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////////
const char *cjose_header_get(cjose_header_t *header, const char *attr, cjose_err *err)
{
    if (NULL == header || NULL == attr)
    {
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return NULL;
    }

    json_t *value_obj = json_object_get((json_t *)header, attr);
    if (NULL == value_obj)
    {
        return NULL;
    }

    return json_string_value(value_obj);
}

////////////////////////////////////////////////////////////////////////////////
bool cjose_header_set_raw(cjose_header_t *header, const char *attr, const char *value, cjose_err *err)
{
    if (NULL == header || NULL == attr || NULL == value)
    {
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return false;
    }

    json_error_t j_err;
    // JSON_DECODE_ANY: the documented contract accepts any valid JSON value,
    // including a top-level scalar (e.g. RFC 7797 "b64":false), which jansson's
    // default (RFC 4627) mode rejects
    json_t *value_obj = json_loads(value, JSON_DECODE_ANY, &j_err);
    if (NULL == value_obj)
    {
        // unfortunately, it's not possible to tell whether the error is due
        // to syntax, or memory shortage. See https://github.com/akheron/jansson/issues/352
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return false;
    }

    // json_object_set_new fails on OOM or an invalid attr key, and releases
    // value_obj either way; don't report success with the attribute unset
    if (0 != json_object_set_new((json_t *)header, attr, value_obj))
    {
        CJOSE_ERROR(err, CJOSE_ERR_NO_MEMORY);
        return false;
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////////
char *cjose_header_get_raw(cjose_header_t *header, const char *attr, cjose_err *err)
{
    if (NULL == header || NULL == attr)
    {
        CJOSE_ERROR(err, CJOSE_ERR_INVALID_ARG);
        return NULL;
    }

    json_t *value_obj = json_object_get((json_t *)header, attr);
    if (NULL == value_obj)
    {
        return NULL;
    }

    // JSON_ENCODE_ANY so a top-level scalar value (see cjose_header_set_raw)
    // round-trips instead of returning NULL
    return json_dumps(value_obj, JSON_COMPACT | JSON_PRESERVE_ORDER | JSON_ENCODE_ANY);
}
