/**
 * @file
 * @brief API for encoding/decoding of BACnet/SC BVLC messages
 * @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
 * @date May 2022
 * @copyright SPDX-License-Identifier: MIT
 */
#ifndef BACNET_DATALINK_BSC_BVLC_SC_H
#define BACNET_DATALINK_BSC_BVLC_SC_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"

#ifndef BVLC_SC_NPDU_SIZE_CONF
#define BVLC_SC_NPDU_SIZE 1440
#else
#define BVLC_SC_NPDU_SIZE BVLC_SC_NPDU_SIZE_CONF
#endif

#define BVLC_SC_NPDU_MAX_SIZE \
    61327 /* Table 6-1. NPDU Lengths of BACnet Data Link Layers */
#define BVLC_SC_VMAC_SIZE 6
#define BVLC_SC_UUID_SIZE 16
#define BSC_PRE (2 * BVLC_SC_VMAC_SIZE)

#if !defined(USER_DEFINED_BVLC_SC_HEADER_OPTION_MAX)
#define BVLC_SC_HEADER_OPTION_MAX                                       \
    4 /* though BACNet standard does not limit number of option headers \
     the implementation defines max value */
#else
#define BVLC_SC_HEADER_OPTION_MAX USER_DEFINED_BVLC_SC_HEADER_OPTION_MAX
#endif

#if BVLC_SC_NPDU_SIZE > BVLC_SC_NPDU_MAX_SIZE
#error \
    "Maximum NPDU Length on BACNet/SC Data Link must be <= BVLC_SC_NPDU_MAX_SIZE"
#endif

/*
 * BACnet/SC BVLC Messages (functions) (AB.2 BACnet/SC Virtual Link Layer
 * Messages)
 */

typedef enum BVLC_SC_Message_Type {
    BVLC_SC_RESULT = 0x00,
    BVLC_SC_ENCAPSULATED_NPDU = 0x01,
    BVLC_SC_ADDRESS_RESOLUTION = 0x02,
    BVLC_SC_ADDRESS_RESOLUTION_ACK = 0x03,
    BVLC_SC_ADVERTISIMENT = 0x04,
    BVLC_SC_ADVERTISIMENT_SOLICITATION = 0x05,
    BVLC_SC_CONNECT_REQUEST = 0x06,
    BVLC_SC_CONNECT_ACCEPT = 0x07,
    BVLC_SC_DISCONNECT_REQUEST = 0x08,
    BVLC_SC_DISCONNECT_ACK = 0x09,
    BVLC_SC_HEARTBEAT_REQUEST = 0x0a,
    BVLC_SC_HEARTBEAT_ACK = 0x0b,
    BVLC_SC_PROPRIETARY_MESSAGE = 0x0c
} BVLC_SC_MESSAGE_TYPE;

/*
 * AB.2.2 Control Flags
 */

#define BVLC_SC_CONTROL_DATA_OPTIONS (1 << 0)
#define BVLC_SC_CONTROL_DEST_OPTIONS (1 << 1)
#define BVLC_SC_CONTROL_DEST_VADDR (1 << 2)
#define BVLC_SC_CONTROL_ORIG_VADDR (1 << 3)

/*
 * AB.2.3 Header Options
 */

#define BVLC_SC_HEADER_DATA (1 << 5)
#define BVLC_SC_HEADER_MUST_UNDERSTAND (1 << 6)
#define BVLC_SC_HEADER_MORE (1 << 7)
#define BVLC_SC_HEADER_OPTION_TYPE_MASK (0x1F)

/**
 * BACnet SC VMAC Address
 *
 * B.1.5.2 VMAC Addressing of Nodes
 * For the BVLC message exchange, BACnet/SC nodes are identified by their
 * 6-octet virtual MAC address as defined in Clause H.7.3.
 * For broadcast BVLC messages that need to reach all nodes of the BACnet/SC
 * network, the destination VMAC address shall be the non-EUI-48 value
 * X'FFFFFFFFFFFF', referred to as the Local Broadcast VMAC address.
 * The reserved EUI-48 value X'000000000000' is not used by this data link
 * and therefore can be used internally to indicate that a VMAC is unknown
 * or uninitialized.
 * @{
 */

typedef struct BACnet_SC_VMAC_Address {
    uint8_t address[BVLC_SC_VMAC_SIZE];
} BACNET_SC_VMAC_ADDRESS;
/** @} */

/**
 * BACnet SC UUID
 * AB.1.5.3 Device UUID
 * Every BACnet device that supports one or more BACnet/SC network ports shall
 * have a Universally Unique ID (UUID) as defined in RFC 4122. This UUID
 * identifies the device regardless of its current VMAC address or device
 * instance number and is referred to as the device UUID.
 * This device UUID shall be generated before first deployment of the device in
 * an installation, shall be persistently stored across device restarts, and
 * shall not change over the entire lifetime of a device.
 * If a device is replaced in an installation, the new device is not required
 * to reuse the UUID of the replaced device. For BACnet/SC, it is assumed
 * that existing connections to the device being replaced are all terminated
 * before the new device comes into operation.
 * @{
 */

