/** * @file * @brief BACnetDestination complex data type encode and decode * @author Steve Karg * @date December 2022 * @section LICENSE * * Copyright (C) 2022 Steve Karg * * 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/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(BACNET_RECIPIENT *r1, 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_same(&r1->type.address, &r2->type.address); } else { status = false; } } } 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, 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 */ bool bacnet_recipient_device_wildcard(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(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(BACNET_DESTINATION *d1, 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, 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(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, 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; } if (destination->Recipient.tag == BACNET_RECIPIENT_TAG_DEVICE) { len = encode_context_object_id( apdu, 0, OBJECT_DEVICE, destination->Recipient.type.device.instance); apdu_len += len; if (apdu) { apdu += len; } } else if (destination->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, &destination->Recipient.type.address); apdu_len += len; if (apdu) { apdu += len; } /* closing tag 1 */ len = encode_closing_tag(apdu, 1); 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, 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( uint8_t *apdu, int apdu_size, BACNET_DESTINATION *destination) { int len = 0, apdu_len = 0; BACNET_APPLICATION_DATA_VALUE value = { 0 }; if (!apdu) { return BACNET_STATUS_REJECT; } if (!destination) { return BACNET_STATUS_REJECT; } /* Decode Valid Days */ len = bacapp_decode_application_data(apdu, apdu_size, &value); if ((len == 0) || (len == BACNET_STATUS_ERROR) || (value.tag != BACNET_APPLICATION_TAG_BIT_STRING)) { return BACNET_STATUS_REJECT; } bitstring_copy(&destination->ValidDays, &value.type.Bit_String); apdu_len += len; apdu += len; /* Decode From Time */ len = bacapp_decode_application_data(apdu, apdu_size, &value); if ((len == 0) || (len == BACNET_STATUS_ERROR) || (value.tag != BACNET_APPLICATION_TAG_TIME)) { return BACNET_STATUS_REJECT; } /* store value */ datetime_copy_time(&destination->FromTime, &value.type.Time); apdu_len += len; apdu += len; /* Decode To Time */ len = bacapp_decode_application_data(apdu, apdu_size, &value); if ((len == 0) || (len == BACNET_STATUS_ERROR) || (value.tag != BACNET_APPLICATION_TAG_TIME)) { return BACNET_STATUS_REJECT; } /* store value */ datetime_copy_time(&destination->ToTime, &value.type.Time); apdu_len += len; apdu += len; if (decode_is_context_tag(apdu, BACNET_RECIPIENT_TAG_DEVICE)) { /* device [0] BACnetObjectIdentifier */ destination->Recipient.tag = BACNET_RECIPIENT_TAG_DEVICE; len = decode_context_object_id( apdu, BACNET_RECIPIENT_TAG_DEVICE, &destination->Recipient.type.device.type, &destination->Recipient.type.device.instance); if (len == BACNET_STATUS_ERROR) { return BACNET_STATUS_REJECT; } if (destination->Recipient.type.device.type != OBJECT_DEVICE) { return BACNET_STATUS_REJECT; } apdu_len += len; apdu += len; } else if (decode_is_opening_tag_number( apdu, BACNET_RECIPIENT_TAG_ADDRESS)) { /* address [1] BACnetAddress */ destination->Recipient.tag = BACNET_RECIPIENT_TAG_ADDRESS; /* opening tag [1] is len 1 */ len = 1; apdu_len += len; apdu += len; len = decode_bacnet_address(apdu, &destination->Recipient.type.address); if ((len == 0) || (len == BACNET_STATUS_ERROR)) { return BACNET_STATUS_REJECT; } apdu_len += len; apdu += len; /* closing tag [1] */ if (decode_is_closing_tag_number(apdu, BACNET_RECIPIENT_TAG_ADDRESS)) { /* closing tag [1] is len 1 */ len = 1; apdu_len += len; apdu += len; } else { return BACNET_STATUS_REJECT; } } else { return BACNET_STATUS_REJECT; } /* Process Identifier */ len = bacapp_decode_application_data(apdu, apdu_size, &value); if ((len == 0) || (len == BACNET_STATUS_ERROR) || (value.tag != BACNET_APPLICATION_TAG_UNSIGNED_INT)) { return BACNET_STATUS_REJECT; } /* store value */ destination->ProcessIdentifier = value.type.Unsigned_Int; apdu_len += len; apdu += len; /* Issue Confirmed Notifications */ len = bacapp_decode_application_data(apdu, apdu_size, &value); if ((len == 0) || (len == BACNET_STATUS_ERROR) || (value.tag != BACNET_APPLICATION_TAG_BOOLEAN)) { return BACNET_STATUS_REJECT; } /* store value */ destination->ConfirmedNotify = value.type.Boolean; apdu_len += len; apdu += len; /* Transitions */ len = bacapp_decode_application_data(apdu, apdu_size, &value); if ((len == 0) || (len == BACNET_STATUS_ERROR) || (value.tag != BACNET_APPLICATION_TAG_BIT_STRING)) { return BACNET_STATUS_REJECT; } /* store value */ bitstring_copy(&destination->Transitions, &value.type.Bit_String); 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 len = 0; int buf_len = 0; bool comma; int i; len = snprintf(buf, buf_size, "("); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); /* BACnetDaysOfWeek ::= BIT STRING { monday (0), tuesday (1), wednesday (2), thursday (3), friday (4), saturday (5), sunday (6) } */ /* Use numbers 1-7 (ISO 8601) */ len = snprintf(buf, buf_size, "ValidDays=["); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); comma = false; for (i = 0; i < 7; i++) { if (bitstring_bit((BACNET_BIT_STRING *)&bacdest->ValidDays, i)) { if (comma) { len = snprintf(buf, buf_size, ","); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } len = snprintf(buf, buf_size, "%d", i + 1); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); 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, 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); if (bacdest->Recipient.tag == BACNET_RECIPIENT_TAG_DEVICE) { len = snprintf( buf, buf_size, "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 { network-number Unsigned16, -- A value of 0 indicates the local network mac-address OCTET STRING -- A string of length 0 indicates a broadcast } */ len = snprintf( buf, buf_size, "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); } len = snprintf( buf, buf_size, "%02x", bacdest->Recipient.type.address.mac[i]); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } len = snprintf(buf, buf_size, ")"); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } len = snprintf(buf, buf_size, ";"); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); len = snprintf( buf, buf_size, "ProcessIdentifier=%lu;", (unsigned long)bacdest->ProcessIdentifier); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); len = snprintf( buf, buf_size, "ConfirmedNotify=%s;", bacdest->ConfirmedNotify ? "true" : "false"); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); /* BACnetEventTransitionBits ::= BIT STRING { to-offnormal (0), to-fault (1), to-normal (2) } */ len = snprintf(buf, buf_size, "Transitions=["); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); comma = false; /* TODO remove casting when bitstring_bit() has const added - Github issue * #320 */ if (bitstring_bit( (BACNET_BIT_STRING *)&bacdest->Transitions, TRANSITION_TO_OFFNORMAL)) { len = snprintf(buf, buf_size, "to-offnormal"); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); comma = true; } if (bitstring_bit( (BACNET_BIT_STRING *)&bacdest->Transitions, TRANSITION_TO_FAULT)) { if (comma) { len = snprintf(buf, buf_size, ","); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } len = snprintf(buf, buf_size, "to-fault"); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); comma = true; } if (bitstring_bit( (BACNET_BIT_STRING *)&bacdest->Transitions, TRANSITION_TO_NORMAL)) { if (comma) { len = snprintf(buf, buf_size, ","); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } len = snprintf(buf, buf_size, "to-normal"); buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); } len = snprintf(buf, buf_size, "])"); /* end of the outer paren */ buf_len += bacapp_snprintf_shift(len, &buf, &buf_size); return buf_len; } /** * Parse BACnet_Destination from ASCII string (as entered by user) * * @param bacdest - Destination struct to pupulate 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; }