Add 'CAN-binder/libs/isotp-c/' from commit 'ee24440b7c123ab1b0317e57be33e837a1cb51f1'
authorRomain Forlot <romain.forlot@iot.bzh>
Tue, 2 May 2017 15:51:18 +0000 (17:51 +0200)
committerRomain Forlot <romain.forlot@iot.bzh>
Tue, 2 May 2017 15:51:18 +0000 (17:51 +0200)
git-subtree-dir: CAN-binder/libs/isotp-c
git-subtree-mainline: d170c9faeae2cf29c19f3523714ecdf58be73bed
git-subtree-split: ee24440b7c123ab1b0317e57be33e837a1cb51f1

20 files changed:
CAN-binder/libs/isotp-c/.gitignore [new file with mode: 0644]
CAN-binder/libs/isotp-c/.gitmodules [new file with mode: 0644]
CAN-binder/libs/isotp-c/.travis.yml [new file with mode: 0644]
CAN-binder/libs/isotp-c/CHANGELOG.mkd [new file with mode: 0644]
CAN-binder/libs/isotp-c/LICENSE [new file with mode: 0644]
CAN-binder/libs/isotp-c/Makefile [new file with mode: 0644]
CAN-binder/libs/isotp-c/README.mkd [new file with mode: 0644]
CAN-binder/libs/isotp-c/deps/bitfield-c [new submodule]
CAN-binder/libs/isotp-c/runtests.sh [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/isotp.c [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/isotp.h [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/isotp_types.h [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/receive.c [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/receive.h [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/send.c [new file with mode: 0644]
CAN-binder/libs/isotp-c/src/isotp/send.h [new file with mode: 0644]
CAN-binder/libs/isotp-c/tests/common.c [new file with mode: 0644]
CAN-binder/libs/isotp-c/tests/test_core.c [new file with mode: 0644]
CAN-binder/libs/isotp-c/tests/test_receive.c [new file with mode: 0644]
CAN-binder/libs/isotp-c/tests/test_send.c [new file with mode: 0644]

diff --git a/CAN-binder/libs/isotp-c/.gitignore b/CAN-binder/libs/isotp-c/.gitignore
new file mode 100644 (file)
index 0000000..96a3694
--- /dev/null
@@ -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 (file)
index 0000000..aae6d23
--- /dev/null
@@ -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 (file)
index 0000000..31bfdeb
--- /dev/null
@@ -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 (file)
index 0000000..e6fd5cc
--- /dev/null
@@ -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 (file)
index 0000000..330d61f
--- /dev/null
@@ -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 (file)
index 0000000..90165ba
--- /dev/null
@@ -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 (file)
index 0000000..9803048
--- /dev/null
@@ -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 (submodule)
index 0000000..7f1d547
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 7f1d5473842361f97fef886bc4e98949ecf853b6
diff --git a/CAN-binder/libs/isotp-c/runtests.sh b/CAN-binder/libs/isotp-c/runtests.sh
new file mode 100644 (file)
index 0000000..4781636
--- /dev/null
@@ -0,0 +1,17 @@
+echo "Running unit tests:"
+
+for i in $1/*.bin
+do
+    if test -f $i
+    then
+        if ./$i
+        then
+            echo $i PASS
+        else
+            echo "ERROR in test $i:"
+            exit 1
+        fi
+    fi
+done
+
+echo "${txtbld}$(tput setaf 2)All unit tests passed.$(tput sgr0)"
diff --git a/CAN-binder/libs/isotp-c/src/isotp/isotp.c b/CAN-binder/libs/isotp-c/src/isotp/isotp.c
new file mode 100644 (file)
index 0000000..ce87f1b
--- /dev/null
@@ -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 (file)
index 0000000..3a3658c
--- /dev/null
@@ -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 (file)
index 0000000..3b7fd26
--- /dev/null
@@ -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 (file)
index 0000000..35b7a2a
--- /dev/null
@@ -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 (file)
index 0000000..6788914
--- /dev/null
@@ -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 (file)
index 0000000..e849bb2
--- /dev/null
@@ -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 (file)
index 0000000..1af3266
--- /dev/null
@@ -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 (file)
index 0000000..a9eed39
--- /dev/null
@@ -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 (file)
index 0000000..73b47af
--- /dev/null
@@ -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 (file)
index 0000000..607f906
--- /dev/null
@@ -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 (file)
index 0000000..29cf5de
--- /dev/null
@@ -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;
+}