]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
lib: introduce proper fixed point parsing and representation module
authorVincent Bernat <bernat@luffy.cx>
Wed, 24 Jul 2013 20:20:50 +0000 (22:20 +0200)
committerVincent Bernat <bernat@luffy.cx>
Wed, 24 Jul 2013 20:21:58 +0000 (22:21 +0200)
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
src/lib/Makefile.am
src/lib/atom-private.c
src/lib/fixedpoint.c [new file with mode: 0644]
src/lib/fixedpoint.h [new file with mode: 0644]
tests/Makefile.am
tests/check_fixedpoint.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index d0f8d6db97b3f2ba713081304b2e042a2d08ad1a..80a74ea570694df9906b48e5aaf86f37a590c35f 100644 (file)
--- 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.
index f074c4f16102b609e6bdea8d3c6672e4e9897c5b..f9d9223274121078530d020a2ebb47b537d00dcd 100644 (file)
@@ -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
 
index 1516dbca16f258278c9439d52a1d7d196f98d7d9..0cbf1c0bfc0746c2573f7e1d6be9f0b3dd30f71d 100644 (file)
 #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 (file)
index 0000000..5588b8f
--- /dev/null
@@ -0,0 +1,253 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#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 (file)
index 0000000..cc07643
--- /dev/null
@@ -0,0 +1,42 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * 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 <config.h>
+#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
index fb8ccc18e323a531c59bacf93215669f4828bd28..42f76a0052a9c6515c4ad512c82a0e9222481e30 100644 (file)
@@ -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 (file)
index 0000000..2f13d13
--- /dev/null
@@ -0,0 +1,319 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * 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 <check.h>
+#include <stdlib.h>
+
+#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;
+}