From f4325f00b545d6f33ec79fe8058d0dfad05b2f9f Mon Sep 17 00:00:00 2001 From: Tomasz Kazimierz Motyl Date: Thu, 19 Sep 2024 14:37:36 +0100 Subject: [PATCH] Fixed UTF-8 passwords for DeviceCommunicationControl to hold up to 20 UTF-8 characters (#767) --- src/bacnet/bacstr.c | 23 +++++++++++++++++++++++ src/bacnet/bacstr.h | 2 ++ src/bacnet/basic/object/device.c | 2 +- src/bacnet/basic/service/h_dcc.c | 8 +++++++- src/bacnet/dcc.c | 7 +++++-- src/bacnet/rd.c | 3 ++- 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/bacnet/bacstr.c b/src/bacnet/bacstr.c index c4bb4848..12053b86 100644 --- a/src/bacnet/bacstr.c +++ b/src/bacnet/bacstr.c @@ -587,6 +587,29 @@ bool characterstring_ansi_same( return same_status; } +/** + * Returns number of UTF8 code points in a character string. + * + * @param dest Pointer to the string to count the UTF8 code points. + * + * @return Length of the character string in utf8 codepoints + */ +size_t characterstring_utf8_length(const BACNET_CHARACTER_STRING *str) +{ + size_t count = 0; + int i = 0; + + while ((i < MAX_CHARACTER_STRING_BYTES) && (str->value[i] != '\0')) { + if ((str->value[i] & 0xc0) != 0x80) { + count++; + } + + i++; + } + + return count; +} + /** * Append some characters to the end of the characterstring * diff --git a/src/bacnet/bacstr.h b/src/bacnet/bacstr.h index a10c15ec..3ceedead 100644 --- a/src/bacnet/bacstr.h +++ b/src/bacnet/bacstr.h @@ -118,6 +118,8 @@ const char *characterstring_value(const BACNET_CHARACTER_STRING *char_string); BACNET_STACK_EXPORT size_t characterstring_length(const BACNET_CHARACTER_STRING *char_string); BACNET_STACK_EXPORT +size_t characterstring_utf8_length(const BACNET_CHARACTER_STRING *str); +BACNET_STACK_EXPORT uint8_t characterstring_encoding(const BACNET_CHARACTER_STRING *char_string); BACNET_STACK_EXPORT size_t characterstring_capacity(const BACNET_CHARACTER_STRING *char_string); diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 89bfe347..2c2e1ea2 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -667,7 +667,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) is absent or if the password is incorrect. For those devices that do not require a password, this parameter shall be ignored.*/ if (Reinit_Password && strlen(Reinit_Password) > 0) { - if (characterstring_length(&rd_data->password) > 20) { + if (characterstring_utf8_length(&rd_data->password) > 20) { rd_data->error_class = ERROR_CLASS_SERVICES; rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; } else if (characterstring_ansi_same( diff --git a/src/bacnet/basic/service/h_dcc.c b/src/bacnet/basic/service/h_dcc.c index b2c16105..3f103607 100644 --- a/src/bacnet/basic/service/h_dcc.c +++ b/src/bacnet/basic/service/h_dcc.c @@ -24,7 +24,13 @@ #include "bacnet/basic/services.h" #include "bacnet/datalink/datalink.h" -static char My_Password[32] = "filister"; +/* The byte length of a UTF-8 character can vary. + * In UTF-8, the number of bytes used to represent a character can range from 1 + * to 4 bytes. Commonly used characters in the ASCII set are represented by 1 + * byte,xi while other Unicode characters may require 2, 3, or 4 bytes. Let's + * add space for the null '\0' termination byte. + * */ +static char My_Password[20 * 4 + 1] = "filister"; /** Sets (non-volatile hold) the password to be used for DCC requests. * @param new_password [in] The new DCC password, of up to 31 characters. diff --git a/src/bacnet/dcc.c b/src/bacnet/dcc.c index 9f0ab73c..eddc7901 100644 --- a/src/bacnet/dcc.c +++ b/src/bacnet/dcc.c @@ -8,9 +8,11 @@ #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" +#include "bacnet/bacstr.h" /* BACnet Stack API */ #include "bacnet/bacdcode.h" #include "bacnet/dcc.h" +/** @file dcc.c Enable/Disable Device Communication Control (DCC) */ /* note: the disable and time are not expected to survive over a power cycle or reinitialization. */ @@ -267,7 +269,6 @@ int dcc_decode_service_request( uint32_t len_value_type = 0; BACNET_UNSIGNED_INTEGER decoded_unsigned = 0; uint32_t decoded_enum = 0; - uint32_t password_length = 0; if (apdu && apdu_len_max) { /* Tag 0: timeDuration, in minutes --optional-- */ @@ -321,7 +322,9 @@ int dcc_decode_service_request( &apdu[apdu_len], apdu_len_max - apdu_len, len_value_type, password); if (len > 0) { - password_length = len_value_type - 1; + size_t password_length = + characterstring_utf8_length(password); + /* UTF-8 code points can be up to 4 bytes long */ if ((password_length >= 1) && (password_length <= 20)) { apdu_len += len; } else { diff --git a/src/bacnet/rd.c b/src/bacnet/rd.c index 1d1350f1..367a3a1b 100644 --- a/src/bacnet/rd.c +++ b/src/bacnet/rd.c @@ -51,7 +51,8 @@ int reinitialize_device_encode( } /* password [1] CharacterString (SIZE (1..20)) OPTIONAL */ if (password) { - if ((password->length >= 1) && (password->length <= 20)) { + if ((password->length >= 1) && + (characterstring_utf8_length(password) <= 20)) { len = encode_context_character_string(apdu, 1, password); apdu_len += len; }