From aec4803aa42ccea1865d1f55135e751f17b5fe39 Mon Sep 17 00:00:00 2001 From: w1ne <14119286+w1ne@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:34:41 +0200 Subject: [PATCH 1/2] feat: add shared IO-Link frame helpers --- CMakeLists.txt | 1 + include/iolinki/frame.h | 45 +++++++++++++ src/frame.c | 92 +++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_frame.c | 145 ++++++++++++++++++++++++++++++++++++++++ zephyr/CMakeLists.txt | 1 + 6 files changed, 285 insertions(+) create mode 100644 include/iolinki/frame.h create mode 100644 src/frame.c create mode 100644 tests/test_frame.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dcdd6a..be1741e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(iolinki STATIC src/phy_generic.c src/phy_virtual.c src/crc.c + src/frame.c src/dll.c src/isdu.c src/events.c diff --git a/include/iolinki/frame.h b/include/iolinki/frame.h new file mode 100644 index 0000000..4ad4063 --- /dev/null +++ b/include/iolinki/frame.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2026 Andrii Shylenko + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This file is part of iolinki. + * See LICENSE for details. + */ + +#ifndef IOLINK_FRAME_H +#define IOLINK_FRAME_H + +#include "iolinki/config.h" +#include +#include +#include + +/** + * @file frame.h + * @brief Shared IO-Link frame encoding and decoding helpers. + */ + +typedef struct { + uint8_t status; + bool pd_valid; + bool event_pending; + bool checksum_ok; + uint8_t pd[IOLINK_PD_IN_MAX_SIZE]; + uint8_t pd_len; + uint8_t od[IOLINK_OD_MAX_SIZE]; + uint8_t od_len; +} iolink_frame_operate_response_t; + +int iolink_frame_encode_type0(uint8_t mc, uint8_t* out, size_t out_size); +int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, + uint8_t pd_out_len, + uint8_t od_len, + uint8_t* out, + size_t out_size); +int iolink_frame_decode_operate_response(const uint8_t* frame, + size_t frame_len, + uint8_t pd_in_len, + uint8_t od_len, + iolink_frame_operate_response_t* out); + +#endif /* IOLINK_FRAME_H */ diff --git a/src/frame.c b/src/frame.c new file mode 100644 index 0000000..46f5a76 --- /dev/null +++ b/src/frame.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2026 Andrii Shylenko + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This file is part of iolinki. + * See LICENSE for details. + */ + +#include "iolinki/frame.h" +#include "iolinki/crc.h" +#include "iolinki/protocol.h" +#include + +int iolink_frame_encode_type0(uint8_t mc, uint8_t* out, size_t out_size) +{ + if ((out == NULL) || (out_size < IOLINK_M_SEQ_TYPE0_LEN)) { + return -1; + } + + out[0] = mc; + out[1] = iolink_checksum_ck(mc, 0U); + + return (int) IOLINK_M_SEQ_TYPE0_LEN; +} + +int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, + uint8_t pd_out_len, + uint8_t od_len, + uint8_t* out, + size_t out_size) +{ + size_t pos = 0U; + const size_t frame_len = (size_t) IOLINK_M_SEQ_HEADER_LEN + pd_out_len + od_len + 1U; + + if ((out == NULL) || ((pd_out == NULL) && (pd_out_len > 0U)) || + (pd_out_len > IOLINK_PD_OUT_MAX_SIZE) || (od_len == 0U) || (od_len > IOLINK_OD_MAX_SIZE) || + (out_size < frame_len)) { + return -1; + } + + out[pos++] = 0U; + out[pos++] = 0U; + + if (pd_out_len > 0U) { + memcpy(&out[pos], pd_out, pd_out_len); + pos += pd_out_len; + } + + if (od_len > 0U) { + memset(&out[pos], 0, od_len); + pos += od_len; + } + + out[pos] = iolink_crc6(out, (uint8_t) pos); + + return (int) frame_len; +} + +int iolink_frame_decode_operate_response(const uint8_t* frame, + size_t frame_len, + uint8_t pd_in_len, + uint8_t od_len, + iolink_frame_operate_response_t* out) +{ + size_t pos = 0U; + const size_t expected_len = 1U + pd_in_len + od_len + 1U; + + if ((frame == NULL) || (out == NULL) || (pd_in_len > IOLINK_PD_IN_MAX_SIZE) || + (od_len == 0U) || (od_len > IOLINK_OD_MAX_SIZE) || (frame_len != expected_len) || (frame_len < 2U)) { + return -1; + } + + memset(out, 0, sizeof(*out)); + + out->status = frame[pos++]; + out->pd_valid = ((out->status & IOLINK_OD_STATUS_PD_VALID) != 0U); + out->event_pending = ((out->status & IOLINK_OD_STATUS_EVENT) != 0U); + out->checksum_ok = (iolink_crc6(frame, (uint8_t) (frame_len - 1U)) == frame[frame_len - 1U]); + + if (pd_in_len > 0U) { + memcpy(out->pd, &frame[pos], pd_in_len); + out->pd_len = pd_in_len; + pos += pd_in_len; + } + + if (od_len > 0U) { + memcpy(out->od, &frame[pos], od_len); + out->od_len = od_len; + } + + return 0; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f9975cb..4c7ce82 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,6 +25,7 @@ if(CMOCKA_FOUND) # Initial tests add_iolink_test(test_init test_init.c) add_iolink_test(test_crc test_crc.c) + add_iolink_test(test_frame test_frame.c) add_iolink_test(test_dll test_dll.c) add_iolink_test(test_pd test_pd.c) add_iolink_test(test_isdu test_isdu.c) diff --git a/tests/test_frame.c b/tests/test_frame.c new file mode 100644 index 0000000..37af16d --- /dev/null +++ b/tests/test_frame.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2026 Andrii Shylenko + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This file is part of iolinki. + * See LICENSE for details. + */ + +/** + * @file test_frame.c + * @brief Unit tests for shared IO-Link frame helpers + */ + +#include +#include +#include +#include +#include +#include + +#include "iolinki/frame.h" +#include "iolinki/protocol.h" + +static void test_encode_type0_idle(void** state) +{ + (void) state; + uint8_t frame[2] = {0U}; + + assert_int_equal(iolink_frame_encode_type0(0x00U, frame, sizeof(frame)), 2); + assert_memory_equal(frame, ((const uint8_t[]) {0x00U, 0x24U}), sizeof(frame)); +} + +static void test_encode_type0_transition(void** state) +{ + (void) state; + uint8_t frame[2] = {0U}; + + assert_int_equal(iolink_frame_encode_type0(0x0FU, frame, sizeof(frame)), 2); + assert_memory_equal(frame, ((const uint8_t[]) {0x0FU, 0x0DU}), sizeof(frame)); +} + +static void test_encode_type1_empty_cycle(void** state) +{ + (void) state; + uint8_t frame[4] = {0U}; + + assert_int_equal(iolink_frame_encode_type1_cycle(NULL, 0U, 1U, frame, sizeof(frame)), 4); + assert_memory_equal(frame, ((const uint8_t[]) {0x00U, 0x00U, 0x00U, 0x09U}), sizeof(frame)); +} + +static void test_encode_type0_rejects_undersized_buffer(void** state) +{ + (void) state; + uint8_t frame[1] = {0U}; + + assert_int_equal(iolink_frame_encode_type0(0x00U, frame, sizeof(frame)), -1); +} + +static void test_encode_type1_rejects_zero_od_len(void** state) +{ + (void) state; + uint8_t frame[4] = {0U}; + + assert_int_equal(iolink_frame_encode_type1_cycle(NULL, 0U, 0U, frame, sizeof(frame)), -1); +} + +static void test_encode_type1_rejects_oversized_od_len(void** state) +{ + (void) state; + uint8_t frame[8] = {0U}; + + assert_int_equal(iolink_frame_encode_type1_cycle(NULL, + 0U, + (uint8_t) (IOLINK_OD_MAX_SIZE + 1U), + frame, + sizeof(frame)), + -1); +} + +static void test_encode_type1_allows_max_od_len(void** state) +{ + (void) state; + uint8_t frame[IOLINK_M_SEQ_HEADER_LEN + IOLINK_OD_MAX_SIZE + 1U] = {0U}; + + assert_int_equal(iolink_frame_encode_type1_cycle(NULL, 0U, IOLINK_OD_MAX_SIZE, frame, sizeof(frame)), + (int) sizeof(frame)); +} + +static void test_decode_operate_response_with_pd(void** state) +{ + (void) state; + const uint8_t frame[] = {0x20U, 0xA5U, 0x00U, 0x0DU}; + iolink_frame_operate_response_t resp = {0}; + + assert_int_equal(iolink_frame_decode_operate_response(frame, sizeof(frame), 1U, 1U, &resp), 0); + assert_true(resp.checksum_ok); + assert_true(resp.pd_valid); + assert_false(resp.event_pending); + assert_int_equal(resp.pd_len, 1U); + assert_int_equal(resp.pd[0], 0xA5U); +} + +static void test_decode_operate_response_bad_checksum_succeeds_with_flag_false(void** state) +{ + (void) state; + const uint8_t frame[] = {0x20U, 0xA5U, 0x00U, 0x00U}; + iolink_frame_operate_response_t resp = {0}; + + assert_int_equal(iolink_frame_decode_operate_response(frame, sizeof(frame), 1U, 1U, &resp), 0); + assert_false(resp.checksum_ok); + assert_true(resp.pd_valid); + assert_int_equal(resp.pd_len, 1U); + assert_int_equal(resp.pd[0], 0xA5U); +} + +static void test_decode_operate_response_rejects_oversized_pd_in_len(void** state) +{ + (void) state; + const uint8_t frame[] = {0x00U, 0x00U}; + iolink_frame_operate_response_t resp = {0}; + + assert_int_equal(iolink_frame_decode_operate_response(frame, + sizeof(frame), + (uint8_t) (IOLINK_PD_IN_MAX_SIZE + 1U), + 0U, + &resp), + -1); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_encode_type0_idle), + cmocka_unit_test(test_encode_type0_transition), + cmocka_unit_test(test_encode_type1_empty_cycle), + cmocka_unit_test(test_encode_type0_rejects_undersized_buffer), + cmocka_unit_test(test_encode_type1_rejects_zero_od_len), + cmocka_unit_test(test_encode_type1_rejects_oversized_od_len), + cmocka_unit_test(test_encode_type1_allows_max_od_len), + cmocka_unit_test(test_decode_operate_response_with_pd), + cmocka_unit_test(test_decode_operate_response_bad_checksum_succeeds_with_flag_false), + cmocka_unit_test(test_decode_operate_response_rejects_oversized_pd_in_len), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 3fc1133..e5a3dcc 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -14,6 +14,7 @@ zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src) zephyr_library_sources( ../src/iolink_core.c ../src/crc.c + ../src/frame.c ../src/dll.c ../src/isdu.c ../src/events.c From 26c3de75f851cb04142f9228d95ea1244214db32 Mon Sep 17 00:00:00 2001 From: w1ne <14119286+w1ne@users.noreply.github.com> Date: Sat, 27 Jun 2026 20:51:18 +0200 Subject: [PATCH 2/2] fix(frame): drop dead od_len guards, satisfy cppcheck + clang-format Removes the always-true 'od_len > 0' branches (od_len is validated non-zero by the entry guards) and the redundant 'frame_len < 2' condition flagged by cppcheck knownConditionTrueFalse, and applies clang-format (Google, 100col) to frame.c/frame.h/test_frame.c so the code-quality gate passes. --- include/iolinki/frame.h | 17 ++++++----------- src/frame.c | 32 ++++++++++++-------------------- tests/test_frame.c | 25 ++++++++++--------------- 3 files changed, 28 insertions(+), 46 deletions(-) diff --git a/include/iolinki/frame.h b/include/iolinki/frame.h index 4ad4063..76cc973 100644 --- a/include/iolinki/frame.h +++ b/include/iolinki/frame.h @@ -19,7 +19,8 @@ * @brief Shared IO-Link frame encoding and decoding helpers. */ -typedef struct { +typedef struct +{ uint8_t status; bool pd_valid; bool event_pending; @@ -31,15 +32,9 @@ typedef struct { } iolink_frame_operate_response_t; int iolink_frame_encode_type0(uint8_t mc, uint8_t* out, size_t out_size); -int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, - uint8_t pd_out_len, - uint8_t od_len, - uint8_t* out, - size_t out_size); -int iolink_frame_decode_operate_response(const uint8_t* frame, - size_t frame_len, - uint8_t pd_in_len, - uint8_t od_len, - iolink_frame_operate_response_t* out); +int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, uint8_t pd_out_len, uint8_t od_len, + uint8_t* out, size_t out_size); +int iolink_frame_decode_operate_response(const uint8_t* frame, size_t frame_len, uint8_t pd_in_len, + uint8_t od_len, iolink_frame_operate_response_t* out); #endif /* IOLINK_FRAME_H */ diff --git a/src/frame.c b/src/frame.c index 46f5a76..7b42844 100644 --- a/src/frame.c +++ b/src/frame.c @@ -23,11 +23,8 @@ int iolink_frame_encode_type0(uint8_t mc, uint8_t* out, size_t out_size) return (int) IOLINK_M_SEQ_TYPE0_LEN; } -int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, - uint8_t pd_out_len, - uint8_t od_len, - uint8_t* out, - size_t out_size) +int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, uint8_t pd_out_len, uint8_t od_len, + uint8_t* out, size_t out_size) { size_t pos = 0U; const size_t frame_len = (size_t) IOLINK_M_SEQ_HEADER_LEN + pd_out_len + od_len + 1U; @@ -46,27 +43,23 @@ int iolink_frame_encode_type1_cycle(const uint8_t* pd_out, pos += pd_out_len; } - if (od_len > 0U) { - memset(&out[pos], 0, od_len); - pos += od_len; - } + /* od_len is guaranteed non-zero by the guard above. */ + memset(&out[pos], 0, od_len); + pos += od_len; out[pos] = iolink_crc6(out, (uint8_t) pos); return (int) frame_len; } -int iolink_frame_decode_operate_response(const uint8_t* frame, - size_t frame_len, - uint8_t pd_in_len, - uint8_t od_len, - iolink_frame_operate_response_t* out) +int iolink_frame_decode_operate_response(const uint8_t* frame, size_t frame_len, uint8_t pd_in_len, + uint8_t od_len, iolink_frame_operate_response_t* out) { size_t pos = 0U; const size_t expected_len = 1U + pd_in_len + od_len + 1U; - if ((frame == NULL) || (out == NULL) || (pd_in_len > IOLINK_PD_IN_MAX_SIZE) || - (od_len == 0U) || (od_len > IOLINK_OD_MAX_SIZE) || (frame_len != expected_len) || (frame_len < 2U)) { + if ((frame == NULL) || (out == NULL) || (pd_in_len > IOLINK_PD_IN_MAX_SIZE) || (od_len == 0U) || + (od_len > IOLINK_OD_MAX_SIZE) || (frame_len != expected_len)) { return -1; } @@ -83,10 +76,9 @@ int iolink_frame_decode_operate_response(const uint8_t* frame, pos += pd_in_len; } - if (od_len > 0U) { - memcpy(out->od, &frame[pos], od_len); - out->od_len = od_len; - } + /* od_len is guaranteed non-zero by the guard above. */ + memcpy(out->od, &frame[pos], od_len); + out->od_len = od_len; return 0; } diff --git a/tests/test_frame.c b/tests/test_frame.c index 37af16d..a6ee98b 100644 --- a/tests/test_frame.c +++ b/tests/test_frame.c @@ -27,7 +27,7 @@ static void test_encode_type0_idle(void** state) uint8_t frame[2] = {0U}; assert_int_equal(iolink_frame_encode_type0(0x00U, frame, sizeof(frame)), 2); - assert_memory_equal(frame, ((const uint8_t[]) {0x00U, 0x24U}), sizeof(frame)); + assert_memory_equal(frame, ((const uint8_t[]){0x00U, 0x24U}), sizeof(frame)); } static void test_encode_type0_transition(void** state) @@ -36,7 +36,7 @@ static void test_encode_type0_transition(void** state) uint8_t frame[2] = {0U}; assert_int_equal(iolink_frame_encode_type0(0x0FU, frame, sizeof(frame)), 2); - assert_memory_equal(frame, ((const uint8_t[]) {0x0FU, 0x0DU}), sizeof(frame)); + assert_memory_equal(frame, ((const uint8_t[]){0x0FU, 0x0DU}), sizeof(frame)); } static void test_encode_type1_empty_cycle(void** state) @@ -45,7 +45,7 @@ static void test_encode_type1_empty_cycle(void** state) uint8_t frame[4] = {0U}; assert_int_equal(iolink_frame_encode_type1_cycle(NULL, 0U, 1U, frame, sizeof(frame)), 4); - assert_memory_equal(frame, ((const uint8_t[]) {0x00U, 0x00U, 0x00U, 0x09U}), sizeof(frame)); + assert_memory_equal(frame, ((const uint8_t[]){0x00U, 0x00U, 0x00U, 0x09U}), sizeof(frame)); } static void test_encode_type0_rejects_undersized_buffer(void** state) @@ -69,11 +69,8 @@ static void test_encode_type1_rejects_oversized_od_len(void** state) (void) state; uint8_t frame[8] = {0U}; - assert_int_equal(iolink_frame_encode_type1_cycle(NULL, - 0U, - (uint8_t) (IOLINK_OD_MAX_SIZE + 1U), - frame, - sizeof(frame)), + assert_int_equal(iolink_frame_encode_type1_cycle(NULL, 0U, (uint8_t) (IOLINK_OD_MAX_SIZE + 1U), + frame, sizeof(frame)), -1); } @@ -82,8 +79,9 @@ static void test_encode_type1_allows_max_od_len(void** state) (void) state; uint8_t frame[IOLINK_M_SEQ_HEADER_LEN + IOLINK_OD_MAX_SIZE + 1U] = {0U}; - assert_int_equal(iolink_frame_encode_type1_cycle(NULL, 0U, IOLINK_OD_MAX_SIZE, frame, sizeof(frame)), - (int) sizeof(frame)); + assert_int_equal( + iolink_frame_encode_type1_cycle(NULL, 0U, IOLINK_OD_MAX_SIZE, frame, sizeof(frame)), + (int) sizeof(frame)); } static void test_decode_operate_response_with_pd(void** state) @@ -119,11 +117,8 @@ static void test_decode_operate_response_rejects_oversized_pd_in_len(void** stat const uint8_t frame[] = {0x00U, 0x00U}; iolink_frame_operate_response_t resp = {0}; - assert_int_equal(iolink_frame_decode_operate_response(frame, - sizeof(frame), - (uint8_t) (IOLINK_PD_IN_MAX_SIZE + 1U), - 0U, - &resp), + assert_int_equal(iolink_frame_decode_operate_response( + frame, sizeof(frame), (uint8_t) (IOLINK_PD_IN_MAX_SIZE + 1U), 0U, &resp), -1); }