/** * @file * @brief API for encoding/decoding of BACnet/SC BVLC messages * @author Kirill Neznamov * @date May 2022 * SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 */ #include "bacnet/datalink/bsc/bvlc-sc.h" typedef enum BACNet_Option_Validation_Type { BACNET_USER_OPTION_VALIDATION, BACNET_PDU_DEST_OPTION_VALIDATION, BACNET_PDU_DATA_OPTION_VALIDATION } BACNET_OPTION_VALIDATION_TYPE; static const char *s_message_is_incompleted = "header options is truncated"; static const char *s_invalid_header_option_type = "header option type must be 'Secure Path' or 'Proprietary Header'"; static const char *s_invalid_header_1 = "'Secure Path' header option can be added only to data options in bvlc " "message"; static const char *s_invalid_header_2 = "'Secure Path' header option must not have header data"; static const char *s_invalid_header_3 = "'Proprietary Header' option must have header data"; static const char *s_result_incomplete = "BVLC-Result message has incomplete payload"; static const char *s_result_incorrect_bvlc_function = "parameter 'Result For BVLC Function' is out of range"; static const char *s_result_incorrect_result_code = "parameter 'Result Code' must be 0x00 (ACK) or 0x01(NAK)"; static const char *s_result_inconsistent = "BVLC-Result message has data inconsistency in payload"; static const char *s_result_unexpected_data = "BVLC-Result message is longer than expected"; static const char *s_advertisiment_incomplete = "advertisiment message has incomplete payload"; static const char *s_advertisiment_unexpected = "advertisiment message is longer than expected"; static const char *s_advertisiment_param1_error = "parameter 'Hub Connection Status' in advertisiment message must be in " "range [0, 2]"; static const char *s_advertisiment_param2_error = "parameter 'Accept Direct Connections'in advertisiment message must be in " "range [0, 1]"; static const char *s_connect_request_incomplete = "connect-request message has incomplete payload"; static const char *s_connect_request_unexpected = "connect-request message is longer than expected"; static const char *s_connect_accept_incomplete = "connect-accept message has incomplete payload"; static const char *s_connect_accept_unexpected = "connect-accept is longer than expected"; static const char *s_proprietary_incomplete = "proprietary message has incomplete payload"; static const char *s_hdr_incomplete1 = "message is incomplete, 'Originating Virtual Address' field is truncated"; static const char *s_hdr_incomplete2 = "message is incomplete, 'Destination Virtual Address' field is truncated"; static const char *s_unknown_bvlc_function = "unknown value of 'BVLC Function' field in message"; static const char *s_dest_options_list_too_long = "message contains more than #BVLC_SC_HEADER_OPTION_MAX options in destion " "options list"; static const char *s_data_options_list_too_long = "message contains more than #BVLC_SC_HEADER_OPTION_MAX options in data " "options list"; static const char *s_result_unexpected_data_options = "BVLC-Result message must not have data options"; static const char *s_result_payload_expected = "BVLC-Result message must have payload"; static const char *s_encapsulated_npdu_payload_expected = "encapsulated-npdu message must have payload"; static const char *s_address_resolution_data_options = "address-resolution message must not have data options"; static const char *s_address_resolution_unexpected = "address-resolution message is longer than expected"; static const char *s_address_resolution_ack_data_options = "address-resolutio-ack message must not have data options"; static const char *s_advertisiment_data_options = "advertisiment message must not have data options"; static const char *s_advertisiment_payload_expected = "advertisiment message must have payload"; static const char *s_advertisiment_solicitation_data_options = "advertisiment solicitation message must not have data options"; static const char *s_advertisiment_solicitation_payload_expected = "advertisiment solicitation message must have payload"; static const char *s_origin_unexpected = "'Originating Virtual Address' field must be absent in message"; static const char *s_dest_unexpected = "'Destination Virtual Address' field must be absent in message"; static const char *s_data_option_unexpected = "message must not have data options"; static const char *s_message_too_long = "message is longer than expected"; static const char *s_absent_payload = "payload is absent in the message"; static const char *s_proprietary_data_options = "proprietary message must not have data options"; static const char *s_proprietary_payload = "proprietary message must have payload"; /** * @brief Validate BVLC-SC header options * @param validation_type - type of validation * @param option_headers - pointer to the option headers * @param option_headers_max_len - maximum length of the option headers * @param out_option_headers_real_length - pointer to the real length of the * option headers * @param out_option_header_num - pointer to the number of the option headers * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param error_desc_string - pointer to the error description string * @return true if the option headers are valid, false otherwise */ static bool bvlc_sc_validate_options_headers( BACNET_OPTION_VALIDATION_TYPE validation_type, uint8_t *option_headers, size_t option_headers_max_len, size_t *out_option_headers_real_length, size_t *out_option_header_num, uint16_t *error_code, uint16_t *error_class, const char **error_desc_string) { int options_len = 0; uint8_t flags = 0; uint8_t option; uint16_t hdr_len; if (!option_headers_max_len || !out_option_headers_real_length) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_message_is_incompleted; return false; } if (out_option_header_num) { *out_option_header_num = 0; } while (options_len < option_headers_max_len) { flags = option_headers[options_len]; option = flags & BVLC_SC_HEADER_OPTION_TYPE_MASK; if (option != BVLC_SC_OPTION_TYPE_SECURE_PATH && option != BVLC_SC_OPTION_TYPE_PROPRIETARY) { *error_code = ERROR_CODE_HEADER_ENCODING_ERROR; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_invalid_header_option_type; return false; } if (option == BVLC_SC_OPTION_TYPE_SECURE_PATH) { if (validation_type == BACNET_PDU_DEST_OPTION_VALIDATION) { /* According BACNet standard secure path header option can be added only to data options. (AB.2.3.1 Secure Path Header Option) */ *error_code = ERROR_CODE_HEADER_ENCODING_ERROR; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_invalid_header_1; return false; } if (flags & BVLC_SC_HEADER_DATA) { /* securepath option header does not have data header according bacnet stadard */ *error_code = ERROR_CODE_HEADER_ENCODING_ERROR; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_invalid_header_2; return false; } options_len++; } else if (option == BVLC_SC_OPTION_TYPE_PROPRIETARY) { if (!(flags & BVLC_SC_HEADER_DATA)) { /* proprietary option must have header data */ *error_code = ERROR_CODE_HEADER_ENCODING_ERROR; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_invalid_header_3; return false; } options_len++; if (options_len + 2 > option_headers_max_len) { /* not enough data to process header that means that probably message is incomplete */ *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_message_is_incompleted; return false; } memcpy(&hdr_len, &option_headers[options_len], 2); options_len += 2 /* header length */ + hdr_len /* length of data header */; } if (options_len > option_headers_max_len) { /* not enough data to process header that means that probably message is incomplete */ *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *error_desc_string = s_message_is_incompleted; return false; } if (out_option_header_num && (*out_option_header_num < USHRT_MAX)) { *out_option_header_num += 1; } if (!(flags & BVLC_SC_HEADER_MORE)) { break; } } *out_option_headers_real_length = options_len; return true; } /** * @brief Function adds header option to data option list * into provided pdu and stores the result in out_pdu. * Note that last added option will be the first option in * the list. * @param out_pdu - buffer where modified pdu will be stored in * @param out_pdu_size - size of the out_pdu * @param pdu - input pdu to which user wants to add option * @param pdu_size - size of input pdu * @param sc_option - pointer to encoded header option * @param sc_option_len - length of encoded header option * @return returns length (>0) in bytes of a pdu with added option */ static size_t bvlc_sc_add_option( bool to_data_option, uint8_t *pdu, size_t pdu_size, uint8_t *in_pdu, size_t in_pdu_len, uint8_t *sc_option, size_t sc_option_len) { size_t offs = 4; uint8_t flags = 0; size_t options_len = 0; uint8_t mask; uint16_t error_code; uint16_t error_class; BACNET_OPTION_VALIDATION_TYPE vt; const char *err_desc; if (!in_pdu_len || !in_pdu || !sc_option_len || !pdu_size || !pdu || !sc_option) { return 0; } if (pdu_size < 4 || in_pdu_len < 4) { return 0; } if (sc_option_len + in_pdu_len > USHRT_MAX) { return 0; } if (pdu_size < in_pdu_len) { return 0; } if (pdu_size < sc_option_len + in_pdu_len) { return 0; } /* ensure that sc_option does not have flag more options */ if (sc_option[0] & BVLC_SC_HEADER_MORE) { return 0; } if (!to_data_option && (sc_option[0] & BVLC_SC_HEADER_OPTION_TYPE_MASK) == BVLC_SC_OPTION_TYPE_SECURE_PATH) { /* According BACNet standard secure path header option can be added only to data options. (AB.2.3.1 Secure Path Header Option) */ return 0; } /* ensure that user wants to add valid option */ if (!bvlc_sc_validate_options_headers( BACNET_USER_OPTION_VALIDATION, sc_option, sc_option_len, &options_len, NULL, &error_code, &error_class, &err_desc)) { return 0; } flags = in_pdu[1]; if (flags & BVLC_SC_CONTROL_DEST_VADDR) { offs += BVLC_SC_VMAC_SIZE; } if (flags & BVLC_SC_CONTROL_ORIG_VADDR) { offs += BVLC_SC_VMAC_SIZE; } if (offs > in_pdu_len) { return 0; } /* insert options */ if (to_data_option) { mask = BVLC_SC_CONTROL_DATA_OPTIONS; vt = BACNET_PDU_DATA_OPTION_VALIDATION; if (flags & BVLC_SC_CONTROL_DEST_OPTIONS) { /* some options are already presented in message. Validate them at first. */ if (!bvlc_sc_validate_options_headers( BACNET_PDU_DEST_OPTION_VALIDATION, &in_pdu[offs], in_pdu_len - offs, &options_len, NULL, &error_code, &error_class, &err_desc)) { return 0; } offs += options_len; } } else { mask = BVLC_SC_CONTROL_DEST_OPTIONS; vt = BACNET_PDU_DEST_OPTION_VALIDATION; } if ((flags & mask)) { /* some options are already presented in message. Validate them at first. */ if (!bvlc_sc_validate_options_headers( vt, &in_pdu[offs], in_pdu_len - offs, &options_len, NULL, &error_code, &error_class, &err_desc)) { return 0; } } if (pdu == in_pdu) { /* user would like to use the same buffer where in_pdu is already stored. So we need to expand it for sc_option_len */ memmove(&pdu[offs + sc_option_len], &pdu[offs], in_pdu_len - offs); memcpy(&pdu[offs], sc_option, sc_option_len); } else { /* user would like to use a new buffer for in_pdu with option.*/ memcpy(pdu, in_pdu, offs); memcpy(&pdu[offs], sc_option, sc_option_len); if ((flags & mask)) { pdu[offs] |= BVLC_SC_HEADER_MORE; } memcpy(&pdu[offs + sc_option_len], &in_pdu[offs], in_pdu_len - offs); } if ((flags & mask)) { pdu[offs] |= BVLC_SC_HEADER_MORE; } else { pdu[1] |= mask; } return in_pdu_len + sc_option_len; } /** * @brief Function adds header option to destination option list into provided pdu and stores the result in out_pdu. Note that last added option will be the first option in the list. * @param out_pdu - buffer where modified pdu will be stored in * @param out_pdu_size - size of the out_pdu * @param pdu - input pdu to which user wants to add option * @param pdu_size - size of input pdu * @param sc_option - pointer to encoded header option * @param sc_option_len - length of encoded header option * @return returns length (>0) in bytes of a pdu with added option or 0 in a case of error (validation of pdu or sc_option fails) */ 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) { return bvlc_sc_add_option( false, out_pdu, out_pdu_size, pdu, pdu_size, sc_option, sc_option_len); } /** * @brief Function adds header option to data option list into provided pdu and stores the result in out_pdu. Note that last added option will be the first option in the list. * @param out_pdu - buffer where modified pdu will be stored in * @param out_pdu_size - size of the out_pdu * @param pdu - input pdu to which user wants to add option * @param pdu_size - size of input pdu * @param sc_option - pointer to encoded header option * @param sc_option_len - length of encoded header option * @return returns length (>0) in bytes of a pdu with added option or 0 in a case of error (validation of pdu or sc_option fails) */ 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) { return bvlc_sc_add_option( true, out_pdu, out_pdu_size, pdu, pdu_size, sc_option, sc_option_len); } /** * @brief Function encodes proprietary header option in correspondence * of BACNet standard AB.2.3.2 Proprietary Header Options. * Any proprietary header option shall consist of the * following fields: * * Header Marker 1-octet 'More Options' = 0 or 1, * 'Must Understand' = 0 or 1, * 'Header Data Flag' = 1, * 'Header Option Type' = 31 * Header Length 2-octets Length of 'Header Data' field, * in octets. * Header Data 3-N octets Required to include: * Vendor Identifier 2-octets Vendor Identifier, with most * significant octet first, of the * organization defining this option. * Proprietary Option Type 1-octet An indication of the proprietary * header option type. * Proprietary Header data Variable A proprietary string of octets. * Can be zero length. * * @param pdu - buffer to store encoded option * @param pdu_size - size of pdu * @param must_understand - value for 'Must Understand' flag. * @param pdu_size - size of input pdu * @param vendor_id - vendor identifier * @param proprietary_option_type - proprietary header option type. * @param proprietary_data - buffer with proprietary data * @param proprietary_data_len - size of buffer with proprietary data * @return value (>0) in bytes of a encoded options or 0 in a case of * an error */ 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) { size_t total_len = sizeof(vendor_id) + proprietary_data_len + sizeof(uint8_t); if (proprietary_data_len > BVLC_SC_NPDU_SIZE - 3 /* header marker len + header length len) */ - sizeof(vendor_id)) { return 0; } if (pdu_size < total_len + 3 /* header marker len + header data len) */) { return 0; } /* reset More Options, Must Understand and Header Data flags they will be updated as a result of call bvlc_sc_add_sc_option() */ pdu[0] = BVLC_SC_OPTION_TYPE_PROPRIETARY; if (must_understand) { pdu[0] |= BVLC_SC_HEADER_MUST_UNDERSTAND; } pdu[0] |= BVLC_SC_HEADER_DATA; memcpy(&pdu[1], &total_len, sizeof(total_len)); memcpy(&pdu[3], &vendor_id, sizeof(vendor_id)); memcpy(&pdu[5], &proprietary_option_type, sizeof(proprietary_option_type)); memcpy(&pdu[6], proprietary_data, proprietary_data_len); return total_len + 3; } /** * @brief Function encodes security path header option in correspondence * of BACNet standard AB.2.3.1 Secure Path Header Option. * Any proprietary header option shall consist of the * following fields: * * Header Marker 1-octet 'Last Option' = 0 or 1, * 'Must Understand' = 0 or 1, * 'Header Data Flag' = 1, * 'Header Option Type' = 31 * * @param pdu - buffer to store encoded option * @param pdu_size - size of pdu * @param must_understand - value for 'Must Understand' flag. * @return value (>0) in bytes of a encoded options or 0 in a case of * an error */ size_t bvlc_sc_encode_secure_path_option( uint8_t *pdu, size_t pdu_size, bool must_understand) { if (pdu_size < 1) { return 0; } pdu[0] = BVLC_SC_OPTION_TYPE_SECURE_PATH; if (must_understand) { pdu[0] |= BVLC_SC_HEADER_MUST_UNDERSTAND; } return 1; } /** * @brief Decodes BVLC header options marker. Function assumes to be called * iteratively until end of option list will be reached. User must * call this function on previously validated options list only * because sanity checks are omitted.That valided option list can * be get by some bvlc_sc_encode_ function calls. * * @param in_option_list - buffer containing list of header options.It must point to head list item to be decoded. * @param out_opt_type - pointer to store decoded option type, must not be NULL * @param out_must_understand - pointer to store decoded 'must understand' * flag from options marker, must not be NULL. * @param out_next_option - pointer to next option list item to be decoded, * can be NULL if no any option items left. * * @return 0 in a case if decoding failed, otherwise returns * length value of decoded option in bytes. * * Typically code for parsing of option list looks like following: * * uint8_t *current = in_options_list; * int option_len = 0; * while(current) { * bvlc_sc_decode_option_hdr(current, * &type, ...., ¤t); * } * */ static void bvlc_sc_decode_option_hdr( uint8_t *in_options_list, BVLC_SC_OPTION_TYPE *out_opt_type, bool *out_must_understand, uint8_t **out_next_option) { *out_next_option = NULL; *out_opt_type = (BVLC_SC_OPTION_TYPE)(in_options_list[0] & BVLC_SC_HEADER_OPTION_TYPE_MASK); *out_must_understand = (in_options_list[0] & BVLC_SC_HEADER_MUST_UNDERSTAND) ? true : false; if (*out_opt_type == BVLC_SC_OPTION_TYPE_SECURE_PATH) { if (in_options_list[0] & BVLC_SC_HEADER_MORE) { *out_next_option = in_options_list + 1; } } else if (*out_opt_type == BVLC_SC_OPTION_TYPE_PROPRIETARY) { uint16_t hdr_len; memcpy(&hdr_len, &in_options_list[1], sizeof(hdr_len)); hdr_len += sizeof(hdr_len) + 1; if (in_options_list[0] & BVLC_SC_HEADER_MORE) { *out_next_option = in_options_list + hdr_len; } } } /** * @brief Decodes BVLC header proprietary option * Function assumes to be called iteratively until end of option list * will be reached. User must call this function on previously validated * options list only because sanity checks are omitted. That valided * option list can be get by some bvlc_sc_encode_ function calls. * @param in_options_list - buffer containing list of header options.It must * point to head list item to be decoded. * @param out_vendor_id - pointer to store vendor id, must not be NULL * @param out_proprietary_option_type - pointer to store proprietary option * type, must not be NULL * @param out_proprietary_data - pointer to store proprietary data, can be NULL * @param out_proprietary_data_len - pointer to store length of proprietary * data, can be NULL * @return 0 in a case if decoding failed, otherwise return */ static void bvlc_sc_decode_proprietary_option( uint8_t *in_options_list, uint16_t *out_vendor_id, uint8_t *out_proprietary_option_type, uint8_t **out_proprietary_data, size_t *out_proprietary_data_len) { uint16_t hdr_len; *out_proprietary_data = NULL; *out_proprietary_data_len = 0; memcpy(&hdr_len, &in_options_list[1], sizeof(hdr_len)); memcpy(out_vendor_id, &in_options_list[3], sizeof(uint16_t)); *out_proprietary_option_type = in_options_list[5]; if (hdr_len > 3) { *out_proprietary_data = &in_options_list[6]; *out_proprietary_data_len = hdr_len - 3; } } /** * @brief encode BVLC-SC header * * BACNet standard AB.2.2 BVLC-SC Header Format: * BVLC Function 1-octet BVLC function code * Control Flags 1-octet Control flags * Message ID 2-octets Message identifier * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param bvlc_function - BVLC function code, check BVLC_SC_MESSAGE_TYPE enum. * @param message_id - The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @return number of bytes encoded, in a case of error returns 0. */ static size_t bvlc_sc_encode_common( uint8_t *pdu, size_t pdu_len, uint8_t bvlc_function, uint16_t message_id, BACNET_SC_VMAC_ADDRESS *origin, BACNET_SC_VMAC_ADDRESS *dest) { size_t offs = 4; if (pdu_len < 4) { return 0; } pdu[0] = bvlc_function; pdu[1] = 0; memcpy(&pdu[2], &message_id, sizeof(message_id)); if (origin) { if (pdu_len < offs + BVLC_SC_VMAC_SIZE) { return 0; } pdu[1] |= BVLC_SC_CONTROL_ORIG_VADDR; memcpy(&pdu[offs], &origin->address[0], BVLC_SC_VMAC_SIZE); offs += BVLC_SC_VMAC_SIZE; } if (dest) { if (pdu_len < offs + BVLC_SC_VMAC_SIZE) { return 0; } pdu[1] |= BVLC_SC_CONTROL_DEST_VADDR; memcpy(&pdu[offs], &dest->address[0], BVLC_SC_VMAC_SIZE); offs += BVLC_SC_VMAC_SIZE; } return offs; } /** * @brief Function encodes the BVLC-Result message according BACNet standard * AB.2.4.1 BVLC-Result Format. * * BVLC Function 1-octet (X'00') BVLC-Result * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * Payload * Result For BVLC Function 1-octet BVLC function for which this * is a result * Result Code 1-octet(X'00') ACK: Successful completion. The * 'Error Header Marker' and all * subsequent parameters shall be * absent. * (X'01') NAK: The BVLC function failed. * The 'Error Header Marker', * the 'Error Class', the 'Error * Code', and the 'Error Details' * shall be present. * Error Header Marker 1-octet The header marker of the * (Conditional) destination option that caused * the BVLC function to fail. If * the NAK is unrelated to a header * option, this parameter shall be * X'00'. * Error Class 2-octets The 'Error Class' field of the * (Conditional) 'Error' datatype defined in * Clause 21. * Error Code 2-octets The 'Error Code' field of the * (Conditional) 'Error' datatype defined in * Clause 21. * Error Details Variable UTF-8 reason text. Can be an * (Conditional) empty string using no octets. * Note that this string is not * encoded as defined in Clause * 20.2.9, has no character set * indication octet, and no trailing * zero octets. See BVLC-Result * examples in Clause AB.2.17. * * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @param bvlc_function - BVLC function for which this is a result. * check BVLC_SC_MESSAGE_TYPE enum. * @param result_code - 0 (ACK) or 1 (NAK) * @param error_header_marker -can be NULL depending on result code. * The header marker of the destination option * that caused the BVLC function to fail * @param error_class - can be NULL depending on result code. * Check BACNET_ERROR_CLASS enum. * @param error_code - can be NULL depending on result code. * check BACNET_ERROR_CODE enum. * @param utf8_details_string - can be NULL depending on result code. * UTF-8 reason text. * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; if (bvlc_function > (uint16_t)BVLC_SC_PROPRIETARY_MESSAGE) { /* unsupported bvlc function */ return 0; } if (result_code != 0 && result_code != 1) { /* According AB.2.4.1 BVLC-Result Format result code must be 0x00 or 0x01 */ return 0; } if (result_code) { if (!error_class || !error_code) { /* According AB.2.4.1 BVLC-Result Format error_class and error_code must be presented */ return 0; } } offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_RESULT, message_id, origin, dest); if (!offs) { return 0; } if (pdu_len < offs + 2) { return 0; } pdu[offs++] = bvlc_function; pdu[offs++] = result_code; if (!result_code) { if (error_header_marker || error_class || error_code || utf8_details_string) { return 0; } return offs; } if (pdu_len < offs + 4) { return 0; } if (error_header_marker) { pdu[offs++] = *error_header_marker; } else { pdu[offs++] = 0; } memcpy(&pdu[offs], error_class, sizeof(*error_class)); offs += sizeof(*error_class); memcpy(&pdu[offs], error_code, sizeof(*error_code)); offs += sizeof(*error_code); if (utf8_details_string) { if (pdu_len < offs + strlen((const char *)utf8_details_string)) { return 0; } memcpy( &pdu[offs], utf8_details_string, strlen((const char *)utf8_details_string)); offs += strlen((const char *)utf8_details_string); } return offs; } /** * @brief Function decodes the BVLC-Result message according BACNet standard * AB.2.4.1 BVLC-Result Format. * @param payload - pointer to the decoded data * @param packed_payload - pointer to the packed data * @param packed_payload_len - size of the packed data * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param err_desc - pointer to the error description string * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_result( BVLC_SC_DECODED_DATA *payload, uint8_t *packed_payload, size_t packed_payload_len, uint16_t *error_code, uint16_t *error_class, const char **err_desc) { size_t i; if (packed_payload_len < 2) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_incomplete; return false; } memset(&payload->result, 0, sizeof(payload->result)); if (packed_payload[0] > (uint8_t)BVLC_SC_PROPRIETARY_MESSAGE) { /* unknown BVLC function */ *error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_incorrect_bvlc_function; return false; } payload->result.bvlc_function = packed_payload[0]; if (packed_payload[1] != 0 && packed_payload[1] != 1) { /* result code must be 1 or 0 */ *error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_incorrect_result_code; return false; } payload->result.result = packed_payload[1]; if (packed_payload[1] == 1) { if (packed_payload_len < 7) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_incomplete; return false; } payload->result.error_header_marker = packed_payload[2]; memcpy( &payload->result.error_class, &packed_payload[3], sizeof(payload->result.error_class)); memcpy( &payload->result.error_code, &packed_payload[5], sizeof(payload->result.error_code)); if (packed_payload_len > 7) { payload->result.utf8_details_string = &packed_payload[7]; payload->result.utf8_details_string_len = packed_payload_len - 7; for (i = 0; i < packed_payload_len - 7; i++) { if (payload->result.utf8_details_string[i] == 0) { break; } } if (i != packed_payload_len - 7) { *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_inconsistent; return false; } } } else if (packed_payload_len > 2) { /* According EA-001-4 'Clarifying BVLC-Result in BACnet/SC */ /* If a BVLC message is received that is longer than expected, */ /* a BVLC-Result NAK shall be returned if it was a unicast message, */ /* indicating an 'Error Class' of COMMUNICATION and 'Error Code' of */ /* UNEXPECTED_DATA. The message shall be discarded and not be processed. */ *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_unexpected_data; return false; } return true; } /** * @brief Function encodes the Encapsulated-NPDU message according * BACNet standard AB.2.5 Encapsulated-NPDU. * * BVLC Function 1-octet (X'01') Encapsulated-NPDU * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Optional, 0 to N header options * Payload * BACnet NPDU Variable * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @param npdu - Buffer which contains BACnet NPDU * @param npdu_size - Size of buffer which contains BACnet NPDU * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_ENCAPSULATED_NPDU, message_id, origin, dest); if (!offs) { return 0; } if (pdu_len < offs + npdu_size) { return 0; } memcpy(&pdu[offs], npdu, npdu_size); offs += npdu_size; return offs; } /** * @brief Function encodes the Address-Resolution message according BACNet * standard AB.2.6 Address-Resolution * * BVLC Function 1-octet (X'02') Address-Resolution * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_ADDRESS_RESOLUTION, message_id, origin, dest); return offs; } /** * @brief Function encodes the Address-Resolution-ACK message according * BACNet standard AB.2.7.1 Address-Resolution-ACK Format. * * BVLC Function 1-octet (X'03') Address-Resolution-ACK * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * Payload * WebSocket-URIs Variable UTF-8 string containing a list * of WebSocket URIs as of RFC3986, * separated by a single space * character (X'20'), where the * source BACnet/SC node accepts * direct connections. Can be an * empty string using zero octets. * See Clause AB.3.3. * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @param web_socket_uris - UTF-8 string containing a list of * WebSocket URIs as of RFC 3986, separated by a * single space character (X'20'), where the source * BACnet/SC node accepts direct connections. * Can be an empty string using zero octets. * @param web_socket_uris_len- length of web_socket_uris without trailing zero. * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_ADDRESS_RESOLUTION_ACK, message_id, origin, dest); if (!offs) { return 0; } if (web_socket_uris && web_socket_uris_len) { memcpy(&pdu[offs], web_socket_uris, web_socket_uris_len); offs += web_socket_uris_len; } return offs; } /** * @brief Function encodes the Advertisement message according * BACNet standard AB.2.8.1 Advertisement Format. * * BVLC Function 1-octet (X'04') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * Payload * Hub Connection Status 1-octet X'00' No hub connection. * X'01' Connected to primary hub. * X'02' Connected to failover hub. * Accept Direct Connections 1-octet X'00' The node does not support * accepting direct * connections. * X'01' The node supports accepting * direct connections. * Maximum BVLC Length 2-octet The maximum BVLC message size * that can be received and * processed by the node, in number * of octets. * Maximum NPDU Length 2-octet The maximum NPDU message size * that can be handled by the node's * network entity, in number of * octets. * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @param hub_status - hub connection status * @param support- accept direct connections * @param max_bvlc_len - the maximum BVLC message size * @param max_npdu_size - the maximum NPDU message size * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_ADVERTISIMENT, message_id, origin, dest); if (!offs) { return 0; } pdu[offs++] = (uint8_t)hub_status; pdu[offs++] = (uint8_t)support; memcpy(&pdu[offs], &max_bvlc_len, sizeof(max_bvlc_len)); offs += sizeof(max_bvlc_len); memcpy(&pdu[offs], &max_npdu_size, sizeof(max_npdu_size)); offs += sizeof(max_npdu_size); return offs; } /** * @brief Function encodes the Advertisement-Solicitation message according * BACNet standard AB.2.9.1 Advertisement-Solicitation Format. * @param payload - pointer to the decoded data * @param packed_payload - pointer to packed data * @param packed_payload_len - size of packed data * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param err_desc - pointer to the error description string * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_advertisiment( BVLC_SC_DECODED_DATA *payload, uint8_t *packed_payload, size_t packed_payload_len, uint16_t *error_code, uint16_t *error_class, const char **err_desc) { if (packed_payload_len < 6) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_incomplete; return false; } if (packed_payload_len > 6) { *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_unexpected; return false; } if (packed_payload[0] > BACNET_SC_HUB_CONNECTOR_STATE_MAX) { *error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_param1_error; return false; } if (packed_payload[1] > BVLC_SC_DIRECT_CONNECTION_SUPPORT_MAX) { *error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_param2_error; return false; } payload->advertisiment.hub_status = packed_payload[0]; payload->advertisiment.support = packed_payload[1]; memcpy( &payload->advertisiment.max_bvlc_len, &packed_payload[2], sizeof(payload->advertisiment.max_bvlc_len)); memcpy( &payload->advertisiment.max_npdu_len, &packed_payload[4], sizeof(payload->advertisiment.max_npdu_len)); return true; } /** * @brief Function encodes the Advertisement-Solicitation message according * BACNet standard AB.2.9.1 Advertisement-Solicitation Format. * * BVLC Function 1-octet (X'05') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_ADVERTISIMENT_SOLICITATION, message_id, origin, dest); return offs; } /** * @brief Function encodes the Connect-Request message according * BACNet standard AB.2.10.1 Connect-Request Format. * * BVLC Function 1-octet (X'06') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 Absent, is always for * connection peer node. * Destination Virtual Address 0 If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * Payload * VMAC Address 6-octets The VMAC address of the * requesting node. * Device UUID 16-octet The device UUID of the * requesting node. * Maximum BVLC Length 2-octet The maximum BVLC message size * that can be received and * processed by the node, in number * of octets. * Maximum NPDU Length 2-octet The maximum NPDU message size * that can be handled by the node's * network entity, in number of * octets. * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param local_vmac - VMAC address * @param local_uuid - The device UUID * @param max_bvlc_len - the maximum BVLC message size * @param max_npdu_size - the maximum NPDU message size * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; if (!local_vmac || !local_uuid) { return 0; } offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_CONNECT_REQUEST, message_id, NULL, NULL); if (!offs) { return 0; } if (pdu_len < (offs + BVLC_SC_VMAC_SIZE + BVLC_SC_UUID_SIZE + 2 * sizeof(uint16_t))) { return 0; } memcpy(&pdu[offs], local_vmac, BVLC_SC_VMAC_SIZE); offs += BVLC_SC_VMAC_SIZE; memcpy(&pdu[offs], local_uuid, BVLC_SC_UUID_SIZE); offs += BVLC_SC_UUID_SIZE; memcpy(&pdu[offs], &max_bvlc_len, sizeof(max_bvlc_len)); offs += sizeof(max_bvlc_len); memcpy(&pdu[offs], &max_npdu_size, sizeof(max_npdu_size)); offs += sizeof(max_npdu_size); return (unsigned int)offs; } /** * @brief Function decodes the Connect-Request message according * BACNet standard AB.2.10.1 Connect-Request Format. * @param payload - pointer to the decoded data * @param packed_payload - pointer to the packed data * @param packed_payload_len - size of the packed data * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param err_desc - pointer to the error description string * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_connect_request( BVLC_SC_DECODED_DATA *payload, uint8_t *packed_payload, size_t packed_payload_len, uint16_t *error_code, uint16_t *error_class, const char **err_desc) { if (packed_payload_len < 26) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_connect_request_incomplete; return false; } else if (packed_payload_len > 26) { *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_connect_request_unexpected; return false; } payload->connect_request.vmac = (BACNET_SC_VMAC_ADDRESS *)packed_payload; packed_payload += BVLC_SC_VMAC_SIZE; payload->connect_request.uuid = (BACNET_SC_UUID *)packed_payload; packed_payload += BVLC_SC_UUID_SIZE; memcpy( &payload->connect_request.max_bvlc_len, packed_payload, sizeof(payload->connect_request.max_bvlc_len)); packed_payload += sizeof(payload->connect_request.max_bvlc_len); memcpy( &payload->connect_request.max_npdu_len, packed_payload, sizeof(payload->connect_request.max_npdu_len)); return true; } /** * @brief Function encodes the Connect-Accept message according * BACNet standard AB.2.11.1 Connect-Accept Format. * * BVLC Function 1-octet (X'07') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 Absent, is always for * connection peer node. * Destination Virtual Address 0 If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * Payload * VMAC Address 6-octets The VMAC address of the * requesting node. * Device UUID 16-octet The device UUID of the * requesting node. * Maximum BVLC Length 2-octet The maximum BVLC message size * that can be received and * processed by the node, in number * of octets. * Maximum NPDU Length 2-octet The maximum NPDU message size * that can be handled by the node's * network entity, in number of * octets. * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param local_vmac - VMAC address * @param local_uuid - The device UUID * @param max_bvlc_len - the maximum BVLC message size * @param max_npdu_size - the maximum NPDU message size * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; if (!local_vmac || !local_uuid) { return 0; } offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_CONNECT_ACCEPT, message_id, NULL, NULL); if (!offs) { return 0; } if (pdu_len < (offs + BVLC_SC_VMAC_SIZE + BVLC_SC_UUID_SIZE + 2 * sizeof(uint16_t))) { return 0; } memcpy(&pdu[offs], local_vmac, BVLC_SC_VMAC_SIZE); offs += BVLC_SC_VMAC_SIZE; memcpy(&pdu[offs], local_uuid, BVLC_SC_UUID_SIZE); offs += BVLC_SC_UUID_SIZE; memcpy(&pdu[offs], &max_bvlc_len, sizeof(max_bvlc_len)); offs += sizeof(max_bvlc_len); memcpy(&pdu[offs], &max_npdu_len, sizeof(max_npdu_len)); offs += sizeof(max_npdu_len); return (unsigned int)offs; } /** * @brief Function decodes the Connect-Accept message according * BACNet standard AB.2.11.1 Connect-Accept Format. * @param payload - pointer to the decoded data * @param packed_payload - pointer to the packed data * @param packed_payload_len - size of the packed data * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param err_desc - pointer to the error description string * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_connect_accept( BVLC_SC_DECODED_DATA *payload, uint8_t *packed_payload, size_t packed_payload_len, uint16_t *error_code, uint16_t *error_class, const char **err_desc) { if (packed_payload_len < 26) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_connect_accept_incomplete; return false; } else if (packed_payload_len > 26) { *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_connect_accept_unexpected; return false; } payload->connect_accept.vmac = (BACNET_SC_VMAC_ADDRESS *)packed_payload; packed_payload += BVLC_SC_VMAC_SIZE; payload->connect_accept.uuid = (BACNET_SC_UUID *)packed_payload; packed_payload += BVLC_SC_UUID_SIZE; memcpy( &payload->connect_accept.max_bvlc_len, packed_payload, sizeof(payload->connect_accept.max_bvlc_len)); packed_payload += sizeof(payload->connect_accept.max_bvlc_len); memcpy( &payload->connect_accept.max_npdu_len, packed_payload, sizeof(payload->connect_accept.max_npdu_len)); return true; } /** * @brief Function encodes the Disconnect-Request message according * BACNet standard AB.2.12.1 Disconnect-Request Format. * * BVLC Function 1-octet (X'08') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 Absent, is always for * connection peer node. * Destination Virtual Address 0 If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @return number of bytes encoded, in a case of error returns 0. */ size_t bvlc_sc_encode_disconnect_request( uint8_t *pdu, size_t pdu_len, uint16_t message_id) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_DISCONNECT_REQUEST, message_id, NULL, NULL); return offs; } /** * @brief Function encodes the Disconnect-ACK message according * BACNet standard AB.2.13.1 Disconnect-ACK Format. * * BVLC Function 1-octet (X'09') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 Absent, is always for * connection peer node. * Destination Virtual Address 0 If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @return number of bytes encoded, in a case of error returns 0. */ size_t bvlc_sc_encode_disconnect_ack(uint8_t *pdu, size_t pdu_len, uint16_t message_id) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_DISCONNECT_ACK, message_id, NULL, NULL); return offs; } /** * @brief Function encodes the Heartbeat-Request message according * BACNet standard AB.2.14.1 Heartbeat-Request Format. * * BVLC Function 1-octet (X'0A') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 Absent, is always for * connection peer node. * Destination Virtual Address 0 If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @return number of bytes encoded, in a case of error returns 0. */ size_t bvlc_sc_encode_heartbeat_request( uint8_t *pdu, size_t pdu_len, uint16_t message_id) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_HEARTBEAT_REQUEST, message_id, NULL, NULL); return offs; } /** * @brief Function encodes the Heartbeat-ACK message according * BACNet standard AB.2.15.1 Heartbeat-ACK Format. * * BVLC Function 1-octet (X'0B') Advertisement * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 Absent, is always for * connection peer node. * Destination Virtual Address 0 If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @return number of bytes encoded, in a case of error returns 0. */ size_t bvlc_sc_encode_heartbeat_ack(uint8_t *pdu, size_t pdu_len, uint16_t message_id) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_HEARTBEAT_ACK, message_id, NULL, NULL); return offs; } /** * @brief Function encodes the Proprietary Message according BACNet standard * AB.2.16.1 Proprietary Message Format. * * BVLC Function 1-octet (X'0C') Proprietary-Message. * Control Flags 1-octet Control flags. * Message ID 2-octets The message identifier of the * message for which this message * is the result. * Originating Virtual Address 0 or 6-octets If absent, message is from * connection peer node * Destination Virtual Address 0 or 6-octets If absent, message is for * connection peer node * Destination Options Variable Optional, 0 to N header options * Data Options 0-octets Shall be absent. * Payload 3-N octets The payload shall consist of at * least the vendor identifier and * the proprietary function octet. * Vendor identifier 2-octets Vendor Identifier, with most * significant octet first, of the * organization defining this * message. * Proprietary Function 1-octet The vendor-defined function code. * * Proprietary Data Variable Optional vendor-defined payload * data. * @param pdu - A buffer to store the encoded pdu. * @param pdu_len - Size of the buffer to store the encoded pdu. * @param message_id- The message identifier * @param origin - Originating virtual address, can be NULL * @param dest - Destination virtual address, can be NULL * @param vendor_id - vendor identifier. * @param proprietary_function - The vendor-defined function code * @param proprietary_data - buffer which holds optional vendor-defined * payload data. * @param proprietary_data_len - length of proprietary_data buffer. * @return number of bytes encoded, in a case of error returns 0. */ 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) { size_t offs; offs = bvlc_sc_encode_common( pdu, pdu_len, BVLC_SC_PROPRIETARY_MESSAGE, message_id, origin, dest); if (!offs) { return 0; } if (pdu_len < (offs + sizeof(vendor_id) + sizeof(proprietary_function) + proprietary_data_len)) { return 0; } memcpy(&pdu[offs], &vendor_id, sizeof(vendor_id)); offs += sizeof(vendor_id); memcpy(&pdu[offs], &proprietary_function, sizeof(proprietary_function)); offs += sizeof(proprietary_function); memcpy(&pdu[offs], proprietary_data, proprietary_data_len); offs += proprietary_data_len; return (unsigned int)offs; } /** * @brief Function decodes the Proprietary Message according BACNet standard * AB.2.16.1 Proprietary Message Format. * @param payload - pointer to the decoded data * @param packed_payload - pointer to the packed data * @param packed_payload_len - size of the packed data * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param err_desc - pointer to the error description string * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_proprietary( BVLC_SC_DECODED_DATA *payload, uint8_t *packed_payload, size_t packed_payload_len, uint16_t *error_code, uint16_t *error_class, const char **err_desc) { if (packed_payload_len < 3) { *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_proprietary_incomplete; return false; } memcpy( &payload->proprietary.vendor_id, packed_payload, sizeof(payload->proprietary.vendor_id)); packed_payload += sizeof(payload->proprietary.vendor_id); payload->proprietary.function = packed_payload[0]; packed_payload++; if (packed_payload_len - 3 > 0) { payload->proprietary.data = packed_payload; payload->proprietary.data_len = packed_payload_len - 3; } else { payload->proprietary.data = NULL; payload->proprietary.data_len = 0; } return true; } /** * @brief Decode the BVLC-SC header * @param message - buffer with the message * @param message_len - length of the message * @param hdr - pointer to the decoded header * @param error_code - pointer to the error code * @param error_class - pointer to the error class * @param err_desc - pointer to the error description string * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_hdr( uint8_t *message, size_t message_len, BVLC_SC_DECODED_HDR *hdr, uint16_t *error_code, uint16_t *error_class, const char **err_desc) { size_t offs = 4; bool ret = false; size_t hdr_opt_len = 0; memset(hdr, 0, sizeof(*hdr)); if (message_len < 4) { /* According EA-001-4 'Clarifying BVLC-Result in BACnet/SC ' */ /* If a BVLC message is received that has fewer than four octets, a */ /* BVLC-Result NAK shall not be returned. */ /* The message shall be discarded and not be processed. */ *error_code = ERROR_CODE_DISCARD; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = NULL; return ret; } hdr->bvlc_function = message[0]; memcpy(&hdr->message_id, &message[2], sizeof(hdr->message_id)); if (message[1] & BVLC_SC_CONTROL_ORIG_VADDR) { hdr->origin = (BACNET_SC_VMAC_ADDRESS *)&message[offs]; offs += BVLC_SC_VMAC_SIZE; if (offs > message_len) { hdr->origin = NULL; *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_hdr_incomplete1; return false; } } if (message[1] & BVLC_SC_CONTROL_DEST_VADDR) { hdr->dest = (BACNET_SC_VMAC_ADDRESS *)&message[offs]; offs += BVLC_SC_VMAC_SIZE; if (offs > message_len) { hdr->dest = NULL; *error_code = ERROR_CODE_MESSAGE_INCOMPLETE; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_hdr_incomplete2; return false; } } /* in order to handle correctly item AB.3.1.5 Common Error Situations, */ /* in a case a BVLC message is received with unknown BVLC function, */ /* upper layer logic must understand if the message is unicast or not. */ /* That's why that error handling is put after filling of address fields. */ if (message[0] > BVLC_SC_PROPRIETARY_MESSAGE) { *error_code = ERROR_CODE_BVLC_FUNCTION_UNKNOWN; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_unknown_bvlc_function; return ret; } if (message[1] & BVLC_SC_CONTROL_DEST_OPTIONS) { ret = bvlc_sc_validate_options_headers( BACNET_PDU_DEST_OPTION_VALIDATION, &message[offs], message_len - offs, &hdr_opt_len, &hdr->dest_options_num, error_code, error_class, err_desc); if (!ret) { return false; } hdr->dest_options = &message[offs]; hdr->dest_options_len = hdr_opt_len; offs += hdr_opt_len; } if (message[1] & BVLC_SC_CONTROL_DATA_OPTIONS) { ret = bvlc_sc_validate_options_headers( BACNET_PDU_DATA_OPTION_VALIDATION, &message[offs], message_len - offs, &hdr_opt_len, &hdr->data_options_num, error_code, error_class, err_desc); if (!ret) { return false; } hdr->data_options = &message[offs]; hdr->data_options_len = hdr_opt_len; offs += hdr_opt_len; } if (offs >= message_len) { /* no payload */ return true; } hdr->payload = &message[offs]; hdr->payload_len = message_len - offs; return true; } /** * @brief Function decodes the BACnet SC header options. * @param option_array - pointer to the array of decoded options * @param options_list - pointer to the packed options list * @return true if the data is decoded successfully, false otherwise */ static bool bvlc_sc_decode_header_options( BVLC_SC_DECODED_HDR_OPTION *option_array, uint8_t *options_list) { uint8_t *next_option = options_list; size_t i = 0; while (next_option) { bvlc_sc_decode_option_hdr( options_list, &option_array[i].type, &option_array[i].must_understand, &next_option); option_array[i].packed_header_marker = options_list[0]; if (option_array[i].type == BVLC_SC_OPTION_TYPE_PROPRIETARY) { bvlc_sc_decode_proprietary_option( options_list, &option_array[i].specific.proprietary.vendor_id, &option_array[i].specific.proprietary.option_type, &option_array[i].specific.proprietary.data, &option_array[i].specific.proprietary.data_len); } i++; options_list = next_option; } return true; } /** * @brief Function decodes the BACnet SC header options if they exist. * @param message - pointer to the decoded message */ static void bvlc_sc_decode_dest_options_if_exists(BVLC_SC_DECODED_MESSAGE *message) { if (message->hdr.dest_options) { bvlc_sc_decode_header_options( message->dest_options, message->hdr.dest_options); } } /** * @brief Function decodes the BACnet SC header options if they exist. * @param message - pointer to the decoded message */ static void bvlc_sc_decode_data_options_if_exists(BVLC_SC_DECODED_MESSAGE *message) { if (message->hdr.data_options) { bvlc_sc_decode_header_options( message->data_options, message->hdr.data_options); } } /** * @brief Function decodes BACNet/SC message. * * @param buf - A buffer which holds BACNet/SC PDU. * @param buf_len - length of a buffer which holds BACNet/SC PDU. * @param message- pointer to structure for decoded data. * @param error-code - the value of parameter is filled if function returns * false. Check BACNET_ERROR_CLASS enum. * @param error_class - the value of parameter is filled if function returns * false. Check BACNET_ERROR_CODE enum. * @return true if PDU was successfully decoded otherwise returns false, * and error and class parameters are filled with corresponded codes. */ 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) { if (!message || !buf_len || !error_code || !error_class || !buf || !err_desc) { return false; } memset(message->data_options, 0, sizeof(message->data_options)); memset(message->dest_options, 0, sizeof(message->dest_options)); memset(&message->payload, 0, sizeof(message->payload)); if (!bvlc_sc_decode_hdr( buf, buf_len, &message->hdr, error_code, error_class, err_desc)) { return false; } if (message->hdr.dest_options) { if (message->hdr.dest_options_num > BVLC_SC_HEADER_OPTION_MAX) { /* dest header options list is too long */ *error_code = ERROR_CODE_OUT_OF_MEMORY; *error_class = ERROR_CLASS_RESOURCES; *err_desc = s_dest_options_list_too_long; return false; } } if (message->hdr.data_options) { if (message->hdr.data_options_num > BVLC_SC_HEADER_OPTION_MAX) { /* data header options list is too long */ *error_code = ERROR_CODE_OUT_OF_MEMORY; *error_class = ERROR_CLASS_RESOURCES; *err_desc = s_data_options_list_too_long; return false; } } switch (message->hdr.bvlc_function) { case BVLC_SC_RESULT: { if (message->hdr.data_options) { /* The BVLC-Result message must not have data options */ *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_unexpected_data_options; return false; } if (!message->hdr.payload || !message->hdr.payload_len) { *error_code = ERROR_CODE_PAYLOAD_EXPECTED; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_result_payload_expected; return false; } bvlc_sc_decode_dest_options_if_exists(message); if (!bvlc_sc_decode_result( &message->payload, message->hdr.payload, message->hdr.payload_len, error_code, error_class, err_desc)) { return false; } break; } case BVLC_SC_ENCAPSULATED_NPDU: { if (!message->hdr.payload || !message->hdr.payload_len) { *error_code = ERROR_CODE_PAYLOAD_EXPECTED; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_encapsulated_npdu_payload_expected; return false; } bvlc_sc_decode_dest_options_if_exists(message); bvlc_sc_decode_data_options_if_exists(message); message->payload.encapsulated_npdu.npdu = message->hdr.payload; message->payload.encapsulated_npdu.npdu_len = message->hdr.payload_len; break; } case BVLC_SC_ADDRESS_RESOLUTION: { if (message->hdr.data_options) { /* The Address-Resolution message must not have data options */ *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_address_resolution_data_options; return false; } if (message->hdr.payload || message->hdr.payload_len) { /* According EA-001-4 'Clarifying BVLC-Result in BACnet/SC */ /* If a BVLC message is received that is longer than expected, */ /* a BVLC-Result NAK shall be returned if it was a unicast */ /* message, indicating an 'Error Class' of COMMUNICATION and */ /* 'Error Code' of UNEXPECTED_DATA. The message shall be */ /* discarded and not be processed. */ *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_address_resolution_unexpected; return false; } bvlc_sc_decode_dest_options_if_exists(message); break; } case BVLC_SC_ADDRESS_RESOLUTION_ACK: { if (message->hdr.data_options) { /* The Address-Resolution Ack message must not have data options */ *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_address_resolution_ack_data_options; return false; } bvlc_sc_decode_dest_options_if_exists(message); if (message->hdr.payload_len) { message->payload.address_resolution_ack .utf8_websocket_uri_string = message->hdr.payload; } else { message->payload.address_resolution_ack .utf8_websocket_uri_string = NULL; } message->payload.address_resolution_ack .utf8_websocket_uri_string_len = message->hdr.payload_len; break; } case BVLC_SC_ADVERTISIMENT: { if (message->hdr.data_options) { /* The advertisiment message must not have data options */ *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_data_options; return false; } if (!message->hdr.payload || !message->hdr.payload_len) { *error_code = ERROR_CODE_PAYLOAD_EXPECTED; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_payload_expected; return false; } bvlc_sc_decode_dest_options_if_exists(message); if (!bvlc_sc_decode_advertisiment( &message->payload, message->hdr.payload, message->hdr.payload_len, error_code, error_class, err_desc)) { return false; } break; } case BVLC_SC_ADVERTISIMENT_SOLICITATION: { if (message->hdr.data_options) { /* The advertisiment solicitation message must not have data * options */ *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_solicitation_data_options; return false; } if (message->hdr.payload || message->hdr.payload_len) { *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_advertisiment_solicitation_payload_expected; return false; } bvlc_sc_decode_dest_options_if_exists(message); break; } case BVLC_SC_CONNECT_REQUEST: case BVLC_SC_CONNECT_ACCEPT: case BVLC_SC_DISCONNECT_REQUEST: case BVLC_SC_DISCONNECT_ACK: case BVLC_SC_HEARTBEAT_REQUEST: case BVLC_SC_HEARTBEAT_ACK: { if (message->hdr.origin != 0) { *error_code = ERROR_CODE_HEADER_ENCODING_ERROR; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_origin_unexpected; return false; } if (message->hdr.dest != 0) { *error_code = ERROR_CODE_HEADER_ENCODING_ERROR; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_dest_unexpected; return false; } if (message->hdr.data_options) { *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_data_option_unexpected; return false; } if (message->hdr.bvlc_function == BVLC_SC_CONNECT_REQUEST || message->hdr.bvlc_function == BVLC_SC_CONNECT_ACCEPT) { if (!message->hdr.payload || !message->hdr.payload_len) { *error_code = ERROR_CODE_PAYLOAD_EXPECTED; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_absent_payload; return false; } } else { if (message->hdr.payload || message->hdr.payload_len) { *error_code = ERROR_CODE_UNEXPECTED_DATA; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_message_too_long; return false; } } bvlc_sc_decode_dest_options_if_exists(message); if (message->hdr.bvlc_function == BVLC_SC_CONNECT_REQUEST) { if (!bvlc_sc_decode_connect_request( &message->payload, message->hdr.payload, message->hdr.payload_len, error_code, error_class, err_desc)) { return false; } } else if (message->hdr.bvlc_function == BVLC_SC_CONNECT_ACCEPT) { if (!bvlc_sc_decode_connect_accept( &message->payload, message->hdr.payload, message->hdr.payload_len, error_code, error_class, err_desc)) { return false; } } break; } case BVLC_SC_PROPRIETARY_MESSAGE: { if (message->hdr.data_options) { /* The proprietary message must not have data options */ *error_code = ERROR_CODE_INCONSISTENT_PARAMETERS; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_proprietary_data_options; return false; } if (!message->hdr.payload || !message->hdr.payload_len) { *error_code = ERROR_CODE_PAYLOAD_EXPECTED; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_proprietary_payload; return false; } bvlc_sc_decode_dest_options_if_exists(message); if (!bvlc_sc_decode_proprietary( &message->payload, message->hdr.payload, message->hdr.payload_len, error_code, error_class, err_desc)) { return false; } break; } default: *error_code = ERROR_CODE_BVLC_FUNCTION_UNKNOWN; *error_class = ERROR_CLASS_COMMUNICATION; *err_desc = s_unknown_bvlc_function; return false; } return true; } /** * @brief Function removes destination address of BACNet/SC message * and sets originating address instead of it. * It does it job only if message has destination address * and does not have origination address, otherwise pdu * stays unchanged. * @param pdu - BACNet/SC PDU. * @param pdu_len - length of a buffer which holds BACNet/SC PDU. * @param orig- origination vmac. */ void bvlc_sc_remove_dest_set_orig( uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig) { size_t offs = 4; if (pdu && pdu_len > 4) { if (!(pdu[1] & BVLC_SC_CONTROL_ORIG_VADDR) && (pdu[1] & BVLC_SC_CONTROL_DEST_VADDR)) { pdu[1] &= ~(BVLC_SC_CONTROL_DEST_VADDR); pdu[1] |= BVLC_SC_CONTROL_ORIG_VADDR; memcpy(&pdu[offs], orig, sizeof(*orig)); } } } /** * @brief Function changes or adds originating address into BACNet/SC message. * It is assumed that ppdu points to buffer with pdu that has * BSC_PRE bytes behind. * @param ppdu - pointer to buffer which holds BACNet/SC PDU. * @param pdu_len - length of a buffer which holds BACNet/SC PDU. * @param orig- origination vmac. * @return new pdu length if function succeeded and ppdu points to beginning of * changed pdu, otherwise returns old pdu_len and ppdu is not changed. */ size_t bvlc_sc_set_orig(uint8_t **ppdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig) { uint8_t buf[BSC_PRE]; uint8_t *pdu = *ppdu; size_t offs = 4; if (pdu && pdu_len > 4) { if (pdu[1] & BVLC_SC_CONTROL_ORIG_VADDR) { memcpy(&pdu[offs], orig, sizeof(orig->address)); return pdu_len; } else { memcpy(buf, pdu, 4); buf[1] |= BVLC_SC_CONTROL_ORIG_VADDR; memcpy(&buf[4], orig, sizeof(orig->address)); memcpy(pdu - BVLC_SC_VMAC_SIZE, buf, BVLC_SC_VMAC_SIZE + 4); *ppdu = pdu - BVLC_SC_VMAC_SIZE; return pdu_len + BVLC_SC_VMAC_SIZE; } } else { return pdu_len; } } /** * @brief Function checks if vmac address is broadcast. * @param vmac - pointer vmac address. * @return true if vmac is broadcast. otherwise returns false. */ bool bvlc_sc_is_vmac_broadcast(BACNET_SC_VMAC_ADDRESS *vmac) { int i; for (i = 0; i < BVLC_SC_VMAC_SIZE; i++) { if (vmac->address[i] != 0xFF) { return false; } } return true; } /** * @brief Function checks if it is needed to send BVLC result * response message for given decoded BACNet/SC message. * In a case of errors, standard requires to send such kind * of responses for unicast messages of specific types. * @param dm - pointer to decoded BACNet/SC message. * @return true if vmac is broadcast, otherwise returns false. */ bool bvlc_sc_need_send_bvlc_result(BVLC_SC_DECODED_MESSAGE *dm) { if (dm->hdr.dest == NULL || !bvlc_sc_is_vmac_broadcast(dm->hdr.dest)) { if (dm->hdr.bvlc_function == BVLC_SC_CONNECT_REQUEST || dm->hdr.bvlc_function == BVLC_SC_DISCONNECT_REQUEST || dm->hdr.bvlc_function == BVLC_SC_ENCAPSULATED_NPDU || dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION || dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT_SOLICITATION || dm->hdr.bvlc_function == BVLC_SC_HEARTBEAT_REQUEST || dm->hdr.bvlc_function > BVLC_SC_PROPRIETARY_MESSAGE) { return true; } } return false; } /** * @brief Function checks if destination address of input BACNet/SC * message is broadcast. * @param pdu- buffer with BACNet/SC message. * @param pdu_len- length of buffer of BACNet/SC message. * @return true if destination address of input BACNet/SC * is broadcast, otherwise returns false. */ bool bvlc_sc_pdu_has_dest_broadcast(uint8_t *pdu, size_t pdu_len) { size_t offs = 4; if (pdu_len >= 4) { if (pdu[1] & BVLC_SC_CONTROL_ORIG_VADDR) { offs += BVLC_SC_VMAC_SIZE; } if (pdu[1] & BVLC_SC_CONTROL_DEST_VADDR && (offs + BVLC_SC_VMAC_SIZE) <= pdu_len) { return bvlc_sc_is_vmac_broadcast( (BACNET_SC_VMAC_ADDRESS *)&pdu[offs]); } } return false; } /** * @brief Function checks if input BACNet/SC message has * destination address field. * @param pdu- buffer with BACNet/SC message. * @param pdu_len- length of buffer of BACNet/SC message. * @return true if destination address is presented in * input BACNet/SC message, otherwise returns false. */ bool bvlc_sc_pdu_has_no_dest(uint8_t *pdu, size_t pdu_len) { if (pdu_len >= 4) { if (pdu[1] & BVLC_SC_CONTROL_DEST_VADDR) { return false; } } return true; } /** * @brief Function puts destination address of * BACNet/SC message into vmac if message * contains it. * @param pdu- buffer with BACNet/SC message. * @param pdu_len- length of buffer of BACNet/SC message. * @return true if destination address is presented and was * placed into vmac, otherwise returns false. */ bool bvlc_sc_pdu_get_dest( uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *vmac) { size_t offs = 4; if (pdu_len >= 4) { if (pdu[1] & BVLC_SC_CONTROL_ORIG_VADDR) { offs += BVLC_SC_VMAC_SIZE; } if (pdu[1] & BVLC_SC_CONTROL_DEST_VADDR && (offs + BVLC_SC_VMAC_SIZE) <= pdu_len) { memcpy(&vmac->address[0], &pdu[offs], BVLC_SC_VMAC_SIZE); return true; } } return false; } /** * @brief Function removes originating and destination * address fields from input BACNet/SC message. * @param ppdu- pointer to buffer of BACNet/SC message. * @param pdu_len- length of buffer of BACNet/SC message. * @return new length of changed pdu if originating or destination * addresses were removed or old pdu length if * pdu was not changed. If pdu was changed, ppdu contains * updated pointer to buffer to modified BACNet/SC message. */ size_t bvlc_sc_remove_orig_and_dest(uint8_t **ppdu, size_t pdu_len) { uint8_t *pdu = *ppdu; size_t offs = 4; if (pdu_len > 4) { if (pdu[1] & BVLC_SC_CONTROL_ORIG_VADDR) { offs += BVLC_SC_VMAC_SIZE; } if (pdu[1] & BVLC_SC_CONTROL_DEST_VADDR) { offs += BVLC_SC_VMAC_SIZE; } pdu[1] &= ~(BVLC_SC_CONTROL_ORIG_VADDR); pdu[1] &= ~(BVLC_SC_CONTROL_DEST_VADDR); memmove(&pdu[offs - 4], pdu, 4); *ppdu = &pdu[offs - 4]; return pdu_len - offs + 4; } return pdu_len; }