diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3180a0..884b1601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,14 @@ The git repositories are hosted at the following sites: ### Security ### Added +* Added BACnetRecipient and BACnetAddressBinding codecs for EPICS application. + The implementation includes full encode/decode, ASCII conversion, + comparison, and copy functions for both data types, along with + comprehensive test coverage and supporting string utility functions. + Integrated the new types into the application data framework with + application tag assignments. (#1163) +* Added library specific string manipulation utilities including strcmp, + strncmp, and snprintf with offset functions. (#1163) * Added default option to bactext name functions so that NULL can be returned when a name does not exist. (#1160) * Added library specific ltrim, rtrim, and trim string functions. (#1159) diff --git a/apps/mstpcap/Makefile b/apps/mstpcap/Makefile index afdfc421..fc0f7507 100644 --- a/apps/mstpcap/Makefile +++ b/apps/mstpcap/Makefile @@ -14,6 +14,7 @@ SRCS = main.c \ ${BACNET_SRC_DIR}/bacnet/bacint.c \ ${BACNET_SRC_DIR}/bacnet/bacreal.c \ ${BACNET_SRC_DIR}/bacnet/bacstr.c \ + ${BACNET_SRC_DIR}/bacnet/bactext.c \ ${BACNET_SRC_DIR}/bacnet/iam.c \ ${BACNET_SRC_DIR}/bacnet/datetime.c \ ${BACNET_SRC_DIR}/bacnet/indtext.c \ diff --git a/apps/router/Makefile b/apps/router/Makefile index d4a091ba..1e86d63d 100644 --- a/apps/router/Makefile +++ b/apps/router/Makefile @@ -43,6 +43,7 @@ SRCS = main.c \ ${BACNET_SOURCE_DIR}/bacint.c \ ${BACNET_SOURCE_DIR}/bacreal.c \ ${BACNET_SOURCE_DIR}/bacstr.c \ + ${BACNET_SOURCE_DIR}/bactext.c \ ${BACNET_SOURCE_DIR}/npdu.c \ ${BACNET_SOURCE_DIR}/bacaddr.c \ ${BACNET_SOURCE_DIR}/hostnport.c \ diff --git a/src/bacnet/bacaddr.c b/src/bacnet/bacaddr.c index 5c52d1ed..10dcbfbf 100644 --- a/src/bacnet/bacaddr.c +++ b/src/bacnet/bacaddr.c @@ -16,10 +16,11 @@ #include "bacnet/bacdcode.h" #include "bacnet/bacint.h" #include "bacnet/bacstr.h" +#include "bacnet/bactext.h" #include "bacnet/bacaddr.h" /** - * @brief Copy a #BACNET_ADDRESS value to another + * @brief Copy a #BACNET_ADDRESS value to another, or initialize if src=NULL * @param dest - #BACNET_ADDRESS to be copied into * @param src - #BACNET_ADDRESS to be copied from */ @@ -37,6 +38,16 @@ void bacnet_address_copy(BACNET_ADDRESS *dest, const BACNET_ADDRESS *src) for (i = 0; i < MAX_MAC_LEN; i++) { dest->adr[i] = src->adr[i]; } + } else if (dest) { + dest->mac_len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->mac[i] = 0; + } + dest->net = 0; + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } } } @@ -87,6 +98,57 @@ bool bacnet_address_same(const BACNET_ADDRESS *dest, const BACNET_ADDRESS *src) return true; } +/** + * @brief Compare two BACnetAddress strictly from encoding based + * on network number. + * BACnetAddress ::= SEQUENCE { + * network-number Unsigned16,-- A value of 0 indicates the local network + * mac-address OctetString -- A string of length 0 indicates a broadcast + * } + * @param dest - BACnetAddress to be compared + * @param src - BACnetAddress to be compared + * @return true if the same values + */ +bool bacnet_address_net_same( + const BACNET_ADDRESS *dest, const BACNET_ADDRESS *src) +{ + uint8_t i = 0; /* loop counter */ + + if (!dest || !src) { + return false; + } + if (dest->net != src->net) { + return false; + } + if (dest->net == 0) { + /* local address stored in MAC */ + if (dest->mac_len != src->mac_len) { + return false; + } + for (i = 0; i < MAX_MAC_LEN; i++) { + if (i < dest->mac_len) { + if (dest->mac[i] != src->mac[i]) { + return false; + } + } + } + } else { + /* remote address stored in ADR */ + if (dest->len != src->len) { + return false; + } + for (i = 0; i < MAX_MAC_LEN; i++) { + if (i < dest->len) { + if (dest->adr[i] != src->adr[i]) { + return false; + } + } + } + } + + return true; +} + /** * @brief Configure a #BACNET_ADDRESS from mac, dnet, and adr * @param dest - #BACNET_ADDRESS to be configured @@ -194,6 +256,10 @@ void bacnet_address_mac_init( /** * @brief Parse an ASCII string for a bacnet-address + * @details Address formats: + * 192.168.1.42:47808 - for BACnet/IP + * ff:aa:ff:bb:ff:cc - for Ethernet + * fa or 127 - for ARCNET or MS/TP * @param mac [out] BACNET_MAC_ADDRESS structure to store the results * @param arg [in] nul terminated ASCII string to parse * @return true if the address was parsed @@ -226,7 +292,6 @@ bool bacnet_address_mac_from_ascii(BACNET_MAC_ADDRESS *mac, const char *arg) c = sscanf( arg, "%2x:%2x:%2x:%2x:%2x:%2x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]); - if (c > 0) { for (i = 0; i < c; i++) { mac->adr[i] = a[i]; @@ -239,6 +304,65 @@ bool bacnet_address_mac_from_ascii(BACNET_MAC_ADDRESS *mac, const char *arg) return status; } +/** + * @brief Parse an ASCII string for a bacnet-address + * @details Address formats: mac net addr + * 192.168.1.42:47808 - for BACnet/IP + * ff:aa:ff:bb:ff:cc - for Ethernet + * fa - for ARCNET or MS/TP + * 65535 - network number, 0 if mac but no addr + * @param src [out] BACNET_MAC_ADDRESS structure to store the results + * @param arg [in] null terminated ASCII string to parse + * @return true if the address was parsed + */ +bool bacnet_address_from_ascii(BACNET_ADDRESS *src, const char *arg) +{ + bool status = false; + int count = 0; + unsigned snet = 0, i; + char mac_string[80] = { "" }, sadr_string[80] = { "" }; + BACNET_MAC_ADDRESS mac = { 0 }; + + if (!(src && arg)) { + return false; + } + count = sscanf( + arg, "{%79[^,],%u,%79[^}]}", &mac_string[0], &snet, &sadr_string[0]); + if (count > 0) { + if (bacnet_address_mac_from_ascii(&mac, mac_string)) { + if (src) { + src->mac_len = mac.len; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->mac[i] = mac.adr[i]; + } + } + } + if (src) { + src->net = (uint16_t)snet; + } + if (snet) { + if (bacnet_address_mac_from_ascii(&mac, sadr_string)) { + if (src) { + src->len = mac.len; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = mac.adr[i]; + } + } + } + } else { + if (src) { + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } + } + } + status = true; + } + + return status; +} + /** * @brief Decodes a BACnetAddress value from APDU buffer * From clause 21. FORMAL DESCRIPTION OF APPLICATION PROTOCOL DATA UNITS @@ -260,19 +384,22 @@ int bacnet_address_decode( int len = 0; int apdu_len = 0; uint8_t i = 0; - BACNET_UNSIGNED_INTEGER decoded_unsigned = 0; + BACNET_UNSIGNED_INTEGER snet = 0; BACNET_OCTET_STRING mac_addr = { 0 }; + if (!apdu) { + return BACNET_STATUS_ERROR; + } /* network number */ len = bacnet_unsigned_application_decode( - &apdu[apdu_len], apdu_size - apdu_len, &decoded_unsigned); + &apdu[apdu_len], apdu_size - apdu_len, &snet); if (len <= 0) { return BACNET_STATUS_ERROR; } - if (decoded_unsigned <= UINT16_MAX) { + if (snet <= UINT16_MAX) { /* bounds checking - passed! */ if (value) { - value->net = (uint16_t)decoded_unsigned; + value->net = (uint16_t)snet; } } else { return BACNET_STATUS_ERROR; @@ -284,15 +411,29 @@ int bacnet_address_decode( if (len <= 0) { return BACNET_STATUS_ERROR; } - if (value) { - if (mac_addr.length > sizeof(value->mac)) { - return BACNET_STATUS_ERROR; + if (snet) { + if (value) { + if (mac_addr.length > sizeof(value->adr)) { + return BACNET_STATUS_ERROR; + } + /* bounds checking - passed! */ + value->len = mac_addr.length; + /* copy address */ + for (i = 0; i < value->len; i++) { + value->adr[i] = mac_addr.value[i]; + } } - /* bounds checking - passed! */ - value->mac_len = mac_addr.length; - /* copy address */ - for (i = 0; i < value->mac_len; i++) { - value->mac[i] = mac_addr.value[i]; + } else { + if (value) { + if (mac_addr.length > sizeof(value->mac)) { + return BACNET_STATUS_ERROR; + } + /* bounds checking - passed! */ + value->mac_len = mac_addr.length; + /* copy address */ + for (i = 0; i < value->mac_len; i++) { + value->mac[i] = mac_addr.value[i]; + } } } apdu_len += len; @@ -317,6 +458,9 @@ int bacnet_address_context_decode( int len = 0; int apdu_len = 0; + if (!apdu) { + return BACNET_STATUS_ERROR; + } if (!bacnet_is_opening_tag_number( &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) { return BACNET_STATUS_ERROR; @@ -346,23 +490,28 @@ int bacnet_address_context_decode( */ int encode_bacnet_address(uint8_t *apdu, const BACNET_ADDRESS *destination) { - int apdu_len = 0; - BACNET_OCTET_STRING mac_addr; + int apdu_len = 0, len, adr_len, mac_len; if (destination) { /* network number */ - apdu_len += encode_application_unsigned(apdu, destination->net); - /* encode mac address as an octet-string */ - if (destination->len != 0) { - octetstring_init(&mac_addr, destination->adr, destination->len); - } else { - octetstring_init(&mac_addr, destination->mac, destination->mac_len); - } + len = encode_application_unsigned(apdu, destination->net); + apdu_len += len; if (apdu) { - apdu += apdu_len; + apdu += len; } - apdu_len += encode_application_octet_string(apdu, &mac_addr); + /* encode mac address as an octet-string */ + if (destination->len > 0) { + adr_len = min(destination->len, sizeof(destination->adr)); + len = encode_application_octet_string_buffer( + apdu, destination->adr, adr_len); + } else { + mac_len = min(destination->mac_len, sizeof(destination->mac)); + len = encode_application_octet_string_buffer( + apdu, destination->mac, mac_len); + } + apdu_len += len; } + return apdu_len; } @@ -500,6 +649,9 @@ int bacnet_vmac_entry_decode( size_t i = 0; BACNET_OCTET_STRING mac_addr = { 0 }; + if (!apdu) { + return BACNET_STATUS_ERROR; + } /* virtual-mac-address [0] OctetString */ len = bacnet_octet_string_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 0, &mac_addr); @@ -565,3 +717,274 @@ bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id) return status; } + +/** + * @brief Encode a given BACnetAddressBinding + * @details + * BACnetAddressBinding ::= SEQUENCE { + * device-identifier BACnetObjectIdentifier, + * device-address BACnetAddress + * @param apdu - APDU buffer for storing the encoded data, or NULL for length + * @param value - BACnetAddressBinding value + * @return number of bytes in the APDU + */ +int bacnet_address_binding_type_encode( + uint8_t *apdu, const BACNET_ADDRESS_BINDING *value) +{ + int apdu_len = 0, len = 0; + + if (!value) { + return 0; + } + len = encode_application_object_id( + apdu, OBJECT_DEVICE, value->device_identifier); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_bacnet_address(apdu, &value->device_address); + apdu_len += len; + + return apdu_len; +} + +/** + * @brief Encode a given BACnetAddressBinding + * @details + * BACnetAddressBinding ::= SEQUENCE { + * device-identifier BACnetObjectIdentifier, + * device-address BACnetAddress + * @param apdu - APDU buffer for storing the encoded data, or NULL for length + * @param apdu_size - size of the APDU buffer + * @param value - BACnetAddressBinding value + * @return number of bytes in the APDU, or 0 if unable to fit. + */ +int bacnet_address_binding_encode( + uint8_t *apdu, size_t apdu_size, const BACNET_ADDRESS_BINDING *value) +{ + size_t apdu_len = 0; /* total length of the apdu, return value */ + + apdu_len = bacnet_address_binding_type_encode(NULL, value); + if (apdu_len > apdu_size) { + apdu_len = 0; + } else { + apdu_len = bacnet_address_binding_type_encode(apdu, value); + } + + return apdu_len; +} + +/** + * @brief Decode a given BACnetAddressBinding + * @details + * BACnetAddressBinding ::= SEQUENCE { + * device-identifier BACnetObjectIdentifier, + * device-address BACnetAddress + * @param apdu - APDU buffer for decoding + * @param apdu_size - Count of valid bytes in the buffer + * @param value - BACnetAddressBinding value to store the decoded data + * @return number of bytes decoded or BACNET_STATUS_ERROR on error + */ +int bacnet_address_binding_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_ADDRESS_BINDING *value) +{ + int apdu_len = 0, len = 0; + BACNET_OBJECT_TYPE object_type; + uint32_t object_instance; + BACNET_ADDRESS *address = NULL; + + if (!apdu) { + return BACNET_STATUS_ERROR; + } + len = bacnet_object_id_application_decode( + &apdu[apdu_len], apdu_size - apdu_len, &object_type, &object_instance); + if (len > 0) { + apdu_len += len; + if (object_type != OBJECT_DEVICE) { + return BACNET_STATUS_ERROR; + } + if (value) { + value->device_identifier = object_instance; + } + } else { + return BACNET_STATUS_ERROR; + } + if (value) { + address = &value->device_address; + } + len = bacnet_address_decode(&apdu[apdu_len], apdu_size - apdu_len, address); + if (len > 0) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief Compare two BACnetAddressBinding values + * @param value1 [in] The first BACnetAddressBinding value + * @param value2 [in] The second BACnetAddressBinding value + * @return True if the values are the same, else False + */ +bool bacnet_address_binding_same( + const BACNET_ADDRESS_BINDING *value1, const BACNET_ADDRESS_BINDING *value2) +{ + if (!value1) { + return false; + } + if (!value2) { + return false; + } + if (value1->device_identifier != value2->device_identifier) { + return false; + } + return bacnet_address_same( + &value1->device_address, &value2->device_address); +} + +/** + * @brief Copy a BACnetAddressBinding to another + * @param value1 [in] The first BACnetAddressBinding value + * @param value2 [in] The second BACnetAddressBinding value + * @return true if the value was copied, else false + */ +bool bacnet_address_binding_copy( + BACNET_ADDRESS_BINDING *dest, const BACNET_ADDRESS_BINDING *src) +{ + if (!(dest && src)) { + return false; + } + dest->device_identifier = src->device_identifier; + bacnet_address_copy(&dest->device_address, &src->device_address); + + return true; +} + +/** + * @brief Initialize a BACnetAddressBinding from device-id and address + * @param dest [out] The BACnetAddressBinding value + * @param device_identifier [in] The device-identifier value + * @param address [in] #BACNET_ADDRESS value + * @return true if the values were copied, else false + */ +bool bacnet_address_binding_init( + BACNET_ADDRESS_BINDING *dest, + uint32_t device_id, + const BACNET_ADDRESS *address) +{ + if (!dest) { + return false; + } + dest->device_identifier = device_id; + bacnet_address_copy(&dest->device_address, address); + + return true; +} + +/** + * @brief Produce a string from a BACnetAddressBinding structure + * @param value [in] The BACnetAddressBinding value + * @param str [out] The string to produce, NULL to get length only + * @param str_size [in] The size of the string buffer + * @return length of the produced string + * @details Output format: + * {(device, 1234),1234,X'c0:a8:00:0f'} + */ +int bacnet_address_binding_to_ascii( + const BACNET_ADDRESS_BINDING *value, char *str, size_t str_len) +{ + int offset = 0; + int i; + + if (!value) { + return 0; + } + offset = bacnet_snprintf(str, str_len, offset, "{"); + /* device-identifier */ + offset = bacnet_snprintf(str, str_len, offset, "("); + offset = bacnet_snprintf( + str, str_len, offset, "%s, ", bactext_object_type_name(OBJECT_DEVICE)); + offset = bacnet_snprintf( + str, str_len, offset, "%lu),", (unsigned long)value->device_identifier); + /* snet */ + offset = bacnet_snprintf( + str, str_len, offset, "%lu,", (unsigned long)value->device_address.net); + /* octetstring */ + offset = bacnet_snprintf(str, str_len, offset, "X'"); + if (value->device_address.net) { + /* adr */ + for (i = 0; i < value->device_address.len; i++) { + offset = bacnet_snprintf( + str, str_len, offset, "%02X", + (unsigned)value->device_address.adr[i]); + } + } else { + /* mac */ + for (i = 0; i < value->device_address.mac_len; i++) { + offset = bacnet_snprintf( + str, str_len, offset, "%02X", + (unsigned)value->device_address.mac[i]); + } + } + offset = bacnet_snprintf(str, str_len, offset, "'"); + offset = bacnet_snprintf(str, str_len, offset, "}"); + + return offset; +} + +/** + * @brief Parse a string into a BACnetAddressBinding structure + * @param value [out] The BACnetAddressBinding value + * @param argv [in] The string to parse + * @return true on success, else false + * @details format: + * {(device, 1234),1234,X'c0:a8:00:0f'} + */ +bool bacnet_address_binding_from_ascii( + BACNET_ADDRESS_BINDING *value, const char *arg) +{ + int count = 0, i = 0; + unsigned snet = 0; + char mac_string[80] = { "" }, obj_string[80] = { "" }; + BACNET_MAC_ADDRESS mac = { 0 }; + uint32_t object_type = 0; + unsigned long object_instance = 0; + + if (!(value && arg)) { + return false; + } + count = sscanf( + arg, "{(%79[^,], %lu),%u,X'%79[^']'}", obj_string, &object_instance, + &snet, mac_string); + if (count == 4) { + if (!bactext_object_type_strtol(obj_string, &object_type)) { + return false; + } + if (object_type != OBJECT_DEVICE) { + return false; + } + if (!bacnet_address_mac_from_ascii(&mac, mac_string)) { + return false; + } + if (value) { + value->device_identifier = object_instance; + value->device_address.net = snet; + if (snet) { + value->device_address.len = mac.len; + for (i = 0; i < MAX_MAC_LEN; i++) { + value->device_address.adr[i] = mac.adr[i]; + } + } else { + value->device_address.mac_len = mac.len; + for (i = 0; i < MAX_MAC_LEN; i++) { + value->device_address.mac[i] = mac.adr[i]; + } + } + } + return true; + } + + return false; +} diff --git a/src/bacnet/bacaddr.h b/src/bacnet/bacaddr.h index f025651a..3dfb8fe9 100644 --- a/src/bacnet/bacaddr.h +++ b/src/bacnet/bacaddr.h @@ -30,6 +30,17 @@ typedef struct BACnetVMACEntry { struct BACnetVMACEntry *next; } BACNET_VMAC_ENTRY; +/** BACnetAddressBinding ::= SEQUENCE { + * device-identifier BACnetObjectIdentifier, + * device-address BACnetAddress + * + */ +typedef struct BACnetAddressBinding { + uint32_t device_identifier; + BACNET_ADDRESS device_address; + struct BACnetAddressBinding *next; +} BACNET_ADDRESS_BINDING; + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -39,6 +50,9 @@ void bacnet_address_copy(BACNET_ADDRESS *dest, const BACNET_ADDRESS *src); BACNET_STACK_EXPORT bool bacnet_address_same(const BACNET_ADDRESS *dest, const BACNET_ADDRESS *src); BACNET_STACK_EXPORT +bool bacnet_address_net_same( + const BACNET_ADDRESS *dest, const BACNET_ADDRESS *src); +BACNET_STACK_EXPORT bool bacnet_address_init( BACNET_ADDRESS *dest, const BACNET_MAC_ADDRESS *mac, @@ -53,6 +67,8 @@ void bacnet_address_mac_init( BACNET_MAC_ADDRESS *mac, const uint8_t *adr, uint8_t len); BACNET_STACK_EXPORT bool bacnet_address_mac_from_ascii(BACNET_MAC_ADDRESS *mac, const char *arg); +BACNET_STACK_EXPORT +bool bacnet_address_from_ascii(BACNET_ADDRESS *src, const char *arg); BACNET_STACK_EXPORT int bacnet_address_decode( @@ -90,6 +106,33 @@ int bacnet_vmac_entry_decode( BACNET_STACK_EXPORT bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id); +BACNET_STACK_EXPORT +int bacnet_address_binding_type_encode( + uint8_t *apdu, const BACNET_ADDRESS_BINDING *value); +BACNET_STACK_EXPORT +int bacnet_address_binding_decode( + const uint8_t *apdu, size_t adpu_size, BACNET_ADDRESS_BINDING *value); +BACNET_STACK_EXPORT +int bacnet_address_binding_encode( + uint8_t *apdu, size_t adpu_size, const BACNET_ADDRESS_BINDING *value); +BACNET_STACK_EXPORT +bool bacnet_address_binding_same( + const BACNET_ADDRESS_BINDING *value1, const BACNET_ADDRESS_BINDING *value2); +BACNET_STACK_EXPORT +bool bacnet_address_binding_copy( + BACNET_ADDRESS_BINDING *dest, const BACNET_ADDRESS_BINDING *src); +BACNET_STACK_EXPORT +bool bacnet_address_binding_init( + BACNET_ADDRESS_BINDING *dest, + uint32_t device_id, + const BACNET_ADDRESS *address); +BACNET_STACK_EXPORT +int bacnet_address_binding_to_ascii( + const BACNET_ADDRESS_BINDING *value, char *buf, size_t buf_size); +BACNET_STACK_EXPORT +bool bacnet_address_binding_from_ascii( + BACNET_ADDRESS_BINDING *value, const char *arg); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index f5357ad7..655d76da 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -535,6 +535,18 @@ int bacapp_encode_application_data( apdu, &value->type.Timer_Value); break; #endif +#if defined(BACAPP_RECIPIENT) + case BACNET_APPLICATION_TAG_RECIPIENT: + apdu_len = + bacnet_recipient_encode(apdu, &value->type.Recipient); + break; +#endif +#if defined(BACAPP_ADDRESS_BINDING) + case BACNET_APPLICATION_TAG_ADDRESS_BINDING: + apdu_len = bacnet_address_binding_type_encode( + apdu, &value->type.Address_Binding); + break; +#endif #if defined(BACAPP_NO_VALUE) case BACNET_APPLICATION_TAG_NO_VALUE: apdu_len = bacnet_timer_value_no_value_encode(apdu); @@ -1308,14 +1320,14 @@ int bacapp_known_property_tag( case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: case PROP_RESTART_NOTIFICATION_RECIPIENTS: case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS: - /* FIXME: Properties using BACnetRecipient */ - return -1; + /* Properties using BACnetRecipient */ + return BACNET_APPLICATION_TAG_RECIPIENT; case PROP_DEVICE_ADDRESS_BINDING: case PROP_MANUAL_SLAVE_ADDRESS_BINDING: case PROP_SLAVE_ADDRESS_BINDING: - /* FIXME: BACnetAddressBinding */ - return -1; + /* BACnetAddressBinding */ + return BACNET_APPLICATION_TAG_ADDRESS_BINDING; case PROP_LOG_BUFFER: /* BACnetLogRecord */ @@ -1684,6 +1696,18 @@ int bacapp_decode_application_tag_value( apdu, apdu_size, &value->type.Timer_Value); break; #endif +#if defined(BACAPP_RECIPIENT) + case BACNET_APPLICATION_TAG_RECIPIENT: + apdu_len = bacnet_recipient_decode( + apdu, apdu_size, &value->type.Recipient); + break; +#endif +#if defined(BACAPP_ADDRESS_BINDING) + case BACNET_APPLICATION_TAG_ADDRESS_BINDING: + apdu_len = bacnet_address_binding_decode( + apdu, apdu_size, &value->type.Address_Binding); + break; +#endif #if defined(BACAPP_LOG_RECORD) case BACNET_APPLICATION_TAG_LOG_RECORD: /* BACnetLogRecord */ @@ -2196,10 +2220,12 @@ int bacapp_snprintf_octet_string( int len = 0; int slen = 0; int i = 0; + const uint8_t *octet_str; + slen = bacapp_snprintf(str, str_len, "X'"); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); len = octetstring_length(value); if (len > 0) { - const uint8_t *octet_str; octet_str = octetstring_value((BACNET_OCTET_STRING *)value); for (i = 0; i < len; i++) { slen = bacapp_snprintf(str, str_len, "%02X", *octet_str); @@ -2207,6 +2233,8 @@ int bacapp_snprintf_octet_string( octet_str++; } } + slen = bacapp_snprintf(str, str_len, "'"); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); return ret_val; } @@ -4027,6 +4055,18 @@ int bacapp_snprintf_value( &value->type.Timer_Value, str, str_len); break; #endif +#if defined(BACAPP_RECIPIENT) + case BACNET_APPLICATION_TAG_RECIPIENT: + ret_val = bacnet_recipient_to_ascii( + &value->type.Recipient, str, str_len); + break; +#endif +#if defined(BACAPP_ADDRESS_BINDING) + case BACNET_APPLICATION_TAG_ADDRESS_BINDING: + ret_val = bacnet_address_binding_to_ascii( + &value->type.Address_Binding, str, str_len); + break; +#endif #if defined(BACAPP_NO_VALUE) case BACNET_APPLICATION_TAG_NO_VALUE: ret_val = bacnet_timer_value_no_value_to_ascii(str, str_len); @@ -4542,8 +4582,11 @@ bool bacapp_parse_application_data( #endif #if defined(BACAPP_OCTET_STRING) case BACNET_APPLICATION_TAG_OCTET_STRING: - status = - octetstring_init_ascii_hex(&value->type.Octet_String, argv); + if (!octetstring_init_ascii_epics( + &value->type.Octet_String, argv)) { + status = octetstring_init_ascii_hex( + &value->type.Octet_String, argv); + } break; #endif #if defined(BACAPP_CHARACTER_STRING) @@ -4741,6 +4784,18 @@ bool bacapp_parse_application_data( &value->type.Timer_Value, argv); break; #endif +#if defined(BACAPP_RECIPIENT) + case BACNET_APPLICATION_TAG_RECIPIENT: + status = + bacnet_recipient_from_ascii(&value->type.Recipient, argv); + break; +#endif +#if defined(BACAPP_ADDRESS_BINDING) + case BACNET_APPLICATION_TAG_ADDRESS_BINDING: + status = bacnet_address_binding_from_ascii( + &value->type.Address_Binding, argv); + break; +#endif #if defined(BACAPP_NO_VALUE) case BACNET_APPLICATION_TAG_NO_VALUE: status = @@ -5495,6 +5550,19 @@ bool bacapp_same_value( &value->type.Timer_Value, &test_value->type.Timer_Value); break; #endif +#if defined(BACAPP_RECIPIENT) + case BACNET_APPLICATION_TAG_RECIPIENT: + status = bacnet_recipient_same( + &value->type.Recipient, &test_value->type.Recipient); + break; +#endif +#if defined(BACAPP_ADDRESS_BINDING) + case BACNET_APPLICATION_TAG_ADDRESS_BINDING: + status = bacnet_address_binding_same( + &value->type.Address_Binding, + &test_value->type.Address_Binding); + break; +#endif #if defined(BACAPP_NO_VALUE) case BACNET_APPLICATION_TAG_NO_VALUE: if (value->tag == test_value->tag) { diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h index 397335d4..9a9e9825 100644 --- a/src/bacnet/bacapp.h +++ b/src/bacnet/bacapp.h @@ -17,6 +17,7 @@ /* BACnet Stack API */ #include "bacnet/access_rule.h" #include "bacnet/bacaction.h" +#include "bacnet/bacaddr.h" #include "bacnet/bacdest.h" #include "bacnet/bacint.h" #include "bacnet/baclog.h" @@ -179,6 +180,12 @@ typedef struct BACnet_Application_Data_Value { #if defined(BACAPP_TIMER_VALUE) BACNET_TIMER_STATE_CHANGE_VALUE Timer_Value; #endif +#if defined(BACAPP_RECIPIENT) + BACNET_RECIPIENT Recipient; +#endif +#if defined(BACAPP_ADDRESS_BINDING) + BACNET_ADDRESS_BINDING Address_Binding; +#endif #if defined(BACAPP_LOG_RECORD) BACNET_LOG_RECORD Log_Record; #endif diff --git a/src/bacnet/bacdcode.c b/src/bacnet/bacdcode.c index bb8cdba2..a1927879 100644 --- a/src/bacnet/bacdcode.c +++ b/src/bacnet/bacdcode.c @@ -2256,6 +2256,36 @@ int encode_application_octet_string( return len; } +/** + * @brief Encode the BACnet Octet String value as application tagged + * from clause 20.2.8 Encoding of an Octet String Value + * and 20.2.1 General Rules for Encoding BACnet Tags + * @param apdu - buffer to hold the bytes + * @param buffer - the octet string to be encoded + * @param buffer_size - the size of the octet string to be encoded + * @return returns the number of apdu bytes encoded + */ +int encode_application_octet_string_buffer( + uint8_t *apdu, const uint8_t *buffer, size_t buffer_size) +{ + int apdu_len = 0, len = 0, i = 0; + + len = encode_tag( + apdu, BACNET_APPLICATION_TAG_OCTET_STRING, false, buffer_size); + apdu_len += len; + if (apdu) { + apdu += len; + } + for (i = 0; i < buffer_size; i++) { + if (apdu && buffer) { + apdu[i] = buffer[i]; + } + } + apdu_len += buffer_size; + + return apdu_len; +} + /** * @brief Encode the BACnet Octet String Value as context tagged * from clause 20.2.8 Encoding of an Octet String Value diff --git a/src/bacnet/bacdcode.h b/src/bacnet/bacdcode.h index 27d7fd35..d9f5a888 100644 --- a/src/bacnet/bacdcode.h +++ b/src/bacnet/bacdcode.h @@ -383,6 +383,9 @@ BACNET_STACK_EXPORT int encode_application_octet_string( uint8_t *apdu, const BACNET_OCTET_STRING *octet_string); BACNET_STACK_EXPORT +int encode_application_octet_string_buffer( + uint8_t *apdu, const uint8_t *buffer, size_t buffer_size); +BACNET_STACK_EXPORT int encode_context_octet_string( uint8_t *apdu, uint8_t tag_number, const BACNET_OCTET_STRING *octet_string); BACNET_STACK_EXPORT diff --git a/src/bacnet/bacdest.c b/src/bacnet/bacdest.c index 774b1c0c..7f56fec2 100644 --- a/src/bacnet/bacdest.c +++ b/src/bacnet/bacdest.c @@ -19,6 +19,7 @@ #include "bacnet/bacapp.h" #include "bacnet/bacdcode.h" #include "bacnet/bacdest.h" +#include "bacnet/bactext.h" #include "bacnet/basic/binding/address.h" /** @@ -83,8 +84,8 @@ bool bacnet_recipient_same( status = true; } } else if (r1->tag == BACNET_RECIPIENT_TAG_ADDRESS) { - status = - bacnet_address_same(&r1->type.address, &r2->type.address); + status = bacnet_address_net_same( + &r1->type.address, &r2->type.address); } else { status = false; } @@ -136,6 +137,20 @@ void bacnet_recipient_copy(BACNET_RECIPIENT *dest, const BACNET_RECIPIENT *src) } } +/** + * @brief Compare the BACnetRecipient data structure device object wildcard + * @param recipient - BACnetRecipient structure + * @return true if BACnetRecipient is equal to the device object wildcard + */ +void bacnet_recipient_device_wildcard_set(BACNET_RECIPIENT *recipient) +{ + if (recipient) { + recipient->tag = BACNET_RECIPIENT_TAG_DEVICE; + recipient->type.device.type = OBJECT_DEVICE; + recipient->type.device.instance = BACNET_MAX_INSTANCE; + } +} + /** * @brief Compare the BACnetRecipient data structure device object wildcard * @param recipient - BACnetRecipient structure @@ -440,6 +455,46 @@ int bacnet_destination_decode( return apdu_len; } +/** + * @brief Decode the BACnetDestination context tagged complex data + * @param apdu Pointer to the APDU buffer. + * @param apdu_size - the APDU buffer length + * @param tag_number The tag number that shall + * hold the time stamp. + * @param value Pointer to the variable that shall + * take the time stamp values. + * @return number of bytes decoded, zero if tag mismatch, + * or BACNET_STATUS_ERROR if an error occurs + */ +int bacnet_destination_context_decode( + const uint8_t *apdu, + uint32_t apdu_size, + uint8_t tag_number, + BACNET_DESTINATION *value) +{ + int len = 0; + int apdu_len = 0; + + if (!bacnet_is_opening_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) { + return 0; + } + apdu_len += len; + len = + bacnet_destination_decode(&apdu[apdu_len], apdu_size - apdu_len, value); + if (len < 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + if (!bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + + return apdu_len; +} + /** * @brief Encode the BACnetRecipient complex data * @@ -648,13 +703,11 @@ int bacnet_recipient_context_decode( int bacnet_destination_to_ascii( const BACNET_DESTINATION *bacdest, char *buf, size_t buf_size) { - int len = 0; - int buf_len = 0; + int offset = 0; bool comma; int i; - len = snprintf(buf, buf_size, "("); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "("); /* BACnetDaysOfWeek ::= BIT STRING { monday (0), @@ -667,39 +720,32 @@ int bacnet_destination_to_ascii( } */ /* Use numbers 1-7 (ISO 8601) */ - len = snprintf(buf, buf_size, "ValidDays=["); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "ValidDays=["); comma = false; for (i = 0; i < 7; i++) { if (bitstring_bit(&bacdest->ValidDays, i)) { if (comma) { - len = snprintf(buf, buf_size, ","); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, ","); } - len = snprintf(buf, buf_size, "%d", i + 1); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "%d", i + 1); comma = true; } } - len = snprintf(buf, buf_size, "];"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - len = snprintf( - buf, buf_size, "FromTime=%d:%02d:%02d.%02d;", bacdest->FromTime.hour, - bacdest->FromTime.min, bacdest->FromTime.sec, + offset = bacnet_snprintf(buf, buf_size, offset, "];"); + offset = bacnet_snprintf( + buf, buf_size, offset, "FromTime=%d:%02d:%02d.%02d;", + bacdest->FromTime.hour, bacdest->FromTime.min, bacdest->FromTime.sec, bacdest->FromTime.hundredths); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - len = snprintf( - buf, buf_size, "ToTime=%d:%02d:%02d.%02d;", bacdest->ToTime.hour, - bacdest->ToTime.min, bacdest->ToTime.sec, bacdest->ToTime.hundredths); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - len = snprintf(buf, buf_size, "Recipient="); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf( + buf, buf_size, offset, "ToTime=%d:%02d:%02d.%02d;", + bacdest->ToTime.hour, bacdest->ToTime.min, bacdest->ToTime.sec, + bacdest->ToTime.hundredths); + offset = bacnet_snprintf(buf, buf_size, offset, "Recipient="); if (bacdest->Recipient.tag == BACNET_RECIPIENT_TAG_DEVICE) { - len = snprintf( - buf, buf_size, "Device(type=%d,instance=%lu)", + offset = bacnet_snprintf( + buf, buf_size, offset, "Device(type=%d,instance=%lu)", bacdest->Recipient.type.device.type, (unsigned long)bacdest->Recipient.type.device.instance); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } else { /* BACnetAddress ::= SEQUENCE { @@ -708,39 +754,28 @@ int bacnet_destination_to_ascii( a broadcast } */ - len = snprintf( - buf, buf_size, + offset = bacnet_snprintf( + buf, buf_size, offset, "Address(net=%d,mac=", bacdest->Recipient.type.address.net); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - /* TODO determine if it's IPv4+port or Ethernet mac address and print it * nicer - how? Both are 6 bytes long. */ - for (i = 0; i < bacdest->Recipient.type.address.mac_len; i++) { if (i > 0) { - len = snprintf(buf, buf_size, ":"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, ":"); } - len = snprintf( - buf, buf_size, "%02x", bacdest->Recipient.type.address.mac[i]); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf( + buf, buf_size, offset, "%02x", + bacdest->Recipient.type.address.mac[i]); } - len = snprintf(buf, buf_size, ")"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, ")"); } - len = snprintf(buf, buf_size, ";"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - - len = snprintf( - buf, buf_size, "ProcessIdentifier=%lu;", + offset = bacnet_snprintf(buf, buf_size, offset, ";"); + offset = bacnet_snprintf( + buf, buf_size, offset, "ProcessIdentifier=%lu;", (unsigned long)bacdest->ProcessIdentifier); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - - len = snprintf( - buf, buf_size, "ConfirmedNotify=%s;", + offset = bacnet_snprintf( + buf, buf_size, offset, "ConfirmedNotify=%s;", bacdest->ConfirmedNotify ? "true" : "false"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - /* BACnetEventTransitionBits ::= BIT STRING { to-offnormal (0), @@ -748,38 +783,28 @@ int bacnet_destination_to_ascii( to-normal (2) } */ - len = snprintf(buf, buf_size, "Transitions=["); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); - + offset = bacnet_snprintf(buf, buf_size, offset, "Transitions=["); comma = false; - /* TODO remove casting when bitstring_bit() has const added - Github issue - * #320 */ if (bitstring_bit(&bacdest->Transitions, TRANSITION_TO_OFFNORMAL)) { - len = snprintf(buf, buf_size, "to-offnormal"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "to-offnormal"); comma = true; } if (bitstring_bit(&bacdest->Transitions, TRANSITION_TO_FAULT)) { if (comma) { - len = snprintf(buf, buf_size, ","); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, ","); } - len = snprintf(buf, buf_size, "to-fault"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "to-fault"); comma = true; } if (bitstring_bit(&bacdest->Transitions, TRANSITION_TO_NORMAL)) { if (comma) { - len = snprintf(buf, buf_size, ","); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, ","); } - len = snprintf(buf, buf_size, "to-normal"); - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "to-normal"); } - len = snprintf(buf, buf_size, "])"); /* end of the outer paren */ - buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); + offset = bacnet_snprintf(buf, buf_size, offset, "])"); - return buf_len; + return offset; } /** @@ -1165,3 +1190,178 @@ parse_end: return true; } + +/** + * @brief Parse an ASCII string for a BACnetRecipient address + * @details Address format: {X'c0:a8:00:0f',1234,X'c0:a8:00:0f'} + * @param src [out] BACNET_MAC_ADDRESS structure to store the results + * @param arg [in] null terminated ASCII string to parse + * @return true if the address was parsed + */ +bool bacnet_recipient_address_from_ascii(BACNET_ADDRESS *src, const char *arg) +{ + bool status = false; + int count = 0; + unsigned snet = 0, i; + char mac_string[80] = { "" }, sadr_string[80] = { "" }; + BACNET_MAC_ADDRESS mac = { 0 }; + + if (!(src && arg)) { + return false; + } + count = sscanf( + arg, "{X'%79[^']',%u,X'%79[^']'}", &mac_string[0], &snet, + &sadr_string[0]); + if (count > 0) { + if (bacnet_address_mac_from_ascii(&mac, mac_string)) { + if (src) { + src->mac_len = mac.len; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->mac[i] = mac.adr[i]; + } + } + } + if (src) { + src->net = (uint16_t)snet; + } + if (snet) { + if (bacnet_address_mac_from_ascii(&mac, sadr_string)) { + if (src) { + src->len = mac.len; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = mac.adr[i]; + } + } + } + } else { + if (src) { + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } + } + } + status = true; + } + + return status; +} + +/** + * Parse BACnet_Recipient from ASCII string (as entered by user) + * @param value - struct to store data parsed from the ASCII + * @param str - ASCII string, zero terminated + * @return true on success + * @note + * BACnetRecipient ::= CHOICE { + * device [0] BACnetObjectIdentifier, + * address [1] BACnetAddress + * } + */ +bool bacnet_recipient_from_ascii(BACNET_RECIPIENT *value_out, const char *str) +{ + BACNET_RECIPIENT value = { 0 }; + char object_string[80] = ""; + unsigned long object_instance = 0; + uint32_t found_index = 0; + int count; + + if (!str) { + return false; + } + if (str[0] == 0) { + return false; + } + if (str[0] == '(') { + /* (device, 1234) */ + count = sscanf(str, "(%79[^,], %lu)", object_string, &object_instance); + if (count == 2) { + if (bactext_object_type_strtol(object_string, &found_index)) { + value.tag = BACNET_RECIPIENT_TAG_DEVICE; + value.type.device.type = (BACNET_OBJECT_TYPE)found_index; + value.type.device.instance = object_instance; + } else { + return false; + } + } else { + return false; + } + } else if (str[0] == '{') { + value.tag = BACNET_RECIPIENT_TAG_ADDRESS; + /* {X'c0:a8:00:0f',1234,X'c0:a8:00:0f'} */ + if (!bacnet_recipient_address_from_ascii(&value.type.address, str)) { + return false; + } + } + if (value_out) { + bacnet_recipient_copy(value_out, &value); + } + + return true; +} + +/** + * @brief Convert BACnetRecipient to ASCII for printing + * @note + * BACnetRecipient ::= CHOICE { + * device [0] BACnetObjectIdentifier, + * address [1] BACnetAddress + * } + * Output format: + * (device, 1234) + * {X'c0:a8:00:0f',1234,X'c0:a8:00:0f'} + * @param value - struct to convert to ASCII + * @param buf - ASCII output buffer + * @param buf_size - ASCII output buffer capacity + * @return the number of characters which would be generated for the given + * input, excluding the trailing null. + * @note buf and buf_size may be null and zero to return only the size + */ +int bacnet_recipient_to_ascii( + const BACNET_RECIPIENT *value, char *str, size_t str_len) +{ + int offset = 0; + int i; + + if (!value) { + return 0; + } + if (value->tag == BACNET_RECIPIENT_TAG_DEVICE) { + /* device-identifier */ + offset = bacnet_snprintf(str, str_len, offset, "("); + offset = bacnet_snprintf( + str, str_len, offset, "%s, ", + bactext_object_type_name(value->type.device.type)); + offset = bacnet_snprintf( + str, str_len, offset, "%lu)", + (unsigned long)value->type.device.instance); + } else { + offset = bacnet_snprintf(str, str_len, offset, "{"); + /* MAC */ + offset = bacnet_snprintf(str, str_len, offset, "X'"); + for (i = 0; i < value->type.address.mac_len; i++) { + offset = bacnet_snprintf( + str, str_len, offset, "%02X", + (unsigned)value->type.address.mac[i]); + } + offset = bacnet_snprintf(str, str_len, offset, "',"); + /* snet */ + offset = bacnet_snprintf( + str, str_len, offset, "%lu", + (unsigned long)value->type.address.net); + /* octetstring */ + if (value->type.address.net) { + /* adr */ + offset = bacnet_snprintf(str, str_len, offset, ",X'"); + for (i = 0; i < value->type.address.len; i++) { + offset = bacnet_snprintf( + str, str_len, offset, "%02X", + (unsigned)value->type.address.adr[i]); + } + offset = bacnet_snprintf(str, str_len, offset, "'"); + } + offset = bacnet_snprintf(str, str_len, offset, "}"); + } + + return offset; +} diff --git a/src/bacnet/bacdest.h b/src/bacnet/bacdest.h index 74b49ed8..f86ceb75 100644 --- a/src/bacnet/bacdest.h +++ b/src/bacnet/bacdest.h @@ -71,6 +71,12 @@ BACNET_STACK_EXPORT int bacnet_destination_decode( const uint8_t *apdu, int apdu_len, BACNET_DESTINATION *destination); BACNET_STACK_EXPORT +int bacnet_destination_context_decode( + const uint8_t *apdu, + uint32_t apdu_size, + uint8_t tag_number, + BACNET_DESTINATION *value); +BACNET_STACK_EXPORT void bacnet_destination_default_init(BACNET_DESTINATION *destination); BACNET_STACK_EXPORT bool bacnet_destination_default(const BACNET_DESTINATION *destination); @@ -92,6 +98,9 @@ void bacnet_recipient_copy(BACNET_RECIPIENT *dest, const BACNET_RECIPIENT *src); BACNET_STACK_EXPORT bool bacnet_recipient_same( const BACNET_RECIPIENT *r1, const BACNET_RECIPIENT *r2); + +BACNET_STACK_EXPORT +void bacnet_recipient_device_wildcard_set(BACNET_RECIPIENT *recipient); BACNET_STACK_EXPORT bool bacnet_recipient_device_wildcard(const BACNET_RECIPIENT *recipient); BACNET_STACK_EXPORT @@ -112,6 +121,14 @@ int bacnet_recipient_context_decode( uint8_t tag_number, BACNET_RECIPIENT *value); +BACNET_STACK_EXPORT +bool bacnet_recipient_address_from_ascii(BACNET_ADDRESS *src, const char *arg); +BACNET_STACK_EXPORT +bool bacnet_recipient_from_ascii(BACNET_RECIPIENT *value_out, const char *str); +BACNET_STACK_EXPORT +int bacnet_recipient_to_ascii( + const BACNET_RECIPIENT *value, char *buf, size_t buf_size); + BACNET_STACK_EXPORT int bacnet_destination_to_ascii( const BACNET_DESTINATION *bacdest, char *buf, size_t buf_size); diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 7dfef32c..1d0a9fd7 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1691,6 +1691,8 @@ typedef enum { BACNET_APPLICATION_TAG_PROPERTY_VALUE, /* BACnetTimerStateChangeValue */ BACNET_APPLICATION_TAG_TIMER_VALUE, + /* BACnetAddressBinding */ + BACNET_APPLICATION_TAG_ADDRESS_BINDING, /* no-value - context tagged null */ BACNET_APPLICATION_TAG_NO_VALUE, /* ABSTRACT-SYNTAX - constructed value */ diff --git a/src/bacnet/bacstr.c b/src/bacnet/bacstr.c index cd3a5802..ecc35918 100644 --- a/src/bacnet/bacstr.c +++ b/src/bacnet/bacstr.c @@ -1087,6 +1087,32 @@ bool octetstring_init_ascii_hex( return status; } +/** + * @brief Converts an null terminated ASCII Hex EPICS formatted string + * to an octet string. + * @details format: X'c0:a8:00:0f' + * @param octet_string Pointer to the octet string. + * @param arg Pointer to the HEX-ASCII EPICS format string + * @return true if successfully converted and fits; false if too long + */ +bool octetstring_init_ascii_epics( + BACNET_OCTET_STRING *octet_string, const char *arg) +{ + bool status = false; /* return value */ + + if (!octet_string) { + return false; + } + if (!arg) { + return false; + } + if (bacnet_strnicmp(arg, "X'", 2) == 0) { + status = octetstring_init_ascii_hex(octet_string, arg + 2); + } + + return status; +} + /** * Copy an octet string from source to destination. * @@ -1273,14 +1299,8 @@ bool octetstring_value_same( } #endif -/** - * @brief Compare two strings, case insensitive - * @param a - first string - * @param b - second string - * @return 0 if the strings are equal, non-zero if not - * @note The stricmp() function is not included in the C standard. - */ -int bacnet_stricmp(const char *a, const char *b) +static int bacnet_strnicmp_internal( + const char *a, const char *b, size_t length, bool case_insensitive) { int twin_a, twin_b; @@ -1290,18 +1310,66 @@ int bacnet_stricmp(const char *a, const char *b) if (b == NULL) { return 1; } + if (length == 0) { + length = strlen(a); + } do { twin_a = *(const unsigned char *)a; twin_b = *(const unsigned char *)b; - twin_a = tolower(toupper(twin_a)); - twin_b = tolower(toupper(twin_b)); + if (case_insensitive) { + twin_a = tolower(toupper(twin_a)); + twin_b = tolower(toupper(twin_b)); + } a++; b++; - } while ((twin_a == twin_b) && (twin_a != '\0')); + length--; + } while ((twin_a == twin_b) && (twin_a != '\0') && (length > 0)); return twin_a - twin_b; } +/** + * @brief Compare two strings, case sensitive + * @param a - first string + * @param b - second string + * @return 0 if the strings are equal, non-zero if not + */ +int bacnet_strcmp(const char *a, const char *b) +{ + return bacnet_strnicmp_internal(a, b, 0, false); +} + +/** + * @brief Compare two strings, case insensitive + * @param a - first string + * @param b - second string + * @return 0 if the strings are equal, non-zero if not + * @note The stricmp() function is not included in the C standard. + */ +int bacnet_stricmp(const char *a, const char *b) +{ + return bacnet_strnicmp_internal(a, b, 0, true); +} + +/** + * @brief Compare two strings, case sensitive, with length limit + * @details The strncmp() function compares, at most, the first n characters + * of string1 and string2 with sensitivity to case. + * + * The function operates on null terminated strings. + * The string arguments to the function are expected to contain + * a null character (\0) marking the end of the string. + * + * @param a - first string + * @param b - second string + * @param length - maximum length to compare + * @return 0 if the strings are equal, non-zero if not + */ +int bacnet_strncmp(const char *a, const char *b, size_t length) +{ + return bacnet_strnicmp_internal(a, b, length, false); +} + /** * @brief Compare two strings, case insensitive, with length limit * @details The strnicmp() function compares, at most, the first n characters @@ -1319,28 +1387,7 @@ int bacnet_stricmp(const char *a, const char *b) */ int bacnet_strnicmp(const char *a, const char *b, size_t length) { - int twin_a, twin_b; - - if (length == 0) { - return 0; - } - if (a == NULL) { - return -1; - } - if (b == NULL) { - return 1; - } - do { - twin_a = *(const unsigned char *)a; - twin_b = *(const unsigned char *)b; - twin_a = tolower(toupper(twin_a)); - twin_b = tolower(toupper(twin_b)); - a++; - b++; - length--; - } while ((twin_a == twin_b) && (twin_a != '\0') && (length > 0)); - - return twin_a - twin_b; + return bacnet_strnicmp_internal(a, b, length, true); } /** @@ -1784,7 +1831,7 @@ bool bacnet_string_to_unsigned( * @return character string buffer */ char * -bacnet_sprintf_to_ascii(char *buffer, size_t size, const char *format, ...) +bacnet_snprintf_to_ascii(char *buffer, size_t size, const char *format, ...) { va_list args; @@ -1807,7 +1854,7 @@ bacnet_sprintf_to_ascii(char *buffer, size_t size, const char *format, ...) */ char *bacnet_dtoa(double value, char *buffer, size_t size, unsigned precision) { - return bacnet_sprintf_to_ascii(buffer, size, "%.*f", precision, value); + return bacnet_snprintf_to_ascii(buffer, size, "%.*f", precision, value); } /** @@ -1819,7 +1866,7 @@ char *bacnet_dtoa(double value, char *buffer, size_t size, unsigned precision) */ char *bacnet_itoa(int value, char *buffer, size_t size) { - return bacnet_sprintf_to_ascii(buffer, size, "%d", value); + return bacnet_snprintf_to_ascii(buffer, size, "%d", value); } /** @@ -1831,7 +1878,7 @@ char *bacnet_itoa(int value, char *buffer, size_t size) */ char *bacnet_ltoa(long value, char *buffer, size_t size) { - return bacnet_sprintf_to_ascii(buffer, size, "%ld", value); + return bacnet_snprintf_to_ascii(buffer, size, "%ld", value); } /** @@ -1843,7 +1890,7 @@ char *bacnet_ltoa(long value, char *buffer, size_t size) */ char *bacnet_utoa(unsigned value, char *buffer, size_t size) { - return bacnet_sprintf_to_ascii(buffer, size, "%u", value); + return bacnet_snprintf_to_ascii(buffer, size, "%u", value); } /** @@ -1855,7 +1902,7 @@ char *bacnet_utoa(unsigned value, char *buffer, size_t size) */ char *bacnet_ultoa(unsigned long value, char *buffer, size_t size) { - return bacnet_sprintf_to_ascii(buffer, size, "%lu", value); + return bacnet_snprintf_to_ascii(buffer, size, "%lu", value); } /** @@ -1909,3 +1956,90 @@ char *bacnet_trim(char *str, const char *trimmedchars) { return bacnet_ltrim(bacnet_rtrim(str, trimmedchars), trimmedchars); } + +/** + * @brief Parse a token from a string. + * @details From a string, a buffer to receive the "token" that gets scanned, + * the length of the buffer, and a string of "break" characters that stop + * the scan. The function will copy the string into the buffer up to any + * of the break characters, or until the buffer is full, and will always + * leave the buffer null-terminated. The function will return a pointer + * to the first non-breaking character after the one that stopped the scan. + * @param s string to parse + * @param tok buffer that receives the "token" that gets scanned + * @param toklen length of the buffer + * @param brk string of break characters that will stop the scan + * @return a pointer to the first non-breaking character after the one that + * stopped the scan or NULL on error or end of string. + * @note public domain by Ray Gardner, modified by Bob Stout and Steve Karg + */ +char *bacnet_stptok(const char *s, char *tok, size_t toklen, const char *brk) +{ + char *lim; /* limit of token */ + const char *b; /* current break character */ + + /* check for invalid pointers */ + if (!s || !tok || !brk) { + return NULL; + } + + /* check for empty string */ + if (!*s) { + return NULL; + } + + lim = tok + toklen - 1; + while (*s && tok < lim) { + for (b = brk; *b; b++) { + if (*s == *b) { + *tok = 0; + for (++s, b = brk; *s && *b; ++b) { + if (*s == *b) { + ++s; + b = brk; + } + } + if (!*s) { + return NULL; + } + return (char *)s; + } + } + *tok++ = *s++; + } + *tok = 0; + + if (!*s) { + return NULL; + } + + return (char *)s; +} + +/** + * @brief General purpose print formatter snprintf() function with offset + * @param buffer - destination string + * @param size - length of the destination string + * @param offset - offset into the buffer to start the string + * @param format - format string + * @return total number of characters written from the beginning of buffer + */ +int bacnet_snprintf( + char *buffer, size_t size, int offset, const char *format, ...) +{ + int length = 0; + va_list args; + + if (offset < size) { + size -= offset; + if (buffer) { + buffer += offset; + } + va_start(args, format); + length = vsnprintf(buffer, size, format, args); + va_end(args); + return offset + length; + } + + return size; +} diff --git a/src/bacnet/bacstr.h b/src/bacnet/bacstr.h index 6ba5027f..c5d3b5c1 100644 --- a/src/bacnet/bacstr.h +++ b/src/bacnet/bacstr.h @@ -144,6 +144,9 @@ BACNET_STACK_EXPORT bool octetstring_init_ascii_hex( BACNET_OCTET_STRING *octet_string, const char *ascii_hex); BACNET_STACK_EXPORT +bool octetstring_init_ascii_epics( + BACNET_OCTET_STRING *octet_string, const char *arg); +BACNET_STACK_EXPORT bool octetstring_copy( BACNET_OCTET_STRING *dest, const BACNET_OCTET_STRING *src); BACNET_STACK_EXPORT @@ -172,9 +175,13 @@ bool octetstring_value_same( const BACNET_OCTET_STRING *octet_string1, const BACNET_OCTET_STRING *octet_string2); +BACNET_STACK_EXPORT +int bacnet_strcmp(const char *a, const char *b); BACNET_STACK_EXPORT int bacnet_stricmp(const char *a, const char *b); BACNET_STACK_EXPORT +int bacnet_strncmp(const char *a, const char *b, size_t length); +BACNET_STACK_EXPORT int bacnet_strnicmp(const char *a, const char *b, size_t length); BACNET_STACK_EXPORT @@ -217,7 +224,10 @@ BACNET_STACK_EXPORT char *bacnet_ultoa(unsigned long value, char *buffer, size_t size); BACNET_STACK_EXPORT char * -bacnet_sprintf_to_ascii(char *buffer, size_t count, const char *format, ...); +bacnet_snprintf_to_ascii(char *buffer, size_t count, const char *format, ...); +BACNET_STACK_EXPORT +int bacnet_snprintf( + char *buffer, size_t count, int offset, const char *format, ...); BACNET_STACK_EXPORT char *bacnet_ltrim(char *str, const char *trimmedchars); @@ -226,6 +236,9 @@ char *bacnet_rtrim(char *str, const char *trimmedchars); BACNET_STACK_EXPORT char *bacnet_trim(char *str, const char *trimmedchars); +BACNET_STACK_EXPORT +char *bacnet_stptok(const char *s, char *tok, size_t toklen, const char *brk); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/bacnet/config.h b/src/bacnet/config.h index 69c72538..995c3fc6 100644 --- a/src/bacnet/config.h +++ b/src/bacnet/config.h @@ -226,6 +226,8 @@ defined(BACAPP_ACCESS_RULE) || \ defined(BACAPP_CHANNEL_VALUE) || \ defined(BACAPP_TIMER_VALUE) || \ + defined(BACAPP_RECIPIENT) || \ + defined(BACAPP_ADDRESS_BINDING) || \ defined(BACAPP_NO_VALUE) || \ defined(BACAPP_LOG_RECORD) || \ defined(BACAPP_SECURE_CONNECT) || \ @@ -278,6 +280,8 @@ #define BACAPP_ACCESS_RULE #define BACAPP_CHANNEL_VALUE #define BACAPP_TIMER_VALUE +#define BACAPP_RECIPIENT +#define BACAPP_ADDRESS_BINDING #define BACAPP_NO_VALUE #define BACAPP_LOG_RECORD #define BACAPP_SECURE_CONNECT @@ -307,6 +311,8 @@ defined(BACAPP_ACCESS_RULE) || \ defined(BACAPP_CHANNEL_VALUE) || \ defined(BACAPP_TIMER_VALUE) || \ + defined(BACAPP_RECIPIENT) || \ + defined(BACAPP_ADDRESS_BINDING) || \ defined(BACAPP_NO_VALUE) || \ defined(BACAPP_LOG_RECORD) #define BACAPP_COMPLEX_TYPES diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9b4ad057..f249af27 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -46,6 +46,13 @@ if (CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "GNU") add_compile_options(-Wno-write-strings) add_compile_options(-Wno-implicit-fallthrough) + # some consistent defines used for all builds - avoids inconsistent error + add_compile_definitions( + BIG_ENDIAN=0 + PRINT_ENABLED=1 + BACAPP_PRINT_ENABLED=1 + ) + add_link_options(-fprofile-arcs -ftest-coverage) endif() diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..e80d2019 --- /dev/null +++ b/test/README.md @@ -0,0 +1,47 @@ +# BACnet Stack Verification and Validation Testing + +CMake and C99 are used for the verification and validation testing. + +## Testing in the Github workflow pipeline with Makefile + +The Makefile 'test' recipe is used for the CMake recipe used in the pipeline. + +The 'retest' can be used to re-build the test. + +Changing to the test/build folder and running 'make test' can show +more details about a failing build or failing test. Running the actual +test by running ./test/build/bacnet/.../test_xyz has more detailed +results of running the test. + +## Testing with VS Code with CMake extension and C/C++ build tools + +* Open the test/ folder as a workspace. +* The folder structure follows the src/bacnet folder structure. +* Configure the CMake extension to use the C/C++ build tools kit. +* Configure the CMake extension to use the Debug build type. +* Build each or 'all' project using the CMake extension. +* Each unit or verification test can be build individually. +* The LCOV Cmake recipe will create Line-of-Code Coverage that + can be viewed in a browser starting at test/build/lcoverage/index.html +* The CTest extension can also be used to run the tests. + +## Validation and Unit testing with ctest or ztest + +* The ctest suite is used from cmake managing the tests. Add new test + files following the src/bacnet folder structure into the CMakeLists.txt + file. Copy and existing test folder at the same folder depth to start. +* The ztest framework (from Zephyr) has been ported to use in the tests + and includes the fff mocking macros. The zassert_x() macros include a + formatted print as the last argument so that print style debug can be + used to show values that fail which aids in writing a test. +* Tests can also be written without the zassert_x() macros by including + assert.h and using the assert() macro. + +## Use pre-commit tools for changes and additions + +* This library uses 'pre-commit' tools to format the files. +* Run pre-commit after staging a file and before committing + to avoid pipeline build failures. +* If a file fails pre-commit checking, the file will be re-formated + correctly by the pre-commit tool. Add the fixed file to the staged + files and run pre-commit again. diff --git a/test/bacnet/bacaddr/CMakeLists.txt b/test/bacnet/bacaddr/CMakeLists.txt index cd81c1b3..527eb5a2 100644 --- a/test/bacnet/bacaddr/CMakeLists.txt +++ b/test/bacnet/bacaddr/CMakeLists.txt @@ -37,9 +37,11 @@ add_executable(${PROJECT_NAME} # Support files and stubs (pathname alphabetical) ${SRC_DIR}/bacnet/bacaction.c ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacint.c ${SRC_DIR}/bacnet/bacreal.c ${SRC_DIR}/bacnet/bacstr.c - ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/indtext.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/bacaddr/src/main.c b/test/bacnet/bacaddr/src/main.c index 29a47858..c9ae0255 100644 --- a/test/bacnet/bacaddr/src/main.c +++ b/test/bacnet/bacaddr/src/main.c @@ -31,6 +31,10 @@ static void test_BACNET_ADDRESS(void) zassert_false(status, NULL); status = bacnet_address_same(NULL, NULL); zassert_false(status, NULL); + bacnet_address_copy(&dest, NULL); + bacnet_address_copy(&src, NULL); + status = bacnet_address_same(&dest, &src); + zassert_true(status, NULL); /* happy day cases */ status = bacnet_address_same(&dest, &dest); @@ -44,6 +48,42 @@ static void test_BACNET_ADDRESS(void) zassert_true(status, NULL); status = bacnet_address_same(&dest, &src); zassert_true(status, NULL); + mac.len = 1; + mac.adr[0] = 3; + adr.len = 1; + adr.adr[0] = 4; + dnet = 1234; + status = bacnet_address_init(&src, &mac, dnet, &adr); + zassert_true(status, NULL); + bacnet_address_copy(&dest, &src); + zassert_true(status, NULL); + status = bacnet_address_net_same(&dest, &src); + zassert_true(status, NULL); + dnet = 0; + status = bacnet_address_init(&src, &mac, dnet, &adr); + zassert_true(status, NULL); + bacnet_address_copy(&dest, &src); + zassert_true(status, NULL); + status = bacnet_address_net_same(&dest, &src); + zassert_true(status, NULL); + /* net + adr */ + dnet = 1; + status = bacnet_address_init(&src, NULL, dnet, &adr); + status = bacnet_address_net_same(&dest, &src); + zassert_false(status, NULL); + /* net=0, mac */ + dnet = 1; + status = bacnet_address_init(&dest, NULL, dnet, &adr); + zassert_true(status, NULL); + dnet = 0; + status = bacnet_address_init(&src, &mac, dnet, NULL); + zassert_true(status, NULL); + status = bacnet_address_net_same(&dest, &src); + zassert_false(status, NULL); + status = bacnet_address_net_same(NULL, &src); + zassert_false(status, NULL); + status = bacnet_address_net_same(&dest, NULL); + zassert_false(status, NULL); /* remote dnet is non-zero */ dnet = 1; status = bacnet_address_init(&dest, &mac, dnet, &adr); @@ -105,6 +145,11 @@ static void test_BACNET_ADDRESS(void) zassert_true(status, NULL); status = bacnet_address_same(&dest, &src); zassert_true(status, NULL); + /* initialize using copy */ + bacnet_address_copy(&dest, NULL); + zassert_equal(dest.mac_len, 0, NULL); + zassert_equal(dest.len, 0, NULL); + zassert_equal(dest.net, 0, NULL); } #if defined(CONFIG_ZTEST_NEW_API) @@ -215,8 +260,36 @@ static void test_BACnetAddress_Codec(void) zassert_equal(value.net, test_value.net, NULL); zassert_equal(value.mac_len, test_value.mac_len, NULL); zassert_equal(value.mac_len, 6, NULL); + /* test deprecated function */ + test_len = decode_bacnet_address(apdu, &test_value); + zassert_equal(len, test_len, NULL); + zassert_equal(value.net, test_value.net, NULL); + zassert_equal(value.mac_len, test_value.mac_len, NULL); + zassert_equal(value.mac_len, 6, NULL); + /* negative tests - NULL value */ test_len = bacnet_address_decode(apdu, sizeof(apdu), NULL); zassert_equal(len, test_len, NULL); + /* network address */ + value.net = 1; + value.len = 3; + value.adr[0] = 1; + value.adr[1] = 2; + value.adr[2] = 3; + len = encode_bacnet_address(NULL, &value); + test_len = encode_bacnet_address(apdu, &value); + zassert_true(len > 0, NULL); + zassert_true(test_len > 0, NULL); + zassert_equal(len, test_len, "len=%d test_len=%d", len, test_len); + test_len = bacnet_address_decode(apdu, sizeof(apdu), &test_value); + zassert_equal(len, test_len, NULL); + zassert_equal(value.net, test_value.net, NULL); + zassert_equal(value.mac_len, test_value.mac_len, NULL); + zassert_equal(value.len, test_value.len, NULL); + zassert_equal(value.len, 3, NULL); + zassert_equal(value.adr[0], test_value.adr[0], NULL); + zassert_equal(value.adr[1], test_value.adr[1], NULL); + zassert_equal(value.adr[2], test_value.adr[2], NULL); + /* context tagged */ tag_number = 1; len = encode_context_bacnet_address(NULL, tag_number, &value); test_len = encode_context_bacnet_address(apdu, tag_number, &value); @@ -229,6 +302,12 @@ static void test_BACnetAddress_Codec(void) zassert_equal(value.net, test_value.net, NULL); zassert_equal(value.mac_len, test_value.mac_len, NULL); zassert_equal(value.mac_len, 6, NULL); + /* validate deprecated function */ + test_len = decode_context_bacnet_address(apdu, tag_number, &test_value); + zassert_equal(len, test_len, NULL); + zassert_equal(value.net, test_value.net, NULL); + zassert_equal(value.mac_len, test_value.mac_len, NULL); + zassert_equal(value.mac_len, 6, NULL); test_len = bacnet_address_context_decode( apdu, sizeof(apdu), tag_number, &test_value); /* negative tests - NULL value */ @@ -256,9 +335,18 @@ static void test_bacnet_vmac_entry_codec(void) { uint8_t apdu[MAX_APDU]; BACNET_VMAC_ENTRY value = { 0 }, test_value = { 0 }; + BACNET_ADDRESS test_addr = { 0 }; int test_len = 0, apdu_len = 0, null_len = 0; + bool status = false; unsigned i; + status = bacnet_vmac_address_set(NULL, 0); + zassert_false(status, NULL); + status = bacnet_vmac_address_set(&test_addr, 12345); + zassert_true(status, NULL); + zassert_equal(test_addr.mac_len, 3, NULL); + zassert_equal(test_addr.net, 0, NULL); + /* VMAC */ value.virtual_mac_address.adr[0] = 1; value.virtual_mac_address.adr[1] = 2; value.virtual_mac_address.adr[2] = 3; @@ -268,6 +356,8 @@ static void test_bacnet_vmac_entry_codec(void) value.native_mac_address[2] = 6; value.native_mac_address[3] = 7; value.native_mac_address_len = 4; + null_len = bacnet_vmac_entry_encode(NULL, sizeof(apdu), NULL); + zassert_equal(null_len, 0, NULL); null_len = bacnet_vmac_entry_encode(NULL, sizeof(apdu), &value); apdu_len = bacnet_vmac_entry_encode(apdu, sizeof(apdu), &value); zassert_true(apdu_len > 0, NULL); @@ -302,6 +392,124 @@ static void test_bacnet_vmac_entry_codec(void) } } +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacnet_address_tests, test_bacnet_address_ascii) +#else +static void test_bacnet_address_ascii(void) +#endif +{ + char ascii_mac_net_adr[80] = "{ff:00:ff:01:ff:02,1,7f}"; + char ascii_mac_net[80] = "{192.168.1.1:47808,0}"; + BACNET_ADDRESS value = { 0 }; + bool status; + + status = bacnet_address_from_ascii(NULL, NULL); + zassert_false(status, NULL); + status = bacnet_address_from_ascii(&value, NULL); + zassert_false(status, NULL); + status = bacnet_address_from_ascii(NULL, ascii_mac_net_adr); + zassert_false(status, NULL); + status = bacnet_address_from_ascii(&value, ascii_mac_net_adr); + zassert_true(status, NULL); + zassert_equal(value.mac_len, 6, NULL); + zassert_equal(value.mac[0], 0xff, NULL); + zassert_equal(value.mac[1], 0x00, NULL); + zassert_equal(value.mac[2], 0xff, NULL); + zassert_equal(value.mac[3], 0x01, NULL); + zassert_equal(value.mac[4], 0xff, NULL); + zassert_equal(value.mac[5], 0x02, NULL); + zassert_equal(value.net, 1, NULL); + zassert_equal(value.len, 1, NULL); + zassert_equal(value.adr[0], 0x7f, NULL); + status = bacnet_address_from_ascii(&value, ascii_mac_net); + zassert_true(status, NULL); + zassert_equal(value.mac_len, 6, NULL); + zassert_equal(value.mac[0], 192, NULL); + zassert_equal(value.mac[1], 168, NULL); + zassert_equal(value.mac[2], 1, NULL); + zassert_equal(value.mac[3], 1, NULL); + zassert_equal(value.net, 0, NULL); + zassert_equal(value.len, 0, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacnet_address_tests, test_BACNET_ADDRESS_BINDING) +#else +static void test_BACNET_ADDRESS_BINDING(void) +#endif +{ + uint8_t apdu[MAX_APDU] = { 0 }; + BACNET_ADDRESS addr = { + .mac_len = 1, .mac[0] = 0x01, .net = 0, .adr[0] = 0, .len = 0 + }; + BACNET_ADDRESS_BINDING binding = { 0 }, test_binding = { 0 }; + bool status = false; + int len, test_len, apdu_len, null_len; + char str[80] = ""; + + null_len = bacnet_address_binding_type_encode(NULL, NULL); + zassert_equal(null_len, 0, NULL); + status = bacnet_address_binding_init(&binding, 12345, &addr); + len = bacnet_address_binding_type_encode(NULL, &binding); + null_len = bacnet_address_binding_type_encode(NULL, &binding); + zassert_not_equal(null_len, 0, NULL); + apdu_len = bacnet_address_binding_type_encode(apdu, &binding); + zassert_not_equal(null_len, 0, NULL); + zassert_equal(null_len, apdu_len, NULL); + test_len = bacnet_address_binding_decode(apdu, apdu_len, &test_binding); + zassert_equal(apdu_len, test_len, NULL); + status = bacnet_address_same( + &binding.device_address, &test_binding.device_address); + zassert_true(status, NULL); + status = bacnet_address_binding_same(&binding, &test_binding); + zassert_true(status, NULL); + null_len = bacnet_address_binding_encode(NULL, 0, &binding); + zassert_equal(null_len, 0, NULL); + null_len = bacnet_address_binding_encode(NULL, 0, NULL); + zassert_equal(null_len, 0, NULL); + null_len = bacnet_address_binding_encode(NULL, sizeof(apdu), &binding); + zassert_not_equal(null_len, 0, NULL); + apdu_len = bacnet_address_binding_encode(apdu, sizeof(apdu), &binding); + zassert_equal(null_len, apdu_len, NULL); + /* shrink the apdu size for decoding test */ + while (apdu_len) { + apdu_len--; + test_len = bacnet_address_binding_decode(apdu, apdu_len, &test_binding); + zassert_equal(test_len, BACNET_STATUS_ERROR, NULL); + } + /* negative tests - NULL value */ + apdu_len = bacnet_address_binding_encode(apdu, sizeof(apdu), &binding); + zassert_equal(null_len, apdu_len, NULL); + test_len = bacnet_address_binding_decode(apdu, sizeof(apdu), NULL); + zassert_equal(apdu_len, test_len, NULL); + test_len = bacnet_address_binding_decode(NULL, 0, NULL); + zassert_equal(test_len, BACNET_STATUS_ERROR, NULL); + status = bacnet_address_binding_copy(&test_binding, &binding); + zassert_true(status, NULL); + status = bacnet_address_binding_same(&test_binding, &binding); + zassert_true(status, NULL); + /* ASCII */ + len = bacnet_address_binding_to_ascii(&binding, str, sizeof(str)); + zassert_true(len > 0, NULL); + status = bacnet_address_binding_from_ascii(&test_binding, str); + zassert_true(status, NULL); + status = bacnet_address_binding_same(&test_binding, &binding); + zassert_true(status, NULL); + /* negative tests - NULL value */ + status = bacnet_address_binding_same(NULL, &binding); + zassert_false(status, NULL); + status = bacnet_address_binding_same(&test_binding, NULL); + zassert_false(status, NULL); + status = bacnet_address_binding_copy(NULL, &binding); + zassert_false(status, NULL); + status = bacnet_address_binding_copy(&test_binding, NULL); + zassert_false(status, NULL); + status = bacnet_address_binding_init(NULL, 0, &addr); + zassert_false(status, NULL); + len = bacnet_address_binding_to_ascii(NULL, str, sizeof(str)); + zassert_equal(len, 0, NULL); +} + /** * @} */ @@ -314,8 +522,9 @@ void test_main(void) bacnet_address_tests, ztest_unit_test(test_BACNET_ADDRESS), ztest_unit_test(test_BACNET_MAC_ADDRESS), ztest_unit_test(test_BACnetAddress_Codec), - ztest_unit_test(test_bacnet_vmac_entry_codec)); - + ztest_unit_test(test_bacnet_vmac_entry_codec), + ztest_unit_test(test_bacnet_address_ascii), + ztest_unit_test(test_BACNET_ADDRESS_BINDING)); ztest_run_test_suite(bacnet_address_tests); } #endif diff --git a/test/bacnet/bacapp/src/main.c b/test/bacnet/bacapp/src/main.c index 377dd5f3..2461a466 100644 --- a/test/bacnet/bacapp/src/main.c +++ b/test/bacnet/bacapp/src/main.c @@ -1334,7 +1334,8 @@ void test_bacapp_snprintf( /* normal case */ len = bacapp_snprintf_value(str, str_len + 1, &object_value); zassert_mem_equal( - str, expected, str_len, "str='%s' expected='%s'", str, expected); + str, expected, str_len, "%s: str='%s' expected='%s'", + bactext_application_tag_name(tag_number), str, expected); zassert_equal(len, str_len, NULL); /* test when buffer is too small the behavior matches snprintf(). */ test_len = len; @@ -1380,14 +1381,17 @@ static void test_bacapp_sprintf_epics(void) BACNET_APPLICATION_TAG_DOUBLE, "-3.14159", "-3.141590"); test_bacapp_snprintf( BACNET_APPLICATION_TAG_OCTET_STRING, "1234567890ABCDEF", - "1234567890ABCDEF"); + "X'1234567890ABCDEF'"); test_bacapp_snprintf( - BACNET_APPLICATION_TAG_OCTET_STRING, "12-34-56-78-90-AB-CD-EF", - "1234567890ABCDEF"); + BACNET_APPLICATION_TAG_OCTET_STRING, "X'1234567890ABCDEF'", + "X'1234567890ABCDEF'"); test_bacapp_snprintf( - BACNET_APPLICATION_TAG_OCTET_STRING, "12 34 56 78 90 AB CD EF", - "1234567890ABCDEF"); - test_bacapp_snprintf(BACNET_APPLICATION_TAG_OCTET_STRING, "", ""); + BACNET_APPLICATION_TAG_OCTET_STRING, "X'12-34-56-78-90-AB-CD-EF'", + "X'1234567890ABCDEF'"); + test_bacapp_snprintf( + BACNET_APPLICATION_TAG_OCTET_STRING, "X'12 34 56 78 90 AB CD EF'", + "X'1234567890ABCDEF'"); + test_bacapp_snprintf(BACNET_APPLICATION_TAG_OCTET_STRING, "", "X''"); test_bacapp_snprintf( BACNET_APPLICATION_TAG_CHARACTER_STRING, "Karg!", "\"Karg!\""); test_bacapp_snprintf(BACNET_APPLICATION_TAG_CHARACTER_STRING, "", "\"\""); diff --git a/test/bacnet/bacdest/src/main.c b/test/bacnet/bacdest/src/main.c index b074f966..431bfd2c 100644 --- a/test/bacnet/bacdest/src/main.c +++ b/test/bacnet/bacdest/src/main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include /** @@ -21,49 +22,6 @@ /** * @brief Test */ -static void -testBACnetRecipientData(BACNET_RECIPIENT *data1, BACNET_RECIPIENT *data2) -{ - unsigned i = 0; - - if (data1 && data2) { - zassert_equal(data1->tag, data2->tag, NULL); - if (data1->tag == BACNET_RECIPIENT_TAG_DEVICE) { - zassert_equal( - data1->type.device.type, data2->type.device.type, NULL); - zassert_equal( - data1->type.device.instance, data2->type.device.instance, NULL); - } else if (data1->tag == BACNET_RECIPIENT_TAG_ADDRESS) { - zassert_equal( - data1->type.address.net, data2->type.address.net, NULL); - if (data1->type.address.net == BACNET_BROADCAST_NETWORK) { - zassert_equal( - data1->type.address.mac_len, data2->type.address.mac_len, - NULL); - } else if (data1->type.address.net) { - zassert_equal( - data1->type.address.len, data2->type.address.len, NULL); - for (i = 0; i < data1->type.address.len; i++) { - zassert_equal( - data1->type.address.adr[i], data2->type.address.adr[i], - NULL); - } - } else { - zassert_equal( - data1->type.address.mac_len, data2->type.address.mac_len, - NULL); - for (i = 0; i < data1->type.address.mac_len; i++) { - zassert_equal( - data1->type.address.mac[i], data2->type.address.mac[i], - NULL); - } - } - } else { - zassert_true(data1->tag < BACNET_RECIPIENT_TAG_MAX, NULL); - } - } -} - #if defined(CONFIG_ZTEST_NEW_API) ZTEST(bacnet_destination_tests, testBACnetDestination) #else @@ -73,6 +31,7 @@ static void testBACnetDestination(void) uint8_t apdu[MAX_APDU] = { 0 }; BACNET_DESTINATION destination = { 0 }, test_destination = { 0 }; int apdu_len = 0, null_len = 0, test_len = 0; + bool status = false; destination.Recipient.tag = BACNET_RECIPIENT_TAG_DEVICE; destination.Recipient.type.device.type = OBJECT_DEVICE; @@ -82,8 +41,8 @@ static void testBACnetDestination(void) zassert_equal(apdu_len, null_len, NULL); test_len = bacnet_destination_decode(apdu, apdu_len, &test_destination); zassert_equal(apdu_len, test_len, NULL); - testBACnetRecipientData( - &destination.Recipient, &test_destination.Recipient); + status = bacnet_destination_same(&destination, &test_destination); + zassert_true(status, NULL); destination.Recipient.tag = BACNET_RECIPIENT_TAG_ADDRESS; destination.Recipient.type.address.net = 1234; @@ -99,6 +58,8 @@ static void testBACnetDestination(void) zassert_equal(apdu_len, null_len, NULL); test_len = bacnet_destination_decode(apdu, apdu_len, &test_destination); zassert_equal(test_len, apdu_len, NULL); + status = bacnet_destination_same(&destination, &test_destination); + zassert_true(status, NULL); null_len = bacnet_destination_encode(NULL, &destination); apdu_len = bacnet_destination_encode(apdu, &destination); @@ -107,12 +68,27 @@ static void testBACnetDestination(void) zassert_equal(test_len, apdu_len, NULL); test_len = bacnet_destination_decode(apdu, apdu_len, NULL); zassert_equal(test_len, apdu_len, NULL); + status = bacnet_destination_same(&destination, &test_destination); + zassert_true(status, NULL); + bacnet_destination_copy(&test_destination, &destination); + status = bacnet_destination_same(&destination, &test_destination); + zassert_true(status, NULL); /* decoding, some negative tests */ test_len = bacnet_destination_decode(NULL, apdu_len, &test_destination); zassert_equal(test_len, BACNET_STATUS_REJECT, NULL); test_len = bacnet_destination_decode(apdu, 0, &test_destination); zassert_equal(test_len, BACNET_STATUS_REJECT, NULL); + /* context */ + null_len = bacnet_destination_context_encode(NULL, 4, &destination); + apdu_len = bacnet_destination_context_encode(apdu, 4, &destination); + zassert_equal(apdu_len, null_len, NULL); + test_len = + bacnet_destination_context_decode(apdu, apdu_len, 4, &test_destination); + zassert_equal(apdu_len, test_len, NULL); + /* defaults */ + status = bacnet_destination_default(&destination); + zassert_false(status, NULL); } /** * @} @@ -163,6 +139,99 @@ static void test_BACnetDestination_ASCII(void) } } +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacnet_destination_tests, testBACnetRecipient) +#else +static void testBACnetRecipient(void) +#endif +{ + uint8_t apdu[MAX_APDU] = { 0 }; + BACNET_RECIPIENT value = { 0 }, test_value = { 0 }; + BACNET_MAC_ADDRESS mac = { .len = 1, .adr[0] = 0x01 }; + BACNET_MAC_ADDRESS adr = { .len = 1, .adr[0] = 0x02 }; + uint16_t snet = 1234; + BACNET_ADDRESS address = { 0 }; + int apdu_len = 0, null_len = 0, test_len = 0; + uint8_t tag_number = 4; + bool status = false; + + /* device */ + bacnet_recipient_device_set(&value, OBJECT_DEVICE, 123); + status = bacnet_recipient_device_valid(&value); + zassert_true(status, NULL); + bacnet_recipient_copy(&test_value, &value); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); + null_len = bacnet_recipient_encode(NULL, &value); + apdu_len = bacnet_recipient_encode(apdu, &value); + zassert_equal(apdu_len, null_len, NULL); + test_len = bacnet_recipient_decode(apdu, apdu_len, &test_value); + zassert_equal(apdu_len, test_len, NULL); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); + /* address */ + status = bacnet_address_init(&address, &mac, snet, &adr); + zassert_true(status, NULL); + bacnet_recipient_address_set(&value, &address); + bacnet_recipient_copy(&test_value, &value); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); + null_len = bacnet_recipient_encode(NULL, &value); + apdu_len = bacnet_recipient_encode(apdu, &value); + zassert_equal(apdu_len, null_len, NULL); + test_len = bacnet_recipient_decode(apdu, apdu_len, &test_value); + zassert_equal(apdu_len, test_len, NULL); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); + /* context */ + null_len = bacnet_recipient_context_encode(NULL, tag_number, &value); + apdu_len = bacnet_recipient_context_encode(apdu, tag_number, &value); + zassert_equal(apdu_len, null_len, NULL); + test_len = bacnet_recipient_context_decode( + apdu, apdu_len, tag_number, &test_value); + zassert_equal(apdu_len, test_len, NULL); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); + + bacnet_recipient_device_wildcard_set(&value); + status = bacnet_recipient_device_wildcard(&value); + zassert_true(status, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacnet_destination_tests, test_BACnetRecipient_ASCII) +#else +static void test_BACnetRecipient_ASCII(void) +#endif +{ + bool status = false; + BACNET_RECIPIENT value = { 0 }, test_value = { 0 }; + BACNET_ADDRESS address = { 0 }; + BACNET_MAC_ADDRESS mac = { .len = 1, .adr[0] = 0x01 }; + BACNET_MAC_ADDRESS adr = { .len = 1, .adr[0] = 0x02 }; + uint16_t snet = 1234; + int len = 0; + char ascii[80] = ""; + + bacnet_recipient_device_set(&value, OBJECT_DEVICE, 4194303); + len = bacnet_recipient_to_ascii(&value, ascii, sizeof(ascii)); + zassert_true(len > 0, NULL); + status = bacnet_recipient_from_ascii(&test_value, ascii); + zassert_true(status, "ascii=%s", ascii); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); + + status = bacnet_address_init(&address, &mac, snet, &adr); + zassert_true(status, NULL); + bacnet_recipient_address_set(&value, &address); + len = bacnet_recipient_to_ascii(&value, ascii, sizeof(ascii)); + zassert_true(len > 0, NULL); + status = bacnet_recipient_from_ascii(&test_value, ascii); + zassert_true(status, "ascii=%s", ascii); + status = bacnet_recipient_same(&value, &test_value); + zassert_true(status, NULL); +} + #if defined(CONFIG_ZTEST_NEW_API) ZTEST_SUITE(bacnet_destination_tests, NULL, NULL, NULL, NULL, NULL); #else @@ -170,7 +239,9 @@ void test_main(void) { ztest_test_suite( bacnet_destination_tests, ztest_unit_test(testBACnetDestination), - ztest_unit_test(test_BACnetDestination_ASCII)); + ztest_unit_test(test_BACnetDestination_ASCII), + ztest_unit_test(testBACnetRecipient), + ztest_unit_test(test_BACnetRecipient_ASCII)); ztest_run_test_suite(bacnet_destination_tests); } diff --git a/test/bacnet/bacstr/src/main.c b/test/bacnet/bacstr/src/main.c index 39b151ab..8736fe4b 100644 --- a/test/bacnet/bacstr/src/main.c +++ b/test/bacnet/bacstr/src/main.c @@ -5,9 +5,24 @@ * @date 2004 * @copyright SPDX-License-Identifier: MIT */ +#include +#include +#include +#include #include #include +/** + * @brief compare two double precision floating points to 3 decimal places + * @param x1 - first comparison value + * @param x2 - second comparison value + * @return true if the value is the same to 3 decimal points + */ +static bool is_float_equal(double x1, double x2) +{ + return fabs(x1 - x2) < 0.001; +} + /** * @addtogroup bacnet_tests * @{ @@ -120,6 +135,8 @@ static void testCharacterString(void) { BACNET_CHARACTER_STRING bacnet_string = { 0 }, bacnet_string2 = { 0 }; const char *value = "Joshua,Mary,Anna,Christopher"; + const char *utf8_value = "Joshua😍Mary😍Anna😍Christopher"; + const char *result = NULL; char test_value[MAX_APDU] = "Patricia"; char test_append_value[MAX_APDU] = " and the Kids"; char test_append_string[MAX_APDU] = ""; @@ -129,12 +146,19 @@ static void testCharacterString(void) size_t test_length = 0; size_t i = 0; - /* verify initialization */ + /* verify UTF8 initialization */ + status = characterstring_init(&bacnet_string, CHARACTER_UTF8, NULL, 0); + zassert_true(status, NULL); + zassert_equal(characterstring_length(&bacnet_string), 0, NULL); + zassert_equal( + characterstring_encoding(&bacnet_string), CHARACTER_UTF8, NULL); + /* verify ANSI initialization */ status = characterstring_init(&bacnet_string, CHARACTER_ANSI_X34, NULL, 0); zassert_true(status, NULL); zassert_equal(characterstring_length(&bacnet_string), 0, NULL); zassert_equal( characterstring_encoding(&bacnet_string), CHARACTER_ANSI_X34, NULL); + /* empty string is the same as NULL */ status = characterstring_same(&bacnet_string, NULL); zassert_true(status, NULL); @@ -156,11 +180,11 @@ static void testCharacterString(void) status = characterstring_init( &bacnet_string, CHARACTER_ANSI_X34, &test_value[0], test_length); zassert_true(status, NULL); - value = characterstring_value(&bacnet_string); + result = characterstring_value(&bacnet_string); length = characterstring_length(&bacnet_string); zassert_equal(length, test_length, NULL); for (i = 0; i < test_length; i++) { - zassert_equal(value[i], test_value[i], NULL); + zassert_equal(result[i], test_value[i], NULL); } test_length = characterstring_copy_value( test_string, sizeof(test_string), &bacnet_string); @@ -174,10 +198,10 @@ static void testCharacterString(void) test_length = strlen(test_append_string); zassert_true(status, NULL); length = characterstring_length(&bacnet_string); - value = characterstring_value(&bacnet_string); + result = characterstring_value(&bacnet_string); zassert_equal(length, test_length, NULL); for (i = 0; i < test_length; i++) { - zassert_equal(value[i], test_append_string[i], NULL); + zassert_equal(result[i], test_append_string[i], NULL); } /* init from valid ASCII string */ status = characterstring_init_ansi(&bacnet_string, value); @@ -223,6 +247,22 @@ static void testCharacterString(void) status = characterstring_ansi_copy( test_append_string, sizeof(test_append_string), &bacnet_string); zassert_equal(strncmp(value, test_append_string, strlen(value)), 0, NULL); + /* UTF-8 specific */ + status = characterstring_init_ansi(&bacnet_string, value); + zassert_true(status, NULL); + length = characterstring_length(&bacnet_string); + status = characterstring_init_ansi(&bacnet_string, utf8_value); + zassert_true(status, NULL); + zassert_equal( + characterstring_encoding(&bacnet_string), CHARACTER_UTF8, NULL); + status = characterstring_valid(&bacnet_string); + zassert_true(status, NULL); + test_length = characterstring_utf8_length(&bacnet_string); + zassert_equal( + length, test_length, "value=\"%s\" length=%d, test_length=%d", value, + length, test_length); + status = characterstring_printable(&bacnet_string); + zassert_false(status, NULL); } /** @@ -394,6 +434,17 @@ static void test_bacnet_stricmp(void) zassert_not_equal(rv, 0, NULL); rv = bacnet_stricmp(test_name_a, NULL); zassert_not_equal(rv, 0, NULL); + /* case sensitive */ + rv = bacnet_strcmp(name_a, name_a); + zassert_equal(rv, 0, NULL); + rv = bacnet_strcmp(name_a, test_name_a); + zassert_not_equal(rv, 0, NULL); + rv = bacnet_strcmp(test_name_a, test_name_b); + zassert_not_equal(rv, 0, NULL); + rv = bacnet_strcmp(NULL, test_name_b); + zassert_not_equal(rv, 0, NULL); + rv = bacnet_strcmp(test_name_a, NULL); + zassert_not_equal(rv, 0, NULL); } /** @@ -409,6 +460,18 @@ static void test_bacnet_strnicmp(void) const char *name_a = "Patricia", *test_name_a = "patricia"; const char *name_b = "CamelCase", *test_name_b = "CAMELCASE"; + /* case sensitive */ + rv = bacnet_strncmp(name_a, name_a, strlen(name_a)); + zassert_equal(rv, 0, NULL); + rv = bacnet_strncmp(name_a, test_name_a, strlen(name_a)); + zassert_not_equal(rv, 0, NULL); + rv = bacnet_strncmp(test_name_a, test_name_b, strlen(test_name_a)); + zassert_not_equal(rv, 0, NULL); + rv = bacnet_strncmp(NULL, test_name_b, strlen(test_name_b)); + zassert_not_equal(rv, 0, NULL); + rv = bacnet_strncmp(test_name_a, NULL, strlen(test_name_a)); + zassert_not_equal(rv, 0, NULL); + /* case insensitive */ rv = bacnet_strnicmp(name_a, test_name_a, strlen(name_a)); zassert_equal(rv, 0, NULL); rv = bacnet_strnicmp(name_b, test_name_b, strlen(name_b)); @@ -451,6 +514,299 @@ static void test_bacnet_strnlen(void) test_len = bacnet_strnlen(test_name, 512); zassert_equal(len, test_len, "len=%u test_len=%d", len, test_len); } + +/** + * @brief Test encode/decode API for bacnet_strto. functions + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacstr_tests, test_bacnet_strto) +#else +static void test_bacnet_strto(void) +#endif +{ + bool status; + const char *empty_string = ""; + const char *extra_text_string = "123yyx"; + const char *test_unsigned_long_string = "1234567890"; + unsigned long unsigned_long_value, test_unsigned_long_value = 1234567890; + const char *test_long_string = "-1234567890"; + long long_value, test_long_value = -1234567890; + const char *test_float_positive_string = "1.23"; + float float_value, test_float_value = 1.23f; + double double_value, test_double_value = 1.23; + long double long_double_value, test_long_double_value = 1.23L; + const char *test_float_negative_string = "-1.23"; + float float_negative_value, test_float_negative_value = -1.23f; + double double_negative_value, test_double_negative_value = -1.23; + long double long_double_negative_value, + test_long_double_negative_value = -1.23L; + char buffer[80] = "", *ascii_result = NULL; + + /* unsigned long */ + status = bacnet_strtoul(test_unsigned_long_string, &unsigned_long_value); + zassert_true(status, NULL); + zassert_equal(unsigned_long_value, test_unsigned_long_value, NULL); + status = bacnet_strtoul(empty_string, &unsigned_long_value); + zassert_false(status, NULL); + status = bacnet_strtoul(extra_text_string, &unsigned_long_value); + zassert_false(status, NULL); + ascii_result = bacnet_ultoa(unsigned_long_value, buffer, sizeof(buffer)); + zassert_equal(bacnet_strcmp(buffer, test_unsigned_long_string), 0, NULL); + zassert_equal(ascii_result, buffer, NULL); + ascii_result = bacnet_utoa(unsigned_long_value, buffer, sizeof(buffer)); + zassert_equal(bacnet_strcmp(buffer, test_unsigned_long_string), 0, NULL); + zassert_equal(ascii_result, buffer, NULL); + /* long */ + status = bacnet_strtol(test_long_string, &long_value); + zassert_true(status, NULL); + zassert_equal(long_value, test_long_value, NULL); + status = bacnet_strtol(empty_string, &long_value); + zassert_false(status, NULL); + status = bacnet_strtol(extra_text_string, &long_value); + zassert_false(status, NULL); + ascii_result = bacnet_ltoa(long_value, buffer, sizeof(buffer)); + zassert_equal(bacnet_strcmp(buffer, test_long_string), 0, NULL); + zassert_equal(ascii_result, buffer, NULL); + ascii_result = bacnet_itoa(long_value, buffer, sizeof(buffer)); + zassert_equal(bacnet_strcmp(buffer, test_long_string), 0, NULL); + zassert_equal(ascii_result, buffer, NULL); + /* single precision */ + status = bacnet_strtof(test_float_positive_string, &float_value); + zassert_true(status, NULL); + zassert_true(is_float_equal(float_value, test_float_value), NULL); + status = bacnet_strtof(test_float_negative_string, &float_negative_value); + zassert_true(status, NULL); + zassert_true( + is_float_equal(float_negative_value, test_float_negative_value), NULL); + status = bacnet_strtof(empty_string, &float_value); + zassert_false(status, NULL); + status = bacnet_strtof(extra_text_string, &float_value); + zassert_false(status, NULL); + /* double precision */ + status = bacnet_strtod(test_float_positive_string, &double_value); + zassert_true(status, NULL); + zassert_true(is_float_equal(double_value, test_double_value), NULL); + status = bacnet_strtod(test_float_negative_string, &double_negative_value); + zassert_true(status, NULL); + zassert_true( + is_float_equal(double_negative_value, test_double_negative_value), + NULL); + status = bacnet_strtod(empty_string, &double_value); + zassert_false(status, NULL); + status = bacnet_strtod(extra_text_string, &double_value); + zassert_false(status, NULL); + ascii_result = + bacnet_dtoa(double_negative_value, buffer, sizeof(buffer), 2); + zassert_equal(bacnet_strcmp(buffer, test_float_negative_string), 0, NULL); + zassert_equal(ascii_result, buffer, NULL); + /* long double precision */ + status = bacnet_strtold(test_float_positive_string, &long_double_value); + zassert_true(status, NULL); + zassert_true( + is_float_equal(long_double_value, test_long_double_value), NULL); + status = + bacnet_strtold(test_float_negative_string, &long_double_negative_value); + zassert_true(status, NULL); + zassert_true( + is_float_equal( + long_double_negative_value, test_long_double_negative_value), + NULL); + status = bacnet_strtold(empty_string, &long_double_value); + zassert_false(status, NULL); + status = bacnet_strtold(extra_text_string, &long_double_value); + zassert_false(status, NULL); +} + +/** + * @brief Test encode/decode API for bacnet_string_to_x functions + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacstr_tests, test_bacnet_string_to_x) +#else +static void test_bacnet_string_to_x(void) +#endif +{ + bool status; + const char *empty_string = ""; + const char *extra_text_string = "123yyx"; + const char *test_uint8_t_string = "123"; + const char *test_uint16_t_string = "12345"; + const char *test_uint32_t_string = "1234567890"; + const char *test_int32_t_string = "-1234567890"; + const char *test_true_string = "true"; + const char *test_false_string = "false"; + const char *test_active_string = "active"; + const char *test_inactive_string = "inactive"; + const char *test_true_numeric_string = "1"; + const char *test_false_numeric_string = "0"; + const char *test_unsigned_string = "1234567890"; + const char *test_ascii_string = "abcdefghijklmnopqrstuvwxyz"; + uint8_t uint8_t_value, test_uint8_t_value = 123; + uint16_t uint16_t_value, test_uint16_t_value = 12345; + uint32_t uint32_t_value, test_uint32_t_value = 1234567890; + int32_t int32_t_value, test_int32_t_value = -1234567890; + BACNET_UNSIGNED_INTEGER bacnet_unsigned_integer, + test_bacnet_unsigned_integer = 1234567890; + bool bool_value, test_true_value = true, test_false_value = false; + char ascii_string[80] = "", *ascii_string_result = NULL; + + /* uint8_t */ + status = bacnet_string_to_uint8(test_uint8_t_string, &uint8_t_value); + zassert_true(status, NULL); + zassert_equal(uint8_t_value, test_uint8_t_value, NULL); + status = bacnet_string_to_uint8(empty_string, &uint8_t_value); + zassert_false(status, NULL); + status = bacnet_string_to_uint8(extra_text_string, &uint8_t_value); + zassert_false(status, NULL); + /* uint16_t */ + status = bacnet_string_to_uint16(test_uint16_t_string, &uint16_t_value); + zassert_true(status, NULL); + zassert_equal(uint16_t_value, test_uint16_t_value, NULL); + status = bacnet_string_to_uint16(empty_string, &uint16_t_value); + zassert_false(status, NULL); + status = bacnet_string_to_uint16(extra_text_string, &uint16_t_value); + zassert_false(status, NULL); + /* uint32_t */ + status = bacnet_string_to_uint32(test_uint32_t_string, &uint32_t_value); + zassert_true(status, NULL); + zassert_equal(uint32_t_value, test_uint32_t_value, NULL); + status = bacnet_string_to_uint32(empty_string, &uint32_t_value); + zassert_false(status, NULL); + status = bacnet_string_to_uint32(extra_text_string, &uint32_t_value); + zassert_false(status, NULL); + /* int32_t */ + status = bacnet_string_to_int32(test_int32_t_string, &int32_t_value); + zassert_true(status, NULL); + zassert_equal(int32_t_value, test_int32_t_value, NULL); + status = bacnet_string_to_int32(empty_string, &int32_t_value); + zassert_false(status, NULL); + /* bool */ + status = bacnet_string_to_bool(test_true_string, &bool_value); + zassert_true(status, NULL); + zassert_equal(bool_value, test_true_value, NULL); + status = bacnet_string_to_bool(test_false_string, &bool_value); + zassert_true(status, NULL); + zassert_equal(bool_value, test_false_value, NULL); + status = bacnet_string_to_bool(empty_string, &bool_value); + zassert_false(status, NULL); + status = bacnet_string_to_bool(extra_text_string, &bool_value); + zassert_false(status, NULL); + /* active/inactive */ + status = bacnet_string_to_bool(test_active_string, &bool_value); + zassert_true(status, NULL); + zassert_equal(bool_value, test_true_value, NULL); + status = bacnet_string_to_bool(test_inactive_string, &bool_value); + zassert_true(status, NULL); + zassert_equal(bool_value, test_false_value, NULL); + status = bacnet_string_to_bool(empty_string, &bool_value); + zassert_false(status, NULL); + /* 0/1 */ + status = bacnet_string_to_bool(test_true_numeric_string, &bool_value); + zassert_true(status, NULL); + zassert_equal(bool_value, test_true_value, NULL); + status = bacnet_string_to_bool(test_false_numeric_string, &bool_value); + zassert_true(status, NULL); + zassert_equal(bool_value, test_false_value, NULL); + /* bacnet_unsigned_integer */ + status = bacnet_string_to_unsigned( + test_unsigned_string, &bacnet_unsigned_integer); + zassert_true(status, NULL); + zassert_equal(bacnet_unsigned_integer, test_bacnet_unsigned_integer, NULL); + status = bacnet_string_to_unsigned(empty_string, &bacnet_unsigned_integer); + zassert_false(status, NULL); + status = + bacnet_string_to_unsigned(extra_text_string, &bacnet_unsigned_integer); + zassert_false(status, NULL); + /* ascii string */ + ascii_string_result = bacnet_snprintf_to_ascii( + ascii_string, sizeof(ascii_string), "%s", test_ascii_string); + zassert_equal( + bacnet_strcmp(ascii_string_result, test_ascii_string), 0, NULL); +} + +/** + * @brief Test encode/decode API for bacnet trim functions + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacstr_tests, test_bacnet_string_trim) +#else +static void test_bacnet_string_trim(void) +#endif +{ + char trim_left[80] = " abcdefg", *trim_left_result = NULL; + char trim_right[80] = "abcdefg ", *trim_right_result = NULL; + char trim_both[80] = " abcdefg ", *trim_both_result = NULL; + char *trim_test_value = "abcdefg"; + char *empty_string = ""; + + trim_left_result = bacnet_ltrim(trim_left, " "); + trim_right_result = bacnet_rtrim(trim_right, " "); + trim_both_result = bacnet_trim(trim_both, " "); + zassert_equal(bacnet_strcmp(trim_left_result, trim_test_value), 0, NULL); + zassert_equal(bacnet_strcmp(trim_right_result, trim_test_value), 0, NULL); + zassert_equal(bacnet_strcmp(trim_both_result, trim_test_value), 0, NULL); + trim_left_result = bacnet_ltrim(empty_string, " "); + trim_right_result = bacnet_rtrim(empty_string, " "); + trim_both_result = bacnet_trim(empty_string, " "); + zassert_equal(bacnet_strcmp(trim_left_result, empty_string), 0, NULL); + zassert_equal(bacnet_strcmp(trim_right_result, empty_string), 0, NULL); + zassert_equal(bacnet_strcmp(trim_both_result, empty_string), 0, NULL); +} + +/** + * @brief Test bacnet_stptok string tokenizer + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacstr_tests, test_bacnet_stptok) +#else +static void test_bacnet_stptok(void) +#endif +{ + char *pCmd = "I Love You\r\n"; + char token[80] = ""; + + pCmd = bacnet_stptok(pCmd, token, sizeof(token), " \r\n"); + + zassert_true(bacnet_strcmp(token, "I") == 0, NULL); + zassert_true(bacnet_strcmp(pCmd, "Love You\r\n") == 0, NULL); + + pCmd = bacnet_stptok(pCmd, token, sizeof(token), " \r\n"); + + zassert_true(bacnet_strcmp(token, "Love") == 0, NULL); + zassert_true(bacnet_strcmp(pCmd, "You\r\n") == 0, NULL); + + pCmd = bacnet_stptok(pCmd, token, sizeof(token), " \r\n"); + + zassert_true(bacnet_strcmp(token, "You") == 0, NULL); + zassert_true(pCmd == NULL, NULL); +} + +/** + * @brief Test encode/decode API for bacnet snprintf and shift functions + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bacstr_tests, test_bacnet_snprintf) +#else +static void test_bacnet_snprintf(void) +#endif +{ + int buf_len = 0, str_len; + int i; + char str[30] = ""; + + str_len = sizeof(str); + for (i = 0; i < 5; i++) { + /* appending formatted strings */ + buf_len = bacnet_snprintf(str, str_len, buf_len, "{"); + buf_len = + bacnet_snprintf(str, str_len, buf_len, "REALLY BIG STRING BASS"); + buf_len = bacnet_snprintf(str, str_len, buf_len, "}"); + } + zassert_equal(buf_len, str_len, "buf_len=%d str_len=%d", buf_len, str_len); + zassert_equal( + str[buf_len - 1], 0, "str[%d]=%c", buf_len - 1, str[buf_len - 1]); +} + /** * @} */ @@ -465,8 +821,12 @@ void test_main(void) ztest_unit_test(testCharacterString), ztest_unit_test(testOctetString), ztest_unit_test(test_bacnet_stricmp), ztest_unit_test(test_bacnet_strnicmp), - ztest_unit_test(test_bacnet_strnlen)); - + ztest_unit_test(test_bacnet_strnlen), + ztest_unit_test(test_bacnet_strto), + ztest_unit_test(test_bacnet_string_to_x), + ztest_unit_test(test_bacnet_string_trim), + ztest_unit_test(test_bacnet_stptok), + ztest_unit_test(test_bacnet_snprintf)); ztest_run_test_suite(bacstr_tests); } #endif diff --git a/test/bacnet/basic/bbmd/CMakeLists.txt b/test/bacnet/basic/bbmd/CMakeLists.txt index de08cc49..b1dd71f5 100644 --- a/test/bacnet/basic/bbmd/CMakeLists.txt +++ b/test/bacnet/basic/bbmd/CMakeLists.txt @@ -34,10 +34,12 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacaddr.c ${SRC_DIR}/bacnet/bacdcode.c ${SRC_DIR}/bacnet/bacint.c - ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/iam.c + ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/npdu.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/bbmd6/CMakeLists.txt b/test/bacnet/basic/bbmd6/CMakeLists.txt index 4191dd48..b0fe1d25 100644 --- a/test/bacnet/basic/bbmd6/CMakeLists.txt +++ b/test/bacnet/basic/bbmd6/CMakeLists.txt @@ -35,9 +35,11 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacaddr.c ${SRC_DIR}/bacnet/bacdcode.c ${SRC_DIR}/bacnet/bacint.c - ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/iam.c + ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/npdu.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/bzll/CMakeLists.txt b/test/bacnet/basic/bzll/CMakeLists.txt index 5b6d457f..3611a402 100644 --- a/test/bacnet/basic/bzll/CMakeLists.txt +++ b/test/bacnet/basic/bzll/CMakeLists.txt @@ -33,9 +33,11 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacaddr.c ${SRC_DIR}/bacnet/bacdcode.c ${SRC_DIR}/bacnet/bacint.c - ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/iam.c + ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/npdu.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/datalink/dlmstp/CMakeLists.txt b/test/bacnet/datalink/dlmstp/CMakeLists.txt index 90e2a62e..dd90d9d4 100644 --- a/test/bacnet/datalink/dlmstp/CMakeLists.txt +++ b/test/bacnet/datalink/dlmstp/CMakeLists.txt @@ -40,8 +40,10 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacint.c ${SRC_DIR}/bacnet/bacreal.c ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/ringbuf.c ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/npdu.c # Test and test library files ./src/main.c diff --git a/test/bacnet/datalink/mstp/CMakeLists.txt b/test/bacnet/datalink/mstp/CMakeLists.txt index 335bd4c4..d5df9a93 100644 --- a/test/bacnet/datalink/mstp/CMakeLists.txt +++ b/test/bacnet/datalink/mstp/CMakeLists.txt @@ -45,8 +45,10 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacaddr.c ${SRC_DIR}/bacnet/bacdcode.c ${SRC_DIR}/bacnet/bacint.c - ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/npdu.c ${SRC_DIR}/bacnet/basic/sys/bigend.c # Support files and stubs (pathname alphabetical) diff --git a/test/bacnet/npdu/CMakeLists.txt b/test/bacnet/npdu/CMakeLists.txt index 527d61e1..7bcdc323 100644 --- a/test/bacnet/npdu/CMakeLists.txt +++ b/test/bacnet/npdu/CMakeLists.txt @@ -42,11 +42,13 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacint.c ${SRC_DIR}/bacnet/bacreal.c ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/service/h_apdu.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/tsm/tsm.c ${SRC_DIR}/bacnet/dcc.c + ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/proplist.c ${SRC_DIR}/bacnet/reject.c ${SRC_DIR}/bacnet/rp.c