diff options
Diffstat (limited to 'libs/isotp-c/src')
-rw-r--r-- | libs/isotp-c/src/isotp/isotp.c | 32 | ||||
-rw-r--r-- | libs/isotp-c/src/isotp/isotp.h | 41 | ||||
-rw-r--r-- | libs/isotp-c/src/isotp/isotp_types.h | 144 | ||||
-rw-r--r-- | libs/isotp-c/src/isotp/receive.c | 166 | ||||
-rw-r--r-- | libs/isotp-c/src/isotp/receive.h | 86 | ||||
-rw-r--r-- | libs/isotp-c/src/isotp/send.c | 89 | ||||
-rw-r--r-- | libs/isotp-c/src/isotp/send.h | 86 |
7 files changed, 644 insertions, 0 deletions
diff --git a/libs/isotp-c/src/isotp/isotp.c b/libs/isotp-c/src/isotp/isotp.c new file mode 100644 index 00000000..ce87f1bd --- /dev/null +++ b/libs/isotp-c/src/isotp/isotp.c @@ -0,0 +1,32 @@ +#include <isotp/isotp.h> +#include <bitfield/bitfield.h> +#include <inttypes.h> + +/* void isotp_set_timeout(IsoTpHandler* handler, uint16_t timeout_ms) { */ + /* handler->timeout_ms = timeout_ms; */ +/* } */ + +IsoTpShims isotp_init_shims(LogShim log, SendCanMessageShim send_can_message, + SetTimerShim set_timer) { + IsoTpShims shims = { + log: log, + send_can_message: send_can_message, + set_timer: set_timer, + frame_padding: ISO_TP_DEFAULT_FRAME_PADDING_STATUS + }; + return shims; +} + +void isotp_message_to_string(const IsoTpMessage* message, char* destination, + size_t destination_length) { + snprintf(destination, destination_length, "ID: 0x%" SCNd32 ", Payload: 0x%02x%02x%02x%02x%02x%02x%02x%02x", + message->arbitration_id, + message->payload[0], + message->payload[1], + message->payload[2], + message->payload[3], + message->payload[4], + message->payload[5], + message->payload[6], + message->payload[7]); +} diff --git a/libs/isotp-c/src/isotp/isotp.h b/libs/isotp-c/src/isotp/isotp.h new file mode 100644 index 00000000..3a3658c7 --- /dev/null +++ b/libs/isotp-c/src/isotp/isotp.h @@ -0,0 +1,41 @@ +#ifndef __ISOTP_H__ +#define __ISOTP_H__ + +#include <isotp/isotp_types.h> +#include <isotp/send.h> +#include <isotp/receive.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: Initialize an IsoTpShims with the given callback functions. + * + * If any callbacks are not to be used, set them to NULL. For documentation of + * the function type signatures, see isotp_types.h. This struct is a handy + * encapsulation used to pass the shims around to the various isotp_* functions. + * + * Returns a struct with the fields initailized to the callbacks. + */ +IsoTpShims isotp_init_shims(LogShim log, + SendCanMessageShim send_can_message, + SetTimerShim set_timer); + +/* Public: Render an IsoTpMessage as a string into the given buffer. + * + * message - the message to convert to a string, for debug logging. + * destination - the target string buffer. + * destination_length - the size of the destination buffer, i.e. the max size + * for the rendered string. + */ +void isotp_message_to_string(const IsoTpMessage* message, char* destination, + size_t destination_length); + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_H__ diff --git a/libs/isotp-c/src/isotp/isotp_types.h b/libs/isotp-c/src/isotp/isotp_types.h new file mode 100644 index 00000000..3b7fd26d --- /dev/null +++ b/libs/isotp-c/src/isotp/isotp_types.h @@ -0,0 +1,144 @@ +#ifndef __ISOTP_TYPES__ +#define __ISOTP_TYPES__ + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> + +#define CAN_MESSAGE_BYTE_SIZE 8 +#define MAX_ISO_TP_MESSAGE_SIZE 4096 +// TODO we want to avoid malloc, and we can't be allocated 4K on the stack for +// each IsoTpMessage, so for now we're setting an artificial max message size +// here - for most multi-frame use cases, 256 bytes is plenty. +#define OUR_MAX_ISO_TP_MESSAGE_SIZE 127 + +/* Private: IsoTp nibble specifics for PCI and Payload. + */ +#define PCI_NIBBLE_INDEX 0 +#define PAYLOAD_LENGTH_NIBBLE_INDEX 1 +#define PAYLOAD_BYTE_INDEX 1 + +/* Private: The default timeout to use when waiting for a response during a + * multi-frame send or receive. + */ +#define ISO_TP_DEFAULT_RESPONSE_TIMEOUT 100 + +/* Private: Determines if by default, padding is added to ISO-TP message frames. + */ +#define ISO_TP_DEFAULT_FRAME_PADDING_STATUS true + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: A container for a sent or received ISO-TP message. + * + * completed - An IsoTpMessage is the return value from a few functions - this + * attribute will be true if the message is actually completely received. + * If the function returns but is only partially through receiving the + * message, this will be false, the multi_frame attribute will be true, + * and you should not consider the other data to be valid. + * multi_frame - Designates the message is being built with multi-frame. + * arbitration_id - The arbitration ID of the message. + * payload - The optional payload of the message - don't forget to check the + * size! + * size - The size of the payload. The size will be 0 if there is no payload. + */ +typedef struct { + const uint32_t arbitration_id; + uint8_t payload[OUR_MAX_ISO_TP_MESSAGE_SIZE]; + uint16_t size; + bool completed; + bool multi_frame; +} IsoTpMessage; + +/* Public: The type signature for an optional logging function, if the user + * wishes to provide one. It should print, store or otherwise display the + * message. + * + * message - A format string to log using the given parameters. + * ... (vargs) - the parameters for the format string. + */ +typedef void (*LogShim)(const char* message, ...); +/* Public: The type signature for a function to send a single CAN message. + * + * arbitration_id - The arbitration ID of the message. + * data - The data payload for the message. NULL is valid if size is also 0. + * size - The size of the data payload, in bytes. + * + * Returns true if the CAN message was sent successfully. + */ +typedef bool (*SendCanMessageShim)(const uint32_t arbitration_id, + const uint8_t* data, const uint8_t size); + +/* Public: The type signature for a... TODO, not used yet. + */ +typedef bool (*SetTimerShim)(uint16_t time_ms, void (*callback)); + +/* Public: The signature for a function to be called when an ISO-TP message has + * been completely received. + * + * message - The received message. + */ +typedef void (*IsoTpMessageReceivedHandler)(const IsoTpMessage* message); + +/* Public: the signature for a function to be called when an ISO-TP message has + * been completely sent, or had a fatal error during sending. + * + * message - The sent message. + * success - True if the message was sent successfully. + */ +typedef void (*IsoTpMessageSentHandler)(const IsoTpMessage* message, + const bool success); + +/* Public: The signature for a function to be called when a CAN frame has been + * sent as as part of sending or receive an ISO-TP message. + * + * This is really only useful for debugging the library itself. + * + * message - The ISO-TP message that generated this CAN frame. + */ +typedef void (*IsoTpCanFrameSentHandler)(const IsoTpMessage* message); + +/* Public: A container for the 3 shim functions used by the library to interact + * with the wider system. + * + * Use the isotp_init_shims(...) function to create an instance of this struct. + * + * By default, all CAN frames sent from this device in the process of an ISO-TP + * message are padded out to a complete 8 byte frame. This is often required by + * ECUs. To disable this feature, change the 'frame_padding' field to false on + * the IsoTpShims object returned from isotp_init_shims(...). + * + * frame_padding - true if outgoing CAN frames should be padded to a full 8 + * bytes. + */ +typedef struct { + LogShim log; + SendCanMessageShim send_can_message; + SetTimerShim set_timer; + bool frame_padding; +} IsoTpShims; + +/* Private: PCI types, for identifying each frame of an ISO-TP message. + */ +typedef enum { + PCI_SINGLE = 0x0, + PCI_FIRST_FRAME = 0x1, + PCI_CONSECUTIVE_FRAME = 0x2, + PCI_FLOW_CONTROL_FRAME = 0x3 +} IsoTpProtocolControlInformation; + +/* Private: PCI flow control identifiers. + */ +typedef enum { + PCI_FLOW_STATUS_CONTINUE = 0x0, + PCI_FLOW_STATUS_WAIT = 0x1, + PCI_FLOW_STATUS_OVERFLOW = 0x2 +} IsoTpFlowStatus; + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_TYPES__ diff --git a/libs/isotp-c/src/isotp/receive.c b/libs/isotp-c/src/isotp/receive.c new file mode 100644 index 00000000..35b7a2a6 --- /dev/null +++ b/libs/isotp-c/src/isotp/receive.c @@ -0,0 +1,166 @@ +#include <isotp/receive.h> +#include <isotp/send.h> +#include <bitfield/bitfield.h> +#include <string.h> +#include <stdlib.h> + +#define ARBITRATION_ID_OFFSET 0x8 + +static void isotp_complete_receive(IsoTpReceiveHandle* handle, IsoTpMessage* message) { + if(handle->message_received_callback != NULL) { + handle->message_received_callback(message); + } +} + +bool isotp_handle_single_frame(IsoTpReceiveHandle* handle, IsoTpMessage* message) { + isotp_complete_receive(handle, message); + return true; +} + +bool isotp_handle_multi_frame(IsoTpReceiveHandle* handle, IsoTpMessage* message) { + // call this once all consecutive frames have been received + isotp_complete_receive(handle, message); + return true; +} + +bool isotp_send_flow_control_frame(IsoTpShims* shims, IsoTpMessage* message) { + uint8_t can_data[CAN_MESSAGE_BYTE_SIZE] = {0}; + + if(!set_nibble(PCI_NIBBLE_INDEX, PCI_FLOW_CONTROL_FRAME, can_data, sizeof(can_data))) { + shims->log("Unable to set PCI in CAN data"); + return false; + } + + shims->send_can_message(message->arbitration_id - ARBITRATION_ID_OFFSET, can_data, + shims->frame_padding ? 8 : 1 + message->size); + return true; +} + + +IsoTpReceiveHandle isotp_receive(IsoTpShims* shims, + const uint32_t arbitration_id, IsoTpMessageReceivedHandler callback) { + IsoTpReceiveHandle handle = { + success: false, + completed: false, + arbitration_id: arbitration_id, + message_received_callback: callback + }; + + return handle; +} + +IsoTpMessage isotp_continue_receive(IsoTpShims* shims, + IsoTpReceiveHandle* handle, const uint32_t arbitration_id, + const uint8_t data[], const uint8_t size) { + IsoTpMessage message = { + arbitration_id: arbitration_id, + completed: false, + multi_frame: false, + payload: {0}, + size: 0 + }; + + if(size < 1) { + return message; + } + + if(handle->arbitration_id != arbitration_id) { + if(shims->log != NULL) { + // You may turn this on for debugging, but in normal operation it's + // very noisy if you are passing all received CAN messages to this + // handler. + /* shims->log("The arb ID 0x%x doesn't match the expected rx ID 0x%x", */ + /* arbitration_id, handle->arbitration_id); */ + } + return message; + } + + IsoTpProtocolControlInformation pci = (IsoTpProtocolControlInformation) + get_nibble(data, size, 0); + + // TODO this is set up to handle rx a response with a payload, but not to + // handle flow control responses for multi frame messages that we're in the + // process of sending + + switch(pci) { + case PCI_SINGLE: { + uint8_t payload_length = get_nibble(data, size, 1); + + if(payload_length > 0) { + memcpy(message.payload, &data[1], payload_length); + } + + message.size = payload_length; + message.completed = true; + handle->success = true; + handle->completed = true; + isotp_handle_single_frame(handle, &message); + break; + } + //If multi-frame, then the payload length is contained in the 12 + //bits following the first nibble of Byte 0. + case PCI_FIRST_FRAME: { + uint16_t payload_length = (get_nibble(data, size, 1) << 8) + get_byte(data, size, 1); + + if(payload_length > OUR_MAX_ISO_TP_MESSAGE_SIZE) { + shims->log("Multi-frame response too large for receive buffer."); + break; + } + + //Need to allocate memory for the combination of multi-frame + //messages. That way we don't have to allocate 4k of memory + //for each multi-frame response. + uint8_t* combined_payload = NULL; + combined_payload = (uint8_t*)malloc(sizeof(uint8_t)*payload_length); + + if(combined_payload == NULL) { + shims->log("Unable to allocate memory for multi-frame response."); + break; + } + + memcpy(combined_payload, &data[2], CAN_MESSAGE_BYTE_SIZE - 2); + handle->receive_buffer = combined_payload; + handle->received_buffer_size = CAN_MESSAGE_BYTE_SIZE - 2; + handle->incoming_message_size = payload_length; + + message.multi_frame = true; + handle->success = false; + handle->completed = false; + isotp_send_flow_control_frame(shims, &message); + break; + } + case PCI_CONSECUTIVE_FRAME: { + uint8_t start_index = handle->received_buffer_size; + uint8_t remaining_bytes = handle->incoming_message_size - start_index; + message.multi_frame = true; + + if(remaining_bytes > 7) { + memcpy(&handle->receive_buffer[start_index], &data[1], CAN_MESSAGE_BYTE_SIZE - 1); + handle->received_buffer_size = start_index + 7; + } else { + memcpy(&handle->receive_buffer[start_index], &data[1], remaining_bytes); + handle->received_buffer_size = start_index + remaining_bytes; + + if(handle->received_buffer_size != handle->incoming_message_size){ + free(handle->receive_buffer); + handle->success = false; + shims->log("Error capturing all bytes of multi-frame. Freeing memory."); + } else { + memcpy(message.payload,&handle->receive_buffer[0],handle->incoming_message_size); + free(handle->receive_buffer); + message.size = handle->incoming_message_size; + message.completed = true; + shims->log("Successfully captured all of multi-frame. Freeing memory."); + + handle->success = true; + handle->completed = true; + isotp_handle_multi_frame(handle, &message); + } + } + break; + } + default: + break; + } + return message; +} diff --git a/libs/isotp-c/src/isotp/receive.h b/libs/isotp-c/src/isotp/receive.h new file mode 100644 index 00000000..6788914a --- /dev/null +++ b/libs/isotp-c/src/isotp/receive.h @@ -0,0 +1,86 @@ +#ifndef __ISOTP_RECEIVE_H__ +#define __ISOTP_RECEIVE_H__ + +#include <isotp/isotp.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: A handle for beginning and continuing receiving a single ISO-TP + * message - both single and multi-frame. + * + * Since an ISO-TP message may contain multiple frames, we need to keep a handle + * around while waiting for subsequent CAN messages to complete the message. + * This struct encapsulates the local state required. + * + * completed - True if the received message request is completely finished. + * success - True if the message request was successful. The value if this field + * isn't valid if 'completed' isn't true. + */ +typedef struct { + bool completed; + bool success; + + // Private + uint32_t arbitration_id; + IsoTpMessageReceivedHandler message_received_callback; + uint16_t timeout_ms; + // timeout_ms: ISO_TP_DEFAULT_RESPONSE_TIMEOUT, + uint8_t* receive_buffer; + uint16_t received_buffer_size; + uint16_t incoming_message_size; + // TODO timer callback for multi frame +} IsoTpReceiveHandle; + +/* Public: Initiate receiving a single ISO-TP message on a particular + * arbitration ID. + * + * Note that no actual CAN data has been received at this point - this just sets + * up a handle to be used when new CAN messages to arrive, so they can be parsed + * as potential single or multi-frame ISO-TP messages. + * + * shims - Low-level shims required to send and receive CAN messages, etc. + * arbitration_id - The arbitration ID to receive the message on. + * callback - an optional function to be called when the message is completely + * received (use NULL if no callback required). + * + * Returns a handle to be used with isotp_continue_receive when a new CAN frame + * arrives. The 'completed' field in the returned IsoTpReceiveHandle will be true + * when the message is completely sent. + */ +IsoTpReceiveHandle isotp_receive(IsoTpShims* shims, + const uint32_t arbitration_id, IsoTpMessageReceivedHandler callback); + +/* Public: Continue to receive a an ISO-TP message, based on a freshly + * received CAN message. + * + * For a multi-frame ISO-TP message, this function must be called + * repeatedly whenever a new CAN message is received in order to complete + * receipt. + * + * TODO does this API work for if we wanted to receive an ISO-TP message and + * send our own flow control messages back? + * + * shims - Low-level shims required to send and receive CAN messages, etc. + * handle - An IsoTpReceiveHandle previously returned by isotp_receive(...). + * arbitration_id - The arbitration_id of the received CAN message. + * data - The data of the received CAN message. + * size - The size of the data in the received CAN message. + * + * Returns an IsoTpMessage with the 'completed' field set to true if a message + * was completely received. If 'completed' is false, more CAN frames are + * required to complete the messages, or the arbitration ID didn't match this + * handle. Keep passing the same handle to this function when CAN frames arrive. + */ +IsoTpMessage isotp_continue_receive(IsoTpShims* shims, + IsoTpReceiveHandle* handle, const uint32_t arbitration_id, + const uint8_t data[], const uint8_t size); + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_RECEIVE_H__ diff --git a/libs/isotp-c/src/isotp/send.c b/libs/isotp-c/src/isotp/send.c new file mode 100644 index 00000000..e849bb2c --- /dev/null +++ b/libs/isotp-c/src/isotp/send.c @@ -0,0 +1,89 @@ +#include <isotp/send.h> +#include <bitfield/bitfield.h> +#include <string.h> + +#define PCI_NIBBLE_INDEX 0 +#define PAYLOAD_LENGTH_NIBBLE_INDEX 1 +#define PAYLOAD_BYTE_INDEX 1 + +void isotp_complete_send(IsoTpShims* shims, IsoTpMessage* message, + bool status, IsoTpMessageSentHandler callback) { + if(callback != NULL) { + callback(message, status); + } +} + +IsoTpSendHandle isotp_send_single_frame(IsoTpShims* shims, IsoTpMessage* message, + IsoTpMessageSentHandler callback) { + IsoTpSendHandle handle = { + success: false, + completed: true + }; + + uint8_t can_data[CAN_MESSAGE_BYTE_SIZE] = {0}; + if(!set_nibble(PCI_NIBBLE_INDEX, PCI_SINGLE, can_data, sizeof(can_data))) { + shims->log("Unable to set PCI in CAN data"); + return handle; + } + + if(!set_nibble(PAYLOAD_LENGTH_NIBBLE_INDEX, message->size, can_data, + sizeof(can_data))) { + shims->log("Unable to set payload length in CAN data"); + return handle; + } + + if(message->size > 0) { + memcpy(&can_data[1], message->payload, message->size); + } + + shims->send_can_message(message->arbitration_id, can_data, + shims->frame_padding ? 8 : 1 + message->size); + handle.success = true; + isotp_complete_send(shims, message, true, callback); + return handle; +} + +IsoTpSendHandle isotp_send_multi_frame(IsoTpShims* shims, IsoTpMessage* message, + IsoTpMessageSentHandler callback) { + // TODO make sure to copy message into a local buffer + shims->log("Only single frame messages are supported"); + IsoTpSendHandle handle = { + success: false, + completed: true + }; + // TODO need to set sending and receiving arbitration IDs separately if we + // can't always just add 0x8 (and I think we can't) + return handle; +} + +IsoTpSendHandle isotp_send(IsoTpShims* shims, const uint16_t arbitration_id, + const uint8_t payload[], uint16_t size, + IsoTpMessageSentHandler callback) { + IsoTpMessage message = { + arbitration_id: arbitration_id, + size: size + }; + + memcpy(message.payload, payload, size); + if(size < 8) { + return isotp_send_single_frame(shims, &message, callback); + } else { + return isotp_send_multi_frame(shims, &message, callback); + } +} + +bool isotp_continue_send(IsoTpShims* shims, IsoTpSendHandle* handle, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size) { + // TODO this will need to be tested when we add multi-frame support, + // which is when it'll be necessary to pass in CAN messages to SENDING + // handles. + if(handle->receiving_arbitration_id != arbitration_id) { + if(shims->log != NULL) { + shims->log("The arb ID 0x%x doesn't match the expected tx continuation ID 0x%x", + arbitration_id, handle->receiving_arbitration_id); + } + return false; + } + return false; +} diff --git a/libs/isotp-c/src/isotp/send.h b/libs/isotp-c/src/isotp/send.h new file mode 100644 index 00000000..1af32668 --- /dev/null +++ b/libs/isotp-c/src/isotp/send.h @@ -0,0 +1,86 @@ +#ifndef __ISOTP_SEND_H__ +#define __ISOTP_SEND_H__ + +#include <isotp/isotp.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Public: A handle for beginning and continuing sending a single ISO-TP + * message - both single and multi-frame. + * + * Since an ISO-TP message may contain multiple frames, we need to keep a handle + * around while waiting for flow control messages from the receiver. + * This struct encapsulates the local state required. + * + * completed - True if the message was completely sent, or the send was + * otherwise cancelled. + * success - True if the message send request was successful. The value if this + * field isn't valid if 'completed' isn't true. + */ +typedef struct { + bool completed; + bool success; + + // Private + uint16_t sending_arbitration_id; + uint16_t receiving_arbitration_id; + IsoTpMessageSentHandler message_sent_callback; + IsoTpCanFrameSentHandler can_frame_sent_callback; + // TODO going to need some state here for multi frame messages +} IsoTpSendHandle; + +/* Public: Initiate sending a single ISO-TP message. + * + * If the message fits in a single ISO-TP frame (i.e. the payload isn't more + * than 7 bytes) it will be sent immediately and the returned IsoTpSendHandle's + * 'completed' flag will be true. + * + * For multi-frame messages, see isotp_continue_send(...). + * + * shims - Low-level shims required to send CAN messages, etc. + * arbitration_id - The arbitration ID to send the message on. + * payload - The payload for the message. If no payload, NULL is valid is size + * is also 0. + * size - The size of the payload, or 0 if no payload. + * callback - an optional function to be called when the message is completely + * sent (use NULL if no callback required). + * + * Returns a handle to be used with isotp_continue_send to continue sending + * multi-frame messages. The 'completed' field in the returned IsoTpSendHandle + * will be true when the message is completely sent. + */ +IsoTpSendHandle isotp_send(IsoTpShims* shims, const uint16_t arbitration_id, + const uint8_t payload[], uint16_t size, + IsoTpMessageSentHandler callback); + +/* Public: Continue to send a multi-frame ISO-TP message, based on a freshly + * received CAN message (potentially from the receiver about flow control). + * + * For a multi-frame ISO-TP message, this function must be called + * repeatedly whenever a new CAN message is received in order to complete the + * send. The sender can't just blast everything onto the bus at once - it must + * wait for some response from the receiver to know how much to send at once. + * + * shims - Low-level shims required to send CAN messages, etc. + * handle - An IsoTpSendHandle previously returned by isotp_send(...). + * arbitration_id - The arbitration_id of the received CAN message. + * data - The data of the received CAN message. + * size - The size of the data in the received CAN message. + * + * Returns true if the message was completely sent, or the send was + * otherwise cancelled. Check the 'success' field of the handle to see if + * it was successful. + */ +bool isotp_continue_send(IsoTpShims* shims, IsoTpSendHandle* handle, + const uint16_t arbitration_id, const uint8_t data[], + const uint8_t size); + +#ifdef __cplusplus +} +#endif + +#endif // __ISOTP_SEND_H__ |