
/**
 * \file
 * \brief
 * CBOR parsing
 */

#ifndef CN_CBOR_H
#define CN_CBOR_H

#ifdef  __cplusplus
extern "C" {
#endif
#ifdef EMACS_INDENTATION_HELPER
} /* Duh. */
#endif

#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>

/**
 * All of the different kinds of CBOR values.
 */
typedef enum cn_cbor_type {
  /** false */
  CN_CBOR_FALSE,
  /** true */
  CN_CBOR_TRUE,
  /** null */
  CN_CBOR_NULL,
  /** undefined */
  CN_CBOR_UNDEF,
  /** Positive integers */
  CN_CBOR_UINT,
  /** Negative integers */
  CN_CBOR_INT,
  /** Byte string */
  CN_CBOR_BYTES,
  /** UTF-8 string */
  CN_CBOR_TEXT,
  /** Byte string, in chunks.  Each chunk is a child. */
  CN_CBOR_BYTES_CHUNKED,
  /** UTF-8 string, in chunks.  Each chunk is a child */
  CN_CBOR_TEXT_CHUNKED,
  /** Array of CBOR values.  Each array element is a child, in order */
  CN_CBOR_ARRAY,
  /** Map of key/value pairs.  Each key and value is a child, alternating. */
  CN_CBOR_MAP,
  /** Tag describing the next value.  The next value is the single child. */
  CN_CBOR_TAG,
  /** Simple value, other than the defined ones */
  CN_CBOR_SIMPLE,
  /** Doubles, floats, and half-floats */
  CN_CBOR_DOUBLE,
  /** Floats, and half-floats */
  CN_CBOR_FLOAT,
  /** An error has occurred */
  CN_CBOR_INVALID
} cn_cbor_type;

/**
 * Flags used during parsing.  Not useful for consumers of the
 * `cn_cbor` structure.
 */
typedef enum cn_cbor_flags {
  /** The count field will be used for parsing */
  CN_CBOR_FL_COUNT = 1,
  /** An indefinite number of children */
  CN_CBOR_FL_INDEF = 2,
  /** Not used yet; the structure must free the v.str pointer when the
     structure is freed */
  CN_CBOR_FL_OWNER = 0x80,            /* of str */
} cn_cbor_flags;

/**
 * A CBOR value
 */
typedef struct cn_cbor {
  /** The type of value */
  cn_cbor_type type;
  /** Flags used at parse time */
  cn_cbor_flags flags;
  /** Data associated with the value; different branches of the union are
      used depending on the `type` field. */
  union {
    /** CN_CBOR_BYTES */
    const uint8_t* bytes;
    /** CN_CBOR_TEXT */
    const char* str;
    /** CN_CBOR_INT */
    long sint;
    /** CN_CBOR_UINT */
    unsigned long uint;
    /** CN_CBOR_DOUBLE */
    double dbl;
    /** CN_CBOR_FLOAT */
    float f;
    /** for use during parsing */
    unsigned long count;
  } v;                          /* TBD: optimize immediate */
  /** Number of children.
    * @note: for maps, this is 2x the number of entries */
  int length;
  /** The first child value */
  struct cn_cbor* first_child;
  /** The last child value */
  struct cn_cbor* last_child;
  /** The sibling after this one, or NULL if this is the last */
  struct cn_cbor* next;
  /** The parent of this value, or NULL if this is the root */
  struct cn_cbor* parent;
} cn_cbor;

/**
 * All of the different kinds of errors
 */
typedef enum cn_cbor_error {
  /** No error has occurred */
  CN_CBOR_NO_ERROR,
  /** More data was expected while parsing */
  CN_CBOR_ERR_OUT_OF_DATA,
  /** Some extra data was left over at the end of parsing */
  CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED,
  /** A map should be alternating keys and values.  A break was found
      when a value was expected */
  CN_CBOR_ERR_ODD_SIZE_INDEF_MAP,
  /** A break was found where it wasn't expected */
  CN_CBOR_ERR_BREAK_OUTSIDE_INDEF,
  /** Indefinite encoding works for bstrs, strings, arrays, and maps.
      A different major type tried to use it. */
  CN_CBOR_ERR_MT_UNDEF_FOR_INDEF,
  /** Additional Information values 28-30 are reserved */
  CN_CBOR_ERR_RESERVED_AI,
  /** A chunked encoding was used for a string or bstr, and one of the elements
      wasn't the expected (string/bstr) type */
  CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING,
  /** An invalid parameter was passed to a function */
  CN_CBOR_ERR_INVALID_PARAMETER,
  /** Allocation failed */
  CN_CBOR_ERR_OUT_OF_MEMORY,
  /** A float was encountered during parse but the library was built without
      support for float types. */
  CN_CBOR_ERR_FLOAT_NOT_SUPPORTED
} cn_cbor_error;

