diff --git a/AUTHORS.txt b/AUTHORS.txt index 2349f5d..a533b56 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,3 +1,4 @@ Nick James Kirkby Mark Corbin Jens Fernández Mühlke +Zhifa Lee diff --git a/README.md b/README.md index 0f35866..15867df 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ iso14229 is designed to build on any platform. | 0x35 | request upload | ✅ | | 0x36 | transfer data | ✅ | | 0x37 | request transfer exit | ✅ | -| 0x38 | request file transfer | ❌ | +| 0x38 | request file transfer | ✅ | | 0x3D | write memory by address | ❌ | | 0x3E | tester present | ✅ | | 0x83 | access timing parameter | ❌ | @@ -312,6 +312,34 @@ typedef struct { | `0x31` | `kRequestOutOfRange` | `data` contents invalid, length incorrect | | `0x72` | `kGeneralProgrammingFailure` | finalizing the data transfer failed | +### `UDS_SRV_EVT_RequestFileTransfer` (0x38) + +#### Arguments + +```c +typedef struct { + const uint8_t modeOfOperation; /*! requested specifier for operation mode */ + const uint16_t filePathLen; /*! request data length */ + const uint8_t *filePath; /*! requested file path and name */ + const uint8_t dataFormatIdentifier; /*! optional specifier for format of data */ + const size_t fileSizeUnCompressed; /*! optional file size */ + const size_t fileSizeCompressed; /*! optional file size */ + uint16_t maxNumberOfBlockLength; /*! optional response: inform client how many data bytes to + send in each `TransferData` request */ +} UDSRequestFileTransferArgs_t; +``` + +#### Supported Responses + +| Value | enum | Meaning | +| - | - | - | +| `0x00` | `kPositiveResponse` | Request accepted | +| `0x13` | `kIncorrectMessageLengthOrInvalidFormat` | Length of the message is wrong | +| `0x22` | `kConditionsNotCorrect` | Downloading or uploading data is ongoing or other conditions to be able to execute this service are not met | +| `0x31` | `kRequestOutOfRange` | `data` contents invalid, length incorrect | +| `0x33` | `kSecurityAccessDenied` | The server is secure | +| `0x70` | `kUploadDownloadNotAccepted` | An attempt to download to a server's memory cannot be accomplished due to some fault conditions | + ## Examples [examples/README.md](examples/README.md) diff --git a/src/client.c b/src/client.c index 8c2dbd2..ba8f804 100644 --- a/src/client.c +++ b/src/client.c @@ -537,6 +537,52 @@ UDSErr_t UDSSendRequestTransferExit(UDSClient_t *client) { return _SendRequest(client); } +UDSErr_t UDSSendRequestFileTransfer(UDSClient_t *client, enum FileOperationMode mode, const char *filePath, + uint8_t dataFormatIdentifier, uint8_t fileSizeParameterLength, + size_t fileSizeUncompressed, size_t fileSizeCompressed){ + UDSErr_t err = PreRequestCheck(client); + if (err) { + return err; + } + uint16_t filePathLen = strlen(filePath); + if (filePathLen < 1)return UDS_ERR; + + uint8_t fileSizeBytes = 0; + if ((mode == kAddFile) || (mode == kReplaceFile)){ + fileSizeBytes = fileSizeParameterLength; + } + size_t bufSize = 5 + filePathLen + fileSizeBytes + fileSizeBytes; + if ((mode == kAddFile) || (mode == kReplaceFile) || (mode == kReadFile)){ + bufSize += 1; + } + if (client->send_buf_size < bufSize)return UDS_ERR_BUFSIZ; + + client->send_buf[0] = kSID_REQUEST_FILE_TRANSFER; + client->send_buf[1] = mode; + client->send_buf[2] = (filePathLen >> 8) & 0xFF; + client->send_buf[3] = filePathLen & 0xFF; + memcpy(&client->send_buf[4], filePath, filePathLen); + if ((mode == kAddFile) || (mode == kReplaceFile) || (mode == kReadFile)){ + client->send_buf[4 + filePathLen] = dataFormatIdentifier; + } + if ((mode == kAddFile) || (mode == kReplaceFile)){ + client->send_buf[5 + filePathLen] = fileSizeParameterLength; + uint8_t *ptr = &client->send_buf[6 + filePathLen]; + for (int i = fileSizeParameterLength - 1; i >= 0; i--) { + *ptr = (fileSizeUncompressed & (0xFF << (8 * i))) >> (8 * i); + ptr++; + } + + for (int i = fileSizeParameterLength - 1; i >= 0; i--) { + *ptr = (fileSizeCompressed & (0xFF << (8 * i))) >> (8 * i); + ptr++; + } + } + + client->send_size = bufSize; + return _SendRequest(client); +} + /** * @brief * diff --git a/src/client.h b/src/client.h index 144948e..f88ac10 100644 --- a/src/client.h +++ b/src/client.h @@ -112,6 +112,10 @@ UDSErr_t UDSSendTransferDataStream(UDSClient_t *client, uint8_t blockSequenceCou const uint16_t blockLength, FILE *fd); UDSErr_t UDSSendRequestTransferExit(UDSClient_t *client); +UDSErr_t UDSSendRequestFileTransfer(UDSClient_t *client, enum FileOperationMode mode, const char *filePath, + uint8_t dataFormatIdentifier, uint8_t fileSizeParameterLength, + size_t fileSizeUncompressed, size_t fileSizeCompressed); + UDSErr_t UDSCtrlDTCSetting(UDSClient_t *client, uint8_t dtcSettingType, uint8_t *dtcSettingControlOptionRecord, uint16_t len); UDSErr_t UDSUnpackRDBIResponse(const uint8_t *buf, size_t buf_len, uint16_t did, uint8_t *data, diff --git a/src/server.c b/src/server.c index e7fe92b..71ba2fe 100644 --- a/src/server.c +++ b/src/server.c @@ -643,6 +643,77 @@ static uint8_t _0x37_RequestTransferExit(UDSServer_t *srv, UDSReq_t *r) { return NegativeResponse(r, err); } } +static uint8_t _0x38_RequestFileTransfer(UDSServer_t *srv, UDSReq_t *r) { + uint8_t err = kPositiveResponse; + + if (srv->xferIsActive) { + return NegativeResponse(r, kConditionsNotCorrect); + } + if (r->recv_len < UDS_0X38_REQ_BASE_LEN) { + return NegativeResponse(r, kIncorrectMessageLengthOrInvalidFormat); + } + + uint8_t operation = r->recv_buf[1]; + uint16_t file_path_len = ((uint16_t)r->recv_buf[2] << 8) + r->recv_buf[3]; + uint8_t file_mode = 0; + if ((operation == kAddFile) || (operation == kReplaceFile) || (operation == kReadFile)){ + file_mode = r->recv_buf[UDS_0X38_REQ_BASE_LEN + file_path_len]; + } + size_t file_size_uncompressed = 0; + size_t file_size_compressed = 0; + if ((operation == kAddFile) || (operation == kReplaceFile)){ + size_t size = r->recv_buf[UDS_0X38_REQ_BASE_LEN + file_path_len + 1]; + if (size > sizeof(size_t)){ + return NegativeResponse(r, kRequestOutOfRange); + } + for (size_t byteIdx = 0; byteIdx < size; byteIdx++) { + size_t byte = r->recv_buf[UDS_0X38_REQ_BASE_LEN + file_path_len + 2 + byteIdx]; + uint8_t shiftBytes = size - 1 - byteIdx; + file_size_uncompressed |= byte << (8 * shiftBytes); + } + for (size_t byteIdx = 0; byteIdx < size; byteIdx++) { + size_t byte = r->recv_buf[UDS_0X38_REQ_BASE_LEN + file_path_len + 2 + size + byteIdx]; + uint8_t shiftBytes = size - 1 - byteIdx; + file_size_compressed |= byte << (8 * shiftBytes); + } + } + UDSRequestFileTransferArgs_t args = { + .modeOfOperation = operation, + .filePathLen = file_path_len, + .filePath = &r->recv_buf[UDS_0X38_REQ_BASE_LEN], + .dataFormatIdentifier = file_mode, + .fileSizeUnCompressed = file_size_uncompressed, + .fileSizeCompressed = file_size_compressed, + }; + + err = EmitEvent(srv, UDS_SRV_EVT_RequestFileTransfer, &args); + + if (kPositiveResponse != err) { + return NegativeResponse(r, err); + } + + ResetTransfer(srv); + srv->xferIsActive = true; + srv->xferTotalBytes = args.fileSizeCompressed; + srv->xferBlockLength = args.maxNumberOfBlockLength; + + if (args.maxNumberOfBlockLength > UDS_TP_MTU) { + args.maxNumberOfBlockLength = UDS_TP_MTU; + } + + r->send_buf[0] = UDS_RESPONSE_SID_OF(kSID_REQUEST_FILE_TRANSFER); + r->send_buf[1] = args.modeOfOperation; + r->send_buf[2] = sizeof(args.maxNumberOfBlockLength); + for (uint8_t idx = 0; idx < sizeof(args.maxNumberOfBlockLength); idx++) { + uint8_t shiftBytes = sizeof(args.maxNumberOfBlockLength) - 1 - idx; + uint8_t byte = args.maxNumberOfBlockLength >> (shiftBytes * 8); + r->send_buf[UDS_0X38_RESP_BASE_LEN + idx] = byte; + } + r->send_buf[UDS_0X38_RESP_BASE_LEN + sizeof(args.maxNumberOfBlockLength)] = args.dataFormatIdentifier; + + r->send_len = UDS_0X38_RESP_BASE_LEN + sizeof(args.maxNumberOfBlockLength) + 1; + return kPositiveResponse; +} static uint8_t _0x3E_TesterPresent(UDSServer_t *srv, UDSReq_t *r) { if ((r->recv_len < UDS_0X3E_REQ_MIN_LEN) || (r->recv_len > UDS_0X3E_REQ_MAX_LEN)) { @@ -721,7 +792,7 @@ static UDSService getServiceForSID(uint8_t sid) { case kSID_REQUEST_TRANSFER_EXIT: return _0x37_RequestTransferExit; case kSID_REQUEST_FILE_TRANSFER: - return NULL; + return _0x38_RequestFileTransfer; case kSID_WRITE_MEMORY_BY_ADDRESS: return NULL; case kSID_TESTER_PRESENT: @@ -791,6 +862,7 @@ static uint8_t evaluateServiceResponse(UDSServer_t *srv, UDSReq_t *r) { case kSID_REQUEST_DOWNLOAD: case kSID_REQUEST_UPLOAD: case kSID_TRANSFER_DATA: + case kSID_REQUEST_FILE_TRANSFER: case kSID_REQUEST_TRANSFER_EXIT: { assert(service); response = service(srv, r); @@ -804,7 +876,6 @@ static uint8_t evaluateServiceResponse(UDSServer_t *srv, UDSReq_t *r) { case kSID_READ_PERIODIC_DATA_BY_IDENTIFIER: case kSID_DYNAMICALLY_DEFINE_DATA_IDENTIFIER: case kSID_INPUT_CONTROL_BY_IDENTIFIER: - case kSID_REQUEST_FILE_TRANSFER: case kSID_WRITE_MEMORY_BY_ADDRESS: case kSID_ACCESS_TIMING_PARAMETER: case kSID_SECURED_DATA_TRANSMISSION: diff --git a/src/server.h b/src/server.h index bff866e..f2c4b9d 100644 --- a/src/server.h +++ b/src/server.h @@ -162,6 +162,17 @@ typedef struct { uint16_t len); /*! function for copying response data (optional) */ } UDSRequestTransferExitArgs_t; +typedef struct { + const uint8_t modeOfOperation; /*! requested specifier for operation mode */ + const uint16_t filePathLen; /*! request data length */ + const uint8_t *filePath; /*! requested file path and name */ + const uint8_t dataFormatIdentifier; /*! optional specifier for format of data */ + const size_t fileSizeUnCompressed; /*! optional file size */ + const size_t fileSizeCompressed; /*! optional file size */ + uint16_t maxNumberOfBlockLength; /*! optional response: inform client how many data bytes to + send in each `TransferData` request */ +} UDSRequestFileTransferArgs_t; + typedef struct { const uint16_t sid; /*! serviceIdentifier */ const uint8_t *optionRecord; /*! optional data */ diff --git a/src/uds.h b/src/uds.h index 141c74e..6f0bb64 100644 --- a/src/uds.h +++ b/src/uds.h @@ -14,6 +14,7 @@ enum UDSServerEvent { UDS_SRV_EVT_RequestUpload, // UDSRequestUploadArgs_t * UDS_SRV_EVT_TransferData, // UDSTransferDataArgs_t * UDS_SRV_EVT_RequestTransferExit, // UDSRequestTransferExitArgs_t * + UDS_SRV_EVT_RequestFileTransfer, // UDSRequestFileTransferArgs_t * UDS_SRV_EVT_SessionTimeout, // NULL UDS_SRV_EVT_DoScheduledReset, // enum UDSEcuResetType * UDS_SRV_EVT_CUSTOM, // UDSCustomArgs_t * @@ -149,6 +150,17 @@ enum RoutineControlType { kRequestRoutineResults = 3, }; +/** + * @addtogroup requestFileTransfer_0x38 + */ +enum FileOperationMode { + kAddFile = 1, + kDeleteFile = 2, + kReplaceFile = 3, + kReadFile = 4, + kReadDir = 5, +}; + /** * @addtogroup controlDTCSetting_0x85 */ @@ -188,6 +200,8 @@ enum DTCSettingType { #define UDS_0X36_RESP_BASE_LEN 2U #define UDS_0X37_REQ_BASE_LEN 1U #define UDS_0X37_RESP_BASE_LEN 1U +#define UDS_0X38_REQ_BASE_LEN 4U +#define UDS_0X38_RESP_BASE_LEN 3U #define UDS_0X3E_REQ_MIN_LEN 2U #define UDS_0X3E_REQ_MAX_LEN 2U #define UDS_0X3E_RESP_LEN 2U diff --git a/test/BUILD b/test/BUILD index c6594fe..c30bb09 100644 --- a/test/BUILD +++ b/test/BUILD @@ -25,6 +25,7 @@ TEST_SRCS = [ "test_client_0x22_RDBI_unpack_response.c", "test_client_0x31_RCRRP.c", "test_client_0x34_request_download.c", + "test_client_0x38_request_file_transfer.c", "test_client_busy.c", "test_client_p2.c", "test_client_suppress_positive_response.c", @@ -36,6 +37,7 @@ TEST_SRCS = [ "test_server_0x27_security_access.c", "test_server_0x31_RCRRP.c", "test_server_0x34.c", + "test_server_0x38_request_file_transfer.c", "test_server_0x3E_suppress_positive_response.c", "test_server_0x83_diagnostic_session_control.c", "test_server_session_timeout.c", diff --git a/test/test_client_0x38_request_file_transfer.c b/test/test_client_0x38_request_file_transfer.c new file mode 100644 index 0000000..74d06ea --- /dev/null +++ b/test/test_client_0x38_request_file_transfer.c @@ -0,0 +1,24 @@ +#include "test/test.h" + +int main() { + UDSClient_t client; + { + ENV_CLIENT_INIT(client); + + EXPECT_OK(UDSSendRequestFileTransfer(&client, kAddFile, "/data/testfile.zip", 0x00, 3, 0x112233, 0x001122)); + + const uint8_t ADDFILE_REQUEST[] = {0x38, 0x01, 0x00, 0x12, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x74, 0x65, + 0x73, 0x74, 0x66, 0x69, 0x6C, 0x65, 0x2E, 0x7A, 0x69, 0x70, 0x00, 0x03, + 0x11, 0x22, 0x33, 0x00, 0x11, 0x22}; + TEST_MEMORY_EQUAL(client.send_buf, ADDFILE_REQUEST, sizeof(ADDFILE_REQUEST)); + } + { + ENV_CLIENT_INIT(client); + + EXPECT_OK(UDSSendRequestFileTransfer(&client, kDeleteFile, "/data/testfile.zip", 0x00, 0, 0, 0)); + + const uint8_t DELETEFILE_REQUEST[] = {0x38, 0x02, 0x00, 0x12, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x74, 0x65, + 0x73, 0x74, 0x66, 0x69, 0x6C, 0x65, 0x2E, 0x7A, 0x69, 0x70}; + TEST_MEMORY_EQUAL(client.send_buf, DELETEFILE_REQUEST, sizeof(DELETEFILE_REQUEST)); + } +} \ No newline at end of file diff --git a/test/test_server_0x38_request_file_transfer.c b/test/test_server_0x38_request_file_transfer.c new file mode 100644 index 0000000..ffad785 --- /dev/null +++ b/test/test_server_0x38_request_file_transfer.c @@ -0,0 +1,81 @@ +#include "test/test.h" + +uint8_t fn_addfile(UDSServer_t *srv, UDSServerEvent_t ev, const void *arg) { + TEST_INT_EQUAL(ev, UDS_SRV_EVT_RequestFileTransfer); + UDSRequestFileTransferArgs_t *r = (UDSRequestFileTransferArgs_t *)arg; + TEST_INT_EQUAL(0x01, r->modeOfOperation); + TEST_INT_EQUAL(18, r->filePathLen); + TEST_MEMORY_EQUAL((void *)"/data/testfile.zip", r->filePath, r->filePathLen); + TEST_INT_EQUAL(0x00, r->dataFormatIdentifier); + TEST_INT_EQUAL(0x112233, r->fileSizeUnCompressed); + TEST_INT_EQUAL(0x001122, r->fileSizeCompressed); + r->maxNumberOfBlockLength = 0x0081; + return kPositiveResponse; +} + +uint8_t fn_delfile(UDSServer_t *srv, UDSServerEvent_t ev, const void *arg) { + TEST_INT_EQUAL(ev, UDS_SRV_EVT_RequestFileTransfer); + UDSRequestFileTransferArgs_t *r = (UDSRequestFileTransferArgs_t *)arg; + TEST_INT_EQUAL(0x02, r->modeOfOperation); + TEST_INT_EQUAL(18, r->filePathLen); + TEST_MEMORY_EQUAL((void *)"/data/testfile.zip", r->filePath, r->filePathLen); + return kPositiveResponse; +} + +int main() { + { // case 0: No handler + UDSTpHandle_t *mock_client = ENV_TpNew("client"); + UDSServer_t srv; + ENV_SERVER_INIT(srv); + + // when no handler function is installed, sending this request to the server + uint8_t ADDFILE_REQUEST[] = {0x38, 0x01, 0x00, 0x12, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x74, 0x65, + 0x73, 0x74, 0x66, 0x69, 0x6C, 0x65, 0x2E, 0x7A, 0x69, 0x70, 0x00, 0x03, + 0x11, 0x22, 0x33, 0x00, 0x11, 0x22}; + UDSTpSend(mock_client, ADDFILE_REQUEST, sizeof(ADDFILE_REQUEST), NULL); + + // should return a kServiceNotSupported response + uint8_t RESP[] = {0x7F, 0x38, 0x11}; + EXPECT_IN_APPROX_MS(UDSTpGetRecvLen(mock_client) > 0, srv.p2_ms); + TEST_MEMORY_EQUAL(UDSTpGetRecvBuf(mock_client, NULL), RESP, sizeof(RESP)); + TPMockReset(); + } + + { // case 1: add file + UDSTpHandle_t *mock_client = ENV_TpNew("client"); + UDSServer_t srv; + ENV_SERVER_INIT(srv); + // when a handler is installed that implements UDS-1:2013 Table 435 + srv.fn = fn_addfile; + + // sending this request to the server + uint8_t ADDFILE_REQUEST[] = {0x38, 0x01, 0x00, 0x12, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x74, 0x65, + 0x73, 0x74, 0x66, 0x69, 0x6C, 0x65, 0x2E, 0x7A, 0x69, 0x70, 0x00, 0x03, + 0x11, 0x22, 0x33, 0x00, 0x11, 0x22}; + UDSTpSend(mock_client, ADDFILE_REQUEST, sizeof(ADDFILE_REQUEST), NULL); + + // should receive a positive response matching UDS-1:2013 Table 435 + uint8_t RESP[] = {0x78, 0x01, 0x02, 0x00, 0x81}; + EXPECT_IN_APPROX_MS(UDSTpGetRecvLen(mock_client) > 0, srv.p2_ms); + TEST_MEMORY_EQUAL(UDSTpGetRecvBuf(mock_client, NULL), RESP, sizeof(RESP)); + TPMockReset(); + } + { // case 2: delete file + UDSTpHandle_t *mock_client = ENV_TpNew("client"); + UDSServer_t srv; + ENV_SERVER_INIT(srv); + // when a handler is installed that implements UDS-1:2013 Table 435 + srv.fn = fn_delfile; + + // sending this request to the server + const uint8_t DELETEFILE_REQUEST[] = {0x38, 0x02, 0x00, 0x12, 0x2F, 0x64, 0x61, 0x74, 0x61, 0x2F, 0x74, 0x65, + 0x73, 0x74, 0x66, 0x69, 0x6C, 0x65, 0x2E, 0x7A, 0x69, 0x70}; + UDSTpSend(mock_client, DELETEFILE_REQUEST, sizeof(DELETEFILE_REQUEST), NULL); + + // should receive a positive response matching UDS-1:2013 Table 435 + uint8_t RESP[] = {0x78, 0x02}; + EXPECT_IN_APPROX_MS(UDSTpGetRecvLen(mock_client) > 0, srv.p2_ms); + TEST_MEMORY_EQUAL(UDSTpGetRecvBuf(mock_client, NULL), RESP, sizeof(RESP)); + TPMockReset(); + } +}