// SPDX-License-Identifier: BSD-3-Clause
/*
 * cmdu.h
 * defines structs and functions for TLVs and CMDU manipulation.
 *
 * Copyright (C) 2021-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 */

#ifndef CMDU_H
#define CMDU_H

#include <stdint.h>
#include <sys/time.h>
#include <libubox/list.h>

#include <easy/easy.h>

/** struct cmdu_header - IEEE-1905 CMDU header */
struct cmdu_header {
	uint8_t version;
	uint8_t rsvd;		/**< reserved */
	uint16_t type;
	uint16_t mid;		/**< message id */
	uint8_t fid;		/**< fragment id */
	uint8_t flag;
} __attribute__ ((packed));

#define IS_CMDU_LAST_FRAGMENT(c)	!!((c)->hdr.flag & 0x80)
#define IS_CMDU_RELAY_MCAST(c)		!!((c)->hdr.flag & 0x40)

#define CMDU_SET_LAST_FRAGMENT(c)	(c)->hdr.flag |= 0x80
#define CMDU_SET_RELAY_MCAST(c)		(c)->hdr.flag |= 0x40

#define CMDU_DEFAULT_TIMEOUT		1000

/** struct cmdu_linear - represents a CMDU frame with header and TLVs */
struct cmdu_linear {
	struct cmdu_header hdr;
	uint8_t data[];
} __attribute__ ((packed));


#define TLV_HLEN		3

struct cmdu_frag {
	uint8_t *data;
	uint16_t len;
	struct list_head list;
};

enum cmdu_frag_scheme {
	CMDU_FRAG_SCHEME_BOUNDARY_TLV,
	CMDU_FRAG_SCHEME_BOUNDARY_OCTET,
};

/**
 * @struct cmdu_buff
 * @brief Control structure for a CMDU buffer
 *
 * This structure abstracts out a CMDU frame. In addition to holding pointer
 * to an actual CMDU frame, it holds meta information about the CMDU,
 * such as, for a Rx CMDU which interface it arrived on, sender's macaddress
 * etc.
 *
 * APIs are provided for working with a cmdu_buff. There are functions to
 * parse a cmdu_buff and get the list of TLVs within it, ititerate through TLVs
 * in a CMDU, adding a TLV, peeking a TLV, getting size of a CMDU frame etc.
 *
 * On the transmit and receive path, it is possible to achieve zero-memcpy for
 * CMDU manipulation including inserting/extracting the ethernet frame header.
 */
struct cmdu_buff {
	uint8_t *head;
	uint8_t *data;
	uint8_t *tail;
	uint8_t *end;
	uint8_t dev_macaddr[6];
	char dev_ifname[16];
	uint8_t origin[6];	/* source address of sender */
	uint8_t aladdr[6];	/* AL address of sender (when available) */
	uint32_t flags;
	uint16_t datalen;
	uint16_t len;
	struct cmdu_linear *cdata;
	uint32_t num_frags;
	struct list_head fraglist;
	struct list_head list;
};

#define CMDU_PROCESSED	0x1

#define IS_CMDU_PROCESSED(c)	!!((c)->flags & CMDU_PROCESSED)
#define CMDU_SET_PROCESSED(c)	((c)->flags |= CMDU_PROCESSED)


/**
 * @enum tlv_presence
 * @brief defines the policy for occurrences of a TLV in a CMDU frame.
 */
enum tlv_presence {
	TLV_PRESENT_UNDEFINED,
	TLV_PRESENT_ONE,		/**< only one tlv of this type present */

	TLV_PRESENT_MORE,		/**< one or more tlvs of this tlv present */
	TLV_PRESENT_ONE_OR_MORE = TLV_PRESENT_MORE,

	TLV_PRESENT_OPTIONAL_ONE,	/**< zero or one of this tlv present */
	TLV_PRESENT_ZERO_OR_ONE = TLV_PRESENT_OPTIONAL_ONE,

	TLV_PRESENT_OPTIONAL_MORE,	/**< zero or more of this tlv present */
	TLV_PRESENT_ZERO_OR_MORE = TLV_PRESENT_OPTIONAL_MORE,

	TLV_PRESENT_NUM,
};

struct tlv_policy {
	uint8_t type;
	uint16_t len;
	uint16_t minlen;
	uint16_t maxlen;
	enum tlv_presence present;
};

/**
 * @struct tlv
 * @brief defines an IEEE-1905 TLV
 */
struct tlv {
	uint8_t type;
	uint16_t len;
	uint8_t data[];
} __attribute__ ((packed));

