From 6a8adcce3d2d15989976817b773004735c8d4c98 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 3 Jun 2022 15:47:30 -0500 Subject: [PATCH] Feature/writepropertymultiple error handler (#289) * Added extended BACnet Error PDU handling for WPM * Added BACnetObjectPropertyReference to bacapp module. * Added unit testing for BACnetObjectPropertyReference Co-authored-by: Steve Karg --- apps/ptransfer/main.c | 18 +- apps/readrange/main.c | 1 - apps/writepropm/main.c | 30 +++- src/bacnet/bacapp.c | 34 ++++ src/bacnet/bacapp.h | 4 + src/bacnet/bacdevobjpropref.c | 214 ++++++++++++++++++++++++ src/bacnet/bacdevobjpropref.h | 43 ++++- src/bacnet/bacenum.h | 48 ++++-- src/bacnet/basic/service/h_apdu.c | 128 +++++++------- src/bacnet/basic/service/h_apdu.h | 17 ++ src/bacnet/wpm.c | 136 +++++++++++++++ src/bacnet/wpm.h | 5 + test/bacnet/bacdevobjpropref/src/main.c | 51 +++++- test/bacnet/npdu/CMakeLists.txt | 1 + 14 files changed, 642 insertions(+), 88 deletions(-) diff --git a/apps/ptransfer/main.c b/apps/ptransfer/main.c index 0bf21e59..fdf704e6 100644 --- a/apps/ptransfer/main.c +++ b/apps/ptransfer/main.c @@ -99,6 +99,21 @@ static void MyErrorHandler(BACNET_ADDRESS *src, /* Error_Detected = true; */ } +/* complex error reply function */ +static void MyPrivateTransferErrorHandler( + BACNET_ADDRESS * src, + uint8_t invoke_id, + uint8_t service_choice, + uint8_t * service_request, + uint16_t service_len) +{ + (void)src; + (void)invoke_id; + (void)service_choice; + (void)service_request; + (void)service_len; +} + static void MyAbortHandler( BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server) { @@ -148,7 +163,8 @@ static void Init_Service_Handlers(void) /* handle any errors coming back */ apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); - apdu_set_error_handler(SERVICE_CONFIRMED_PRIVATE_TRANSFER, MyErrorHandler); + apdu_set_complex_error_handler(SERVICE_CONFIRMED_PRIVATE_TRANSFER, + MyPrivateTransferErrorHandler); apdu_set_abort_handler(MyAbortHandler); apdu_set_reject_handler(MyRejectHandler); } diff --git a/apps/readrange/main.c b/apps/readrange/main.c index 25c165f2..e903e449 100644 --- a/apps/readrange/main.c +++ b/apps/readrange/main.c @@ -131,7 +131,6 @@ static void Init_Service_Handlers(void) /* handle any errors coming back */ apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); - apdu_set_error_handler(SERVICE_CONFIRMED_PRIVATE_TRANSFER, MyErrorHandler); apdu_set_abort_handler(MyAbortHandler); apdu_set_reject_handler(MyRejectHandler); } diff --git a/apps/writepropm/main.c b/apps/writepropm/main.c index e66f15d6..d3b82c40 100644 --- a/apps/writepropm/main.c +++ b/apps/writepropm/main.c @@ -70,18 +70,30 @@ static bool Error_Detected = false; /* Used for verbose */ static bool Verbose = false; -static void MyErrorHandler(BACNET_ADDRESS *src, +static void MyWritePropertyMultipleErrorHandler( + BACNET_ADDRESS * src, uint8_t invoke_id, - BACNET_ERROR_CLASS error_class, - BACNET_ERROR_CODE error_code) + uint8_t service_choice, + uint8_t * service_request, + uint16_t service_len) { + int len = 0; + BACNET_WRITE_PROPERTY_DATA wp_data = { 0 }; + + (void)service_choice; if (address_match(&Target_Address, src) && (invoke_id == Request_Invoke_ID)) { - printf("BACnet Error: %s: %s\n", - bactext_error_class_name((int)error_class), - bactext_error_code_name((int)error_code)); + len = wpm_error_ack_decode_apdu(service_request, service_len, &wp_data); + if (len > 0) { + printf("BACnet Error: %s: %s\n", + bactext_error_class_name((int)wp_data.error_class), + bactext_error_code_name((int)wp_data.error_code)); + printf("BACnet Error: %s %u: %s\n", + bactext_object_type_name((int)wp_data.object_type), + (unsigned)wp_data.object_instance, + bactext_property_name((int)wp_data.object_property)); + } Error_Detected = true; - /* FIXME: WPM error has extra data for first failed write. */ } } @@ -109,6 +121,7 @@ static void MyRejectHandler( } } + static void MyWritePropertyMultipleSimpleAckHandler( BACNET_ADDRESS *src, uint8_t invoke_id) { @@ -136,7 +149,8 @@ static void Init_Service_Handlers(void) apdu_set_confirmed_simple_ack_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, MyWritePropertyMultipleSimpleAckHandler); /* handle any errors coming back */ - apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler); + apdu_set_complex_error_handler(SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + MyWritePropertyMultipleErrorHandler); apdu_set_abort_handler(MyAbortHandler); apdu_set_reject_handler(MyRejectHandler); } diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index 27c47b9c..8399447e 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -158,6 +158,7 @@ int bacapp_encode_application_data( apdu, &value->type.Lighting_Command); break; case BACNET_APPLICATION_TAG_HOST_N_PORT: + /* BACnetHostNPort */ apdu_len = host_n_port_encode(apdu, &value->type.Host_Address); break; @@ -166,6 +167,16 @@ int bacapp_encode_application_data( apdu_len = bacapp_encode_device_obj_property_ref( apdu, &value->type.Device_Object_Property_Reference); break; + case BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE: + /* BACnetDeviceObjectReference */ + apdu_len = bacapp_encode_device_obj_ref( + apdu, &value->type.Device_Object_Reference); + break; + case BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE: + /* BACnetObjectPropertyReference */ + apdu_len = bacapp_encode_obj_property_ref( + apdu, &value->type.Object_Property_Reference); + break; #endif default: break; @@ -289,6 +300,17 @@ int bacapp_decode_data(uint8_t *apdu, len = bacapp_decode_device_obj_property_ref( apdu, &value->type.Device_Object_Property_Reference); break; + case BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE: + /* BACnetDeviceObjectReference */ + len = bacapp_decode_device_obj_ref( + apdu, &value->type.Device_Object_Reference); + break; + case BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE: + /* BACnetObjectPropertyReference */ + len = bacapp_decode_obj_property_ref( + apdu, len_value_type, + &value->type.Object_Property_Reference); + break; #endif default: break; @@ -589,6 +611,18 @@ int bacapp_encode_context_data_value(uint8_t *apdu, apdu, context_tag_number, &value->type.Device_Object_Property_Reference); break; + case BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE: + /* BACnetDeviceObjectReference */ + apdu_len = bacapp_encode_context_device_obj_ref( + apdu, context_tag_number, + &value->type.Device_Object_Reference); + break; + case BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE: + /* BACnetObjectPropertyReference */ + apdu_len = bacapp_encode_context_obj_property_ref( + apdu, context_tag_number, + &value->type.Object_Property_Reference); + break; #endif default: break; diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h index 5c9c3fd3..35f603ae 100644 --- a/src/bacnet/bacapp.h +++ b/src/bacnet/bacapp.h @@ -84,6 +84,10 @@ typedef struct BACnet_Application_Data_Value { BACNET_HOST_N_PORT Host_Address; BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE Device_Object_Property_Reference; + BACNET_DEVICE_OBJECT_REFERENCE + Device_Object_Reference; + BACNET_OBJECT_PROPERTY_REFERENCE + Object_Property_Reference; #endif } type; /* simple linked list if needed */ diff --git a/src/bacnet/bacdevobjpropref.c b/src/bacnet/bacdevobjpropref.c index 493e5e52..a983031d 100644 --- a/src/bacnet/bacdevobjpropref.c +++ b/src/bacnet/bacdevobjpropref.c @@ -1,6 +1,7 @@ /*####COPYRIGHTBEGIN#### ------------------------------------------- Copyright (C) 2008 John Minack + Copyright (C) 2022 Steve Karg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -400,3 +401,216 @@ int bacapp_decode_context_device_obj_ref( } return len; } + + +/** + * @brief Encode a BACnetObjectPropertyReference + * + * BACnetObjectPropertyReference ::= SEQUENCE { + * object-identifier [0] BACnetObjectIdentifier, + * property-identifier [1] BACnetPropertyIdentifier, + * property-array-index [2] Unsigned OPTIONAL + * -- used only with array datatype + * -- if omitted with an array the entire array is referenced + * } + * + * @param apdu - the APDU buffer, or NULL for length + * @param reference - BACnetObjectPropertyReference + * @return length of the APDU buffer + */ +int bacapp_encode_obj_property_ref(uint8_t *apdu, + BACNET_OBJECT_PROPERTY_REFERENCE *reference) +{ + int len = 0; + uint8_t *apdu_offset = NULL; + int apdu_len = 0; + + if (!reference) { + return 0; + } + if (reference->object_identifier.type == OBJECT_NONE) { + return 0; + } + if (apdu) { + apdu_offset = apdu; + } + len = encode_context_object_id(apdu_offset, 0, + reference->object_identifier.type, + reference->object_identifier.instance); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = encode_context_enumerated( + apdu_offset, 1, reference->property_identifier); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + if (reference->property_array_index != BACNET_ARRAY_ALL) { + len = encode_context_unsigned( + apdu_offset, 2, reference->property_array_index); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Encode the BACnetObjectPropertyReference as Context Tagged + * @param apdu - the APDU buffer + * @param tag_number - context tag number to be encoded + * @param reference - BACnetObjectPropertyReference to encode + * @return length of the APDU buffer + */ +int bacapp_encode_context_obj_property_ref(uint8_t *apdu, + uint8_t tag_number, + BACNET_OBJECT_PROPERTY_REFERENCE *reference) +{ + int len = 0; + int apdu_len = 0; + uint8_t *apdu_offset = NULL; + + if (reference && (reference->object_identifier.type == OBJECT_NONE)) { + return 0; + } + if (apdu) { + apdu_offset = apdu; + } + len = encode_opening_tag(apdu_offset, tag_number); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = bacapp_encode_obj_property_ref(apdu_offset, reference); + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + } + len = encode_closing_tag(apdu_offset, tag_number); + apdu_len += len; + + return apdu_len; +} + +/** + * @brief Decode a BACnetObjectPropertyReference + * + * BACnetObjectPropertyReference ::= SEQUENCE { + * object-identifier [0] BACnetObjectIdentifier, + * property-identifier [1] BACnetPropertyIdentifier, + * property-array-index [2] Unsigned OPTIONAL + * -- used only with array datatype + * -- if omitted with an array the entire array is referenced + * } + * + * @param apdu - the APDU buffer + * @param apdu_len_max - the APDU buffer length + * @param reference - BACnetObjectPropertyReference to decode into + * @return length of the APDU buffer decoded, or 0 if failed to decode + */ +int bacapp_decode_obj_property_ref(uint8_t *apdu, + uint16_t apdu_len_max, + BACNET_OBJECT_PROPERTY_REFERENCE *reference) +{ + int apdu_len = 0; + int len = 0; + BACNET_OBJECT_ID object_identifier; + BACNET_PROPERTY_ID property_identifier; + BACNET_UNSIGNED_INTEGER unsigned_value; + + if (apdu && (apdu_len_max > 0)) { + /* object-identifier [0] BACnetObjectIdentifier */ + len = bacnet_object_id_context_decode(&apdu[apdu_len], + apdu_len_max - apdu_len, 0, &object_identifier.type, + &object_identifier.instance); + if (len > 0) { + apdu_len += len; + } else if (len <= 0) { + return 0; + } + /* property-identifier [1] BACnetPropertyIdentifier */ + len = bacnet_enumerated_context_decode( + &apdu[apdu_len], apdu_len_max - apdu_len, 1, &property_identifier); + if (len > 0) { + apdu_len += len; + } else if (len <= 0) { + return 0; + } + if (reference) { + reference->object_identifier.type = object_identifier.type; + reference->object_identifier.instance = object_identifier.instance; + reference->property_identifier = property_identifier; + reference->property_array_index = BACNET_ARRAY_ALL; + } + /* property-array-index [2] Unsigned OPTIONAL */ + if (apdu_len_max > apdu_len) { + if (decode_is_context_tag(&apdu[apdu_len], 2)) { + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_len_max - apdu_len, 2, + &unsigned_value); + if (len > 0) { + apdu_len += len; + if (unsigned_value > UINT32_MAX) { + return 0; + } + if (reference) { + reference->property_array_index = unsigned_value; + } + } else if (len <= 0) { + return 0; + } + } + } + } + + return apdu_len; +} + +/** + * Decode the context object property reference. Check for + * an opening tag and a closing tag as well. + * + * @param apdu Pointer to the buffer containing the data to decode. + * @param apdu_len_max - the APDU buffer length + * @param tag_number Tag number + * @param value Pointer to the context device object reference, + * that shall be decoded. + * + * @return Bytes decoded or BACNET_STATUS_ERROR on failure. + */ +int bacapp_decode_context_obj_property_ref( + uint8_t * apdu, + uint16_t apdu_len_max, + uint8_t tag_number, + BACNET_OBJECT_PROPERTY_REFERENCE * value) +{ + int len = 0; + int apdu_len = 0; + + if (apdu_len_max == 0) { + return BACNET_STATUS_ERROR; + } + if (decode_is_opening_tag_number(&apdu[apdu_len], tag_number)) { + apdu_len++; + } else { + return BACNET_STATUS_ERROR; + } + len = bacapp_decode_obj_property_ref(&apdu[apdu_len], + apdu_len_max - apdu_len, value); + if (len == 0) { + return BACNET_STATUS_ERROR; + } else { + apdu_len += len; + } + if ((apdu_len_max - apdu_len) == 0) { + return BACNET_STATUS_ERROR; + } + if (decode_is_closing_tag_number(&apdu[apdu_len], tag_number)) { + apdu_len++; + } else { + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} diff --git a/src/bacnet/bacdevobjpropref.h b/src/bacnet/bacdevobjpropref.h index 24816db2..b570a85a 100644 --- a/src/bacnet/bacdevobjpropref.h +++ b/src/bacnet/bacdevobjpropref.h @@ -1,6 +1,7 @@ /************************************************************************** * * Copyright (C) 2008 John Minack +* Copyright (C) 2022 Steve Karg * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -49,7 +50,22 @@ typedef struct BACnetDeviceObjectReference { BACNET_OBJECT_ID objectIdentifier; } BACNET_DEVICE_OBJECT_REFERENCE; - +/** + * BACnetObjectPropertyReference ::= SEQUENCE { + * object-identifier [0] BACnetObjectIdentifier, + * property-identifier [1] BACnetPropertyIdentifier, + * property-array-index [2] Unsigned OPTIONAL + * -- used only with array datatype + * -- if omitted with an array the entire array is referenced + * } + */ +typedef struct BACnet_Object_Property_Reference { + /* note: use type = OBJECT_NONE for unused reference */ + BACNET_OBJECT_ID object_identifier; + BACNET_PROPERTY_ID property_identifier; + /* optional array index - use BACNET_ARRAY_ALL when not used */ + BACNET_ARRAY_INDEX property_array_index; +} BACNET_OBJECT_PROPERTY_REFERENCE; #ifdef __cplusplus extern "C" { @@ -77,7 +93,6 @@ extern "C" { uint8_t tag_number, BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE * value); - BACNET_STACK_EXPORT int bacapp_encode_device_obj_ref( uint8_t * apdu, @@ -100,6 +115,30 @@ extern "C" { uint8_t tag_number, BACNET_DEVICE_OBJECT_REFERENCE * value); + BACNET_STACK_EXPORT + int bacapp_encode_obj_property_ref( + uint8_t * apdu, + BACNET_OBJECT_PROPERTY_REFERENCE * value); + + BACNET_STACK_EXPORT + int bacapp_encode_context_obj_property_ref( + uint8_t * apdu, + uint8_t tag_number, + BACNET_OBJECT_PROPERTY_REFERENCE * value); + + BACNET_STACK_EXPORT + int bacapp_decode_obj_property_ref( + uint8_t * apdu, + uint16_t apdu_len_max, + BACNET_OBJECT_PROPERTY_REFERENCE * value); + + BACNET_STACK_EXPORT + int bacapp_decode_context_obj_property_ref( + uint8_t * apdu, + uint16_t apdu_len_max, + uint8_t tag_number, + BACNET_OBJECT_PROPERTY_REFERENCE * value); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 17584fc7..3cd75581 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1417,17 +1417,21 @@ typedef enum { PDU_TYPE_ABORT = 0x70 } BACNET_PDU_TYPE; -typedef enum { +/* BACnetConfirmedServiceChoice ::= ENUMERATED */ +typedef enum BACnet_Confirmed_Service_Choice { /* Alarm and Event Services */ SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM = 0, + SERVICE_CONFIRMED_AUDIT_NOTIFICATION = 32, SERVICE_CONFIRMED_COV_NOTIFICATION = 1, + SERVICE_CONFIRMED_COV_NOTIFICATION_MULTIPLE = 31, SERVICE_CONFIRMED_EVENT_NOTIFICATION = 2, SERVICE_CONFIRMED_GET_ALARM_SUMMARY = 3, SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY = 4, SERVICE_CONFIRMED_GET_EVENT_INFORMATION = 29, + SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION = 27, SERVICE_CONFIRMED_SUBSCRIBE_COV = 5, SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY = 28, - SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION = 27, + SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY_MULTIPLE = 30, /* File Access Services */ SERVICE_CONFIRMED_ATOMIC_READ_FILE = 6, SERVICE_CONFIRMED_ATOMIC_WRITE_FILE = 7, @@ -1442,6 +1446,7 @@ typedef enum { SERVICE_CONFIRMED_READ_RANGE = 26, SERVICE_CONFIRMED_WRITE_PROPERTY = 15, SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE = 16, + SERVICE_CONFIRMED_AUDIT_LOG_QUERY = 33, /* Remote Device Management Services */ SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL = 17, SERVICE_CONFIRMED_PRIVATE_TRANSFER = 18, @@ -1455,14 +1460,21 @@ typedef enum { SERVICE_CONFIRMED_AUTHENTICATE = 24, SERVICE_CONFIRMED_REQUEST_KEY = 25, /* Services added after 1995 */ - /* readRange (26) see Object Access Services */ - /* lifeSafetyOperation (27) see Alarm and Event Services */ - /* subscribeCOVProperty (28) see Alarm and Event Services */ - /* getEventInformation (29) see Alarm and Event Services */ + /* readRange [26] see Object Access Services */ + /* lifeSafetyOperation [27] see Alarm and Event Services */ + /* subscribeCOVProperty [28] see Alarm and Event Services */ + /* getEventInformation [29] see Alarm and Event Services */ + /* Services added after 2012 */ + /* subscribe-cov-property-multiple [30] see Alarm and Event Services */ + /* confirmed-cov-notification-multiple [31] see Alarm and Event Services */ + /* Services added after 2016 */ + /* confirmed-audit-notification [32] see Alarm and Event Services */ + /* audit-log-query [33] see Object Access Services */ MAX_BACNET_CONFIRMED_SERVICE = 30 } BACNET_CONFIRMED_SERVICE; -typedef enum { +/* BACnetUnconfirmedServiceChoice ::= ENUMERATED */ +typedef enum BACnet_Unconfirmed_Service_Choice { SERVICE_UNCONFIRMED_I_AM = 0, SERVICE_UNCONFIRMED_I_HAVE = 1, SERVICE_UNCONFIRMED_COV_NOTIFICATION = 2, @@ -1475,16 +1487,21 @@ typedef enum { SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION = 9, /* addendum 2010-aa */ SERVICE_UNCONFIRMED_WRITE_GROUP = 10, + /* addendum 2012-aq */ + SERVICE_UNCONFIRMED_COV_NOTIFICATION_MULTIPLE = 11, + /* addendum 2016-bz */ + SERVICE_UNCONFIRMED_WHO_AM_I = 13, + SERVICE_UNCONFIRMED_YOU_ARE = 14, /* Other services to be added as they are defined. */ /* All choice values in this production are reserved */ /* for definition by ASHRAE. */ /* Proprietary extensions are made by using the */ /* UnconfirmedPrivateTransfer service. See Clause 23. */ - MAX_BACNET_UNCONFIRMED_SERVICE = 11 + MAX_BACNET_UNCONFIRMED_SERVICE = 15 } BACNET_UNCONFIRMED_SERVICE; -/* Bit String Enumerations */ -typedef enum { +/* BACnetServicesSupported ::= BIT STRING */ +typedef enum BACnet_Services_Supported { /* Alarm and Event Services */ SERVICE_SUPPORTED_ACKNOWLEDGE_ALARM = 0, SERVICE_SUPPORTED_CONFIRMED_COV_NOTIFICATION = 1, @@ -1494,7 +1511,10 @@ typedef enum { SERVICE_SUPPORTED_GET_EVENT_INFORMATION = 39, SERVICE_SUPPORTED_SUBSCRIBE_COV = 5, SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY = 38, + SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY_MULTIPLE = 41, SERVICE_SUPPORTED_LIFE_SAFETY_OPERATION = 37, + SERVICE_SUPPORTED_CONFIRMED_AUDIT_NOTIFICATION = 44, + SERVICE_SUPPORTED_AUDIT_LOG_QUERY = 45, /* File Access Services */ SERVICE_SUPPORTED_ATOMIC_READ_FILE = 6, SERVICE_SUPPORTED_ATOMIC_WRITE_FILE = 7, @@ -1515,6 +1535,8 @@ typedef enum { SERVICE_SUPPORTED_PRIVATE_TRANSFER = 18, SERVICE_SUPPORTED_TEXT_MESSAGE = 19, SERVICE_SUPPORTED_REINITIALIZE_DEVICE = 20, + SERVICE_SUPPORTED_WHO_AM_I = 47, + SERVICE_SUPPORTED_YOU_ARE = 48, /* Virtual Terminal Services */ SERVICE_SUPPORTED_VT_OPEN = 21, SERVICE_SUPPORTED_VT_CLOSE = 22, @@ -1524,17 +1546,21 @@ typedef enum { SERVICE_SUPPORTED_REQUEST_KEY = 25, SERVICE_SUPPORTED_I_AM = 26, SERVICE_SUPPORTED_I_HAVE = 27, + /* Unconfirmed Services */ SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION = 28, + SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION_MULTIPLE = 43, SERVICE_SUPPORTED_UNCONFIRMED_EVENT_NOTIFICATION = 29, SERVICE_SUPPORTED_UNCONFIRMED_PRIVATE_TRANSFER = 30, SERVICE_SUPPORTED_UNCONFIRMED_TEXT_MESSAGE = 31, SERVICE_SUPPORTED_TIME_SYNCHRONIZATION = 32, SERVICE_SUPPORTED_UTC_TIME_SYNCHRONIZATION = 36, SERVICE_SUPPORTED_WHO_HAS = 33, - SERVICE_SUPPORTED_WHO_IS = 34 + SERVICE_SUPPORTED_WHO_IS = 34, + SERVICE_SUPPORTED_UNCONFIRMED_AUDIT_NOTIFICATION = 46, /* Other services to be added as they are defined. */ /* All values in this production are reserved */ /* for definition by ASHRAE. */ + BACNET_SERVICES_SUPPORTED_MAX = 47 } BACNET_SERVICES_SUPPORTED; /* Bit String Enumerations */ diff --git a/src/bacnet/basic/service/h_apdu.c b/src/bacnet/basic/service/h_apdu.c index 0c03eecb..5d10662b 100644 --- a/src/bacnet/basic/service/h_apdu.c +++ b/src/bacnet/basic/service/h_apdu.c @@ -39,6 +39,7 @@ #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" #include "bacnet/dcc.h" #include "bacnet/iam.h" /* basic objects, services, TSM */ @@ -302,7 +303,36 @@ void apdu_set_confirmed_ack_handler( } } -static error_function Error_Function[MAX_BACNET_CONFIRMED_SERVICE]; +static union { + error_function error; + complex_error_function complex; +} Error_Function[MAX_BACNET_CONFIRMED_SERVICE]; + +/** + * @brief Determine if the service uses Complex Error function + * @param service_choice Service, see SERVICE_CONFIRMED_X enumeration. + * @return true if the service uses a Complex Error function + */ +bool apdu_complex_error(BACNET_CONFIRMED_SERVICE service_choice) +{ + bool status = false; + + switch (service_choice) { + case SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY_MULTIPLE: + case SERVICE_CONFIRMED_ADD_LIST_ELEMENT: + case SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT: + case SERVICE_CONFIRMED_CREATE_OBJECT: + case SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE: + case SERVICE_CONFIRMED_PRIVATE_TRANSFER: + case SERVICE_CONFIRMED_VT_CLOSE: + status = true; + break; + default: + break; + } + + return status; +} /** * @brief Set a error handler function for the given confirmed service. @@ -312,10 +342,28 @@ static error_function Error_Function[MAX_BACNET_CONFIRMED_SERVICE]; * handling. */ void apdu_set_error_handler( - BACNET_CONFIRMED_SERVICE service_choice, error_function pFunction) + BACNET_CONFIRMED_SERVICE service_choice, + error_function pFunction) { - if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { - Error_Function[service_choice] = pFunction; + if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) && + (!apdu_complex_error(service_choice))) { + Error_Function[service_choice].error = pFunction; + } +} + +/** + * @brief Set a complex error handler function for the given confirmed service. + * + * @param service_choice Service, see SERVICE_CONFIRMED_X enumeration. + * @param pFunction Pointer to the function, being in charge of the error + * handling. + */ +void apdu_set_complex_error_handler( + BACNET_CONFIRMED_SERVICE service_choice, + complex_error_function pFunction) +{ + if (apdu_complex_error(service_choice)) { + Error_Function[service_choice].complex = pFunction; } } @@ -499,8 +547,6 @@ void apdu_handler(BACNET_ADDRESS *src, uint8_t *service_request = NULL; uint16_t service_request_len = 0; int len = 0; /* counts where we are in PDU */ - uint8_t tag_number = 0; - uint32_t len_value = 0; uint32_t error_code = 0; uint32_t error_class = 0; uint8_t reason = 0; @@ -648,65 +694,19 @@ void apdu_handler(BACNET_ADDRESS *src, if (apdu_len >= 3) { invoke_id = apdu[1]; service_choice = apdu[2]; - len = 3; - - /* FIXME: Currently special case for C_P_T but there are - others which may need consideration such as - ChangeList-Error, CreateObject-Error, - WritePropertyMultiple-Error and VTClose_Error but they - may be left as is for now until support for these - services is added */ - - if (service_choice == - SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over - opening tag 0 - */ - if (decode_is_opening_tag_number(&apdu[len], 0)) { - len++; /* a tag number of 0 is not extended so only - one octet */ + if (apdu_complex_error(service_choice)) { + if (Error_Function[service_choice].complex) { + Error_Function[service_choice].complex(src, + invoke_id, service_choice, + &apdu[3], apdu_len - 3); } - } - - if (len < apdu_len) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - - if (len < apdu_len) { - /* FIXME: we could validate that the tag is - * enumerated... */ - len += decode_enumerated( - &apdu[len], len_value, &error_class); - - if (len < apdu_len) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - - if (len < apdu_len) { - /* FIXME: we could validate that the tag is - * enumerated... */ - len += decode_enumerated( - &apdu[len], len_value, &error_code); - - if (service_choice == - SERVICE_CONFIRMED_PRIVATE_TRANSFER) { - if (len < apdu_len) { - /* skip over closing tag 0 */ - if (decode_is_closing_tag_number( - &apdu[len], 0)) { - len++; /* a tag number of 0 is - not extended so only - one octet */ - } - } - } - } - } - } - } - - if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { - if (Error_Function[service_choice]) { - Error_Function[service_choice](src, invoke_id, + } else if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) { + len = bacerror_decode_error_class_and_code(&apdu[3], + apdu_len - 3, &error_class, &error_code); + if ((len != 0) && + (Error_Function[service_choice].error)) { + Error_Function[service_choice].error(src, + invoke_id, (BACNET_ERROR_CLASS)error_class, (BACNET_ERROR_CODE)error_code); } diff --git a/src/bacnet/basic/service/h_apdu.h b/src/bacnet/basic/service/h_apdu.h index 9aa1b526..f21c3926 100644 --- a/src/bacnet/basic/service/h_apdu.h +++ b/src/bacnet/basic/service/h_apdu.h @@ -96,6 +96,15 @@ extern "C" { BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code); +/* complex error reply function */ + typedef void ( + *complex_error_function) ( + BACNET_ADDRESS * src, + uint8_t invoke_id, + uint8_t service_choice, + uint8_t * service_request, + uint16_t service_len); + /* generic abort reply function */ typedef void ( *abort_function) ( @@ -150,12 +159,20 @@ extern "C" { size_t * index, bool * bIsConfirmed); + BACNET_STACK_EXPORT + bool apdu_complex_error( + BACNET_CONFIRMED_SERVICE service_choice); BACNET_STACK_EXPORT void apdu_set_error_handler( BACNET_CONFIRMED_SERVICE service_choice, error_function pFunction); + BACNET_STACK_EXPORT + void apdu_set_complex_error_handler( + BACNET_CONFIRMED_SERVICE service_choice, + complex_error_function pFunction); + BACNET_STACK_EXPORT void apdu_set_abort_handler( abort_function pFunction); diff --git a/src/bacnet/wpm.c b/src/bacnet/wpm.c index 64657b75..252d3312 100644 --- a/src/bacnet/wpm.c +++ b/src/bacnet/wpm.c @@ -29,6 +29,7 @@ #include "bacnet/bacenum.h" #include "bacnet/bacdcode.h" #include "bacnet/bacdef.h" +#include "bacnet/bacerror.h" #include "bacnet/wp.h" #include "bacnet/wpm.h" @@ -435,3 +436,138 @@ int wpm_error_ack_encode_apdu( } return len; } + +/** Decoding for WritePropertyMultiple Error + * @ingroup DSWPM + * WritePropertyMultiple-Error ::= SEQUENCE { + * error-type [0] Error, + * first-failed-write-attempt [1] BACnetObjectPropertyReference + * } + * + * @param apdu [in] The contents of the APDU buffer. + * @param apdu_size [in] The size of the APDU buffer. + * @param wp_data [out] The BACNET_WRITE_PROPERTY_DATA structure + * which will contain the response values or error. + * + * @return Count of decoded bytes. + */ +int wpm_error_ack_decode_apdu( + uint8_t *apdu, uint16_t apdu_size, BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + int len = 0, apdu_len = 0; + uint8_t *apdu_offset = NULL; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_SERVICES; + BACNET_ERROR_CODE error_code = ERROR_CODE_SUCCESS; + BACNET_OBJECT_PROPERTY_REFERENCE value; + + if (apdu) { + apdu_offset = apdu; + } + if (wp_data) { + wp_data->error_class = ERROR_CLASS_SERVICES; + wp_data->error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + /* Context tag 0 - Error */ + if (apdu_size == 0) { + return 0; + } + if (decode_is_opening_tag_number(apdu_offset, 0)) { + len = 1; + apdu_len -= len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + if (apdu_size > len) { + apdu_size -= len; + } else { + return 0; + } + } + } else { + return 0; + } + len = bacerror_decode_error_class_and_code(apdu_offset, apdu_size, + &error_class, &error_code); + if (len > 0) { + if (wp_data) { + wp_data->error_class = error_class; + wp_data->error_code = error_code; + } + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + if (apdu_size > len) { + apdu_size -= len; + } else { + return 0; + } + } + } else { + return 0; + } + if (apdu_size == 0) { + return 0; + } + if (decode_is_closing_tag_number(apdu_offset, 0)) { + len = 1; + apdu_len -= len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + if (apdu_size > len) { + apdu_size -= len; + } else { + return 0; + } + } + } else { + return 0; + } + /* Context tag 1 - BACnetObjectPropertyReference */ + if (apdu_size == 0) { + return 0; + } + if (decode_is_opening_tag_number(apdu_offset, 1)) { + len = 1; + apdu_len -= len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + if (apdu_size > len) { + apdu_size -= len; + } else { + return 0; + } + } + } else { + return 0; + } + len = bacapp_decode_obj_property_ref(apdu_offset, apdu_size, &value); + if (len > 0) { + if (wp_data) { + wp_data->object_instance = value.object_identifier.instance; + wp_data->object_type = value.object_identifier.type; + wp_data->object_property = value.property_identifier; + wp_data->array_index = value.property_array_index; + } + apdu_len += len; + if (apdu) { + apdu_offset = &apdu[apdu_len]; + if (apdu_size > len) { + apdu_size -= len; + } else { + return 0; + } + } + } else { + return 0; + } + if (apdu_size == 0) { + return 0; + } + if (decode_is_closing_tag_number(apdu_offset, 1)) { + len = 1; + apdu_len += len; + } else { + return 0; + } + + return apdu_len; +} diff --git a/src/bacnet/wpm.h b/src/bacnet/wpm.h index 805847ad..546fd50e 100644 --- a/src/bacnet/wpm.h +++ b/src/bacnet/wpm.h @@ -93,6 +93,11 @@ extern "C" { uint8_t invoke_id, BACNET_WRITE_PROPERTY_DATA * wp_data); + BACNET_STACK_EXPORT + int wpm_error_ack_decode_apdu( + uint8_t *apdu, + uint16_t apdu_size, + BACNET_WRITE_PROPERTY_DATA * wp_data); #ifdef __cplusplus } diff --git a/test/bacnet/bacdevobjpropref/src/main.c b/test/bacnet/bacdevobjpropref/src/main.c index d892905c..42f78fdd 100644 --- a/test/bacnet/bacdevobjpropref/src/main.c +++ b/test/bacnet/bacdevobjpropref/src/main.c @@ -114,6 +114,54 @@ static void testDevIdRef(void) zassert_equal( inData.deviceIdentifier.type, outData.deviceIdentifier.type, NULL); } + +static void testObjPropRef(void) +{ + BACNET_OBJECT_PROPERTY_REFERENCE inData; + BACNET_OBJECT_PROPERTY_REFERENCE outData; + uint8_t apdu[MAX_APDU]; + uint8_t tag_number = 1; + int inLen; + int outLen; + + inData.object_identifier.instance = 12345; + inData.object_identifier.type = OBJECT_ANALOG_VALUE; + inData.property_identifier = PROP_PRESENT_VALUE; + inData.property_array_index = BACNET_ARRAY_ALL; + inLen = bacapp_encode_obj_property_ref(apdu, &inData); + outLen = bacapp_decode_obj_property_ref(apdu, inLen, &outData); + zassert_equal(outLen, inLen, NULL); + zassert_equal( + inData.object_identifier.type, + outData.object_identifier.type, NULL); + zassert_equal( + inData.object_identifier.instance, + outData.object_identifier.instance, NULL); + zassert_equal( + inData.property_identifier, + outData.property_identifier, NULL); + zassert_equal( + inData.property_array_index, + outData.property_array_index, NULL); + /* context */ + inLen = bacapp_encode_context_obj_property_ref(apdu, tag_number, &inData); + outLen = bacapp_decode_context_obj_property_ref(apdu, inLen, tag_number, + &outData); + zassert_equal(outLen, inLen, NULL); + zassert_equal( + inData.object_identifier.type, + outData.object_identifier.type, NULL); + zassert_equal( + inData.object_identifier.instance, + outData.object_identifier.instance, NULL); + zassert_equal( + inData.property_identifier, + outData.property_identifier, NULL); + zassert_equal( + inData.property_array_index, + outData.property_array_index, NULL); +} + /** * @} */ @@ -123,7 +171,8 @@ void test_main(void) { ztest_test_suite(bacdevobjpropref_tests, ztest_unit_test(testDevIdPropRef), - ztest_unit_test(testDevIdRef) + ztest_unit_test(testDevIdRef), + ztest_unit_test(testObjPropRef) ); ztest_run_test_suite(bacdevobjpropref_tests); diff --git a/test/bacnet/npdu/CMakeLists.txt b/test/bacnet/npdu/CMakeLists.txt index 149cabe2..c5b8f61b 100644 --- a/test/bacnet/npdu/CMakeLists.txt +++ b/test/bacnet/npdu/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable(${PROJECT_NAME} # Support files and stubs (pathname alphabetical) ${SRC_DIR}/bacnet/bacaddr.c ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacerror.c ${SRC_DIR}/bacnet/bacint.c ${SRC_DIR}/bacnet/bacreal.c ${SRC_DIR}/bacnet/bacstr.c