Files
bacnet_stack/src/bacnet/bacstr.c
T

2062 lines
55 KiB
C

/**
* @file
* @brief BACnet bitstring, octectstring, and characterstring encode
* and decode functions
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2004
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include <errno.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacstr.h"
#ifndef BACNET_USE_OCTETSTRING /* Do we need any octet strings? */
#define BACNET_USE_OCTETSTRING 1
#endif
#ifndef BACNET_STRING_UTF8_VALIDATION
#define BACNET_STRING_UTF8_VALIDATION 1
#endif
/* check the limits of bitstring capacity */
#if ((MAX_BITSTRING_BYTES * 8) > (UINT8_MAX + 1))
#error "MAX_BITSTRING_BYTES cannot exceed 32!"
#endif
#if (((MAX_BITSTRING_BYTES * 8) > UINT8_MAX) && (UINT_MAX <= UINT8_MAX))
#error "MAX_BITSTRING_BYTES cannot exceed 31!"
#endif
/**
* Initialize a bit string.
*
* @param bit_string Pointer to the bit string structure.
*/
void bitstring_init(BACNET_BIT_STRING *bit_string)
{
unsigned i;
if (bit_string) {
bit_string->bits_used = 0;
for (i = 0; i < MAX_BITSTRING_BYTES; i++) {
bit_string->value[i] = 0;
}
}
}
/**
* Set bits in the bit string.
*
* @param bit_string Pointer to the bit string structure.
* @param bit_number Number of the bit [0..(MAX_BITSTRING_BYTES*8)-1]
* @param value Value 0/1
*/
void bitstring_set_bit(
BACNET_BIT_STRING *bit_string, uint8_t bit_number, bool value)
{
unsigned byte_number = bit_number / 8;
uint8_t bit_mask = 1;
if (bit_string) {
if (byte_number < MAX_BITSTRING_BYTES) {
/* set max bits used */
if (bit_string->bits_used < (bit_number + 1)) {
bit_string->bits_used = bit_number + 1;
}
bit_mask = bit_mask << (bit_number - (byte_number * 8));
if (value) {
bit_string->value[byte_number] |= bit_mask;
} else {
bit_string->value[byte_number] &= (~(bit_mask));
}
}
}
}
/**
* Return the value of a single bit
* out of the bit string.
*
* @param bit_string Pointer to the bit string structure.
* @param bit_number Number of the bit [0..(MAX_BITSTRING_BYTES*8)-1]
*
* @return Value 0/1
*/
bool bitstring_bit(const BACNET_BIT_STRING *bit_string, uint8_t bit_number)
{
bool value = false;
unsigned byte_number = bit_number / 8;
uint8_t bit_mask = 1;
if (bit_string) {
if (bit_number < (MAX_BITSTRING_BYTES * 8)) {
bit_mask = bit_mask << (bit_number - (byte_number * 8));
if (bit_string->value[byte_number] & bit_mask) {
value = true;
}
}
}
return value;
}
/**
* Return the number of bits used.
*
* @param bit_string Pointer to the bit string structure.
*
* @return Bits used [0..(MAX_BITSTRING_BYTES*8)-1]
*/
uint8_t bitstring_bits_used(const BACNET_BIT_STRING *bit_string)
{
return (bit_string ? bit_string->bits_used : 0);
}
/**
* @brief Write the amount of bits used in the bit string structure.
* @param bit_string Pointer to the bit string structure.
* @param bits_used Number of bits in this bitstring
* @return true on success or false on error.
*/
bool bitstring_bits_used_set(BACNET_BIT_STRING *bit_string, uint8_t bits_used)
{
bool status = false;
if (bit_string) {
bit_string->bits_used = bits_used;
status = true;
}
return status;
}
/**
* Returns the number of bytes that a bit string is using.
*
* @param bit_string Pointer to the bit string structure.
*
* @return Bytes used [0..MAX_BITSTRING_BYTES]
*/
uint8_t bitstring_bytes_used(const BACNET_BIT_STRING *bit_string)
{
uint8_t len = 0; /* return value */
uint8_t used_bytes = 0;
uint8_t last_bit = 0;
if (bit_string && bit_string->bits_used) {
last_bit = bit_string->bits_used - 1;
used_bytes = last_bit / 8;
/* add one for the first byte */
used_bytes++;
len = used_bytes;
}
return len;
}
/**
* Returns an octet at the given bit position.
*
* @param bit_string Pointer to the bit string structure.
* @param octet_index Byte index of the octet [0..MAX_BITSTRING_BYTES-1]
*
* @return Value of the octet.
*/
uint8_t
bitstring_octet(const BACNET_BIT_STRING *bit_string, uint8_t octet_index)
{
uint8_t octet = 0;
if (bit_string) {
if (octet_index < MAX_BITSTRING_BYTES) {
octet = bit_string->value[octet_index];
}
}
return octet;
}
/**
* Set an octet at the given bit position.
*
* @param bit_string Pointer to the bit string structure.
* @param index Byte index of the octet [0..MAX_BITSTRING_BYTES-1]
* @param octet Octet value
*
* @return true on success, false otherwise.
*/
bool bitstring_set_octet(
BACNET_BIT_STRING *bit_string, uint8_t index, uint8_t octet)
{
bool status = false;
if (bit_string) {
if (index < MAX_BITSTRING_BYTES) {
bit_string->value[index] = octet;
status = true;
}
}
return status;
}
/**
* Write the amount of bits used in the bit
* string structure.
*
* @param bit_string Pointer to the bit string structure.
* @param bytes_used Count of bytes used.
* @param unused_bits Count of remaining unused bits in
* the last byte.
*
* @return true on success or false on error.
*/
bool bitstring_set_bits_used(
BACNET_BIT_STRING *bit_string, uint8_t bytes_used, uint8_t unused_bits)
{
bool status = false;
if (bit_string && bytes_used) {
bit_string->bits_used = bytes_used * 8;
bit_string->bits_used -= unused_bits;
status = true;
}
return status;
}
/**
* Return the capacity of the bit string.
*
* @param bit_string Pointer to the bit string structure.
*
* @return Capacitiy in bits [0..(MAX_BITSTRING_BYTES*8)]
*/
unsigned bitstring_bits_capacity(const BACNET_BIT_STRING *bit_string)
{
if (bit_string) {
return min((MAX_BITSTRING_BYTES * 8), (UINT8_MAX + 1));
} else {
return 0;
}
}
/**
* Copy bits from one bit string to another.
*
* @param dest Pointer to the destination bit string structure.
* @param src Pointer to the source bit string structure.
*
* @return true on success, false otherwise.
*/
bool bitstring_copy(BACNET_BIT_STRING *dest, const BACNET_BIT_STRING *src)
{
unsigned i;
bool status = false;
if (dest && src) {
dest->bits_used = src->bits_used;
for (i = 0; i < MAX_BITSTRING_BYTES; i++) {
dest->value[i] = src->value[i];
}
status = true;
}
return status;
}
/**
* Returns true if the same length and contents.
*
* @param bitstring1 Pointer to the first bit string structure.
* @param bitstring2 Pointer to the second bit string structure.
*
* @return true if the content of both bit strings are
* the same, false otherwise.
*/
bool bitstring_same(
const BACNET_BIT_STRING *bitstring1, const BACNET_BIT_STRING *bitstring2)
{
int i; /* loop counter */
int bytes_used = 0;
uint8_t compare_mask = 0;
if (bitstring1 && bitstring2) {
bytes_used = (int)(bitstring1->bits_used / 8);
if ((bitstring1->bits_used == bitstring2->bits_used) &&
(bytes_used <= MAX_BITSTRING_BYTES)) {
/* compare fully used bytes */
for (i = 0; i < bytes_used; i++) {
if (bitstring1->value[i] != bitstring2->value[i]) {
return false;
}
}
/* compare only the relevant bits of last partly used byte */
compare_mask = 0xFF >> (8 - (bitstring1->bits_used % 8));
if ((bitstring1->value[bytes_used] & compare_mask) !=
(bitstring2->value[bytes_used] & compare_mask)) {
return false;
} else {
return true;
}
}
}
return false;
}
/**
* Converts an null terminated ASCII string to an bitstring.
*
* Expects "1,0,1,0,1,1" or "101011" as the bits
*
* @param bit_string Pointer to the bit string structure.
* @param ascii Pointer to a zero terminated string, made up from
* '0' and '1' like "010010011", that shall be
* converted into a bit string.
*
* @return true if successfully converted and fits; false if too long.
*/
bool bitstring_init_ascii(BACNET_BIT_STRING *bit_string, const char *ascii)
{
bool status = false; /* return value */
unsigned index = 0; /* offset into buffer */
uint8_t bit_number = 0;
if (bit_string) {
bitstring_init(bit_string);
if (ascii[0] == 0) {
/* nothing to decode, so success! */
status = true;
} else {
while (ascii[index] != 0) {
if (bit_number >= bitstring_bits_capacity(bit_string)) {
/* too long of a string */
status = false;
break;
}
if (ascii[index] == '1') {
bitstring_set_bit(bit_string, bit_number, true);
bit_number++;
status = true;
} else if (ascii[index] == '0') {
bitstring_set_bit(bit_string, bit_number, false);
bit_number++;
status = true;
} else {
/* skip non-numeric or alpha */
index++;
continue;
}
/* next character */
index++;
}
}
}
return status;
}
#define CHARACTER_STRING_CAPACITY (MAX_CHARACTER_STRING_BYTES - 1)
/**
* Initialize a BACnet character string.
* Returns false if the string exceeds capacity.
* Initialize by using value=NULL
*
* @param char_string Pointer to the BACnet string
* @param encoding Encoding that shall be used
* like CHARACTER_UTF8
* @param value C-string used to initialize the object
* @param length C-String length in characters.
*
* @return true on success, false if the string exceeds capacity.
*/
bool characterstring_init(
BACNET_CHARACTER_STRING *char_string,
uint8_t encoding,
const char *value,
size_t length)
{
bool status = false; /* return value */
size_t i; /* counter */
if (char_string) {
char_string->length = 0;
char_string->encoding = encoding;
/* save a byte at the end for NULL -
note: assumes printable characters */
if (length <= CHARACTER_STRING_CAPACITY) {
if (value) {
for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) {
if (i < length) {
char_string->value[char_string->length] = value[i];
char_string->length++;
} else {
char_string->value[i] = 0;
}
}
} else {
for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) {
char_string->value[i] = 0;
}
}
status = true;
}
}
return status;
}
/**
* Initialize a BACnet character string.
* Returns false if the string exceeds capacity.
* Initialize by using value=NULL
*
* @param char_string Pointer to the BACnet string
* @param value C-string used to initialize the object
* @param tmax C-String length in characters.
*
* @return true/false
*/
bool characterstring_init_ansi_safe(
BACNET_CHARACTER_STRING *char_string, const char *value, size_t tmax)
{
return characterstring_init(
char_string, CHARACTER_ANSI_X34, value,
value ? bacnet_strnlen(value, tmax) : 0);
}
/**
* Initialize a BACnet character string.
* Returns false if the string exceeds capacity.
* Initialize by using value=NULL
*
* @param char_string Pointer to the BACnet string
* @param value C-string used to initialize the object
*
* @return true/false
*/
bool characterstring_init_ansi(
BACNET_CHARACTER_STRING *char_string, const char *value)
{
return characterstring_init(
char_string, CHARACTER_ANSI_X34, value, value ? strlen(value) : 0);
}
/**
* Copy a character string.
*
* @param dest Pointer to the destination string.
* @param src Pointer to the source string.
*
* @return true/false
*/
bool characterstring_copy(
BACNET_CHARACTER_STRING *dest, const BACNET_CHARACTER_STRING *src)
{
if (dest && src) {
return characterstring_init(
dest, characterstring_encoding(src), characterstring_value(src),
characterstring_length(src));
}
return false;
}
/**
* Copy a character string into a C-string.
*
* @param dest Pointer to the destination C-string buffer.
* @param dest_max_len Size of the destination C-string buffer.
* @param src Pointer to the source BACnet string.
*
* @return true/false
*/
bool characterstring_ansi_copy(
char *dest, size_t dest_max_len, const BACNET_CHARACTER_STRING *src)
{
size_t i; /* counter */
if (dest && src) {
if ((src->encoding == CHARACTER_ANSI_X34) &&
(src->length < dest_max_len)) {
for (i = 0; i < dest_max_len; i++) {
if (i < src->length) {
dest[i] = src->value[i];
} else {
dest[i] = 0;
}
}
return true;
}
}
return false;
}
/**
* @brief Copy a BACnetCharacterString into a buffer with null padding
* @param dest Pointer to the destination buffer.
* @param dest_max_len Size of the destination buffer.
* @param src Pointer to the source BACnetCharacterString.
* @return Length of the copied BACnetCharacterString, or 0 if the string
* exceeds the destination buffer size.
*/
size_t characterstring_copy_value(
char *dest, size_t dest_max_len, const BACNET_CHARACTER_STRING *src)
{
size_t i = 0, length = 0;
if (dest && src) {
if (src->length < dest_max_len) {
length = src->length;
for (i = 0; i < dest_max_len; i++) {
if (i < src->length) {
dest[i] = src->value[i];
} else {
dest[i] = 0;
}
}
}
}
return length;
}
/**
* Returns true if the character encoding and string
* contents are the same.
*
* @param dest Pointer to the first string to test.
* @param src Pointer to the second string to test.
*
* @return true if the character encoding and string contents are the same
*/
bool characterstring_same(
const BACNET_CHARACTER_STRING *dest, const BACNET_CHARACTER_STRING *src)
{
size_t i; /* counter */
bool same_status = false;
if (src && dest) {
if ((src->encoding == dest->encoding) &&
(src->length == dest->length) &&
(src->length <= MAX_CHARACTER_STRING_BYTES)) {
same_status = true;
for (i = 0; i < src->length; i++) {
if (src->value[i] != dest->value[i]) {
same_status = false;
break;
}
}
}
} else if (src) {
if (src->length == 0) {
same_status = true;
}
} else if (dest) {
if (dest->length == 0) {
same_status = true;
}
}
return same_status;
}
/**
* Returns true if the BACnet string and the C-string
* contents are the same.
*
* @param dest Pointer to the first string to test.
* @param src Pointer to the second string to test.
*
* @return true if the character encoding and string contents are the same
*/
bool characterstring_ansi_same(
const BACNET_CHARACTER_STRING *src1, const char *src2)
{
size_t i; /* counter */
bool same_status = false;
if (src1 && src2) {
if ((src1->encoding == CHARACTER_ANSI_X34) &&
(src1->length == strlen(src2)) &&
(src1->length <= MAX_CHARACTER_STRING_BYTES)) {
same_status = true;
for (i = 0; i < src1->length; i++) {
if (src2[i] != src1->value[i]) {
same_status = false;
break;
}
}
}
} else if (src2) {
/* NULL matches an empty string in our world */
if (strlen(src2) == 0) {
same_status = true;
}
} else if (src1) {
if (src1->length == 0) {
same_status = true;
}
}
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
*
* @param char_string Pointer to the BACnet string to which
* the content of the C-string shall be added.
* @param value Pointer to the C-String to be added.
* @param length Count of characters to add.
*
* @param src Pointer to the first string to test.
*
* @return false if the string exceeds capacity.
*/
bool characterstring_append(
BACNET_CHARACTER_STRING *char_string, const char *value, size_t length)
{
size_t i; /* counter */
bool status = false; /* return value */
if (char_string) {
if ((length + char_string->length) <= CHARACTER_STRING_CAPACITY) {
for (i = 0; i < length; i++) {
char_string->value[char_string->length] = value[i];
char_string->length++;
}
status = true;
}
}
return status;
}
/**
* @brief This function sets a new length without changing
* the value. If length exceeds capacity, no modification
* happens and function returns false.
*
* @return true on success, false if the string exceeds
* capacity.
*/
bool characterstring_truncate(
BACNET_CHARACTER_STRING *char_string, size_t length)
{
bool status = false; /* return value */
if (char_string) {
if (length <= CHARACTER_STRING_CAPACITY) {
char_string->length = length;
status = true;
}
}
return status;
}
/**
* @brief Returns the pointer to the C-string for the given BACnet string.
*
* @param char_string Pointer to the character string.
*
* @return Pointer to a zero-terminated C-string.
*/
const char *characterstring_value(const BACNET_CHARACTER_STRING *char_string)
{
const char *value = NULL;
if (char_string) {
value = char_string->value;
}
return value;
}
/**
* @brief Returns the length for the given BACnet string.
*
* @param char_string Pointer to the character string.
*
* @return Length of the character string, but
* maximum MAX_CHARACTER_STRING_BYTES.
*/
size_t characterstring_length(const BACNET_CHARACTER_STRING *char_string)
{
size_t length = 0;
if (char_string) {
length = char_string->length;
/* Length within bounds? */
if (length > CHARACTER_STRING_CAPACITY) {
length = CHARACTER_STRING_CAPACITY;
}
}
return length;
}
/**
* @brief Returns the possible capacity for the given BACnet string.
*
* @param char_string Pointer to the character string.
*
* @return MAX_CHARACTER_STRING_BYTES
*/
size_t characterstring_capacity(const BACNET_CHARACTER_STRING *char_string)
{
size_t length = 0;
if (char_string) {
length = CHARACTER_STRING_CAPACITY;
}
return length;
}
/**
* @brief Returns the character encoding for the given BACnet string.
*
* @param char_string Pointer to the character string.
*
* @return Encoding, like CHARACTER_ANSI_X34
*/
uint8_t characterstring_encoding(const BACNET_CHARACTER_STRING *char_string)
{
uint8_t encoding = 0;
if (char_string) {
encoding = char_string->encoding;
}
return encoding;
}
/**
* @brief Set the character encoding for the given BACnet string.
*
* @param char_string Pointer to the character string.
* @param Encoding, like CHARACTER_ANSI_X34
*
* @return true/false on error
*/
bool characterstring_set_encoding(
BACNET_CHARACTER_STRING *char_string, uint8_t encoding)
{
bool status = false;
if (char_string) {
char_string->encoding = encoding;
status = true;
}
return status;
}
/**
* @brief Returns true if string is printable.
*
* Used to assist in the requirement that
* "The set of characters used in the Object_Name shall be
* restricted to printable characters."
*
* Printable character: a character that represents a printable
* symbol as opposed to a device control character. These
* include, but are not limited to, upper- and lowercase letters,
* punctuation marks, and mathematical symbols. The exact set
* depends upon the character set being used. In ANSI X3.4 the
* printable characters are represented by single octets in the range
* X'20' - X'7E'.
*
* @param char_string Pointer to the character string.
*
* @return true/false on error
*/
bool characterstring_printable(const BACNET_CHARACTER_STRING *char_string)
{
bool status = false; /* return value */
size_t i; /* counter */
size_t imax;
char chr;
if (char_string) {
if (char_string->encoding == CHARACTER_ANSI_X34) {
status = true;
imax = char_string->length;
if (imax > CHARACTER_STRING_CAPACITY) {
imax = CHARACTER_STRING_CAPACITY;
}
for (i = 0; i < imax; i++) {
chr = char_string->value[i];
if ((chr < 0x20) || (chr > 0x7E)) {
status = false;
break;
}
}
} else {
status = true;
}
}
return status;
}
#if BACNET_STRING_UTF8_VALIDATION
/* Basic UTF-8 manipulation routines
* by Jeff Bezanson
* placed in the public domain Fall 2005 */
static const char trailingBytesForUTF8[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
};
/**
* @brief Based on the valid_utf8 routine from the PCRE library by Philip Hazel
* length is in bytes, since without knowing whether the string is valid
* it's hard to know how many characters there are!
*
* @param str Pointer to the character string.
* @param length Count of bytes to check. The count of bytes
* does not necessarily match the count of chars.
*
* @return true if the string is valid, false otherwise.
*/
bool utf8_isvalid(const char *str, size_t length)
{
const unsigned char *p, *pend;
unsigned char c;
size_t ab;
/* An empty string is valid. */
if (length == 0) {
return true;
}
/* Check pointer. */
if (!str) {
return false;
}
/* Check characters. */
pend = (const unsigned char *)str + length;
for (p = (const unsigned char *)str; p < pend; p++) {
c = *p;
/* null in middle of string */
if (c == 0) {
return false;
}
/* ASCII character */
if (c < 128) {
continue;
}
if ((c & 0xc0) != 0xc0) {
return false;
}
ab = (size_t)trailingBytesForUTF8[c];
if (length < ab) {
return false;
}
length -= ab;
p++;
/* Check top bits in the second byte */
if ((*p & 0xc0) != 0x80) {
return false;
}
/* Check for overlong sequences for each different length */
switch (ab) {
/* Check for xx00 000x */
case 1:
if ((c & 0x3e) == 0) {
return false;
}
continue; /* We know there aren't any more bytes to check */
/* Check for 1110 0000, xx0x xxxx */
case 2:
if (c == 0xe0 && (*p & 0x20) == 0) {
return false;
}
break;
/* Check for 1111 0000, xx00 xxxx */
case 3:
if (c == 0xf0 && (*p & 0x30) == 0) {
return false;
}
break;
/* Check for 1111 1000, xx00 0xxx */
case 4:
if (c == 0xf8 && (*p & 0x38) == 0) {
return false;
}
break;
/* Check for leading 0xfe or 0xff,
and then for 1111 1100, xx00 00xx */
case 5:
if (c == 0xfe || c == 0xff || (c == 0xfc && (*p & 0x3c) == 0)) {
return false;
}
break;
default:
break;
}
/* Check for valid bytes after the 2nd, if any; all must start 10 */
while (--ab > 0) {
if ((*(++p) & 0xc0) != 0x80) {
return false;
}
}
}
return true;
}
#else
bool utf8_isvalid(const char *str, size_t length)
{
(void)str;
(void)length;
return true;
}
#endif
/**
* Check if the character string is valid or not.
*
* @param char_string Pointer to the character string.
*
* @return true if the string is valid, false otherwise.
*/
bool characterstring_valid(const BACNET_CHARACTER_STRING *char_string)
{
bool valid = false; /* return value */
if (char_string) {
if (char_string->encoding < MAX_CHARACTER_STRING_ENCODING) {
if (char_string->encoding == CHARACTER_UTF8) {
/*UTF8 check*/
if (utf8_isvalid(char_string->value, char_string->length)) {
valid = true;
}
} else {
/*non UTF8*/
valid = true;
}
}
}
return valid;
}
#if BACNET_USE_OCTETSTRING
/**
* @brief Initialize an octet string with the given bytes or
* zeros, if NULL for the value is provided.
*
* @param octet_string Pointer to the octet string.
* @param value Pointer to the bytes to be copied to the octet
* string or NULL to initialize the octet string.
* @param length Count of bytes used to fill the octet string.
*
* @return true on success, false if the string exceeds capacity.
*/
bool octetstring_init(
BACNET_OCTET_STRING *octet_string, const uint8_t *value, size_t length)
{
bool status = false; /* return value */
size_t i; /* counter */
uint8_t *pb = NULL;
if (octet_string && (length <= MAX_OCTET_STRING_BYTES)) {
octet_string->length = 0;
if (value) {
pb = octet_string->value;
for (i = 0; i < MAX_OCTET_STRING_BYTES; i++) {
if (i < length) {
*pb = value[i];
} else {
*pb = 0;
}
pb++;
}
octet_string->length = length;
} else {
memset(octet_string->value, 0, MAX_OCTET_STRING_BYTES);
}
status = true;
}
return status;
}
/** @brief Converts an null terminated ASCII Hex string to an octet string.
*
* @param octet_string Pointer to the octet string.
* @param ascii_hex Pointer to the HEX-ASCII string.
*
* @return true if successfully converted and fits; false if too long */
bool octetstring_init_ascii_hex(
BACNET_OCTET_STRING *octet_string, const char *ascii_hex)
{
bool status = false; /* return value */
unsigned index = 0; /* offset into buffer */
uint8_t value = 0;
char hex_pair_string[3] = "";
if (octet_string && ascii_hex) {
octet_string->length = 0;
if (ascii_hex[0] == 0) {
/* nothing to decode, so success! */
status = true;
} else {
while (ascii_hex[index] != 0) {
if (!isalnum((int)ascii_hex[index])) {
/* skip non-numeric or alpha */
index++;
continue;
}
if (ascii_hex[index + 1] == 0) {
/* not a hex pair */
status = false;
break;
}
hex_pair_string[0] = ascii_hex[index];
hex_pair_string[1] = ascii_hex[index + 1];
value = (uint8_t)strtol(hex_pair_string, NULL, 16);
if (octet_string->length <= MAX_OCTET_STRING_BYTES) {
octet_string->value[octet_string->length] = value;
octet_string->length++;
/* at least one pair was decoded */
status = true;
} else {
/* too long */
status = false;
break;
}
/* set up for next pair */
index += 2;
}
}
}
return status;
}
/**
* @brief Converts an null terminated ASCII Hex EPICS formatted string
* to an octet string.
* @details format: X'c0:a8:00:0f'
* @param octet_string Pointer to the octet string.
* @param arg Pointer to the HEX-ASCII EPICS format string
* @return true if successfully converted and fits; false if too long
*/
bool octetstring_init_ascii_epics(
BACNET_OCTET_STRING *octet_string, const char *arg)
{
bool status = false; /* return value */
if (!octet_string) {
return false;
}
if (!arg) {
return false;
}
if (bacnet_strnicmp(arg, "X'", 2) == 0) {
status = octetstring_init_ascii_hex(octet_string, arg + 2);
}
return status;
}
/**
* Copy an octet string from source to destination.
*
* @param dest Pointer to the destination octet string.
* @param src Pointer to the source octet string.
*
* @return true on success, false otherwise.
*/
bool octetstring_copy(BACNET_OCTET_STRING *dest, const BACNET_OCTET_STRING *src)
{
return octetstring_init(
dest, octetstring_value((BACNET_OCTET_STRING *)src),
octetstring_length(src));
}
/**
* @brief Copy bytes from the octet string to a byte buffer.
*
* @param dest Pointer to the byte buffer.
* @param length Bytes to be copied from the
* octet string to the buffer.
* @param src Pointer to the octet string.
*
* @return Returns the number of bytes copied, or 0 if
* the dest cannot hold entire octetstring value.
*/
size_t octetstring_copy_value(
uint8_t *dest, size_t length, const BACNET_OCTET_STRING *src)
{
size_t bytes_copied = 0;
size_t i; /* counter */
if (src && dest) {
if (src->length <= length) {
for (i = 0; i < src->length; i++) {
dest[i] = src->value[i];
}
bytes_copied = src->length;
}
}
return bytes_copied;
}
/**
* @brief Append bytes to the end of the octet string.
*
* @param octet_string Pointer to the octet string.
* @param value Pointer to the byte buffer to be appended.
* @param length Bytes to be appended.
*
* @return false if the string exceeds capacity.
*/
bool octetstring_append(
BACNET_OCTET_STRING *octet_string, const uint8_t *value, size_t length)
{
size_t i; /* counter */
bool status = false; /* return value */
if (octet_string) {
if ((length + octet_string->length) <= MAX_OCTET_STRING_BYTES) {
for (i = 0; i < length; i++) {
octet_string->value[octet_string->length] = value[i];
octet_string->length++;
}
status = true;
}
}
return status;
}
/**
* @brief This function sets a new length without changing the value.
* If length exceeds capacity, no modification happens and the
* function returns false.
*
* @param octet_string Pointer to the octet string.
* @param length New length the octet string is truncated to.
*
* @return tur on success, false otherwise.
*/
bool octetstring_truncate(BACNET_OCTET_STRING *octet_string, size_t length)
{
bool status = false; /* return value */
if (octet_string) {
if (length <= MAX_OCTET_STRING_BYTES) {
octet_string->length = length;
status = true;
}
}
return status;
}
/**
* @brief Returns a pointer to the value (data) of
* the given octet string.
*
* @param octet_string Pointer to the octet string.
*
* @return Value as a pointer to a byte array or NULL on error.
*/
uint8_t *octetstring_value(BACNET_OCTET_STRING *octet_string)
{
uint8_t *value = NULL;
if (octet_string) {
value = octet_string->value;
}
return value;
}
/**
* @brief Returns the length in bytes of
* the given octet string.
*
* @param octet_string Pointer to the octet string.
*
* @return Length in bytes. Returns always 0 on error.
*/
size_t octetstring_length(const BACNET_OCTET_STRING *octet_string)
{
size_t length = 0;
if (octet_string) {
length = octet_string->length;
/* Force length to be within bounds. */
if (length > MAX_OCTET_STRING_BYTES) {
length = MAX_OCTET_STRING_BYTES;
}
}
return length;
}
/**
* @brief Returns the maximum capacity of an octet string.
*
* @param octet_string Pointer to the octet string.
*
* @return Capacity in bytes. Returns always 0 on error.
*/
size_t octetstring_capacity(const BACNET_OCTET_STRING *octet_string)
{
size_t length = 0;
if (octet_string) {
length = MAX_OCTET_STRING_BYTES;
}
return length;
}
/**
* @brief Returns true if the same length and contents.
*
* @param octet_string1 Pointer to the first octet string.
* @param octet_string2 Pointer to the second octet string.
*
* @return true if the octet strings are the same, false otherwise.
*/
bool octetstring_value_same(
const BACNET_OCTET_STRING *octet_string1,
const BACNET_OCTET_STRING *octet_string2)
{
size_t i = 0; /* loop counter */
if (octet_string1 && octet_string2) {
if ((octet_string1->length == octet_string2->length) &&
(octet_string1->length <= MAX_OCTET_STRING_BYTES)) {
for (i = 0; i < octet_string1->length; i++) {
if (octet_string1->value[i] != octet_string2->value[i]) {
return false;
}
}
return true;
}
}
return false;
}
#endif
static int bacnet_strnicmp_internal(
const char *a, const char *b, size_t length, bool case_insensitive)
{
int twin_a, twin_b;
if (a == NULL) {
return -1;
}
if (b == NULL) {
return 1;
}
if (length == 0) {
length = strlen(a);
}
do {
twin_a = *(const unsigned char *)a;
twin_b = *(const unsigned char *)b;
if (case_insensitive) {
twin_a = tolower(toupper(twin_a));
twin_b = tolower(toupper(twin_b));
}
a++;
b++;
length--;
} while ((twin_a == twin_b) && (twin_a != '\0') && (length > 0));
return twin_a - twin_b;
}
/**
* @brief Compare two strings, case sensitive
* @param a - first string
* @param b - second string
* @return 0 if the strings are equal, non-zero if not
*/
int bacnet_strcmp(const char *a, const char *b)
{
return bacnet_strnicmp_internal(a, b, 0, false);
}
/**
* @brief Compare two strings, case insensitive
* @param a - first string
* @param b - second string
* @return 0 if the strings are equal, non-zero if not
* @note The stricmp() function is not included in the C standard.
*/
int bacnet_stricmp(const char *a, const char *b)
{
return bacnet_strnicmp_internal(a, b, 0, true);
}
/**
* @brief Compare two strings, case sensitive, with length limit
* @details The strncmp() function compares, at most, the first n characters
* of string1 and string2 with sensitivity to case.
*
* The function operates on null terminated strings.
* The string arguments to the function are expected to contain
* a null character (\0) marking the end of the string.
*
* @param a - first string
* @param b - second string
* @param length - maximum length to compare
* @return 0 if the strings are equal, non-zero if not
*/
int bacnet_strncmp(const char *a, const char *b, size_t length)
{
return bacnet_strnicmp_internal(a, b, length, false);
}
/**
* @brief Compare two strings, case insensitive, with length limit
* @details The strnicmp() function compares, at most, the first n characters
* of string1 and string2 without sensitivity to case.
*
* The function operates on null terminated strings.
* The string arguments to the function are expected to contain
* a null character (\0) marking the end of the string.
*
* @param a - first string
* @param b - second string
* @param length - maximum length to compare
* @return 0 if the strings are equal, non-zero if not
* @note The strnicmp() function is not included in the C standard.
*/
int bacnet_strnicmp(const char *a, const char *b, size_t length)
{
return bacnet_strnicmp_internal(a, b, length, true);
}
/**
* @brief Return the length of a string, within a maximum length
* @note The strnlen function is non-standard and not available in
* all libc implementations. This function is a workaround for that.
* @details The strnlen function computes the smaller of the number
* of characters in the array pointed to by s, not including any
* terminating null character, or the value of the maxlen argument.
* The strnlen function examines no more than maxlen bytes of the
* array pointed to by s.
* @param s - string to check
* @param maxlen - maximum length to check
* @return The strnlen function returns the number of bytes that
* precede the first null character in the array pointed to by s,
* if s contains a null character within the first maxlen characters;
* otherwise, it returns maxlen.
*/
size_t bacnet_strnlen(const char *str, size_t maxlen)
{
const char *p = memchr(str, 0, maxlen);
if (p == NULL) {
return maxlen;
}
return (p - str);
}
/**
* @brief Attempt to convert a numeric string into a unsigned long value
* @param str - string to convert
* @param long_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_strtoul(const char *str, unsigned long *long_value)
{
char *endptr;
unsigned long value;
errno = 0;
value = strtoul(str, &endptr, 0);
if (endptr == str) {
/* Conversion was not possible */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (long_value) {
*long_value = value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a signed long integer
* @param str - string to convert
* @param long_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_strtol(const char *str, long *long_value)
{
char *endptr;
long value;
errno = 0;
value = strtol(str, &endptr, 0);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (long_value) {
*long_value = value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a finite floating point value
* @param str - string to convert
* @param float_value - where to put the converted value
* @return true if converted and finite value is set
* @return false if not converted and finite value is not set
*/
bool bacnet_strtof(const char *str, float *float_value)
{
char *endptr;
float value;
errno = 0;
value = strtof(str, &endptr);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (float_value) {
*float_value = value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a finite double precision
* floating point value
* @param str - string to convert
* @param double_value - where to put the converted value
* @return true if converted and finite value is set
* @return false if not converted and finite value is not set
*/
bool bacnet_strtod(const char *str, double *double_value)
{
char *endptr;
double value;
errno = 0;
value = strtod(str, &endptr);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (double_value) {
*double_value = value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a finite double precision
* floating point value
* @param str - string to convert
* @param long_double_value - where to put the converted value
* @return true if converted and finite value is set
* @return false if not converted and finite value is not set
*/
bool bacnet_strtold(const char *str, long double *long_double_value)
{
char *endptr;
long double value;
errno = 0;
value = strtold(str, &endptr);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (long_double_value) {
*long_double_value = value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a uint8_t value
* @param str - string to convert
* @param uint32_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_string_to_uint8(const char *str, uint8_t *uint8_value)
{
char *endptr;
unsigned long value;
errno = 0;
value = strtoul(str, &endptr, 0);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (value > UINT8_MAX) {
/* If the value is outside the range of this datatype */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (uint8_value) {
*uint8_value = (uint8_t)value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a uint16_t value
* @param str - string to convert
* @param uint32_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_string_to_uint16(const char *str, uint16_t *uint16_value)
{
char *endptr;
unsigned long value;
errno = 0;
value = strtoul(str, &endptr, 0);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (value > UINT16_MAX) {
/* If the value is outside the range of this datatype */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (uint16_value) {
*uint16_value = (uint16_t)value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into a uint32_t value
* @param str - string to convert
* @param uint32_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_string_to_uint32(const char *str, uint32_t *uint32_value)
{
char *endptr;
unsigned long value;
errno = 0;
value = strtoul(str, &endptr, 0);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (value > UINT32_MAX) {
/* If the value is outside the range of this datatype */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (uint32_value) {
*uint32_value = (uint32_t)value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into an int32_t value
* @param str - string to convert
* @param int32_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_string_to_int32(const char *str, int32_t *int32_value)
{
char *endptr;
long value;
errno = 0;
value = strtol(str, &endptr, 0);
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (value < INT32_MIN || value > INT32_MAX) {
/* The correct value is outside the range of this data type */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (int32_value) {
*int32_value = (int32_t)value;
}
return true;
}
/**
* @brief Attempt to convert a numeric string into an bool value
* @param str - string to convert
* @param bool_value - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_string_to_bool(const char *str, bool *bool_value)
{
bool status = false;
long long_value = 0;
if (bacnet_stricmp(str, "true") == 0 ||
bacnet_stricmp(str, "active") == 0) {
status = true;
if (bool_value) {
*bool_value = true;
}
} else if (
bacnet_stricmp(str, "false") == 0 ||
bacnet_stricmp(str, "inactive") == 0) {
status = true;
if (bool_value) {
*bool_value = false;
}
} else {
status = bacnet_strtol(str, &long_value);
if (!status) {
return false;
}
if (bool_value) {
if (long_value) {
*bool_value = true;
} else {
*bool_value = false;
}
}
}
return status;
}
/**
* @brief Attempt to convert a numeric string into a unsigned long value
* @param str - string to convert
* @param unsigned_int - where to put the converted value
* @return true if converted and value is set
* @return false if not converted and value is not set
*/
bool bacnet_string_to_unsigned(
const char *str, BACNET_UNSIGNED_INTEGER *unsigned_int)
{
char *endptr;
#ifdef UINT64_MAX
unsigned long long value;
#else
unsigned long value;
#endif
errno = 0;
#ifdef UINT64_MAX
value = strtoull(str, &endptr, 0);
#else
value = strtoul(str, &endptr, 0);
#endif
if (endptr == str) {
/* No digits found */
return false;
}
if (errno == ERANGE) {
/* Conversion is outside the range of representable values
so errno set to [ERANGE] */
return false;
}
if (*endptr != '\0') {
/* Extra text found */
return false;
}
if (unsigned_int) {
*unsigned_int = (BACNET_UNSIGNED_INTEGER)value;
}
return true;
}
/**
* @brief General purpose print formatter which returns the formatted string
* @param buffer - destination string
* @param count - length of the destination string
* @param format - format string
* @return character string buffer
*/
char *
bacnet_snprintf_to_ascii(char *buffer, size_t size, const char *format, ...)
{
va_list args;
va_start(args, format);
/* The vsnprintf function always writes a null terminator,
even if it truncates the output. */
(void)vsnprintf(buffer, size, format, args);
va_end(args);
return buffer;
}
/**
* @brief Convert a value into a string and return the base-10 string
* @param value Number to be converted.
* @param buffer Buffer that holds the result of the conversion.
* @param size Length of the buffer in units of the character type.
* @param precision Number of decimal places
* @return character string buffer
*/
char *bacnet_dtoa(double value, char *buffer, size_t size, unsigned precision)
{
return bacnet_snprintf_to_ascii(buffer, size, "%.*f", precision, value);
}
/**
* @brief Convert a value into a string and return the base-10 string
* @param value Number to be converted.
* @param buffer Buffer that holds the result of the conversion.
* @param size Length of the buffer in units of the character type.
* @return character string buffer
*/
char *bacnet_itoa(int value, char *buffer, size_t size)
{
return bacnet_snprintf_to_ascii(buffer, size, "%d", value);
}
/**
* @brief Convert a value into a string and return the base-10 string
* @param value Number to be converted.
* @param buffer Buffer that holds the result of the conversion.
* @param size Length of the buffer in units of the character type.
* @return character string buffer
*/
char *bacnet_ltoa(long value, char *buffer, size_t size)
{
return bacnet_snprintf_to_ascii(buffer, size, "%ld", value);
}
/**
* @brief Convert a value into a string and return the base-10 string
* @param value Number to be converted.
* @param buffer Buffer that holds the result of the conversion.
* @param size Length of the buffer in units of the character type.
* @return character string buffer
*/
char *bacnet_utoa(unsigned value, char *buffer, size_t size)
{
return bacnet_snprintf_to_ascii(buffer, size, "%u", value);
}
/**
* @brief Convert a value into a string and return the base-10 string
* @param value Number to be converted.
* @param buffer Buffer that holds the result of the conversion.
* @param size Length of the buffer in units of the character type.
* @return character string buffer
*/
char *bacnet_ultoa(unsigned long value, char *buffer, size_t size)
{
return bacnet_snprintf_to_ascii(buffer, size, "%lu", value);
}
/**
* @brief trim characters from the left side of a string
* @param str - string to trim
* @param trimmedchars - characters to trim from the string
* @return the trimmed string
*/
char *bacnet_ltrim(char *str, const char *trimmedchars)
{
if (str[0] == 0) {
return str;
}
while (strchr(trimmedchars, *str)) {
str++;
}
return str;
}
/**
* @brief trim characters from the right side of a string
* @param str - string to trim
* @param trimmedchars - characters to trim from the string
* @return the trimmed string
*/
char *bacnet_rtrim(char *str, const char *trimmedchars)
{
char *end;
if (str[0] == 0) {
return str;
}
end = str + strlen(str) - 1;
while (strchr(trimmedchars, *end)) {
*end = 0;
if (end == str) {
break;
}
end--;
}
return str;
}
/**
* @brief trim characters from the right side and left side of a string
* @param str - string to trim
* @param trimmedchars - characters to trim from the string
* @return the trimmed string
*/
char *bacnet_trim(char *str, const char *trimmedchars)
{
return bacnet_ltrim(bacnet_rtrim(str, trimmedchars), trimmedchars);
}
/**
* @brief Parse a token from a string.
* @details From a string, a buffer to receive the "token" that gets scanned,
* the length of the buffer, and a string of "break" characters that stop
* the scan. The function will copy the string into the buffer up to any
* of the break characters, or until the buffer is full, and will always
* leave the buffer null-terminated. The function will return a pointer
* to the first non-breaking character after the one that stopped the scan.
* @param s string to parse
* @param tok buffer that receives the "token" that gets scanned
* @param toklen length of the buffer
* @param brk string of break characters that will stop the scan
* @return a pointer to the first non-breaking character after the one that
* stopped the scan or NULL on error or end of string.
* @note public domain by Ray Gardner, modified by Bob Stout and Steve Karg
*/
char *bacnet_stptok(const char *s, char *tok, size_t toklen, const char *brk)
{
char *lim; /* limit of token */
const char *b; /* current break character */
/* check for invalid pointers */
if (!s || !tok || !brk) {
return NULL;
}
/* check for empty string */
if (!*s) {
return NULL;
}
lim = tok + toklen - 1;
while (*s && tok < lim) {
for (b = brk; *b; b++) {
if (*s == *b) {
*tok = 0;
for (++s, b = brk; *s && *b; ++b) {
if (*s == *b) {
++s;
b = brk;
}
}
if (!*s) {
return NULL;
}
return (char *)s;
}
}
*tok++ = *s++;
}
*tok = 0;
if (!*s) {
return NULL;
}
return (char *)s;
}
/**
* @brief General purpose print formatter snprintf() function with offset
* @param buffer - destination string
* @param size - length of the destination string, or zero for length
* @param offset - offset into the buffer to start the string
* @param format - format string
* @return total number of characters written from the beginning of buffer
*/
int bacnet_snprintf(
char *buffer, size_t size, int offset, const char *format, ...)
{
int length = 0, write_length = 0;
va_list args;
char *write_buffer = NULL;
size_t write_size = 0;
if (offset < 0) {
return offset;
}
if (offset < size) {
write_size = size - offset;
if (buffer) {
write_buffer = buffer + offset;
}
} else if (size == 0) {
write_size = 0;
/* when size is zero, nothing is written to the buffer */
} else {
return size;
}
va_start(args, format);
length = vsnprintf(write_buffer, write_size, format, args);
va_end(args);
write_length = offset + length;
if (size != 0) {
/* limit the return value to the size of the buffer */
if (write_length > size) {
write_length = size;
}
}
return write_length;
}