/**
 * Allocates a TLV
 * @param[in] datalen length of tlv data in bytes
 *
 * @return newly allocated tlv on success, or NULL if failed.
 */
struct tlv *tlv_alloc(uint16_t datalen);

/** Free an allocated TLV */
void tlv_free_linear(struct tlv *t);

/** Zeros out a TLV */
void tlv_zero(struct tlv *t);

/* following functions for internal use only */
int tlv_ok(struct tlv *t, int rem);
struct tlv *tlv_next(struct tlv *t, int *rem);

/** Get length of TLV data */
uint16_t tlv_length(struct tlv *t);

/** Get total length of a TLV including the header */
uint16_t tlv_total_length(struct tlv *t);

/** Helper function to stringify TLV type */
const char *tlv_type2str(uint8_t type);

/* Allocates cmdu_buff to hold 'size' length cmdu payload */
struct cmdu_buff *cmdu_alloc(int size);		// XXX: internal use

/** Allocates cmdu_buff to hold 'size' length cmdu payload */
struct cmdu_buff *cmdu_alloc_frame(int size);

/**
 * Allocates cmdu_buff to hold full-size ethernet frame of 1500 bytes
 *
 * @return newly allocated cmdu_buff on success, or NULL if failed.
 *
 * This function is useful to allocate CMDUs for transmit when size of the
 * payload i.e. TLV data is not known a-priori.
 */
struct cmdu_buff *cmdu_alloc_default(void);

/**
 * Allocates full-sized cmdu_buff without CMDU header info
 *
 * @return newly allocated cmdu_buff on success, or NULL if failed.
 *
 * This function is useful to allocate cmdu_buff for tx/rx of LLDP frames.
 */
struct cmdu_buff *cmdu_alloc_nohdr(void);


/**
 * Convenient function to allocate cmdu_buff that can hold a full-size CMDU
 * @param[in] type  CMDU type
 * @param[in|out] mid  CMDU mid if nonzero, or valid mid returned
 *
 * @return newly allocated cmdu_buff on success, or NULL if failed.
 *
 * If mid passed is 0, then the next valid mid gets assigned to the newly
 * allocated cmdu.
 */
struct cmdu_buff *cmdu_alloc_simple(uint16_t type, uint16_t *mid);


/**
 * Prepare cmdu_buff from a received CMDU whose fields are known
 * @param[in] type  CMDU type
 * @param[in|out] mid  CMDU mid if nonzero, or valid mid returned
 * @param[in] ifname  interface name through which the CMDU is received
 * @param[in] origin  macaddress of the source that sent the CMDU
 * @param[in] tlvs  flattened TLV data bytes
 * @param[in] tlvslen  length of the TLV data bytes
 *
 * @return newly allocated cmdu_buff on success, or NULL if failed.
 */
struct cmdu_buff *cmdu_alloc_custom(uint16_t type, uint16_t *mid,
				    char *ifname, uint8_t *origin,
				    uint8_t *tlvs, uint32_t tlvslen);


struct cmdu_buff *cmdu_realloc(struct cmdu_buff *c, size_t size);


/** Free CMDU allocated through cmdu_alloc*() functions */
void cmdu_free(struct cmdu_buff *c);

void cmdu_set_type(struct cmdu_buff *c, uint16_t type);
uint16_t cmdu_get_type(struct cmdu_buff *c);
void cmdu_set_mid(struct cmdu_buff *c, uint16_t mid);
uint16_t cmdu_get_mid(struct cmdu_buff *c);
void cmdu_set_fid(struct cmdu_buff *c, uint8_t fid);
uint8_t cmdu_get_fid(struct cmdu_buff *c);
uint8_t *cmdu_get_origin(struct cmdu_buff *c);

/** Full size of a CMDU frame including the CMDU header */
int cmdu_size(struct cmdu_buff *c);

/** Get a valid CMDU 'mid' that can be used next */
uint16_t cmdu_get_next_mid(void);

int cmdu_midgen_init(void);
void cmdu_midgen_exit(void);

/** Helper function to get expected response CMDU for a request CMDU */
uint16_t cmdu_expect_response(uint16_t req_type);

/** Helper function to check if a CMDU is of relay multicast type */
int cmdu_should_relay(uint16_t type);

/** Function to check if a CMDU type is valid */
int is_cmdu_type_valid(uint16_t type);

/** Function to check if a CMDU is of response type */
int is_cmdu_type_response(uint16_t type);

/** Function to check if a CMDU type must include atleast one TLV */
int is_cmdu_tlv_required(uint16_t type);

