Initial commit, pulled from openxc/vi-firmware.
authorChristopher Peplin <chris.peplin@rhubarbtech.com>
Tue, 24 Dec 2013 15:56:35 +0000 (10:56 -0500)
committerChristopher Peplin <chris.peplin@rhubarbtech.com>
Tue, 24 Dec 2013 15:56:35 +0000 (10:56 -0500)
13 files changed:
CHANGELOG.mkd [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.mkd [new file with mode: 0644]
runtests.sh [new file with mode: 0644]
src/bitfield/bitfield.c [new file with mode: 0644]
src/bitfield/bitfield.h [new file with mode: 0644]
src/canutil/read.c [new file with mode: 0644]
src/canutil/read.h [new file with mode: 0644]
src/canutil/write.c [new file with mode: 0644]
src/canutil/write.h [new file with mode: 0644]
tests/bitfield_tests.c [new file with mode: 0644]
tests/tests.bin [new file with mode: 0755]

diff --git a/CHANGELOG.mkd b/CHANGELOG.mkd
new file mode 100644 (file)
index 0000000..e710764
--- /dev/null
@@ -0,0 +1,5 @@
+# CAN Message Utilities for C
+
+## v0.1
+
+* Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..330d61f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2013 Ford Motor Company
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..8385c3a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+CC = gcc
+INCLUDES = -Isrc
+CFLAGS = $(INCLUDES) -c -w -Wall -Werror -g -ggdb
+LDFLAGS =
+LDLIBS = -lcheck
+
+TEST_DIR = tests
+
+# Guard against \r\n line endings only in Cygwin
+OSTYPE := $(shell uname)
+ifneq ($(OSTYPE),Darwin)
+       OSTYPE := $(shell uname -o)
+       ifeq ($(OSTYPE),Cygwin)
+               TEST_SET_OPTS = igncr
+       endif
+endif
+
+SRC = $(wildcard src/**/*.c)
+OBJS = $(SRC:.c=.o)
+TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
+TEST_OBJS = $(TEST_SRC:.c=.o)
+
+all: $(OBJS)
+
+test: $(TEST_DIR)/tests.bin
+       @set -o $(TEST_SET_OPTS) >/dev/null 2>&1
+       @export SHELLOPTS
+       @sh runtests.sh $(TEST_DIR)
+
+$(TEST_DIR)/tests.bin: $(TEST_OBJS) $(OBJS)
+       @mkdir -p $(dir $@)
+       $(CC) $(LDFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $^ $(LDLIBS)
+
+clean:
+       rm -rf **/*.o $(TEST_DIR)/*.bin
diff --git a/README.mkd b/README.mkd
new file mode 100644 (file)
index 0000000..0d8efb7
--- /dev/null
@@ -0,0 +1,18 @@
+CAN Message Utilities for C
+============
+
+## Testing
+
+The library includes a test suite that uses the `check` C unit test library.
+
+    $ make test
+
+## Authors
+
+Chris Peplin cpeplin@ford.com
+
+## License
+
+Copyright (c) 2013 Ford Motor Company
+
+Licensed under the BSD license.
diff --git a/runtests.sh b/runtests.sh
new file mode 100644 (file)
index 0000000..4781636
--- /dev/null
@@ -0,0 +1,17 @@
+echo "Running unit tests:"
+
+for i in $1/*.bin
+do
+    if test -f $i
+    then
+        if ./$i
+        then
+            echo $i PASS
+        else
+            echo "ERROR in test $i:"
+            exit 1
+        fi
+    fi
+done
+
+echo "${txtbld}$(tput setaf 2)All unit tests passed.$(tput sgr0)"
diff --git a/src/bitfield/bitfield.c b/src/bitfield/bitfield.c
new file mode 100644 (file)
index 0000000..d383db0
--- /dev/null
@@ -0,0 +1,60 @@
+#include <bitfield/bitfield.h>
+
+/**
+ * Find the ending bit of a bitfield within the final byte.
+ *
+ * Returns: a bit position from 0 to 7.
+ */
+int findEndBit(int startBit, int numBits) {
+    int endBit = (startBit + numBits) % 8;
+    return endBit == 0 ? 8 : endBit;
+}
+
+uint64_t bitmask(int numBits) {
+    return (((uint64_t)0x1) << numBits) - 1;
+}
+
+int startingByte(int startBit) {
+    return startBit / 8;
+}
+
+int endingByte(int startBit, int numBits) {
+    return (startBit + numBits - 1) / 8;
+}
+
+uint64_t getBitField(uint64_t data, int startBit, int numBits, bool bigEndian) {
+    int startByte = startingByte(startBit);
+    int endByte = endingByte(startBit, numBits);
+
+    if(!bigEndian) {
+        data = __builtin_bswap64(data);
+    }
+    uint8_t* bytes = (uint8_t*)&data;
+    uint64_t ret = bytes[startByte];
+    if(startByte != endByte) {
+        // The lowest byte address contains the most significant bit.
+        int i;
+        for(i = startByte + 1; i <= endByte; i++) {
+            ret = ret << 8;
+            ret = ret | bytes[i];
+        }
+    }
+
+    ret >>= 8 - findEndBit(startBit, numBits);
+    return ret & bitmask(numBits);
+}
+
+/**
+ * TODO it would be nice to have a warning if you call with this a value that
+ * won't fit in the number of bits you've specified it should use.
+ */
+void setBitField(uint64_t* data, uint64_t value, int startBit, int numBits) {
+    int shiftDistance = 64 - startBit - numBits;
+    value <<= shiftDistance;
+    *data &= ~(bitmask(numBits) << shiftDistance);
+    *data |= value;
+}
+
+uint8_t nthByte(uint64_t source, int byteNum) {
+    return (source >> (64 - ((byteNum + 1) * 8))) & 0xFF;
+}
diff --git a/src/bitfield/bitfield.h b/src/bitfield/bitfield.h
new file mode 100644 (file)
index 0000000..2776673
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef __BITFIELD_H__
+#define __BITFIELD_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* Public: Reads a subset of bits from a byte array.
+ *
+ * data - the bytes in question.
+ * startPos - the starting index of the bit field (beginning from 0).
+ * numBits - the width of the bit field to extract.
+ * bigEndian - if the data passed in is little endian, set this to false and it
+ *      will be flipped before grabbing the bit field.
+ *
+ * Bit fields are positioned according to big-endian bit layout, but inside the
+ * bit field, values are represented as little-endian. Therefore, to get the bit
+ * field, we swap the overall byte order if bigEndian == false and
+ * use the value we find in the field (assuming the embedded platform is little
+ * endian).
+ *
+ * For example, the bit layout of the value "42" (i.e. 00101010 set at position
+ * 14 with length 6 is:
+ *
+ *     000000000000001010100000000000000000000000000000000000000000000
+ *
+ * and the same value and position but with length 8 is:
+ *
+ *     000000000000000010101000000000000000000000000000000000000000000
+ *
+ * If the architecture where is code is running is little-endian, the input data
+ * will be swapped before grabbing the bit field.
+ *
+ * Examples
+ *
+ *  uint64_t value = getBitField(data, 2, 4);
+ *
+ * Returns the value of the requested bit field.
+ */
+uint64_t getBitField(uint64_t data, int startPos, int numBits, bool bigEndian);
+
+/* Public: Set the bit field in the given data array to the new value.
+ *
+ * data - a byte array with size at least startPos + numBits.
+ * value - the value to set in the bit field.
+ * startPos - the starting index of the bit field (beginning from 0).
+ */
+void setBitField(uint64_t* data, uint64_t value, int startPos, int numBits);
+
+/* Public: Retreive the nth byte out of 8 bytes in a uint64_t.
+ *
+ * source - the source data to retreive the byte from.
+ * byteNum - the index of the byte, starting at 0 and assuming big-endian order.
+ *
+ * Returns the requested byte from the source bytes.
+ */
+uint8_t nthByte(uint64_t source, int byteNum);
+
+#endif // __BITFIELD_H__
diff --git a/src/canutil/read.c b/src/canutil/read.c
new file mode 100644 (file)
index 0000000..6b4e40a
--- /dev/null
@@ -0,0 +1,14 @@
+#include <bitfield/bitfield.h>
+
+float parseFloat(uint64_t data, uint8_t bitPosition, uint8_t bitSize,
+        float factor, float offset) {
+    uint64_t rawValue = getBitField(data, bitPosition,
+            bitSize, true);
+    return rawValue * factor + offset;
+}
+
+bool parseBoolean(uint64_t data, uint8_t bitPosition, uint8_t bitSize,
+        float factor, float offset) {
+    float value = parseFloat(data, bitPosition, bitSize, factor, offset);
+    return value == 0.0 ? false : true;
+}
diff --git a/src/canutil/read.h b/src/canutil/read.h
new file mode 100644 (file)
index 0000000..815f26b
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __READ_H__
+#define __READ_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+float parseFloat(uint64_t data, uint8_t bitPosition, uint8_t bitSize,
+        float factor, float offset);
+
+bool parseBoolean(uint64_t data, uint8_t bitPosition, uint8_t bitSize,
+        float factor, float offset);
+
+#endif // __READ_H__
diff --git a/src/canutil/write.c b/src/canutil/write.c
new file mode 100644 (file)
index 0000000..fdcba1f
--- /dev/null
@@ -0,0 +1,18 @@
+#include "write.h"
+
+uint64_t encodeFloat(float value, float offset, float factor, uint8_t bitPosition,
+        uint8_t bitSize) {
+    float rawValue = (value - offset) / factor;
+    if(rawValue > 0) {
+        // round up to avoid losing precision when we cast to an int
+        rawValue += 0.5;
+    }
+    uint64_t result = 0;
+    setBitField(&result, rawValue, bitPosition, bitSize);
+    return result;
+}
+
+uint64_t encodeBoolean(bool value, float offset, float factor,
+                uint8_t bitPosition, uint8_t bitSize) {
+        return encodeFloat(value, offset, factor, bitPosition, bitSize);
+}
diff --git a/src/canutil/write.h b/src/canutil/write.h
new file mode 100644 (file)
index 0000000..85a5c1a
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __WRITE_H__
+#define __WRITE_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+uint64_t encodeFloat(float value, float offset, float factor, uint8_t bitPosition,
+        uint8_t bitSize);
+
+uint64_t encodeBoolean(bool value, float offset, float factor,
+                uint8_t bitPosition, uint8_t bitSize);
+
+#endif // __WRITE_H__
diff --git a/tests/bitfield_tests.c b/tests/bitfield_tests.c
new file mode 100644 (file)
index 0000000..d248989
--- /dev/null
@@ -0,0 +1,225 @@
+#include <check.h>
+#include <stdint.h>
+#include <bitfield/bitfield.h>
+
+START_TEST (test_large_bitmask)
+{
+    // yeah, this isn't a public method but I wanted to unit test it to track
+    // down a bug
+    extern uint64_t bitmask(int numBits);
+    uint64_t result = bitmask(32);
+    fail_if(result != 0xffffffff);
+}
+END_TEST
+
+START_TEST (test_one_bit_not_swapped)
+{
+    uint64_t data = 0x80;
+    uint64_t result = getBitField(data, 0, 1, false);
+    fail_if(result == 1);
+}
+END_TEST
+
+START_TEST (test_one_bit)
+{
+    uint64_t data = 0x8000000000000000;
+    uint64_t result = getBitField(data, 0, 1, false);
+    fail_unless(result == 0x1,
+            "First bits in 0x%X was 0x%X instead of 0x1", data, result);
+}
+END_TEST
+
+START_TEST (test_32_bit_parse)
+{
+    uint64_t data = 0x0402574d555a0401;
+    uint64_t result = getBitField(data, 16, 32, false);
+    uint64_t expectedValue = 0x574d555a;
+    fail_unless(result == expectedValue,
+            "Field retrieved in 0x%X was 0x%X instead of %d", data,
+            result, expectedValue);
+}
+END_TEST
+
+START_TEST (test_16_bit_parse)
+{
+    uint64_t data = 0xF34DFCFF00000000;
+    uint64_t result = getBitField(data, 16, 16, false);
+    uint64_t expectedValue = 0xFCFF;
+    fail_unless(result == expectedValue,
+            "Field retrieved in 0x%X was 0x%X instead of %d", data,
+            result, expectedValue);
+}
+END_TEST
+
+START_TEST (test_one_byte)
+{
+    uint64_t data = 0xFA00000000000000;
+    uint64_t result = getBitField(data, 0, 4, false);
+    fail_unless(result == 0xF,
+            "First 4 bits in 0x%X was 0x%X instead of 0xF", data, result);
+    result = getBitField(data, 4, 4, false);
+    fail_unless(result == 0xA,
+            "First 4 bits in 0x%X was 0x%X instead of 0xA", data, result);
+    result = getBitField(data, 0, 8, false);
+    fail_unless(result == 0xFA,
+            "All bits in 0x%X were 0x%X instead of 0x%X", data, result, data);
+}
+END_TEST
+
+START_TEST (test_multi_byte)
+{
+    uint64_t data = 0x12FA000000000000;
+    uint64_t result = getBitField(data, 0, 4, false);
+    fail_unless(result == 0x1,
+            "First 4 bits in 0x%X was 0x%X instead of 0xF", (data >> 60) & 0xF,
+            result);
+    result = getBitField(data, 4, 4, false);
+    fail_unless(result == 0x2,
+            "Second 4 bits in 0x%X was %d instead of 0xA", (data >> 56) & 0xF,
+            result);
+    result = getBitField(data, 8, 4, false);
+    fail_unless(result == 0xF,
+            "First 4 bits in 0x%X was %d instead of 0x1", (data >> 52) & 0xF,
+            result);
+    result = getBitField(data, 12, 4, false);
+    fail_unless(result == 0xA,
+            "Second 4 bits in 0x%X was %d instead of 0x2", (data >> 48) % 0xF,
+            result);
+}
+END_TEST
+
+START_TEST (test_get_multi_byte)
+{
+    uint64_t data = 0x12FA000000000000;
+    uint64_t result = getBitField(data, 0, 9, false);
+    ck_assert_int_eq(result, 0x25);
+}
+END_TEST
+
+START_TEST (test_get_off_byte_boundary)
+{
+    uint64_t data = 0x000012FA00000000;
+    uint64_t result = getBitField(data, 12, 8, false);
+    ck_assert_int_eq(result, 0x01);
+} END_TEST
+
+START_TEST (test_set_field)
+{
+    uint64_t data = 0;
+    setBitField(&data, 1, 0, 1);
+    uint64_t result = getBitField(data, 0, 1, false);
+    ck_assert_int_eq(result, 0x1);
+    data = 0;
+    setBitField(&data, 1, 1, 1);
+    result = getBitField(data, 1, 1, false);
+    ck_assert_int_eq(result, 0x1);
+
+    data = 0;
+    setBitField(&data, 0xf, 3, 4);
+    result = getBitField(data, 3, 4, false);
+    ck_assert_int_eq(result, 0xf);
+}
+END_TEST
+
+START_TEST (test_set_doesnt_clobber_existing_data)
+{
+    uint64_t data = 0xFFFC4DF300000000;
+    setBitField(&data, 0x4fc8, 16, 16);
+    uint64_t result = getBitField(data, 16, 16, false);
+    fail_unless(result == 0x4fc8,
+            "Field retrieved in 0x%X was 0x%X instead of 0x%X", data, result,
+            0xc84f);
+
+    data = 0x8000000000000000;
+    setBitField(&data, 1, 21, 1);
+    fail_unless(data == 0x8000040000000000LLU,
+            "Expected combined value 0x8000040000000000 but got 0x%X%X",
+            data >> 32, data);
+}
+END_TEST
+
+START_TEST (test_set_off_byte_boundary)
+{
+    uint64_t data = 0xFFFC4DF300000000;
+    setBitField(&data, 0x12, 12, 8);
+    uint64_t result = getBitField(data, 12, 12, false);
+    ck_assert_int_eq(result,0x12d);
+}
+END_TEST
+
+START_TEST (test_set_odd_number_of_bits)
+{
+    uint64_t data = 0xFFFC4DF300000000LLU;
+    setBitField(&data, 0x12, 11, 5);
+    uint64_t result = getBitField(data, 11, 5, false);
+    fail_unless(result == 0x12,
+            "Field set in 0x%X%X%X%X was %d instead of %d", data, result,
+            0x12);
+
+    data = 0xFFFC4DF300000000LLU;
+    setBitField(&data, 0x2, 11, 5);
+    result = getBitField(data, 11, 5, false);
+    fail_unless(result == 0x2,
+            "Field set in 0x%X%X%X%X was %d instead of %d", data, result,
+            0x2);
+}
+END_TEST
+
+START_TEST(test_nth_byte)
+{
+    uint64_t data = 0x00000000F34DFCFF;
+    uint8_t result = nthByte(data, 0);
+    uint8_t expected = 0x0;
+    ck_assert_int_eq(result, expected);
+
+    result = nthByte(data, 4);
+    expected = 0xF3;
+    ck_assert_int_eq(result, expected);
+
+    result = nthByte(data, 5);
+    expected = 0x4D;
+    ck_assert_int_eq(result, expected);
+
+    result = nthByte(data, 6);
+    expected = 0xFC;
+    ck_assert_int_eq(result, expected);
+
+    result = nthByte(data, 7);
+    expected = 0xFF;
+    ck_assert_int_eq(result, expected);
+}
+END_TEST
+
+Suite* bitfieldSuite(void) {
+    Suite* s = suite_create("bitfield");
+    TCase *tc_core = tcase_create("core");
+    tcase_add_test(tc_core, test_large_bitmask);
+    tcase_add_test(tc_core, test_one_bit);
+    tcase_add_test(tc_core, test_one_bit_not_swapped);
+    tcase_add_test(tc_core, test_one_byte);
+    tcase_add_test(tc_core, test_16_bit_parse);
+    tcase_add_test(tc_core, test_32_bit_parse);
+    tcase_add_test(tc_core, test_multi_byte);
+    tcase_add_test(tc_core, test_get_multi_byte);
+    tcase_add_test(tc_core, test_get_off_byte_boundary);
+    tcase_add_test(tc_core, test_set_field);
+    tcase_add_test(tc_core, test_set_doesnt_clobber_existing_data);
+    tcase_add_test(tc_core, test_set_off_byte_boundary);
+    tcase_add_test(tc_core, test_set_odd_number_of_bits);
+    tcase_add_test(tc_core, test_nth_byte);
+    suite_add_tcase(s, tc_core);
+
+    return s;
+}
+
+int main(void) {
+    int numberFailed;
+    Suite* s = bitfieldSuite();
+    SRunner *sr = srunner_create(s);
+    // Don't fork so we can actually use gdb
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr, CK_NORMAL);
+    numberFailed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (numberFailed == 0) ? 0 : 1;
+}
diff --git a/tests/tests.bin b/tests/tests.bin
new file mode 100755 (executable)
index 0000000..5fd0916
Binary files /dev/null and b/tests/tests.bin differ