#ifndef AWS_C_CAL_DER_H
#define AWS_C_CAL_DER_H
/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include <aws/cal/exports.h>

#include <aws/common/array_list.h>
#include <aws/common/byte_buf.h>

struct aws_der_encoder;
struct aws_der_decoder;

/*
 * Note: encoder/decoder only supports unsigned representations of integers and usage
 * of signed integers might lead to unexpected results.
 * Context: DER spec requires ints to be stored in big endian format with MSB
 * representing signedness. To disambiguate between negative number and big
 * positive number, null byte can be added in front of positive number. DER spec
 * requires representation to be the shortest possible one.
 * During encoding aws_der_encoder_write_unsigned_integer assumes that cursor
 * points to a positive number and will prepend 0 if needed by DER spec to
 * indicate its positive number. Encoder does not support writing negative numbers.
 * Decoder aws_der_encoder_write_unsigned_integer will strip any leading 0 as
 * needed and will error out if der contains negative number.
 * Take special care when integrating with 3p libraries cause they might expect
 * different format. Ex. this format matches what openssl calls bin format
 * (BN_bin2bn) and might not work as expected with openssl mpi format.
 */

enum aws_der_type {
    /* Primitives */
    AWS_DER_BOOLEAN = 0x01,
    AWS_DER_INTEGER = 0x02,
    AWS_DER_BIT_STRING = 0x03,
    AWS_DER_OCTET_STRING = 0x04,
    AWS_DER_NULL = 0x05,
    AWS_DER_OBJECT_IDENTIFIER = 0x06,
    AWS_DER_BMPString = 0x1e,
    AWS_DER_UNICODE_STRING = AWS_DER_BMPString,
    AWS_DER_IA5String = 0x16, /* Unsupported */
    AWS_DER_PrintableString = 0x13,
    AWS_DER_TeletexString = 0x14, /* Unsupported */

    /* Constructed types */
    AWS_DER_SEQUENCE = 0x30,
    AWS_DER_SEQUENCE_OF = AWS_DER_SEQUENCE,
    AWS_DER_SET = 0x31,
    AWS_DER_SET_OF = AWS_DER_SET,
    AWS_DER_UTF8_STRING = 0x0c,

    /* class types */
    AWS_DER_CLASS_UNIVERSAL = 0x00,
    AWS_DER_CLASS_APPLICATION = 0x40,
    AWS_DER_CLASS_CONTEXT = 0x80,
    AWS_DER_CLASS_PRIVATE = 0xc0,

    /* forms */
    AWS_DER_FORM_CONSTRUCTED = 0x20,
    AWS_DER_FORM_PRIMITIVE = 0x00,
};

AWS_EXTERN_C_BEGIN

/**
 * Initializes a DER encoder
 * @param allocator The allocator to use for all allocations within the encoder
 * @param capacity The initial capacity of the encoder scratch buffer (the max size of all encoded TLVs)
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API struct aws_der_encoder *aws_der_encoder_new(struct aws_allocator *allocator, size_t capacity);

/**
 * Cleans up a DER encoder
 * @param encoder The encoder to clean up
 *
 * Note that this destroys the encoder buffer, invalidating any references to the contents given via get_contents()
 */
AWS_CAL_API void aws_der_encoder_destroy(struct aws_der_encoder *encoder);

