Files
bacnet_stack/src/bacnet/bacdest.c
T
2024-05-30 09:16:05 -05:00

1024 lines
36 KiB
C

/**
* @file
* @brief BACnetDestination complex data type encode and decode
* @author Steve Karg <skarg@users.sourceforge.net>
* @date December 2022
* @section LICENSE
*
* Copyright (C) 2022 Steve Karg <skarg@users.sourceforge.net>
*
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
/* 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;
}