/** Parsing status of received CMDU */
enum CMDU_STATUS {
	CMDU_STATUS_OK,
	CMDU_STATUS_ERR_TLV_MALFORMED,
	CMDU_STATUS_ERR_TLV_NUM_LESS,	/* mandatory tlv(s) absent */
	CMDU_STATUS_ERR_TLV_NUM_MORE,
	CMDU_STATUS_ERR_TLV_NO_EOM,
	CMDU_STATUS_ERR_TLV_RESIDUE_DATA,
	CMDU_STATUS_ERR_TLV_LEN_INSUFFICIENT,
	CMDU_STATUS_ERR_TLV_LEN_OVERFLOW,
	CMDU_STATUS_ERR_CMDU_MALFORMED,
	CMDU_STATUS_ERR_MISC,

	IEEE1905_ERROR_MAXNUM,
	IEEE1905_ERROR_LAST = IEEE1905_ERROR_MAXNUM - 1,
};


extern int *ieee1905_get_errval(void);
#define ieee1905_error	(*ieee1905_get_errval())

const char *ieee1905_strerror(int err);

/* Maximum number of tlvs of a single type allowed per cmdu */
#define TLV_MAXNUM	128

/** Parse a CMDU to get list of the TLVs present in it
 *
 * @param[in] c  cmdu_buff for parsing
 * @param[in|out] tv  array of TLVs to hold the returned TLVs
 * @param[in] policy  policy for TLV parsing
 * @param[in] policy_len  length of the policy
 *
 * @return 0 on success, else non-zero.
 *
 * The TLVs are returned in the passed 'tv' array.
 */
int cmdu_parse_tlvs(struct cmdu_buff *c, struct tlv *tv[][TLV_MAXNUM],
		    struct tlv_policy *policy, int policy_len);


/** Parse a CMDU to get list of the TLVs of a single type
 *
 * @param[in] c  cmdu_buff for parsing
 * @param[in|out] tv  array of TLVs to hold the returned TLVs
 * @param[in] policy  policy for TLV parsing
 * @param[in|out] *num  number of TLVs
 *
 * @return 0 on success, else non-zero.
 *
 * This function can be used when number of TLVs of the same type is present
 * more then 16 times; f.e. CMDUs containing TLVs for scanresults.
 * The TLVs are returned in the passed 'tv' array. And, *num is updated with
 * the actual number of TLVs present.
 */
int cmdu_parse_tlv_single(struct cmdu_buff *c, struct tlv *tv[],
			  struct tlv_policy *policy, int *num);


/** Copy append flattended TLVs data buffer into a CMDU */
int cmdu_copy_tlvs_linear(struct cmdu_buff *c, uint8_t *tlvs, uint32_t tlvslen);

/** Copy append un-flattened TLVs into a CMDU */
int cmdu_copy_tlvs(struct cmdu_buff *c, struct tlv *tv[], int tv_arrsize);

/** Create a clone or duplicate of a CMDU */
struct cmdu_buff *cmdu_clone(struct cmdu_buff *frm);

/** Function to reserve space at the tail of a CMDU buffer */
struct tlv *cmdu_reserve_tlv(struct cmdu_buff *c, uint16_t tlv_datalen);

/** Function puts a TLV within the CMDU buffer at the reserved area */
int cmdu_put_tlv(struct cmdu_buff *c, struct tlv *t);

/** Copy data at the tail of a CMDU */
int cmdu_put(struct cmdu_buff *c, uint8_t *bytes, int len);

/** Append End-Of-Message TLV to a CMDU */
int cmdu_put_eom(struct cmdu_buff *c);

/** Remove End-Of-Message TLV from a CMDU */
int cmdu_pull_eom(struct cmdu_buff *c);

struct tlv *cmdu_extract_tlv(struct cmdu_buff *c, uint8_t tlv_type);
struct tlv *cmdu_peek_tlv(struct cmdu_buff *c, uint8_t tlv_type);

#if 0
int cmdu_reserve(struct cmdu_buff *c, size_t s);
int cmdu_expand(struct cmdu_buff *c, size_t newsize);
#endif


/** Helper function to stringify a CMDU type */
const char *cmdu_type2str(uint16_t type);

/** Iterate through TLVs within a CMDU */
#define cmdu_for_each_tlv(pos, tlvs, rem)	\
	for (pos = (struct tlv *) tlvs;		\
	     tlv_ok(pos, rem);			\
	     pos = tlv_next(pos, &rem))



#endif /* CMDU_H */