/**
 * Strings matching the `cn_cbor_error` conditions.
 *
 * @todo: turn into a function to make the type safety more clear?
 */
extern const char *cn_cbor_error_str[];

/**
 * Errors
 */
typedef struct cn_cbor_errback {
  /** The position in the input where the erorr happened */
  int pos;
  /** The error, or CN_CBOR_NO_ERROR if none */
  cn_cbor_error err;
} cn_cbor_errback;

#ifdef USE_CBOR_CONTEXT

/**
 * Allocate and zero out memory for a single cn_cbor structure.
 * The `context` is the `cn_cbor_context.context` passed in earlier to the CBOR routine.
 *
 * @param[in] context The allocation context
 */
typedef cn_cbor* (*cn_calloc_func)(void *context);

/**
 * Free memory previously allocated with a context.  If using a pool allocator,
 * this function will often be a no-op.
 *
 * @note: it may be that this is never needed; if so, it will be removed for
 * clarity and speed.
 *
 * @param  ptr     The structure to deallocate
 * @param  context The allocation context
 */
typedef void (*cn_free_func)(cn_cbor *ptr, void *context);

/**
 * The allocation context.
 */
typedef struct cn_cbor_context {
    /** The pool `calloc` routine.  Must allocate and zero. */
    cn_calloc_func calloc_func;
    /** The pool `free` routine.  Often a no-op, but required. */
    cn_free_func  free_func;
    /** Typically, the pool object, to be used when calling `calloc_func`
      * and `free_func` */
    void *context;
} cn_cbor_context;

/** When USE_CBOR_CONTEXT is defined, many functions take an extra `context`
  * parameter */
#define CBOR_CONTEXT , cn_cbor_context *context
/** When USE_CBOR_CONTEXT is defined, some functions take an extra `context`
  * parameter at the beginning */
#define CBOR_CONTEXT_COMMA cn_cbor_context *context,

#else

#define CBOR_CONTEXT
#define CBOR_CONTEXT_COMMA

#endif

/**
 * Decode an array of CBOR bytes into structures.
 *
 * @param[in]  buf          The array of bytes to parse
 * @param[in]  len          The number of bytes in the array
 * @param[in]  CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out] errp         Error, if NULL is returned
 * @return                  The parsed CBOR structure, or NULL on error
 */
cn_cbor* cn_cbor_decode(const uint8_t *buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp);

/**
 * Get a value from a CBOR map that has the given string as a key.
 *
 * @param[in]  cb           The CBOR map
 * @param[in]  key          The string to look up in the map
 * @return                  The matching value, or NULL if the key is not found
 */
cn_cbor* cn_cbor_mapget_string(const cn_cbor* cb, const char* key);

/**
 * Get a value from a CBOR map that has the given integer as a key.
 *
 * @param[in]  cb           The CBOR map
 * @param[in]  key          The int to look up in the map
 * @return                  The matching value, or NULL if the key is not found
 */
cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key);

/**
 * Get the item with the given index from a CBOR array.
 *
 * @param[in]  cb           The CBOR map
 * @param[in]  idx          The array index
 * @return                  The matching value, or NULL if the index is invalid
 */
cn_cbor* cn_cbor_index(const cn_cbor* cb, unsigned int idx);

/**
 * Free the given CBOR structure.
 * You MUST NOT try to free a cn_cbor structure with a parent (i.e., one
 * that is not a root in the tree).
 *
 * @param[in]  cb           The CBOR value to free.  May be NULL, or a root object.
 * @param[in]  CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 */
void cn_cbor_free(cn_cbor* cb CBOR_CONTEXT);

