diff options
Diffstat (limited to 'CAN-binder/libs')
43 files changed, 3140 insertions, 0 deletions
diff --git a/CAN-binder/libs/CMakeLists.txt b/CAN-binder/libs/CMakeLists.txt new file mode 100644 index 00000000..38ccd2d1 --- /dev/null +++ b/CAN-binder/libs/CMakeLists.txt @@ -0,0 +1,29 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + +# Add target to project dependency list +fill_include_dir(${CMAKE_CURRENT_SOURCE_DIR}) +file(GLOB filelist "${CMAKE_CURRENT_SOURCE_DIR}/*") +foreach(filename ${filelist}) + if(IS_DIRECTORY ${filename}) + STRING(REGEX REPLACE "^.*\/(.*)$" "\\1" shortname ${filename}) + find_source_files(${filename}) + PROJECT_TARGET_ADD(${shortname}) + add_library(${shortname} STATIC ${sources_files}) + endif(IS_DIRECTORY ${filename}) +endforeach() diff --git a/CAN-binder/libs/bitfield-c/.gitignore b/CAN-binder/libs/bitfield-c/.gitignore new file mode 100644 index 00000000..834a305d --- /dev/null +++ b/CAN-binder/libs/bitfield-c/.gitignore @@ -0,0 +1,5 @@ +*.o +.DS_Store +*~ +*.bin +build diff --git a/CAN-binder/libs/bitfield-c/.travis.yml b/CAN-binder/libs/bitfield-c/.travis.yml new file mode 100644 index 00000000..7654abaa --- /dev/null +++ b/CAN-binder/libs/bitfield-c/.travis.yml @@ -0,0 +1,7 @@ +language: c +compiler: + - gcc +script: make test +before_install: + - sudo apt-get update -qq + - sudo apt-get install check diff --git a/CAN-binder/libs/bitfield-c/CHANGELOG.mkd b/CAN-binder/libs/bitfield-c/CHANGELOG.mkd new file mode 100644 index 00000000..eea48c06 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/CHANGELOG.mkd @@ -0,0 +1,5 @@ +# Bitfield Utilities in C + +## v0.1 + +* Initial release diff --git a/CAN-binder/libs/bitfield-c/LICENSE b/CAN-binder/libs/bitfield-c/LICENSE new file mode 100644 index 00000000..330d61f4 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/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/CAN-binder/libs/bitfield-c/Makefile b/CAN-binder/libs/bitfield-c/Makefile new file mode 100644 index 00000000..e93efb47 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/Makefile @@ -0,0 +1,53 @@ +CC = gcc +INCLUDES = -Isrc +CFLAGS = $(INCLUDES) -c -Wall -Werror -g -ggdb -coverage +LDFLAGS = -coverage -lm +LDLIBS = -lcheck + +TEST_DIR = tests +TEST_OBJDIR = build + +# 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) +OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(SRC:.c=.o)) +TEST_SRC = $(wildcard $(TEST_DIR)/*_tests.c) +TESTS=$(patsubst %.c,$(TEST_OBJDIR)/%.bin,$(TEST_SRC)) + +all: $(OBJS) + +test: $(TESTS) + @set -o $(TEST_SET_OPTS) >/dev/null 2>&1 + @export SHELLOPTS + @sh runtests.sh $(TEST_OBJDIR)/$(TEST_DIR) + +COVERAGE_INFO_FILENAME = coverage.info +COVERAGE_INFO_PATH = $(TEST_OBJDIR)/$(COVERAGE_INFO_FILENAME) +coverage: + @lcov --base-directory . --directory $(TEST_OBJDIR) --zerocounters -q + @make clean + @make test + @lcov --base-directory . --directory $(TEST_OBJDIR) -c -o $(TEST_OBJDIR)/coverage.info + @lcov --remove $(COVERAGE_INFO_PATH) "/usr/*" -o $(COVERAGE_INFO_PATH) + @genhtml -o $(TEST_OBJDIR)/coverage -t "isotp-c test coverage" --num-spaces 4 $(COVERAGE_INFO_PATH) + @$(BROWSER) $(TEST_OBJDIR)/coverage/index.html + @echo "$(GREEN)Coverage information generated in $(TEST_OBJDIR)/coverage/index.html.$(COLOR_RESET)" + +$(TEST_OBJDIR)/%.o: %.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $< + +$(TEST_OBJDIR)/%.bin: $(TEST_OBJDIR)/%.o $(OBJS) $(TEST_SUPPORT_OBJS) + @mkdir -p $(dir $@) + $(CC) $(LDFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $^ $(LDLIBS) + +clean: + rm -rf $(TEST_OBJDIR) diff --git a/CAN-binder/libs/bitfield-c/README.mkd b/CAN-binder/libs/bitfield-c/README.mkd new file mode 100644 index 00000000..439b3dd0 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/README.mkd @@ -0,0 +1,107 @@ +Bitfield Utilities in C +=========================== + +This is a C library with functions to help encode and decode Controller Area +Network (CAN) message payloads or other bitfields. + +The header files contain complete function documentation, but to get you +started, here are examples using the API: + +## Bitfield Manipulation + +The bitfields are stored in `uint8_t[]`. + + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result = get_byte(data, sizeof(data), 0); + // result = 0x12; + result = get_nibble(data, sizeof(data), 0); + // result = 0x1; + bool success = copy_bits_right_aligned(data, 4, 4, 12, result, 4) + // success == true + // result[0] == 0x2 + // result[1] == 0x34 + +## 8 Byte Helpers + +If you are dealing with 8 byte CAN messages as `uint64_t`, there are some +additional functions prefixed with `eightbyte_` that may be faster or more +useful. + +### 8 Byte Decoding + + uint64_t data = 0x8000000000000000; + uint64_t result = eightbyte_get_bitfield(data, 0, 1, false); + // result == 0x1 + + data = 0x0402574d555a0401; + result = eightbyte_get_bitfield(data, 16, 32, false); + // result = 0x574d555a; + + data = 0x00000000F34DFCFF; + result = eightbyte_get_byte(data, 0, false); + //result = 0x0 + + result = eightbyte_get_byte(data, 4, false); + //result = 0xF3 + + result = eightbyte_get_nibble(data, 10, false); + //result = 0x4; + +### 8 Byte Encoding + + uint64_t data = 0; + fail_unless(8byte_set_bitfield(1, 0, 1, &data)); + uint64_t result = eightbyte_get_bitfield(data, 0, 1, false); + ck_assert_int_eq(result, 0x1); + +### CAN Signal Encoding + +The library supports encoding floating point CAN signals as well as booleans +into a uint64_t payload. + + uint64_t payload = eightbyte_encode_float(1, 1, 3, 1, 0) + // payload == 0x1000000000000000 + + payload = eightbyte_encode_bool(true, 1, 3); + // payload == 0x1000000000000000 + +### CAN Signal Decoding + +The library supports parsing floating point CAN signals as well as booleans. + + uint64_t payload = 0xeb00000000000000; + float float_result = eightbyte_parse_float(payload, + 2, // starting bit + 4, // width of the signal's field + 1001.0, // transformation factor for the signal value + -30000.0); // transformation offset for the signal value + // float_result == -19990.0 + + bool bool_result = eightbyte_parse_bool(payload, + 0, // starting bit + 1, // width of the signal's field + 1.0, // transformation factor for the signal value + 0); // transformation offset for the signal value + // bool_result == true + +## Testing + +The library includes a test suite that uses the `check` C unit test library. It +requires the unit testing library `check`. + + $ make test + +You can also see the test coverage if you have `lcov` installed and the +`BROWSER` environment variable set to your choice of web browsers: + + $ BROWSER=google-chrome-stable make coverage + +## Authors + +Chris Peplin cpeplin@ford.com + +## License + +Copyright (c) 2013 Ford Motor Company + +Licensed under the BSD license. diff --git a/CAN-binder/libs/bitfield-c/runtests.sh b/CAN-binder/libs/bitfield-c/runtests.sh new file mode 100644 index 00000000..4781636b --- /dev/null +++ b/CAN-binder/libs/bitfield-c/runtests.sh @@ -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/CAN-binder/libs/bitfield-c/src/bitfield/8byte.c b/CAN-binder/libs/bitfield-c/src/bitfield/8byte.c new file mode 100644 index 00000000..9325ed1b --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/bitfield/8byte.c @@ -0,0 +1,61 @@ +#include <bitfield/bitfield.h> +#include <bitfield/8byte.h> +#include <stddef.h> +#include <limits.h> +#include <string.h> + +#define EIGHTBYTE_BIT (8 * sizeof(uint64_t)) + +uint8_t eightbyte_get_nibble(const uint64_t source, const uint8_t nibble_index, + const bool data_is_big_endian) { + return (uint8_t) eightbyte_get_bitfield(source, NIBBLE_SIZE * nibble_index, + NIBBLE_SIZE, data_is_big_endian); +} + +uint8_t eightbyte_get_byte(uint64_t source, const uint8_t byte_index, + const bool data_is_big_endian) { + if(data_is_big_endian) { + source = __builtin_bswap64(source); + } + return (source >> (EIGHTBYTE_BIT - ((byte_index + 1) * CHAR_BIT))) & 0xFF; +} + +// TODO is this funciton necessary anymore? is it any faster for uint64_t than +// get_bitfield(data[], ...)? is the performance better on a 32 bit platform +// like the PIC32? +uint64_t eightbyte_get_bitfield(uint64_t source, const uint16_t offset, + const uint16_t bit_count, const bool data_is_big_endian) { + int startByte = offset / CHAR_BIT; + int endByte = (offset + bit_count - 1) / CHAR_BIT; + + if(!data_is_big_endian) { + source = __builtin_bswap64(source); + } + + uint8_t* bytes = (uint8_t*)&source; + uint64_t ret = bytes[startByte]; + if(startByte != endByte) { + // The lowest byte address contains the most significant bit. + uint8_t i; + for(i = startByte + 1; i <= endByte; i++) { + ret = ret << 8; + ret = ret | bytes[i]; + } + } + + ret >>= 8 - find_end_bit(offset + bit_count); + return ret & bitmask(bit_count); +} + +bool eightbyte_set_bitfield(uint64_t value, const uint16_t offset, + const uint16_t bit_count, uint64_t* destination) { + if(value > bitmask(bit_count)) { + return false; + } + + int shiftDistance = EIGHTBYTE_BIT - offset - bit_count; + value <<= shiftDistance; + *destination &= ~(bitmask(bit_count) << shiftDistance); + *destination |= value; + return true; +} diff --git a/CAN-binder/libs/bitfield-c/src/bitfield/8byte.h b/CAN-binder/libs/bitfield-c/src/bitfield/8byte.h new file mode 100644 index 00000000..04512690 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/bitfield/8byte.h @@ -0,0 +1,88 @@ +#ifndef __8BYTE_H__ +#define __8BYTE_H__ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Reads a subset of bits into a uint64_t. + * + * source - the bytes in question. + * offset - the starting index of the bit field (beginning from 0). + * bit_count - the width of the bit field to extract. + * data_is_big_endian - 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. + * + * 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 = get_bitfield(data, 2, 4); + * + * Returns the value of the requested bit field, right aligned in a uint64_t. + */ +uint64_t eightbyte_get_bitfield(uint64_t source, const uint16_t offset, + const uint16_t bit_count, const bool data_is_big_endian); + +/* Public: Return a single nibble from the payload, with range checking. + * + * source - the source payload. + * nibble_index - the index of the nibble to retreive. The leftmost nibble is + * index 0. + * data_is_big_endian - if the data passed in is little endian, set this to false and it + * will be flipped before grabbing the bit field. + * + * Returns the retreived nibble, right aligned in a uint8_t. + */ +uint8_t eightbyte_get_nibble(const uint64_t source, const uint8_t nibble_index, + const bool data_is_big_endian); + +/* Public: Return a single byte from the payload, with range checking. + * + * source - the source byte array. + * byte_index - the index of the byte to retreive. The leftmost byte is index 0. + * data_is_big_endian - if the data passed in is little endian, set this to false and it + * will be flipped before grabbing the bit field. + * + * Returns the retreived byte. + */ +uint8_t eightbyte_get_byte(const uint64_t source, const uint8_t byte_index, + const bool data_is_big_endian); + +/* Public: Set the bit field in the given data array to the new value. + * + * destination - a byte array with size at least offset + bit_count. + * value - the value to set in the bit field. + * offset - the starting index of the bit field (beginning from 0). + * bit_count - the number of bits to set in the data. + * + * Returns true if the bit_count is enough to fully represent the value, and + * false if it will not fit. + */ +bool eightbyte_set_bitfield(uint64_t value, + const uint16_t offset, const uint16_t bit_count, uint64_t* destination); + +/* Private: Determine the index of the last bit used. + */ +uint8_t find_end_bit(const uint16_t num_bits); + +#ifdef __cplusplus +} +#endif + +#endif // __8BYTE_H__ diff --git a/CAN-binder/libs/bitfield-c/src/bitfield/bitarray.c b/CAN-binder/libs/bitfield-c/src/bitfield/bitarray.c new file mode 100644 index 00000000..dcb9a08f --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/bitfield/bitarray.c @@ -0,0 +1,145 @@ +#include <bitfield/bitfield.h> +#include <stddef.h> +#include <limits.h> +#include <string.h> + +#define PREPARE_FIRST_COPY() \ + do { \ + if (bit_count >= (CHAR_BIT - destination_offset_modulo)) { \ + *destination &= reverse_mask[destination_offset_modulo]; \ + bit_count -= CHAR_BIT - destination_offset_modulo; \ + } else { \ + *destination &= reverse_mask[destination_offset_modulo] \ + | reverse_mask_xor[destination_offset_modulo + bit_count + 1];\ + c &= reverse_mask[destination_offset_modulo + bit_count ];\ + bit_count = 0; \ + } } while (0) + +static const uint8_t reverse_mask[] = + { 0x55, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; +static const uint8_t reverse_mask_xor[] = + { 0xff, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01, 0x00 }; + +bool copy_bits(const uint8_t* source_origin, const uint16_t source_length, + const uint16_t source_offset, uint16_t bit_count, + uint8_t* destination_origin, const uint16_t destination_length, + const uint16_t destination_offset) { + if(bit_count < 1) { + return false; + } + + if(source_offset + bit_count > source_length * CHAR_BIT || + destination_offset + bit_count > destination_length * CHAR_BIT ) { + return false; + } + + const uint8_t* source = source_origin + (source_offset / CHAR_BIT); + uint8_t* destination = destination_origin + (destination_offset / CHAR_BIT); + int source_offset_modulo = source_offset % CHAR_BIT; + int destination_offset_modulo = destination_offset % CHAR_BIT; + + if(source_offset_modulo == destination_offset_modulo) { + if(source_offset_modulo > 0) { + uint8_t c = reverse_mask_xor[destination_offset_modulo] & *source++; + PREPARE_FIRST_COPY(); + *destination++ |= c; + } + + int byte_len = bit_count / CHAR_BIT; + int bit_count_modulo = bit_count % CHAR_BIT; + + if(byte_len > 0) { + memcpy(destination, source, byte_len); + source += byte_len; + destination += byte_len; + } + + if(bit_count_modulo > 0) { + *destination &= reverse_mask_xor[bit_count_modulo]; + *destination |= reverse_mask[bit_count_modulo] & *source; + } + } else { + int bit_diff_left_shift; + int bit_diff_right_shift; + uint8_t c; + /* + * Begin: Line things up on destination. + */ + if(source_offset_modulo > destination_offset_modulo) { + bit_diff_left_shift = source_offset_modulo - destination_offset_modulo; + bit_diff_right_shift = CHAR_BIT - bit_diff_left_shift; + + c = *source++ << bit_diff_left_shift; + c |= *source >> bit_diff_right_shift; + c &= reverse_mask_xor[destination_offset_modulo]; + } else { + bit_diff_right_shift = destination_offset_modulo - source_offset_modulo; + bit_diff_left_shift = CHAR_BIT - bit_diff_right_shift; + + c = *source >> bit_diff_right_shift & + reverse_mask_xor[destination_offset_modulo]; + } + PREPARE_FIRST_COPY(); + *destination++ |= c; + + /* + * Middle: copy with only shifting the source. + */ + int byte_len = bit_count / CHAR_BIT; + while(--byte_len >= 0) { + c = *source++ << bit_diff_left_shift; + c |= *source >> bit_diff_right_shift; + *destination++ = c; + } + + /* + * End: copy the remaing bits; + */ + int bit_count_modulo = bit_count % CHAR_BIT; + if(bit_count_modulo > 0) { + c = *source++ << bit_diff_left_shift; + c |= *source >> bit_diff_right_shift; + c &= reverse_mask[bit_count_modulo]; + + *destination &= reverse_mask_xor[bit_count_modulo]; + *destination |= c; + } + } + return true; +} + +uint16_t bits_to_bytes(uint32_t bits) { + uint8_t byte_count = bits / CHAR_BIT; + if(bits % CHAR_BIT != 0) { + ++byte_count; + } + return byte_count; +} + +/** + * Find the ending bit of a bitfield within the final byte. + * + * Returns: a bit position from 0 to 7. + */ +uint8_t find_end_bit(const uint16_t numBits) { + int endBit = numBits % CHAR_BIT; + return endBit == 0 ? CHAR_BIT : endBit; +} + +bool copy_bits_right_aligned(const uint8_t source[], const uint16_t source_length, + const uint16_t offset, const uint16_t bit_count, + uint8_t* destination, const uint16_t destination_length) { + return copy_bits(source, source_length, offset, bit_count, destination, + destination_length, + // provide a proper destination offset so the result is right + // aligned + (destination_length - bits_to_bytes(bit_count)) * CHAR_BIT + + CHAR_BIT - find_end_bit(bit_count)); +} + +bool copy_bytes_right_aligned(const uint8_t source[], const uint16_t source_length, + const uint16_t offset, const uint16_t byte_count, + uint8_t* destination, const uint16_t destination_length) { + return copy_bits_right_aligned(source, source_length, offset * CHAR_BIT, + byte_count * CHAR_BIT, destination, destination_length); +} diff --git a/CAN-binder/libs/bitfield-c/src/bitfield/bitfield.c b/CAN-binder/libs/bitfield-c/src/bitfield/bitfield.c new file mode 100644 index 00000000..795f0208 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/bitfield/bitfield.c @@ -0,0 +1,74 @@ +#include <bitfield/bitfield.h> +#include <limits.h> +#include <string.h> +#include <stddef.h> +#include <sys/param.h> + +uint64_t bitmask(const uint8_t bit_count) { + return (((uint64_t)0x1) << bit_count) - 1; +} + +uint8_t get_nibble(const uint8_t source[], const uint8_t source_length, + const uint8_t nibble_index) { + uint8_t byte_index = nibble_index / 2; + uint8_t result = get_byte(source, source_length, byte_index); + if(nibble_index % 2 == 0) { + result >>= NIBBLE_SIZE; + } + result &= bitmask(NIBBLE_SIZE); + return result; +} + +uint8_t get_byte(const uint8_t source[], const uint8_t source_length, + const uint8_t byte_index) { + if(byte_index < source_length) { + return source[byte_index]; + } + return 0; +} + +uint64_t get_bitfield(const uint8_t source[], const uint8_t source_length, + const uint16_t offset, const uint16_t bit_count) { + if(bit_count > 64 || bit_count < 1) { + // TODO error reporting? + return 0; + } + + ArrayOrBytes combined; + memset(combined.bytes, 0, sizeof(combined.bytes)); + if(copy_bits_right_aligned(source, source_length, offset, bit_count, + combined.bytes, sizeof(combined.bytes))) { + if(BYTE_ORDER == LITTLE_ENDIAN) { + combined.whole = __builtin_bswap64(combined.whole); + } + } else { + // debug("couldn't copy enough bits from source") + } + return combined.whole; +} + +bool set_nibble(const uint16_t nibble_index, const uint8_t value, + uint8_t* destination, const uint16_t destination_length) { + return copy_bits(&value, CHAR_BIT, NIBBLE_SIZE, NIBBLE_SIZE, destination, + destination_length, nibble_index * NIBBLE_SIZE); +} + +bool set_bitfield(const uint64_t value, const uint16_t offset, + const uint16_t bit_count, uint8_t destination[], + uint16_t destination_length) { + if(value > bitmask(bit_count)) { + return false; + } + + ArrayOrBytes combined = { + whole: value + }; + + if(BYTE_ORDER == LITTLE_ENDIAN) { + combined.whole = __builtin_bswap64(combined.whole); + } + + return copy_bits(combined.bytes, sizeof(combined.bytes), + sizeof(combined.bytes) * CHAR_BIT - bit_count, bit_count, + destination, destination_length, offset); +} diff --git a/CAN-binder/libs/bitfield-c/src/bitfield/bitfield.h b/CAN-binder/libs/bitfield-c/src/bitfield/bitfield.h new file mode 100644 index 00000000..df92639a --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/bitfield/bitfield.h @@ -0,0 +1,220 @@ +#ifndef __BITFIELD_H__ +#define __BITFIELD_H__ + +#include <stdint.h> +#include <stdbool.h> + +#define NIBBLE_SIZE (CHAR_BIT / 2) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Reads a subset of bits into a uint64_t, right aligned so they may be + * interpreted as a number. + * + * source - the bytes in question. + * source_size - the number of bytes in the source. + * offset - the starting index of the bit field (beginning from 0). + * bit_count - the width of the bit field to extract. This must be less than or + * equal to 64. + * + * Bit fields are positioned according to big-endian bit layout and the data is + * swapped automatically as necessary depending on the compiled architecture. + * + * 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 + * + * Examples + * + * uint64_t value = get_bitfield(data, data_size, 2, 4); + * + * Returns the value of the requested bit field, right aligned in a uint64_t. + */ +uint64_t get_bitfield(const uint8_t source[], const uint8_t source_length, + const uint16_t offset, const uint16_t bit_count); + +/* Public: Return a single nibble from the byte array, with range checking. + * + * source - the source byte array. + * source_length - the total length of the source array. + * nibble_index - the index of the nibble to retreive. The leftmost nibble is + * index 0. + * + * Returns the retreived nibble, right aligned in a uint8_t. + */ +uint8_t get_nibble(const uint8_t source[], const uint8_t source_length, + const uint8_t nibble_index); + +/* Public: Return a single byte from the byte array, with range checking. + * + * source - the source byte array. + * source_length - the total length of the source array. + * byte_index - the index of the byte to retreive. The leftmost byte is index 0. + * + * Returns the retreived byte. + */ +uint8_t get_byte(const uint8_t source[], const uint8_t source_length, + const uint8_t byte_index); + +/* Public: Copy a range of bits from one bit array to another. + * + * The range does not need to be byte aligned, and the source and destination do + * not have to be the same size (as long as the desitnation has enough room to + * fit the range). + * + * A bit array with regards to this function always has the leftmost bit in byte + * 0, i.e. bit index is the leftmost bit of byte 0. Endianness does not matter. + * + * For example: + * + * uint8_t source[4] = {0x11, 0x22, 0x33, 0x44}; + * uint8_t destination[4] = {0}; + * copy_bits(source, sizeof(source), 8, 8, destination, + * sizeof(destination), 0); + * // destination[0] == 0x22 + * // destination[1] == 0x0 + * // destination[2] == 0x0 + * // destination[3] == 0x0 + * + * Thanks to + * http://stackoverflow.com/questions/3534535/whats-a-time-efficient-algorithm-to-copy-unaligned-bit-arrays + * for the implementation of the algorithm. + * + * source_origin - the source array. + * source_length - the total length of the source array in bytes, + * for range checking. + * source_offset - an offset in bits to start the copy from the source array. + * Specify 0 to start from source_origin. + * bit_count - the number of bits to copy. + * destination_origin - the destination array. + * desitnation_length - the total length of the destination array in bytes, + * for range checking. + * destination_offset - an offset in bits to start placing the copied range into + * the destination array. Specify 0 to start from the beginning of the + * destination. If you are copying a range not aligned on a byte, you + * probably want to set this to a positive offset to right the resulting + * bits in the destination. + * + * Returns true if the copy was successful and false if the range exceeded the + * size of the source or destination, or if the range size negative or 0. + */ +bool copy_bits(const uint8_t* source_origin, const uint16_t source_length, + const uint16_t source_offset, uint16_t bit_count, + uint8_t* destination_origin, const uint16_t destination_length, + const uint16_t destination_offset); + +/* Public: Copy a range of bits from one array to another, right aligning the + * result. + * + * This is mostly useful if you want to cast the result to an integer type + * instead of a byte array. + * + * For example: + * + * uint8_t source[4] = {0x11, 0x22, 0x33, 0x44}; + * uint8_t destination[4] = {0}; + * copy_bits_right_aligned(source, sizeof(source), 8, 8, destination, + * sizeof(destination)); + * // destination[0] == 0x0 + * // destination[1] == 0x0 + * // destination[2] == 0x0 + * // destination[3] == 0x22 + * + * int value = (int)destination; + * // value == 0x22 == 32 + * + * The arguments are the same as copy_bits, but without the destination_offset + * option - that's set automatically to right align the result. + * + * Returns true if the copy was successful and false if the range exceeded the + * size of the source or destination, or if the range size negative or 0. + */ +bool copy_bits_right_aligned(const uint8_t source[], const uint16_t source_length, + const uint16_t offset, const uint16_t bit_count, + uint8_t* destination, const uint16_t destination_length); + +/* Public: Copy a range of bytes from one byte array to another. + * + * The source and destination do not have to be the same size (as long as the + * desitnation has enough room to fit the range). + * + * source_origin - the source array. + * source_length - the total length of the source array in bytes, + * for range checking. + * source_offset - a byte offset to start the copy from the source array. + * Specify 0 to start from source_origin. + * byte_count - the number of bytes to copy. + * destination_origin - the destination array. + * desitnation_length - the total length of the destination array in bytes, + * for range checking. + * destination_offset - an offset in bytes to start placing the copied range into + * the destination array. Specify 0 to start from the beginning of the + * destination. + * + * Returns true if the copy was successful and false if the range exceeded the + * size of the source or destination, or if the range size negative or 0. + */ +bool copy_bytes_right_aligned(const uint8_t source[], const uint16_t source_length, + const uint16_t offset, const uint16_t byte_count, + uint8_t* destination, const uint16_t destination_length); + +/* Public: Set the a nibble in the given data array to the new value. + * + * nibble_index - the index of the nibble to retreive. The leftmost nibble is + * index 0. + * value - the value to set in the bit field. + * destination - the destination array. + * destination_length - the total length of the destination array in bytes, + * for range checking. + * + * Returns true if the bit_count is enough to fully represent the value, and + * false if it will not fit. + */ +bool set_nibble(const uint16_t nibble_index, const uint8_t value, + uint8_t* destination, const uint16_t destination_length); + +/* Public: Set the bit field in the given data array to the new value. + * + * value - the value to set in the bit field. + * offset - the starting index of the bit field (beginning from 0). + * bit_count - the number of bits to set in the data. + * destination - the destination array. + * destination_length - the total length of the destination array in bytes, + * for range checking. + * + * Returns true if the bit_count is enough to fully represent the value, and + * false if it will not fit. + */ +bool set_bitfield(const uint64_t value, const uint16_t offset, + const uint16_t bit_count, uint8_t destination[], + uint16_t destination_length); + +/* Public: Return a right aligned bitmask for a uint64_t. + * + * bit_count - the number of bits to mask, right aligned. + */ +uint64_t bitmask(const uint8_t bit_count); + +/* Private: + */ +uint16_t bits_to_bytes(uint32_t bits); + +/* Private: A union to assist swapping between uint64_t and a uint8_t array. + */ +typedef union { + uint64_t whole; + uint8_t bytes[sizeof(uint64_t)]; +} ArrayOrBytes; + +#ifdef __cplusplus +} +#endif + +#endif // __BITFIELD_H__ diff --git a/CAN-binder/libs/bitfield-c/src/canutil/read.c b/CAN-binder/libs/bitfield-c/src/canutil/read.c new file mode 100644 index 00000000..d0cbb71a --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/canutil/read.c @@ -0,0 +1,34 @@ +#include <canutil/read.h> +#include <bitfield/bitfield.h> +#include <bitfield/8byte.h> + +static float decode_float(uint64_t raw, float factor, float offset) { + return raw * factor + offset; +} + +float eightbyte_parse_float(uint64_t data, uint8_t bit_offset, uint8_t bit_size, + float factor, float offset) { + return decode_float(eightbyte_get_bitfield(data, bit_offset, bit_size, + true), factor, offset); +} + +bool eightbyte_parse_bool(uint64_t data, uint8_t bit_offset, uint8_t bit_size, + float factor, float offset) { + float value = eightbyte_parse_float(data, bit_offset, bit_size, factor, offset); + return value == 0.0 ? false : true; +} + +float bitfield_parse_float(const uint8_t source[], const uint16_t source_length, + const uint8_t bit_offset, const uint8_t bit_size, const float factor, + const float offset) { + return decode_float(get_bitfield(source, source_length, bit_offset, bit_size), + factor, offset); +} + +bool bitfield_parse_bool(const uint8_t source[], const uint16_t source_length, + const uint8_t bit_offset, const uint8_t bit_size, const float factor, + const float offset) { + float value = bitfield_parse_float(source, source_length, bit_offset, + bit_size, factor, offset); + return value == 0.0 ? false : true; +} diff --git a/CAN-binder/libs/bitfield-c/src/canutil/read.h b/CAN-binder/libs/bitfield-c/src/canutil/read.h new file mode 100644 index 00000000..86fea785 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/canutil/read.h @@ -0,0 +1,81 @@ +#ifndef __READ_H__ +#define __READ_H__ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Parse a CAN signal from a message and apply required transformation. + * + * source - the payload containing the signal. + * bit_offset - the starting bit for the signal. + * bit_size - the width of the signal. + * factor - the transformation factor for the signal value, applied after + * pulling out the bit field. Use 1.0 for no factor. + * offset - the transformation offset for the signal value, applied after + * pulling out the bit field. Use 0 for no offset. + * + * Returns the decoded and transformed value of the signal. + */ +float eightbyte_parse_float(const uint64_t source, const uint8_t bit_offset, + const uint8_t bit_size, const float factor, const float offset); + +/* Public: Parse a CAN signal from a message storage as a byte array and apply + * required transformation. + * + * source - the payload containing the signal. + * source_size - the size of the payload in bytes. + * bit_offset - the starting bit for the signal. + * bit_size - the width of the signal. + * factor - the transformation factor for the signal value, applied after + * pulling out the bit field. Use 1.0 for no factor. + * offset - the transformation offset for the signal value, applied after + * pulling out the bit field. Use 0 for no offset. + * + * Returns the decoded and transformed value of the signal. + */ +float bitfield_parse_float(const uint8_t source[], const uint16_t source_size, + const uint8_t bit_offset, const uint8_t bit_size, const float factor, + const float offset); + +/* Public: Parse a CAN signal from a message and interpret it as a boolean. + * + * source - the payload containing the signal. + * bit_offset - the starting bit for the signal. + * bit_size - the width of the signal. + * factor - the transformation factor for the signal value, applied after + * pulling out the bit field. Use 1.0 for no factor. + * offset - the transformation offset for the signal value, applied after + * pulling out the bit field. Use 0 for no offset. + * + * Returns false if the value was 0, otherwise true. + */ +bool eightbyte_parse_bool(uint64_t source, uint8_t bit_offset, uint8_t bit_size, + float factor, float offset); + +/* Public: Parse a CAN signal from a message storage as a byte array and + * interpret it as a boolean. + * + * source - the payload containing the signal. + * source_size - the size of the payload in bytes. + * bit_offset - the starting bit for the signal. + * bit_size - the width of the signal. + * factor - the transformation factor for the signal value, applied after + * pulling out the bit field. Use 1.0 for no factor. + * offset - the transformation offset for the signal value, applied after + * pulling out the bit field. Use 0 for no offset. + * + * Returns false if the value was 0, otherwise true. + */ +bool bitfield_parse_bool(const uint8_t source[], const uint16_t source_size, + const uint8_t bit_offset, const uint8_t bit_size, const float factor, + const float offset); + +#ifdef __cplusplus +} +#endif + +#endif // __READ_H__ diff --git a/CAN-binder/libs/bitfield-c/src/canutil/write.c b/CAN-binder/libs/bitfield-c/src/canutil/write.c new file mode 100644 index 00000000..7f3a3e04 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/canutil/write.c @@ -0,0 +1,48 @@ +#include <canutil/write.h> +#include <bitfield/bitfield.h> +#include <bitfield/8byte.h> + +uint64_t float_to_fixed_point(const float value, const float factor, + const float offset) { + float raw = (value - offset) / factor; + if(raw > 0) { + // round up to avoid losing precision when we cast to an int + // TODO do we need a way to encode an int back to a signal without any + // rounding? + raw += 0.5; + } + return (uint64_t)raw; +} + +uint64_t eightbyte_encode_float(float value, uint8_t bit_offset, uint8_t bit_size, + float factor, float offset) { + uint64_t result = 0; + if(!eightbyte_set_bitfield(float_to_fixed_point(value, factor, offset), + bit_offset, bit_size, &result)) { + // debug("%f will not fit in a %d bit field", value, bit_size); + } + return result; +} + +uint64_t eightbyte_encode_bool(const bool value, const uint8_t bit_offset, + const uint8_t bit_size) { + return eightbyte_encode_float(value, bit_offset, bit_size, 1.0, 0); +} + +bool bitfield_encode_float(const float value, const uint8_t bit_offset, + const uint8_t bit_size, const float factor, const float offset, + uint8_t destination[], const uint8_t destination_length) { + if(!set_bitfield(float_to_fixed_point(value, factor, offset), bit_offset, + bit_size, destination, destination_length)) { + // debug("%f will not fit in a %d bit field", value, bit_size); + return false; + } + return true; +} + +bool bitfield_encode_bool(const bool value, const uint8_t bit_offset, + const uint8_t bit_size, uint8_t destination[], + const uint16_t destination_length) { + return bitfield_encode_float(value, bit_offset, bit_size, 1.0, 0, + destination, destination_length); +} diff --git a/CAN-binder/libs/bitfield-c/src/canutil/write.h b/CAN-binder/libs/bitfield-c/src/canutil/write.h new file mode 100644 index 00000000..c2bef20e --- /dev/null +++ b/CAN-binder/libs/bitfield-c/src/canutil/write.h @@ -0,0 +1,57 @@ +#ifndef __WRITE_H__ +#define __WRITE_H__ + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Encode a floating point number into a fixed point, fixed bit width + * field in a bit array. + * + * value - the floating point value to encode. + * bit_offset - the starting point for the encoded bits in the returned value. + * bit_size - The max width of the field in the resulting bit array. If bit_size + * isn't big enough to store the fixed point version of the value, the + * bitfeld will *not* be set. TODO some error reporting would be nice. + * factor - a factor used to transform from floating to fixed point before + * encoding. Use 1.0 for no factor. + * offset - an offset used to transform from floating to fixed point before + * encoding. Use 0 for no offset. + * + * Returns a big-endian uint64_t with the value encoded as a bitfield. + */ +uint64_t eightbyte_encode_float(float value, uint8_t bit_offset, + uint8_t bit_size, float factor, float offset); + +uint64_t float_to_fixed_point(const float value, const float factor, + const float offset); + +bool bitfield_encode_float(const float value, const uint8_t bit_offset, + const uint8_t bit_size, const float factor, const float offset, + uint8_t destination[], const uint8_t destination_length); + +/* Public: Encode a boolean into fixed bit width field in a bit array. + * + * value - the boolean value to encode - true will be 1, false will be 0. + * bit_offset - the starting point for the encoded bits in the returned value. + * bit_size - The max width of the field in the resulting bit array. If bit_size + * isn't big enough to store the fixed point version of the value, the + * bitfeld will *not* be set. TODO some error reporting would be nice. + * + * Returns a big-endian uint64_t with the value encoded as a bitfield. + */ +uint64_t eightbyte_encode_bool(const bool value, const uint8_t bit_offset, + const uint8_t bit_size); + +bool bitfield_encode_bool(const bool value, const uint8_t bit_offset, const + uint8_t bit_size, uint8_t destination[], + const uint16_t destination_length); + +#ifdef __cplusplus +} +#endif + +#endif // __WRITE_H__ diff --git a/CAN-binder/libs/bitfield-c/tests/8byte_tests.c b/CAN-binder/libs/bitfield-c/tests/8byte_tests.c new file mode 100644 index 00000000..64554acc --- /dev/null +++ b/CAN-binder/libs/bitfield-c/tests/8byte_tests.c @@ -0,0 +1,261 @@ +#include <check.h> +#include <stdint.h> +#include <bitfield/bitfield.h> +#include <bitfield/8byte.h> + +START_TEST (test_large_bitmask) +{ + 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 = eightbyte_get_bitfield(data, 0, 1, false); + fail_if(result == 1); +} +END_TEST + +START_TEST (test_one_bit) +{ + uint64_t data = 0x8000000000000000; + uint64_t result = eightbyte_get_bitfield(data, 0, 1, false); + fail_unless(result == 0x1, + "First bit in 0x%llx was 0x%llx instead of 0x1", data, result); +} +END_TEST + +START_TEST (test_32_bit_parse) +{ + uint64_t data = 0x0402574d555a0401; + uint64_t result = eightbyte_get_bitfield(data, 16, 32, false); + uint64_t expectedValue = 0x574d555a; + fail_unless(result == expectedValue, + "Field retrieved in 0x%llx was 0x%llx instead of 0x%llx", data, + result, expectedValue); +} +END_TEST + +START_TEST (test_16_bit_parse) +{ + uint64_t data = 0xF34DFCFF00000000; + uint64_t result = eightbyte_get_bitfield(data, 16, 16, false); + uint64_t expectedValue = 0xFCFF; + fail_unless(result == expectedValue, + "Field retrieved in 0x%llx was 0x%llx instead of 0x%llx", data, + result, expectedValue); +} +END_TEST + +START_TEST (test_one_byte) +{ + uint64_t data = 0xFA00000000000000; + uint64_t result = eightbyte_get_bitfield(data, 0, 4, false); + fail_unless(result == 0xF, + "First nibble in 0x%llx was 0x%llx instead of 0xF", data, result); + result = eightbyte_get_bitfield(data, 4, 4, false); + fail_unless(result == 0xA, + "Second nibble in 0x%llx was 0x%llx instead of 0xA", data, result); + result = eightbyte_get_bitfield(data, 0, 8, false); + fail_unless(result == 0xFA, + "All bits in 0x%llx were 0x%llx instead of 0x%llx", data, result, data); +} +END_TEST + +START_TEST (test_multi_byte) +{ + uint64_t data = 0x12FA000000000000; + uint64_t result = eightbyte_get_bitfield(data, 0, 4, false); + fail_unless(result == 0x1, + "First 4 bits in 0x%llx was 0x%llx instead of 0xF", (data >> 60) & 0xF, + result); + result = eightbyte_get_bitfield(data, 4, 4, false); + fail_unless(result == 0x2, + "Second 4 bits in 0x%llx was 0x%llx instead of 0xA", (data >> 56) & 0xF, + result); + result = eightbyte_get_bitfield(data, 8, 4, false); + fail_unless(result == 0xF, + "First 4 bits in 0x%llx was 0x%llx instead of 0x1", (data >> 52) & 0xF, + result); + result = eightbyte_get_bitfield(data, 12, 4, false); + fail_unless(result == 0xA, + "Second 4 bits in 0x%llx was 0x%llx instead of 0x2", (data >> 48) % 0xF, + result); +} +END_TEST + +START_TEST (test_get_multi_byte) +{ + uint64_t data = 0x12FA000000000000; + uint64_t result = eightbyte_get_bitfield(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 = eightbyte_get_bitfield(data, 12, 8, false); + ck_assert_int_eq(result, 0x01); +} END_TEST + +START_TEST (test_set_wont_fit) +{ + uint64_t data = 0; + fail_if(eightbyte_set_bitfield(100, 0, 1, &data)); +} +END_TEST + +START_TEST (test_set_field) +{ + uint64_t data = 0; + fail_unless(eightbyte_set_bitfield(1, 0, 1, &data)); + uint64_t result = eightbyte_get_bitfield(data, 0, 1, false); + ck_assert_int_eq(result, 0x1); + data = 0; + fail_unless(eightbyte_set_bitfield(1, 1, 1, &data)); + result = eightbyte_get_bitfield(data, 1, 1, false); + ck_assert_int_eq(result, 0x1); + + data = 0; + fail_unless(eightbyte_set_bitfield(0xf, 3, 4, &data)); + result = eightbyte_get_bitfield(data, 3, 4, false); + ck_assert_int_eq(result, 0xf); +} +END_TEST + +START_TEST (test_set_doesnt_clobber_existing_data) +{ + uint64_t data = 0xFFFC4DF300000000; + fail_unless(eightbyte_set_bitfield(0x4fc8, 16, 16, &data)); + uint64_t result = eightbyte_get_bitfield(data, 16, 16, false); + fail_unless(result == 0x4fc8, + "Field retrieved in 0x%llx was 0x%llx instead of 0x%x", data, result, + 0xc84f); + + data = 0x8000000000000000; + fail_unless(eightbyte_set_bitfield(1, 21, 1, &data)); + fail_unless(data == 0x8000040000000000LLU, + "Expected combined value 0x8000040000000000 but got 0x%llx%llx", + data >> 32, data); +} +END_TEST + +START_TEST (test_set_off_byte_boundary) +{ + uint64_t data = 0xFFFC4DF300000000; + fail_unless(eightbyte_set_bitfield(0x12, 12, 8, &data)); + uint64_t result = eightbyte_get_bitfield(data, 12, 12, false); + ck_assert_int_eq(result,0x12d); +} +END_TEST + +START_TEST (test_set_odd_number_of_bits) +{ + uint64_t data = 0xFFFC4DF300000000LLU; + fail_unless(eightbyte_set_bitfield(0x12, 11, 5, &data)); + uint64_t result = eightbyte_get_bitfield(data, 11, 5, false); + fail_unless(result == 0x12, + "Field set in 0x%llx%llx%llx%llx was 0x%llx instead of 0x%llx", data, result, + 0x12); + + data = 0xFFFC4DF300000000LLU; + fail_unless(eightbyte_set_bitfield(0x2, 11, 5, &data)); + result = eightbyte_get_bitfield(data, 11, 5, false); + fail_unless(result == 0x2, + "Field set in 0x%llx%llx%llx%llx was 0x%llx instead of 0x%llx", data, result, + 0x2); +} +END_TEST + +START_TEST(test_eightbyte_get_byte) +{ + uint64_t data = 0x00000000F34DFCFF; + uint8_t result = eightbyte_get_byte(data, 0, false); + uint8_t expected = 0x0; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_byte(data, 4, false); + expected = 0xF3; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_byte(data, 5, false); + expected = 0x4D; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_byte(data, 6, false); + expected = 0xFC; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_byte(data, 7, false); + expected = 0xFF; + ck_assert_int_eq(result, expected); +} +END_TEST + +START_TEST(test_eightbyte_get_nibble) +{ + uint64_t data = 0x00000000F34DFCFF; + uint8_t result = eightbyte_get_nibble(data, 0, false); + uint8_t expected = 0x0; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_nibble(data, 2, false); + expected = 0x0; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_nibble(data, 8, false); + expected = 0xF; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_nibble(data, 9, false); + expected = 0x3; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_nibble(data, 10, false); + expected = 0x4; + ck_assert_int_eq(result, expected); + + result = eightbyte_get_nibble(data, 13, false); + expected = 0xC; + 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_wont_fit); + 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_eightbyte_get_nibble); + tcase_add_test(tc_core, test_eightbyte_get_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/CAN-binder/libs/bitfield-c/tests/bitfield_tests.c b/CAN-binder/libs/bitfield-c/tests/bitfield_tests.c new file mode 100644 index 00000000..b8c83b59 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/tests/bitfield_tests.c @@ -0,0 +1,132 @@ +#include <check.h> +#include <stdint.h> +#include <bitfield/bitfield.h> + +START_TEST (test_get_byte) +{ + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result = get_byte(data, sizeof(data), 0); + ck_assert_int_eq(result, 0x12); + result = get_byte(data, sizeof(data), 3); + ck_assert_int_eq(result, 0x78); +} +END_TEST + +START_TEST (test_set_nibble) +{ + uint8_t data[4] = {0}; + fail_unless(set_nibble(0, 0x1, data, sizeof(data))); + fail_unless(set_nibble(1, 0x2, data, sizeof(data))); + fail_unless(set_nibble(2, 0x3, data, sizeof(data))); + fail_unless(set_nibble(3, 0x4, data, sizeof(data))); + fail_unless(set_nibble(4, 0x5, data, sizeof(data))); + ck_assert_int_eq(data[0], 0x12); + ck_assert_int_eq(data[1], 0x34); + ck_assert_int_eq(data[2], 0x50); +} +END_TEST + +START_TEST (test_set_bitfield) +{ + uint8_t data[4] = {0}; + fail_unless(set_bitfield(0x12, 0, 8, data, sizeof(data))); + fail_unless(set_bitfield(bitmask(3), 10, 3, data, sizeof(data))); + ck_assert_int_eq(data[0], 0x12); + ck_assert_int_eq(data[1], 0x38); +} +END_TEST + +START_TEST (test_set_bitfield_doesnt_fit) +{ + uint8_t data[4] = {0}; + fail_if(set_bitfield(0xffff, 0, 8, data, sizeof(data))); + ck_assert_int_eq(data[0], 0); + ck_assert_int_eq(data[1], 0); + ck_assert_int_eq(data[2], 0); + ck_assert_int_eq(data[3], 0); +} +END_TEST + +START_TEST (test_get_nibble) +{ + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result = get_nibble(data, sizeof(data), 0); + ck_assert_int_eq(result, 0x1); + result = get_nibble(data, sizeof(data), 1); + ck_assert_int_eq(result, 0x2); + result = get_nibble(data, sizeof(data), 2); + ck_assert_int_eq(result, 0x3); +} +END_TEST + +START_TEST (test_get_bits_out_of_range) +{ + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result[4]; + fail_if(copy_bits_right_aligned(data, sizeof(data), 25, 16, result, + sizeof(result))); +} +END_TEST + +START_TEST (test_get_bits) +{ + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result[4] = {0}; + fail_unless(copy_bits_right_aligned(data, sizeof(data), 0, 16, result, + sizeof(result))); + ck_assert_int_eq(result[2], 0x12); + ck_assert_int_eq(result[3], 0x34); +} +END_TEST + +START_TEST (test_copy_bytes) +{ + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result[4] = {0}; + fail_unless(copy_bytes_right_aligned(data, sizeof(data), 1, 3, result, + sizeof(result))); + ck_assert_int_eq(result[1], 0x34); + ck_assert_int_eq(result[2], 0x56); + ck_assert_int_eq(result[3], 0x78); +} +END_TEST + +START_TEST (test_get_uneven_bits) +{ + uint8_t data[4] = {0x12, 0x34, 0x56, 0x78}; + uint8_t result[4] = {0}; + fail_unless(copy_bits_right_aligned(data, sizeof(data), 4, 12, result, + sizeof(result))); + ck_assert_int_eq(result[2], 0x2); + ck_assert_int_eq(result[3], 0x34); +} +END_TEST + +Suite* bitfieldSuite(void) { + Suite* s = suite_create("bitfield"); + TCase *tc_core = tcase_create("core"); + tcase_add_test(tc_core, test_get_byte); + tcase_add_test(tc_core, test_get_nibble); + tcase_add_test(tc_core, test_set_nibble); + tcase_add_test(tc_core, test_set_bitfield); + tcase_add_test(tc_core, test_set_bitfield_doesnt_fit); + tcase_add_test(tc_core, test_get_bits); + tcase_add_test(tc_core, test_copy_bytes); + tcase_add_test(tc_core, test_get_bits_out_of_range); + tcase_add_test(tc_core, test_get_uneven_bits); + 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/CAN-binder/libs/bitfield-c/tests/read_tests.c b/CAN-binder/libs/bitfield-c/tests/read_tests.c new file mode 100644 index 00000000..5008cc52 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/tests/read_tests.c @@ -0,0 +1,67 @@ +#include <check.h> +#include <stdint.h> +#include <canutil/read.h> + +const uint64_t BIG_ENDIAN_TEST_DATA = __builtin_bswap64(0xEB00000000000000); +const uint8_t ARRAY_TEST_DATA[] = {0xEB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + +START_TEST (test_eightbyte_parse_float) +{ + float result = eightbyte_parse_float(BIG_ENDIAN_TEST_DATA, 2, 4, 1001.0, + -30000.0); + float correctResult = 0xA * 1001.0 - 30000.0; + fail_unless(result == correctResult, + "parse is incorrect: %f but should be %f", result, correctResult); +} +END_TEST + +START_TEST (test_eightbyte_parse_bool) +{ + bool result = eightbyte_parse_bool(BIG_ENDIAN_TEST_DATA, 0, 1, 1.0, 0); + bool correctResult = true; + fail_unless(result == correctResult, + "parse is incorrect: %d but should be %d", result, correctResult); +} +END_TEST + +START_TEST (test_bitfield_parse_float) +{ + float result = bitfield_parse_float(ARRAY_TEST_DATA, + sizeof(ARRAY_TEST_DATA), 2, 4, 1001.0, -30000.0); + float correctResult = 0xA * 1001.0 - 30000.0; + fail_unless(result == correctResult, + "parse is incorrect: %f but should be %f", result, correctResult); +} +END_TEST + +START_TEST (test_bitfield_parse_bool) +{ + fail_unless(bitfield_parse_bool(ARRAY_TEST_DATA, sizeof(ARRAY_TEST_DATA), + 0, 1, 1.0, 0)); +} +END_TEST + +Suite* canreadSuite(void) { + Suite* s = suite_create("read"); + TCase *tc_core = tcase_create("core"); + tcase_add_checked_fixture(tc_core, NULL, NULL); + tcase_add_test(tc_core, test_eightbyte_parse_float); + tcase_add_test(tc_core, test_eightbyte_parse_bool); + tcase_add_test(tc_core, test_bitfield_parse_float); + tcase_add_test(tc_core, test_bitfield_parse_bool); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = canreadSuite(); + 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/CAN-binder/libs/bitfield-c/tests/write_tests.c b/CAN-binder/libs/bitfield-c/tests/write_tests.c new file mode 100644 index 00000000..4d5d8fc3 --- /dev/null +++ b/CAN-binder/libs/bitfield-c/tests/write_tests.c @@ -0,0 +1,105 @@ +#include <canutil/write.h> +#include <check.h> +#include <stdint.h> + +START_TEST (test_eightbyte_encode_float_precision) +{ + uint64_t value = eightbyte_encode_float(50, 2, 19, 0.001, 0); + ck_assert_int_eq(value, 0x061a800000000000LLU); +} +END_TEST + +START_TEST (test_eightbyte_encode_float) +{ + uint64_t value = eightbyte_encode_float(0, 1, 3, 1, 0); + ck_assert_int_eq(value, 0); + + value = eightbyte_encode_float(1, 1, 3, 1, 0); + ck_assert_int_eq(value, 0x1000000000000000LLU); +} +END_TEST + +START_TEST (test_eightbyte_encode_bool) +{ + uint64_t value = eightbyte_encode_bool(true, 1, 3); + ck_assert_int_eq(value, 0x1000000000000000LLU); + value = eightbyte_encode_bool(false, 1, 3); + ck_assert_int_eq(value, 0x0000000000000000LLU); +} +END_TEST + +START_TEST (test_bitfield_encode_float) +{ + uint8_t data[8] = {0}; + bitfield_encode_float(0, 1, 3, 1, 0, data, sizeof(data)); + ck_assert_int_eq(data[0], 0); + ck_assert_int_eq(data[1], 0); + ck_assert_int_eq(data[2], 0); + ck_assert_int_eq(data[3], 0); + ck_assert_int_eq(data[4], 0); + ck_assert_int_eq(data[5], 0); + ck_assert_int_eq(data[6], 0); + ck_assert_int_eq(data[7], 0); + + bitfield_encode_float(1, 1, 3, 1, 0, data, sizeof(data)); + ck_assert_int_eq(data[0], 0x10); + ck_assert_int_eq(data[1], 0); + ck_assert_int_eq(data[2], 0); + ck_assert_int_eq(data[3], 0); + ck_assert_int_eq(data[4], 0); + ck_assert_int_eq(data[5], 0); + ck_assert_int_eq(data[6], 0); + ck_assert_int_eq(data[7], 0); +} +END_TEST + +START_TEST (test_bitfield_encode_bool) +{ + uint8_t data[8] = {0}; + bitfield_encode_bool(true, 1, 3, data, sizeof(data)); + ck_assert_int_eq(data[0], 0x10); + ck_assert_int_eq(data[1], 0); + ck_assert_int_eq(data[2], 0); + ck_assert_int_eq(data[3], 0); + ck_assert_int_eq(data[4], 0); + ck_assert_int_eq(data[5], 0); + ck_assert_int_eq(data[6], 0); + ck_assert_int_eq(data[7], 0); + + bitfield_encode_bool(false, 1, 3, data, sizeof(data)); + ck_assert_int_eq(data[0], 0); + ck_assert_int_eq(data[1], 0); + ck_assert_int_eq(data[2], 0); + ck_assert_int_eq(data[3], 0); + ck_assert_int_eq(data[4], 0); + ck_assert_int_eq(data[5], 0); + ck_assert_int_eq(data[6], 0); + ck_assert_int_eq(data[7], 0); +} +END_TEST + +Suite* canwriteSuite(void) { + Suite* s = suite_create("write"); + TCase *tc_core = tcase_create("core"); + tcase_add_checked_fixture(tc_core, NULL, NULL); + tcase_add_test(tc_core, test_eightbyte_encode_float); + tcase_add_test(tc_core, test_eightbyte_encode_bool); + tcase_add_test(tc_core, test_eightbyte_encode_float_precision); + tcase_add_test(tc_core, test_bitfield_encode_float); + tcase_add_test(tc_core, test_bitfield_encode_bool); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = canwriteSuite(); + 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/CAN-binder/libs/ini-config/ini-config.cpp b/CAN-binder/libs/ini-config/ini-config.cpp new file mode 100644 index 00000000..631a0201 --- /dev/null +++ b/CAN-binder/libs/ini-config/ini-config.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015, 2016 ,2017 "IoT.bzh" + * Author "Loïc Collignon" <loic.collignon@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ini-config.hpp" + +bool starts_with(const std::string& text, const std::string& token) +{ + if(text.length() < token.length()) return false; + return (text.compare(0, token.length(), token) == 0); +} + +void ini_config::read_file(const std::string& filename) +{ + std::ifstream f(filename); + if (f) + { + std::regex r_section("^\\s*\\[([^\\]]+)\\]\\s*(#.*)?$"); + std::regex r_key("^\\s*([^\\s]+)\\s*=\\s*\"([^\"]+)\"\\s*(#.*)?$"); + std::string current_section; + std::string line; + while (std::getline(f, line)) + { + std::smatch mr; + + switch (qualify(line)) + { + case line_type::section: + if (std::regex_match(line, mr, r_section) && mr.size() >= 2 && mr[1].matched) + { + current_section = mr[1].str(); + } + break; + case line_type::key: + if(std::regex_match(line, mr, r_key) && mr.size() >= 2 && mr[1].matched) + { + std::string key = current_section + '/' + mr[1].str(); + config_[key] = (mr.size() >= 3 && mr[2].matched) ? mr[2].str() : ""; + } + break; + case line_type::ignore: + break; + } + } + } +} + +std::map<std::string, std::string> ini_config::get_keys(const std::string& section) +{ + std::map<std::string, std::string> ret; + + std::string prefix = section + '/'; + for(auto i = config_.begin(); + i != config_.end(); + ++i) + { + if (starts_with(i->first, prefix)) + { + ret[i->first] = i->second; + } + } + return ret; +} + +std::string ini_config::get_value(const std::string& section, const std::string& key) +{ + return config_[section + '/' + key]; +} + +ini_config::line_type ini_config::qualify(std::string& line) +{ + if (line.size()) + { + for (std::string::value_type c : line) + { + if (c == '#') return line_type::ignore; + if (c == '[') return line_type::section; + if (!std::isspace(c, std::locale("C"))) return line_type::key; + } + } + return line_type::ignore; +}
\ No newline at end of file diff --git a/CAN-binder/libs/ini-config/ini-config.hpp b/CAN-binder/libs/ini-config/ini-config.hpp new file mode 100644 index 00000000..3a154547 --- /dev/null +++ b/CAN-binder/libs/ini-config/ini-config.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015, 2016 ,2017 "IoT.bzh" + * Author "Loïc Collignon" <loic.collignon@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INI_CONFIG_HPP +#define INI_CONFIG_HPP + +#include <string> +#include <fstream> +#include <map> +#include <regex> +#include <algorithm> + +// Représente un fichier de configuration. +class ini_config +{ +public: + using map = std::map<std::string, std::string>; + + void read_file(const std::string& filename); + + map get_keys(const std::string& section); + std::string get_value(const std::string& section, const std::string& key); + + typename map::size_type size() const { return config_.size(); } + typename map::iterator begin() { return config_.begin(); } + typename map::iterator end() { return config_.end(); } + typename map::const_iterator cbegin() const { return config_.cbegin(); } + typename map::const_iterator cend() const { return config_.cend(); } + typename map::reverse_iterator rbegin() { return config_.rbegin(); } + typename map::reverse_iterator rend() { return config_.rend(); } + typename map::const_reverse_iterator crbegin() const { return config_.crbegin(); } + typename map::const_reverse_iterator crend() const { return config_.crend(); } + +private: + map config_; + + enum class line_type + { + ignore = 0, + section = 1, + key = 2 + }; + + line_type qualify(std::string& line); +}; + +#endif // INI_CONFIG_HPP diff --git a/CAN-binder/libs/isotp-c/.gitignore b/CAN-binder/libs/isotp-c/.gitignore new file mode 100644 index 00000000..96a3694f --- /dev/null +++ b/CAN-binder/libs/isotp-c/.gitignore @@ -0,0 +1,6 @@ +*.o +.DS_Store +*~ +*.bin +*.gcno +build diff --git a/CAN-binder/libs/isotp-c/.gitmodules b/CAN-binder/libs/isotp-c/.gitmodules new file mode 100644 index 00000000..aae6d232 --- /dev/null +++ b/CAN-binder/libs/isotp-c/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/canutil"] + path = deps/bitfield-c + url = https://github.com/openxc/canutil diff --git a/CAN-binder/libs/isotp-c/.travis.yml b/CAN-binder/libs/isotp-c/.travis.yml new file mode 100644 index 00000000..31bfdeb3 --- /dev/null +++ b/CAN-binder/libs/isotp-c/.travis.yml @@ -0,0 +1,8 @@ +language: c +compiler: + - gcc +script: make test +before_install: + - git submodule update --init + - sudo apt-get update -qq + - sudo apt-get install check diff --git a/CAN-binder/libs/isotp-c/CHANGELOG.mkd b/CAN-binder/libs/isotp-c/CHANGELOG.mkd new file mode 100644 index 00000000..e6fd5cc0 --- /dev/null +++ b/CAN-binder/libs/isotp-c/CHANGELOG.mkd @@ -0,0 +1,10 @@ +# ISO 15765-2 Support Library in C + +## v0.2 + +* Add multi-frame support for diagnostic responses. An IsoTpMessage payload is + currently limited to 256 bytes. + +## v0.1 + +* Initial release diff --git a/CAN-binder/libs/isotp-c/LICENSE b/CAN-binder/libs/isotp-c/LICENSE new file mode 100644 index 00000000..330d61f4 --- /dev/null +++ b/CAN-binder/libs/isotp-c/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/CAN-binder/libs/isotp-c/Makefile b/CAN-binder/libs/isotp-c/Makefile new file mode 100644 index 00000000..90165ba6 --- /dev/null +++ b/CAN-binder/libs/isotp-c/Makefile @@ -0,0 +1,56 @@ +CC = gcc +INCLUDES = -Isrc -Ideps/bitfield-c/src +CFLAGS = $(INCLUDES) -c -Wall -Werror -g -ggdb -std=gnu99 -coverage +LDFLAGS = -coverage +LDLIBS = -lcheck -lm -lrt -lpthread + +TEST_DIR = tests +TEST_OBJDIR = build + +# 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 + +LIBS_PATH = deps +SRC = $(wildcard src/**/*.c) +SRC += $(wildcard deps/bitfield-c/src/**/*.c) +OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(SRC:.c=.o)) +TEST_SRC = $(wildcard $(TEST_DIR)/test_*.c) +TESTS=$(patsubst %.c,$(TEST_OBJDIR)/%.bin,$(TEST_SRC)) +TEST_SUPPORT_SRC = $(TEST_DIR)/common.c +TEST_SUPPORT_OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(TEST_SUPPORT_SRC:.c=.o)) + +all: $(OBJS) + +test: $(TESTS) + @set -o $(TEST_SET_OPTS) >/dev/null 2>&1 + @export SHELLOPTS + @sh runtests.sh $(TEST_OBJDIR)/$(TEST_DIR) + +COVERAGE_INFO_FILENAME = coverage.info +COVERAGE_INFO_PATH = $(TEST_OBJDIR)/$(COVERAGE_INFO_FILENAME) +coverage: + @lcov --base-directory . --directory src --zerocounters -q + @make clean + @make test + @lcov --base-directory . --directory $(TEST_OBJDIR) -c -o $(TEST_OBJDIR)/coverage.info + @lcov --remove $(COVERAGE_INFO_PATH) "$(LIBS_PATH)/bitfield-c/*" -o $(COVERAGE_INFO_PATH) + @genhtml -o $(TEST_OBJDIR)/coverage -t "isotp-c test coverage" --num-spaces 4 $(COVERAGE_INFO_PATH) + @$(BROWSER) $(TEST_OBJDIR)/coverage/index.html + @echo "$(GREEN)Coverage information generated in $(TEST_OBJDIR)/coverage/index.html.$(COLOR_RESET)" + +$(TEST_OBJDIR)/%.o: %.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $< + +$(TEST_OBJDIR)/%.bin: $(TEST_OBJDIR)/%.o $(OBJS) $(TEST_SUPPORT_OBJS) + @mkdir -p $(dir $@) + $(CC) $(LDFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $^ $(LDLIBS) + +clean: + rm -rf $(TEST_OBJDIR) diff --git a/CAN-binder/libs/isotp-c/README.mkd b/CAN-binder/libs/isotp-c/README.mkd new file mode 100644 index 00000000..98030483 --- /dev/null +++ b/CAN-binder/libs/isotp-c/README.mkd @@ -0,0 +1,142 @@ +ISO-TP (ISO 15765-2) Support Library in C +================================ + +This is a platform agnostic C library that implements the ISO 15765-2 (also +known as ISO-TP) protocol, which runs over a CAN bus. Quoting Wikipedia: + +>ISO 15765-2, or ISO-TP, is an international standard for sending data packets +>over a CAN-Bus. The protocol allows for the transport of messages that exceed +>the eight byte maximum payload of CAN frames. ISO-TP segments longer messages +>into multiple frames, adding metadata that allows the interpretation of +>individual frames and reassembly into a complete message packet by the +>recipient. It can carry up to 4095 bytes of payload per message packet. + +This library doesn't assume anything about the source of the ISO-TP messages or +the underlying interface to CAN. It uses dependency injection to give you +complete control. + +The current version supports *only single frame ISO-TP messages*. This is fine +for OBD-II diagnostic messages, for example, but this library needs some +additional work before it can support sending larger messages. + +## Usage + +First, create some shim functions to let this library use your lower level +system: + + // required, this must send a single CAN message with the given arbitration + // ID (i.e. the CAN message ID) and data. The size will never be more than 8 + // bytes. + void send_can(const uint16_t arbitration_id, const uint8_t* data, + const uint8_t size) { + ... + } + + // optional, provide to receive debugging log messages + void debug(const char* format, ...) { + ... + } + + + // not used in the current version + void set_timer(uint16_t time_ms, void (*callback)) { + ... + } + +With your shims in place, create an IsoTpShims object to pass them around: + + IsoTpShims shims = isotp_init_shims(debug, send_can, set_timer); + +### API + +With your shims in hand, send an ISO-TP message: + + // Optional: This is your callback that will be called when the message is + // completely sent. If it was single frame (the only type supported right + // now), this will be called immediately. + void message_sent(const IsoTpMessage* message, const bool success) { + // You received the message! Do something with it. + } + + IsoTpSendHandle handle = isotp_send(&shims, 0x100, NULL, 0, message_sent); + + if(handle.completed) { + if(!handle.success) { + // something happened and it already failed - possibly we aren't able to + // send CAN messages + return; + } else { + // If the message fit in a single frame, it's already been sent + // and you're done + } + } else { + while(true) { + // Continue to read from CAN, passing off each message to the handle + // this will return true when the message is completely sent (which + // may take more than one call if it was multi frame and we're waiting + // on flow control responses from the receiver) + bool complete = isotp_continue_send(&shims, &handle, 0x100, data, size); + + if(complete && handle.completed) { + if(handle.success) { + // All frames of the message have now been sent, following + // whatever flow control feedback it got from the receiver + } else { + // the message was unable to be sent and we bailed - fatal + // error! + } + } + } + } + +Finally, receive an ISO-TP message: + + // Optional: This is your callback for when a complete ISO-TP message is + // received at the arbitration ID you specify. The completed message is + // also returned by isotp_continue_receive, which can sometimes be more + // useful since you have more context. + void message_received(const IsoTpMessage* message) { + } + + IsoTpReceiveHandle handle = isotp_receive(&shims, 0x100, message_received); + if(!handle.success) { + // something happened and it already failed - possibly we aren't able to + // send CAN messages + } else { + while(true) { + // Continue to read from CAN, passing off each message to the handle + IsoTpMessage message = isotp_continue_receive(&shims, &handle, 0x100, data, size); + + if(message.completed && handle.completed) { + if(handle.success) { + // A message has been received successfully + } else { + // Fatal error - we weren't able to receive a message and + // gave up trying. A message using flow control may have + // timed out. + } + } + } + } + +## Testing + +The library includes a test suite that uses the `check` C unit test library. + + $ make test + +You can also see the test coverage if you have `lcov` installed and the +`BROWSER` environment variable set to your choice of web browsers: + + $ BROWSER=google-chrome-stable make coverage + +## Authors + +* Chris Peplin cpeplin@ford.com +* David Boll dboll2@ford.com (the inspiration for the library's API is from David) + +## License + +Copyright (c) 2013 Ford Motor Company + +Licensed under the BSD license. diff --git a/CAN-binder/libs/isotp-c/deps/bitfield-c b/CAN-binder/libs/isotp-c/deps/bitfield-c new file mode 160000 +Subproject 7f1d5473842361f97fef886bc4e98949ecf853b diff --git a/CAN-binder/libs/isotp-c/runtests.sh b/CAN-binder/libs/isotp-c/runtests.sh new file mode 100644 index 00000000..4781636b --- /dev/null +++ b/CAN-binder/libs/isotp-c/runtests.sh @@ -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/CAN-binder/libs/isotp-c/src/isotp/isotp.c b/CAN-binder/libs/isotp-c/src/isotp/isotp.c new file mode 100644 index 00000000..ce87f1bd --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/isotp.c @@ -0,0 +1,32 @@ +#include <isotp/isotp.h> +#include <bitfield/bitfield.h> +#include <inttypes.h> + +/* void isotp_set_timeout(IsoTpHandler* handler, uint16_t timeout_ms) { */ + /* handler->timeout_ms = timeout_ms; */ +/* } */ + +IsoTpShims isotp_init_shims(LogShim log, SendCanMessageShim send_can_message, + SetTimerShim set_timer) { + IsoTpShims shims = { + log: log, + send_can_message: send_can_message, + set_timer: set_timer, + frame_padding: ISO_TP_DEFAULT_FRAME_PADDING_STATUS + }; + return shims; +} + +void isotp_message_to_string(const IsoTpMessage* message, char* destination, + size_t destination_length) { + snprintf(destination, destination_length, "ID: 0x%" SCNd32 ", Payload: 0x%02x%02x%02x%02x%02x%02x%02x%02x", + message->arbitration_id, + message->payload[0], + message->payload[1], + message->payload[2], + message->payload[3], + message->payload[4], + message->payload[5], + message->payload[6], + message->payload[7]); +} diff --git a/CAN-binder/libs/isotp-c/src/isotp/isotp.h b/CAN-binder/libs/isotp-c/src/isotp/isotp.h new file mode 100644 index 00000000..3a3658c7 --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/isotp.h @@ -0,0 +1,41 @@ +#ifndef __ISOTP_H__ +#define __ISOTP_H__ + +#include <isotp/isotp_types.h> +#include <isotp/send.h> +#include <isotp/receive.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Initialize an IsoTpShims with the given callback functions. + * + * If any callbacks are not to be used, set them to NULL. For documentation of + * the function type signatures, see isotp_types.h. This struct is a handy + * encapsulation used to pass the shims around to the various isotp_* functions. + * + * Returns a struct with the fields initailized to the callbacks. + */ +IsoTpShims isotp_init_shims(LogShim log, + SendCanMessageShim send_can_message, + SetTimerShim set_timer); + +/* Public: Render an IsoTpMessage as a string into the given buffer. + * + * message - the message to convert to a string, for debug logging. + * destination - the target string buffer. + * destination_length - the size of the destination buffer, i.e. the max size + * for the rendered string. + */ +void isotp_message_to_string(const IsoTpMessage* message, char* destination, + size_t destination_length); + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_H__ diff --git a/CAN-binder/libs/isotp-c/src/isotp/isotp_types.h b/CAN-binder/libs/isotp-c/src/isotp/isotp_types.h new file mode 100644 index 00000000..3b7fd26d --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/isotp_types.h @@ -0,0 +1,144 @@ +#ifndef __ISOTP_TYPES__ +#define __ISOTP_TYPES__ + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> + +#define CAN_MESSAGE_BYTE_SIZE 8 +#define MAX_ISO_TP_MESSAGE_SIZE 4096 +// TODO we want to avoid malloc, and we can't be allocated 4K on the stack for +// each IsoTpMessage, so for now we're setting an artificial max message size +// here - for most multi-frame use cases, 256 bytes is plenty. +#define OUR_MAX_ISO_TP_MESSAGE_SIZE 127 + +/* Private: IsoTp nibble specifics for PCI and Payload. + */ +#define PCI_NIBBLE_INDEX 0 +#define PAYLOAD_LENGTH_NIBBLE_INDEX 1 +#define PAYLOAD_BYTE_INDEX 1 + +/* Private: The default timeout to use when waiting for a response during a + * multi-frame send or receive. + */ +#define ISO_TP_DEFAULT_RESPONSE_TIMEOUT 100 + +/* Private: Determines if by default, padding is added to ISO-TP message frames. + */ +#define ISO_TP_DEFAULT_FRAME_PADDING_STATUS true + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: A container for a sent or received ISO-TP message. + * + * completed - An IsoTpMessage is the return value from a few functions - this + * attribute will be true if the message is actually completely received. + * If the function returns but is only partially through receiving the + * message, this will be false, the multi_frame attribute will be true, + * and you should not consider the other data to be valid. + * multi_frame - Designates the message is being built with multi-frame. + * arbitration_id - The arbitration ID of the message. + * payload - The optional payload of the message - don't forget to check the + * size! + * size - The size of the payload. The size will be 0 if there is no payload. + */ +typedef struct { + const uint32_t arbitration_id; + uint8_t payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; + uint16_t size; + bool completed; + bool multi_frame; +} IsoTpMessage; + +/* Public: The type signature for an optional logging function, if the user + * wishes to provide one. It should print, store or otherwise display the + * message. + * + * message - A format string to log using the given parameters. + * ... (vargs) - the parameters for the format string. + */ +typedef void (*LogShim)(const char* message, ...); +/* Public: The type signature for a function to send a single CAN message. + * + * arbitration_id - The arbitration ID of the message. + * data - The data payload for the message. NULL is valid if size is also 0. + * size - The size of the data payload, in bytes. + * + * Returns true if the CAN message was sent successfully. + */ +typedef bool (*SendCanMessageShim)(const uint32_t arbitration_id, + const uint8_t* data, const uint8_t size); + +/* Public: The type signature for a... TODO, not used yet. + */ +typedef bool (*SetTimerShim)(uint16_t time_ms, void (*callback)); + +/* Public: The signature for a function to be called when an ISO-TP message has + * been completely received. + * + * message - The received message. + */ +typedef void (*IsoTpMessageReceivedHandler)(const IsoTpMessage* message); + +/* Public: the signature for a function to be called when an ISO-TP message has + * been completely sent, or had a fatal error during sending. + * + * message - The sent message. + * success - True if the message was sent successfully. + */ +typedef void (*IsoTpMessageSentHandler)(const IsoTpMessage* message, + const bool success); + +/* Public: The signature for a function to be called when a CAN frame has been + * sent as as part of sending or receive an ISO-TP message. + * + * This is really only useful for debugging the library itself. + * + * message - The ISO-TP message that generated this CAN frame. + */ +typedef void (*IsoTpCanFrameSentHandler)(const IsoTpMessage* message); + +/* Public: A container for the 3 shim functions used by the library to interact + * with the wider system. + * + * Use the isotp_init_shims(...) function to create an instance of this struct. + * + * By default, all CAN frames sent from this device in the process of an ISO-TP + * message are padded out to a complete 8 byte frame. This is often required by + * ECUs. To disable this feature, change the 'frame_padding' field to false on + * the IsoTpShims object returned from isotp_init_shims(...). + * + * frame_padding - true if outgoing CAN frames should be padded to a full 8 + * bytes. + */ +typedef struct { + LogShim log; + SendCanMessageShim send_can_message; + SetTimerShim set_timer; + bool frame_padding; +} IsoTpShims; + +/* Private: PCI types, for identifying each frame of an ISO-TP message. + */ +typedef enum { + PCI_SINGLE = 0x0, + PCI_FIRST_FRAME = 0x1, + PCI_CONSECUTIVE_FRAME = 0x2, + PCI_FLOW_CONTROL_FRAME = 0x3 +} IsoTpProtocolControlInformation; + +/* Private: PCI flow control identifiers. + */ +typedef enum { + PCI_FLOW_STATUS_CONTINUE = 0x0, + PCI_FLOW_STATUS_WAIT = 0x1, + PCI_FLOW_STATUS_OVERFLOW = 0x2 +} IsoTpFlowStatus; + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_TYPES__ diff --git a/CAN-binder/libs/isotp-c/src/isotp/receive.c b/CAN-binder/libs/isotp-c/src/isotp/receive.c new file mode 100644 index 00000000..35b7a2a6 --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/receive.c @@ -0,0 +1,166 @@ +#include <isotp/receive.h> +#include <isotp/send.h> +#include <bitfield/bitfield.h> +#include <string.h> +#include <stdlib.h> + +#define ARBITRATION_ID_OFFSET 0x8 + +static void isotp_complete_receive(IsoTpReceiveHandle* handle, IsoTpMessage* message) { + if(handle->message_received_callback != NULL) { + handle->message_received_callback(message); + } +} + +bool isotp_handle_single_frame(IsoTpReceiveHandle* handle, IsoTpMessage* message) { + isotp_complete_receive(handle, message); + return true; +} + +bool isotp_handle_multi_frame(IsoTpReceiveHandle* handle, IsoTpMessage* message) { + // call this once all consecutive frames have been received + isotp_complete_receive(handle, message); + return true; +} + +bool isotp_send_flow_control_frame(IsoTpShims* shims, IsoTpMessage* message) { + uint8_t can_data[CAN_MESSAGE_BYTE_SIZE] = {0}; + + if(!set_nibble(PCI_NIBBLE_INDEX, PCI_FLOW_CONTROL_FRAME, can_data, sizeof(can_data))) { + shims->log("Unable to set PCI in CAN data"); + return false; + } + + shims->send_can_message(message->arbitration_id - ARBITRATION_ID_OFFSET, can_data, + shims->frame_padding ? 8 : 1 + message->size); + return true; +} + + +IsoTpReceiveHandle isotp_receive(IsoTpShims* shims, + const uint32_t arbitration_id, IsoTpMessageReceivedHandler callback) { + IsoTpReceiveHandle handle = { + success: false, + completed: false, + arbitration_id: arbitration_id, + message_received_callback: callback + }; + + return handle; +} + +IsoTpMessage isotp_continue_receive(IsoTpShims* shims, + IsoTpReceiveHandle* handle, const uint32_t arbitration_id, + const uint8_t data[], const uint8_t size) { + IsoTpMessage message = { + arbitration_id: arbitration_id, + completed: false, + multi_frame: false, + payload: {0}, + size: 0 + }; + + if(size < 1) { + return message; + } + + if(handle->arbitration_id != arbitration_id) { + if(shims->log != NULL) { + // You may turn this on for debugging, but in normal operation it's + // very noisy if you are passing all received CAN messages to this + // handler. + /* shims->log("The arb ID 0x%x doesn't match the expected rx ID 0x%x", */ + /* arbitration_id, handle->arbitration_id); */ + } + return message; + } + + IsoTpProtocolControlInformation pci = (IsoTpProtocolControlInformation) + get_nibble(data, size, 0); + + // TODO this is set up to handle rx a response with a payload, but not to + // handle flow control responses for multi frame messages that we're in the + // process of sending + + switch(pci) { + case PCI_SINGLE: { + uint8_t payload_length = get_nibble(data, size, 1); + + if(payload_length > 0) { + memcpy(message.payload, &data[1], payload_length); + } + + message.size = payload_length; + message.completed = true; + handle->success = true; + handle->completed = true; + isotp_handle_single_frame(handle, &message); + break; + } + //If multi-frame, then the payload length is contained in the 12 + //bits following the first nibble of Byte 0. + case PCI_FIRST_FRAME: { + uint16_t payload_length = (get_nibble(data, size, 1) << 8) + get_byte(data, size, 1); + + if(payload_length > OUR_MAX_ISO_TP_MESSAGE_SIZE) { + shims->log("Multi-frame response too large for receive buffer."); + break; + } + + //Need to allocate memory for the combination of multi-frame + //messages. That way we don't have to allocate 4k of memory + //for each multi-frame response. + uint8_t* combined_payload = NULL; + combined_payload = (uint8_t*)malloc(sizeof(uint8_t)*payload_length); + + if(combined_payload == NULL) { + shims->log("Unable to allocate memory for multi-frame response."); + break; + } + + memcpy(combined_payload, &data[2], CAN_MESSAGE_BYTE_SIZE - 2); + handle->receive_buffer = combined_payload; + handle->received_buffer_size = CAN_MESSAGE_BYTE_SIZE - 2; + handle->incoming_message_size = payload_length; + + message.multi_frame = true; + handle->success = false; + handle->completed = false; + isotp_send_flow_control_frame(shims, &message); + break; + } + case PCI_CONSECUTIVE_FRAME: { + uint8_t start_index = handle->received_buffer_size; + uint8_t remaining_bytes = handle->incoming_message_size - start_index; + message.multi_frame = true; + + if(remaining_bytes > 7) { + memcpy(&handle->receive_buffer[start_index], &data[1], CAN_MESSAGE_BYTE_SIZE - 1); + handle->received_buffer_size = start_index + 7; + } else { + memcpy(&handle->receive_buffer[start_index], &data[1], remaining_bytes); + handle->received_buffer_size = start_index + remaining_bytes; + + if(handle->received_buffer_size != handle->incoming_message_size){ + free(handle->receive_buffer); + handle->success = false; + shims->log("Error capturing all bytes of multi-frame. Freeing memory."); + } else { + memcpy(message.payload,&handle->receive_buffer[0],handle->incoming_message_size); + free(handle->receive_buffer); + message.size = handle->incoming_message_size; + message.completed = true; + shims->log("Successfully captured all of multi-frame. Freeing memory."); + + handle->success = true; + handle->completed = true; + isotp_handle_multi_frame(handle, &message); + } + } + break; + } + default: + break; + } + return message; +} diff --git a/CAN-binder/libs/isotp-c/src/isotp/receive.h b/CAN-binder/libs/isotp-c/src/isotp/receive.h new file mode 100644 index 00000000..6788914a --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/receive.h @@ -0,0 +1,86 @@ +#ifndef __ISOTP_RECEIVE_H__ +#define __ISOTP_RECEIVE_H__ + +#include <isotp/isotp.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: A handle for beginning and continuing receiving a single ISO-TP + * message - both single and multi-frame. + * + * Since an ISO-TP message may contain multiple frames, we need to keep a handle + * around while waiting for subsequent CAN messages to complete the message. + * This struct encapsulates the local state required. + * + * completed - True if the received message request is completely finished. + * success - True if the message request was successful. The value if this field + * isn't valid if 'completed' isn't true. + */ +typedef struct { + bool completed; + bool success; + + // Private + uint32_t arbitration_id; + IsoTpMessageReceivedHandler message_received_callback; + uint16_t timeout_ms; + // timeout_ms: ISO_TP_DEFAULT_RESPONSE_TIMEOUT, + uint8_t* receive_buffer; + uint16_t received_buffer_size; + uint16_t incoming_message_size; + // TODO timer callback for multi frame +} IsoTpReceiveHandle; + +/* Public: Initiate receiving a single ISO-TP message on a particular + * arbitration ID. + * + * Note that no actual CAN data has been received at this point - this just sets + * up a handle to be used when new CAN messages to arrive, so they can be parsed + * as potential single or multi-frame ISO-TP messages. + * + * shims - Low-level shims required to send and receive CAN messages, etc. + * arbitration_id - The arbitration ID to receive the message on. + * callback - an optional function to be called when the message is completely + * received (use NULL if no callback required). + * + * Returns a handle to be used with isotp_continue_receive when a new CAN frame + * arrives. The 'completed' field in the returned IsoTpReceiveHandle will be true + * when the message is completely sent. + */ +IsoTpReceiveHandle isotp_receive(IsoTpShims* shims, + const uint32_t arbitration_id, IsoTpMessageReceivedHandler callback); + +/* Public: Continue to receive a an ISO-TP message, based on a freshly + * received CAN message. + * + * For a multi-frame ISO-TP message, this function must be called + * repeatedly whenever a new CAN message is received in order to complete + * receipt. + * + * TODO does this API work for if we wanted to receive an ISO-TP message and + * send our own flow control messages back? + * + * shims - Low-level shims required to send and receive CAN messages, etc. + * handle - An IsoTpReceiveHandle previously returned by isotp_receive(...). + * arbitration_id - The arbitration_id of the received CAN message. + * data - The data of the received CAN message. + * size - The size of the data in the received CAN message. + * + * Returns an IsoTpMessage with the 'completed' field set to true if a message + * was completely received. If 'completed' is false, more CAN frames are + * required to complete the messages, or the arbitration ID didn't match this + * handle. Keep passing the same handle to this function when CAN frames arrive. + */ +IsoTpMessage isotp_continue_receive(IsoTpShims* shims, + IsoTpReceiveHandle* handle, const uint32_t arbitration_id, + const uint8_t data[], const uint8_t size); + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_RECEIVE_H__ diff --git a/CAN-binder/libs/isotp-c/src/isotp/send.c b/CAN-binder/libs/isotp-c/src/isotp/send.c new file mode 100644 index 00000000..e849bb2c --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/send.c @@ -0,0 +1,89 @@ +#include <isotp/send.h> +#include <bitfield/bitfield.h> +#include <string.h> + +#define PCI_NIBBLE_INDEX 0 +#define PAYLOAD_LENGTH_NIBBLE_INDEX 1 +#define PAYLOAD_BYTE_INDEX 1 + +void isotp_complete_send(IsoTpShims* shims, IsoTpMessage* message, + bool status, IsoTpMessageSentHandler callback) { + if(callback != NULL) { + callback(message, status); + } +} + +IsoTpSendHandle isotp_send_single_frame(IsoTpShims* shims, IsoTpMessage* message, + IsoTpMessageSentHandler callback) { + IsoTpSendHandle handle = { + success: false, + completed: true + }; + + uint8_t can_data[CAN_MESSAGE_BYTE_SIZE] = {0}; + if(!set_nibble(PCI_NIBBLE_INDEX, PCI_SINGLE, can_data, sizeof(can_data))) { + shims->log("Unable to set PCI in CAN data"); + return handle; + } + + if(!set_nibble(PAYLOAD_LENGTH_NIBBLE_INDEX, message->size, can_data, + sizeof(can_data))) { + shims->log("Unable to set payload length in CAN data"); + return handle; + } + + if(message->size > 0) { + memcpy(&can_data[1], message->payload, message->size); + } + + shims->send_can_message(message->arbitration_id, can_data, + shims->frame_padding ? 8 : 1 + message->size); + handle.success = true; + isotp_complete_send(shims, message, true, callback); + return handle; +} + +IsoTpSendHandle isotp_send_multi_frame(IsoTpShims* shims, IsoTpMessage* message, + IsoTpMessageSentHandler callback) { + // TODO make sure to copy message into a local buffer + shims->log("Only single frame messages are supported"); + IsoTpSendHandle handle = { + success: false, + completed: true + }; + // TODO need to set sending and receiving arbitration IDs separately if we + // can't always just add 0x8 (and I think we can't) + return handle; +} + +IsoTpSendHandle isotp_send(IsoTpShims* shims, const uint16_t arbitration_id, + const uint8_t payload[], uint16_t size, + IsoTpMessageSentHandler callback) { + IsoTpMessage message = { + arbitration_id: arbitration_id, + size: size + }; + + memcpy(message.payload, payload, size); + if(size < 8) { + return isotp_send_single_frame(shims, &message, callback); + } else { + return isotp_send_multi_frame(shims, &message, callback); + } +} + +bool isotp_continue_send(IsoTpShims* shims, IsoTpSendHandle* handle, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size) { + // TODO this will need to be tested when we add multi-frame support, + // which is when it'll be necessary to pass in CAN messages to SENDING + // handles. + if(handle->receiving_arbitration_id != arbitration_id) { + if(shims->log != NULL) { + shims->log("The arb ID 0x%x doesn't match the expected tx continuation ID 0x%x", + arbitration_id, handle->receiving_arbitration_id); + } + return false; + } + return false; +} diff --git a/CAN-binder/libs/isotp-c/src/isotp/send.h b/CAN-binder/libs/isotp-c/src/isotp/send.h new file mode 100644 index 00000000..1af32668 --- /dev/null +++ b/CAN-binder/libs/isotp-c/src/isotp/send.h @@ -0,0 +1,86 @@ +#ifndef __ISOTP_SEND_H__ +#define __ISOTP_SEND_H__ + +#include <isotp/isotp.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: A handle for beginning and continuing sending a single ISO-TP + * message - both single and multi-frame. + * + * Since an ISO-TP message may contain multiple frames, we need to keep a handle + * around while waiting for flow control messages from the receiver. + * This struct encapsulates the local state required. + * + * completed - True if the message was completely sent, or the send was + * otherwise cancelled. + * success - True if the message send request was successful. The value if this + * field isn't valid if 'completed' isn't true. + */ +typedef struct { + bool completed; + bool success; + + // Private + uint16_t sending_arbitration_id; + uint16_t receiving_arbitration_id; + IsoTpMessageSentHandler message_sent_callback; + IsoTpCanFrameSentHandler can_frame_sent_callback; + // TODO going to need some state here for multi frame messages +} IsoTpSendHandle; + +/* Public: Initiate sending a single ISO-TP message. + * + * If the message fits in a single ISO-TP frame (i.e. the payload isn't more + * than 7 bytes) it will be sent immediately and the returned IsoTpSendHandle's + * 'completed' flag will be true. + * + * For multi-frame messages, see isotp_continue_send(...). + * + * shims - Low-level shims required to send CAN messages, etc. + * arbitration_id - The arbitration ID to send the message on. + * payload - The payload for the message. If no payload, NULL is valid is size + * is also 0. + * size - The size of the payload, or 0 if no payload. + * callback - an optional function to be called when the message is completely + * sent (use NULL if no callback required). + * + * Returns a handle to be used with isotp_continue_send to continue sending + * multi-frame messages. The 'completed' field in the returned IsoTpSendHandle + * will be true when the message is completely sent. + */ +IsoTpSendHandle isotp_send(IsoTpShims* shims, const uint16_t arbitration_id, + const uint8_t payload[], uint16_t size, + IsoTpMessageSentHandler callback); + +/* Public: Continue to send a multi-frame ISO-TP message, based on a freshly + * received CAN message (potentially from the receiver about flow control). + * + * For a multi-frame ISO-TP message, this function must be called + * repeatedly whenever a new CAN message is received in order to complete the + * send. The sender can't just blast everything onto the bus at once - it must + * wait for some response from the receiver to know how much to send at once. + * + * shims - Low-level shims required to send CAN messages, etc. + * handle - An IsoTpSendHandle previously returned by isotp_send(...). + * arbitration_id - The arbitration_id of the received CAN message. + * data - The data of the received CAN message. + * size - The size of the data in the received CAN message. + * + * Returns true if the message was completely sent, or the send was + * otherwise cancelled. Check the 'success' field of the handle to see if + * it was successful. + */ +bool isotp_continue_send(IsoTpShims* shims, IsoTpSendHandle* handle, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size); + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_SEND_H__ diff --git a/CAN-binder/libs/isotp-c/tests/common.c b/CAN-binder/libs/isotp-c/tests/common.c new file mode 100644 index 00000000..a9eed39e --- /dev/null +++ b/CAN-binder/libs/isotp-c/tests/common.c @@ -0,0 +1,91 @@ +#include <isotp/isotp.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +IsoTpShims SHIMS; +IsoTpReceiveHandle RECEIVE_HANDLE; + +uint32_t last_can_frame_sent_arb_id; +uint8_t last_can_payload_sent[8]; +uint8_t last_can_payload_size; +bool can_frame_was_sent; + +bool message_was_received; +uint32_t last_message_received_arb_id; +uint8_t last_message_received_payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; +uint8_t last_message_received_payload_size; + +uint32_t last_message_sent_arb_id; +bool last_message_sent_status; +uint8_t last_message_sent_payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; +uint8_t last_message_sent_payload_size; + +void debug(const char* format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + printf("\r\n"); + va_end(args); +} + +bool mock_send_can(const uint32_t arbitration_id, const uint8_t* data, + const uint8_t size) { + can_frame_was_sent = true; + last_can_frame_sent_arb_id = arbitration_id; + last_can_payload_size = size; + if(size > 0) { + memcpy(last_can_payload_sent, data, size); + } + return true; +} + +void message_received(const IsoTpMessage* message) { + debug("Received ISO-TP message:"); + message_was_received = true; + char str_message[48] = {0}; + isotp_message_to_string(message, str_message, sizeof(str_message)); + debug("%s", str_message); + last_message_received_arb_id = message->arbitration_id; + last_message_received_payload_size = message->size; + if(message->size > 0) { + memcpy(last_message_received_payload, message->payload, message->size); + } +} + +void message_sent(const IsoTpMessage* message, const bool success) { + if(success) { + debug("Sent ISO-TP message:"); + } else { + debug("Unable to send ISO-TP message:"); + } + char str_message[48] = {0}; + isotp_message_to_string(message, str_message, sizeof(str_message)); + debug("%s", str_message); + + last_message_sent_arb_id = message->arbitration_id; + last_message_sent_payload_size = message->size; + last_message_sent_status = success; + if(message->size > 0) { + memcpy(last_message_sent_payload, message->payload, message->size); + } +} + +void can_frame_sent(const uint32_t arbitration_id, const uint8_t* payload, + const uint8_t size) { + debug("Sent CAN Frame with arb ID 0x%x and %d bytes", arbitration_id, size); +} + +void setup() { + SHIMS = isotp_init_shims(debug, mock_send_can, NULL); + RECEIVE_HANDLE = isotp_receive(&SHIMS, 0x2a, message_received); + memset(last_message_sent_payload, 0, OUR_MAX_ISO_TP_MESSAGE_SIZE); + memset(last_message_received_payload, 0, OUR_MAX_ISO_TP_MESSAGE_SIZE); + memset(last_can_payload_sent, 0, sizeof(last_can_payload_sent)); + last_message_sent_status = false; + message_was_received = false; + can_frame_was_sent = false; +} + diff --git a/CAN-binder/libs/isotp-c/tests/test_core.c b/CAN-binder/libs/isotp-c/tests/test_core.c new file mode 100644 index 00000000..73b47af0 --- /dev/null +++ b/CAN-binder/libs/isotp-c/tests/test_core.c @@ -0,0 +1,78 @@ +#include <isotp/receive.h> +#include <check.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +extern IsoTpShims SHIMS; + +extern void message_sent(const IsoTpMessage* message, const bool success); + +extern uint16_t last_can_frame_sent_arb_id; +extern uint8_t last_can_payload_sent[8]; +extern uint8_t last_can_payload_size; +extern bool can_frame_was_sent; + +extern bool message_was_received; +extern uint16_t last_message_received_arb_id; +extern uint8_t last_message_received_payload[]; +extern uint8_t last_message_received_payload_size; + +extern uint16_t last_message_sent_arb_id; +extern bool last_message_sent_status; +extern uint8_t last_message_sent_payload[]; +extern uint8_t last_message_sent_payload_size; + +extern void setup(); + +START_TEST (test_default_frame_padding_on) +{ + ck_assert(SHIMS.frame_padding); + const uint8_t payload[] = {0x12, 0x34}; + uint32_t arbitration_id = 0x2a; + isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), message_sent); + ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); + fail_unless(last_message_sent_status); + ck_assert_int_eq(last_message_sent_payload_size, 2); + ck_assert_int_eq(last_can_payload_size, 8); + +} +END_TEST + +START_TEST (test_disabled_frame_padding) +{ + SHIMS.frame_padding = false; + const uint8_t payload[] = {0x12, 0x34}; + uint32_t arbitration_id = 0x2a; + isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), message_sent); + ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); + fail_unless(last_message_sent_status); + ck_assert_int_eq(last_message_sent_payload_size, 2); + ck_assert_int_eq(last_can_payload_size, 3); + +} +END_TEST + +Suite* testSuite(void) { + Suite* s = suite_create("iso15765"); + TCase *tc_core = tcase_create("core"); + tcase_add_checked_fixture(tc_core, setup, NULL); + tcase_add_test(tc_core, test_default_frame_padding_on); + tcase_add_test(tc_core, test_disabled_frame_padding); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = testSuite(); + 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/CAN-binder/libs/isotp-c/tests/test_receive.c b/CAN-binder/libs/isotp-c/tests/test_receive.c new file mode 100644 index 00000000..607f9061 --- /dev/null +++ b/CAN-binder/libs/isotp-c/tests/test_receive.c @@ -0,0 +1,176 @@ +#include <isotp/isotp.h> +#include <check.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +extern IsoTpShims SHIMS; +extern IsoTpReceiveHandle RECEIVE_HANDLE; + +extern void message_sent(const IsoTpMessage* message, const bool success); + +extern uint16_t last_can_frame_sent_arb_id; +extern uint8_t last_can_payload_sent; +extern uint8_t last_can_payload_size; +extern bool can_frame_was_sent; + +extern bool message_was_received; +extern uint16_t last_message_received_arb_id; +extern uint8_t last_message_received_payload[]; +extern uint8_t last_message_received_payload_size; + +extern uint16_t last_message_sent_arb_id; +extern bool last_message_sent_status; +extern uint8_t last_message_sent_payload[]; +extern uint8_t last_message_sent_payload_size; + +extern void setup(); + +START_TEST (test_receive_empty_can_message) +{ + const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0}; + fail_if(RECEIVE_HANDLE.completed); + IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x100, data, 0); + fail_if(message.completed); + fail_if(message_was_received); +} +END_TEST + +START_TEST (test_receive_wrong_id) +{ + const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0}; + fail_if(RECEIVE_HANDLE.completed); + IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x100, data, 1); + fail_if(message.completed); + fail_if(message_was_received); +} +END_TEST + +START_TEST (test_receive_bad_pci) +{ + // 4 is a reserved number for the PCI field - only 0-3 are allowed + const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0x40}; + IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data, 1); + fail_if(message.completed); + fail_if(message_was_received); +} +END_TEST + +START_TEST (test_receive_single_frame_empty_payload) +{ + const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0x00, 0x12, 0x34}; + fail_if(RECEIVE_HANDLE.completed); + IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data, 3); + fail_unless(RECEIVE_HANDLE.completed); + fail_unless(message.completed); + fail_unless(message_was_received); + ck_assert_int_eq(last_message_received_arb_id, 0x2a); + ck_assert_int_eq(last_message_received_payload_size, 0); +} +END_TEST + +START_TEST (test_receive_single_frame) +{ + const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0x02, 0x12, 0x34}; + IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data, 3); + fail_unless(message.completed); + fail_unless(message_was_received); + ck_assert_int_eq(last_message_received_arb_id, 0x2a); + ck_assert_int_eq(last_message_received_payload_size, 2); + ck_assert_int_eq(last_message_received_payload[0], 0x12); + ck_assert_int_eq(last_message_received_payload[1], 0x34); +} +END_TEST + +START_TEST (test_receive_multi_frame) +{ + const uint8_t data0[CAN_MESSAGE_BYTE_SIZE] = {0x10, 0x14, 0x49, 0x02, 0x01, 0x31, 0x46, 0x4d}; + IsoTpMessage message0 = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data0, 8); + fail_unless(!RECEIVE_HANDLE.completed); + fail_unless(!message0.completed); + fail_unless(!message_was_received); + fail_unless(message0.multi_frame); + //make sure flow control message has been sent. + ck_assert_int_eq(last_can_frame_sent_arb_id, 0x2a - 8); + ck_assert_int_eq(last_can_payload_sent, 0x30); + + const uint8_t data1[CAN_MESSAGE_BYTE_SIZE] = {0x21, 0x43, 0x55, 0x39, 0x4a, 0x39, 0x34, 0x48}; + IsoTpMessage message1 = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data1, 8); + fail_unless(!RECEIVE_HANDLE.completed); + fail_unless(!message1.completed); + fail_unless(!message_was_received); + fail_unless(message1.multi_frame); + + const uint8_t data2[CAN_MESSAGE_BYTE_SIZE] = {0x22, 0x55, 0x41, 0x30, 0x34, 0x35, 0x32, 0x34}; + IsoTpMessage message2 = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data2, 8); + fail_unless(RECEIVE_HANDLE.completed); + fail_unless(message2.completed); + fail_unless(message_was_received); + fail_unless(message2.multi_frame); + + ck_assert_int_eq(last_message_received_arb_id, 0x2a); + ck_assert_int_eq(last_message_received_payload_size, 0x14); + ck_assert_int_eq(last_message_received_payload[0], 0x49); + ck_assert_int_eq(last_message_received_payload[1], 0x02); + ck_assert_int_eq(last_message_received_payload[2], 0x01); + ck_assert_int_eq(last_message_received_payload[3], 0x31); + ck_assert_int_eq(last_message_received_payload[4], 0x46); + ck_assert_int_eq(last_message_received_payload[5], 0x4d); + ck_assert_int_eq(last_message_received_payload[6], 0x43); + ck_assert_int_eq(last_message_received_payload[7], 0x55); + ck_assert_int_eq(last_message_received_payload[8], 0x39); + ck_assert_int_eq(last_message_received_payload[9], 0x4a); + ck_assert_int_eq(last_message_received_payload[10], 0x39); + ck_assert_int_eq(last_message_received_payload[11], 0x34); + ck_assert_int_eq(last_message_received_payload[12], 0x48); + ck_assert_int_eq(last_message_received_payload[13], 0x55); + ck_assert_int_eq(last_message_received_payload[14], 0x41); + ck_assert_int_eq(last_message_received_payload[15], 0x30); + ck_assert_int_eq(last_message_received_payload[16], 0x34); + ck_assert_int_eq(last_message_received_payload[17], 0x35); + ck_assert_int_eq(last_message_received_payload[18], 0x32); + ck_assert_int_eq(last_message_received_payload[19], 0x34); +} +END_TEST + +START_TEST (test_receive_large_multi_frame) +{ + const uint8_t data0[CAN_MESSAGE_BYTE_SIZE] = {0x10, 0x80, 0x49, 0x02, 0x01, 0x31, 0x46, 0x4d}; + IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data0, 8); + //Make sure we don't try to receive messages that are too large and don't send flow control. + fail_unless(!can_frame_was_sent); + fail_unless(!RECEIVE_HANDLE.completed); + fail_unless(!message.completed); + fail_unless(!message_was_received); + fail_unless(!message.multi_frame); +} +END_TEST + +Suite* testSuite(void) { + Suite* s = suite_create("iso15765"); + TCase *tc_core = tcase_create("receive"); + tcase_add_checked_fixture(tc_core, setup, NULL); + tcase_add_test(tc_core, test_receive_wrong_id); + tcase_add_test(tc_core, test_receive_bad_pci); + tcase_add_test(tc_core, test_receive_single_frame); + tcase_add_test(tc_core, test_receive_single_frame_empty_payload); + tcase_add_test(tc_core, test_receive_empty_can_message); + tcase_add_test(tc_core, test_receive_multi_frame); + tcase_add_test(tc_core, test_receive_large_multi_frame); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = testSuite(); + 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/CAN-binder/libs/isotp-c/tests/test_send.c b/CAN-binder/libs/isotp-c/tests/test_send.c new file mode 100644 index 00000000..29cf5dec --- /dev/null +++ b/CAN-binder/libs/isotp-c/tests/test_send.c @@ -0,0 +1,103 @@ +#include <isotp/receive.h> +#include <check.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +extern IsoTpShims SHIMS; + +extern void message_sent(const IsoTpMessage* message, const bool success); + +extern uint16_t last_can_frame_sent_arb_id; +extern uint8_t last_can_payload_sent[8]; +extern uint8_t last_can_payload_size; +extern bool can_frame_was_sent; + +extern bool message_was_received; +extern uint16_t last_message_received_arb_id; +extern uint8_t last_message_received_payload[]; +extern uint8_t last_message_received_payload_size; + +extern uint16_t last_message_sent_arb_id; +extern bool last_message_sent_status; +extern uint8_t last_message_sent_payload[]; +extern uint8_t last_message_sent_payload_size; + +extern void setup(); + +START_TEST (test_send_empty_payload) +{ + SHIMS.frame_padding = false; + uint16_t arbitration_id = 0x2a; + IsoTpSendHandle handle = isotp_send(&SHIMS, arbitration_id, NULL, 0, message_sent); + fail_unless(handle.success); + fail_unless(handle.completed); + ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); + fail_unless(last_message_sent_status); + ck_assert_int_eq(last_message_sent_payload[0], '\0'); + ck_assert_int_eq(last_message_sent_payload_size, 0); + + ck_assert_int_eq(last_can_frame_sent_arb_id, arbitration_id); + fail_unless(can_frame_was_sent); + ck_assert_int_eq(last_can_payload_sent[0], 0x0); + ck_assert_int_eq(last_can_payload_size, 1); +} +END_TEST + +START_TEST (test_send_single_frame) +{ + SHIMS.frame_padding = false; + const uint8_t payload[] = {0x12, 0x34}; + uint16_t arbitration_id = 0x2a; + isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), message_sent); + ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); + fail_unless(last_message_sent_status); + ck_assert_int_eq(last_message_sent_payload[0], 0x12); + ck_assert_int_eq(last_message_sent_payload[1], 0x34); + ck_assert_int_eq(last_message_sent_payload_size, 2); + + ck_assert_int_eq(last_can_frame_sent_arb_id, arbitration_id); + fail_unless(can_frame_was_sent); + ck_assert_int_eq(last_can_payload_sent[0], 0x2); + ck_assert_int_eq(last_can_payload_sent[1], 0x12); + ck_assert_int_eq(last_can_payload_sent[2], 0x34); + ck_assert_int_eq(last_can_payload_size, 3); +} +END_TEST + +START_TEST (test_send_multi_frame) +{ + const uint8_t payload[] = {0x12, 0x34, 0x56, 0x78, 0x90, 0x01, 0x23, + 0x45, 0x67, 0x89}; + uint16_t arbitration_id = 0x2a; + IsoTpSendHandle handle = isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), + message_sent); + fail_unless(handle.completed); + fail_if(handle.success); +} +END_TEST + +Suite* testSuite(void) { + Suite* s = suite_create("iso15765"); + TCase *tc_core = tcase_create("send"); + tcase_add_checked_fixture(tc_core, setup, NULL); + tcase_add_test(tc_core, test_send_empty_payload); + tcase_add_test(tc_core, test_send_single_frame); + tcase_add_test(tc_core, test_send_multi_frame); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int numberFailed; + Suite* s = testSuite(); + 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; +} |