From f730f6c5f6488be68fef5dbee01677b9cf8194ac Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Wed, 24 Jul 2013 22:20:50 +0200 Subject: [PATCH] lib: introduce proper fixed point parsing and representation module This additional modules isolates the complexity of parsing and representing fixed point numbers. This is uses for coordinates in LLDP-MED. The previous version was using an incorrect precision. When parsing user input, the precision is now derivated from the number of digits provided. When displaying a value, the precision is used to add additional 0 if needed. Moreover, the previous version was a bit buggy with some values and with negative numbers. This change contains unittest to tackle most issues. It relies on presence of __builtin_clzll() function, available in GCC and others. Maybe this will become a portability issue. This closes #41. --- NEWS | 5 + src/lib/Makefile.am | 3 +- src/lib/atom-private.c | 118 ++++----------- src/lib/fixedpoint.c | 253 +++++++++++++++++++++++++++++++ src/lib/fixedpoint.h | 42 ++++++ tests/Makefile.am | 6 +- tests/check_fixedpoint.c | 319 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 657 insertions(+), 89 deletions(-) create mode 100644 src/lib/fixedpoint.c create mode 100644 src/lib/fixedpoint.h create mode 100644 tests/check_fixedpoint.c diff --git a/NEWS b/NEWS index d0f8d6db..80a74ea5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +lldpd (0.7.7) + * Fixes: + + Various bugs related to fixed point number handling (for + coordinates in LLDP-MED) + lldpd (0.7.6) * Features: + Provide a way to build packages for OSX. diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index f074c4f1..f9d92232 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -3,7 +3,8 @@ AM_CFLAGS = -I $(top_srcdir)/include lib_LTLIBRARIES = liblldpctl.la include_HEADERS = lldpctl.h -liblldpctl_la_SOURCES = lldpctl.h private.h errors.c connection.c atom.c atom-private.c +liblldpctl_la_SOURCES = lldpctl.h private.h errors.c connection.c atom.c atom-private.c \ + fixedpoint.h fixedpoint.c liblldpctl_la_LIBADD = $(top_builddir)/src/libcommon-daemon-lib.la liblldpctl_la_LDFLAGS = -export-symbols-regex '^lldpctl_' -version-info 5:0:1 diff --git a/src/lib/atom-private.c b/src/lib/atom-private.c index 1516dbca..0cbf1c0b 100644 --- a/src/lib/atom-private.c +++ b/src/lib/atom-private.c @@ -24,10 +24,7 @@ #include "../lldpd-structs.h" #include "../log.h" #include "private.h" - -#define ntohll(x) \ - (((u_int64_t)(ntohl((int)(((x) << 32) >> 32))) << 32) | \ - (unsigned int)ntohl(((int)((x) >> 32)))) +#include "fixedpoint.h" /* Translation from constants to string */ static lldpctl_map_t lldpd_protocol_map[] = { @@ -1895,34 +1892,24 @@ bad: } static const char* -fixed_precision(lldpctl_atom_t *atom, - u_int64_t value, int intpart, int floatpart, int displaysign, - const char *negsuffix, const char *possuffix) -{ - char *buf; - u_int64_t tmp = value; - int negative = 0, n; - u_int32_t integer = 0; - if (value & (1ULL << (intpart + floatpart - 1))) { - negative = 1; - tmp = ~value; - tmp += 1; +read_fixed_precision(lldpctl_atom_t *atom, + char *buffer, unsigned shift, + unsigned intbits, unsigned fltbits, const char *suffix) +{ + struct fp_number fp = fp_buftofp((unsigned char*)buffer, intbits, fltbits, shift); + char *result = fp_fptostr(fp, suffix); + if (result == NULL) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM); + return NULL; } - integer = (u_int32_t)((tmp & - (((1ULL << intpart)-1) << floatpart)) >> floatpart); - tmp = (tmp & ((1<< floatpart) - 1))*10000/(1ULL << floatpart); - if ((buf = _lldpctl_alloc_in_atom(atom, 64)) == NULL) + char *stored = _lldpctl_alloc_in_atom(atom, strlen(result) + 1); + if (stored == NULL) { + free(result); return NULL; - n = snprintf(buf, 64, "%s%u.%04llu%s", - displaysign?(negative?"-":"+"):"", - integer, (unsigned long long int)tmp, - (negative && negsuffix)?negsuffix: - (!negative && possuffix)?possuffix:""); - if (n > -1 && n < 64) - return buf; - SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM); - return NULL; + } + strlcpy(stored, result, strlen(result) + 1); + return stored; } static const char* @@ -1931,7 +1918,6 @@ _lldpctl_atom_get_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key) struct _lldpctl_atom_med_location_t *m = (struct _lldpctl_atom_med_location_t *)atom; char *value; - u_int64_t l; /* Local and remote port */ switch (key) { @@ -1943,20 +1929,16 @@ _lldpctl_atom_get_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key) m->location->data[15]); case lldpctl_k_med_location_latitude: if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break; - memcpy(&l, m->location->data, sizeof(u_int64_t)); - l = (ntohll(l) & 0x03FFFFFFFF000000ULL) >> 24; - return fixed_precision(atom, l, 9, 25, 0, " S", " N"); + return read_fixed_precision(atom, m->location->data, + 0, 9, 25, "NS"); case lldpctl_k_med_location_longitude: if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break; - memcpy(&l, m->location->data + 5, sizeof(u_int64_t)); - l = (ntohll(l) & 0x03FFFFFFFF000000ULL) >> 24; - return fixed_precision(atom, l, 9, 25, 0, " W", " E"); + return read_fixed_precision(atom, m->location->data, + 40, 9, 25, "EW"); case lldpctl_k_med_location_altitude: if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break; - l = 0; - memcpy(&l, m->location->data + 10, 5); - l = (ntohll(l) & 0x3FFFFFFF000000ULL) >> 24; - return fixed_precision(atom, l, 22, 8, 1, NULL, NULL); + return read_fixed_precision(atom, m->location->data, + 84, 22, 8, NULL); case lldpctl_k_med_location_altitude_unit: if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break; switch (m->location->data[10] & 0xf0) { @@ -1987,51 +1969,13 @@ _lldpctl_atom_get_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key) return NULL; } -static void -write_fixed_precision(uint8_t *where, double l, - int precisionnb, int intnb, int floatnb) -{ - int intpart, floatpart, precision = 6; - if (l > 0) { - intpart = (int)l; - floatpart = (l - intpart) * (1 << floatnb); - } else { - intpart = -(int)l; - floatpart = (-(l + intpart)) * (1 << floatnb); - intpart = ~intpart; intpart += 1; - floatpart = ~floatpart; floatpart += 1; - } - if ((1 << precisionnb) - 1 < precision) - precision = (1 << precisionnb) - 1; - /* We need to write precision, int part and float part. */ - do { - int obit, i, o; - unsigned int ints[3] = { precision, intpart, floatpart }; - unsigned int bits[3] = { precisionnb, intnb, floatnb }; - for (i = 0, obit = 8, o = 0; i < 3;) { - if (obit > bits[i]) { - where[o] = where[o] | - ((ints[i] & ((1 << bits[i]) - 1)) << (obit - bits[i])); - obit -= bits[i]; - i++; - } else { - where[o] = where[o] | - ((ints[i] >> (bits[i] - obit)) & ((1 << obit) - 1)); - bits[i] -= obit; - obit = 8; - o++; - } - } - } while(0); -} - static lldpctl_atom_t* _lldpctl_atom_set_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key, const char *value) { struct _lldpctl_atom_med_location_t *mloc = (struct _lldpctl_atom_med_location_t *)atom; - double l; + struct fp_number fp; char *end; /* Only local port can be modified */ @@ -2044,33 +1988,33 @@ _lldpctl_atom_set_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key, case lldpctl_k_med_location_latitude: if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad; if (mloc->location->data == NULL || mloc->location->data_len != 16) goto bad; - l = strtod(value, &end); + fp = fp_strtofp(value, &end, 9, 25); if (!end) goto bad; if (end && *end != '\0') { if (*(end+1) != '\0') goto bad; - if (*end == 'S') l = -l; + if (*end == 'S') fp = fp_negate(fp); else if (*end != 'N') goto bad; } - write_fixed_precision((uint8_t*)mloc->location->data, l, 6, 9, 25); + fp_fptobuf(fp, (unsigned char*)mloc->location->data, 0); return atom; case lldpctl_k_med_location_longitude: if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad; if (mloc->location->data == NULL || mloc->location->data_len != 16) goto bad; - l = strtod(value, &end); + fp = fp_strtofp(value, &end, 9, 25); if (!end) goto bad; if (end && *end != '\0') { if (*(end+1) != '\0') goto bad; - if (*end == 'W') l = -l; + if (*end == 'W') fp = fp_negate(fp); else if (*end != 'E') goto bad; } - write_fixed_precision((uint8_t*)mloc->location->data + 5, l, 6, 9, 25); + fp_fptobuf(fp, (unsigned char*)mloc->location->data, 40); return atom; case lldpctl_k_med_location_altitude: if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad; if (mloc->location->data == NULL || mloc->location->data_len != 16) goto bad; - l = strtod(value, &end); + fp = fp_strtofp(value, &end, 22, 8); if (!end || *end != '\0') goto bad; - write_fixed_precision((uint8_t*)mloc->location->data + 11, l, 2, 22, 8); + fp_fptobuf(fp, (unsigned char*)mloc->location->data, 84); return atom; case lldpctl_k_med_location_altitude_unit: if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad; diff --git a/src/lib/fixedpoint.c b/src/lib/fixedpoint.c new file mode 100644 index 00000000..5588b8f4 --- /dev/null +++ b/src/lib/fixedpoint.c @@ -0,0 +1,253 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include "fixedpoint.h" + +/* This is not a general purpose fixed point library. First, there is no + * arithmetic. Second, some functions assume that the total precision does not + * exceed 64 bits. + */ + +#ifdef ENABLE_LLDPMED + +#define ntohll(x) \ + (((u_int64_t)(ntohl((int)(((x) << 32) >> 32))) << 32) | \ + (unsigned int)ntohl(((int)((x) >> 32)))) + +/** + * Convert a string to fixed point number. + * + * @param repr String to convert. + * @param end If not NULL, will contain a pointer to the character after the + * last character used in the conversion. + * @param intbits Number of bits to represent the integer part. + * @param fltbits Number of bits to represent the float part. + * @return A fixed point number. + * + * If there is an overflow, there will be a truncation. Moreover, the fraction + * part will be rounded to the nearest possible power of two representation. The + * point will depend on the number of decimal provided with the fraction + * part. + */ +struct fp_number +fp_strtofp(const char *repr, char **end, + unsigned intbits, unsigned fltbits) +{ + char *endptr = NULL, *e2; + struct fp_number result = { + .integer = { 0, intbits }, + .fraction = { 0, fltbits, 0 } + }; + result.integer.value = strtoll(repr, &endptr, 10); + if (result.integer.value >= (1LL << (intbits - 1))) + result.integer.value = (1LL << (intbits - 1)) - 1; + else if (result.integer.value < ~(1LL << (intbits - 1)) + 1) + result.integer.value = ~(1LL << (intbits - 1)) + 1; + if (*endptr == '.') { + long long precision = 1; + e2 = endptr + 1; + result.fraction.value = strtoll(e2, &endptr, 10); + /* Convert to a representation in power of two. Get the + * precision from the number of digits provided. This is NOT the + * value of the higher bits in the binary representation: we + * consider that if the user inputs, 0.9375, it means to + * represent anything between 0 and 0.9999 with the same + * precision. Therefore, we don't have only 4 bits of precision + * but 14. */ + while (e2++ != endptr) precision *= 10; + result.fraction.value <<= fltbits; + result.fraction.value /= precision; + result.fraction.precision = sizeof(precision) * 8 - + __builtin_clzll(precision - 1); + if (result.fraction.precision > fltbits) + result.fraction.precision = fltbits; + } + if (end) *end = endptr; + return result; +} + +/** + * Get a string representation of a fixed point number. + * + * @param fp Fixed point number. + * @param suffix If not NULL, use the first character when positive and the + * second one when negative instead of prefixing by `-`. + * @return the string representation + * + * Since we convert from binary to decimal, we are as precise as the binary + * representation. + */ +char * +fp_fptostr(struct fp_number fp, const char *suffix) +{ + char *result = NULL; + char *frac = NULL; + int negative = (fp.integer.value < 0); + if (fp.fraction.value == 0) + frac = strdup(""); + else { + long long decimal = fp.fraction.value; + long long precision = 1; + int len = 0; + while ((1LL << fp.fraction.precision) > precision) { + precision *= 10; + len += 1; + } + /* We did round-up, when converting from decimal. We round-down + * to have some coherency. */ + precision /= 10; len -= 1; + if (precision == 0) precision = 1; + decimal *= precision; + decimal >>= fp.fraction.bits; + if (asprintf(&frac, ".%0*llu", len, decimal) == -1) + return NULL; + } + if (asprintf(&result, "%s%llu%s%c", + (suffix == NULL && negative) ? "-" : "", + (negative) ? (-fp.integer.value) : fp.integer.value, + frac, + (suffix && !negative) ? suffix[0] : + (suffix && negative) ? suffix[1] : ' ') == -1) { + free(frac); + return NULL; + } + free(frac); + if (!suffix) result[strlen(result) - 1] = '\0'; + return result; +} + +/** + * Turn a fixed point number into its representation in a buffer. + * + * @param fp Fixed point number. + * @param buf Output buffer. + * @param shift Number of bits to skip at the beginning of the buffer. + * + * The representation of a fixed point number is the precision (always 6 bits + * because we assume that int part + frac part does not exceed 64 bits), the + * integer part and the fractional part. + */ +void +fp_fptobuf(struct fp_number fp, unsigned char *buf, unsigned shift) +{ + unsigned long long value = (fp.integer.value >= 0) ? + ((fp.integer.value << fp.fraction.bits) + fp.fraction.value) : + (~(((unsigned long long)(-fp.integer.value) << fp.fraction.bits) + + fp.fraction.value) + 1); + unsigned long long ints[] = { fp.integer.bits + fp.fraction.precision, + value }; + unsigned int bits[] = { 6, + fp.integer.bits + fp.fraction.bits }; + + unsigned i, obit, o; + for (i = 0, obit = 8 - (shift % 8), o = shift / 8; i < 2;) { + if (obit > bits[i]) { + /* We need to clear bits that will be overwritten but do not touch other bits */ + buf[o] = buf[o] & (~((1 << obit) - 1) | + ((1 << (obit - bits[i])) - 1)); + buf[o] = buf[o] | + ((ints[i] & ((1 << bits[i]) - 1)) << (obit - bits[i])); + obit -= bits[i]; + i++; + } else { + /* As in the other branch... */ + buf[o] = buf[o] & (~((1 << obit) - 1)); + buf[o] = buf[o] | + ((ints[i] >> (bits[i] - obit)) & ((1 << obit) - 1)); + bits[i] -= obit; + obit = 8; + o++; + } + } +} + +/** + * Parse a fixed point number from a buffer. + * + * @param buf Input buffer + * @param intbits Number of bits used for integer part. + * @param fltbits Number of bits used for fractional part. + * @param shift Number of bits to skip at the beginning of the buffer. + * + * @return the parsed fixed point number. + * + * The representation is the same as for @c fp_fptobuf(). + */ +struct fp_number +fp_buftofp(const unsigned char *buf, + unsigned intbits, unsigned fltbits, + unsigned shift) +{ + unsigned long long value = 0, precision = 0; + unsigned long long *ints[] = { &precision, + &value }; + unsigned int bits[] = { 6, + intbits + fltbits }; + + unsigned o, ibit, i; + for (o = 0, ibit = 8 - (shift % 8), i = shift / 8; o < 2;) { + if (ibit > bits[o]) { + *ints[o] = *ints[o] | ((buf[i] >> (ibit - bits[o])) & ((1ULL << bits[o]) - 1)); + ibit -= bits[o]; + o++; + } else { + *ints[o] = *ints[o] | ((buf[i] & ((1ULL << ibit) - 1)) << (bits[o] - ibit)); + bits[o] -= ibit; + ibit = 8; + i++; + } + } + + /* Don't handle too low precision */ + if (precision > intbits) + precision -= intbits; + else + precision = intbits; + + int negative = !!(value & (1ULL << (intbits + fltbits - 1))); + if (negative) value = (~value + 1) & ((1ULL << (intbits + fltbits - 1)) - 1); + struct fp_number result = { + .integer = { value >> fltbits, intbits }, + .fraction = { value & ((1ULL << fltbits) - 1), fltbits, precision } + }; + if (negative) result.integer.value = -result.integer.value; + + return result; +} + +/** + * Negate a fixed point number. + */ +struct fp_number +fp_negate(struct fp_number fp) +{ + unsigned intbits = fp.integer.bits; + struct fp_number result = fp; + result.integer.value = -result.integer.value; + if (result.integer.value >= (1LL << (intbits - 1))) + result.integer.value = (1LL << (intbits - 1)) - 1; + else if (result.integer.value < ~(1LL << (intbits - 1)) + 1) + result.integer.value = ~(1LL << (intbits - 1)) + 1; + return result; +} + +#endif diff --git a/src/lib/fixedpoint.h b/src/lib/fixedpoint.h new file mode 100644 index 00000000..cc076439 --- /dev/null +++ b/src/lib/fixedpoint.h @@ -0,0 +1,42 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include +#endif + +#if ! defined FIXEDPOINT_H && defined ENABLE_LLDPMED +#define FIXEDPOINT_H + +struct fp_number { + struct { + long long value; + unsigned bits; + } integer; + struct { + long long value; + unsigned bits; + unsigned precision; + } fraction; +}; +struct fp_number fp_strtofp(const char *, char **, unsigned, unsigned); +struct fp_number fp_buftofp(const unsigned char *, unsigned, unsigned, unsigned); +struct fp_number fp_negate(struct fp_number); +char *fp_fptostr(struct fp_number, const char *); +void fp_fptobuf(struct fp_number, unsigned char *, unsigned); + +#endif diff --git a/tests/Makefile.am b/tests/Makefile.am index fb8ccc18..42f76a00 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,5 @@ AM_CFLAGS = -I $(top_srcdir)/include -TESTS = check_marshal check_lldp check_cdp check_sonmp check_edp +TESTS = check_marshal check_lldp check_cdp check_sonmp check_edp check_fixedpoint if HAVE_CHECK @@ -22,6 +22,10 @@ check_edp_SOURCES = check_edp.c \ $(top_srcdir)/src/daemon/lldpd.h \ common.h common.c +check_fixedpoint_SOURCES = check_fixedpoint.c \ + $(top_srcdir)/src/lib/fixedpoint.h \ + $(top_srcdir)/src/lib/fixedpoint.c + AM_CFLAGS += @CHECK_CFLAGS@ LDADD = $(top_builddir)/src/daemon/liblldpd.la @CHECK_LIBS@ @LIBEVENT_LDFLAGS@ diff --git a/tests/check_fixedpoint.c b/tests/check_fixedpoint.c new file mode 100644 index 00000000..2f13d13a --- /dev/null +++ b/tests/check_fixedpoint.c @@ -0,0 +1,319 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "../src/lib/fixedpoint.h" + +#ifdef ENABLE_LLDPMED + +START_TEST(test_string_parsing_suffix) { + char *end; + fp_strtofp("4541T", &end, 14, 8); + ck_assert_int_eq(*end, 'T'); + fp_strtofp("4541.U", &end, 14, 8); + ck_assert_int_eq(*end, 'U'); + fp_strtofp("4541.676V", &end, 14, 8); + ck_assert_int_eq(*end, 'V'); +} +END_TEST + +START_TEST(test_string_parsing_positive_int) { + struct fp_number fp = fp_strtofp("4541T", NULL, 14, 8); + ck_assert_int_eq(fp.integer.bits, 14); + ck_assert_int_eq(fp.integer.value, 4541); + ck_assert_int_eq(fp.fraction.bits, 8); + ck_assert_int_eq(fp.fraction.value, 0); + ck_assert_int_eq(fp.fraction.precision, 0); +} +END_TEST + +START_TEST(test_string_parsing_negative_int) { + struct fp_number fp = fp_strtofp("-4214N", NULL, 14, 8); + ck_assert_int_eq(fp.integer.bits, 14); + ck_assert_int_eq(fp.integer.value, -4214); + ck_assert_int_eq(fp.fraction.bits, 8); + ck_assert_int_eq(fp.fraction.value, 0); + ck_assert_int_eq(fp.fraction.precision, 0); +} +END_TEST + +START_TEST(test_string_parsing_positive_int_overflow) { + struct fp_number fp1 = fp_strtofp("4098", NULL, 13, 8); + struct fp_number fp2 = fp_strtofp("4096", NULL, 13, 8); + struct fp_number fp3 = fp_strtofp("4095", NULL, 13, 8); + struct fp_number fp4 = fp_strtofp("4094", NULL, 13, 8); + ck_assert_int_eq(fp1.integer.value, 4095); + ck_assert_int_eq(fp2.integer.value, 4095); + ck_assert_int_eq(fp3.integer.value, 4095); + ck_assert_int_eq(fp4.integer.value, 4094); +} +END_TEST + +START_TEST(test_string_parsing_negative_int_overflow) { + struct fp_number fp1 = fp_strtofp("-4097", NULL, 13, 8); + struct fp_number fp2 = fp_strtofp("-4096", NULL, 13, 8); + struct fp_number fp3 = fp_strtofp("-4095", NULL, 13, 8); + struct fp_number fp4 = fp_strtofp("-4094", NULL, 13, 8); + ck_assert_int_eq(fp1.integer.value, -4096); + ck_assert_int_eq(fp2.integer.value, -4096); + ck_assert_int_eq(fp3.integer.value, -4095); + ck_assert_int_eq(fp4.integer.value, -4094); +} +END_TEST + +START_TEST(test_string_parsing_positive_float) { + struct fp_number fp1 = fp_strtofp("1542.6250E", NULL, 13, 20); + ck_assert_int_eq(fp1.integer.value, 1542); + ck_assert_int_eq(fp1.fraction.precision, 14); + ck_assert_int_eq((fp1.fraction.value * 10000) >> fp1.fraction.bits, 6250); + + struct fp_number fp2 = fp_strtofp("1542.06250E", NULL, 13, 4); + ck_assert_int_eq(fp2.integer.value, 1542); + ck_assert_int_eq(fp2.fraction.precision, 4); + ck_assert_int_eq((fp2.fraction.value * 10000) >> fp2.fraction.bits, 625); +} +END_TEST + +START_TEST(test_string_parsing_negative_float) { + struct fp_number fp = fp_strtofp("-11542.6250N", NULL, 15, 4); + ck_assert_int_eq(fp.integer.value, -11542); + ck_assert_int_eq(fp.fraction.precision, 4); + ck_assert_int_eq((fp.fraction.value * 10000) >> fp.fraction.bits, 6250); +} +END_TEST + +START_TEST(test_string_parsing_no_fract_part) { + struct fp_number fp = fp_strtofp("11542.", NULL, 15, 4); + ck_assert_int_eq(fp.integer.value, 11542); + ck_assert_int_eq(fp.fraction.value, 0); + ck_assert_int_eq(fp.fraction.precision, 1); +} +END_TEST + +START_TEST(test_string_parsing_no_int_part) { + struct fp_number fp = fp_strtofp(".6250E", NULL, 13, 4); + ck_assert_int_eq(fp.integer.value, 0); + ck_assert_int_eq(fp.fraction.precision, 4); + ck_assert_int_eq((fp.fraction.value * 10000) >> fp.fraction.bits, 6250); +} +END_TEST + + +START_TEST(test_string_representation_positive_int) { + struct fp_number fp1 = fp_strtofp("214", NULL, 9, 9); + struct fp_number fp2 = fp_strtofp("11178.0000", NULL, 15, 9); + ck_assert_str_eq(fp_fptostr(fp1, NULL), "214"); + ck_assert_str_eq(fp_fptostr(fp2, NULL), "11178"); + ck_assert_str_eq(fp_fptostr(fp2, "ES"), "11178E"); +} +END_TEST + +START_TEST(test_string_representation_negative_int) { + struct fp_number fp1 = fp_strtofp("-214", NULL, 9, 9); + struct fp_number fp2 = fp_strtofp("-11178.0000", NULL, 15, 9); + ck_assert_str_eq(fp_fptostr(fp1, NULL), "-214"); + ck_assert_str_eq(fp_fptostr(fp2, NULL), "-11178"); + ck_assert_str_eq(fp_fptostr(fp2, "ES"), "11178S"); +} +END_TEST + +START_TEST(test_string_representation_positive_float) { + struct fp_number fp = fp_strtofp("214.6250", NULL, 9, 20); + ck_assert_str_eq(fp_fptostr(fp, NULL), "214.6250"); + ck_assert_str_eq(fp_fptostr(fp, "ES"), "214.6250E"); +} +END_TEST + +START_TEST(test_string_representation_positive_float_with_leading_zero) { + struct fp_number fp = fp_strtofp("214.06250", NULL, 9, 24); + ck_assert_str_eq(fp_fptostr(fp, NULL), "214.06250"); + ck_assert_str_eq(fp_fptostr(fp, "ES"), "214.06250E"); +} +END_TEST + +START_TEST(test_string_representation_negative_float) { + struct fp_number fp1 = fp_strtofp("-214.625", NULL, 22, 10); + struct fp_number fp2 = fp_strtofp("-415.5", NULL, 22, 4); + ck_assert_str_eq(fp_fptostr(fp1, NULL), "-214.625"); + ck_assert_str_eq(fp_fptostr(fp2, NULL), "-415.5"); + ck_assert_str_eq(fp_fptostr(fp2, "ES"), "415.5S"); +} +END_TEST + +START_TEST(test_buffer_representation_positive_float) { + unsigned char buffer[5] = {}; + unsigned char expected[] = { 0x21 << 2, 47 << 1, 0x68, 0x00, 0x00 }; + /* 47.2031250 = 47 + 2**-3 + 2**-4 + 2**-6, precision = 9+24 */ + struct fp_number fp = fp_strtofp("47.2031250", NULL, 9, 25); + fp_fptobuf(fp, buffer, 0); + fail_unless(memcmp(buffer, expected, sizeof(expected)) == 0); +} +END_TEST + +START_TEST(test_buffer_representation_negative_float) { + unsigned char buffer[5] = {}; + unsigned char expected[] = { (0x21 << 2) | 3, 0xa1, 0x98, 0x00, 0x00 }; + /* 47.2031250 = 000101111.0011010000000000000000000 */ + /* -47.2031250 = 111010000.1100101111111111111111111 + 1 */ + /* -47.2031250 = 111010000.1100110000000000000000000 */ + struct fp_number fp = fp_strtofp("-47.2031250", NULL, 9, 25); + fp_fptobuf(fp, buffer, 0); + fail_unless(memcmp(buffer, expected, sizeof(expected)) == 0); +} +END_TEST + +START_TEST(test_buffer_representation_with_shift) { + unsigned char buffer[] = { 0x77, 0xc6, 0x0, 0x0, 0x0, 0x0, 0xc7 }; + unsigned char expected[] = { 0x77, 0xc8, 0x45, 0xe6, 0x80, 0x00, 0x07 }; + struct fp_number fp = fp_strtofp("47.2031250", NULL, 9, 25); + fp_fptobuf(fp, buffer, 12); + fail_unless(memcmp(buffer, expected, sizeof(buffer)) == 0); +} +END_TEST + +START_TEST(test_buffer_representation_altitude) { + unsigned char buffer[5] = {}; + unsigned char expected[] = { (22 + 4) << 2, 0, 0, 14 << 4 | 1 << 3, 0 }; + struct fp_number fp = fp_strtofp("14.5", NULL, 22, 8); + fp_fptobuf(fp, buffer, 0); + fail_unless(memcmp(buffer, expected, sizeof(buffer)) == 0); +} +END_TEST + +START_TEST(test_buffer_parsing_positive_float) { + unsigned char buffer[] = { 0x21 << 2, 47 << 1, 0x68, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 0); + ck_assert_int_eq(fp.integer.value, 47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +START_TEST(test_buffer_parsing_negative_float) { + unsigned char buffer[] = { (0x21 << 2) | 3, 0xa1, 0x98, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 0); + ck_assert_int_eq(fp.integer.value, -47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +/* This is some corner case */ +START_TEST(test_buffer_parsing_positive_float_2) { + unsigned char buffer[] = { 0x40, 0x9c, 0x80, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 0); + ck_assert_int_eq(fp.integer.value, 78); +} +END_TEST + +START_TEST(test_buffer_parsing_positive_float_with_shift) { + unsigned char buffer[] = { 0x77, 0xc8, 0x45, 0xe6, 0x80, 0x00, 0x07 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 12); + ck_assert_int_eq(fp.integer.value, 47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +START_TEST(test_buffer_parsing_negative_float_with_shift) { + unsigned char buffer[] = { 0x00, 0xff, (0x21 << 2) | 3, 0xa1, 0x98, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 16); + ck_assert_int_eq(fp.integer.value, -47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +START_TEST(test_negate_positive) { + struct fp_number fp = fp_strtofp("14.5", NULL, 9, 25); + struct fp_number nfp = fp_negate(fp); + ck_assert_int_eq(nfp.integer.value, -14); + ck_assert_int_eq(fp.fraction.value, nfp.fraction.value); + ck_assert_str_eq(fp_fptostr(nfp, NULL), "-14.5"); +} +END_TEST + +START_TEST(test_negate_negative) { + struct fp_number fp = fp_strtofp("-14.5", NULL, 9, 25); + struct fp_number nfp = fp_negate(fp); + ck_assert_int_eq(nfp.integer.value, 14); + ck_assert_int_eq(fp.fraction.value, nfp.fraction.value); + ck_assert_str_eq(fp_fptostr(nfp, NULL), "14.5"); +} +END_TEST + +#endif + +Suite * +fixedpoint_suite(void) +{ + Suite *s = suite_create("Fixed point representation"); + +#ifdef ENABLE_LLDPMED + TCase *tc_fp = tcase_create("Fixed point representation"); + tcase_add_test(tc_fp, test_string_parsing_suffix); + tcase_add_test(tc_fp, test_string_parsing_positive_int); + tcase_add_test(tc_fp, test_string_parsing_negative_int); + tcase_add_test(tc_fp, test_string_parsing_no_fract_part); + tcase_add_test(tc_fp, test_string_parsing_no_int_part); + tcase_add_test(tc_fp, test_string_parsing_positive_int_overflow); + tcase_add_test(tc_fp, test_string_parsing_negative_int_overflow); + tcase_add_test(tc_fp, test_string_parsing_positive_float); + tcase_add_test(tc_fp, test_string_parsing_negative_float); + tcase_add_test(tc_fp, test_string_representation_positive_int); + tcase_add_test(tc_fp, test_string_representation_negative_int); + tcase_add_test(tc_fp, test_string_representation_positive_float); + tcase_add_test(tc_fp, test_string_representation_positive_float_with_leading_zero); + tcase_add_test(tc_fp, test_string_representation_negative_float); + tcase_add_test(tc_fp, test_buffer_representation_positive_float); + tcase_add_test(tc_fp, test_buffer_representation_negative_float); + tcase_add_test(tc_fp, test_buffer_representation_with_shift); + tcase_add_test(tc_fp, test_buffer_representation_altitude); + tcase_add_test(tc_fp, test_buffer_parsing_positive_float); + tcase_add_test(tc_fp, test_buffer_parsing_positive_float_2); + tcase_add_test(tc_fp, test_buffer_parsing_negative_float); + tcase_add_test(tc_fp, test_buffer_parsing_positive_float_with_shift); + tcase_add_test(tc_fp, test_buffer_parsing_negative_float_with_shift); + tcase_add_test(tc_fp, test_negate_positive); + tcase_add_test(tc_fp, test_negate_negative); + suite_add_tcase(s, tc_fp); +#endif + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = fixedpoint_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} -- 2.39.5