/** @file
 * Definitions for the Wireshark CBOR item decoding API.
 * References:
 *     RFC 8949: https://tools.ietf.org/html/rfc8949
 *
 * Copyright 2019-2021, Brian Sipos <brian.sipos@gmail.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifndef __WSCBOR_H__
#define __WSCBOR_H__

#include <ws_symbol_export.h>
#include <epan/tvbuff.h>
#include <epan/proto.h>
#include <epan/expert.h>
#include <wsutil/wmem/wmem_list.h>

#ifdef __cplusplus
extern "C" {
#endif

/** Register expert info and other wireshark data.
 */
WS_DLL_PUBLIC
void wscbor_init(void);

/** Expose available expert info for this library.
 * @param[out] size Set to the size of the array.
 * @return The array of expert info objects.
 */
WS_DLL_PUBLIC
const ei_register_info * wscbor_expert_items(int *size);

/// The same enumeration from libcbor-0.5
typedef enum cbor_type {
    CBOR_TYPE_UINT = 0, ///< positive integers
    CBOR_TYPE_NEGINT = 1, ///< negative integers
    CBOR_TYPE_BYTESTRING = 2, ///< byte strings
    CBOR_TYPE_STRING = 3, ///< text strings
    CBOR_TYPE_ARRAY = 4, ///< arrays
    CBOR_TYPE_MAP = 5, ///< maps
    CBOR_TYPE_TAG = 6, ///< tags
    CBOR_TYPE_FLOAT_CTRL = 7, ///< decimals and special values (true, false, nil, ...)
} cbor_type;

/// The same enumeration from libcbor-0.5
typedef enum {
    CBOR_CTRL_NONE = 0,
    CBOR_CTRL_FALSE = 20,
    CBOR_CTRL_TRUE = 21,
    CBOR_CTRL_NULL = 22,
    CBOR_CTRL_UNDEF = 23
} _cbor_ctrl;

/// Decoding or require_* error
typedef struct {
    /// The associated expert info
    expert_field *ei;
    /// Optional specific text
    const char *msg;
} wscbor_error_t;

/** Construct a new error object.
 *
 * @param alloc The allocator to use.
 * @param ei The specific error type.
 * @param format If non-NULL, a message format string.
 * @return The new object.
 */
WS_DLL_PUBLIC
wscbor_error_t * wscbor_error_new(wmem_allocator_t *alloc, expert_field *ei, const char *format, ...);

/// Tag metadata and value
typedef struct {
    /// The start offset of this tag head
    gint start;
    /// The length of just this tag head
    gint length;
    /// The tag value
    guint64 value;
} wscbor_tag_t;

struct _wscbor_chunk_priv_t;
typedef struct _wscbor_chunk_priv_t wscbor_chunk_priv_t;
/// A data-containing, optionally-tagged chunk of CBOR
typedef struct {
    /// Internal private data
    wscbor_chunk_priv_t *_priv;

    /// The start offset of this chunk
    gint start;
    /// The length of just this header and any preceding tags
    gint head_length;
    /// The length of this chunk and its immediate definite data (i.e. strings)
    gint data_length;
    /// Errors processing this chunk (type wscbor_error_t*)
    wmem_list_t *errors;
    /// Tags on this chunk, in encoded order (type wscbor_tag_t*)
    wmem_list_t *tags;

    /// Major type of this block.
    /// This will be one of the cbor_type values.
    cbor_type type_major;
    /// Minor type of this item
    guint8 type_minor;
    /// The header-encoded value
    guint64 head_value;
} wscbor_chunk_t;

/** Scan for a tagged chunk of headers.
 * The chunk of byte string and text string items includes the data content
 * in its @c offset.
 *
 * @param alloc The allocator to use.
 * @param tvb The TVB to read from.
 * @param[in,out] offset The offset with in @c tvb.
 * This is updated to be just past the new chunk.
 * @return The chunk of data found, including any errors.
 * This never returns NULL.
 * @post This can throw ReportedBoundsError or ContainedBoundsError
 * if the read itself ran out of data.
 */
WS_DLL_PUBLIC
wscbor_chunk_t * wscbor_chunk_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset);

/** Free a chunk and its lists.
 */
WS_DLL_PUBLIC
void wscbor_chunk_free(wscbor_chunk_t *chunk);

/** After both reading and decoding a chunk, report on any errors found.
 * @param pinfo The associated packet.
 * @param item The associated tree item.
 * @param chunk The chunk with possible errors.
 * @return The error count.
 */
WS_DLL_PUBLIC
guint64 wscbor_chunk_mark_errors(packet_info *pinfo, proto_item *item, const wscbor_chunk_t *chunk);

/** Determine if a chunk has errors.
 * @param chunk The chunk with possible errors.
 * @return The error count.
 */
WS_DLL_PUBLIC
guint wscbor_has_errors(const wscbor_chunk_t *chunk);

