Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions include/iolinki/frame.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/**
* @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 */
84 changes: 84 additions & 0 deletions src/frame.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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 <string.h>

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;
}

/* 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)
{
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)) {
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;
}

/* od_len is guaranteed non-zero by the guard above. */
memcpy(out->od, &frame[pos], od_len);
out->od_len = od_len;

return 0;
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
140 changes: 140 additions & 0 deletions tests/test_frame.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* 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 <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdint.h>
#include <stdbool.h>

#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);
}
1 change: 1 addition & 0 deletions zephyr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading