From 13cd7c74b6a7b31f3d018d2f7b8161aa647b8f12 Mon Sep 17 00:00:00 2001 From: Valerio De Benedetto Date: Sat, 22 Jan 2022 11:34:47 +0100 Subject: [PATCH] Additional tests --- modbusino.c | 65 +++-- modbusino.h | 5 +- tests/CMakeLists.txt | 5 +- tests/modbusino_tests.c | 554 ++++++++++++++++++++++++++++++++++++++-- tests/modbusino_tests.h | 4 +- 5 files changed, 578 insertions(+), 55 deletions(-) diff --git a/modbusino.c b/modbusino.c index 8782934..a804053 100644 --- a/modbusino.c +++ b/modbusino.c @@ -1,7 +1,7 @@ #include "modbusino.h" #include +#include #include -#include #ifdef MBSN_DEBUG #include @@ -43,18 +43,6 @@ (m)->msg.buf_idx += 2 #endif -/* -static msg_state msg_state_create() { - msg_state s; - memset(&s, 0, sizeof(msg_state)); - return s; -} - -static msg_state res_from_req(msg_state* req) { - msg_state res = *req; - return res; -} -*/ static void msg_buf_reset(mbsn_t* mbsn) { mbsn->msg.buf_idx = 0; @@ -93,6 +81,7 @@ int mbsn_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf) { mbsn->byte_timeout_ms = -1; mbsn->read_timeout_ms = -1; + mbsn->byte_spacing_ms = 0; if (!platform_conf) return MBSN_ERROR_INVALID_ARGUMENT; @@ -105,7 +94,6 @@ int mbsn_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf) { mbsn->platform = *platform_conf; - return MBSN_ERROR_NONE; } @@ -141,6 +129,11 @@ void mbsn_set_byte_timeout(mbsn_t* mbsn, int32_t timeout_ms) { } +void mbsn_set_byte_spacing(mbsn_t* mbsn, uint32_t spacing_ms) { + mbsn->byte_spacing_ms = spacing_ms; +} + + void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address) { mbsn->dest_address_rtu = address; } @@ -196,7 +189,14 @@ static mbsn_error recv(mbsn_t* mbsn, uint32_t count) { static mbsn_error send(mbsn_t* mbsn) { + uint32_t spacing_ms = 0; + if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) + spacing_ms = mbsn->byte_spacing_ms; + for (int i = 0; i < mbsn->msg.buf_idx; i++) { + if (spacing_ms != 0) + mbsn->platform.sleep(spacing_ms); + int ret = mbsn->platform.write_byte(mbsn->msg.buf[i], mbsn->read_timeout_ms); if (ret == 0) { return MBSN_ERROR_TIMEOUT; @@ -539,10 +539,10 @@ static mbsn_error handle_write_single_coil(mbsn_t* mbsn) { return err; if (!mbsn->msg.ignored) { - if (value != 0 && value != 0xFF00) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - if (mbsn->callbacks.write_single_coil) { + if (value != 0 && value != 0xFF00) + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + err = mbsn->callbacks.write_single_coil(address, value == 0 ? false : true); if (err != MBSN_ERROR_NONE) { if (mbsn_error_is_exception(err)) @@ -552,8 +552,9 @@ static mbsn_error handle_write_single_coil(mbsn_t* mbsn) { } if (!mbsn->msg.broadcast) { - send_msg_header(mbsn, 2); + send_msg_header(mbsn, 4); put_2(mbsn, address); + put_2(mbsn, value); err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) return err; @@ -591,7 +592,8 @@ static mbsn_error handle_write_single_register(mbsn_t* mbsn) { } if (!mbsn->msg.broadcast) { - send_msg_header(mbsn, 1); + send_msg_header(mbsn, 4); + put_2(mbsn, address); put_2(mbsn, value); err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) @@ -621,8 +623,9 @@ static mbsn_error handle_write_multiple_coils(mbsn_t* mbsn) { return err; mbsn_bitfield coils; - for (int i = 0; i < coils_bytes; i++) + for (int i = 0; i < coils_bytes; i++) { coils[i] = get_1(mbsn); + } err = recv_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) @@ -682,7 +685,7 @@ static mbsn_error handle_write_multiple_registers(mbsn_t* mbsn) { return err; uint16_t registers[0x007B]; - for (int i = 0; i < quantity; i++) { + for (int i = 0; i < registers_bytes / 2; i++) { registers[i] = get_2(mbsn); } @@ -865,9 +868,6 @@ mbsn_error mbsn_server_receive(mbsn_t* mbsn) { static mbsn_error read_discrete(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t quantity, mbsn_bitfield values) { - if (address == MBSN_BROADCAST_ADDRESS) - return MBSN_ERROR_INVALID_ARGUMENT; - if (quantity < 1 || quantity > 2000) return MBSN_ERROR_INVALID_ARGUMENT; @@ -920,10 +920,7 @@ mbsn_error mbsn_read_discrete_inputs(mbsn_t* mbsn, uint16_t address, uint16_t qu } -mbsn_error read_registers(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t quantity, uint16_t* registers) { - if (address == MBSN_BROADCAST_ADDRESS) - return MBSN_ERROR_INVALID_ARGUMENT; - +static mbsn_error read_registers(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t quantity, uint16_t* registers) { if (quantity < 1 || quantity > 125) return MBSN_ERROR_INVALID_ARGUMENT; @@ -983,8 +980,10 @@ mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) { msg_state_req(mbsn, 5); send_msg_header(mbsn, 4); + uint16_t value_req = value ? 0xFF00 : 0; + put_2(mbsn, address); - put_2(mbsn, value ? 0xFF00 : 0); + put_2(mbsn, value_req); mbsn_error err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) @@ -1009,7 +1008,7 @@ mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) { if (address_res != address) return MBSN_ERROR_INVALID_RESPONSE; - if (value_res != value) + if (value_res != value_req) return MBSN_ERROR_INVALID_RESPONSE; } @@ -1018,7 +1017,7 @@ mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) { mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t value) { - msg_state_req(mbsn, 5); + msg_state_req(mbsn, 6); send_msg_header(mbsn, 4); put_2(mbsn, address); @@ -1056,7 +1055,7 @@ mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t v mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const mbsn_bitfield coils) { - if (quantity < 0 || quantity > 0x07B0) + if (quantity < 1 || quantity > 0x07B0) return MBSN_ERROR_INVALID_ARGUMENT; if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) @@ -1107,7 +1106,7 @@ mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t qu mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const uint16_t* registers) { - if (quantity < 0 || quantity > 0x007B) + if (quantity < 1 || quantity > 0x007B) return MBSN_ERROR_INVALID_ARGUMENT; if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) diff --git a/modbusino.h b/modbusino.h index 00d7a29..b010526 100644 --- a/modbusino.h +++ b/modbusino.h @@ -51,8 +51,8 @@ typedef struct mbsn_callbacks { mbsn_error (*read_input_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); mbsn_error (*write_single_coil)(uint16_t address, bool value); mbsn_error (*write_single_register)(uint16_t address, uint16_t value); - mbsn_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, mbsn_bitfield coils); - mbsn_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, uint16_t* registers); + mbsn_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, const mbsn_bitfield coils); + mbsn_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers); } mbsn_callbacks; @@ -72,6 +72,7 @@ typedef struct mbsn_t { int32_t byte_timeout_ms; int32_t read_timeout_ms; + uint32_t byte_spacing_ms; mbsn_platform_conf platform; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 34dd375..418cca9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,11 +2,12 @@ cmake_minimum_required(VERSION 3.21) project(modbusino_tests C) set(CMAKE_C_STANDARD 99) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") include_directories(. ..) -add_definitions(-DMBSN_DEBUG) +#add_definitions(-DMBSN_DEBUG) add_executable(modbusino_tests ../modbusino.c modbusino_tests.c) diff --git a/tests/modbusino_tests.c b/tests/modbusino_tests.c index 56bfa80..06488d4 100644 --- a/tests/modbusino_tests.c +++ b/tests/modbusino_tests.c @@ -153,17 +153,31 @@ mbsn_error read_discrete(uint16_t address, uint16_t quantity, mbsn_bitfield coil mbsn_bitfield_write(coils_out, 2, 1); } + if (address == 65526 && quantity == 10) { + mbsn_bitfield_write(coils_out, 0, 1); + mbsn_bitfield_write(coils_out, 1, 0); + mbsn_bitfield_write(coils_out, 2, 1); + mbsn_bitfield_write(coils_out, 3, 0); + mbsn_bitfield_write(coils_out, 4, 1); + mbsn_bitfield_write(coils_out, 5, 0); + mbsn_bitfield_write(coils_out, 6, 1); + mbsn_bitfield_write(coils_out, 7, 0); + mbsn_bitfield_write(coils_out, 8, 1); + mbsn_bitfield_write(coils_out, 9, 0); + } + return MBSN_ERROR_NONE; } void test_fc1(mbsn_transport transport) { + const uint8_t fc = 1; uint8_t raw_res[260]; start_client_and_server(transport, (mbsn_callbacks){}); should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - assert(mbsn_read_coils(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + assert(mbsn_read_coils(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); stop_client_and_server(); @@ -175,19 +189,19 @@ void test_fc1(mbsn_transport transport) { should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 2000"); assert(mbsn_read_coils(&CLIENT, 1, 2001, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 65535"); + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); assert(mbsn_read_coils(&CLIENT, 65530, 7, NULL) == MBSN_ERROR_INVALID_ARGUMENT); should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, 1, (uint16_t[]){htons(1), htons(0)}, 4)); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, 1, (uint16_t[]){htons(1), htons(2001)}, 4)); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 65535"); - check(mbsn_send_raw_pdu(&CLIENT, 1, (uint16_t[]){htons(65530), htons(7)}, 4)); + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(65530), htons(7)}, 4)); assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); @@ -206,17 +220,30 @@ void test_fc1(mbsn_transport transport) { assert(mbsn_bitfield_read(bf, 1) == 0); assert(mbsn_bitfield_read(bf, 2) == 1); + check(mbsn_read_coils(&CLIENT, 65526, 10, bf)); + assert(mbsn_bitfield_read(bf, 0) == 1); + assert(mbsn_bitfield_read(bf, 1) == 0); + assert(mbsn_bitfield_read(bf, 2) == 1); + assert(mbsn_bitfield_read(bf, 3) == 0); + assert(mbsn_bitfield_read(bf, 4) == 1); + assert(mbsn_bitfield_read(bf, 5) == 0); + assert(mbsn_bitfield_read(bf, 6) == 1); + assert(mbsn_bitfield_read(bf, 7) == 0); + assert(mbsn_bitfield_read(bf, 8) == 1); + assert(mbsn_bitfield_read(bf, 9) == 0); + stop_client_and_server(); } void test_fc2(mbsn_transport transport) { + const uint8_t fc = 2; uint8_t raw_res[260]; start_client_and_server(transport, (mbsn_callbacks){}); should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - assert(mbsn_read_discrete_inputs(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + assert(mbsn_read_discrete_inputs(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); stop_client_and_server(); @@ -228,19 +255,19 @@ void test_fc2(mbsn_transport transport) { should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 2000"); assert(mbsn_read_discrete_inputs(&CLIENT, 1, 2001, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 65535"); + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); assert(mbsn_read_discrete_inputs(&CLIENT, 65530, 7, NULL) == MBSN_ERROR_INVALID_ARGUMENT); should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, 2, (uint16_t[]){htons(1), htons(0)}, 4)); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, 2, (uint16_t[]){htons(1), htons(2001)}, 4)); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 65535"); - check(mbsn_send_raw_pdu(&CLIENT, 2, (uint16_t[]){htons(65530), htons(7)}, 4)); + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(65530), htons(7)}, 4)); assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); @@ -259,13 +286,499 @@ void test_fc2(mbsn_transport transport) { assert(mbsn_bitfield_read(bf, 1) == 0); assert(mbsn_bitfield_read(bf, 2) == 1); + check(mbsn_read_discrete_inputs(&CLIENT, 65526, 10, bf)); + assert(mbsn_bitfield_read(bf, 0) == 1); + assert(mbsn_bitfield_read(bf, 1) == 0); + assert(mbsn_bitfield_read(bf, 2) == 1); + assert(mbsn_bitfield_read(bf, 3) == 0); + assert(mbsn_bitfield_read(bf, 4) == 1); + assert(mbsn_bitfield_read(bf, 5) == 0); + assert(mbsn_bitfield_read(bf, 6) == 1); + assert(mbsn_bitfield_read(bf, 7) == 0); + assert(mbsn_bitfield_read(bf, 8) == 1); + assert(mbsn_bitfield_read(bf, 9) == 0); + stop_client_and_server(); } -mbsn_transport transports[2] = {MBSN_TRANSPORT_TCP, MBSN_TRANSPORT_RTU}; -const char* transports_str[2] = {"TCP", "RTU"}; +mbsn_error read_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out) { + if (address == 1) + return -1; + if (address == 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 10 && quantity == 3) { + registers_out[0] = 100; + registers_out[1] = 0; + registers_out[2] = 200; + } + + return MBSN_ERROR_NONE; +} + + +void test_fc3(mbsn_transport transport) { + const uint8_t fc = 3; + uint8_t raw_res[260]; + + start_client_and_server(transport, (mbsn_callbacks){}); + + should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + assert(mbsn_read_holding_registers(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.read_holding_registers = read_registers}); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + assert(mbsn_read_holding_registers(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 125"); + assert(mbsn_read_holding_registers(&CLIENT, 1, 126, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + assert(mbsn_read_holding_registers(&CLIENT, 0xFFFF, 2, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + assert(mbsn_read_holding_registers(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_read_holding_registers(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_read_holding_registers(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + uint16_t regs[3]; + check(mbsn_read_holding_registers(&CLIENT, 10, 3, regs)); + assert(regs[0] == 100); + assert(regs[1] == 0); + assert(regs[2] == 200); + + stop_client_and_server(); +} + + +void test_fc4(mbsn_transport transport) { + const uint8_t fc = 4; + uint8_t raw_res[260]; + + start_client_and_server(transport, (mbsn_callbacks){}); + + should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + assert(mbsn_read_input_registers(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.read_input_registers = read_registers}); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + assert(mbsn_read_input_registers(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 125"); + assert(mbsn_read_input_registers(&CLIENT, 1, 126, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + assert(mbsn_read_input_registers(&CLIENT, 0xFFFF, 2, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + assert(mbsn_read_input_registers(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_read_input_registers(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_read_input_registers(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + uint16_t regs[3]; + check(mbsn_read_input_registers(&CLIENT, 10, 3, regs)); + assert(regs[0] == 100); + assert(regs[1] == 0); + assert(regs[2] == 200); + + stop_client_and_server(); +} + + +mbsn_error write_coil(uint16_t address, bool value) { + if (address == 1) + return -1; + + if (address == 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4 && !value) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + if (address == 5 && value) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + return MBSN_ERROR_NONE; +} + + +void test_fc5(mbsn_transport transport) { + const uint8_t fc = 5; + uint8_t raw_res[260]; + + start_client_and_server(transport, (mbsn_callbacks){}); + + should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + assert(mbsn_write_single_coil(&CLIENT, 0, true) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.write_single_coil = write_coil}); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE when calling with value !0x0000 or 0xFF000"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(6), htons(0x0001)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(6), htons(0xFFFF)}, 4)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + assert(mbsn_write_single_coil(&CLIENT, 1, true) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_write_single_coil(&CLIENT, 2, true) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_write_single_coil(&CLIENT, 3, true) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + check(mbsn_write_single_coil(&CLIENT, 4, true)); + check(mbsn_write_single_coil(&CLIENT, 5, false)); + + should("echo request's address and value"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(4), htons(0xFF00)}, 4)); + check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + assert(((uint16_t*) raw_res)[0] == ntohs(4)); + assert(((uint16_t*) raw_res)[1] == ntohs(0xFF00)); + + stop_client_and_server(); +} + + +mbsn_error write_register(uint16_t address, uint16_t value) { + if (address == 1) + return -1; + + if (address == 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4 && !value) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + if (address == 5 && value) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + return MBSN_ERROR_NONE; +} + + +void test_fc6(mbsn_transport transport) { + const uint8_t fc = 6; + uint8_t raw_res[260]; + + start_client_and_server(transport, (mbsn_callbacks){}); + + should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + assert(mbsn_write_single_register(&CLIENT, 0, 123) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.write_single_register = write_register}); + + should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + assert(mbsn_write_single_register(&CLIENT, 1, 123) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_write_single_register(&CLIENT, 2, 123) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_write_single_register(&CLIENT, 3, 123) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + check(mbsn_write_single_register(&CLIENT, 4, true)); + check(mbsn_write_single_register(&CLIENT, 5, false)); + + should("echo request's address and value"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(4), htons(0x123)}, 4)); + check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + assert(((uint16_t*) raw_res)[0] == ntohs(4)); + assert(((uint16_t*) raw_res)[1] == ntohs(0x123)); + + stop_client_and_server(); +} + + +mbsn_error write_coils(uint16_t address, uint16_t quantity, const mbsn_bitfield coils) { + if (address == 1) + return -1; + + if (address == 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4) { + if (quantity != 4) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + assert(mbsn_bitfield_read(coils, 0) == 1); + assert(mbsn_bitfield_read(coils, 1) == 0); + assert(mbsn_bitfield_read(coils, 2) == 1); + assert(mbsn_bitfield_read(coils, 3) == 0); + + return MBSN_ERROR_NONE; + } + + if (address == 5) { + if (quantity != 27) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + assert(mbsn_bitfield_read(coils, 26) == 1); + + return MBSN_ERROR_NONE; + } + + if (address == 7) { + if (quantity != 1) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + return MBSN_ERROR_NONE; + } + + return MBSN_ERROR_NONE; +} + + +void test_fc15(mbsn_transport transport) { + const uint8_t fc = 15; + uint8_t raw_res[260]; + mbsn_bitfield bf = {0}; + + start_client_and_server(transport, (mbsn_callbacks){}); + + should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + assert(mbsn_write_multiple_coils(&CLIENT, 0, 1, bf) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.write_multiple_coils = write_coils}); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + assert(mbsn_write_multiple_coils(&CLIENT, 1, 0, bf) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 0x07B0"); + assert(mbsn_write_multiple_coils(&CLIENT, 1, 0x07B1, bf) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + assert(mbsn_write_multiple_coils(&CLIENT, 0xFFFF, 2, bf) == MBSN_ERROR_INVALID_ARGUMENT); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0), htons(0x0100)}, 6)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2000), htons(0x0100)}, 6)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2), htons(0x0100)}, 6)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + /* + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when quantity does not match byte count"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(5), htons(0x0303)}, 6)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + */ + + should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + assert(mbsn_write_multiple_coils(&CLIENT, 1, 1, bf) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_write_multiple_coils(&CLIENT, 2, 2, bf) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_write_multiple_coils(&CLIENT, 3, 3, bf) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + mbsn_bitfield_write(bf, 0, 1); + mbsn_bitfield_write(bf, 1, 0); + mbsn_bitfield_write(bf, 2, 1); + mbsn_bitfield_write(bf, 3, 0); + check(mbsn_write_multiple_coils(&CLIENT, 4, 4, bf)); + + mbsn_bitfield_write(bf, 26, 1); + check(mbsn_write_multiple_coils(&CLIENT, 5, 27, bf)); + + should("echo request's address and value"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(7), htons(1), htons(0x0100)}, 6)); + check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + assert(((uint16_t*) raw_res)[0] == ntohs(7)); + assert(((uint16_t*) raw_res)[1] == ntohs(1)); + + stop_client_and_server(); +} + + +mbsn_error write_registers(uint16_t address, uint16_t quantity, const uint16_t* registers) { + if (address == 1) + return -1; + + if (address == 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4) { + if (quantity != 4) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + assert(registers[0] == 255); + assert(registers[1] == 1); + assert(registers[2] == 2); + assert(registers[3] == 3); + + return MBSN_ERROR_NONE; + } + + if (address == 5) { + if (quantity != 27) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + assert(registers[26] == 26); + + return MBSN_ERROR_NONE; + } + + if (address == 7) { + if (quantity != 1) + return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; + + return MBSN_ERROR_NONE; + } + + return MBSN_ERROR_NONE; +} + + +void test_fc16(mbsn_transport transport) { + const uint8_t fc = 16; + uint8_t raw_res[260]; + uint16_t registers[125]; + + start_client_and_server(transport, (mbsn_callbacks){}); + + should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + assert(mbsn_write_multiple_registers(&CLIENT, 0, 1, registers) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.write_multiple_registers = write_registers}); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + assert(mbsn_write_multiple_registers(&CLIENT, 1, 0, registers) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 0x007B"); + assert(mbsn_write_multiple_registers(&CLIENT, 1, 0x007C, registers) == MBSN_ERROR_INVALID_ARGUMENT); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + assert(mbsn_write_multiple_registers(&CLIENT, 0xFFFF, 2, registers) == MBSN_ERROR_INVALID_ARGUMENT); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0), htons(0x0200), htons(0)}, 7)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2000), htons(0x0200), htons(0)}, 7)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2), htons(0x0200), htons(0)}, 7)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + /* + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when quantity does not match byte count"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(5), htons(0x0303)}, 6)); + assert(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + */ + + should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + assert(mbsn_write_multiple_registers(&CLIENT, 1, 1, registers) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_write_multiple_registers(&CLIENT, 2, 2, registers) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_write_multiple_registers(&CLIENT, 3, 3, registers) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + registers[0] = 255; + registers[1] = 1; + registers[2] = 2; + registers[3] = 3; + check(mbsn_write_multiple_registers(&CLIENT, 4, 4, registers)); + + registers[26] = 26; + check(mbsn_write_multiple_registers(&CLIENT, 6, 27, registers)); + + should("echo request's address and value"); + check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(7), htons(1), htons(0x0200), htons(0)}, 7)); + check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + assert(((uint16_t*) raw_res)[0] == ntohs(7)); + assert(((uint16_t*) raw_res)[1] == ntohs(1)); + + stop_client_and_server(); +} + + +mbsn_transport transports[2] = {MBSN_TRANSPORT_RTU, MBSN_TRANSPORT_TCP}; +const char* transports_str[2] = {"RTU", "TCP"}; void for_transports(void (*test_fn)(mbsn_transport), const char* should_str) { for (int t = 0; t < sizeof(transports) / sizeof(mbsn_transport); t++) { @@ -274,9 +787,7 @@ void for_transports(void (*test_fn)(mbsn_transport), const char* should_str) { } } - int main() { - for_transports(test_server_create, "create a modbus server"); for_transports(test_server_receive_base, "receive no messages without failing"); @@ -285,6 +796,17 @@ int main() { for_transports(test_fc2, "send and receive FC 02 (0x02) Read Discrete Inputs"); + for_transports(test_fc3, "send and receive FC 03 (0x03) Read Holding Registers"); + + for_transports(test_fc4, "send and receive FC 04 (0x04) Read Input Registers"); + + for_transports(test_fc5, "send and receive FC 05 (0x05) Write Single Coil"); + + for_transports(test_fc6, "send and receive FC 06 (0x06) Write Single Register"); + + for_transports(test_fc15, "send and receive FC 15 (0x0F) Write Multiple Coils"); + + for_transports(test_fc16, "send and receive FC 16 (0x10) Write Multiple registers"); return 0; } diff --git a/tests/modbusino_tests.h b/tests/modbusino_tests.h index 8eecec0..2aaf5d5 100644 --- a/tests/modbusino_tests.h +++ b/tests/modbusino_tests.h @@ -209,10 +209,10 @@ void start_client_and_server(mbsn_transport transport, mbsn_callbacks server_cal check(mbsn_client_create(&CLIENT, platform_conf_pipe_client(transport))); mbsn_set_destination_rtu_address(&CLIENT, TEST_SERVER_ADDR); - mbsn_set_read_timeout(&SERVER, 1000); + mbsn_set_read_timeout(&SERVER, 500); mbsn_set_byte_timeout(&SERVER, 100); - mbsn_set_read_timeout(&CLIENT, -1); + mbsn_set_read_timeout(&CLIENT, 5000); mbsn_set_byte_timeout(&CLIENT, 100); assert(pthread_mutex_lock(&server_stopped_m) == 0);