/**
 * Writes an arbitrarily sized integer to the DER stream
 * @param encoder The encoder to use
 * @param integer A cursor pointing to the integer's memory
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_write_unsigned_integer(struct aws_der_encoder *encoder, struct aws_byte_cursor integer);
/**
 * Writes a boolean to the DER stream
 * @param encoder The encoder to use
 * @param boolean The boolean to write
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_write_boolean(struct aws_der_encoder *encoder, bool boolean);

/**
 * Writes a NULL token to the stream
 * @param encoder The encoder to write to
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_write_null(struct aws_der_encoder *encoder);

/**
 * Writes a BIT_STRING to the stream
 * @param encoder The encoder to use
 * @param bit_string The bit string to encode
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_write_bit_string(struct aws_der_encoder *encoder, struct aws_byte_cursor bit_string);

/**
 * Writes a string to the stream
 * @param encoder The encoder to use
 * @param octet_string The string to encode
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_write_octet_string(
    struct aws_der_encoder *encoder,
    struct aws_byte_cursor octet_string);

/**
 * Writes an object identifier to the stream
 * @param encoder The encoder to use
 * @param byes encoded bytes
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_write_object_identifier(struct aws_der_encoder *encoder, struct aws_byte_cursor bytes);

/**
 * Begins a SEQUENCE of objects in the DER stream
 * @param encoder The encoder to use
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_begin_sequence(struct aws_der_encoder *encoder);

/**
 * Finishes a SEQUENCE and applies it to the DER stream buffer
 * @param encoder The encoder to update
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_end_sequence(struct aws_der_encoder *encoder);

/**
 * Begins a Context Aware Tag of objects in the DER stream
 * The tag behaves similar to sequence with assigned tag value.
 * is_constructed is used to indicate that tag is wrapping constructed or primitive content.
 * Note: currently only supports regular sized tag values (i.e. 5 bits) and will error for anything larger.
 * We can cross that bridge when needed if anyone is really using huge value tags in the wild.
 * @param encoder The encoder to use
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_begin_context_aware_tag(
    struct aws_der_encoder *encoder,
    bool is_constructed,
    uint64_t tag_value);

/**
 * Finishes a SEQUENCE and applies it to the DER stream buffer
 * @param encoder The encoder to update
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_end_context_aware_tag(struct aws_der_encoder *encoder);

/**
 * Begins a SET of objects in the DER stream
 * @param encoder The encoder to use
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_begin_set(struct aws_der_encoder *encoder);

/**
 * Finishes a SET and applies it to the DER stream buffer
 * @param encoder The encoder to update
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_end_set(struct aws_der_encoder *encoder);

/**
 * Retrieves the contents of the encoder stream buffer
 * @param encoder The encoder to read from
 * @param cursor The cursor to point at the stream buffer
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_encoder_get_contents(struct aws_der_encoder *encoder, struct aws_byte_cursor *contents);

/**
 * Initializes an DER decoder
 * @param allocator The allocator to use
 * @param input The DER formatted buffer to parse
 * @return Initialized decoder, or NULL
 */
AWS_CAL_API struct aws_der_decoder *aws_der_decoder_new(struct aws_allocator *allocator, struct aws_byte_cursor input);

/**
 * Initializes new decoder from string at the current location.
 * Useful for cases where asn1 structure is nested inside another one, ex. ec pkcs8.
 * @param decoder Current decoder
 * @return Initialized decoder, or NULL
 */
AWS_CAL_API struct aws_der_decoder *aws_der_decoder_nested_tlv_decoder(struct aws_der_decoder *decoder);

/**
 * Cleans up a DER encoder
 * @param decoder The encoder to clean up
 */
AWS_CAL_API void aws_der_decoder_destroy(struct aws_der_decoder *decoder);

/**
 * Allows for iteration over the decoded TLVs.
 * @param decoder The decoder to iterate over
 * @return true if there is a tlv to read after advancing, false when done
 */
AWS_CAL_API bool aws_der_decoder_next(struct aws_der_decoder *decoder);

/**
 * Resets der decoder to the start.
 * @param decoder The decoder to reset
 */
AWS_CAL_API void aws_der_decoder_reset(struct aws_der_decoder *decoder);

/**
 * The type of the current TLV
 * @param decoder The decoder to inspect
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API enum aws_der_type aws_der_decoder_tlv_type(struct aws_der_decoder *decoder);

/**
 * The size of the current TLV
 * @param decoder The decoder to inspect
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API size_t aws_der_decoder_tlv_length(struct aws_der_decoder *decoder);

/**
 * The number of elements in the current TLV container
 * @param decoder The decoder to inspect
 * @return Number of elements in the current container
 */
AWS_CAL_API size_t aws_der_decoder_tlv_count(struct aws_der_decoder *decoder);

/**
 * Extracts the current TLV string value (BIT_STRING, OCTET_STRING)
 * @param decoder The decoder to extract from
 * @param string The buffer to store the string into
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_decoder_tlv_string(struct aws_der_decoder *decoder, struct aws_byte_cursor *string);

/**
 * Extracts the current TLV INTEGER value (INTEGER)
 * @param decoder The decoder to extract from
 * @param integer The buffer to store the integer into
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_decoder_tlv_unsigned_integer(struct aws_der_decoder *decoder, struct aws_byte_cursor *integer);

/**
 * Extracts the current TLV BOOLEAN value (BOOLEAN)
 * @param decoder The decoder to extract from
 * @param boolean The boolean to store the value into
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_decoder_tlv_boolean(struct aws_der_decoder *decoder, bool *boolean);

/**
 * Extracts the current TLV value as a blob
 * @param decoder The decoder to extract from
 * @param blob The buffer to store the value into
 * @return AWS_OP_ERR if an error occurs, otherwise AWS_OP_SUCCESS
 */
AWS_CAL_API int aws_der_decoder_tlv_blob(struct aws_der_decoder *decoder, struct aws_byte_cursor *blob);

AWS_EXTERN_C_END

#endif