/** Determine if an indefinite break is present.
 *
 * @param chunk The chunk to check.
 * @return True if it's an indefinite break.
 */
WS_DLL_PUBLIC
gboolean wscbor_is_indefinite_break(const wscbor_chunk_t *chunk);

/** Recursively skip items from a stream.
 *
 * @param alloc The allocator to use.
 * @param tvb The data buffer.
 * @param[in,out] offset The initial offset to read and skip over.
 * Will be set to one-past the last valid CBOR (possibly nested) present.
 * @return True if the skipped item was fully valid.
 * @post This can throw ReportedBoundsError or ContainedBoundsError
 * if the read itself ran out of data.
 */
WS_DLL_PUBLIC
gboolean wscbor_skip_next_item(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset);

/** Skip over an item if a chunk has errors.
 * This allows skipping an entire array or map if the major type or size is
 * not as expected.
 *
 * @param alloc The allocator to use.
 * @param tvb The data buffer.
 * @param[in,out] offset The initial offset to read and skip over.
 * @param chunk The chunk with possible errors.
 * @return True if there were errors and the item skipped.
 */
WS_DLL_PUBLIC
gboolean wscbor_skip_if_errors(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset, const wscbor_chunk_t *chunk);


/** Require a specific item major type.
 *
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @param major The required major type.
 * @return True if the item is that type.
 */
WS_DLL_PUBLIC
gboolean wscbor_require_major_type(wscbor_chunk_t *chunk, cbor_type major);

/** Require an array item.
 *
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return True if the item is an array.
 */
WS_DLL_PUBLIC
gboolean wscbor_require_array(wscbor_chunk_t *chunk);

/** Require an array have a specific ranged size.
 *
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @param count_min The minimum acceptable size.
 * @param count_max The maximum acceptable size.
 * @return True if the size is acceptable.
 */
WS_DLL_PUBLIC
gboolean wscbor_require_array_size(wscbor_chunk_t *chunk, guint64 count_min, guint64 count_max);

/** Require a map item.
 *
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return True if the item is a map.
 */
WS_DLL_PUBLIC
gboolean wscbor_require_map(wscbor_chunk_t *chunk);

/** Require a CBOR item to have a boolean value.
 *
 * @param alloc The allocator to use.
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return Pointer to the boolean value, if the item was boolean.
 * The value can be deleted with wscbor_require_delete().
 */
WS_DLL_PUBLIC
gboolean * wscbor_require_boolean(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);

/** Require a CBOR item to have an unsigned-integer value.
 * @note This reader will clip the most significant bit of the value.
 *
 * @param alloc The allocator to use.
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return Pointer to the boolean value, if the item was an integer.
 * The value can be deleted with wscbor_require_delete().
 */
WS_DLL_PUBLIC
guint64 * wscbor_require_uint64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);

/** Require a CBOR item to have an signed- or unsigned-integer value.
 * @note This reader will clip the most significant bit of the value.
 *
 * @param alloc The allocator to use.
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return Pointer to the value, if the item was an integer.
 * The value can be deleted with wscbor_require_delete().
 */
WS_DLL_PUBLIC
gint64 * wscbor_require_int64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);

/** Require a CBOR item to have a text-string value.
 * If the actual text string is not needed, use the following to avoid an
 * unnecessary allocation.
 * @code
 * wscbor_require_major_type(chunk, CBOR_TYPE_STRING)
 * @endcode
 *
 * @param alloc The allocator to use.
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return Pointer to the null-terminated UTF-8, if the item was a tstr.
 * @post This can throw ContainedBoundsError string ran out of data.
 */
WS_DLL_PUBLIC
char * wscbor_require_tstr(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);

/** Require a CBOR item to have a byte-string value.
 * Use tvb_memdup() or similar if the raw byte-string is needed.
 *
 * @param alloc The allocator to use.
 * @param[in,out] chunk The chunk to read from and write errors on.
 * @return Pointer to the value, if the item was an bstr.
 * The value is memory managed by wireshark.
 */
WS_DLL_PUBLIC
tvbuff_t * wscbor_require_bstr(wmem_allocator_t *alloc, wscbor_chunk_t *chunk);

/** Add an item representing an array or map container.
 * If the item is type FT_UINT* or FT_INT* the count of (array) items
 * or map (pairs) is used as the iterm value.
 */
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_container(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);

/** Add an item representing a non-boolean, non-float control value.
 */
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_ctrl(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);

WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_boolean(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gboolean *value);

WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_uint64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value);

WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_int64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gint64 *value);

WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_bitmask(proto_tree *tree, int hfindex, const gint ett, int *const *fields, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value);

WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_tstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);

WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_bstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);

/** Add an item representing the length of a bstr or tstr value.
 */
WS_DLL_PUBLIC
proto_item * proto_tree_add_cbor_strlen(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk);

#ifdef __cplusplus
}
#endif

#endif /* __WSCBOR_H__ */
