diff --git a/bacnet-stack/include/bacapp.h b/bacnet-stack/include/bacapp.h index ca119128..21d6b5d5 100644 --- a/bacnet-stack/include/bacapp.h +++ b/bacnet-stack/include/bacapp.h @@ -203,6 +203,12 @@ extern "C" { #endif #endif +bool bacapp_extract_value( + char **out_string, + char *out_string_end, + size_t *str_len, + BACNET_OBJECT_PROPERTY_VALUE * object_value); + #ifdef BACAPP_PRINT_ENABLED bool bacapp_parse_application_data( BACNET_APPLICATION_TAG tag_number, diff --git a/bacnet-stack/src/bacapp.c b/bacnet-stack/src/bacapp.c index 6c19cd6a..3feea29b 100644 --- a/bacnet-stack/src/bacapp.c +++ b/bacnet-stack/src/bacapp.c @@ -936,9 +936,19 @@ int bacapp_data_len( return total_len; } -#ifdef BACAPP_PRINT_ENABLED -bool bacapp_print_value( - FILE * stream, +/* Extract the value into a string + * Inputs: out_string - ptr to address of the buffer to append the extracted + * value. If NULL, don't store anything, but still + * return the size of the intended output. + * out_string_end - the address of the end of the buffer plus one + * object_value - ptr to BACnet object property value to process + * Outputs: str_len - ptr to the size of the extracted string + * Return: True if extract was successful, false otherwise. + */ +bool bacapp_extract_value( + char **out_string, + char *out_string_end, + size_t *str_len, BACNET_OBJECT_PROPERTY_VALUE * object_value) { bool status = true; /*return value */ @@ -948,6 +958,9 @@ bool bacapp_print_value( BACNET_APPLICATION_DATA_VALUE *value; BACNET_PROPERTY_ID property = PROP_ALL; BACNET_OBJECT_TYPE object_type = MAX_BACNET_OBJECT_TYPE; + char *extracted_value; /* to be allocated here temporarily */ + char *p_extracted_value = NULL; /* pointer to the beginning of allocated block */ + *str_len = 0; /* how large is the extracted string */ if (object_value && object_value->value) { value = object_value->value; @@ -955,181 +968,399 @@ bool bacapp_print_value( object_type = object_value->object_type; switch (value->tag) { case BACNET_APPLICATION_TAG_NULL: - fprintf(stream, "Null"); + /* We can only have "Null" here */ + extracted_value = (char *)calloc(1, sizeof("Null")); + p_extracted_value = extracted_value; + if (extracted_value) + sprintf(extracted_value, "Null"); + else + status = false; break; case BACNET_APPLICATION_TAG_BOOLEAN: - fprintf(stream, "%s", value->type.Boolean ? "TRUE" : "FALSE"); + /* We can only have "FALSE" or "TRUE" here. Assume worst case */ + extracted_value = (char *)calloc(1, sizeof("FALSE")); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "FALSE"); + if (value->type.Boolean) + sprintf(extracted_value, "TRUE"); + } + else + status = false; break; case BACNET_APPLICATION_TAG_UNSIGNED_INT: - fprintf(stream, "%lu", - (unsigned long) value->type.Unsigned_Int); + /* %lu format should never exceed 32 characters */ + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%lu", + (unsigned long) value->type.Unsigned_Int); + } + else + status = false; break; case BACNET_APPLICATION_TAG_SIGNED_INT: - fprintf(stream, "%ld", (long) value->type.Signed_Int); + /* %ld format should never exceed 32 characters */ + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%ld", (long) value->type.Signed_Int); + } + else + status = false; break; case BACNET_APPLICATION_TAG_REAL: - fprintf(stream, "%f", (double) value->type.Real); + /* %f format should never exceed 32 characters */ + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%f", (double) value->type.Real); + } + else + status = false; break; #if defined (BACAPP_DOUBLE) case BACNET_APPLICATION_TAG_DOUBLE: - fprintf(stream, "%f", value->type.Double); + /* %f format should never exceed 32 characters */ + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%f", value->type.Double); + } + else + status = false; break; #endif case BACNET_APPLICATION_TAG_OCTET_STRING: len = octetstring_length(&value->type.Octet_String); - octet_str = octetstring_value(&value->type.Octet_String); - for (i = 0; i < len; i++) { - fprintf(stream, "%02X", *octet_str); - octet_str++; - } + + extracted_value = (char *)calloc( + 2, /* 2 nibbles in a byte */ + len + /* this many bytes of data*/ + 1); /* space for NULL terminator*/ + p_extracted_value = extracted_value; + if (extracted_value) + { + octet_str = octetstring_value(&value->type.Octet_String); + for (i = 0; i < len; i++) { + sprintf(extracted_value, "%02X", *octet_str); + octet_str++; + extracted_value += 2; + } + *extracted_value = '\0'; + } else + status = false; break; case BACNET_APPLICATION_TAG_CHARACTER_STRING: len = characterstring_length(&value->type.Character_String); - char_str = - characterstring_value(&value->type.Character_String); - fprintf(stream, "\""); - for (i = 0; i < len; i++) { - if (isprint(*((unsigned char *) char_str))) { - fprintf(stream, "%c", *char_str); - } else { - fprintf(stream, "."); + + extracted_value = (char *)calloc( + 1, /* 1 char per byte of data */ + len + /* this many bytes of data */ + 3); /* quotes around the string, plus NULL term */ + p_extracted_value = extracted_value; + if (extracted_value) + { + char_str = characterstring_value(&value->type.Character_String); + *extracted_value = '"'; + extracted_value++; + for (i = 0; i < len; i++) { + if (isprint(*((unsigned char *) char_str))) { + *extracted_value = *char_str; + } else { + *extracted_value = '.'; + } + char_str++; + extracted_value++; } - char_str++; + *extracted_value = '"'; + extracted_value++; + *extracted_value = '\0'; + } else { + status = false; } - fprintf(stream, "\""); break; case BACNET_APPLICATION_TAG_BIT_STRING: len = bitstring_bits_used(&value->type.Bit_String); - fprintf(stream, "{"); - for (i = 0; i < len; i++) { - fprintf(stream, "%s", - bitstring_bit(&value->type.Bit_String, - (uint8_t) i) ? "true" : "false"); - if (i < len - 1) - fprintf(stream, ","); + + /* For each bit, extract as 'true' or 'false'. Separate each bit + * value with comma. Put curly braces around the bit string. + * Ensure to have space for NULL string terminator. + */ + extracted_value = (char *)calloc(1, + len*(sizeof("false")-1) + // assume every bit will be false + (len-1)*(sizeof(",")-1) + // separate every bit by comma + 3); // open/close brace and NULL term + p_extracted_value = extracted_value; + if (extracted_value) + { + *extracted_value = '{'; + extracted_value++; + for (i = 0; i < len; i++) { + sprintf(extracted_value, "%s", + bitstring_bit(&value->type.Bit_String, + (uint8_t) i) ? "true" : "false"); + extracted_value += bitstring_bit(&value->type.Bit_String, + (uint8_t) i) ? sizeof("true")-1 : sizeof("false")-1; + if (i < len - 1) + { + *extracted_value = ','; + extracted_value++; + } + } + *extracted_value = '}'; + extracted_value++; + *extracted_value = '\0'; + } else { + status = false; } - fprintf(stream, "}"); break; case BACNET_APPLICATION_TAG_ENUMERATED: switch (property) { case PROP_OBJECT_TYPE: - if (value->type.Enumerated < MAX_ASHRAE_OBJECT_TYPE) { - fprintf(stream, "%s", - bactext_object_type_name(value->type. - Enumerated)); - } else if (value->type.Enumerated < 128) { - fprintf(stream, "reserved %lu", - (unsigned long) value->type.Enumerated); - } else { - fprintf(stream, "proprietary %lu", - (unsigned long) value->type.Enumerated); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_object_type_names + extracted_value = (char *)calloc(1, 64); + p_extracted_value = extracted_value; + if (extracted_value) + { + if (value->type.Enumerated < MAX_ASHRAE_OBJECT_TYPE) { + sprintf(extracted_value, "%s", + bactext_object_type_name(value->type. + Enumerated)); + } else if (value->type.Enumerated < 128) { + sprintf(extracted_value, "reserved %lu", + (unsigned long) value->type.Enumerated); + } else { + sprintf(extracted_value, "proprietary %lu", + (unsigned long) value->type.Enumerated); + } } + else + status = false; break; case PROP_EVENT_STATE: - fprintf(stream, "%s", - bactext_event_state_name(value->type.Enumerated)); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_event_state_names + extracted_value = (char *)calloc(1, 16); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s", + bactext_event_state_name(value->type.Enumerated)); + } + else + status = false; break; case PROP_UNITS: - if (value->type.Enumerated < 256) { - fprintf(stream, "%s", - bactext_engineering_unit_name(value-> - type.Enumerated)); - } else { - fprintf(stream, "proprietary %lu", - (unsigned long) value->type.Enumerated); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_engineering_unit_names + extracted_value = (char *)calloc(1, 64); + p_extracted_value = extracted_value; + if (extracted_value) + { + if (value->type.Enumerated < 256) { + sprintf(extracted_value, "%s", + bactext_engineering_unit_name(value-> + type.Enumerated)); + } else { + sprintf(extracted_value, "proprietary %lu", + (unsigned long) value->type.Enumerated); + } } + else + status = false; break; case PROP_POLARITY: - fprintf(stream, "%s", - bactext_binary_polarity_name(value-> - type.Enumerated)); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_binary_polarity_names + extracted_value = (char *)calloc(1, 8); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s", + bactext_binary_polarity_name(value-> + type.Enumerated)); + } + else + status = false; break; case PROP_PRESENT_VALUE: case PROP_RELINQUISH_DEFAULT: - if (object_type < PROPRIETARY_BACNET_OBJECT_TYPE) { - fprintf(stream, "%s", - bactext_binary_present_value_name(value->type. - Enumerated)); - } else { - fprintf(stream, "%lu", - (unsigned long) value->type.Enumerated); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_binary_present_value_names + extracted_value = (char *)calloc(1, 16); + p_extracted_value = extracted_value; + if (extracted_value) + { + if (object_type < PROPRIETARY_BACNET_OBJECT_TYPE) { + sprintf(extracted_value, "%s", + bactext_binary_present_value_name(value->type. + Enumerated)); + } else { + sprintf(extracted_value, "%lu", + (unsigned long) value->type.Enumerated); + } } + else + status = false; break; case PROP_RELIABILITY: - fprintf(stream, "%s", - bactext_reliability_name(value->type.Enumerated)); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_reliability_names + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s", + bactext_reliability_name(value->type.Enumerated)); + } + else + status = false; break; case PROP_SYSTEM_STATUS: - fprintf(stream, "%s", - bactext_device_status_name(value-> - type.Enumerated)); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_device_status_names + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s", + bactext_device_status_name(value-> + type.Enumerated)); + } + else + status = false; break; case PROP_SEGMENTATION_SUPPORTED: - fprintf(stream, "%s", - bactext_segmentation_name(value->type.Enumerated)); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_segmentation_names + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s", + bactext_segmentation_name(value->type.Enumerated)); + } + else + status = false; break; case PROP_NODE_TYPE: - fprintf(stream, "%s", - bactext_node_type_name(value->type.Enumerated)); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_node_type_names + extracted_value = (char *)calloc(1, 16); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s", + bactext_node_type_name(value->type.Enumerated)); + } + else + status = false; break; default: - fprintf(stream, "%lu", - (unsigned long) value->type.Enumerated); + /* %lu format should never exceed 32 characters */ + extracted_value = (char *)calloc(1, 32); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%lu", + (unsigned long) value->type.Enumerated); + } + else + status = false; break; } break; case BACNET_APPLICATION_TAG_DATE: - fprintf(stream, "%s, %s ", - bactext_day_of_week_name(value->type.Date.wday), - bactext_month_name(value->type.Date.month)); - if (value->type.Date.day == 255) { - fprintf(stream, "(unspecified), "); - } else { - fprintf(stream, "%u, ", (unsigned) value->type.Date.day); - } - if (value->type.Date.year == 2155) { - fprintf(stream, "(unspecified), "); - } else { - fprintf(stream, "%u", (unsigned) value->type.Date.year); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_day_of_week_names and + // bacnet_month_names + extracted_value = (char *)calloc(1, 64); + p_extracted_value = extracted_value; + if (extracted_value) + { + sprintf(extracted_value, "%s, %s ", + bactext_day_of_week_name(value->type.Date.wday), + bactext_month_name(value->type.Date.month)); + extracted_value += strlen(extracted_value); + if (value->type.Date.day == 255) { + sprintf(extracted_value, "(unspecified), "); + } else { + sprintf(extracted_value, "%u, ", (unsigned) value->type.Date.day); + } + extracted_value += strlen(extracted_value); + if (value->type.Date.year == 2155) { + sprintf(extracted_value, "(unspecified), "); + } else { + sprintf(extracted_value, "%u", (unsigned) value->type.Date.year); + } } + else + status = false; break; case BACNET_APPLICATION_TAG_TIME: - if (value->type.Time.hour == 255) { - fprintf(stream, "**:"); - } else { - fprintf(stream, "%02u:", (unsigned) value->type.Time.hour); - } - if (value->type.Time.min == 255) { - fprintf(stream, "**:"); - } else { - fprintf(stream, "%02u:", (unsigned) value->type.Time.min); - } - if (value->type.Time.sec == 255) { - fprintf(stream, "**."); - } else { - fprintf(stream, "%02u.", (unsigned) value->type.Time.sec); - } - if (value->type.Time.hundredths == 255) { - fprintf(stream, "**"); - } else { - fprintf(stream, "%02u", - (unsigned) value->type.Time.hundredths); + /* We can only have Time in HH:MM:SS.ss format here */ + extracted_value = (char *)calloc(1, sizeof("HH:MM:SS.ss")); + p_extracted_value = extracted_value; + if (extracted_value) + { + if (value->type.Time.hour == 255) { + sprintf(extracted_value, "**:"); + } else { + sprintf(extracted_value, "%02u:", (unsigned) value->type.Time.hour); + } + extracted_value += 3; + if (value->type.Time.min == 255) { + sprintf(extracted_value, "**:"); + } else { + sprintf(extracted_value, "%02u:", (unsigned) value->type.Time.min); + } + extracted_value += 3; + if (value->type.Time.sec == 255) { + sprintf(extracted_value, "**."); + } else { + sprintf(extracted_value, "%02u.", (unsigned) value->type.Time.sec); + } + extracted_value += 3; + if (value->type.Time.hundredths == 255) { + sprintf(extracted_value, "**"); + } else { + sprintf(extracted_value, "%02u", + (unsigned) value->type.Time.hundredths); + } } + else + status = false; break; case BACNET_APPLICATION_TAG_OBJECT_ID: - if (value->type.Object_Id.type < MAX_ASHRAE_OBJECT_TYPE) { - fprintf(stream, "(%s, %lu)", - bactext_object_type_name(value->type.Object_Id.type), - (unsigned long) value->type.Object_Id.instance); - } else if (value->type.Object_Id.type < 128) { - fprintf(stream, "(reserved %u, %lu)", - (unsigned) value->type.Object_Id.type, - (unsigned long) value->type.Object_Id.instance); - } else { - fprintf(stream, "(proprietary %u, %lu)", - (unsigned) value->type.Object_Id.type, - (unsigned long) value->type.Object_Id.instance); + // FIXME: this should really be a dynamic number based + // on the longest string in bacnet_object_type_names + extracted_value = (char *)calloc(1, 64); + p_extracted_value = extracted_value; + if (extracted_value) + { + if (value->type.Object_Id.type < MAX_ASHRAE_OBJECT_TYPE) { + sprintf(extracted_value, "(%s, %lu)", + bactext_object_type_name(value->type.Object_Id.type), + (unsigned long) value->type.Object_Id.instance); + } else if (value->type.Object_Id.type < 128) { + sprintf(extracted_value, "(reserved %u, %lu)", + (unsigned) value->type.Object_Id.type, + (unsigned long) value->type.Object_Id.instance); + } else { + sprintf(extracted_value, "(proprietary %u, %lu)", + (unsigned) value->type.Object_Id.type, + (unsigned long) value->type.Object_Id.instance); + } } + else + status = false; break; default: status = false; @@ -1137,6 +1368,76 @@ bool bacapp_print_value( } } + if (status && p_extracted_value) + { + /* Update the size of the extracted strinig */ + *str_len = strlen(p_extracted_value); + + /* if there is space for the new chunk, append it to passed in string */ + if ( + out_string /* we were asked to store to output */ + && /* and */ + ( + *out_string + /* start of output string */ + *str_len /* plus how much to output */ + ) <= /* does not put us past */ + out_string_end /* the end of the string */ + ) + { + sprintf(*out_string, "%s", p_extracted_value); + *out_string += *str_len; + } + + /* make sure to clean up after ourselves */ + free(p_extracted_value); + } + + return status; +} + +#ifdef BACAPP_PRINT_ENABLED +/* Print the extracted value from the requested BACnet object property to the + * specified stream. If stream is NULL, do not print anything. If extraction + * failed, do not print anything. Return the status of the extraction. + */ +bool bacapp_print_value( + FILE * stream, + BACNET_OBJECT_PROPERTY_VALUE * object_value) +{ + /* first, try to find out how large of a response there is */ + size_t len; + bool status = bacapp_extract_value( + NULL, + NULL, + &len, + object_value); + + if (status && len) + { + /* allocate the space needed to extract the response */ + char *msg = (char *)calloc(1, len+1); + char *p_msg = msg; + + if (msg) + { + status = bacapp_extract_value( + &msg, + msg+len, + &len, + object_value); + + /* print to stream, if requested */ + if (stream) + { + fprintf(stream, p_msg); + } + + /* clean up */ + free(p_msg); + } else + status = false; + } + return status; } #endif