├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.mkd ├── LICENSE ├── Makefile ├── README.mkd ├── runtests.sh ├── src └── isotp │ ├── allocate.c │ ├── allocate.h │ ├── isotp.c │ ├── isotp.h │ ├── isotp_types.h │ ├── receive.c │ ├── receive.h │ ├── send.c │ └── send.h └── tests ├── common.c ├── test_allocate.c ├── test_core.c ├── test_receive.c └── test_send.c /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install Dependencies 13 | run: | 14 | sudo apt-get update -qq 15 | sudo apt-get install check 16 | git submodule update --init --recursive 17 | - name: make test 18 | run: make test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .DS_Store 3 | *~ 4 | *.bin 5 | *.gcno 6 | build 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/canutil"] 2 | path = deps/bitfield-c 3 | url = https://github.com/openxc/canutil 4 | -------------------------------------------------------------------------------- /CHANGELOG.mkd: -------------------------------------------------------------------------------- 1 | # ISO 15765-2 Support Library in C 2 | 3 | ## v0.2 4 | 5 | * Add multi-frame support for diagnostic responses. An IsoTpMessage payload is 6 | currently limited to 256 bytes. 7 | 8 | ## v0.1 9 | 10 | * Initial release 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Ford Motor Company 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | INCLUDES = -Isrc -Ideps/bitfield-c/src 3 | CFLAGS = $(INCLUDES) -c -Wall -Werror -g -ggdb -std=gnu99 -coverage 4 | LDFLAGS = -coverage 5 | LDLIBS = -lcheck -lm -lrt -lpthread -lsubunit 6 | 7 | TEST_DIR = tests 8 | TEST_OBJDIR = build 9 | 10 | # Guard against \r\n line endings only in Cygwin 11 | OSTYPE := $(shell uname) 12 | ifneq ($(OSTYPE),Darwin) 13 | OSTYPE := $(shell uname -o) 14 | ifeq ($(OSTYPE),Cygwin) 15 | TEST_SET_OPTS = igncr 16 | endif 17 | endif 18 | 19 | LIBS_PATH = deps 20 | SRC = $(wildcard src/**/*.c) 21 | SRC += $(wildcard deps/bitfield-c/src/**/*.c) 22 | OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(SRC:.c=.o)) 23 | TEST_SRC = $(wildcard $(TEST_DIR)/test_*.c) 24 | TESTS=$(patsubst %.c,$(TEST_OBJDIR)/%.bin,$(TEST_SRC)) 25 | TEST_SUPPORT_SRC = $(TEST_DIR)/common.c 26 | TEST_SUPPORT_OBJS = $(patsubst %,$(TEST_OBJDIR)/%,$(TEST_SUPPORT_SRC:.c=.o)) 27 | 28 | all: $(OBJS) 29 | 30 | test: $(TESTS) 31 | @set -o $(TEST_SET_OPTS) >/dev/null 2>&1 32 | @export SHELLOPTS 33 | @sh runtests.sh $(TEST_OBJDIR)/$(TEST_DIR) 34 | 35 | COVERAGE_INFO_FILENAME = coverage.info 36 | COVERAGE_INFO_PATH = $(TEST_OBJDIR)/$(COVERAGE_INFO_FILENAME) 37 | coverage: 38 | @lcov --base-directory . --directory src --zerocounters -q 39 | @make clean 40 | @make test 41 | @lcov --base-directory . --directory $(TEST_OBJDIR) -c -o $(TEST_OBJDIR)/coverage.info 42 | @lcov --remove $(COVERAGE_INFO_PATH) "$(LIBS_PATH)/bitfield-c/*" -o $(COVERAGE_INFO_PATH) 43 | @genhtml -o $(TEST_OBJDIR)/coverage -t "isotp-c test coverage" --num-spaces 4 $(COVERAGE_INFO_PATH) 44 | @$(BROWSER) $(TEST_OBJDIR)/coverage/index.html 45 | @echo "$(GREEN)Coverage information generated in $(TEST_OBJDIR)/coverage/index.html.$(COLOR_RESET)" 46 | 47 | $(TEST_OBJDIR)/%.o: %.c 48 | @mkdir -p $(dir $@) 49 | $(CC) $(CFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $< 50 | 51 | $(TEST_OBJDIR)/%.bin: $(TEST_OBJDIR)/%.o $(OBJS) $(TEST_SUPPORT_OBJS) 52 | @mkdir -p $(dir $@) 53 | $(CC) $(LDFLAGS) $(CC_SYMBOLS) $(INCLUDES) -o $@ $^ $(LDLIBS) 54 | 55 | clean: 56 | rm -rf $(TEST_OBJDIR) 57 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | ISO-TP (ISO 15765-2) Support Library in C 2 | ================================ 3 | 4 | This is a platform agnostic C library that implements the ISO 15765-2 (also 5 | known as ISO-TP) protocol, which runs over a CAN bus. Quoting Wikipedia: 6 | 7 | >ISO 15765-2, or ISO-TP, is an international standard for sending data packets 8 | >over a CAN-Bus. The protocol allows for the transport of messages that exceed 9 | >the eight byte maximum payload of CAN frames. ISO-TP segments longer messages 10 | >into multiple frames, adding metadata that allows the interpretation of 11 | >individual frames and reassembly into a complete message packet by the 12 | >recipient. It can carry up to 4095 bytes of payload per message packet. 13 | 14 | This library doesn't assume anything about the source of the ISO-TP messages or 15 | the underlying interface to CAN. It uses dependency injection to give you 16 | complete control. 17 | 18 | The current version supports *only single frame ISO-TP messages*. This is fine 19 | for OBD-II diagnostic messages, for example, but this library needs some 20 | additional work before it can support sending larger messages. 21 | 22 | ## Usage 23 | 24 | First, create some shim functions to let this library use your lower level 25 | system: 26 | 27 | // required, this must send a single CAN message with the given arbitration 28 | // ID (i.e. the CAN message ID) and data. The size will never be more than 8 29 | // bytes. 30 | bool send_can(const uint32_t arbitration_id, const uint8_t* data, 31 | const uint8_t size) { 32 | ... 33 | } 34 | 35 | // optional, provide to receive debugging log messages 36 | void debug(const char* format, ...) { 37 | ... 38 | } 39 | 40 | 41 | // not used in the current version 42 | bool set_timer(uint16_t time_ms, void (*callback)) { 43 | ... 44 | } 45 | 46 | With your shims in place, create an IsoTpShims object to pass them around: 47 | 48 | IsoTpShims shims = isotp_init_shims(debug, send_can, set_timer); 49 | 50 | ### API 51 | 52 | With your shims in hand, send an ISO-TP message: 53 | 54 | // Optional: This is your callback that will be called when the message is 55 | // completely sent. If it was single frame (the only type supported right 56 | // now), this will be called immediately. 57 | void message_sent(const IsoTpMessage* message, const bool success) { 58 | // You received the message! Do something with it. 59 | } 60 | 61 | IsoTpSendHandle handle = isotp_send(&shims, 0x100, NULL, 0, message_sent); 62 | 63 | if(handle.completed) { 64 | if(!handle.success) { 65 | // something happened and it already failed - possibly we aren't able to 66 | // send CAN messages 67 | return; 68 | } else { 69 | // If the message fit in a single frame, it's already been sent 70 | // and you're done 71 | } 72 | } else { 73 | while(true) { 74 | // Continue to read from CAN, passing off each message to the handle 75 | // this will return true when the message is completely sent (which 76 | // may take more than one call if it was multi frame and we're waiting 77 | // on flow control responses from the receiver) 78 | bool complete = isotp_continue_send(&shims, &handle, 0x100, data, size); 79 | 80 | if(complete && handle.completed) { 81 | if(handle.success) { 82 | // All frames of the message have now been sent, following 83 | // whatever flow control feedback it got from the receiver 84 | } else { 85 | // the message was unable to be sent and we bailed - fatal 86 | // error! 87 | } 88 | } 89 | } 90 | } 91 | 92 | Finally, receive an ISO-TP message: 93 | 94 | // Optional: This is your callback for when a complete ISO-TP message is 95 | // received at the arbitration ID you specify. The completed message is 96 | // also returned by isotp_continue_receive, which can sometimes be more 97 | // useful since you have more context. 98 | void message_received(const IsoTpMessage* message) { 99 | } 100 | 101 | IsoTpReceiveHandle handle = isotp_receive(&shims, 0x100, message_received); 102 | if(!handle.success) { 103 | // something happened and it already failed - possibly we aren't able to 104 | // send CAN messages 105 | } else { 106 | while(true) { 107 | // Continue to read from CAN, passing off each message to the handle 108 | IsoTpMessage message = isotp_continue_receive(&shims, &handle, 0x100, data, size); 109 | 110 | if(message.completed && handle.completed) { 111 | if(handle.success) { 112 | // A message has been received successfully 113 | } else { 114 | // Fatal error - we weren't able to receive a message and 115 | // gave up trying. A message using flow control may have 116 | // timed out. 117 | } 118 | } 119 | } 120 | } 121 | 122 | ## Testing 123 | 124 | The library includes a test suite that uses the `check` C unit test library. 125 | 126 | $ make test 127 | 128 | You can also see the test coverage if you have `lcov` installed and the 129 | `BROWSER` environment variable set to your choice of web browsers: 130 | 131 | $ BROWSER=google-chrome-stable make coverage 132 | 133 | ## Authors 134 | 135 | * Chris Peplin cpeplin@ford.com 136 | * David Boll dboll2@ford.com (the inspiration for the library's API is from David) 137 | 138 | ## License 139 | 140 | Copyright (c) 2013 Ford Motor Company 141 | 142 | Licensed under the BSD license. 143 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | echo "Running unit tests:" 2 | 3 | for i in $1/*.bin 4 | do 5 | if test -f $i 6 | then 7 | if ./$i 8 | then 9 | echo $i PASS 10 | else 11 | echo "ERROR in test $i:" 12 | exit 1 13 | fi 14 | fi 15 | done 16 | 17 | echo "${txtbld}$(tput setaf 2)All unit tests passed.$(tput sgr0)" 18 | -------------------------------------------------------------------------------- /src/isotp/allocate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | uint8_t* allocate (size_t size) 7 | { 8 | return (uint8_t*) malloc((sizeof(uint8_t))* size); 9 | } 10 | 11 | void free_allocated (uint8_t* data) 12 | { 13 | free(data); 14 | } -------------------------------------------------------------------------------- /src/isotp/allocate.h: -------------------------------------------------------------------------------- 1 | #ifndef __ISOTP_ALLOCATE_H__ 2 | #define __ISOTP_ALLOCATE_H__ 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | uint8_t* allocate (size_t size); 12 | void free_allocated (uint8_t* data); 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | 18 | #endif // __ISOTP_ALLOCATE_H_ -------------------------------------------------------------------------------- /src/isotp/isotp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* void isotp_set_timeout(IsoTpHandler* handler, uint16_t timeout_ms) { */ 6 | /* handler->timeout_ms = timeout_ms; */ 7 | /* } */ 8 | 9 | IsoTpShims isotp_init_shims(LogShim log, SendCanMessageShim send_can_message, 10 | SetTimerShim set_timer) { 11 | IsoTpShims shims = { 12 | log: log, 13 | send_can_message: send_can_message, 14 | set_timer: set_timer, 15 | frame_padding: ISO_TP_DEFAULT_FRAME_PADDING_STATUS 16 | }; 17 | return shims; 18 | } 19 | 20 | void isotp_message_to_string(const IsoTpMessage* message, char* destination, 21 | size_t destination_length) { 22 | snprintf(destination, destination_length, "ID: 0x%" SCNd32 ", Payload: 0x%02x%02x%02x%02x%02x%02x%02x%02x", 23 | message->arbitration_id, 24 | message->payload[0], 25 | message->payload[1], 26 | message->payload[2], 27 | message->payload[3], 28 | message->payload[4], 29 | message->payload[5], 30 | message->payload[6], 31 | message->payload[7]); 32 | } 33 | -------------------------------------------------------------------------------- /src/isotp/isotp.h: -------------------------------------------------------------------------------- 1 | #ifndef __ISOTP_H__ 2 | #define __ISOTP_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | /* Public: Initialize an IsoTpShims with the given callback functions. 16 | * 17 | * If any callbacks are not to be used, set them to NULL. For documentation of 18 | * the function type signatures, see isotp_types.h. This struct is a handy 19 | * encapsulation used to pass the shims around to the various isotp_* functions. 20 | * 21 | * Returns a struct with the fields initailized to the callbacks. 22 | */ 23 | IsoTpShims isotp_init_shims(LogShim log, 24 | SendCanMessageShim send_can_message, 25 | SetTimerShim set_timer); 26 | 27 | /* Public: Render an IsoTpMessage as a string into the given buffer. 28 | * 29 | * message - the message to convert to a string, for debug logging. 30 | * destination - the target string buffer. 31 | * destination_length - the size of the destination buffer, i.e. the max size 32 | * for the rendered string. 33 | */ 34 | void isotp_message_to_string(const IsoTpMessage* message, char* destination, 35 | size_t destination_length); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif // __ISOTP_H__ 42 | -------------------------------------------------------------------------------- /src/isotp/isotp_types.h: -------------------------------------------------------------------------------- 1 | #ifndef __ISOTP_TYPES__ 2 | #define __ISOTP_TYPES__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // 1=Turn on the stitch Multiframe feature of April 2020 9 | // 0=No stitching so accumulate all partials and pass full Diag response 10 | // when all Diag partials have been received. 11 | #define STITCH_MULTIFRAME 1 12 | 13 | 14 | #define CAN_MESSAGE_BYTE_SIZE 8 15 | #if (STITCH_MULTIFRAME==1) 16 | #define MAX_ISO_TP_MESSAGE_SIZE 4096 17 | #else 18 | // Really This could be reduced to 8 bytes 19 | #define MAX_ISO_TP_MESSAGE_SIZE 16 20 | #endif 21 | // TODO we want to avoid malloc, and we can't be allocated 4K on the stack for 22 | // each IsoTpMessage, so for now we're setting an artificial max message size 23 | // here - for most multi-frame use cases, 256 bytes is plenty. 24 | #define OUR_MAX_ISO_TP_MESSAGE_SIZE 127 25 | 26 | /* Private: IsoTp nibble specifics for PCI and Payload. 27 | */ 28 | #define PCI_NIBBLE_INDEX 0 29 | #define PAYLOAD_LENGTH_NIBBLE_INDEX 1 30 | #define PAYLOAD_BYTE_INDEX 1 31 | 32 | /* Private: The default timeout to use when waiting for a response during a 33 | * multi-frame send or receive. 34 | */ 35 | #define ISO_TP_DEFAULT_RESPONSE_TIMEOUT 100 36 | 37 | /* Private: Determines if by default, padding is added to ISO-TP message frames. 38 | */ 39 | #define ISO_TP_DEFAULT_FRAME_PADDING_STATUS true 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | /* Public: A container for a sent or received ISO-TP message. 46 | * 47 | * completed - An IsoTpMessage is the return value from a few functions - this 48 | * attribute will be true if the message is actually completely received. 49 | * If the function returns but is only partially through receiving the 50 | * message, this will be false, the multi_frame attribute will be true, 51 | * and you should not consider the other data to be valid. 52 | * multi_frame - Designates the message is being built with multi-frame. 53 | * arbitration_id - The arbitration ID of the message. 54 | * payload - The optional payload of the message - don't forget to check the 55 | * size! 56 | * size - The size of the payload. The size will be 0 if there is no payload. 57 | */ 58 | typedef struct { 59 | const uint32_t arbitration_id; 60 | uint8_t payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; 61 | uint16_t size; 62 | bool completed; 63 | bool multi_frame; 64 | } IsoTpMessage; 65 | 66 | /* Public: The type signature for an optional logging function, if the user 67 | * wishes to provide one. It should print, store or otherwise display the 68 | * message. 69 | * 70 | * message - A format string to log using the given parameters. 71 | * ... (vargs) - the parameters for the format string. 72 | */ 73 | typedef void (*LogShim)(const char* message, ...); 74 | /* Public: The type signature for a function to send a single CAN message. 75 | * 76 | * arbitration_id - The arbitration ID of the message. 77 | * data - The data payload for the message. NULL is valid if size is also 0. 78 | * size - The size of the data payload, in bytes. 79 | * 80 | * Returns true if the CAN message was sent successfully. 81 | */ 82 | typedef bool (*SendCanMessageShim)(const uint32_t arbitration_id, 83 | const uint8_t* data, const uint8_t size); 84 | 85 | /* Public: The type signature for a... TODO, not used yet. 86 | */ 87 | typedef bool (*SetTimerShim)(uint16_t time_ms, void (*callback)); 88 | 89 | /* Public: The signature for a function to be called when an ISO-TP message has 90 | * been completely received. 91 | * 92 | * message - The received message. 93 | */ 94 | typedef void (*IsoTpMessageReceivedHandler)(const IsoTpMessage* message); 95 | 96 | /* Public: the signature for a function to be called when an ISO-TP message has 97 | * been completely sent, or had a fatal error during sending. 98 | * 99 | * message - The sent message. 100 | * success - True if the message was sent successfully. 101 | */ 102 | typedef void (*IsoTpMessageSentHandler)(const IsoTpMessage* message, 103 | const bool success); 104 | 105 | /* Public: The signature for a function to be called when a CAN frame has been 106 | * sent as as part of sending or receive an ISO-TP message. 107 | * 108 | * This is really only useful for debugging the library itself. 109 | * 110 | * message - The ISO-TP message that generated this CAN frame. 111 | */ 112 | typedef void (*IsoTpCanFrameSentHandler)(const IsoTpMessage* message); 113 | 114 | /* Public: A container for the 3 shim functions used by the library to interact 115 | * with the wider system. 116 | * 117 | * Use the isotp_init_shims(...) function to create an instance of this struct. 118 | * 119 | * By default, all CAN frames sent from this device in the process of an ISO-TP 120 | * message are padded out to a complete 8 byte frame. This is often required by 121 | * ECUs. To disable this feature, change the 'frame_padding' field to false on 122 | * the IsoTpShims object returned from isotp_init_shims(...). 123 | * 124 | * frame_padding - true if outgoing CAN frames should be padded to a full 8 125 | * bytes. 126 | */ 127 | typedef struct { 128 | LogShim log; 129 | SendCanMessageShim send_can_message; 130 | SetTimerShim set_timer; 131 | bool frame_padding; 132 | } IsoTpShims; 133 | 134 | /* Private: PCI types, for identifying each frame of an ISO-TP message. 135 | */ 136 | typedef enum { 137 | PCI_SINGLE = 0x0, 138 | PCI_FIRST_FRAME = 0x1, 139 | PCI_CONSECUTIVE_FRAME = 0x2, 140 | PCI_FLOW_CONTROL_FRAME = 0x3 141 | } IsoTpProtocolControlInformation; 142 | 143 | /* Private: PCI flow control identifiers. 144 | */ 145 | typedef enum { 146 | PCI_FLOW_STATUS_CONTINUE = 0x0, 147 | PCI_FLOW_STATUS_WAIT = 0x1, 148 | PCI_FLOW_STATUS_OVERFLOW = 0x2 149 | } IsoTpFlowStatus; 150 | 151 | #ifdef __cplusplus 152 | } 153 | #endif 154 | 155 | #endif // __ISOTP_TYPES__ 156 | -------------------------------------------------------------------------------- /src/isotp/receive.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define ARBITRATION_ID_OFFSET 0x8 9 | 10 | static void isotp_complete_receive(IsoTpReceiveHandle* handle, IsoTpMessage* message) { 11 | if(handle->message_received_callback != NULL) { 12 | handle->message_received_callback(message); 13 | } 14 | } 15 | 16 | bool isotp_handle_single_frame(IsoTpReceiveHandle* handle, IsoTpMessage* message) { 17 | isotp_complete_receive(handle, message); 18 | return true; 19 | } 20 | 21 | bool isotp_handle_multi_frame(IsoTpReceiveHandle* handle, IsoTpMessage* message) { 22 | // call this once all consecutive frames have been received 23 | isotp_complete_receive(handle, message); 24 | return true; 25 | } 26 | 27 | bool isotp_send_flow_control_frame(IsoTpShims* shims, IsoTpMessage* message) { 28 | uint8_t can_data[CAN_MESSAGE_BYTE_SIZE] = {0}; 29 | 30 | if(!set_nibble(PCI_NIBBLE_INDEX, PCI_FLOW_CONTROL_FRAME, can_data, sizeof(can_data))) { 31 | shims->log("Unable to set PCI in CAN data"); 32 | return false; 33 | } 34 | 35 | shims->send_can_message(message->arbitration_id - ARBITRATION_ID_OFFSET, can_data, 36 | shims->frame_padding ? 8 : 1 + message->size); 37 | return true; 38 | } 39 | 40 | 41 | IsoTpReceiveHandle isotp_receive(IsoTpShims* shims, 42 | const uint32_t arbitration_id, IsoTpMessageReceivedHandler callback) { 43 | IsoTpReceiveHandle handle = { 44 | success: false, 45 | completed: false, 46 | arbitration_id: arbitration_id, 47 | message_received_callback: callback 48 | }; 49 | 50 | return handle; 51 | } 52 | 53 | IsoTpMessage isotp_continue_receive(IsoTpShims* shims, 54 | IsoTpReceiveHandle* handle, const uint32_t arbitration_id, 55 | const uint8_t data[], const uint8_t size) { 56 | IsoTpMessage message = { 57 | arbitration_id: arbitration_id, 58 | completed: false, 59 | multi_frame: false, 60 | payload: {0}, 61 | size: 0 62 | }; 63 | int headersize = 3; 64 | 65 | if(size < 1) { 66 | return message; 67 | } 68 | 69 | if(handle->arbitration_id != arbitration_id) { 70 | if(shims->log != NULL) { 71 | // You may turn this on for debugging, but in normal operation it's 72 | // very noisy if you are passing all received CAN messages to this 73 | // handler. 74 | /* shims->log("The arb ID 0x%x doesn't match the expected rx ID 0x%x", */ 75 | /* arbitration_id, handle->arbitration_id); */ 76 | } 77 | return message; 78 | } 79 | 80 | IsoTpProtocolControlInformation pci = (IsoTpProtocolControlInformation) 81 | get_nibble(data, size, 0); 82 | 83 | // TODO this is set up to handle rx a response with a payload, but not to 84 | // handle flow control responses for multi frame messages that we're in the 85 | // process of sending 86 | 87 | switch(pci) { 88 | case PCI_SINGLE: { 89 | uint8_t payload_length = get_nibble(data, size, 1); 90 | 91 | if(payload_length > 0) { 92 | memcpy(message.payload, &data[1], payload_length); 93 | } 94 | 95 | message.size = payload_length; 96 | message.completed = true; 97 | handle->success = true; 98 | handle->completed = true; 99 | isotp_handle_single_frame(handle, &message); 100 | break; 101 | } 102 | //If multi-frame, then the payload length is contained in the 12 103 | //bits following the first nibble of Byte 0. 104 | case PCI_FIRST_FRAME: { 105 | uint16_t payload_length = (get_nibble(data, size, 1) << 8) + get_byte(data, size, 1); 106 | 107 | if(payload_length > OUR_MAX_ISO_TP_MESSAGE_SIZE) { 108 | shims->log("Multi-frame response too large for receive buffer."); 109 | break; 110 | } 111 | 112 | //combined_payload - pre 2020 was large enough to hold the 113 | // full diagnostic response. When multiframe is fully 114 | // implemented this will only need to be large enough to 115 | // hold a 3 byte header. (gja) 116 | 117 | //Need to allocate memory for the combination of multi-frame 118 | //messages. That way we don't have to allocate 4k of memory 119 | //for each multi-frame response. 120 | uint8_t* combined_payload = NULL; 121 | #if (STITCH_MULTIFRAME==1) 122 | // Only need space for the header for stitched multiframe 123 | combined_payload = allocate(headersize); 124 | #else 125 | combined_payload = allocate(payload_length); 126 | #endif 127 | 128 | if(combined_payload == NULL) { 129 | shims->log("Unable to allocate memory for multi-frame response."); 130 | break; 131 | } 132 | 133 | #if (STITCH_MULTIFRAME==1) 134 | // Only need the header for stitched multiframe 135 | // Since we are no longer collecting the parts and passing the total response at the 136 | // end, we do not need to store it which is what combined_payload was for. We just 137 | // need to keep the header for the data which is only 3 bytes 138 | // TODO: Fix tests/common.c - (OUR_MAX_ISO...) unit test 139 | memcpy(combined_payload, &data[2], headersize); 140 | #else 141 | memcpy(combined_payload, &data[2], CAN_MESSAGE_BYTE_SIZE - 2); 142 | #endif 143 | handle->receive_buffer = combined_payload; 144 | handle->received_buffer_size = CAN_MESSAGE_BYTE_SIZE - 2; 145 | handle->incoming_message_size = payload_length; 146 | 147 | message.multi_frame = true; 148 | handle->success = false; 149 | handle->completed = false; 150 | isotp_send_flow_control_frame(shims, &message); 151 | 152 | // 4/27/2020 Return Partial (First frame has important 3 bytes of 153 | // preamble code which contains mode and pid needed by UDS layer 154 | memcpy(message.payload, &data[2], CAN_MESSAGE_BYTE_SIZE - 2); 155 | message.size = CAN_MESSAGE_BYTE_SIZE - 2; 156 | break; 157 | } 158 | case PCI_CONSECUTIVE_FRAME: { 159 | uint8_t start_index = handle->received_buffer_size; 160 | uint8_t remaining_bytes = handle->incoming_message_size - start_index; 161 | message.multi_frame = true; 162 | 163 | if(remaining_bytes > 7) { // If > 7 then there will be another frame 164 | #if (STITCH_MULTIFRAME!=1) 165 | // Only need to accumulate the message when not stitching in client 166 | memcpy(&handle->receive_buffer[start_index], &data[1], CAN_MESSAGE_BYTE_SIZE - 1); 167 | #endif 168 | handle->received_buffer_size = start_index + 7; 169 | 170 | // 4/27/2020 - Return Partial Frame into message 171 | memcpy(message.payload, &data[1],CAN_MESSAGE_BYTE_SIZE - 1); 172 | message.size = CAN_MESSAGE_BYTE_SIZE - 1; 173 | 174 | } else { 175 | #if (STITCH_MULTIFRAME!=1) 176 | // Only need to accumulate the message when not stitching in client 177 | memcpy(&handle->receive_buffer[start_index], &data[1], remaining_bytes); 178 | #endif 179 | handle->received_buffer_size = start_index + remaining_bytes; 180 | 181 | if(handle->received_buffer_size != handle->incoming_message_size){ 182 | free_allocated(handle->receive_buffer); 183 | handle->success = false; 184 | shims->log("Error capturing all bytes of multi-frame. Freeing memory."); 185 | } else { 186 | #if (STITCH_MULTIFRAME==1) 187 | // Copy the 3 bytes of header and Only copy the last partial 188 | // into message.payload 189 | memcpy(message.payload,&handle->receive_buffer[0], headersize); 190 | memcpy(&message.payload[headersize],&data[1], remaining_bytes); 191 | free_allocated(handle->receive_buffer); 192 | message.size = remaining_bytes + headersize; 193 | #else 194 | // Copy the full assembled meessage with it many parts into the output buffer "message" 195 | memcpy(message.payload,&handle->receive_buffer[0],handle->incoming_message_size); 196 | free_allocated(handle->receive_buffer); 197 | message.size = handle->incoming_message_size; 198 | #endif 199 | message.completed = true; 200 | shims->log("Successfully captured all of multi-frame. Freeing memory."); 201 | 202 | handle->success = true; 203 | handle->completed = true; 204 | isotp_handle_multi_frame(handle, &message); 205 | } 206 | } 207 | break; 208 | } 209 | default: 210 | break; 211 | } 212 | return message; 213 | } 214 | -------------------------------------------------------------------------------- /src/isotp/receive.h: -------------------------------------------------------------------------------- 1 | #ifndef __ISOTP_RECEIVE_H__ 2 | #define __ISOTP_RECEIVE_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* Public: A handle for beginning and continuing receiving a single ISO-TP 13 | * message - both single and multi-frame. 14 | * 15 | * Since an ISO-TP message may contain multiple frames, we need to keep a handle 16 | * around while waiting for subsequent CAN messages to complete the message. 17 | * This struct encapsulates the local state required. 18 | * 19 | * completed - True if the received message request is completely finished. 20 | * success - True if the message request was successful. The value if this field 21 | * isn't valid if 'completed' isn't true. 22 | */ 23 | typedef struct { 24 | bool completed; 25 | bool success; 26 | 27 | // Private 28 | uint32_t arbitration_id; 29 | IsoTpMessageReceivedHandler message_received_callback; 30 | uint16_t timeout_ms; 31 | // timeout_ms: ISO_TP_DEFAULT_RESPONSE_TIMEOUT, 32 | uint8_t* receive_buffer; 33 | uint16_t received_buffer_size; 34 | uint16_t incoming_message_size; 35 | // TODO timer callback for multi frame 36 | } IsoTpReceiveHandle; 37 | 38 | /* Public: Initiate receiving a single ISO-TP message on a particular 39 | * arbitration ID. 40 | * 41 | * Note that no actual CAN data has been received at this point - this just sets 42 | * up a handle to be used when new CAN messages to arrive, so they can be parsed 43 | * as potential single or multi-frame ISO-TP messages. 44 | * 45 | * shims - Low-level shims required to send and receive CAN messages, etc. 46 | * arbitration_id - The arbitration ID to receive the message on. 47 | * callback - an optional function to be called when the message is completely 48 | * received (use NULL if no callback required). 49 | * 50 | * Returns a handle to be used with isotp_continue_receive when a new CAN frame 51 | * arrives. The 'completed' field in the returned IsoTpReceiveHandle will be true 52 | * when the message is completely sent. 53 | */ 54 | IsoTpReceiveHandle isotp_receive(IsoTpShims* shims, 55 | const uint32_t arbitration_id, IsoTpMessageReceivedHandler callback); 56 | 57 | /* Public: Continue to receive a an ISO-TP message, based on a freshly 58 | * received CAN message. 59 | * 60 | * For a multi-frame ISO-TP message, this function must be called 61 | * repeatedly whenever a new CAN message is received in order to complete 62 | * receipt. 63 | * 64 | * TODO does this API work for if we wanted to receive an ISO-TP message and 65 | * send our own flow control messages back? 66 | * 67 | * shims - Low-level shims required to send and receive CAN messages, etc. 68 | * handle - An IsoTpReceiveHandle previously returned by isotp_receive(...). 69 | * arbitration_id - The arbitration_id of the received CAN message. 70 | * data - The data of the received CAN message. 71 | * size - The size of the data in the received CAN message. 72 | * 73 | * Returns an IsoTpMessage with the 'completed' field set to true if a message 74 | * was completely received. If 'completed' is false, more CAN frames are 75 | * required to complete the messages, or the arbitration ID didn't match this 76 | * handle. Keep passing the same handle to this function when CAN frames arrive. 77 | */ 78 | IsoTpMessage isotp_continue_receive(IsoTpShims* shims, 79 | IsoTpReceiveHandle* handle, const uint32_t arbitration_id, 80 | const uint8_t data[], const uint8_t size); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif // __ISOTP_RECEIVE_H__ 87 | -------------------------------------------------------------------------------- /src/isotp/send.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define PCI_NIBBLE_INDEX 0 6 | #define PAYLOAD_LENGTH_NIBBLE_INDEX 1 7 | #define PAYLOAD_BYTE_INDEX 1 8 | 9 | void isotp_complete_send(IsoTpShims* shims, IsoTpMessage* message, 10 | bool status, IsoTpMessageSentHandler callback) { 11 | if(callback != NULL) { 12 | callback(message, status); 13 | } 14 | } 15 | 16 | IsoTpSendHandle isotp_send_single_frame(IsoTpShims* shims, IsoTpMessage* message, 17 | IsoTpMessageSentHandler callback) { 18 | IsoTpSendHandle handle = { 19 | success: false, 20 | completed: true 21 | }; 22 | 23 | uint8_t can_data[CAN_MESSAGE_BYTE_SIZE] = {0}; 24 | if(!set_nibble(PCI_NIBBLE_INDEX, PCI_SINGLE, can_data, sizeof(can_data))) { 25 | shims->log("Unable to set PCI in CAN data"); 26 | return handle; 27 | } 28 | 29 | if(!set_nibble(PAYLOAD_LENGTH_NIBBLE_INDEX, message->size, can_data, 30 | sizeof(can_data))) { 31 | shims->log("Unable to set payload length in CAN data"); 32 | return handle; 33 | } 34 | 35 | if(message->size > 0) { 36 | memcpy(&can_data[1], message->payload, message->size); 37 | } 38 | 39 | shims->send_can_message(message->arbitration_id, can_data, 40 | shims->frame_padding ? 8 : 1 + message->size); 41 | handle.success = true; 42 | isotp_complete_send(shims, message, true, callback); 43 | return handle; 44 | } 45 | 46 | IsoTpSendHandle isotp_send_multi_frame(IsoTpShims* shims, IsoTpMessage* message, 47 | IsoTpMessageSentHandler callback) { 48 | // TODO make sure to copy message into a local buffer 49 | shims->log("Only single frame messages are supported"); 50 | IsoTpSendHandle handle = { 51 | success: false, 52 | completed: true 53 | }; 54 | // TODO need to set sending and receiving arbitration IDs separately if we 55 | // can't always just add 0x8 (and I think we can't) 56 | return handle; 57 | } 58 | 59 | IsoTpSendHandle isotp_send(IsoTpShims* shims, const uint16_t arbitration_id, 60 | const uint8_t payload[], uint16_t size, 61 | IsoTpMessageSentHandler callback) { 62 | IsoTpMessage message = { 63 | arbitration_id: arbitration_id, 64 | size: size 65 | }; 66 | 67 | memcpy(message.payload, payload, size); 68 | if(size < 8) { 69 | return isotp_send_single_frame(shims, &message, callback); 70 | } else { 71 | return isotp_send_multi_frame(shims, &message, callback); 72 | } 73 | } 74 | 75 | bool isotp_continue_send(IsoTpShims* shims, IsoTpSendHandle* handle, 76 | const uint16_t arbitration_id, const uint8_t data[], 77 | const uint8_t size) { 78 | // TODO this will need to be tested when we add multi-frame support, 79 | // which is when it'll be necessary to pass in CAN messages to SENDING 80 | // handles. 81 | if(handle->receiving_arbitration_id != arbitration_id) { 82 | if(shims->log != NULL) { 83 | shims->log("The arb ID 0x%x doesn't match the expected tx continuation ID 0x%x", 84 | arbitration_id, handle->receiving_arbitration_id); 85 | } 86 | return false; 87 | } 88 | return false; 89 | } 90 | -------------------------------------------------------------------------------- /src/isotp/send.h: -------------------------------------------------------------------------------- 1 | #ifndef __ISOTP_SEND_H__ 2 | #define __ISOTP_SEND_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* Public: A handle for beginning and continuing sending a single ISO-TP 13 | * message - both single and multi-frame. 14 | * 15 | * Since an ISO-TP message may contain multiple frames, we need to keep a handle 16 | * around while waiting for flow control messages from the receiver. 17 | * This struct encapsulates the local state required. 18 | * 19 | * completed - True if the message was completely sent, or the send was 20 | * otherwise cancelled. 21 | * success - True if the message send request was successful. The value if this 22 | * field isn't valid if 'completed' isn't true. 23 | */ 24 | typedef struct { 25 | bool completed; 26 | bool success; 27 | 28 | // Private 29 | uint16_t sending_arbitration_id; 30 | uint16_t receiving_arbitration_id; 31 | IsoTpMessageSentHandler message_sent_callback; 32 | IsoTpCanFrameSentHandler can_frame_sent_callback; 33 | // TODO going to need some state here for multi frame messages 34 | } IsoTpSendHandle; 35 | 36 | /* Public: Initiate sending a single ISO-TP message. 37 | * 38 | * If the message fits in a single ISO-TP frame (i.e. the payload isn't more 39 | * than 7 bytes) it will be sent immediately and the returned IsoTpSendHandle's 40 | * 'completed' flag will be true. 41 | * 42 | * For multi-frame messages, see isotp_continue_send(...). 43 | * 44 | * shims - Low-level shims required to send CAN messages, etc. 45 | * arbitration_id - The arbitration ID to send the message on. 46 | * payload - The payload for the message. If no payload, NULL is valid is size 47 | * is also 0. 48 | * size - The size of the payload, or 0 if no payload. 49 | * callback - an optional function to be called when the message is completely 50 | * sent (use NULL if no callback required). 51 | * 52 | * Returns a handle to be used with isotp_continue_send to continue sending 53 | * multi-frame messages. The 'completed' field in the returned IsoTpSendHandle 54 | * will be true when the message is completely sent. 55 | */ 56 | IsoTpSendHandle isotp_send(IsoTpShims* shims, const uint16_t arbitration_id, 57 | const uint8_t payload[], uint16_t size, 58 | IsoTpMessageSentHandler callback); 59 | 60 | /* Public: Continue to send a multi-frame ISO-TP message, based on a freshly 61 | * received CAN message (potentially from the receiver about flow control). 62 | * 63 | * For a multi-frame ISO-TP message, this function must be called 64 | * repeatedly whenever a new CAN message is received in order to complete the 65 | * send. The sender can't just blast everything onto the bus at once - it must 66 | * wait for some response from the receiver to know how much to send at once. 67 | * 68 | * shims - Low-level shims required to send CAN messages, etc. 69 | * handle - An IsoTpSendHandle previously returned by isotp_send(...). 70 | * arbitration_id - The arbitration_id of the received CAN message. 71 | * data - The data of the received CAN message. 72 | * size - The size of the data in the received CAN message. 73 | * 74 | * Returns true if the message was completely sent, or the send was 75 | * otherwise cancelled. Check the 'success' field of the handle to see if 76 | * it was successful. 77 | */ 78 | bool isotp_continue_send(IsoTpShims* shims, IsoTpSendHandle* handle, 79 | const uint16_t arbitration_id, const uint8_t data[], 80 | const uint8_t size); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif // __ISOTP_SEND_H__ 87 | -------------------------------------------------------------------------------- /tests/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | IsoTpShims SHIMS; 9 | IsoTpReceiveHandle RECEIVE_HANDLE; 10 | 11 | uint32_t last_can_frame_sent_arb_id; 12 | uint8_t last_can_payload_sent[8]; 13 | uint8_t last_can_payload_size; 14 | bool can_frame_was_sent; 15 | 16 | bool message_was_received; 17 | uint32_t last_message_received_arb_id; 18 | uint8_t last_message_received_payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; 19 | uint8_t last_message_received_payload_size; 20 | 21 | uint32_t last_message_sent_arb_id; 22 | bool last_message_sent_status; 23 | uint8_t last_message_sent_payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; 24 | uint8_t last_message_sent_payload_size; 25 | 26 | void debug(const char* format, ...) { 27 | va_list args; 28 | va_start(args, format); 29 | vprintf(format, args); 30 | printf("\r\n"); 31 | va_end(args); 32 | } 33 | 34 | bool mock_send_can(const uint32_t arbitration_id, const uint8_t* data, 35 | const uint8_t size) { 36 | can_frame_was_sent = true; 37 | last_can_frame_sent_arb_id = arbitration_id; 38 | last_can_payload_size = size; 39 | if(size > 0) { 40 | memcpy(last_can_payload_sent, data, size); 41 | } 42 | return true; 43 | } 44 | 45 | void message_received(const IsoTpMessage* message) { 46 | debug("Received ISO-TP message:"); 47 | message_was_received = true; 48 | char str_message[48] = {0}; 49 | isotp_message_to_string(message, str_message, sizeof(str_message)); 50 | debug("%s", str_message); 51 | last_message_received_arb_id = message->arbitration_id; 52 | last_message_received_payload_size = message->size; 53 | if(message->size > 0) { 54 | memcpy(last_message_received_payload, message->payload, message->size); 55 | } 56 | } 57 | 58 | void message_sent(const IsoTpMessage* message, const bool success) { 59 | if(success) { 60 | debug("Sent ISO-TP message:"); 61 | } else { 62 | debug("Unable to send ISO-TP message:"); 63 | } 64 | char str_message[48] = {0}; 65 | isotp_message_to_string(message, str_message, sizeof(str_message)); 66 | debug("%s", str_message); 67 | 68 | last_message_sent_arb_id = message->arbitration_id; 69 | last_message_sent_payload_size = message->size; 70 | last_message_sent_status = success; 71 | if(message->size > 0) { 72 | memcpy(last_message_sent_payload, message->payload, message->size); 73 | } 74 | } 75 | 76 | void can_frame_sent(const uint32_t arbitration_id, const uint8_t* payload, 77 | const uint8_t size) { 78 | debug("Sent CAN Frame with arb ID 0x%x and %d bytes", arbitration_id, size); 79 | } 80 | 81 | void setup() { 82 | SHIMS = isotp_init_shims(debug, mock_send_can, NULL); 83 | RECEIVE_HANDLE = isotp_receive(&SHIMS, 0x2a, message_received); 84 | memset(last_message_sent_payload, 0, OUR_MAX_ISO_TP_MESSAGE_SIZE); 85 | memset(last_message_received_payload, 0, OUR_MAX_ISO_TP_MESSAGE_SIZE); 86 | memset(last_can_payload_sent, 0, sizeof(last_can_payload_sent)); 87 | last_message_sent_status = false; 88 | message_was_received = false; 89 | can_frame_was_sent = false; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /tests/test_allocate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern void setup(); 5 | 6 | START_TEST (test_memory_allocation) 7 | { 8 | uint8_t* test_allocation = allocate(4); 9 | ck_assert(test_allocation != NULL); 10 | free_allocated(test_allocation); 11 | } 12 | END_TEST 13 | 14 | Suite* testSuite(void) { 15 | Suite* s = suite_create("iso15765"); 16 | TCase *tc_core = tcase_create("core"); 17 | tcase_add_checked_fixture(tc_core, setup, NULL); 18 | tcase_add_test(tc_core, test_memory_allocation); 19 | suite_add_tcase(s, tc_core); 20 | 21 | return s; 22 | } 23 | 24 | int main(void) { 25 | int numberFailed; 26 | Suite* s = testSuite(); 27 | SRunner *sr = srunner_create(s); 28 | // Don't fork so we can actually use gdb 29 | srunner_set_fork_status(sr, CK_NOFORK); 30 | srunner_run_all(sr, CK_NORMAL); 31 | numberFailed = srunner_ntests_failed(sr); 32 | srunner_free(sr); 33 | return (numberFailed == 0) ? 0 : 1; 34 | } -------------------------------------------------------------------------------- /tests/test_core.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | extern IsoTpShims SHIMS; 9 | 10 | extern void message_sent(const IsoTpMessage* message, const bool success); 11 | 12 | extern uint16_t last_can_frame_sent_arb_id; 13 | extern uint8_t last_can_payload_sent[8]; 14 | extern uint8_t last_can_payload_size; 15 | extern bool can_frame_was_sent; 16 | 17 | extern bool message_was_received; 18 | extern uint16_t last_message_received_arb_id; 19 | extern uint8_t last_message_received_payload[]; 20 | extern uint8_t last_message_received_payload_size; 21 | 22 | extern uint16_t last_message_sent_arb_id; 23 | extern bool last_message_sent_status; 24 | extern uint8_t last_message_sent_payload[]; 25 | extern uint8_t last_message_sent_payload_size; 26 | 27 | extern void setup(); 28 | 29 | START_TEST (test_default_frame_padding_on) 30 | { 31 | ck_assert(SHIMS.frame_padding); 32 | const uint8_t payload[] = {0x12, 0x34}; 33 | uint32_t arbitration_id = 0x2a; 34 | isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), message_sent); 35 | ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); 36 | fail_unless(last_message_sent_status); 37 | ck_assert_int_eq(last_message_sent_payload_size, 2); 38 | ck_assert_int_eq(last_can_payload_size, 8); 39 | 40 | } 41 | END_TEST 42 | 43 | START_TEST (test_disabled_frame_padding) 44 | { 45 | SHIMS.frame_padding = false; 46 | const uint8_t payload[] = {0x12, 0x34}; 47 | uint32_t arbitration_id = 0x2a; 48 | isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), message_sent); 49 | ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); 50 | fail_unless(last_message_sent_status); 51 | ck_assert_int_eq(last_message_sent_payload_size, 2); 52 | ck_assert_int_eq(last_can_payload_size, 3); 53 | 54 | } 55 | END_TEST 56 | 57 | Suite* testSuite(void) { 58 | Suite* s = suite_create("iso15765"); 59 | TCase *tc_core = tcase_create("core"); 60 | tcase_add_checked_fixture(tc_core, setup, NULL); 61 | tcase_add_test(tc_core, test_default_frame_padding_on); 62 | tcase_add_test(tc_core, test_disabled_frame_padding); 63 | suite_add_tcase(s, tc_core); 64 | 65 | return s; 66 | } 67 | 68 | int main(void) { 69 | int numberFailed; 70 | Suite* s = testSuite(); 71 | SRunner *sr = srunner_create(s); 72 | // Don't fork so we can actually use gdb 73 | srunner_set_fork_status(sr, CK_NOFORK); 74 | srunner_run_all(sr, CK_NORMAL); 75 | numberFailed = srunner_ntests_failed(sr); 76 | srunner_free(sr); 77 | return (numberFailed == 0) ? 0 : 1; 78 | } 79 | -------------------------------------------------------------------------------- /tests/test_receive.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Using the the Stitch feature of April 2020 9 | #define MULTIFRAME_STITCH 1 10 | 11 | extern IsoTpShims SHIMS; 12 | extern IsoTpReceiveHandle RECEIVE_HANDLE; 13 | 14 | extern void message_sent(const IsoTpMessage* message, const bool success); 15 | 16 | extern uint16_t last_can_frame_sent_arb_id; 17 | extern uint8_t last_can_payload_sent; 18 | extern uint8_t last_can_payload_size; 19 | extern bool can_frame_was_sent; 20 | 21 | extern bool message_was_received; 22 | extern uint16_t last_message_received_arb_id; 23 | extern uint8_t last_message_received_payload[]; 24 | extern uint8_t last_message_received_payload_size; 25 | 26 | extern uint16_t last_message_sent_arb_id; 27 | extern bool last_message_sent_status; 28 | extern uint8_t last_message_sent_payload[]; 29 | extern uint8_t last_message_sent_payload_size; 30 | 31 | extern void setup(); 32 | 33 | START_TEST (test_receive_empty_can_message) 34 | { 35 | const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0}; 36 | fail_if(RECEIVE_HANDLE.completed); 37 | IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x100, data, 0); 38 | fail_if(message.completed); 39 | fail_if(message_was_received); 40 | } 41 | END_TEST 42 | 43 | START_TEST (test_receive_wrong_id) 44 | { 45 | const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0}; 46 | fail_if(RECEIVE_HANDLE.completed); 47 | IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x100, data, 1); 48 | fail_if(message.completed); 49 | fail_if(message_was_received); 50 | } 51 | END_TEST 52 | 53 | START_TEST (test_receive_bad_pci) 54 | { 55 | // 4 is a reserved number for the PCI field - only 0-3 are allowed 56 | const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0x40}; 57 | IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data, 1); 58 | fail_if(message.completed); 59 | fail_if(message_was_received); 60 | } 61 | END_TEST 62 | 63 | START_TEST (test_receive_single_frame_empty_payload) 64 | { 65 | const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0x00, 0x12, 0x34}; 66 | fail_if(RECEIVE_HANDLE.completed); 67 | IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data, 3); 68 | fail_unless(RECEIVE_HANDLE.completed); 69 | fail_unless(message.completed); 70 | fail_unless(message_was_received); 71 | ck_assert_int_eq(last_message_received_arb_id, 0x2a); 72 | ck_assert_int_eq(last_message_received_payload_size, 0); 73 | } 74 | END_TEST 75 | 76 | START_TEST (test_receive_single_frame) 77 | { 78 | const uint8_t data[CAN_MESSAGE_BYTE_SIZE] = {0x02, 0x12, 0x34}; 79 | IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data, 3); 80 | fail_unless(message.completed); 81 | fail_unless(message_was_received); 82 | ck_assert_int_eq(last_message_received_arb_id, 0x2a); 83 | ck_assert_int_eq(last_message_received_payload_size, 2); 84 | ck_assert_int_eq(last_message_received_payload[0], 0x12); 85 | ck_assert_int_eq(last_message_received_payload[1], 0x34); 86 | } 87 | END_TEST 88 | 89 | 90 | START_TEST (test_receive_multi_frame) 91 | { 92 | const uint8_t data0[CAN_MESSAGE_BYTE_SIZE] = {0x10, 0x14, 0x49, 0x02, 0x01, 0x31, 0x46, 0x4d}; 93 | IsoTpMessage message0 = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data0, 8); 94 | fail_unless(!RECEIVE_HANDLE.completed); 95 | fail_unless(!message0.completed); 96 | fail_unless(!message_was_received); 97 | fail_unless(message0.multi_frame); 98 | //make sure flow control message has been sent. 99 | ck_assert_int_eq(last_can_frame_sent_arb_id, 0x2a - 8); 100 | ck_assert_int_eq(last_can_payload_sent, 0x30); 101 | 102 | const uint8_t data1[CAN_MESSAGE_BYTE_SIZE] = {0x21, 0x43, 0x55, 0x39, 0x4a, 0x39, 0x34, 0x48}; 103 | IsoTpMessage message1 = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data1, 8); 104 | fail_unless(!RECEIVE_HANDLE.completed); 105 | fail_unless(!message1.completed); 106 | fail_unless(!message_was_received); 107 | fail_unless(message1.multi_frame); 108 | 109 | const uint8_t data2[CAN_MESSAGE_BYTE_SIZE] = {0x22, 0x55, 0x41, 0x30, 0x34, 0x35, 0x32, 0x34}; 110 | IsoTpMessage message2 = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data2, 8); 111 | fail_unless(RECEIVE_HANDLE.completed); 112 | fail_unless(message2.completed); 113 | fail_unless(message_was_received); 114 | fail_unless(message2.multi_frame); 115 | 116 | ck_assert_int_eq(last_message_received_arb_id, 0x2a); 117 | #if (MULTIFRAME_STITCH==1) 118 | ck_assert_int_eq(last_message_received_payload_size, 0x0a); 119 | ck_assert_int_eq(last_message_received_payload[0], 0x49); 120 | ck_assert_int_eq(last_message_received_payload[1], 0x02); 121 | ck_assert_int_eq(last_message_received_payload[2], 0x01); 122 | ck_assert_int_eq(last_message_received_payload[3], 0x55); 123 | ck_assert_int_eq(last_message_received_payload[4], 0x41); 124 | ck_assert_int_eq(last_message_received_payload[5], 0x30); 125 | ck_assert_int_eq(last_message_received_payload[6], 0x34); 126 | ck_assert_int_eq(last_message_received_payload[7], 0x35); 127 | ck_assert_int_eq(last_message_received_payload[8], 0x32); 128 | ck_assert_int_eq(last_message_received_payload[9], 0x34); 129 | #else 130 | ck_assert_int_eq(last_message_received_payload_size, 0x14); 131 | ck_assert_int_eq(last_message_received_payload[0], 0x49); 132 | ck_assert_int_eq(last_message_received_payload[1], 0x02); 133 | ck_assert_int_eq(last_message_received_payload[2], 0x01); 134 | ck_assert_int_eq(last_message_received_payload[3], 0x31); 135 | ck_assert_int_eq(last_message_received_payload[4], 0x46); 136 | ck_assert_int_eq(last_message_received_payload[5], 0x4d); 137 | ck_assert_int_eq(last_message_received_payload[6], 0x43); 138 | ck_assert_int_eq(last_message_received_payload[7], 0x55); 139 | ck_assert_int_eq(last_message_received_payload[8], 0x39); 140 | ck_assert_int_eq(last_message_received_payload[9], 0x4a); 141 | ck_assert_int_eq(last_message_received_payload[10], 0x39); 142 | ck_assert_int_eq(last_message_received_payload[11], 0x34); 143 | ck_assert_int_eq(last_message_received_payload[12], 0x48); 144 | ck_assert_int_eq(last_message_received_payload[13], 0x55); 145 | ck_assert_int_eq(last_message_received_payload[14], 0x41); 146 | ck_assert_int_eq(last_message_received_payload[15], 0x30); 147 | ck_assert_int_eq(last_message_received_payload[16], 0x34); 148 | ck_assert_int_eq(last_message_received_payload[17], 0x35); 149 | ck_assert_int_eq(last_message_received_payload[18], 0x32); 150 | ck_assert_int_eq(last_message_received_payload[19], 0x34); 151 | #endif 152 | } 153 | END_TEST 154 | 155 | START_TEST (test_receive_large_multi_frame) 156 | { 157 | const uint8_t data0[CAN_MESSAGE_BYTE_SIZE] = {0x10, 0x80, 0x49, 0x02, 0x01, 0x31, 0x46, 0x4d}; 158 | IsoTpMessage message = isotp_continue_receive(&SHIMS, &RECEIVE_HANDLE, 0x2a, data0, 8); 159 | //Make sure we don't try to receive messages that are too large and don't send flow control. 160 | fail_unless(!can_frame_was_sent); 161 | fail_unless(!RECEIVE_HANDLE.completed); 162 | fail_unless(!message.completed); 163 | fail_unless(!message_was_received); 164 | fail_unless(!message.multi_frame); 165 | } 166 | END_TEST 167 | 168 | Suite* testSuite(void) { 169 | Suite* s = suite_create("iso15765"); 170 | TCase *tc_core = tcase_create("receive"); 171 | tcase_add_checked_fixture(tc_core, setup, NULL); 172 | tcase_add_test(tc_core, test_receive_wrong_id); 173 | tcase_add_test(tc_core, test_receive_bad_pci); 174 | tcase_add_test(tc_core, test_receive_single_frame); 175 | tcase_add_test(tc_core, test_receive_single_frame_empty_payload); 176 | tcase_add_test(tc_core, test_receive_empty_can_message); 177 | tcase_add_test(tc_core, test_receive_multi_frame); 178 | tcase_add_test(tc_core, test_receive_large_multi_frame); 179 | suite_add_tcase(s, tc_core); 180 | 181 | return s; 182 | } 183 | 184 | int main(void) { 185 | int numberFailed; 186 | Suite* s = testSuite(); 187 | SRunner *sr = srunner_create(s); 188 | // Don't fork so we can actually use gdb 189 | srunner_set_fork_status(sr, CK_NOFORK); 190 | srunner_run_all(sr, CK_NORMAL); 191 | numberFailed = srunner_ntests_failed(sr); 192 | srunner_free(sr); 193 | return (numberFailed == 0) ? 0 : 1; 194 | } 195 | -------------------------------------------------------------------------------- /tests/test_send.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | extern IsoTpShims SHIMS; 9 | 10 | extern void message_sent(const IsoTpMessage* message, const bool success); 11 | 12 | extern uint16_t last_can_frame_sent_arb_id; 13 | extern uint8_t last_can_payload_sent[8]; 14 | extern uint8_t last_can_payload_size; 15 | extern bool can_frame_was_sent; 16 | 17 | extern bool message_was_received; 18 | extern uint16_t last_message_received_arb_id; 19 | extern uint8_t last_message_received_payload[]; 20 | extern uint8_t last_message_received_payload_size; 21 | 22 | extern uint16_t last_message_sent_arb_id; 23 | extern bool last_message_sent_status; 24 | extern uint8_t last_message_sent_payload[]; 25 | extern uint8_t last_message_sent_payload_size; 26 | 27 | extern void setup(); 28 | 29 | START_TEST (test_send_empty_payload) 30 | { 31 | SHIMS.frame_padding = false; 32 | uint16_t arbitration_id = 0x2a; 33 | IsoTpSendHandle handle = isotp_send(&SHIMS, arbitration_id, NULL, 0, message_sent); 34 | fail_unless(handle.success); 35 | fail_unless(handle.completed); 36 | ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); 37 | fail_unless(last_message_sent_status); 38 | ck_assert_int_eq(last_message_sent_payload[0], '\0'); 39 | ck_assert_int_eq(last_message_sent_payload_size, 0); 40 | 41 | ck_assert_int_eq(last_can_frame_sent_arb_id, arbitration_id); 42 | fail_unless(can_frame_was_sent); 43 | ck_assert_int_eq(last_can_payload_sent[0], 0x0); 44 | ck_assert_int_eq(last_can_payload_size, 1); 45 | } 46 | END_TEST 47 | 48 | START_TEST (test_send_single_frame) 49 | { 50 | SHIMS.frame_padding = false; 51 | const uint8_t payload[] = {0x12, 0x34}; 52 | uint16_t arbitration_id = 0x2a; 53 | isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), message_sent); 54 | ck_assert_int_eq(last_message_sent_arb_id, arbitration_id); 55 | fail_unless(last_message_sent_status); 56 | ck_assert_int_eq(last_message_sent_payload[0], 0x12); 57 | ck_assert_int_eq(last_message_sent_payload[1], 0x34); 58 | ck_assert_int_eq(last_message_sent_payload_size, 2); 59 | 60 | ck_assert_int_eq(last_can_frame_sent_arb_id, arbitration_id); 61 | fail_unless(can_frame_was_sent); 62 | ck_assert_int_eq(last_can_payload_sent[0], 0x2); 63 | ck_assert_int_eq(last_can_payload_sent[1], 0x12); 64 | ck_assert_int_eq(last_can_payload_sent[2], 0x34); 65 | ck_assert_int_eq(last_can_payload_size, 3); 66 | } 67 | END_TEST 68 | 69 | START_TEST (test_send_multi_frame) 70 | { 71 | const uint8_t payload[] = {0x12, 0x34, 0x56, 0x78, 0x90, 0x01, 0x23, 72 | 0x45, 0x67, 0x89}; 73 | uint16_t arbitration_id = 0x2a; 74 | IsoTpSendHandle handle = isotp_send(&SHIMS, arbitration_id, payload, sizeof(payload), 75 | message_sent); 76 | fail_unless(handle.completed); 77 | fail_if(handle.success); 78 | } 79 | END_TEST 80 | 81 | Suite* testSuite(void) { 82 | Suite* s = suite_create("iso15765"); 83 | TCase *tc_core = tcase_create("send"); 84 | tcase_add_checked_fixture(tc_core, setup, NULL); 85 | tcase_add_test(tc_core, test_send_empty_payload); 86 | tcase_add_test(tc_core, test_send_single_frame); 87 | tcase_add_test(tc_core, test_send_multi_frame); 88 | suite_add_tcase(s, tc_core); 89 | 90 | return s; 91 | } 92 | 93 | int main(void) { 94 | int numberFailed; 95 | Suite* s = testSuite(); 96 | SRunner *sr = srunner_create(s); 97 | // Don't fork so we can actually use gdb 98 | srunner_set_fork_status(sr, CK_NOFORK); 99 | srunner_run_all(sr, CK_NORMAL); 100 | numberFailed = srunner_ntests_failed(sr); 101 | srunner_free(sr); 102 | return (numberFailed == 0) ? 0 : 1; 103 | } 104 | --------------------------------------------------------------------------------