#include "object/aspa.h"

#include "asn1/asn1c/ASProviderAttestation.h"
#include "asn1/decode.h"
#include "config.h"
#include "log.h"
#include "object/signed_object.h"
#include "thread_var.h"

#define ASID_MAX UINT32_MAX

static int
decode_aspa(struct signed_object *sobj, struct ASProviderAttestation **result)
{
	return asn1_decode_octet_string(
		sobj->sdata->encapContentInfo.eContent,
		&asn_DEF_ASProviderAttestation,
		(void **) result,
		true
	);
}

static int
validate_version(INTEGER_t *version)
{
	long primitive;

	if (version == NULL)
		return pr_val_err("Version number is NULL.");
	if (asn_INTEGER2long(version, &primitive) < 0)
		return pr_val_err("Version number %s", strerror(errno));
	if (primitive != 1)
		return pr_val_err("Version number is not 1: %ld", primitive);

	return 0;
}

static int
parse_asid(char const *what, ASId_t *asid, uint32_t *result)
{
	unsigned long primitive;

	if (asid == NULL)
		return pr_val_err("%s is NULL.", what);
	if (asn_INTEGER2ulong(asid, &primitive) < 0)
		return pr_val_err("%s %s", what, strerror(errno));
	if (primitive > ASID_MAX)
		return pr_val_err("%s out of range. (0-%u)", what, ASID_MAX);

	*result = primitive;
	return 0;
}

static int
parse_customer(ASId_t *asid, struct resources *parent, uint32_t *result)
{
	int error;

	error = parse_asid("customerASID", asid, result);
	if (error)
		return error;

	if (*result == 0)
		return pr_val_err("Customer 0 is not allowed...");

	if (!resources_matches_asn(parent, *result))
		return pr_val_err(
		    "EE certificate's ASN extension does not exactly match customerASID %u.",
		    *result);

	return 0;
}

static int
parse_providers(ProviderASSet_t *set, struct aspa *aspa)
{
	uint32_t *providers;
	unsigned int limit;
	int i;
	int error;

	aspa->providers.asids = NULL;
	aspa->providers.count = 0;

	if (set == NULL)
		return pr_val_err("Providers set is NULL.");

	limit = config_get_max_aspa_providers();
	if (set->list.count > limit)
		return pr_val_err("customerASID %u has too many providers: %d > %u",
		    aspa->customer, set->list.count, limit);

	providers = pcalloc(set->list.count, sizeof(uint32_t));
	for (i = 0; i < set->list.count; i++) {
		error = parse_asid("Provider", set->list.array[i], &providers[i]);
		if (error)
			goto cancel;

		if (providers[i] == aspa->customer) {
			error = pr_val_err("The Providers list contains the customer's ASID (%u).",
			    aspa->customer);
			goto cancel;
		}
		if (providers[i] == 0 && set->list.count != 1) {
			error = pr_val_err("Provider ASID '0' is not in a single item list.");
			goto cancel;
		}
		if (i != 0) {
			if (providers[i - 1] == providers[i]) {
				error = pr_val_err("Provider ASID '%u' is listed more than once.", providers[i]);
				goto cancel;
			}
			if (providers[i - 1] > providers[i]) {
				error = pr_val_err("The Provider ASIDs are not listed in ascending order.");
				goto cancel;
			}
		}
	}

	aspa->providers.asids = providers;
	aspa->providers.count = set->list.count;
	return 0;

cancel:	free(providers);
	return error;
}

static int
__handle_aspa(struct ASProviderAttestation *asn1, struct resources *parent)
{
	struct aspa *aspa;
	int error;

	error = validate_version(asn1->version);
	if (error)
		return error;

	aspa = pzalloc(sizeof(struct aspa));
	aspa->refs = 1;

	error = parse_customer(&asn1->customerASID, parent, &aspa->customer);
	if (error)
		goto end;

	error = parse_providers(&asn1->providers, aspa);
	if (error)
		goto end;

	error = vhandler_handle_aspa(aspa);

end:	aspa_refput(aspa);
	return error;
}

int
aspa_traverse(struct rpki_uri *uri, struct rpp *pp)
{
	static OID oid = OID_ASPA;
	struct oid_arcs arcs = OID2ARCS("aspa", oid);
	struct signed_object sobj;
	struct ee_cert ee;
	struct ASProviderAttestation *aspa;
	STACK_OF(X509_CRL) *crl;
	int error;

	/* Prepare */
	pr_val_debug("ASPA '%s' {", uri_val_get_printable(uri));
	fnstack_push_uri(uri);

	/* Decode */
	error = signed_object_decode(&sobj, uri);
	if (error)
		goto revert_log;
	error = decode_aspa(&sobj, &aspa);
	if (error)
		goto revert_sobj;

	/* Prepare validation arguments */
	error = rpp_crl(pp, &crl);
	if (error)
		goto revert_roa;
	eecert_init(&ee, EET_ASPA, crl, false);

	/* Validate and handle everything */
	error = signed_object_validate(&sobj, &arcs, &ee);
	if (error)
		goto revert_args;
	error = __handle_aspa(aspa, ee.res);
	if (error)
		goto revert_args;
	error = refs_validate_ee(&ee.refs, pp, uri); // XXX why last?

revert_args:
	eecert_cleanup(&ee);
revert_roa:
	ASN_STRUCT_FREE(asn_DEF_ASProviderAttestation, aspa);
revert_sobj:
	signed_object_cleanup(&sobj);
revert_log:
	fnstack_pop();
	pr_val_debug("}");
	return error;
}
