From 4e7375321a029a32989bc7546329c385e6edb80f Mon Sep 17 00:00:00 2001 From: Zac Nelson Date: Wed, 14 Sep 2016 09:52:13 -0700 Subject: [PATCH] Add support for multi-frame responses (#4) * first test with multi-frame. storing data in handler. still need to free memory since using malloc. * hardcode the IsoTpMessage payload size. copy the full multi-frame response into a complete isotpMessage once all frames have been received. otherwise, the returned isotpMessage is not complete. * remove unnecessary debug statements. * do not receive multi-frame message if it is larger than the payload buffer size. * update changelog with multi-frame support * fix the changelog version for proper semantic versioning. oops. * move pointer intialization for testing purposes. * add tests for multi-frame. * try latest version of check * fix syntax for version install. * fix tests. no longer check for malloc pointer (not supported in Travis version of check.h - just test that flow control message has not been sent. * add IsoTpMessage field for multi_frame. Need this upstream for doing timeout updates. * add test multi_frame field. --- CHANGELOG.mkd | 5 ++ src/isotp/isotp_types.h | 16 +++++-- src/isotp/receive.c | 101 ++++++++++++++++++++++++++++++++++++---- tests/test_receive.c | 68 +++++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.mkd b/CHANGELOG.mkd index e709094..e6fd5cc 100644 --- a/CHANGELOG.mkd +++ b/CHANGELOG.mkd @@ -1,5 +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/src/isotp/isotp_types.h b/src/isotp/isotp_types.h index 9d05980..6ae3a79 100644 --- a/src/isotp/isotp_types.h +++ b/src/isotp/isotp_types.h @@ -9,8 +9,14 @@ #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 - since we only handle single frame messages, 8 bytes is plenty. -#define OUR_MAX_ISO_TP_MESSAGE_SIZE 8 +// here - for most multi-frame use cases, 256 bytes is plenty. +#define OUR_MAX_ISO_TP_MESSAGE_SIZE 256 + +/* 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. @@ -30,8 +36,9 @@ extern "C" { * 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 and you should not consider the other data - * to be valid. + * 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! @@ -42,6 +49,7 @@ typedef struct { 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 diff --git a/src/isotp/receive.c b/src/isotp/receive.c index 2dc380b..35b7a2a 100644 --- a/src/isotp/receive.c +++ b/src/isotp/receive.c @@ -1,6 +1,10 @@ #include +#include #include #include +#include + +#define ARBITRATION_ID_OFFSET 0x8 static void isotp_complete_receive(IsoTpReceiveHandle* handle, IsoTpMessage* message) { if(handle->message_received_callback != NULL) { @@ -13,6 +17,26 @@ bool isotp_handle_single_frame(IsoTpReceiveHandle* handle, IsoTpMessage* 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 = { @@ -31,6 +55,7 @@ IsoTpMessage isotp_continue_receive(IsoTpShims* shims, IsoTpMessage message = { arbitration_id: arbitration_id, completed: false, + multi_frame: false, payload: {0}, size: 0 }; @@ -53,30 +78,88 @@ IsoTpMessage isotp_continue_receive(IsoTpShims* shims, IsoTpProtocolControlInformation pci = (IsoTpProtocolControlInformation) get_nibble(data, size, 0); - uint8_t payload_length = get_nibble(data, size, 1); - uint8_t payload[payload_length]; - if(payload_length > 0 && size > 0) { - memcpy(payload, &data[1], payload_length); - } - // 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, payload, payload_length); + 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: - shims->log("Only single frame messages are supported"); break; } return message; diff --git a/tests/test_receive.c b/tests/test_receive.c index 1452ed3..3c6a652 100644 --- a/tests/test_receive.c +++ b/tests/test_receive.c @@ -8,6 +8,8 @@ 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; @@ -81,6 +83,70 @@ START_TEST (test_receive_single_frame) } 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] = {0x11, 0x01, 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"); @@ -90,6 +156,8 @@ Suite* testSuite(void) { 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;