typedef struct BACnet_SC_Uuid {
    uint8_t uuid[BVLC_SC_UUID_SIZE];
} BACNET_SC_UUID;
/** @} */

/*
 * AB.2.3.1 Secure Path Header Option
 */
typedef enum BVLC_SC_Option_Type {
    BVLC_SC_OPTION_TYPE_SECURE_PATH = 1,
    BVLC_SC_OPTION_TYPE_PROPRIETARY = 31
} BVLC_SC_OPTION_TYPE;

typedef enum BVLC_SC_Direct_Connection_Support {
    BVLC_SC_DIRECT_CONNECTION_ACCEPT_UNSUPPORTED = 0,
    BVLC_SC_DIRECT_CONNECTION_ACCEPT_SUPPORTED = 1,
    BVLC_SC_DIRECT_CONNECTION_SUPPORT_MAX = 1
} BVLC_SC_DIRECT_CONNECTION_SUPPORT;

typedef struct BVLC_SC_Decoded_Hdr {
    uint8_t bvlc_function;
    uint16_t message_id;
    BACNET_SC_VMAC_ADDRESS *origin;
    BACNET_SC_VMAC_ADDRESS *dest;
    uint8_t *dest_options; /* pointer to packed dest options list in message */
    size_t dest_options_len;
    size_t dest_options_num; /* number of filled items in dest_options */
    uint8_t *data_options; /* pointer to packed data options list in message */
    size_t data_options_len;
    size_t data_options_num; /* number of filled items in data_options */
    uint8_t *payload; /* packed payload, points to data in message */
    size_t payload_len;
} BVLC_SC_DECODED_HDR;

typedef struct BVLC_SC_Decoded_Result {
    uint8_t bvlc_function;
    uint8_t result;
    uint8_t error_header_marker;
    uint16_t error_class;
    uint16_t error_code;
    uint8_t *utf8_details_string; /* NOTE!: this is utf 8 string without
                                     trailing zero */
    size_t utf8_details_string_len;
} BVLC_SC_DECODED_RESULT;

typedef struct BVLC_SC_Decoded_Address_Resolution_Ack {
    uint8_t *utf8_websocket_uri_string; /* NOTE!: this is utf 8 string without
                                           trailing zero */
    size_t utf8_websocket_uri_string_len;
} BVLC_SC_DECODED_ADDRESS_RESOLUTION_ACK;

typedef struct BVLC_SC_Decoded_Ecapsulated_NPDU {
    uint8_t *npdu;
    size_t npdu_len;
} BVLC_SC_DECODED_ENCAPSULATED_NPDU;

typedef struct BVLC_SC_Decoded_Advertisiment {
    BACNET_SC_HUB_CONNECTOR_STATE hub_status;
    BVLC_SC_DIRECT_CONNECTION_SUPPORT support;
    uint16_t max_bvlc_len;
    uint16_t max_npdu_len;
} BVLC_SC_DECODED_ADVERTISIMENT;

typedef struct BVLC_SC_Decoded_Connect_Request {
    BACNET_SC_VMAC_ADDRESS *vmac;
    BACNET_SC_UUID *uuid;
    uint16_t max_bvlc_len;
    uint16_t max_npdu_len;
} BVLC_SC_DECODED_CONNECT_REQUEST;

typedef struct BVLC_SC_Decoded_Connect_Accept {
    BACNET_SC_VMAC_ADDRESS *vmac;
    BACNET_SC_UUID *uuid;
    uint16_t max_bvlc_len;
    uint16_t max_npdu_len;
} BVLC_SC_DECODED_CONNECT_ACCEPT;

typedef struct BVLC_SC_Decoded_Proprietary {
    uint16_t vendor_id;
    uint8_t function;
    uint8_t *data;
    size_t data_len;
} BVLC_SC_DECODED_PROPRIETARY;

typedef union BVLC_SC_Decoded_Data {
    BVLC_SC_DECODED_RESULT result;
    BVLC_SC_DECODED_ENCAPSULATED_NPDU encapsulated_npdu;
    BVLC_SC_DECODED_ADDRESS_RESOLUTION_ACK address_resolution_ack;
    BVLC_SC_DECODED_ADVERTISIMENT advertisiment;
    BVLC_SC_DECODED_CONNECT_REQUEST connect_request;
    BVLC_SC_DECODED_CONNECT_ACCEPT connect_accept;
    BVLC_SC_DECODED_PROPRIETARY proprietary;
} BVLC_SC_DECODED_DATA;

typedef struct BVLC_SC_Decoded_Hdr_Proprietary_Option {
    uint16_t vendor_id;
    uint8_t option_type;
    uint8_t *data;
    size_t data_len;
} BVLC_SC_DECODED_HDR_PROPRIETARY_OPTION;

typedef union BVLC_SC_Decoded_Specific_Option_Data {
    BVLC_SC_DECODED_HDR_PROPRIETARY_OPTION proprietary;
} BVLC_SC_DECODED_SPECIFIC_OPTION_DATA;

