libosmocore/src/utils.c

1409 lines
49 KiB
C
Raw Normal View History

/*
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
* (C) 2011 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2014 by Nils O. Selåsdal <noselasd@fiane.dyndns.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <stdio.h>
#include <inttypes.h>
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
#include <limits.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/bit64gen.h>
2011-08-17 12:19:27 +00:00
/*! \addtogroup utils
* @{
* various utility routines
*
* \file utils.c */
2011-08-17 12:19:27 +00:00
static __thread char namebuf[255];
/* shared by osmo_str_tolower() and osmo_str_toupper() */
static __thread char capsbuf[128];
2011-08-17 12:19:27 +00:00
/*! get human-readable string for given value
2011-08-17 12:19:27 +00:00
* \param[in] vs Array of value_string tuples
* \param[in] val Value to be converted
* \returns pointer to human-readable string
*
* If val is found in vs, the array's string entry is returned. Otherwise, an
* "unknown" string containing the actual value is composed in a static buffer
* that is reused across invocations.
2011-08-17 12:19:27 +00:00
*/
const char *get_value_string(const struct value_string *vs, uint32_t val)
{
const char *str = get_value_string_or_null(vs, val);
if (str)
return str;
snprintf(namebuf, sizeof(namebuf), "unknown 0x%"PRIx32, val);
namebuf[sizeof(namebuf) - 1] = '\0';
return namebuf;
}
/*! get human-readable string or NULL for given value
* \param[in] vs Array of value_string tuples
* \param[in] val Value to be converted
* \returns pointer to human-readable string or NULL if val is not found
*/
const char *get_value_string_or_null(const struct value_string *vs,
uint32_t val)
{
int i;
if (!vs)
return NULL;
for (i = 0;; i++) {
if (vs[i].value == 0 && vs[i].str == NULL)
break;
if (vs[i].value == val)
return vs[i].str;
}
return NULL;
}
/*! get numeric value for given human-readable string
2011-08-17 12:19:27 +00:00
* \param[in] vs Array of value_string tuples
* \param[in] str human-readable string
* \returns numeric value (>0) or negative numer in case of error
*/
int get_string_value(const struct value_string *vs, const char *str)
{
int i;
for (i = 0;; i++) {
if (vs[i].value == 0 && vs[i].str == NULL)
break;
if (!strcasecmp(vs[i].str, str))
return vs[i].value;
}
return -EINVAL;
}
/*! Convert BCD-encoded digit into printable character
2011-08-17 12:19:27 +00:00
* \param[in] bcd A single BCD-encoded digit
* \returns single printable character
*/
char osmo_bcd2char(uint8_t bcd)
{
if (bcd < 0xa)
return '0' + bcd;
else
return 'A' + (bcd - 0xa);
}
/*! Convert number in ASCII to BCD value
* \param[in] c ASCII character
* \returns BCD encoded value of character
*/
uint8_t osmo_char2bcd(char c)
{
if (c >= '0' && c <= '9')
return c - 0x30;
else if (c >= 'A' && c <= 'F')
return 0xa + (c - 'A');
else if (c >= 'a' && c <= 'f')
return 0xa + (c - 'a');
else
return 0;
}
/*! Convert BCD to string.
* The given nibble offsets are interpreted in BCD order, i.e. nibble 0 is bcd[0] & 0xf, nibble 1 is bcd[0] >> 4, nibble
* 3 is bcd[1] & 0xf, etc..
* \param[out] dst Output string buffer, is always nul terminated when dst_size > 0.
* \param[in] dst_size sizeof() the output string buffer.
* \param[in] bcd Binary coded data buffer.
* \param[in] start_nibble Offset to start from, in nibbles, typically 1 to skip the first nibble.
* \param[in] end_nibble Offset to stop before, in nibbles, e.g. sizeof(bcd)*2 - (bcd[0] & GSM_MI_ODD? 0:1).
* \param[in] allow_hex If false, return error if there are digits other than 0-9. If true, return those as [A-F].
* \returns The strlen that would be written if the output buffer is large enough, excluding nul byte (like
* snprintf()), or -EINVAL if allow_hex is false and a digit > 9 is encountered. On -EINVAL, the conversion is
* still completed as if allow_hex were passed as true. Return -ENOMEM if dst is NULL or dst_size is zero.
* If end_nibble <= start_nibble, write an empty string to dst and return 0.
*/
int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibble, int end_nibble, bool allow_hex)
{
char *dst_end = dst + dst_size - 1;
int nibble_i;
int rc = 0;
if (!dst || dst_size < 1 || start_nibble < 0)
return -ENOMEM;
for (nibble_i = start_nibble; nibble_i < end_nibble && dst < dst_end; nibble_i++, dst++) {
uint8_t nibble = bcd[nibble_i >> 1];
if ((nibble_i & 1))
nibble >>= 4;
nibble &= 0xf;
if (!allow_hex && nibble > 9)
rc = -EINVAL;
*dst = osmo_bcd2char(nibble);
}
*dst = '\0';
if (rc < 0)
return rc;
return OSMO_MAX(0, end_nibble - start_nibble);
}
add osmo_mobile_identity API Implement better API around 3GPP TS 24.008 Mobile Identity coding. struct osmo_mobile_identity is a decoded representation of the raw Mobile Identity, with a string representation as well as dedicated raw uint32_t TMSI. The aim is to remove all uncertainty about decoded buffer sizes / data types. I have patches ready for current osmo CNI programs, replacing the Mobile Identity coding with this new API. Deprecate the old MI API. osmo-bsc: I71c3b4c65dbfdfa51409e09d4868aea83225338a osmo-msc: Ic3f969e739654c1e8c387aedeeba5cce07fe2307 osmo-sgsn: I4cacb10bac419633ca0c14f244f9903f7f517b49 Note that some GPRS and SGs related coding is done here in libosmocore and hence currently remains using the old implementation (see previous version of this patch: Ic3f969e739654c1e8c387aedeeba5cce07fe2307). New API functions provide properly size-checking implementations of: - decoding a raw MI from a bunch of MI octets; - locating and decoding MI from a full 3GPP TS 24.008 Complete Layer 3 msgb; - encoding to a buffer; - encoding to the end of a msgb. Other than the old gsm48_generate_mid(), omit a TLV tag and length from encoding. Many callers manually stripped the tag and value after calling gsm48_generate_mid(). The aim is to leave writing a TL to the caller entirely, especially since some callers need to use a TvL, i.e. support a variable-size length of 8 or 16 bit. New validity checks so far not implemented anywhere else: - stricter validation of number of digits of IMSI, IMEI, IMEI-SV MI. - stricter on filler nibbles to be 0xf. As a result, applications using osmo_mobile_identity will be stricter in rejecting coding mistakes (some of which we currently have in our test suites, and which we'll need to fix). Rationale: While implementing osmo-bsc's MSC pooling feature in osmo-bsc, this API will be used to reduce the number of times a Mobile Identity is extracted from a raw RSL message. Extracting the Mobile Identity from messages has numerous duplicate implementations across our code with various levels of specialization. https://xkcd.com/927/ To name a few: - libosmocore: gsm48_mi_to_string(), osmo_mi_name_buf() - osmo-bsc: extract_sub() - osmo-msc: mm_rx_loc_upd_req(), cm_serv_reuse_conn(), gsm48_rx_mm_serv_req(), vlr_proc_acc_req() We have existing functions to produce a human readable string from a Mobile Identity, more or less awkward: - gsm48_mi_to_string() decodes a TMSI as a decimal number. These days we use hexadecimal TMSI everywhere. - osmo_mi_name_buf() decodes the BCD digits from a raw MI every time, so we'd need to pass around the raw message bytes. Also, osmo_mi_name_buf() has the wrong signature, it should return a length like snprintf(). - osmo-bsc's extract_sub() first uses gsm48_mi_to_string() which encodes the raw uint32_t TMSI to a string, and then calls strtoul() via tmsi_from_string() to code those back to a raw uint32_t. Each of the above implementations employ their own size overflow checks, each invoke osmo_bcd2str() and implement their own TMSI osmo_load32be() handling. Too much code dup, let's hope that each and every one is correct. In osmo-bsc, I am now implementing MSC pooling, and need to extract NRI bits from a TMSI Mobile Identity. Since none of the above functions are general enough to be re-used, I found myself again copy-pasting Mobile Identity code: locating the MI in a 24.008 message with proper size checks, decoding MI octets. This time I would like it to become a generally re-usable API. This patch was first merged as Ic3f969e739654c1e8c387aedeeba5cce07fe2307 and caused test fallout, because it re-implemented old API with the new stricter decoding. In this patch version, old API remains 1:1 unchanged to avoid such fallout. Applications will soon switch to the new osmo_mobile_identity API and become stricter on MI coding when that happens, not implicitly by a new libosmocore version. Change-Id: If4f7be606e54cfa1c59084cf169785b1cbda5cf5
2020-05-26 00:45:23 +00:00
/*! Convert string to BCD.
* The given nibble offsets are interpreted in BCD order, i.e. nibble 0 is bcd[0] & 0x0f, nibble 1 is bcd[0] & 0xf0, nibble
* 3 is bcd[1] & 0x0f, etc..
* \param[out] dst Output BCD buffer.
* \param[in] dst_size sizeof() the output string buffer.
* \param[in] digits String containing decimal or hexadecimal digits in upper or lower case.
* \param[in] start_nibble Offset to start from, in nibbles, typically 1 to skip the first (MI type) nibble.
* \param[in] end_nibble Negative to write all digits found in str, followed by 0xf nibbles to fill any started octet.
* If >= 0, stop before this offset in nibbles, e.g. to get default behavior, pass
* start_nibble + strlen(str) + ((start_nibble + strlen(str)) & 1? 1 : 0) + 1.
* \param[in] allow_hex If false, return error if there are hexadecimal digits (A-F). If true, write those to
* BCD.
* \returns The buffer size in octets that is used to place all bcd digits (including the skipped nibbles
* from 'start_nibble' and rounded up to full octets); -EINVAL on invalid digits;
* -ENOMEM if dst is NULL, if dst_size is too small to contain all nibbles, or if start_nibble is negative.
*/
int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex)
{
const char *digit = digits;
int nibble_i;
if (!dst || !dst_size || start_nibble < 0)
return -ENOMEM;
if (end_nibble < 0) {
end_nibble = start_nibble + strlen(digits);
/* If the last octet is not complete, add another filler nibble */
if (end_nibble & 1)
end_nibble++;
}
if ((end_nibble / 2) > dst_size)
return -ENOMEM;
for (nibble_i = start_nibble; nibble_i < end_nibble; nibble_i++) {
uint8_t nibble = 0xf;
int octet = nibble_i >> 1;
if (*digit) {
char c = *digit;
digit++;
if (c >= '0' && c <= '9')
nibble = c - '0';
else if (allow_hex && c >= 'A' && c <= 'F')
nibble = 0xa + (c - 'A');
else if (allow_hex && c >= 'a' && c <= 'f')
nibble = 0xa + (c - 'a');
else
return -EINVAL;
}
nibble &= 0xf;
if ((nibble_i & 1))
dst[octet] = (nibble << 4) | (dst[octet] & 0x0f);
else
dst[octet] = (dst[octet] & 0xf0) | nibble;
}
/* floor(float(end_nibble) / 2) */
return end_nibble / 2;
}
/*! Parse a string containing hexadecimal digits
* \param[in] str string containing ASCII encoded hexadecimal digits
* \param[out] b output buffer
* \param[in] max_len maximum space in output buffer
* \returns number of parsed octets, or -1 on error
*/
int osmo_hexparse(const char *str, uint8_t *b, int max_len)
{
char c;
uint8_t v;
const char *strpos;
unsigned int nibblepos = 0;
memset(b, 0x00, max_len);
for (strpos = str; (c = *strpos); strpos++) {
/* skip whitespace */
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
continue;
/* If the buffer is too small, error out */
if (nibblepos >= (max_len << 1))
return -1;
if (c >= '0' && c <= '9')
v = c - '0';
else if (c >= 'a' && c <= 'f')
v = 10 + (c - 'a');
else if (c >= 'A' && c <= 'F')
v = 10 + (c - 'A');
else
return -1;
b[nibblepos >> 1] |= v << (nibblepos & 1 ? 0 : 4);
nibblepos ++;
}
/* In case of uneven amount of digits, the last byte is not complete
* and that's an error. */
if (nibblepos & 1)
return -1;
return nibblepos >> 1;
}
static __thread char hexd_buff[4096];
static const char hex_chars[] = "0123456789abcdef";
/*! Convert binary sequence to hexadecimal ASCII string.
* \param[out] out_buf Output buffer to write the resulting string to.
* \param[in] out_buf_size sizeof(out_buf).
* \param[in] buf Input buffer, pointer to sequence of bytes.
* \param[in] len Length of input buf in number of bytes.
* \param[in] delim String to separate each byte; NULL or "" for no delim.
* \param[in] delim_after_last If true, end the string in delim (true: "1a:ef:d9:", false: "1a:ef:d9");
* if out_buf has insufficient space, the string will always end in a delim.
* \returns out_buf, containing a zero-terminated string, or "" (empty string) if out_buf == NULL or out_buf_size < 1.
*
* This function will print a sequence of bytes as hexadecimal numbers, adding one delim between each byte (e.g. for
* delim passed as ":", return a string like "1a:ef:d9").
*
* The delim_after_last argument exists to be able to exactly show the original osmo_hexdump() behavior, which always
* ends the string with a delimiter.
*/
const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned char *buf, int len, const char *delim,
bool delim_after_last)
{
int i;
char *cur = out_buf;
size_t delim_len;
if (!out_buf || !out_buf_size)
return "";
delim = delim ? : "";
delim_len = strlen(delim);
for (i = 0; i < len; i++) {
const char *delimp = delim;
int len_remain = out_buf_size - (cur - out_buf) - 1;
if (len_remain < (2 + delim_len)
&& !(!delim_after_last && i == (len - 1) && len_remain >= 2))
break;
*cur++ = hex_chars[buf[i] >> 4];
*cur++ = hex_chars[buf[i] & 0xf];
if (i == (len - 1) && !delim_after_last)
break;
while (len_remain > 1 && *delimp) {
*cur++ = *delimp++;
len_remain--;
}
}
*cur = '\0';
return out_buf;
}
2010-07-30 09:43:30 +00:00
/*! Convert a sequence of unpacked bits to ASCII string, in user-supplied buffer.
* \param[out] buf caller-provided output string buffer
* \param[out] buf_len size of buf in bytes
2011-08-17 12:19:27 +00:00
* \param[in] bits A sequence of unpacked bits
* \param[in] len Length of bits
* \return The output buffer (buf).
2011-08-17 12:19:27 +00:00
*/
char *osmo_ubit_dump_buf(char *buf, size_t buf_len, const uint8_t *bits, unsigned int len)
{
int i;
if (len > buf_len-1)
len = buf_len-1;
memset(buf, 0, buf_len);
for (i = 0; i < len; i++) {
char outch;
switch (bits[i]) {
case 0:
outch = '0';
break;
case 0xff:
outch = '?';
break;
case 1:
outch = '1';
break;
default:
outch = 'E';
break;
}
buf[i] = outch;
}
buf[buf_len-1] = 0;
return buf;
}
/*! Convert a sequence of unpacked bits to ASCII string, in static buffer.
* \param[in] bits A sequence of unpacked bits
* \param[in] len Length of bits
* \returns string representation in static buffer.
*/
char *osmo_ubit_dump(const uint8_t *bits, unsigned int len)
{
return osmo_ubit_dump_buf(hexd_buff, sizeof(hexd_buff), bits, len);
}
/*! Convert binary sequence to hexadecimal ASCII string
2011-08-17 12:19:27 +00:00
* \param[in] buf pointer to sequence of bytes
* \param[in] len length of buf in number of bytes
* \returns pointer to zero-terminated string
*
* This function will print a sequence of bytes as hexadecimal numbers,
* adding one space character between each byte (e.g. "1a ef d9")
*
* The maximum size of the output buffer is 4096 bytes, i.e. the maximum
* number of input bytes that can be printed in one call is 1365!
2011-08-17 12:19:27 +00:00
*/
char *osmo_hexdump(const unsigned char *buf, int len)
2010-07-30 09:43:30 +00:00
{
osmo_hexdump_buf(hexd_buff, sizeof(hexd_buff), buf, len, " ", true);
return hexd_buff;
2010-07-30 09:43:30 +00:00
}
/*! Convert binary sequence to hexadecimal ASCII string
* \param[in] ctx talloc context from where to allocate the output string
* \param[in] buf pointer to sequence of bytes
* \param[in] len length of buf in number of bytes
* \returns pointer to zero-terminated string
*
* This function will print a sequence of bytes as hexadecimal numbers,
* adding one space character between each byte (e.g. "1a ef d9")
*
* The maximum size of the output buffer is 4096 bytes, i.e. the maximum
* number of input bytes that can be printed in one call is 1365!
*/
char *osmo_hexdump_c(const void *ctx, const unsigned char *buf, int len)
{
size_t hexd_buff_len = len * 3 + 1;
char *hexd_buff = talloc_size(ctx, hexd_buff_len);
if (!hexd_buff)
return NULL;
osmo_hexdump_buf(hexd_buff, hexd_buff_len, buf, len, " ", true);
return hexd_buff;
}
/*! Convert binary sequence to hexadecimal ASCII string
2011-08-17 12:19:27 +00:00
* \param[in] buf pointer to sequence of bytes
* \param[in] len length of buf in number of bytes
* \returns pointer to zero-terminated string
*
* This function will print a sequence of bytes as hexadecimal numbers,
* without any space character between each byte (e.g. "1aefd9")
*
* The maximum size of the output buffer is 4096 bytes, i.e. the maximum
* number of input bytes that can be printed in one call is 2048!
2011-08-17 12:19:27 +00:00
*/
char *osmo_hexdump_nospc(const unsigned char *buf, int len)
2010-07-30 09:43:30 +00:00
{
osmo_hexdump_buf(hexd_buff, sizeof(hexd_buff), buf, len, "", true);
return hexd_buff;
2010-07-30 09:43:30 +00:00
}
/*! Convert binary sequence to hexadecimal ASCII string
* \param[in] ctx talloc context from where to allocate the output string
* \param[in] buf pointer to sequence of bytes
* \param[in] len length of buf in number of bytes
* \returns pointer to zero-terminated string
*
* This function will print a sequence of bytes as hexadecimal numbers,
* without any space character between each byte (e.g. "1aefd9")
*
* The maximum size of the output buffer is 4096 bytes, i.e. the maximum
* number of input bytes that can be printed in one call is 2048!
*/
char *osmo_hexdump_nospc_c(const void *ctx, const unsigned char *buf, int len)
{
size_t hexd_buff_len = len * 2 + 1;
char *hexd_buff = talloc_size(ctx, hexd_buff_len);
if (!hexd_buff)
return NULL;
osmo_hexdump_buf(hexd_buff, hexd_buff_len, buf, len, "", true);
return hexd_buff;
}
/* Compat with previous typo to preserve abi */
char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len)
#if defined(__MACH__) && defined(__APPLE__)
;
#else
__attribute__((weak, alias("osmo_hexdump_nospc")));
#endif
#include "../config.h"
#ifdef HAVE_CTYPE_H
#include <ctype.h>
/*! Convert an entire string to lower case
2011-08-17 12:19:27 +00:00
* \param[out] out output string, caller-allocated
* \param[in] in input string
*/
void osmo_str2lower(char *out, const char *in)
{
unsigned int i;
for (i = 0; i < strlen(in); i++)
out[i] = tolower((const unsigned char)in[i]);
out[strlen(in)] = '\0';
}
/*! Convert an entire string to upper case
2011-08-17 12:19:27 +00:00
* \param[out] out output string, caller-allocated
* \param[in] in input string
*/
void osmo_str2upper(char *out, const char *in)
{
unsigned int i;
for (i = 0; i < strlen(in); i++)
out[i] = toupper((const unsigned char)in[i]);
out[strlen(in)] = '\0';
}
#endif /* HAVE_CTYPE_H */
2011-08-17 12:19:27 +00:00
/*! Wishful thinking to generate a constant time compare
* \param[in] exp Expected data
* \param[in] rel Comparison value
* \param[in] count Number of bytes to compare
* \returns 1 in case \a exp equals \a rel; zero otherwise
*
* Compare count bytes of exp to rel. Return 0 if they are identical, 1
* otherwise. Do not return a mismatch on the first mismatching byte,
* but always compare all bytes, regardless. The idea is that the amount of
* matching bytes cannot be inferred from the time the comparison took. */
int osmo_constant_time_cmp(const uint8_t *exp, const uint8_t *rel, const int count)
{
int x = 0, i;
for (i = 0; i < count; ++i)
x |= exp[i] ^ rel[i];
/* if x is zero, all data was identical */
return x? 1 : 0;
}
/*! Generic retrieval of 1..8 bytes as big-endian uint64_t
* \param[in] data Input data as byte-array
* \param[in] data_len Length of \a data in octets
* \returns uint64_t of \a data interpreted as big-endian
*
* This is like osmo_load64be_ext, except that if data_len is less than
* sizeof(uint64_t), the data is interpreted as the least significant bytes
* (osmo_load64be_ext loads them as the most significant bytes into the
* returned uint64_t). In this way, any integer size up to 64 bits can be
* decoded conveniently by using sizeof(), without the need to call specific
* numbered functions (osmo_load16, 32, ...). */
uint64_t osmo_decode_big_endian(const uint8_t *data, size_t data_len)
{
uint64_t value = 0;
while (data_len > 0) {
value = (value << 8) + *data;
data += 1;
data_len -= 1;
}
return value;
}
/*! Generic big-endian encoding of big endian number up to 64bit
* \param[in] value unsigned integer value to be stored
* \param[in] data_len number of octets
* \returns static buffer containing big-endian stored value
*
* This is like osmo_store64be_ext, except that this returns a static buffer of
* the result (for convenience, but not threadsafe). If data_len is less than
* sizeof(uint64_t), only the least significant bytes of value are encoded. */
uint8_t *osmo_encode_big_endian(uint64_t value, size_t data_len)
{
static __thread uint8_t buf[sizeof(uint64_t)];
OSMO_ASSERT(data_len <= ARRAY_SIZE(buf));
osmo_store64be_ext(value, buf, data_len);
return buf;
}
/*! Copy a C-string into a sized buffer
* \param[in] src source string
* \param[out] dst destination string
* \param[in] siz size of the \a dst buffer
* \returns length of \a src
*
* Copy at most \a siz bytes from \a src to \a dst, ensuring that the result is
* NUL terminated. The NUL character is included in \a siz, i.e. passing the
* actual sizeof(*dst) is correct.
*
* Note, a similar function that also limits the input buffer size is osmo_print_n().
*/
size_t osmo_strlcpy(char *dst, const char *src, size_t siz)
{
size_t ret = src ? strlen(src) : 0;
if (siz) {
size_t len = OSMO_MIN(siz - 1, ret);
if (len)
memcpy(dst, src, len);
dst[len] = '\0';
}
return ret;
}
/*! Find first occurence of a char in a size limited string.
* Like strchr() but with a buffer size limit.
* \param[in] str String buffer to examine.
* \param[in] str_size sizeof(str).
* \param[in] c Character to look for.
* \return Pointer to the matched char, or NULL if not found.
*/
const char *osmo_strnchr(const char *str, size_t str_size, char c)
{
const char *end = str + str_size;
const char *pos;
if (!str)
return NULL;
for (pos = str; pos < end; pos++) {
if (c == *pos)
return pos;
if (!*pos)
return NULL;
}
return NULL;
}
/*! Validate that a given string is a hex string within given size limits.
* Note that each hex digit amounts to a nibble, so if checking for a hex
* string to result in N bytes, pass amount of digits as 2*N.
* \param str A nul-terminated string to validate, or NULL.
* \param min_digits least permitted amount of digits.
* \param max_digits most permitted amount of digits.
* \param require_even if true, require an even amount of digits.
* \returns true when the hex_str contains only hexadecimal digits (no
* whitespace) and matches the requested length; also true
* when min_digits <= 0 and str is NULL.
*/
bool osmo_is_hexstr(const char *str, int min_digits, int max_digits,
bool require_even)
{
int len;
/* Use unsigned char * to avoid a compiler warning of
* "error: array subscript has type 'char' [-Werror=char-subscripts]" */
const unsigned char *pos = (const unsigned char*)str;
if (!pos)
return min_digits < 1;
for (len = 0; *pos && len < max_digits; len++, pos++)
if (!isxdigit(*pos))
return false;
if (len < min_digits)
return false;
/* With not too many digits, we should have reached *str == nul */
if (*pos)
return false;
if (require_even && (len & 1))
return false;
return true;
}
static const char osmo_identifier_illegal_chars[] = "., {}[]()<>|~\\^`'\"?=;/+*&%$#!";
/*! Determine if a given identifier is valid, i.e. doesn't contain illegal chars
* \param[in] str String to validate
* \param[in] sep_chars Permitted separation characters between identifiers.
* \returns true in case \a str contains only valid identifiers and sep_chars, false otherwise
*/
bool osmo_separated_identifiers_valid(const char *str, const char *sep_chars)
{
/* characters that are illegal in names */
unsigned int i;
size_t len;
/* an empty string is not a valid identifier */
if (!str || (len = strlen(str)) == 0)
return false;
for (i = 0; i < len; i++) {
if (sep_chars && strchr(sep_chars, str[i]))
continue;
/* check for 7-bit ASCII */
if (str[i] & 0x80)
return false;
if (!isprint((int)str[i]))
return false;
/* check for some explicit reserved control characters */
if (strchr(osmo_identifier_illegal_chars, str[i]))
return false;
}
return true;
}
/*! Determine if a given identifier is valid, i.e. doesn't contain illegal chars
* \param[in] str String to validate
* \returns true in case \a str contains valid identifier, false otherwise
*/
bool osmo_identifier_valid(const char *str)
{
return osmo_separated_identifiers_valid(str, NULL);
}
/*! Replace characters in the given string buffer so that it is guaranteed to pass osmo_separated_identifiers_valid().
* To guarantee passing osmo_separated_identifiers_valid(), replace_with must not itself be an illegal character. If in
* doubt, use '-'.
* \param[inout] str Identifier to sanitize, must be nul terminated and in a writable buffer.
* \param[in] sep_chars Additional characters that are allowed besides osmo_identifier_illegal_chars.
* \param[in] replace_with Replace any illegal characters with this character.
*/
void osmo_identifier_sanitize_buf(char *str, const char *sep_chars, char replace_with)
{
char *pos;
if (!str)
return;
for (pos = str; *pos; pos++) {
if (strchr(osmo_identifier_illegal_chars, *pos)
|| (sep_chars && strchr(sep_chars, *pos)))
*pos = replace_with;
}
}
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
/*! Like osmo_escape_str_buf2, but with unusual ordering of arguments, and may sometimes return string constants instead
* of writing to buf for error cases or empty input.
* Most *_buf() functions have the buffer and size as first arguments, here the arguments are last.
* In particular, this function signature doesn't work with OSMO_STRBUF_APPEND_NOLEN().
* \param[in] str A string that may contain any characters.
* \param[in] len Pass -1 to print until nul char, or >= 0 to force a length.
* \param[inout] buf string buffer to write escaped characters to.
* \param[in] bufsize size of \a buf.
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
* \returns buf containing an escaped representation, possibly truncated,
* or "(null)" if str == NULL, or "(error)" in case of errors.
*/
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
const char *osmo_escape_str_buf(const char *str, int in_len, char *buf, size_t bufsize)
{
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
if (!str)
return "(null)";
if (!buf || !bufsize)
return "(error)";
return osmo_escape_str_buf2(buf, bufsize, str, in_len);
}
/*! Copy N characters to a buffer with a function signature useful for OSMO_STRBUF_APPEND().
* Similarly to snprintf(), the result is always nul terminated (except if buf is NULL or bufsize is 0).
* \param[out] buf Target buffer.
* \param[in] bufsize sizeof(buf).
* \param[in] str String to copy.
* \param[in] n Maximum number of non-nul characters to copy.
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
*/
int osmo_print_n(char *buf, size_t bufsize, const char *str, size_t n)
{
size_t write_n;
if (!str)
str = "";
n = strnlen(str, n);
if (!buf || !bufsize)
return n;
write_n = n;
if (write_n >= bufsize)
write_n = bufsize - 1;
if (write_n)
strncpy(buf, str, write_n);
buf[write_n] = '\0';
return n;
}
/*! Return the string with all non-printable characters escaped.
* This internal function is the implementation for all osmo_escape_str* and osmo_quote_str* API versions.
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
* It provides both the legacy (non C compatible) escaping, as well as C compatible string constant syntax,
* and it provides a return value of characters-needed, to allow producing un-truncated strings in all cases.
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
* \param[out] buf string buffer to write escaped characters to.
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
* \param[in] legacy_format If false, return C compatible string constants ("\x0f"), if true the legacy
* escaping format ("\15"). The legacy format also escapes as "\a\b\f\v", while
* the non-legacy format also escapes those as "\xNN" sequences.
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
*/
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
static size_t _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format)
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
{
struct osmo_strbuf sb = { .buf = buf, .len = bufsize };
int in_pos = 0;
int next_unprintable = 0;
if (!str)
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
in_len = 0;
if (in_len < 0)
in_len = strlen(str);
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
/* Make sure of '\0' termination */
if (!in_len)
OSMO_STRBUF_PRINTF(sb, "%s", "");
while (in_pos < in_len) {
for (next_unprintable = in_pos;
next_unprintable < in_len && isprint((int)str[next_unprintable])
&& str[next_unprintable] != '"'
&& str[next_unprintable] != '\\';
next_unprintable++);
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
OSMO_STRBUF_APPEND(sb, osmo_print_n, &str[in_pos], next_unprintable - in_pos);
in_pos = next_unprintable;
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
if (in_pos == in_len)
goto done;
switch (str[next_unprintable]) {
#define BACKSLASH_CASE(c, repr) \
case c: \
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
OSMO_STRBUF_PRINTF(sb, "\\%c", repr); \
break
BACKSLASH_CASE('\n', 'n');
BACKSLASH_CASE('\r', 'r');
BACKSLASH_CASE('\t', 't');
BACKSLASH_CASE('\0', '0');
BACKSLASH_CASE('\\', '\\');
BACKSLASH_CASE('"', '"');
default:
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
if (legacy_format) {
switch (str[next_unprintable]) {
BACKSLASH_CASE('\a', 'a');
BACKSLASH_CASE('\b', 'b');
BACKSLASH_CASE('\v', 'v');
BACKSLASH_CASE('\f', 'f');
default:
OSMO_STRBUF_PRINTF(sb, "\\%u", (unsigned char)str[in_pos]);
break;
}
break;
}
OSMO_STRBUF_PRINTF(sb, "\\x%02x", (unsigned char)str[in_pos]);
break;
}
in_pos ++;
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
#undef BACKSLASH_CASE
}
done:
return sb.chars_needed;
}
/*! Return the string with all non-printable characters escaped.
* \param[out] buf string buffer to write escaped characters to.
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
* \return The output buffer (buf).
*/
char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len)
{
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
_osmo_escape_str_buf(buf, bufsize, str, in_len, true);
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
return buf;
}
/*! Return the string with all non-printable characters escaped.
* Call osmo_escape_str_buf() with a static buffer.
* \param[in] str A string that may contain any characters.
* \param[in] len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns buf containing an escaped representation, possibly truncated, or str itself.
*/
const char *osmo_escape_str(const char *str, int in_len)
{
return osmo_escape_str_buf(str, in_len, namebuf, sizeof(namebuf));
}
/*! Return the string with all non-printable characters escaped, in dynamically-allocated buffer.
* \param[in] str A string that may contain any characters.
* \param[in] len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns dynamically-allocated output buffer, containing an escaped representation
*/
char *osmo_escape_str_c(const void *ctx, const char *str, int in_len)
{
/* The string will be at least as long as in_len, but some characters might need escaping.
* These extra bytes should catch most usual escaping situations, avoiding a second run in OSMO_NAME_C_IMPL. */
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
OSMO_NAME_C_IMPL(ctx, in_len + 16, "ERROR", _osmo_escape_str_buf, str, in_len, true);
}
/*! Return a quoted and escaped representation of the string.
* This internal function is the implementation for all osmo_quote_str* API versions.
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
* It provides both the legacy (non C compatible) escaping, as well as C compatible string constant syntax,
* and it provides a return value of characters-needed, to allow producing un-truncated strings in all cases.
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
* \param[out] buf string buffer to write escaped characters to.
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
* \param[in] legacy_format If false, return C compatible string constants ("\x0f"), if true the legacy
* escaping format ("\15"). The legacy format also escapes as "\a\b\f\v", while
* the non-legacy format also escapes those as "\xNN" sequences.
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
*/
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
static size_t _osmo_quote_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format)
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
{
struct osmo_strbuf sb = { .buf = buf, .len = bufsize };
if (!str)
OSMO_STRBUF_PRINTF(sb, "NULL");
else {
OSMO_STRBUF_PRINTF(sb, "\"");
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
OSMO_STRBUF_APPEND(sb, _osmo_escape_str_buf, str, in_len, legacy_format);
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
OSMO_STRBUF_PRINTF(sb, "\"");
}
return sb.chars_needed;
}
/*! Like osmo_escape_str_buf2(), but returns double-quotes around a string, or "NULL" for a NULL string.
* This allows passing any char* value and get its C representation as string.
* The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN().
* \param[out] buf string buffer to write escaped characters to.
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
* \return The output buffer (buf).
*/
char *osmo_quote_str_buf2(char *buf, size_t bufsize, const char *str, int in_len)
{
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
_osmo_quote_str_buf(buf, bufsize, str, in_len, true);
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
return buf;
}
/*! Like osmo_quote_str_buf2, but with unusual ordering of arguments, and may sometimes return string constants instead
* of writing to buf for error cases or empty input.
* Most *_buf() functions have the buffer and size as first arguments, here the arguments are last.
* In particular, this function signature doesn't work with OSMO_STRBUF_APPEND_NOLEN().
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns buf containing a quoted and escaped representation, possibly truncated.
*/
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
const char *osmo_quote_str_buf(const char *str, int in_len, char *buf, size_t bufsize)
{
if (!str)
return "NULL";
add osmo_{escape,quote}_str_buf2() for standard args ordering To be able to append an escaped or quoted string using OSMO_STRBUF_APPEND_NOLEN(), the function signature must have the buf and len as first args, like most other *_buf() functions. Add osmo_escape_str_buf2() and osmo_quote_str_buf2() to match this signature. A recent patch [1] has changed the return value of osmo_escape_str_buf() to char*, removing the const. However, the functions may return const strings, hence re-add the const. The new signatures always return the non-const buffer. To avoid code duplication, implement osmo_quote_str_buf() and osmo_escape_str_buf() by calling the new functions. I decided to allow slight changes to the behavior for current osmo_escape_str() and osmo_escape_str_buf(), because impact on callers is minimal: (1) The new implementation uses OSMO_STRBUF_*, and in consequence osmo_quote_str() no longer prints an ending double quote after truncated strings; Before, a truncated output was, sic: "this string is trunca" and now this becomes, sic: "this string is truncat I decided to not keep the old behavior because it is questionable to begin with. It looks like the string actually ended at the truncation boundary instead of the reason being not enough space in the output buffer. (2) The new osmo_escape_str_buf2() function obviously cannot pass-thru an unchanged char* if no escaping was needed. Sacrifice this tiny optimization feature to avoid code duplication: - it is an unnoticeable optimization, - the caller anyway always passes a string buffer, - the feature caused handling strings and buffers differently depending on their content (i.e. code that usually writes out strings in full length "suddenly" truncates because a non-printable character is contained, etc.) I considered adding a skip_if_unescaped flag to the osmo_quote_str_buf2() function signature, but in the end decided that the API clutter is not worth having for all the above reasons. Adjust tests to accomodate above changes. [1] 4a62eda225ab7f3c9556990c81a6fc5e19b5eec8 Ibf85f79e93244f53b2684ff6f1095c5b41203e05 Change-Id: Id748b906b0083b1f1887f2be7a53cae705a8a9ae
2019-03-05 15:42:50 +00:00
if (!buf || !bufsize)
return "(error)";
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
_osmo_quote_str_buf(buf, bufsize, str, in_len, true);
return buf;
}
/*! Like osmo_quote_str_buf() but returns the result in a static buffer.
* The static buffer is shared with get_value_string() and osmo_escape_str().
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns static buffer containing a quoted and escaped representation, possibly truncated.
*/
const char *osmo_quote_str(const char *str, int in_len)
{
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
_osmo_quote_str_buf(namebuf, sizeof(namebuf), str, in_len, true);
return namebuf;
}
/*! Like osmo_quote_str_buf() but returns the result in a dynamically-allocated buffer.
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns dynamically-allocated buffer containing a quoted and escaped representation.
*/
char *osmo_quote_str_c(const void *ctx, const char *str, int in_len)
{
/* The string will be at least as long as in_len, but some characters might need escaping.
* These extra bytes should catch most usual escaping situations, avoiding a second run in OSMO_NAME_C_IMPL. */
add osmo_escape_cstr and osmo_quote_cstr Provide string escaping that - returns the required buffer size, so it can be used with OSMO_STRBUF_APPEND(). - uses C compatible string constant escaping sequences. This is intended as a replacement for all previous osmo_escape_str* and osmo_quote_str* API. It pains me that I didn't get them right the first nor the second time: - The buffer functions do not return the chars needed, which is required for allocating sufficient memory in the *_c versions of the functions. - Because of that, these functions are accurately usable for OSMO_STRBUF_APPEND(), producing truncated strings, for example when dumping a GSUP message. - They do not use the C equivalent string constant escaping: for some reason I thought "\15" would be valid, but it should be "\x0f". If I could, I would completely drop those mislead implementations ... but backwards compat prohibits that. A previous patch already provided internal static functions that accurately return the required buffer size. Enhance these to also support C compatible string escaping, and use them as implementation of the new functions: osmo_escape_cstr_buf() osmo_escape_cstr_c() osmo_quote_cstr_buf() osmo_quote_cstr_c() In the tests for these, also test C string equivalence. Naming: from API versions, it would be kind of logical to call them osmo_escape_str_buf3() and osmo_escape_str_c2(). Since these anyway return a different escaping, it makes sense to me to have distinct names instead. Quasi missing are variants of the non-C-compatible weird legacy escaping that return the required buffer size, but I refrain from adding those, because we have enough API cruft as it is. Just always use these new cstr variants. Change-Id: I3dfb892036e01000033dd8e7e4a6a0c32a3caa9b
2019-11-20 23:12:10 +00:00
OSMO_NAME_C_IMPL(ctx, in_len + 16, "ERROR", _osmo_quote_str_buf, str, in_len, true);
}
/*! Return the string with all non-printable characters escaped.
* In contrast to osmo_escape_str_buf2(), this returns the needed buffer size suitable for OSMO_STRBUF_APPEND(), and
* this escapes characters in a way compatible with C string constant syntax.
* \param[out] buf string buffer to write escaped characters to.
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
*/
size_t osmo_escape_cstr_buf(char *buf, size_t bufsize, const char *str, int in_len)
{
return _osmo_escape_str_buf(buf, bufsize, str, in_len, false);
}
/*! Return the string with all non-printable characters escaped, in dynamically-allocated buffer.
* In contrast to osmo_escape_str_c(), this escapes characters in a way compatible with C string constant syntax, and
* allocates sufficient memory in all cases.
* \param[in] str A string that may contain any characters.
* \param[in] len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns dynamically-allocated buffer, containing an escaped representation.
*/
char *osmo_escape_cstr_c(void *ctx, const char *str, int in_len)
{
/* The string will be at least as long as in_len, but some characters might need escaping.
* These extra bytes should catch most usual escaping situations, avoiding a second run in OSMO_NAME_C_IMPL. */
OSMO_NAME_C_IMPL(ctx, in_len + 16, "ERROR", _osmo_escape_str_buf, str, in_len, false);
}
/*! Like osmo_escape_str_buf2(), but returns double-quotes around a string, or "NULL" for a NULL string.
* This allows passing any char* value and get its C representation as string.
* The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN().
* In contrast to osmo_escape_str_buf2(), this returns the needed buffer size suitable for OSMO_STRBUF_APPEND(), and
* this escapes characters in a way compatible with C string constant syntax.
* \param[out] buf string buffer to write escaped characters to.
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
*/
size_t osmo_quote_cstr_buf(char *buf, size_t bufsize, const char *str, int in_len)
{
return _osmo_quote_str_buf(buf, bufsize, str, in_len, false);
}
/*! Return the string quoted and with all non-printable characters escaped, in dynamically-allocated buffer.
* In contrast to osmo_quote_str_c(), this escapes characters in a way compatible with C string constant syntax, and
* allocates sufficient memory in all cases.
* \param[in] str A string that may contain any characters.
* \param[in] len Pass -1 to print until nul char, or >= 0 to force a length.
* \returns dynamically-allocated buffer, containing a quoted and escaped representation.
*/
char *osmo_quote_cstr_c(void *ctx, const char *str, int in_len)
{
/* The string will be at least as long as in_len plus two quotes, but some characters might need escaping.
* These extra bytes should catch most usual escaping situations, avoiding a second run in OSMO_NAME_C_IMPL. */
OSMO_NAME_C_IMPL(ctx, in_len + 16, "ERROR", _osmo_quote_str_buf, str, in_len, false);
}
/*! perform an integer square root operation on unsigned 32bit integer.
* This implementation is taken from "Hacker's Delight" Figure 11-1 "Integer square root, Newton's
* method", which can also be found at http://www.hackersdelight.org/hdcodetxt/isqrt.c.txt */
uint32_t osmo_isqrt32(uint32_t x)
{
uint32_t x1;
int s, g0, g1;
if (x <= 1)
return x;
s = 1;
x1 = x - 1;
if (x1 > 0xffff) {
s = s + 8;
x1 = x1 >> 16;
}
if (x1 > 0xff) {
s = s + 4;
x1 = x1 >> 8;
}
if (x1 > 0xf) {
s = s + 2;
x1 = x1 >> 4;
}
if (x1 > 0x3) {
s = s + 1;
}
g0 = 1 << s; /* g0 = 2**s */
g1 = (g0 + (x >> s)) >> 1; /* g1 = (g0 + x/g0)/2 */
/* converges after four to five divisions for arguments up to 16,785,407 */
while (g1 < g0) {
g0 = g1;
g1 = (g0 + (x/g0)) >> 1;
}
return g0;
}
/*! Convert a string to lowercase, while checking buffer size boundaries.
* The result written to \a dest is guaranteed to be nul terminated if \a dest_len > 0.
* If dest == src, the string is converted in-place, if necessary truncated at dest_len - 1 characters
* length as well as nul terminated.
* Note: similar osmo_str2lower(), but safe to use for src strings of arbitrary length.
* \param[out] dest Target buffer to write lowercase string.
* \param[in] dest_len Maximum buffer size of dest (e.g. sizeof(dest)).
* \param[in] src String to convert to lowercase.
* \returns Length of \a src, like osmo_strlcpy(), but if \a dest == \a src at most \a dest_len - 1.
*/
size_t osmo_str_tolower_buf(char *dest, size_t dest_len, const char *src)
{
size_t rc;
if (dest == src) {
if (dest_len < 1)
return 0;
dest[dest_len - 1] = '\0';
rc = strlen(dest);
} else {
if (dest_len < 1)
return strlen(src);
rc = osmo_strlcpy(dest, src, dest_len);
}
for (; *dest; dest++)
*dest = tolower(*dest);
return rc;
}
/*! Convert a string to lowercase, using a static buffer.
* The resulting string may be truncated if the internally used static buffer is shorter than src.
* The internal buffer is at least 128 bytes long, i.e. guaranteed to hold at least 127 characters and a
* terminating nul. The static buffer returned is shared with osmo_str_toupper().
* See also osmo_str_tolower_buf().
* \param[in] src String to convert to lowercase.
* \returns Resulting lowercase string in a static buffer, always nul terminated.
*/
const char *osmo_str_tolower(const char *src)
{
osmo_str_tolower_buf(capsbuf, sizeof(capsbuf), src);
return capsbuf;
}
/*! Convert a string to lowercase, dynamically allocating the output from given talloc context
* See also osmo_str_tolower_buf().
* \param[in] ctx talloc context from where to allocate the output string
* \param[in] src String to convert to lowercase.
* \returns Resulting lowercase string in a dynamically allocated buffer, always nul terminated.
*/
char *osmo_str_tolower_c(const void *ctx, const char *src)
{
size_t buf_len = strlen(src) + 1;
char *buf = talloc_size(ctx, buf_len);
if (!buf)
return NULL;
osmo_str_tolower_buf(buf, buf_len, src);
return buf;
}
/*! Convert a string to uppercase, while checking buffer size boundaries.
* The result written to \a dest is guaranteed to be nul terminated if \a dest_len > 0.
* If dest == src, the string is converted in-place, if necessary truncated at dest_len - 1 characters
* length as well as nul terminated.
* Note: similar osmo_str2upper(), but safe to use for src strings of arbitrary length.
* \param[out] dest Target buffer to write uppercase string.
* \param[in] dest_len Maximum buffer size of dest (e.g. sizeof(dest)).
* \param[in] src String to convert to uppercase.
* \returns Length of \a src, like osmo_strlcpy(), but if \a dest == \a src at most \a dest_len - 1.
*/
size_t osmo_str_toupper_buf(char *dest, size_t dest_len, const char *src)
{
size_t rc;
if (dest == src) {
if (dest_len < 1)
return 0;
dest[dest_len - 1] = '\0';
rc = strlen(dest);
} else {
if (dest_len < 1)
return strlen(src);
rc = osmo_strlcpy(dest, src, dest_len);
}
for (; *dest; dest++)
*dest = toupper(*dest);
return rc;
}
/*! Convert a string to uppercase, using a static buffer.
* The resulting string may be truncated if the internally used static buffer is shorter than src.
* The internal buffer is at least 128 bytes long, i.e. guaranteed to hold at least 127 characters and a
* terminating nul. The static buffer returned is shared with osmo_str_tolower().
* See also osmo_str_toupper_buf().
* \param[in] src String to convert to uppercase.
* \returns Resulting uppercase string in a static buffer, always nul terminated.
*/
const char *osmo_str_toupper(const char *src)
{
osmo_str_toupper_buf(capsbuf, sizeof(capsbuf), src);
return capsbuf;
}
/*! Convert a string to uppercase, dynamically allocating the output from given talloc context
* See also osmo_str_tolower_buf().
* \param[in] ctx talloc context from where to allocate the output string
* \param[in] src String to convert to uppercase.
* \returns Resulting uppercase string in a dynamically allocated buffer, always nul terminated.
*/
char *osmo_str_toupper_c(const void *ctx, const char *src)
{
size_t buf_len = strlen(src) + 1;
char *buf = talloc_size(ctx, buf_len);
if (!buf)
return NULL;
osmo_str_toupper_buf(buf, buf_len, src);
return buf;
}
/*! Calculate the Luhn checksum (as used for IMEIs).
* \param[in] in Input digits in ASCII string representation.
* \param[in] in_len Count of digits to use for the input (14 for IMEI).
* \returns checksum char (e.g. '3'); negative on error
*/
char osmo_luhn(const char* in, int in_len)
{
int i, sum = 0;
/* All input must be numbers */
for (i = 0; i < in_len; i++) {
if (!isdigit((unsigned char)in[i]))
return -EINVAL;
}
/* Double every second digit and add it to sum */
for (i = in_len - 1; i >= 0; i -= 2) {
int dbl = (in[i] - '0') * 2;
if (dbl > 9)
dbl -= 9;
sum += dbl;
}
/* Add other digits to sum */
for (i = in_len - 2; i >= 0; i -= 2)
sum += in[i] - '0';
/* Final checksum */
return (sum * 9) % 10 + '0';
}
/*! Compare start of a string.
* This is an optimisation of 'strstr(str, startswith_str) == str' because it doesn't search through the entire string.
* \param str (Longer) string to compare.
* \param startswith_str (Shorter) string to compare with the start of str.
* \return true iff the first characters of str fully match startswith_str or startswith_str is empty. */
bool osmo_str_startswith(const char *str, const char *startswith_str)
{
if (!startswith_str || !*startswith_str)
return true;
if (!str)
return false;
return strncmp(str, startswith_str, strlen(startswith_str)) == 0;
}
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
/*! Convert a string of a floating point number to a signed int, with a decimal factor (fixed-point precision).
* For example, with precision=3, convert "-1.23" to -1230. In other words, the float value is multiplied by
* 10 to-the-power-of precision to obtain the returned integer.
* The usable range of digits is -INT64_MAX .. INT64_MAX -- note, not INT64_MIN! The value of INT64_MIN is excluded to
* reduce implementation complexity. See also utils_test.c.
* The advantage over using sscanf("%f") is guaranteed precision: float or double types may apply rounding in the
* conversion result. osmo_float_str_to_int() and osmo_int_to_float_str_buf() guarantee true results when converting
* back and forth between string and int.
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
* \param[out] val Returned integer value.
* \param[in] str String of a float, like '-12.345'.
* \param[in] precision Fixed-point precision, or * \returns 0 on success, negative on error.
*/
int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision)
{
const char *point;
char *endptr;
const char *p;
int64_t sign = 1;
int64_t integer = 0;
int64_t decimal = 0;
int64_t precision_factor;
int64_t integer_max;
int64_t decimal_max;
int i;
OSMO_ASSERT(val);
*val = 0;
if (!str)
return -EINVAL;
if (str[0] == '-') {
str = str + 1;
sign = -1;
} else if (str[0] == '+') {
str = str + 1;
}
if (!str[0])
return -EINVAL;
/* Validate entire string as purely digits and at most one decimal dot. If not doing this here in advance,
* parsing digits might stop early because of precision cut-off and miss validation of input data. */
point = NULL;
for (p = str; *p; p++) {
if (*p == '.') {
if (point)
return -EINVAL;
point = p;
} else if (!isdigit(*p))
return -EINVAL;
}
/* Parse integer part if there is one. If the string starts with a point, there's nothing to parse for the
* integer part. */
if (!point || point > str) {
errno = 0;
integer = strtoll(str, &endptr, 10);
if ((errno == ERANGE && (integer == LLONG_MAX || integer == LLONG_MIN))
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
|| (errno != 0 && integer == 0))
return -ERANGE;
if ((point && endptr != point)
|| (!point && *endptr))
return -EINVAL;
}
/* Parse the fractional part if there is any, and if the precision is nonzero (if we even care about fractional
* digits) */
if (precision && point && point[1] != '\0') {
/* limit the number of digits parsed to 'precision'.
* If 'precision' is larger than the 19 digits representable in int64_t, skip some, to pick up lower
* magnitude digits. */
unsigned int skip_digits = (precision < 20) ? 0 : precision - 20;
char decimal_str[precision + 1];
osmo_strlcpy(decimal_str, point+1, precision+1);
/* fill with zeros to make exactly 'precision' digits */
for (i = strlen(decimal_str); i < precision; i++)
decimal_str[i] = '0';
decimal_str[precision] = '\0';
for (i = 0; i < skip_digits; i++) {
/* When skipping digits because precision > nr-of-digits-in-int64_t, they must be zero;
* if there is a nonzero digit above the precision, it's -ERANGE. */
if (decimal_str[i] != '0')
return -ERANGE;
}
errno = 0;
decimal = strtoll(decimal_str + skip_digits, &endptr, 10);
if ((errno == ERANGE && (decimal == LLONG_MAX || decimal == LLONG_MIN))
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
|| (errno != 0 && decimal == 0))
return -ERANGE;
if (*endptr)
return -EINVAL;
}
if (precision > 18) {
/* Special case of returning more digits than fit in int64_t range, e.g.
* osmo_float_str_to_int("0.0000000012345678901234567", precision=25) -> 12345678901234567. */
precision_factor = 0;
integer_max = 0;
decimal_max = INT64_MAX;
} else {
/* Do not surpass the resulting int64_t range. Depending on the amount of precision, the integer part
* and decimal part have specific ranges they must comply to. */
precision_factor = 1;
for (i = 0; i < precision; i++)
precision_factor *= 10;
integer_max = INT64_MAX / precision_factor;
if (integer == integer_max)
decimal_max = INT64_MAX % precision_factor;
else
decimal_max = INT64_MAX;
}
if (integer > integer_max)
return -ERANGE;
if (decimal > decimal_max)
return -ERANGE;
*val = sign * (integer * precision_factor + decimal);
return 0;
}
/*! Convert an integer to a floating point string using a decimal quotient (fixed-point precision).
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
* For example, with precision = 3, convert -1230 to "-1.23".
* The usable range of digits is -INT64_MAX .. INT64_MAX -- note, not INT64_MIN! The value of INT64_MIN is excluded to
* reduce implementation complexity. See also utils_test.c.
* The advantage over using printf("%.6g") is guaranteed precision: float or double types may apply rounding in the
* conversion result. osmo_float_str_to_int() and osmo_int_to_float_str_buf() guarantee true results when converting
* back and forth between string and int.
* The resulting string omits trailing zeros in the fractional part (like "%g" would) but never applies rounding.
add osmo_float_str_to_int() and osmo_int_to_float_str_*() This will be useful to handle latitude and longitude numbers for GAD, which is the location estimate representation used for LCS (Location Services). The OsmoSMLC VTY user interface will provide floating-point strings like "23.456" while GAD stores them as micro-degress 23456000. The osmo_gad_to_str* will also convert latitude and longitude to floating-point string. There was code review concerns against adding this API, upon which I tried to use floating point string formats. But I encountered various problems with accuracy and trailing zeros. For global positioning data (latitude and longitude), even inaccuracy on the sixth significant decimal digit causes noticeable positional shift. To achieve sufficient accuracy on the least significant end, I need to use double instead of float. To remove trailing zeros, the idea was to use '%.6g' format, but that can cause rounding. '%.6f' on a double looks ok, but always includes trailing zeros. A test program shows: %.6g of ((double)(int32_t)23230100)/1e6 = "23.2301" <-- good %.6g of ((double)(int32_t)42419993)/1e6 = "42.42" <-- bad rounding %.6g of ((double)(int32_t)23230199)/1e6 = "23.2302" <-- bad rounding %.6f of ((double)(int32_t)23230100)/1e6 = "23.230100" <-- trailing zeros %.6f of ((double)(int32_t)42419993)/1e6 = "42.419993" <-- good %.6f of ((double)(int32_t)23230199)/1e6 = "23.230199" <-- good It looks like when accepting that there will be trailing zeros, using double with '%.6f' would work out, but in the end I am not certain enough that there aren't more hidden rounding / precision glitches. Hence I decided to reinforce the need to add this API: it is glitch free in sufficient precision for latitude and longitude data, because it is based on integer arithmetic. The need for this precision is particular to the (new) OsmoSMLC vty configuration, where reading and writing back user config must not modify the values the user entered. Considering to add these functions to osmo-smlc.git, we might as well add them here to libosmocore utils, and also use them in osmo_gad_to_str_*() functions. Change-Id: Ib9aee749cd331712a4dcdadfb6a2dfa4c26da957
2020-09-30 21:47:47 +00:00
* \param[out] buf Buffer to write string to.
* \param[in] buflen sizeof(buf).
* \param[in] val Value to convert to float.
* \returns number of chars that would be written, like snprintf().
*/
int osmo_int_to_float_str_buf(char *buf, size_t buflen, int64_t val, unsigned int precision)
{
struct osmo_strbuf sb = { .buf = buf, .len = buflen };
unsigned int i;
unsigned int w;
int64_t precision_factor;
if (val < 0) {
OSMO_STRBUF_PRINTF(sb, "-");
if (val == INT64_MIN) {
OSMO_STRBUF_PRINTF(sb, "ERR");
return sb.chars_needed;
}
val = -val;
}
if (precision > 18) {
/* Special case of returning more digits than fit in int64_t range, e.g.
* osmo_int_to_float_str(12345678901234567, precision=25) -> "0.0000000012345678901234567". */
if (!val) {
OSMO_STRBUF_PRINTF(sb, "0");
return sb.chars_needed;
}
OSMO_STRBUF_PRINTF(sb, "0.");
for (i = 19; i < precision; i++)
OSMO_STRBUF_PRINTF(sb, "0");
precision = 19;
} else {
precision_factor = 1;
for (i = 0; i < precision; i++)
precision_factor *= 10;
OSMO_STRBUF_PRINTF(sb, "%" PRId64, val / precision_factor);
val %= precision_factor;
if (!val)
return sb.chars_needed;
OSMO_STRBUF_PRINTF(sb, ".");
}
/* print fractional part, skip trailing zeros */
w = precision;
while (!(val % 10)) {
val /= 10;
w--;
}
OSMO_STRBUF_PRINTF(sb, "%0*" PRId64, w, val);
return sb.chars_needed;
}
/*! Convert an integer with a factor of a million to a floating point string.
* For example, convert -1230000 to "-1.23".
* \param[in] ctx Talloc ctx to allocate string buffer from.
* \param[in] val Value to convert to float.
* \returns resulting string, dynamically allocated.
*/
char *osmo_int_to_float_str_c(void *ctx, int64_t val, unsigned int precision)
{
OSMO_NAME_C_IMPL(ctx, 16, "ERROR", osmo_int_to_float_str_buf, val, precision)
}
/*! @} */