From c064e47368679e0e97bbcdb83e5b68559b072470 Mon Sep 17 00:00:00 2001 From: Valerio De Benedetto Date: Fri, 21 Jan 2022 15:50:03 +0100 Subject: [PATCH] FC1 and FC2 tests --- modbusino.c | 679 ++++++++++++++++++++++------------------ modbusino.h | 22 +- tests/CMakeLists.txt | 3 +- tests/modbusino_tests.c | 126 +++++++- tests/modbusino_tests.h | 10 +- 5 files changed, 532 insertions(+), 308 deletions(-) diff --git a/modbusino.c b/modbusino.c index 86b93df..8782934 100644 --- a/modbusino.c +++ b/modbusino.c @@ -3,6 +3,9 @@ #include #include +#ifdef MBSN_DEBUG +#include +#endif #if !defined(MBSN_BIG_ENDIAN) && !defined(MBSN_LITTLE_ENDIAN) #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || \ @@ -12,70 +15,73 @@ defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) #define MBSN_LITTLE_ENDIAN #else -#error "Failed to automatically detect platform endianness. Please define either MBSN_BIG_ENDIAN or MBSN_LITTLE_ENDIAN" +#error "Failed to automatically detect platform endianness. Please define either MBSN_BIG_ENDIAN or MBSN_LITTLE_ENDIAN." #endif #endif - #define get_1(m) \ - (m)->msg_buf[(m)->msg_buf_idx]; \ - (m)->msg_buf_idx++ + (m)->msg.buf[(m)->msg.buf_idx]; \ + (m)->msg.buf_idx++ #define put_1(m, b) \ - (m)->msg_buf[(m)->msg_buf_idx] = (b); \ - (m)->msg_buf_idx++ + (m)->msg.buf[(m)->msg.buf_idx] = (b); \ + (m)->msg.buf_idx++ #ifdef MBSN_BIG_ENDIAN #define get_2(m) \ - (*(uint16_t*) ((m)->msg_buf + (m)->msg_buf_idx)); \ - (m)->msg_buf_idx += 2 + (*(uint16_t*) ((m)->msg.buf + (m)->msg.buf_idx)); \ + (m)->msg.buf_idx += 2 #define put_2(m, w) \ - (*(uint16_t*) ((m)->msg_buf + (m)->msg_buf_idx)) = (w); \ - (m)->msg_buf_idx += 2 + (*(uint16_t*) ((m)->msg.buf + (m)->msg.buf_idx)) = (w); \ + (m)->msg.buf_idx += 2 #else #define get_2(m) \ - ((uint16_t) ((m)->msg_buf[(m)->msg_buf_idx + 1])) | (((uint16_t) (m)->msg_buf[(m)->msg_buf_idx] << 8)); \ - (m)->msg_buf_idx += 2 + ((uint16_t) ((m)->msg.buf[(m)->msg.buf_idx + 1])) | (((uint16_t) (m)->msg.buf[(m)->msg.buf_idx] << 8)); \ + (m)->msg.buf_idx += 2 #define put_2(m, w) \ - (m)->msg_buf[(m)->msg_buf_idx] = ((uint8_t) ((((uint16_t) (w)) & 0xFF00) >> 8)); \ - (m)->msg_buf[(m)->msg_buf_idx + 1] = ((uint8_t) (((uint16_t) (w)) & 0x00FF)); \ - (m)->msg_buf_idx += 2 + (m)->msg.buf[(m)->msg.buf_idx] = ((uint8_t) ((((uint16_t) (w)) & 0xFF00) >> 8)); \ + (m)->msg.buf[(m)->msg.buf_idx + 1] = ((uint8_t) (((uint16_t) (w)) & 0x00FF)); \ + (m)->msg.buf_idx += 2 #endif - -typedef struct msg_state { - uint8_t unit_id; - uint8_t fc; - uint16_t transaction_id; - bool broadcast; - bool ignored; -} msg_state; - - +/* 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; +} -static msg_state req_create(uint8_t unit_id, uint8_t fc) { - static uint16_t TID = 0x0102; +static void msg_state_reset(mbsn_t* mbsn) { + msg_buf_reset(mbsn); + mbsn->msg.unit_id = 0; + mbsn->msg.fc = 0; + mbsn->msg.transaction_id = 0; + mbsn->msg.broadcast = false; + mbsn->msg.ignored = 0; +} - msg_state req = msg_state_create(); - req.unit_id = unit_id; - req.fc = fc; - req.transaction_id = TID; - if (req.unit_id == 0) - req.broadcast = true; + +static void msg_state_req(mbsn_t* mbsn, uint8_t fc) { + static uint16_t TID = 1; + + msg_state_reset(mbsn); + mbsn->msg.unit_id = mbsn->dest_address_rtu; + mbsn->msg.fc = fc; + mbsn->msg.transaction_id = TID; + if (mbsn->msg.unit_id == 0) + mbsn->msg.broadcast = true; TID++; - return req; } @@ -158,33 +164,70 @@ static uint16_t crc_calc(const uint8_t* data, unsigned int length) { } -static void reset_msg_buf(mbsn_t* mbsn) { - mbsn->msg_buf_idx = 0; -} - - static mbsn_error recv(mbsn_t* mbsn, uint32_t count) { int r = 0; while (r != count) { - int ret = mbsn->platform.read_byte(mbsn->msg_buf + mbsn->msg_buf_idx + r, mbsn->byte_timeout_ms); - if (ret == 0) + int ret = mbsn->platform.read_byte(mbsn->msg.buf + mbsn->msg.buf_idx + r, mbsn->byte_timeout_ms); + if (ret == 0) { +#ifdef MBSN_DEBUG + if (mbsn->address_rtu == 0) + printf("c: to\n"); + else + printf("s: to\n"); +#endif return MBSN_ERROR_TIMEOUT; - else if (ret != 1) + } + else if (ret != 1) { return MBSN_ERROR_TRANSPORT; + } r++; } +#ifdef MBSN_DEBUG + if (mbsn->address_rtu == 0) + printf("c: recv %d\n", count); + else + printf("s: recv %d\n", count); +#endif + return MBSN_ERROR_NONE; } static mbsn_error send(mbsn_t* mbsn) { - for (int i = 0; i < mbsn->msg_buf_idx; i++) { - int ret = mbsn->platform.write_byte(mbsn->msg_buf[i], mbsn->read_timeout_ms); - if (ret == 0) + for (int i = 0; i < mbsn->msg.buf_idx; i++) { + int ret = mbsn->platform.write_byte(mbsn->msg.buf[i], mbsn->read_timeout_ms); + if (ret == 0) { return MBSN_ERROR_TIMEOUT; - else if (ret != 1) + } + else if (ret != 1) { + return MBSN_ERROR_TRANSPORT; + } + } + +#ifdef MBSN_DEBUG + if (mbsn->address_rtu == 0) + printf("c: sent %d\n", mbsn->msg.buf_idx - 1); + else + printf("s: sent %d\n", mbsn->msg.buf_idx - 1); +#endif + + return MBSN_ERROR_NONE; +} + + +static mbsn_error recv_msg_footer(mbsn_t* mbsn) { + if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { + uint16_t crc = crc_calc(mbsn->msg.buf, mbsn->msg.buf_idx); + + mbsn_error err = recv(mbsn, 2); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t recv_crc = get_2(mbsn); + + if (recv_crc != crc) return MBSN_ERROR_TRANSPORT; } @@ -192,13 +235,13 @@ static mbsn_error send(mbsn_t* mbsn) { } -static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_byte_received) { +static mbsn_error recv_msg_header(mbsn_t* mbsn, bool* first_byte_received) { // We wait for the read timeout here, just for the first message byte int32_t old_byte_timeout = mbsn->byte_timeout_ms; mbsn->byte_timeout_ms = mbsn->read_timeout_ms; - *s_out = msg_state_create(); - reset_msg_buf(mbsn); + msg_state_reset(mbsn); + if (first_byte_received) *first_byte_received = false; @@ -213,21 +256,13 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by if (first_byte_received) *first_byte_received = true; - uint8_t unit_id = get_1(mbsn); - - // Check if message is for us - if (unit_id == 0) - s_out->broadcast = true; - else if (unit_id != mbsn->address_rtu) - s_out->ignored = true; - else - s_out->ignored = false; + mbsn->msg.unit_id = get_1(mbsn); err = recv(mbsn, 1); if (err != MBSN_ERROR_NONE) return err; - s_out->fc = get_1(mbsn); + mbsn->msg.fc = get_1(mbsn); } else if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { mbsn_error err = recv(mbsn, 1); @@ -248,13 +283,13 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by return err; // Starting over - reset_msg_buf(mbsn); + msg_buf_reset(mbsn); - s_out->transaction_id = get_2(mbsn); + mbsn->msg.transaction_id = get_2(mbsn); uint16_t protocol_id = get_2(mbsn); uint16_t length = get_2(mbsn); // We should actually check the length of the request against this value - s_out->unit_id = get_1(mbsn); - s_out->fc = get_1(mbsn); + mbsn->msg.unit_id = get_1(mbsn); + mbsn->msg.fc = get_1(mbsn); if (protocol_id != 0) return MBSN_ERROR_TRANSPORT; @@ -267,44 +302,87 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by } -static mbsn_error recv_msg_footer(mbsn_t* mbsn) { - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - uint16_t crc = crc_calc(mbsn->msg_buf, mbsn->msg_buf_idx); +static mbsn_error recv_req_header(mbsn_t* mbsn, bool* first_byte_received) { + mbsn_error err = recv_msg_header(mbsn, first_byte_received); + if (err != MBSN_ERROR_NONE) + return err; - mbsn_error err = recv(mbsn, 2); - if (err != MBSN_ERROR_NONE) - return err; + // Check if request is for us + if (mbsn->msg.unit_id == MBSN_BROADCAST_ADDRESS) + mbsn->msg.broadcast = true; + else if (mbsn->msg.unit_id != mbsn->address_rtu) + mbsn->msg.ignored = true; + else + mbsn->msg.ignored = false; - uint16_t recv_crc = get_2(mbsn); + return MBSN_ERROR_NONE; +} - if (recv_crc != crc) - return MBSN_ERROR_TRANSPORT; + +static mbsn_error recv_res_header(mbsn_t* mbsn) { + uint16_t req_transaction_id = mbsn->msg.transaction_id; + uint8_t req_fc = mbsn->msg.fc; + + mbsn_error err = recv_msg_header(mbsn, NULL); + if (err != MBSN_ERROR_NONE) + return err; + + if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { + if (mbsn->msg.transaction_id != req_transaction_id) + return MBSN_ERROR_INVALID_RESPONSE; + } + + if (mbsn->msg.ignored) + return MBSN_ERROR_INVALID_RESPONSE; + + if (mbsn->msg.fc != req_fc) { + if (mbsn->msg.fc - 0x80 == req_fc) { + err = recv(mbsn, 1); + if (err != MBSN_ERROR_NONE) + return err; + + uint8_t exception = get_1(mbsn); + err = recv_msg_footer(mbsn); + if (err != MBSN_ERROR_NONE) + return err; + + if (exception < 1 || exception > 4) + return MBSN_ERROR_INVALID_RESPONSE; + else + return exception; + } + else { + return MBSN_ERROR_INVALID_RESPONSE; + } } return MBSN_ERROR_NONE; } -static void send_msg_header(mbsn_t* mbsn, msg_state* s, uint16_t data_length) { - reset_msg_buf(mbsn); +static void send_msg_header(mbsn_t* mbsn, uint16_t data_length) { + msg_buf_reset(mbsn); if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - put_1(mbsn, s->unit_id); + put_1(mbsn, mbsn->msg.unit_id); } else if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { - put_2(mbsn, s->transaction_id); + put_2(mbsn, mbsn->msg.transaction_id); put_2(mbsn, 0); put_2(mbsn, (uint16_t) (1 + 1 + data_length)); - put_1(mbsn, s->unit_id); + put_1(mbsn, mbsn->msg.unit_id); } - put_1(mbsn, s->fc); + put_1(mbsn, mbsn->msg.fc); + + if (mbsn->msg.unit_id == MBSN_BROADCAST_ADDRESS) + mbsn->msg.broadcast = true; } static mbsn_error send_msg_footer(mbsn_t* mbsn) { if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - uint16_t crc = crc_calc(mbsn->msg_buf, mbsn->msg_buf_idx); + uint16_t crc = crc_calc(mbsn->msg.buf, mbsn->msg.buf_idx); put_2(mbsn, crc); } @@ -313,19 +391,16 @@ static mbsn_error send_msg_footer(mbsn_t* mbsn) { } -static mbsn_error handle_exception(mbsn_t* mbsn, msg_state* req, uint8_t exception) { - msg_state res = *req; - res.fc += 0x80; - - send_msg_header(mbsn, &res, 1); +static mbsn_error handle_exception(mbsn_t* mbsn, uint8_t exception) { + mbsn->msg.fc += 0x80; + send_msg_header(mbsn, 1); put_1(mbsn, exception); return send_msg_footer(mbsn); } -static mbsn_error handle_read_discrete(mbsn_t* mbsn, msg_state* req, - mbsn_error (*callback)(uint16_t, uint16_t, mbsn_bitfield)) { +static mbsn_error handle_read_discrete(mbsn_t* mbsn, mbsn_error (*callback)(uint16_t, uint16_t, mbsn_bitfield)) { mbsn_error err = recv(mbsn, 4); if (err != MBSN_ERROR_NONE) return err; @@ -337,28 +412,26 @@ static mbsn_error handle_read_discrete(mbsn_t* mbsn, msg_state* req, if (err != MBSN_ERROR_NONE) return err; - if (!req->ignored) { + if (!mbsn->msg.ignored) { if (quantity < 1 || quantity > 2000) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - if ((uint32_t) address + (uint32_t) quantity > 65535) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); if (callback) { - mbsn_bitfield bf; + mbsn_bitfield bf = {0}; err = callback(address, quantity, bf); if (err != MBSN_ERROR_NONE) { - if (err < 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + if (mbsn_error_is_exception(err)) + return handle_exception(mbsn, err); else - return err; + return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); } - if (!req->broadcast) { + if (!mbsn->msg.broadcast) { uint8_t discrete_bytes = (quantity / 8) + 1; - msg_state res = res_from_req(req); - - send_msg_header(mbsn, &res, discrete_bytes); + send_msg_header(mbsn, discrete_bytes); put_1(mbsn, discrete_bytes); @@ -372,7 +445,7 @@ static mbsn_error handle_read_discrete(mbsn_t* mbsn, msg_state* req, } } else { - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); } } @@ -380,8 +453,7 @@ static mbsn_error handle_read_discrete(mbsn_t* mbsn, msg_state* req, } -static mbsn_error handle_read_registers(mbsn_t* mbsn, msg_state* req, - mbsn_error (*callback)(uint16_t, uint16_t, uint16_t*)) { +static mbsn_error handle_read_registers(mbsn_t* mbsn, mbsn_error (*callback)(uint16_t, uint16_t, uint16_t*)) { mbsn_error err = recv(mbsn, 4); if (err != MBSN_ERROR_NONE) return err; @@ -393,28 +465,26 @@ static mbsn_error handle_read_registers(mbsn_t* mbsn, msg_state* req, if (err != MBSN_ERROR_NONE) return err; - if (!req->ignored) { + if (!mbsn->msg.ignored) { if (quantity < 1 || quantity > 125) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - if ((uint32_t) address + (uint32_t) quantity > 65535) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); if (callback) { - uint16_t regs[125]; + uint16_t regs[125] = {0}; err = callback(address, quantity, regs); if (err != MBSN_ERROR_NONE) { - if (err < 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + if (mbsn_error_is_exception(err)) + return handle_exception(mbsn, err); else - return err; + return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); } - if (!req->broadcast) { + if (!mbsn->msg.broadcast) { uint8_t regs_bytes = quantity * 2; - msg_state res = res_from_req(req); - - send_msg_header(mbsn, &res, regs_bytes); + send_msg_header(mbsn, regs_bytes); put_1(mbsn, regs_bytes); @@ -428,7 +498,7 @@ static mbsn_error handle_read_registers(mbsn_t* mbsn, msg_state* req, } } else { - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); } } @@ -436,27 +506,27 @@ static mbsn_error handle_read_registers(mbsn_t* mbsn, msg_state* req, } -static mbsn_error handle_read_coils(mbsn_t* mbsn, msg_state* req) { - return handle_read_discrete(mbsn, req, mbsn->callbacks.read_coils); +static mbsn_error handle_read_coils(mbsn_t* mbsn) { + return handle_read_discrete(mbsn, mbsn->callbacks.read_coils); } -static mbsn_error handle_read_discrete_inputs(mbsn_t* mbsn, msg_state* req) { - return handle_read_discrete(mbsn, req, mbsn->callbacks.read_discrete_inputs); +static mbsn_error handle_read_discrete_inputs(mbsn_t* mbsn) { + return handle_read_discrete(mbsn, mbsn->callbacks.read_discrete_inputs); } -static mbsn_error handle_read_holding_registers(mbsn_t* mbsn, msg_state* req) { - return handle_read_registers(mbsn, req, mbsn->callbacks.read_holding_registers); +static mbsn_error handle_read_holding_registers(mbsn_t* mbsn) { + return handle_read_registers(mbsn, mbsn->callbacks.read_holding_registers); } -static mbsn_error handle_read_input_registers(mbsn_t* mbsn, msg_state* req) { - return handle_read_registers(mbsn, req, mbsn->callbacks.read_input_registers); +static mbsn_error handle_read_input_registers(mbsn_t* mbsn) { + return handle_read_registers(mbsn, mbsn->callbacks.read_input_registers); } -static mbsn_error handle_write_single_coil(mbsn_t* mbsn, msg_state* req) { +static mbsn_error handle_write_single_coil(mbsn_t* mbsn) { mbsn_error err = recv(mbsn, 4); if (err != MBSN_ERROR_NONE) return err; @@ -468,33 +538,29 @@ static mbsn_error handle_write_single_coil(mbsn_t* mbsn, msg_state* req) { if (err != MBSN_ERROR_NONE) return err; - if (!req->ignored) { + if (!mbsn->msg.ignored) { if (value != 0 && value != 0xFF00) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); if (mbsn->callbacks.write_single_coil) { err = mbsn->callbacks.write_single_coil(address, value == 0 ? false : true); if (err != MBSN_ERROR_NONE) { - if (err < 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + if (mbsn_error_is_exception(err)) + return handle_exception(mbsn, err); else - return err; + return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); } - if (!req->broadcast) { - msg_state res = res_from_req(req); - - send_msg_header(mbsn, &res, 2); - + if (!mbsn->msg.broadcast) { + send_msg_header(mbsn, 2); put_2(mbsn, address); - err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) return err; } } else { - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); } } @@ -502,7 +568,7 @@ static mbsn_error handle_write_single_coil(mbsn_t* mbsn, msg_state* req) { } -static mbsn_error handle_write_single_register(mbsn_t* mbsn, msg_state* req) { +static mbsn_error handle_write_single_register(mbsn_t* mbsn) { mbsn_error err = recv(mbsn, 4); if (err != MBSN_ERROR_NONE) return err; @@ -514,30 +580,26 @@ static mbsn_error handle_write_single_register(mbsn_t* mbsn, msg_state* req) { if (err != MBSN_ERROR_NONE) return err; - if (!req->ignored) { + if (!mbsn->msg.ignored) { if (mbsn->callbacks.write_single_register) { err = mbsn->callbacks.write_single_register(address, value); if (err != MBSN_ERROR_NONE) { - if (err < 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + if (mbsn_error_is_exception(err)) + return handle_exception(mbsn, err); else - return err; + return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); } - if (!req->broadcast) { - msg_state res = res_from_req(req); - - send_msg_header(mbsn, &res, 1); - + if (!mbsn->msg.broadcast) { + send_msg_header(mbsn, 1); put_2(mbsn, value); - err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) return err; } } else { - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); } } @@ -545,7 +607,7 @@ static mbsn_error handle_write_single_register(mbsn_t* mbsn, msg_state* req) { } -static mbsn_error handle_write_multiple_coils(mbsn_t* mbsn, msg_state* req) { +static mbsn_error handle_write_multiple_coils(mbsn_t* mbsn) { mbsn_error err = recv(mbsn, 5); if (err != MBSN_ERROR_NONE) return err; @@ -566,40 +628,39 @@ static mbsn_error handle_write_multiple_coils(mbsn_t* mbsn, msg_state* req) { if (err != MBSN_ERROR_NONE) return err; - if (!req->ignored) { + if (!mbsn->msg.ignored) { if (quantity < 1 || quantity > 0x07B0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); if (coils_bytes == 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); if ((quantity / 8) + 1 != coils_bytes) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); if (mbsn->callbacks.write_multiple_coils) { err = mbsn->callbacks.write_multiple_coils(address, quantity, coils); if (err != MBSN_ERROR_NONE) { - if (err < 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + if (mbsn_error_is_exception(err)) + return handle_exception(mbsn, err); else - return err; + return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); } - if (!req->broadcast) { - msg_state res = res_from_req(req); - - send_msg_header(mbsn, &res, 4); - + if (!mbsn->msg.broadcast) { + send_msg_header(mbsn, 4); put_2(mbsn, address); put_2(mbsn, quantity); - err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) return err; } } else { - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); } } @@ -607,7 +668,7 @@ static mbsn_error handle_write_multiple_coils(mbsn_t* mbsn, msg_state* req) { } -static mbsn_error handle_write_multiple_registers(mbsn_t* mbsn, msg_state* req) { +static mbsn_error handle_write_multiple_registers(mbsn_t* mbsn) { mbsn_error err = recv(mbsn, 5); if (err != MBSN_ERROR_NONE) return err; @@ -629,40 +690,39 @@ static mbsn_error handle_write_multiple_registers(mbsn_t* mbsn, msg_state* req) if (err != MBSN_ERROR_NONE) return err; - if (!req->ignored) { + if (!mbsn->msg.ignored) { if (quantity < 1 || quantity > 0x007B) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); if (registers_bytes == 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); if (registers_bytes != quantity * 2) - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); if (mbsn->callbacks.write_multiple_registers) { err = mbsn->callbacks.write_multiple_registers(address, quantity, registers); if (err != MBSN_ERROR_NONE) { - if (err < 0) - return handle_exception(mbsn, req, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + if (mbsn_error_is_exception(err)) + return handle_exception(mbsn, err); else - return err; + return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); } - if (!req->broadcast) { - msg_state res = res_from_req(req); - - send_msg_header(mbsn, &res, 4); - + if (!mbsn->msg.broadcast) { + send_msg_header(mbsn, 4); put_2(mbsn, address); put_2(mbsn, quantity); - err = send_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) return err; } } else { - return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_FUNCTION); + return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); } } @@ -670,39 +730,39 @@ static mbsn_error handle_write_multiple_registers(mbsn_t* mbsn, msg_state* req) } -static mbsn_error handle_req_fc(mbsn_t* mbsn, msg_state* req) { +static mbsn_error handle_req_fc(mbsn_t* mbsn) { mbsn_error err; - switch (req->fc) { + switch (mbsn->msg.fc) { case 1: - err = handle_read_coils(mbsn, req); + err = handle_read_coils(mbsn); break; case 2: - err = handle_read_discrete_inputs(mbsn, req); + err = handle_read_discrete_inputs(mbsn); break; case 3: - err = handle_read_holding_registers(mbsn, req); + err = handle_read_holding_registers(mbsn); break; case 4: - err = handle_read_input_registers(mbsn, req); + err = handle_read_input_registers(mbsn); break; case 5: - err = handle_write_single_coil(mbsn, req); + err = handle_write_single_coil(mbsn); break; case 6: - err = handle_write_single_register(mbsn, req); + err = handle_write_single_register(mbsn); break; case 15: - err = handle_write_multiple_coils(mbsn, req); + err = handle_write_multiple_coils(mbsn); break; case 16: - err = handle_write_multiple_registers(mbsn, req); + err = handle_write_multiple_registers(mbsn); break; default: @@ -714,10 +774,10 @@ static mbsn_error handle_req_fc(mbsn_t* mbsn, msg_state* req) { mbsn_error mbsn_server_receive(mbsn_t* mbsn) { - msg_state req = msg_state_create(); + msg_state_reset(mbsn); bool first_byte_received = false; - mbsn_error err = recv_msg_header(mbsn, &req, &first_byte_received); + mbsn_error err = recv_req_header(mbsn, &first_byte_received); if (err != MBSN_ERROR_NONE) { if (!first_byte_received && err == MBSN_ERROR_TIMEOUT) return MBSN_ERROR_NONE; @@ -793,50 +853,29 @@ mbsn_error mbsn_server_receive(mbsn_t* mbsn) { return MBSN_ERROR_TRANSPORT; } */ - return handle_req_fc(mbsn, &req); -} - -static mbsn_error recv_res_header(mbsn_t* mbsn, msg_state* req, msg_state* res_out) { - mbsn_error err = recv_msg_header(mbsn, res_out, NULL); - if (err != MBSN_ERROR_NONE) - return err; - - if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { - if (res_out->transaction_id != req->transaction_id) - return MBSN_ERROR_INVALID_RESPONSE; + err = handle_req_fc(mbsn); + if (err != MBSN_ERROR_NONE) { + if (!mbsn_error_is_exception(err)) + return err; } - if (res_out->ignored) - return MBSN_ERROR_INVALID_RESPONSE; - - if (res_out->fc != req->fc) { - if (res_out->fc - 0x80 == req->fc) { - err = recv(mbsn, 1); - if (err != MBSN_ERROR_NONE) - return err; - - uint8_t exception = get_1(mbsn); - if (exception < 1 || exception > 4) - return MBSN_ERROR_INVALID_RESPONSE; - else - return exception; - } - else { - return MBSN_ERROR_INVALID_RESPONSE; - } - } - - return MBSN_ERROR_NONE; + return err; } 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; - msg_state req = req_create(mbsn->dest_address_rtu, fc); - send_msg_header(mbsn, &req, 4); + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return MBSN_ERROR_INVALID_ARGUMENT; + + msg_state_req(mbsn, fc); + send_msg_header(mbsn, 4); put_2(mbsn, address); put_2(mbsn, quantity); @@ -845,8 +884,7 @@ static mbsn_error read_discrete(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint if (err != MBSN_ERROR_NONE) return err; - msg_state res; - err = recv_res_header(mbsn, &req, &res); + err = recv_res_header(mbsn); if (err != MBSN_ERROR_NONE) return err; @@ -883,11 +921,17 @@ 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; + if (quantity < 1 || quantity > 125) return MBSN_ERROR_INVALID_ARGUMENT; - msg_state req = req_create(mbsn->dest_address_rtu, fc); - send_msg_header(mbsn, &req, 4); + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return MBSN_ERROR_INVALID_ARGUMENT; + + msg_state_req(mbsn, fc); + send_msg_header(mbsn, 4); put_2(mbsn, address); put_2(mbsn, quantity); @@ -896,8 +940,7 @@ mbsn_error read_registers(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t q if (err != MBSN_ERROR_NONE) return err; - msg_state res; - err = recv_res_header(mbsn, &req, &res); + err = recv_res_header(mbsn); if (err != MBSN_ERROR_NONE) return err; @@ -937,8 +980,8 @@ mbsn_error mbsn_read_input_registers(mbsn_t* mbsn, uint16_t address, uint16_t qu mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) { - msg_state req = req_create(mbsn->dest_address_rtu, 5); - send_msg_header(mbsn, &req, 4); + msg_state_req(mbsn, 5); + send_msg_header(mbsn, 4); put_2(mbsn, address); put_2(mbsn, value ? 0xFF00 : 0); @@ -947,35 +990,36 @@ mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) { if (err != MBSN_ERROR_NONE) return err; - msg_state res; - err = recv_res_header(mbsn, &req, &res); - if (err != MBSN_ERROR_NONE) - return err; + if (!mbsn->msg.broadcast) { + err = recv_res_header(mbsn); + if (err != MBSN_ERROR_NONE) + return err; - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; + err = recv(mbsn, 4); + if (err != MBSN_ERROR_NONE) + return err; - uint16_t address_res = get_2(mbsn); - uint16_t value_res = get_2(mbsn); + uint16_t address_res = get_2(mbsn); + uint16_t value_res = get_2(mbsn); - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; + err = recv_msg_footer(mbsn); + if (err != MBSN_ERROR_NONE) + return err; - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; + if (address_res != address) + return MBSN_ERROR_INVALID_RESPONSE; - if (value_res != value) - return MBSN_ERROR_INVALID_RESPONSE; + if (value_res != value) + return MBSN_ERROR_INVALID_RESPONSE; + } return MBSN_ERROR_NONE; } mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t value) { - msg_state req = req_create(mbsn->dest_address_rtu, 5); - send_msg_header(mbsn, &req, 4); + msg_state_req(mbsn, 5); + send_msg_header(mbsn, 4); put_2(mbsn, address); put_2(mbsn, value); @@ -984,27 +1028,28 @@ mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t v if (err != MBSN_ERROR_NONE) return err; - msg_state res; - err = recv_res_header(mbsn, &req, &res); - if (err != MBSN_ERROR_NONE) - return err; + if (!mbsn->msg.broadcast) { + err = recv_res_header(mbsn); + if (err != MBSN_ERROR_NONE) + return err; - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; + err = recv(mbsn, 4); + if (err != MBSN_ERROR_NONE) + return err; - uint16_t address_res = get_2(mbsn); - uint16_t value_res = get_2(mbsn); + uint16_t address_res = get_2(mbsn); + uint16_t value_res = get_2(mbsn); - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; + err = recv_msg_footer(mbsn); + if (err != MBSN_ERROR_NONE) + return err; - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; + if (address_res != address) + return MBSN_ERROR_INVALID_RESPONSE; - if (value_res != value) - return MBSN_ERROR_INVALID_RESPONSE; + if (value_res != value) + return MBSN_ERROR_INVALID_RESPONSE; + } return MBSN_ERROR_NONE; } @@ -1014,10 +1059,13 @@ mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t qu if (quantity < 0 || quantity > 0x07B0) return MBSN_ERROR_INVALID_ARGUMENT; + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return MBSN_ERROR_INVALID_ARGUMENT; + uint8_t coils_bytes = (quantity / 8) + 1; - msg_state req = req_create(mbsn->dest_address_rtu, 15); - send_msg_header(mbsn, &req, 5 + coils_bytes); + msg_state_req(mbsn, 15); + send_msg_header(mbsn, 5 + coils_bytes); put_2(mbsn, address); put_2(mbsn, quantity); @@ -1031,27 +1079,28 @@ mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t qu if (err != MBSN_ERROR_NONE) return err; - msg_state res; - err = recv_res_header(mbsn, &req, &res); - if (err != MBSN_ERROR_NONE) - return err; + if (!mbsn->msg.broadcast) { + err = recv_res_header(mbsn); + if (err != MBSN_ERROR_NONE) + return err; - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; + err = recv(mbsn, 4); + if (err != MBSN_ERROR_NONE) + return err; - uint16_t address_res = get_2(mbsn); - uint16_t quantity_res = get_2(mbsn); + uint16_t address_res = get_2(mbsn); + uint16_t quantity_res = get_2(mbsn); - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; + err = recv_msg_footer(mbsn); + if (err != MBSN_ERROR_NONE) + return err; - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; + if (address_res != address) + return MBSN_ERROR_INVALID_RESPONSE; - if (quantity_res != quantity) - return MBSN_ERROR_INVALID_RESPONSE; + if (quantity_res != quantity) + return MBSN_ERROR_INVALID_RESPONSE; + } return MBSN_ERROR_NONE; } @@ -1061,10 +1110,13 @@ mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_ if (quantity < 0 || quantity > 0x007B) return MBSN_ERROR_INVALID_ARGUMENT; + if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) + return MBSN_ERROR_INVALID_ARGUMENT; + uint8_t registers_bytes = quantity * 2; - msg_state req = req_create(mbsn->dest_address_rtu, 16); - send_msg_header(mbsn, &req, 5 + registers_bytes); + msg_state_req(mbsn, 16); + send_msg_header(mbsn, 5 + registers_bytes); put_2(mbsn, address); put_2(mbsn, quantity); @@ -1078,27 +1130,60 @@ mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_ if (err != MBSN_ERROR_NONE) return err; - msg_state res; - err = recv_res_header(mbsn, &req, &res); + if (!mbsn->msg.broadcast) { + err = recv_res_header(mbsn); + if (err != MBSN_ERROR_NONE) + return err; + + err = recv(mbsn, 4); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t address_res = get_2(mbsn); + uint16_t quantity_res = get_2(mbsn); + + err = recv_msg_footer(mbsn); + if (err != MBSN_ERROR_NONE) + return err; + + if (address_res != address) + return MBSN_ERROR_INVALID_RESPONSE; + + if (quantity_res != quantity) + return MBSN_ERROR_INVALID_RESPONSE; + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_t data_len) { + msg_state_req(mbsn, fc); + send_msg_header(mbsn, data_len); + for (int i = 0; i < data_len; i++) { + put_1(mbsn, ((uint8_t*) (data))[i]); + } + + return send_msg_footer(mbsn); +} + + +mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t data_out_len) { + mbsn_error err = recv_res_header(mbsn); if (err != MBSN_ERROR_NONE) return err; - err = recv(mbsn, 4); + err = recv(mbsn, data_out_len); if (err != MBSN_ERROR_NONE) return err; - uint16_t address_res = get_2(mbsn); - uint16_t quantity_res = get_2(mbsn); + for (int i = 0; i < data_out_len; i++) { + ((uint8_t*) (data_out))[i] = get_1(mbsn); + } err = recv_msg_footer(mbsn); if (err != MBSN_ERROR_NONE) return err; - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; - - if (quantity_res != quantity) - return MBSN_ERROR_INVALID_RESPONSE; - return MBSN_ERROR_NONE; } diff --git a/modbusino.h b/modbusino.h index f93245b..00d7a29 100644 --- a/modbusino.h +++ b/modbusino.h @@ -28,7 +28,7 @@ typedef uint8_t mbsn_bitfield[250]; #define mbsn_bitfield_read(bf, b) ((bool) ((bf)[(b) / 8] & (0x1 << ((b) % 8)))) #define mbsn_bitfield_write(bf, b, v) \ - (((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & !(0x1 << ((b) % 8))))) + (((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8))))) typedef enum mbsn_transport { @@ -57,9 +57,16 @@ typedef struct mbsn_callbacks { typedef struct mbsn_t { - // Private fields - uint8_t msg_buf[260]; - uint16_t msg_buf_idx; + struct { + uint8_t buf[260]; + uint16_t buf_idx; + + uint8_t unit_id; + uint8_t fc; + uint16_t transaction_id; + bool broadcast; + bool ignored; + } msg; mbsn_callbacks callbacks; @@ -73,6 +80,9 @@ typedef struct mbsn_t { } mbsn_t; +static const uint8_t MBSN_BROADCAST_ADDRESS = 0; + + mbsn_error mbsn_client_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf); mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, const mbsn_platform_conf* platform_conf, @@ -102,6 +112,10 @@ 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); +mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_t data_len); + +mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t data_out_len); + const char* mbsn_strerror(int error); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f1dddf9..34dd375 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,10 +2,11 @@ cmake_minimum_required(VERSION 3.21) project(modbusino_tests C) set(CMAKE_C_STANDARD 99) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") include_directories(. ..) -add_definitions(-DMODBUSINO_USE_PRINTF) +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 ec0c5af..56bfa80 100644 --- a/tests/modbusino_tests.c +++ b/tests/modbusino_tests.c @@ -1,9 +1,11 @@ #include "modbusino_tests.h" #include "modbusino.h" #include +#include #include #include + int read_byte_empty(uint8_t* b, int32_t timeout) { return 0; } @@ -135,16 +137,128 @@ void test_server_receive_base(mbsn_transport transport) { } -mbsn_error read_coils_and_error(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) {} +mbsn_error read_discrete(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) { + if (address == 1) + return -1; + if (address == 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; -mbsn_error read_coils_and_no_error(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) {} + if (address == 3) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 10 && quantity == 3) { + mbsn_bitfield_write(coils_out, 0, 1); + mbsn_bitfield_write(coils_out, 1, 0); + mbsn_bitfield_write(coils_out, 2, 1); + } + + return MBSN_ERROR_NONE; +} void test_fc1(mbsn_transport transport) { - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION on calling a FC with unregistered callback"); + 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); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.read_coils = read_discrete}); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + assert(mbsn_read_coils(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + 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"); + 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)); + 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)); + 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)); + 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_coils(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_read_coils(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_read_coils(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + mbsn_bitfield bf; + check(mbsn_read_coils(&CLIENT, 10, 3, bf)); + assert(mbsn_bitfield_read(bf, 0) == 1); + assert(mbsn_bitfield_read(bf, 1) == 0); + assert(mbsn_bitfield_read(bf, 2) == 1); + + stop_client_and_server(); +} + + +void test_fc2(mbsn_transport transport) { + 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); + + stop_client_and_server(); + + start_client_and_server(transport, (mbsn_callbacks){.read_discrete_inputs = read_discrete}); + + should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + assert(mbsn_read_discrete_inputs(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); + + 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"); + 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)); + 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)); + 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)); + 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_discrete_inputs(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + assert(mbsn_read_discrete_inputs(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + assert(mbsn_read_discrete_inputs(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + mbsn_bitfield bf; + check(mbsn_read_discrete_inputs(&CLIENT, 10, 3, bf)); + assert(mbsn_bitfield_read(bf, 0) == 1); + assert(mbsn_bitfield_read(bf, 1) == 0); + assert(mbsn_bitfield_read(bf, 2) == 1); + stop_client_and_server(); } @@ -155,8 +269,7 @@ const char* transports_str[2] = {"TCP", "RTU"}; void for_transports(void (*test_fn)(mbsn_transport), const char* should_str) { for (int t = 0; t < sizeof(transports) / sizeof(mbsn_transport); t++) { - printf("%s: ", transports_str[t]); - should(should_str); + printf("Should %s on %s:\n", should_str, transports_str[t]); test(test_fn(transports[t])); } } @@ -170,5 +283,8 @@ int main() { for_transports(test_fc1, "send and receive FC 01 (0x01) Read Coils"); + for_transports(test_fc2, "send and receive FC 02 (0x02) Read Discrete Inputs"); + + return 0; } diff --git a/tests/modbusino_tests.h b/tests/modbusino_tests.h index 71b4efc..8eecec0 100644 --- a/tests/modbusino_tests.h +++ b/tests/modbusino_tests.h @@ -16,6 +16,10 @@ #define test(f) (nesting++, (f), nesting--) +/* +#define htons(w) (((((uint16_t) (w) &0xFF)) << 8) | (((uint16_t) (w) &0xFF00) >> 8)) +#define ntohs(w) (((((uint16_t) (w) &0xFF)) << 8) | (((uint16_t) (w) &0xFF00) >> 8)) +*/ const uint8_t TEST_SERVER_ADDR = 1; @@ -174,7 +178,8 @@ void* server_listen_thread() { if (is_server_listen_thread_stopped()) break; - check(mbsn_server_receive(&SERVER)); + mbsn_error err = mbsn_server_receive(&SERVER); + check(err); } return NULL; @@ -207,6 +212,9 @@ void start_client_and_server(mbsn_transport transport, mbsn_callbacks server_cal mbsn_set_read_timeout(&SERVER, 1000); mbsn_set_byte_timeout(&SERVER, 100); + mbsn_set_read_timeout(&CLIENT, -1); + mbsn_set_byte_timeout(&CLIENT, 100); + assert(pthread_mutex_lock(&server_stopped_m) == 0); server_stopped = false; assert(pthread_mutex_unlock(&server_stopped_m) == 0);