typedef struct BVLC_SC_Decoded_Hdr_Option {
    uint8_t packed_header_marker;
    BVLC_SC_OPTION_TYPE type;
    bool must_understand;
    BVLC_SC_DECODED_SPECIFIC_OPTION_DATA specific;
} BVLC_SC_DECODED_HDR_OPTION;

typedef struct BVLC_SC_Decoded_Message {
    BVLC_SC_DECODED_HDR hdr;
    BVLC_SC_DECODED_HDR_OPTION data_options[BVLC_SC_HEADER_OPTION_MAX];
    BVLC_SC_DECODED_HDR_OPTION dest_options[BVLC_SC_HEADER_OPTION_MAX];
    BVLC_SC_DECODED_DATA payload;
} BVLC_SC_DECODED_MESSAGE;

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

BACNET_STACK_EXPORT
size_t bvlc_sc_add_option_to_destination_options(
    uint8_t *out_pdu,
    size_t out_pdu_size,
    uint8_t *pdu,
    size_t pdu_size,
    uint8_t *sc_option,
    size_t sc_option_len);

BACNET_STACK_EXPORT
size_t bvlc_sc_add_option_to_data_options(
    uint8_t *out_pdu,
    size_t out_pdu_size,
    uint8_t *pdu,
    size_t pdu_size,
    uint8_t *sc_option,
    size_t sc_option_len);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_proprietary_option(
    uint8_t *pdu,
    size_t pdu_size,
    bool must_understand,
    uint16_t vendor_id,
    uint8_t proprietary_option_type,
    uint8_t *proprietary_data,
    size_t proprietary_data_len);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_secure_path_option(
    uint8_t *pdu, size_t pdu_size, bool must_understand);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_result(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest,
    uint8_t bvlc_function,
    uint8_t result_code,
    uint8_t *error_header_marker,
    uint16_t *error_class,
    uint16_t *error_code,
    const char *utf8_details_string);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_encapsulated_npdu(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest,
    uint8_t *npdu,
    size_t npdu_size);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_address_resolution(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_address_resolution_ack(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest,
    uint8_t *web_socket_uris,
    size_t web_socket_uris_len);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_advertisiment(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest,
    BACNET_SC_HUB_CONNECTOR_STATE hub_status,
    BVLC_SC_DIRECT_CONNECTION_SUPPORT support,
    uint16_t max_bvlc_len,
    uint16_t max_npdu_size);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_advertisiment_solicitation(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_connect_request(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *local_vmac,
    BACNET_SC_UUID *local_uuid,
    uint16_t max_bvlc_len,
    uint16_t max_npdu_size);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_connect_accept(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *local_vmac,
    BACNET_SC_UUID *local_uuid,
    uint16_t max_bvlc_len,
    uint16_t max_npdu_len);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_disconnect_request(
    uint8_t *pdu, size_t pdu_len, uint16_t message_id);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_disconnect_ack(
    uint8_t *pdu, size_t pdu_len, uint16_t message_id);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_heartbeat_request(
    uint8_t *out_buf, size_t out_buf_len, uint16_t message_id);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_heartbeat_ack(
    uint8_t *out_buf, size_t out_buf_len, uint16_t message_id);

BACNET_STACK_EXPORT
size_t bvlc_sc_encode_proprietary_message(
    uint8_t *pdu,
    size_t pdu_len,
    uint16_t message_id,
    BACNET_SC_VMAC_ADDRESS *origin,
    BACNET_SC_VMAC_ADDRESS *dest,
    uint16_t vendor_id,
    uint8_t proprietary_function,
    uint8_t *proprietary_data,
    size_t proprietary_data_len);

BACNET_STACK_EXPORT
bool bvlc_sc_decode_message(
    uint8_t *buf,
    size_t buf_len,
    BVLC_SC_DECODED_MESSAGE *message,
    uint16_t *error_code,
    uint16_t *error_class,
    const char **err_desc);

BACNET_STACK_EXPORT
void bvlc_sc_remove_dest_set_orig(
    uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig);

BACNET_STACK_EXPORT
size_t
bvlc_sc_set_orig(uint8_t **ppdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig);

BACNET_STACK_EXPORT
bool bvlc_sc_is_vmac_broadcast(BACNET_SC_VMAC_ADDRESS *vmac);

BACNET_STACK_EXPORT
bool bvlc_sc_need_send_bvlc_result(BVLC_SC_DECODED_MESSAGE *dm);

BACNET_STACK_EXPORT
bool bvlc_sc_pdu_has_dest_broadcast(uint8_t *pdu, size_t pdu_len);

BACNET_STACK_EXPORT
bool bvlc_sc_pdu_has_no_dest(uint8_t *pdu, size_t pdu_len);

BACNET_STACK_EXPORT
bool bvlc_sc_pdu_get_dest(
    uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *vmac);

BACNET_STACK_EXPORT
size_t bvlc_sc_remove_orig_and_dest(uint8_t **ppdu, size_t pdu_len);

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