/**
 * Write a CBOR value and all of the child values.
 *
 * @param[in]  buf        The buffer into which to write
 * @param[in]  buf_offset The offset (in bytes) from the beginning of the buffer
 *                        to start writing at
 * @param[in]  buf_size   The total length (in bytes) of the buffer
 * @param[in]  cb         [description]
 * @return                -1 on fail, or number of bytes written
 */
ssize_t cn_cbor_encoder_write(uint8_t *buf,
			      size_t buf_offset,
			      size_t buf_size,
			      const cn_cbor *cb);

/**
 * Create a CBOR map.
 *
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created map, or NULL on error
 */
cn_cbor* cn_cbor_map_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp);

/**
 * Create a CBOR byte string.  The data in the byte string is *not* owned
 * by the CBOR object, so it is not freed automatically.
 *
 * @param[in]   data         The data
 * @param[in]   len          The number of bytes of data
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_data_create(const uint8_t* data, int len
                             CBOR_CONTEXT,
                             cn_cbor_errback *errp);

/**
 * Create a CBOR UTF-8 string.  The data is not checked for UTF-8 correctness.
 * The data being stored in the string is *not* owned the CBOR object, so it is
 * not freed automatically.
 *
 * @note: Do NOT use this function with untrusted data.  It calls strlen, and
 * relies on proper NULL-termination.
 *
 * @param[in]   data         NULL-terminated UTF-8 string
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_string_create(const char* data
                               CBOR_CONTEXT,
                               cn_cbor_errback *errp);

/**
 * Create a CBOR integer (either positive or negative).
 *
 * @param[in]   value    the value of the integer
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_int_create(int64_t value
                            CBOR_CONTEXT,
                            cn_cbor_errback *errp);

#ifndef CBOR_NO_FLOAT
/**
 * Create a CBOR float.
 *
 * @param[in]   value    the value of the float
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_float_create(float value
                              CBOR_CONTEXT,
                              cn_cbor_errback *errp);

/**
 * Create a CBOR double.
 *
 * @param[in]   value    the value of the double
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_double_create(double value
                               CBOR_CONTEXT,
                               cn_cbor_errback *errp);
#endif /* CBOR_NO_FLOAT */

/**
 * Put a CBOR object into a map with a CBOR object key.  Duplicate checks are NOT
 * currently performed.
 *
 * @param[in]   cb_map       The map to insert into
 * @param[in]   key          The key
 * @param[in]   cb_value     The value
 * @param[out]  errp         Error
 * @return                   True on success
 */
bool cn_cbor_map_put(cn_cbor* cb_map,
                     cn_cbor *cb_key, cn_cbor *cb_value,
                     cn_cbor_errback *errp);

/**
 * Put a CBOR object into a map with an integer key.  Duplicate checks are NOT
 * currently performed.
 *
 * @param[in]   cb_map       The map to insert into
 * @param[in]   key          The integer key
 * @param[in]   cb_value     The value
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error
 * @return                   True on success
 */
bool cn_cbor_mapput_int(cn_cbor* cb_map,
                        int64_t key, cn_cbor* cb_value
                        CBOR_CONTEXT,
                        cn_cbor_errback *errp);

/**
 * Put a CBOR object into a map with a string key.  Duplicate checks are NOT
 * currently performed.
 *
 * @note: do not call this routine with untrusted string data.  It calls
 * strlen, and requires a properly NULL-terminated key.
 *
 * @param[in]   cb_map       The map to insert into
 * @param[in]   key          The string key
 * @param[in]   cb_value     The value
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error
 * @return                   True on success
 */
bool cn_cbor_mapput_string(cn_cbor* cb_map,
                           const char* key, cn_cbor* cb_value
                           CBOR_CONTEXT,
                           cn_cbor_errback *errp);

/**
 * Create a CBOR array
 *
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_array_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp);

/**
 * Append an item to the end of a CBOR array.
 *
 * @param[in]   cb_array  The array into which to insert
 * @param[in]   cb_value  The value to insert
 * @param[out]  errp      Error
 * @return                True on success
 */
bool cn_cbor_array_append(cn_cbor* cb_array,
                          cn_cbor* cb_value,
                          cn_cbor_errback *errp);

#ifdef  __cplusplus
}
#endif

#endif  /* CN_CBOR_H */
