/** * @file * @brief BACnetDestination complex data type encode and decode * @author Steve Karg * @date December 2022 * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 */ #include #include #include #include #include #include #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ #include "bacnet/bacaddr.h" #include "bacnet/bacapp.h" #include "bacnet/bacdcode.h" #include "bacnet/bacdest.h" #include "bacnet/bactext.h" #include "bacnet/basic/binding/address.h" /** * @brief Initialize the BACnetDestination data structure with defaults * * BACnetDestination ::= SEQUENCE { * valid-days BACnetDaysOfWeek, * from-time Time, * to-time Time, * recipient BACnetRecipient, * process-identifier Unsigned32, * issue-confirmed-notifications BOOLEAN, * transitions BACnetEventTransitionBits * } * * @param destination BACnetDestination to be initialized */ void bacnet_destination_default_init(BACNET_DESTINATION *destination) { unsigned i; if (!destination) { return; } /* configure for every day, all day long */ for (i = 0; i < MAX_BACNET_DAYS_OF_WEEK; i++) { bitstring_set_bit(&destination->ValidDays, i, true); } datetime_set_time(&destination->FromTime, 0, 0, 0, 0); datetime_set_time(&destination->ToTime, 23, 59, 59, 99); /* initialize Recipient to *wildcard* device instance - invalid! */ destination->Recipient.tag = BACNET_RECIPIENT_TAG_DEVICE; destination->Recipient.type.device.type = OBJECT_DEVICE; destination->Recipient.type.device.instance = BACNET_MAX_INSTANCE; destination->ProcessIdentifier = 0; destination->ConfirmedNotify = false; bitstring_set_bit( &destination->Transitions, TRANSITION_TO_OFFNORMAL, false); bitstring_set_bit(&destination->Transitions, TRANSITION_TO_FAULT, false); bitstring_set_bit(&destination->Transitions, TRANSITION_TO_NORMAL, false); } /** * @brief Compare the BACnetRecipient complex data of r1 and r2 * @param r1 - BACnetRecipient 1 structure * @param r2 - BACnetRecipient 2 structure * @return true if r1 and r2 are the same */ bool bacnet_recipient_same( const BACNET_RECIPIENT *r1, const BACNET_RECIPIENT *r2) { bool status = false; if (r1 && r2) { if (r1->tag == r2->tag) { status = true; } if (status) { if (r1->tag == BACNET_RECIPIENT_TAG_DEVICE) { if ((r1->type.device.type == r2->type.device.type) && (r1->type.device.instance == r2->type.device.instance)) { status = true; } } else if (r1->tag == BACNET_RECIPIENT_TAG_ADDRESS) { status = bacnet_address_net_same( &r1->type.address, &r2->type.address); } else { status = false; } } } return status; } /** * @brief Set the BACnetRecipient complex data to a device * @param dest - BACnetRecipient structure * @param object_type - BACnetObjectType enumeration value * @param instance - BACnetObjectInstance value */ void bacnet_recipient_device_set( BACNET_RECIPIENT *dest, BACNET_OBJECT_TYPE object_type, uint32_t instance) { if (dest) { dest->tag = BACNET_RECIPIENT_TAG_DEVICE; dest->type.device.type = object_type; dest->type.device.instance = instance; } } /** * @brief Set the BACnetRecipient complex data to an address * @param dest - BACnetRecipient structure * @param address - BACnetAddress structure */ void bacnet_recipient_address_set( BACNET_RECIPIENT *dest, const BACNET_ADDRESS *address) { if (dest && address) { dest->tag = BACNET_RECIPIENT_TAG_ADDRESS; bacnet_address_copy(&dest->type.address, address); } } /** * @brief Inspect the BACnetRecipient data structure for valid router address * @param recipient - BACnetRecipient structure * @return true if BACnetRecipient is a valid address */ bool bacnet_recipient_address_router_unknown(const BACNET_RECIPIENT *recipient) { bool status = false; if (recipient) { if ((recipient->tag == BACNET_RECIPIENT_TAG_ADDRESS) && (recipient->type.address.net != 0) && (recipient->type.address.net != BACNET_BROADCAST_NETWORK) && (recipient->type.address.len != 0) && (recipient->type.address.mac_len == 0)) { status = true; } } return status; } /** * @brief Copy the BACnetRecipient complex data from src to dest * @param src - BACnetRecipient 1 structure * @param dest - BACnetRecipient 2 structure */ void bacnet_recipient_copy(BACNET_RECIPIENT *dest, const BACNET_RECIPIENT *src) { if (dest && src) { memmove(dest, src, sizeof(BACNET_RECIPIENT)); } } /** * @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 * @return true if BACnetRecipient is equal to the device object wildcard */ bool bacnet_recipient_device_wildcard(const BACNET_RECIPIENT *recipient) { bool status = false; if (recipient) { if ((recipient->tag == BACNET_RECIPIENT_TAG_DEVICE) && (recipient->type.device.type == OBJECT_DEVICE) && (recipient->type.device.instance == BACNET_MAX_INSTANCE)) { status = true; } } return status; } /** * @brief Compare the BACnetRecipient data structure to a valid device * @param recipient - BACnetRecipient structure * @return true if BACnetRecipient is a valid device object instance */ bool bacnet_recipient_device_valid(const BACNET_RECIPIENT *recipient) { bool status = false; if (recipient) { if ((recipient->tag == BACNET_RECIPIENT_TAG_DEVICE) && (recipient->type.device.type == OBJECT_DEVICE) && (recipient->type.device.instance < BACNET_MAX_INSTANCE)) { status = true; } } return status; } /** * @brief Compare the BACnetDestination complex data of dest1 and dest2 * @param d1 - BACnetDestination 1 structure * @param d2 - BACnetDestination 2 structure * @return true if dest1 and dest2 are the same */ bool bacnet_destination_same( const BACNET_DESTINATION *d1, const BACNET_DESTINATION *d2) { bool status = false; if (d1 && d2) { status = bitstring_same(&d1->ValidDays, &d2->ValidDays); if (status) { status = (datetime_compare_time(&d1->FromTime, &d2->FromTime) == 0); } if (status) { status = (datetime_compare_time(&d1->ToTime, &d2->ToTime) == 0); } if (status) { status = bacnet_recipient_same(&d1->Recipient, &d2->Recipient); } if (status) { status = (d1->ProcessIdentifier == d2->ProcessIdentifier); } if (status) { status = (d1->ConfirmedNotify == d2->ConfirmedNotify); } if (status) { status = bitstring_same(&d1->Transitions, &d2->Transitions); } } return status; } /** * @brief Copy the BACnetDestination complex data from src to dest * @param dest - BACnetDestination 1 structure * @param src - BACnetDestination 2 structure */ void bacnet_destination_copy( BACNET_DESTINATION *dest, const BACNET_DESTINATION *src) { if (dest && src) { memmove(dest, src, sizeof(BACNET_DESTINATION)); } } /** * @brief Compare the BACnetDestination data structure to defaults * @param d1 - BACnetDestination 1 structure * @return true if d1 and d2 (defaults) are the same */ bool bacnet_destination_default(const BACNET_DESTINATION *d1) { BACNET_DESTINATION d2 = { 0 }; bacnet_destination_default_init(&d2); return bacnet_destination_same(d1, &d2); } /** * @brief Encode the BACnetDestination complex data * * BACnetDestination ::= SEQUENCE { * valid-days BACnetDaysOfWeek, * from-time Time, * to-time Time, * recipient BACnetRecipient, * process-identifier Unsigned32, * issue-confirmed-notifications BOOLEAN, * transitions BACnetEventTransitionBits * } * * @param apdu Pointer to the buffer for encoding. * @param destination Pointer to the property data to be encoded. * * @return bytes encoded or zero on error. */ int bacnet_destination_encode( uint8_t *apdu, const BACNET_DESTINATION *destination) { int apdu_len = 0, len = 0; if (destination->Recipient.tag < BACNET_RECIPIENT_TAG_MAX) { len = encode_application_bitstring(apdu, &destination->ValidDays); apdu_len += len; if (apdu) { apdu += len; } len = encode_application_time(apdu, &destination->FromTime); apdu_len += len; if (apdu) { apdu += len; } len = encode_application_time(apdu, &destination->ToTime); apdu_len += len; if (apdu) { apdu += len; } len = bacnet_recipient_encode(apdu, &destination->Recipient); apdu_len += len; if (apdu) { apdu += len; } /* Process Identifier - Unsigned32 */ len = encode_application_unsigned(apdu, destination->ProcessIdentifier); apdu_len += len; if (apdu) { apdu += len; } /* Issue Confirmed Notifications - boolean */ len = encode_application_boolean(apdu, destination->ConfirmedNotify); apdu_len += len; if (apdu) { apdu += len; } /* Transitions - BACnet Event Transition Bits [bitstring] */ len = encode_application_bitstring(apdu, &destination->Transitions); apdu_len += len; } return apdu_len; } /** * @brief Encode a BACnetDestination complex data type * @param apdu - the APDU buffer * @param tag_number - context tag number * @param destination Pointer to the property data to be encoded. * @return length of the APDU buffer, or 0 if not able to encode */ int bacnet_destination_context_encode( uint8_t *apdu, uint8_t tag_number, const BACNET_DESTINATION *destination) { int len = 0; int apdu_len = 0; if (destination) { len = encode_opening_tag(apdu, tag_number); apdu_len += len; if (apdu) { apdu += len; } len = bacnet_destination_encode(apdu, destination); apdu_len += len; if (apdu) { apdu += len; } len = encode_closing_tag(apdu, tag_number); apdu_len += len; } return apdu_len; } /** * @brief Decode the BACnetDestination complex data * * BACnetDestination ::= SEQUENCE { * valid-days BACnetDaysOfWeek, * from-time Time, * to-time Time, * recipient BACnetRecipient, * process-identifier Unsigned32, * issue-confirmed-notifications BOOLEAN, * transitions BACnetEventTransitionBits * } * * @param apdu Pointer to the buffer for decoding. * @param apdu_size Count of valid bytes in the buffer. * @param destination Pointer to the property data to be encoded. * * @return bytes encoded or #BACNET_STATUS_REJECT on error. */ int bacnet_destination_decode( const uint8_t *apdu, int apdu_size, BACNET_DESTINATION *destination) { int len = 0, apdu_len = 0; BACNET_BIT_STRING bitstring = { 0 }; BACNET_TIME btime = { 0 }; BACNET_RECIPIENT recipient = { 0 }; BACNET_UNSIGNED_INTEGER unsigned_value = 0; bool boolean_value = false; if (!apdu) { return BACNET_STATUS_REJECT; } /* Decode Valid Days */ len = bacnet_bitstring_application_decode( &apdu[apdu_len], apdu_size - apdu_len, &bitstring); if (len <= 0) { return BACNET_STATUS_REJECT; } if (destination) { bitstring_copy(&destination->ValidDays, &bitstring); } apdu_len += len; /* Decode From Time */ len = bacnet_time_application_decode( &apdu[apdu_len], apdu_size - apdu_len, &btime); if (len <= 0) { return BACNET_STATUS_REJECT; } if (destination) { datetime_copy_time(&destination->FromTime, &btime); } apdu_len += len; /* Decode To Time */ len = bacnet_time_application_decode( &apdu[apdu_len], apdu_size - apdu_len, &btime); if (len <= 0) { return BACNET_STATUS_REJECT; } if (destination) { datetime_copy_time(&destination->ToTime, &btime); } apdu_len += len; /* Recipient */ len = bacnet_recipient_decode( &apdu[apdu_len], apdu_size - apdu_len, &recipient); if (len < 0) { return BACNET_STATUS_REJECT; } if (destination) { bacnet_recipient_copy(&destination->Recipient, &recipient); } apdu_len += len; /* Process Identifier */ len = bacnet_unsigned_application_decode( &apdu[apdu_len], apdu_size - apdu_len, &unsigned_value); if (len <= 0) { return BACNET_STATUS_REJECT; } if (destination) { destination->ProcessIdentifier = unsigned_value; } apdu_len += len; /* Issue Confirmed Notifications */ len = bacnet_boolean_application_decode( &apdu[apdu_len], apdu_size - apdu_len, &boolean_value); if (len <= 0) { return BACNET_STATUS_REJECT; } if (destination) { destination->ConfirmedNotify = boolean_value; } apdu_len += len; /* Transitions */ len = bacnet_bitstring_application_decode( &apdu[apdu_len], apdu_size - apdu_len, &bitstring); if (len <= 0) { return BACNET_STATUS_REJECT; } if (destination) { bitstring_copy(&destination->Transitions, &bitstring); } apdu_len += len; 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 * * BACnetRecipient ::= CHOICE { * device [0] BACnetObjectIdentifier, * address [1] BACnetAddress * } * * @param apdu Pointer to the buffer for encoding. * @param recipient Pointer to the property data to be encoded. * * @return bytes encoded or zero on error. */ int bacnet_recipient_encode(uint8_t *apdu, const BACNET_RECIPIENT *recipient) { int apdu_len = 0, len = 0; if (recipient->tag == BACNET_RECIPIENT_TAG_DEVICE) { len = encode_context_object_id( apdu, 0, OBJECT_DEVICE, recipient->type.device.instance); apdu_len += len; } else if (recipient->tag == BACNET_RECIPIENT_TAG_ADDRESS) { /* opening tag 1 */ len = encode_opening_tag(apdu, 1); apdu_len += len; if (apdu) { apdu += len; } len = encode_bacnet_address(apdu, &recipient->type.address); apdu_len += len; if (apdu) { apdu += len; } /* closing tag 1 */ len = encode_closing_tag(apdu, 1); apdu_len += len; } return apdu_len; } /** * @brief Encode a list of BACnetRecipient complex data types * @param apdu Pointer to the buffer for encoding. * @param list_head Pointer to the head of the linked list of BACnetRecipient * @return bytes encoded or zero if nothing is encoded */ int bacnet_recipient_list_encode( uint8_t *apdu, BACNET_RECIPIENT_LIST *list_head) { int apdu_len = 0, len = 0; BACNET_RECIPIENT_LIST *list_entry; if (!list_head) { /* encoded nothing */ return 0; } /* how big? */ list_entry = list_head; while (list_entry != NULL) { len = bacnet_recipient_encode(apdu, &list_entry->recipient); apdu_len += len; if (apdu) { apdu += len; } list_entry = list_entry->next; } return apdu_len; } /** * @brief Convert an array of BACnetRecipient to linked list * @param array pointer to element zero of the array * @param size number of elements in the array */ void bacnet_recipient_list_link_array(BACNET_RECIPIENT_LIST *array, size_t size) { size_t i = 0; for (i = 0; i < size; i++) { if (i > 0) { array[i - 1].next = &array[i]; } array[i].next = NULL; } } /** * @brief Encode a BACnetRecipient complex data type * @param apdu - the APDU buffer * @param tag_number - context tag number * @param recipient Pointer to the property data to be encoded. * @return length of the APDU buffer, or 0 if not able to encode */ int bacnet_recipient_context_encode( uint8_t *apdu, uint8_t tag_number, const BACNET_RECIPIENT *recipient) { int len = 0; int apdu_len = 0; if (recipient) { len = encode_opening_tag(apdu, tag_number); apdu_len += len; if (apdu) { apdu += len; } len = bacnet_recipient_encode(apdu, recipient); apdu_len += len; if (apdu) { apdu += len; } len = encode_closing_tag(apdu, tag_number); apdu_len += len; } return apdu_len; } /** * @brief Decode the BACnetRecipient complex data * * BACnetRecipient ::= CHOICE { * device [0] BACnetObjectIdentifier, * address [1] BACnetAddress * } * * @param apdu Pointer to the buffer for decoding. * @param apdu_size Count of valid bytes in the buffer. * @param recipient Pointer to the property data to be decoded, or NULL for * decoding to determine the length. * * @return bytes encoded or #BACNET_STATUS_REJECT on error. */ int bacnet_recipient_decode( const uint8_t *apdu, int apdu_size, BACNET_RECIPIENT *recipient) { int len = 0, apdu_len = 0; BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE; uint32_t instance = 0; BACNET_ADDRESS address; if (!apdu) { return BACNET_STATUS_REJECT; } /* device [0] BACnetObjectIdentifier */ len = bacnet_object_id_context_decode( &apdu[apdu_len], apdu_size - apdu_len, BACNET_RECIPIENT_TAG_DEVICE, &object_type, &instance); if (len > 0) { if (object_type != OBJECT_DEVICE) { return BACNET_STATUS_REJECT; } if (recipient) { recipient->tag = BACNET_RECIPIENT_TAG_DEVICE; recipient->type.device.type = object_type; recipient->type.device.instance = instance; } apdu_len += len; } else { len = bacnet_address_context_decode( &apdu[apdu_len], apdu_size - apdu_len, BACNET_RECIPIENT_TAG_ADDRESS, &address); if (len > 0) { if (recipient) { recipient->tag = BACNET_RECIPIENT_TAG_ADDRESS; bacnet_address_copy(&recipient->type.address, &address); } apdu_len += len; } else { return BACNET_STATUS_REJECT; } } return apdu_len; } /** * @brief Decode a time stamp and check for opening and closing tags. * @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_recipient_context_decode( const uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, BACNET_RECIPIENT *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_recipient_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; } /** * Convert BACnet_Destination to ASCII for printing * * Output format: * * ( * ValidDays=[1,2,5,6,7]; * FromTime=0:00:00.0; * ToTime=23:59:59.9; * Recipient=Device(type=8,instance=15); * ProcessIdentifier=0; * ConfirmedNotify=false; * Transitions=[to-offnormal,to-fault,to-normal] * ) * * - ValidDays ... array of numbers, 1=Mon through 7=Sun * - FromTime, ToTime ... HH:MM:SS.s * - Recipient ... two variants: Recipient=Device(type=8,instance=15) or * Recipient=Address(net=1234,mac=c0:a8:00:0f) * - type ... bacnet object type enum * - instance ... bacnet object instance * - net ... bacnet network number * - mac ... bacnet MAC address; can be separated by colons or periods. * - ProcessIdentifier ... 32bit unsigned int, process ID * - ConfirmedNotify ... true or false * - Transitions ... array with any of the three items: to-offnormal, to-fault, * to-normal * * @param bacdest - Destination 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_destination_to_ascii( const BACNET_DESTINATION *bacdest, char *buf, size_t buf_size) { int offset = 0; bool comma; int i; offset = bacnet_snprintf(buf, buf_size, offset, "("); /* BACnetDaysOfWeek ::= BIT STRING { monday (0), tuesday (1), wednesday (2), thursday (3), friday (4), saturday (5), sunday (6) } */ /* Use numbers 1-7 (ISO 8601) */ offset = bacnet_snprintf(buf, buf_size, offset, "ValidDays=["); comma = false; for (i = 0; i < 7; i++) { if (bitstring_bit(&bacdest->ValidDays, i)) { if (comma) { offset = bacnet_snprintf(buf, buf_size, offset, ","); } offset = bacnet_snprintf(buf, buf_size, offset, "%d", i + 1); comma = true; } } 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); 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) { offset = bacnet_snprintf( buf, buf_size, offset, "Device(type=%d,instance=%lu)", bacdest->Recipient.type.device.type, (unsigned long)bacdest->Recipient.type.device.instance); } else { /* BACnetAddress ::= SEQUENCE { network-number Unsigned16, -- A value of 0 indicates the local network mac-address OCTET STRING -- A string of length 0 indicates a broadcast } */ offset = bacnet_snprintf( buf, buf_size, offset, "Address(net=%d,mac=", bacdest->Recipient.type.address.net); /* 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) { offset = bacnet_snprintf(buf, buf_size, offset, ":"); } offset = bacnet_snprintf( buf, buf_size, offset, "%02x", bacdest->Recipient.type.address.mac[i]); } offset = bacnet_snprintf(buf, buf_size, offset, ")"); } offset = bacnet_snprintf(buf, buf_size, offset, ";"); offset = bacnet_snprintf( buf, buf_size, offset, "ProcessIdentifier=%lu;", (unsigned long)bacdest->ProcessIdentifier); offset = bacnet_snprintf( buf, buf_size, offset, "ConfirmedNotify=%s;", bacdest->ConfirmedNotify ? "true" : "false"); /* BACnetEventTransitionBits ::= BIT STRING { to-offnormal (0), to-fault (1), to-normal (2) } */ offset = bacnet_snprintf(buf, buf_size, offset, "Transitions=["); comma = false; if (bitstring_bit(&bacdest->Transitions, TRANSITION_TO_OFFNORMAL)) { offset = bacnet_snprintf(buf, buf_size, offset, "to-offnormal"); comma = true; } if (bitstring_bit(&bacdest->Transitions, TRANSITION_TO_FAULT)) { if (comma) { offset = bacnet_snprintf(buf, buf_size, offset, ","); } offset = bacnet_snprintf(buf, buf_size, offset, "to-fault"); comma = true; } if (bitstring_bit(&bacdest->Transitions, TRANSITION_TO_NORMAL)) { if (comma) { offset = bacnet_snprintf(buf, buf_size, offset, ","); } offset = bacnet_snprintf(buf, buf_size, offset, "to-normal"); } offset = bacnet_snprintf(buf, buf_size, offset, "])"); return offset; } /** * Parse BACnet_Destination from ASCII string (as entered by user) * * @param bacdest - Destination struct to populate with data from the ASCII * string * @param buf - ASCII string, zero terminated * @return true on success */ bool bacnet_destination_from_ascii(BACNET_DESTINATION *bacdest, const char *buf) { enum ParsePhase { PH_START, PH_PAIR_SPACER, PH_KEYWORD, PH_VALUE_SPACER, PH_VALUE }; enum ParseKeyword { KW_ValidDays = 0, KW_FromTime, KW_ToTime, KW_Recipient, KW_ProcessIdentifier, KW_ConfirmedNotify, KW_Transitions, KW_MAX }; enum ParsePhase ph; size_t buflen; size_t toklen; size_t pos; char c; int i; int j; int _number_i; size_t _must_consume_tmplen; uint32_t tmp; enum ParseKeyword kw = 0; BACNET_TIME *ptime; BACNET_MAC_ADDRESS tmpmac; static const char *KW_LOOKUP[] = { "ValidDays", "FromTime", "ToTime", "Recipient", "ProcessIdentifier", "ConfirmedNotify", "Transitions", }; if (bacdest == NULL || buf == NULL) { return false; } bacnet_destination_default_init(bacdest); /* Helper macros to simplify the parser ... */ /* true if the character is whitespace */ #define ISWHITE(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n') /* Discard characters while they match a given test. Goes to parse_end on NUL. * ctest is a boolean expression where c is the tested character */ #define DISCARD_WHILE(ctest) \ do { \ while (1) { \ c = buf[pos]; \ if (c == 0) { \ goto parse_end; \ } \ if ((ctest)) { \ pos++; \ continue; \ } \ break; \ } \ } while (0) /* Discard all whitespace. Goes to parse_end on NUL. */ #define DISCARD_WHITESPACE() DISCARD_WHILE(ISWHITE(c)) /* Must consume a given word; return false otherwise. */ #define MUST_CONSUME(s) \ do { \ _must_consume_tmplen = strlen(s); \ if (0 == strncmp(&buf[pos], s, _must_consume_tmplen)) { \ pos += _must_consume_tmplen; \ } else { \ return false; \ } \ } while (0) /* Collect a decimal number and store the result into tmp; stop on a non-digit. * Clobbers "c" and "tmp". TODO replace with strtol? */ #define COLLECT_NUMBER_TMP(maxdigits) \ do { \ tmp = 0; \ for (_number_i = 0; _number_i < (maxdigits); _number_i++) { \ c = buf[pos]; \ if (c >= '0' && c <= '9') { \ tmp = (tmp * 10) + (c - '0'); \ pos++; \ } else { \ break; \ } \ } \ } while (0) /* Go through all key=value pieces in the string */ buflen = strlen(buf); ph = PH_START; pos = 0; while (pos < buflen) { switch (ph) { case PH_START: /* Expect the outer opening paren */ DISCARD_WHITESPACE(); MUST_CONSUME("("); ph = PH_KEYWORD; break; case PH_PAIR_SPACER: /* Expect end of string, or semicolon */ DISCARD_WHILE(c == ')' || c == ']' || ISWHITE(c)); MUST_CONSUME(";"); DISCARD_WHITESPACE(); ph = PH_KEYWORD; break; case PH_KEYWORD: /* Key */ DISCARD_WHITESPACE(); for (i = 0; i < KW_MAX; i++) { toklen = strlen(KW_LOOKUP[i]); if (0 == strncmp(&buf[pos], KW_LOOKUP[i], toklen)) { /* kw matched */ kw = i; pos += toklen; ph = PH_VALUE_SPACER; break; } } if (ph != PH_VALUE_SPACER) { /* Invalid token? */ return false; } break; case PH_VALUE_SPACER: /* Equals between key and value, also consuming opening square bracket if present. */ DISCARD_WHITESPACE(); MUST_CONSUME("="); DISCARD_WHILE(c == '[' || ISWHITE(c)); ph = PH_VALUE; break; case PH_VALUE: /* Parse the value */ switch (kw) { case KW_ValidDays: /* Clear all weekdays */ for (i = 0; i < MAX_BACNET_DAYS_OF_WEEK; i++) { bitstring_set_bit(&bacdest->ValidDays, i, false); } j = 0; /* 0 = number, 1 = comma */ do { DISCARD_WHITESPACE(); c = buf[pos]; if (c == 0) { goto parse_end; } if (c == ']') { pos++; /* end of numbers */ break; } if (j == 0) { if (c >= '1' && c <= '7') { bitstring_set_bit( &bacdest->ValidDays, c - '1', true); pos++; j = 1; } else { return false; } } else { MUST_CONSUME(","); j = 0; } } while (1); break; case KW_FromTime: case KW_ToTime: DISCARD_WHITESPACE(); if (kw == KW_FromTime) { ptime = &bacdest->FromTime; } else { ptime = &bacdest->ToTime; } /* TODO implemented in bacapp_parse_application_data - * extract & reuse? */ /* Hour */ COLLECT_NUMBER_TMP(2); ptime->hour = tmp; MUST_CONSUME(":"); /* Min */ COLLECT_NUMBER_TMP(2); ptime->min = tmp; if (buf[pos] == ':') { /* have seconds */ MUST_CONSUME(":"); /* Sec */ COLLECT_NUMBER_TMP(2); ptime->sec = tmp; /* ? hundredths */ c = buf[pos]; if (c == '.') { pos++; COLLECT_NUMBER_TMP(2); ptime->hundredths = tmp; } else { ptime->hundredths = 0; } } else { ptime->sec = 0; ptime->hundredths = 0; } break; case KW_ProcessIdentifier: DISCARD_WHITESPACE(); /* Collect number */ COLLECT_NUMBER_TMP(10); bacdest->ProcessIdentifier = tmp; break; case KW_ConfirmedNotify: DISCARD_WHITESPACE(); if (0 == strncmp(&buf[pos], "true", 4)) { bacdest->ConfirmedNotify = true; pos += 4; } else if (0 == strncmp(&buf[pos], "false", 5)) { bacdest->ConfirmedNotify = false; pos += 5; } else { return false; } break; case KW_Transitions: /* Clear all transitions */ for (i = 0; i < MAX_BACNET_EVENT_TRANSITION; i++) { bitstring_set_bit(&bacdest->Transitions, i, false); } j = 0; /* 0 = value, 1 = comma */ do { DISCARD_WHITESPACE(); c = buf[pos]; if (c == 0) { goto parse_end; } if (c == ']') { pos++; break; } if (j == 0) { if (0 == strncmp(&buf[pos], "to-offnormal", 12)) { bitstring_set_bit( &bacdest->Transitions, TRANSITION_TO_OFFNORMAL, true); pos += 12; } else if ( 0 == strncmp(&buf[pos], "to-fault", 8)) { bitstring_set_bit( &bacdest->Transitions, TRANSITION_TO_FAULT, true); pos += 8; } else if ( 0 == strncmp(&buf[pos], "to-normal", 9)) { bitstring_set_bit( &bacdest->Transitions, TRANSITION_TO_NORMAL, true); pos += 9; } else { return false; } j = 1; } else { MUST_CONSUME(","); j = 0; } } while (1); break; case KW_Recipient: if (0 == strncmp(&buf[pos], "Device", 6)) { pos += 6; bacdest->Recipient.tag = BACNET_RECIPIENT_TAG_DEVICE; DISCARD_WHITESPACE(); MUST_CONSUME("("); DISCARD_WHITESPACE(); MUST_CONSUME("type"); DISCARD_WHITESPACE(); MUST_CONSUME("="); DISCARD_WHITESPACE(); COLLECT_NUMBER_TMP(6); bacdest->Recipient.type.device.type = tmp; DISCARD_WHITESPACE(); MUST_CONSUME(","); DISCARD_WHITESPACE(); MUST_CONSUME("instance"); DISCARD_WHITESPACE(); MUST_CONSUME("="); DISCARD_WHITESPACE(); COLLECT_NUMBER_TMP(10); bacdest->Recipient.type.device.instance = tmp; DISCARD_WHITESPACE(); MUST_CONSUME(")"); } else if (0 == strncmp(&buf[pos], "Address", 7)) { pos += 7; bacdest->Recipient.tag = BACNET_RECIPIENT_TAG_ADDRESS; DISCARD_WHITESPACE(); MUST_CONSUME("("); DISCARD_WHITESPACE(); MUST_CONSUME("net"); DISCARD_WHITESPACE(); MUST_CONSUME("="); DISCARD_WHITESPACE(); COLLECT_NUMBER_TMP(6); bacdest->Recipient.type.address.net = tmp; DISCARD_WHITESPACE(); MUST_CONSUME(","); DISCARD_WHITESPACE(); MUST_CONSUME("mac"); DISCARD_WHITESPACE(); MUST_CONSUME("="); DISCARD_WHITESPACE(); if (!bacnet_address_mac_from_ascii( &tmpmac, &buf[pos])) { return false; } bacdest->Recipient.type.address.mac_len = tmpmac.len; memcpy( &bacdest->Recipient.type.address.mac, &tmpmac.adr, MAX_MAC_LEN); /* address_mac_from_ascii doesn't return number of * digits * - we have to discard until ) */ DISCARD_WHILE(c != ')'); pos++; /* discard the paren */ } break; default: return false; } ph = PH_PAIR_SPACER; break; default: return false; } } 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; }