Files
bacnet_stack/src/bacnet/bacdest.c
T
Steve Karg a70ce07507 Secure BACnet decoders and service requests (#1244)
* Secured BACnetAssignedAccessRights decoder.

* Secured BACnetPropertyState decoder.

* Secured BACnetCredentialAuthenticationFactor decoder.

* Secured BACnetEventState change-of-state [1] SEQUENCE decoder.

* Secured I-Have-Request service decoder.

* Secured Add/Remove ListElement service request decoder.

* Secured ConfirmedPrivateTransfer-Request and UnconfirmedPrivateTransfer-Request decoders.

* Secured ReadPropertyMultiple-Request and -Ack decoders.

* Secured TimeSynchronization-Request decoder.

* Secured WritePropertyMultiple service decoders

* Secured Trend Log object TL_fetch_property() function.

* Secured ReadProperty-Ack decider,

* Refactor BACnet time sync recipient handling by moving timesync linked list structure into bacdest where the recipient encoder and decoder already existed.

* Secured decoding of BACnetPropertyState.

* Secured decoding in the LifeSafetyOperation-Request service.

* Secured BACnetAuthenticationFactor decoding in the Credential Data Input object.

* Fixed WriteProperty decoder to avoid read buffer overrun.  Improved WriteProperty error reporting by adding specific reject codes during decoding similar to WritePropertyMultiple. Deduplicated the WriteProperty handling of abort, reject and error codes.

* Added BACNET_STACK_DEPRECATED_DISABLE guards around all of the deprecated decoding functions to ensure they are not used except intentionally for legacy code bases.

* Changed version to 1.5.0.rc5 for security fix tracking in branch.
2026-02-26 10:48:25 -06:00

1437 lines
47 KiB
C

/**
* @file
* @brief BACnetDestination complex data type encode and decode
* @author Steve Karg <skarg@users.sourceforge.net>
* @date December 2022
* @copyright 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/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;
}