libosmocore/include/osmocom/core/utils.h

351 lines
15 KiB
C
Raw Normal View History

#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <osmocom/core/backtrace.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/panic.h>
#include <osmocom/core/defs.h>
2011-08-17 12:19:27 +00:00
/*! \defgroup utils General-purpose utility functions
* @{
* \file utils.h */
2011-08-17 12:19:27 +00:00
/*! Determine number of elements in an array of static size */
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
/*! Return the maximum of two specified values */
#define OSMO_MAX(a, b) ((a) >= (b) ? (a) : (b))
/*! Return the minimum of two specified values */
#define OSMO_MIN(a, b) ((a) >= (b) ? (b) : (a))
/*! Return a typical cmp result for comparable entities a and b. */
#define OSMO_CMP(a, b) ((a) < (b)? -1 : ((a) > (b)? 1 : 0))
/*! Stringify the name of a macro x, e.g. an FSM event name.
* Note: if nested within another preprocessor macro, this will
* stringify the value of x instead of its name. */
#define OSMO_STRINGIFY(x) #x
/*! Stringify the value of a macro x, e.g. a port number. */
#define OSMO_STRINGIFY_VAL(x) OSMO_STRINGIFY(x)
/*! Make a value_string entry from an enum value name */
#define OSMO_VALUE_STRING(x) { x, #x }
/*! Number of bytes necessary to store given BITS */
#define OSMO_BYTES_FOR_BITS(BITS) ((BITS + 8 - 1) / 8)
/*! Copy a C-string into a sized buffer using sizeof to detect buffer's size */
#define OSMO_STRLCPY_ARRAY(array, src) osmo_strlcpy(array, src, sizeof(array))
/*! A mapping between human-readable string and numeric value */
struct value_string {
uint32_t value; /*!< numeric value */
const char *str; /*!< human-readable string */
};
const char *get_value_string(const struct value_string *vs, uint32_t val);
const char *get_value_string_or_null(const struct value_string *vs,
uint32_t val);
int get_string_value(const struct value_string *vs, const char *str);
char osmo_bcd2char(uint8_t bcd);
/* only works for numbers in ASCII */
uint8_t osmo_char2bcd(char c);
int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibble, int end_nibble, bool allow_hex);
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
int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex);
int osmo_hexparse(const char *str, uint8_t *b, unsigned int max_len);
char *osmo_ubit_dump_buf(char *buf, size_t buf_len, const uint8_t *bits, unsigned int len);
2011-08-17 12:19:27 +00:00
char *osmo_ubit_dump(const uint8_t *bits, unsigned int len);
char *osmo_hexdump(const unsigned char *buf, int len);
char *osmo_hexdump_c(const void *ctx, const unsigned char *buf, int len);
char *osmo_hexdump_nospc(const unsigned char *buf, int len);
char *osmo_hexdump_nospc_c(const void *ctx, const unsigned char *buf, int len);
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);
char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len) __attribute__((__deprecated__));
#define osmo_static_assert(exp, name) typedef int dummy##name [(exp) ? 1 : -1] __attribute__((__unused__));
void osmo_str2lower(char *out, const char *in)
OSMO_DEPRECATED("Use osmo_str_tolower() or osmo_str_tolower_buf() instead,"
" to properly check target memory bounds");
void osmo_str2upper(char *out, const char *in)
OSMO_DEPRECATED("Use osmo_str_toupper() or osmo_str_toupper_buf() instead,"
" to properly check target memory bounds");
size_t osmo_str_tolower_buf(char *dest, size_t dest_len, const char *src);
const char *osmo_str_tolower(const char *src);
char *osmo_str_tolower_c(const void *ctx, const char *src);
size_t osmo_str_toupper_buf(char *dest, size_t dest_len, const char *src);
const char *osmo_str_toupper(const char *src);
char *osmo_str_toupper_c(const void *ctx, const char *src);
#define OSMO_SNPRINTF_RET(ret, rem, offset, len) \
do { \
len += ret; \
if (ret > rem) \
ret = rem; \
offset += ret; \
rem -= ret; \
} while (0)
/*! Helper macro to terminate when an assertion fails
* \param[in] exp Predicate to verify
* This function will generate a backtrace and terminate the program if
* the predicate evaluates to false (0).
*/
#define OSMO_ASSERT(exp) \
if (!(exp)) { \
osmo_panic("Assert failed %s %s:%d\n", #exp, __FILE__, __LINE__); \
}
/*! duplicate a string using talloc and release its prior content (if any)
* \param[in] ctx Talloc context to use for allocation
* \param[out] dst pointer to string, will be updated with ptr to new string
* \param[in] newstr String that will be copied to newly allocated string */
static inline void osmo_talloc_replace_string(void *ctx, char **dst, const char *newstr)
{
if (*dst)
talloc_free(*dst);
*dst = talloc_strdup(ctx, newstr);
}
void osmo_talloc_replace_string_fmt(void *ctx, char **dst, const char *fmt, ...);
/*! Append to a string and re-/allocate if necessary.
* \param[in] ctx Talloc context to use for initial allocation.
* \param[in,out] dest char* to re-/allocate and append to.
* \param[in] fmt printf-like string format.
* \param[in] args Arguments for fmt.
*
* \a dest may be passed in NULL, or a string previously allocated by talloc.
* If an existing string is passed in, it will remain associated with whichever
* ctx it was allocated before, regardless whether it matches \a ctx or not.
*/
#define osmo_talloc_asprintf(ctx, dest, fmt, args ...) \
do { \
if (!dest) \
dest = talloc_asprintf(ctx, fmt, ## args); \
else \
dest = talloc_asprintf_append((char*)dest, fmt, ## args); \
} while (0)
int osmo_constant_time_cmp(const uint8_t *exp, const uint8_t *rel, const int count);
uint64_t osmo_decode_big_endian(const uint8_t *data, size_t data_len);
uint8_t *osmo_encode_big_endian(uint64_t value, size_t data_len);
size_t osmo_strlcpy(char *dst, const char *src, size_t siz);
const char *osmo_strnchr(const char *str, size_t str_size, char c);
bool osmo_is_hexstr(const char *str, int min_digits, int max_digits,
bool require_even);
bool osmo_identifier_valid(const char *str);
bool osmo_separated_identifiers_valid(const char *str, const char *sep_chars);
void osmo_identifier_sanitize_buf(char *str, const char *sep_chars, char replace_with);
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
size_t osmo_escape_cstr_buf(char *buf, size_t bufsize, const char *str, int in_len);
char *osmo_escape_cstr_c(void *ctx, const char *str, int in_len);
size_t osmo_quote_cstr_buf(char *buf, size_t bufsize, const char *str, int in_len);
char *osmo_quote_cstr_c(void *ctx, const char *str, int in_len);
const char *osmo_escape_str(const char *str, int len);
int osmo_escape_str_buf3(char *buf, size_t bufsize, const char *str, int in_len);
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
char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len);
const char *osmo_escape_str_buf(const char *str, int in_len, char *buf, size_t bufsize);
char *osmo_escape_str_c(const void *ctx, const char *str, int in_len);
const char *osmo_quote_str(const char *str, int in_len);
int osmo_quote_str_buf3(char *buf, size_t bufsize, const char *str, int in_len);
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
char *osmo_quote_str_buf2(char *buf, size_t bufsize, const char *str, int in_len);
const char *osmo_quote_str_buf(const char *str, int in_len, char *buf, size_t bufsize);
char *osmo_quote_str_c(const void *ctx, const char *str, int in_len);
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
int osmo_print_n(char *buf, size_t bufsize, const char *str, size_t n);
uint32_t osmo_isqrt32(uint32_t x);
char osmo_luhn(const char* in, int in_len);
/*! State for OSMO_STRBUF_APPEND() and OSMO_STRBUF_PRINTF(). See there for examples. */
struct osmo_strbuf {
/*! Point to the start of a string buffer. */
char *buf;
/*! Total sizeof() the buffer buf points at. */
size_t len;
/*! Current writing position in buf (end of the string written so far). */
char *pos;
/*! After all OSMO_STRBUF_APPEND operations, reflects the total number of characters that would be written had
* buf been large enough. Like snprintf()'s return value, this does not include the terminating nul character.
* Hence, to allocate an adequately sized buffer, add 1 to this number. */
size_t chars_needed;
};
/*! Append a string to a buffer, as printed by an snprintf()-like function and with similar bounds checking.
* Make sure to never write past the end of the buffer, and collect the total size that would be needed.
*
* // an example function implementation to append: write N spaces.
* int print_spaces(char *dst, size_t dst_len, int n)
* {
* int i;
* if (n < 0)
* return -EINVAL;
* for (i = 0; i < n && i < dst_len; i++)
* dst[i] = ' ';
* if (dst_len)
* dst[OSMO_MIN(dst_len - 1, n)] = '\0';
* // return the n that we would have liked to write if space were available:
* return n;
* }
*
* // append above spaces as well as an snprintf()
* void strbuf_example()
* {
* char buf[23];
* struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
*
* OSMO_STRBUF_APPEND(sb, print_spaces, 5);
* OSMO_STRBUF_APPEND(sb, snprintf, "The answer is %d but what is the question?", 42);
* OSMO_STRBUF_APPEND(sb, print_spaces, 423423);
*
* printf("%s\n", buf);
* printf("would have needed %zu bytes\n", sb.chars_needed);
* }
*
* \param[inout] STRBUF A struct osmo_strbuf instance.
* \param[in] func A function with a signature of int func(char *dst, size_t dst_len [, args]) with semantics like
* snprintf().
* \param[in] args Arguments passed to func, if any.
*/
#define OSMO_STRBUF_APPEND(STRBUF, func, args...) do { \
if (!(STRBUF).pos) \
(STRBUF).pos = (STRBUF).buf; \
size_t _sb_remain = (STRBUF).buf ? (STRBUF).len - ((STRBUF).pos - (STRBUF).buf) : 0; \
int _sb_l = func((STRBUF).pos, _sb_remain, ##args); \
if (_sb_l < 0 || (size_t)_sb_l > _sb_remain) \
(STRBUF).pos = (STRBUF).buf + (STRBUF).len; \
else if ((STRBUF).pos) \
(STRBUF).pos += _sb_l; \
if (_sb_l > 0) \
(STRBUF).chars_needed += _sb_l; \
} while(0)
/*! Shortcut for OSMO_STRBUF_APPEND() invocation using snprintf().
*
* int strbuf_example2(char *buf, size_t buflen)
* {
* int i;
* struct osmo_strbuf sb = { .buf = buf, .len = buflen };
*
* OSMO_STRBUF_PRINTF(sb, "T minus");
* for (i = 10; i; i--)
* OSMO_STRBUF_PRINTF(sb, " %d", i);
* OSMO_STRBUF_PRINTF(sb, " ... Lift off!");
*
* return sb.chars_needed;
* }
*
* \param[inout] STRBUF A struct osmo_strbuf instance.
* \param[in] fmt Format string passed to snprintf.
* \param[in] args Additional arguments passed to snprintf, if any.
*/
#define OSMO_STRBUF_PRINTF(STRBUF, fmt, args...) \
OSMO_STRBUF_APPEND(STRBUF, snprintf, fmt, ##args)
/*! Like OSMO_STRBUF_APPEND(), but for function signatures that return the char* buffer instead of a length.
* When using this function, the final STRBUF.chars_needed may not reflect the actual number of characters needed, since
* that number cannot be obtained from this kind of function signature.
* \param[inout] STRBUF A struct osmo_strbuf instance.
* \param[in] func A function with a signature of char *func(char *dst, size_t dst_len [, args]) where
* the returned string is always written to dst.
* \param[in] args Arguments passed to func, if any.
*/
#define OSMO_STRBUF_APPEND_NOLEN(STRBUF, func, args...) do { \
if (!(STRBUF).pos) \
(STRBUF).pos = (STRBUF).buf; \
size_t _sb_remain = (STRBUF).buf ? (STRBUF).len - ((STRBUF).pos - (STRBUF).buf) : 0; \
if (_sb_remain) { \
func((STRBUF).pos, _sb_remain, ##args); \
} \
size_t _sb_l = (STRBUF).pos ? strnlen((STRBUF).pos, _sb_remain) : 0; \
if (_sb_l > _sb_remain) \
(STRBUF).pos = (STRBUF).buf + (STRBUF).len; \
else if ((STRBUF).pos) \
(STRBUF).pos += _sb_l; \
(STRBUF).chars_needed += _sb_l; \
} while(0)
bool osmo_str_startswith(const char *str, const char *startswith_str);
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
int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision);
int osmo_int_to_float_str_buf(char *buf, size_t buflen, int64_t val, unsigned int precision);
char *osmo_int_to_float_str_c(void *ctx, int64_t val, unsigned int precision);
int osmo_str_to_int64(int64_t *result, const char *str, int base, int64_t min_val, int64_t max_val);
int osmo_str_to_int(int *result, const char *str, int base, int min_val, int max_val);
/*! Translate a buffer function to a talloc context function.
* This is the full function body of a char *foo_name_c(void *ctx, val...) function, implemented by an
* int foo_name_buf(buf, buflen, val...) function:
*
* char *foo_name_c(void *ctx, example_t arg)
* {
* OSMO_NAME_C_IMPL(ctx, 64, "ERROR", foo_name_buf, arg)
* }
*
* Return a talloc'd string containing the result of the given foo_name_buf() function, or ON_ERROR on error in the called
* foo_name_buf() function.
*
* If ON_ERROR is NULL, the function returns NULL on error rc from FUNC_BUF. Take care: returning NULL in printf() like
* formats (LOGP()) makes the program crash. If ON_ERROR is non-NULL, it must be a string constant, which is not
* returned directly, but written to an allocated string buffer first.
*
* \param[in] INITIAL_BUFSIZE Which size to first talloc from ctx -- a larger size makes a reallocation less likely, a
* smaller size allocates less unused bytes, zero allocates once but still runs the string composition twice.
* \param[in] ON_ERROR String constant to copy on error rc returned by FUNC_BUF, or NULL to return NULL.
* \param[in] FUNC_BUF Name of a function with signature int foo_buf(char *buf, size_t buflen, ...).
* The function must return the strlen() that it would write to a sufficiently large buffer or
* negative on error, like snprintf().
* \param[in] FUNC_BUF_ARGS Additional arguments to pass to FUNC_BUF after the buf and buflen.
*/
#define OSMO_NAME_C_IMPL(CTX, INITIAL_BUFSIZE, ON_ERROR, FUNC_BUF, FUNC_BUF_ARGS...) \
size_t _len = INITIAL_BUFSIZE; \
int _needed; \
char *_str = NULL; \
if ((INITIAL_BUFSIZE) > 0) { \
_str = (char*)talloc_named_const(CTX, _len, __func__); \
OSMO_ASSERT(_str); \
} \
_needed = FUNC_BUF(_str, _len, ## FUNC_BUF_ARGS); \
if (_needed < 0) \
goto OSMO_NAME_C_on_error; \
if ((unsigned int) _needed < _len) \
return _str; \
_len = _needed + 1; \
if (_str) \
talloc_free(_str); \
_str = (char*)talloc_named_const(CTX, _len, __func__); \
OSMO_ASSERT(_str); \
_needed = FUNC_BUF(_str, _len, ## FUNC_BUF_ARGS); \
if (_needed < 0) \
goto OSMO_NAME_C_on_error; \
return _str; \
OSMO_NAME_C_on_error: \
/* Re-using and re-sizing above allocated buf ends up in very complex code. Just free and strdup. */ \
if (_str) \
talloc_free(_str); \
if (!(ON_ERROR)) \
return NULL; \
_str = talloc_strdup(CTX, ON_ERROR); \
OSMO_ASSERT(_str); \
talloc_set_name_const(_str, __func__); \
return _str;
/*! @} */