diff --git a/.gitignore b/.gitignore index 9e9a23911..7bfc6adcb 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ tests/random-test-server tests/unit-test-client tests/unit-test.h tests/unit-test-server +tests/tcp-server-test tests/version tests/stamp-h2 doc/*.html diff --git a/doc/Makefile.am b/doc/Makefile.am index 08eba6d6d..3e8b1d5cb 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -62,7 +62,11 @@ TXT3 = \ modbus_write_bits.txt \ modbus_write_bit.txt \ modbus_write_registers.txt \ - modbus_write_register.txt + modbus_write_register.txt \ + modbus_tcp_server_start.txt \ + modbus_tcp_server_stop.txt \ + modbus_tcp_server_handle.txt \ + modbus_tcp_server_set_select_timeout.txt TXT7 = libmodbus.txt EXTRA_DIST = asciidoc.conf $(TXT3) $(TXT7) diff --git a/doc/modbus_tcp_server_handle.txt b/doc/modbus_tcp_server_handle.txt new file mode 100644 index 000000000..469906d1b --- /dev/null +++ b/doc/modbus_tcp_server_handle.txt @@ -0,0 +1,38 @@ +modbus_tcp_server_handle(3) +=========================== + + +NAME +---- +modbus_tcp_server_handle - handle data exchange on a modbus context + + +SYNOPSIS +-------- +*int modbus_tcp_server_handle(modbus_tcp_server_t* mb_srv_ctx, modbus_mapping_t* mb_map);* + + +DESCRIPTION +----------- +The *modbus_tcp_server_handle()* function exchanges data with connected clients +based on the supplied _mb_map_ data structure. + +Uses a select that defaults to BLOCKING, but that can be changed with +*modbus_tcp_server_set_select_timeout()*. + + +RETURN VALUE +------------ +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + + +SEE ALSO +-------- +linkmb:modbus_server_create[3] +linkmb:modbus_server_destroy[3] + + +AUTHORS +------- +The libmodbus server documentation was written by DEIF A/S diff --git a/doc/modbus_tcp_server_set_select_timeout.txt b/doc/modbus_tcp_server_set_select_timeout.txt new file mode 100644 index 000000000..d00f4816f --- /dev/null +++ b/doc/modbus_tcp_server_set_select_timeout.txt @@ -0,0 +1,32 @@ +modbus_tcp_server_set_select_timeout(3) +======================================= + + +NAME +---- +modbus_tcp_server_set_select_timeout - set the idle timeout for each client. + + +SYNOPSIS +-------- +*int modbus_tcp_server_set_select_timeout(modbus_tcp_server_t* mb_srv_ctx, uint32_t to_sec, uint32_t to_usec);* + + +DESCRIPTION +----------- +The *modbus_tcp_server_set_select_timeout()* changes the select timeout for +*modbus_server_handle()*. + +Default the select is BLOCKING but with this you can set a timeout so +*modbus_server_handle()* returns within the specified time. + + +RETURN VALUE +------------ +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno. + + +AUTHORS +------- +The libmodbus server documentation was written by DEIF A/S diff --git a/doc/modbus_tcp_server_start.txt b/doc/modbus_tcp_server_start.txt new file mode 100644 index 000000000..a4d7e8f33 --- /dev/null +++ b/doc/modbus_tcp_server_start.txt @@ -0,0 +1,36 @@ +modbus_tcp_server_start(3) +========================== + + +NAME +---- +modbus_tcp_server_start - create a tcp modbus server context + + +SYNOPSIS +-------- +*modbus_tcp_server_t* modbus_tcp_server_start(char* ipaddr, uint16_t port, uint16_t max_connections);* + + +DESCRIPTION +----------- +The *modbus_tcp_server_start()* function shall prepare a context for a +standard modbus tcp server on _port_ with suport for max +_max_connections_ simultainious connections. + + +RETURN VALUE +------------ +The function shall return a context for the modbus server if successful. +Otherwise it shall return NULL and set errno. + + +SEE ALSO +-------- +linkmb:modbus_server_destroy[3] +linkmb:modbus_server_handle[3] + + +AUTHORS +------- +The libmodbus server documentation was written by DEIF A/S diff --git a/doc/modbus_tcp_server_stop.txt b/doc/modbus_tcp_server_stop.txt new file mode 100644 index 000000000..cd6fdece9 --- /dev/null +++ b/doc/modbus_tcp_server_stop.txt @@ -0,0 +1,39 @@ +modbus_tcp_server_stop(3) +========================= + + +NAME +---- +modbus_tcp_server_stop - destroy a tcp modbus server context + + +SYNOPSIS +-------- +*int modbus_tcp_server_stop(modbus_tcp_server_t* mb_srv_ctx);* + + +DESCRIPTION +----------- +The *modbus_tcp_server_stop()* function shall set a shutdown flag +which is processed by modbus_server_handle. + +This causes modbus_server_handle to free all context allocated with +*modbus_tcp_server_create()*, and closes all currently connected +modbus clients. + + +RETURN VALUE +------------ +The function shall return 0 if successful. Otherwise it shall return -1 and set +errno + + +SEE ALSO +-------- +linkmb:modbus_server_create[3] +linkmb:modbus_server_handle[3] + + +AUTHORS +------- +The libmodbus server documentation was written by DEIF A/S diff --git a/src/Makefile.am b/src/Makefile.am index 551fe4328..aa624e664 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,9 @@ libmodbus_la_SOURCES = \ modbus-tcp.c \ modbus-tcp.h \ modbus-tcp-private.h \ - modbus-version.h + modbus-version.h \ + modbus-tcp-server.c \ + modbus-tcp-server.h libmodbus_la_LDFLAGS = -no-undefined \ -version-info $(LIBMODBUS_LT_VERSION_INFO) @@ -35,7 +37,7 @@ endif # Header files to install libmodbusincludedir = $(includedir)/modbus -libmodbusinclude_HEADERS = modbus.h modbus-version.h modbus-rtu.h modbus-tcp.h +libmodbusinclude_HEADERS = modbus.h modbus-version.h modbus-rtu.h modbus-tcp.h modbus-tcp-server.h DISTCLEANFILES = modbus-version.h EXTRA_DIST += modbus-version.h.in diff --git a/src/modbus-tcp-server.c b/src/modbus-tcp-server.c new file mode 100644 index 000000000..34332289d --- /dev/null +++ b/src/modbus-tcp-server.c @@ -0,0 +1,383 @@ +/* + * Copyright © 2016 DEIF A/S + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include +#include +#include +#ifndef _MSC_VER +# include +#endif +#include + +#if defined(_WIN32) +# define OS_WIN32 +/* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later. + * minwg32 headers check WINVER before allowing the use of these */ +# ifndef WINVER +# define WINVER 0x0501 +# endif +/* Already set in modbus-tcp.h but it seems order matters in VS2005 */ +# include +# include +# define SHUT_RDWR 2 +# define TIME_UTC 1 +# define close closesocket +#else +# include +# include +# include +# include +# include +#endif + +#include "modbus.h" +#include "modbus-private.h" +#include "modbus-tcp-server.h" +#include +#include + +#include + +#define MB_TCP_SRV_IDLE_TIMEOUT 60 + +/* modbus client data */ +struct modbus_tcp_client_t { + int socket; + time_t last_update; + struct modbus_tcp_client_t* next; +}; + +/* mdbus server data */ +struct _modbus_tcp_server { + int server_socket; + fd_set refset; + fd_set rdset; + int16_t fdmax; + uint16_t max_connections; + uint16_t port; + uint32_t idle_time_sec; + uint32_t select_to_sec; + uint32_t select_to_usec; + uint8_t shutdown; + + modbus_t* ctx; + /* Linked list of all current connections */ + struct modbus_tcp_client_t* conn; +}; + +static void _modbus_tcp_srv_rm_cli(modbus_tcp_server_t* data, + struct modbus_tcp_client_t* link) { + struct modbus_tcp_client_t* sp = NULL; + struct modbus_tcp_client_t* tmp = NULL; + + for (sp = data->conn; sp != NULL; sp = sp->next) { + if (sp == link) { + close(sp->socket); + FD_CLR(sp->socket, &data->refset); + + /* If we are pointing to HEAD, remove head */ + if (tmp == NULL) { + data->conn = sp->next; + free(sp); + break; + } else { + struct modbus_tcp_client_t* tmp_next = sp->next; + + /* check if last link */ + if (tmp_next == NULL) { + tmp->next = NULL; + free(sp); + } else { + tmp->next = tmp_next; + free(sp); + } + } + } + tmp = sp; + } +} + +static void _modbus_tcp_srv_del_oldest_cli(modbus_tcp_server_t* data) { + struct modbus_tcp_client_t* cli_to_remove = NULL; + time_t oldest_time = 0x7FFFFFFF; + + /* Remove client from list of connected clients*/ + struct modbus_tcp_client_t* sp = NULL; + for (sp = data->conn; sp != NULL; sp = sp->next) { + if (sp->last_update < oldest_time) { + cli_to_remove = sp; + oldest_time = sp->last_update; + } + } + + /* If we found one, remove it */ + if (cli_to_remove != NULL) { + _modbus_tcp_srv_rm_cli(data, cli_to_remove); + } +} + +static void _modbus_tcp_srv_add_cli(modbus_tcp_server_t* data, int socket) { + struct modbus_tcp_client_t* sp = NULL; + int connection_nbr = 0; + + /* Handle HEAD */ + if (data->conn == NULL) { + data->conn = malloc(sizeof(struct modbus_tcp_client_t)); + data->conn->last_update = time(NULL); + data->conn->socket = socket; + data->conn->next = NULL; + + /* add to listener*/ + FD_SET(socket, &data->refset); + + if (socket > data->fdmax) { + /* Keep track of the maximum */ + data->fdmax = socket; + } + return; + } + connection_nbr++; /* HEAD link */ + + /* When we have HEAD, add new links */ + for (sp = data->conn; sp != NULL; sp = sp->next) { + connection_nbr++; + if ((sp->socket == socket) || sp->next == NULL) { + + sp->next = malloc(sizeof(struct modbus_tcp_client_t)); + sp->next->last_update = time(NULL); + sp->next->socket = socket; + sp->next->next = NULL; + + /* add to listener*/ + FD_SET(socket, &data->refset); + + if (socket > data->fdmax) { + /** Keep track of the maximum */ + data->fdmax = socket; + } + break; + } + + /* Modbus specification: if no available slots, remove oldest client when new connects */ + if (connection_nbr >= data->max_connections) { + _modbus_tcp_srv_del_oldest_cli(data); + } + } +} + +static void _modbus_tcp_server_stop(modbus_tcp_server_t* srv_ctx) { + + struct modbus_tcp_client_t* tmp = NULL; + + if (srv_ctx != NULL) { + struct modbus_tcp_client_t* cli = srv_ctx->conn; + + /* Close modbus server sockets*/ + close(srv_ctx->server_socket); + + /* Loop though linked list and close + free all client data */ + while (cli != NULL) { + close(cli->socket); + + tmp = cli; + cli = cli->next; + free(tmp); + tmp = NULL; + } + + /* Release main context */ + if (srv_ctx->ctx != NULL) { + modbus_free(srv_ctx->ctx); + srv_ctx->ctx = NULL; + } + + /* Release server data */ + if (srv_ctx != NULL) { + free(srv_ctx); + srv_ctx = NULL; + } + } +} + +modbus_tcp_server_t* modbus_tcp_server_start(char* ipaddr, uint16_t port, uint16_t max_connections) { + modbus_tcp_server_t* data = malloc(sizeof(modbus_tcp_server_t)); + memset(data, 0, sizeof(modbus_tcp_server_t)); + + data->max_connections = max_connections; + data->port = port; + data->idle_time_sec = MB_TCP_SRV_IDLE_TIMEOUT; + data->select_to_sec = MB_TCP_SRV_BLOCKING_TIMEOUT; + data->select_to_usec = 0; + + /* Check select can support the request connections */ + if (data->max_connections > FD_SETSIZE) { + errno = EFBIG; + return NULL; + } + + /* Create new server */ + data->ctx = modbus_new_tcp(ipaddr, port); + if (data->ctx == NULL) { + /* libmodbus has set errno */ + free(data); + return NULL; + } + + /* Open socket */ + data->server_socket = modbus_tcp_listen(data->ctx, 5); + + if (data->server_socket < 0) { + modbus_free(data->ctx); + /* libmodbus has set errno */ + free(data); + return NULL; + } + + /* Clear the reference set of socket */ + FD_ZERO(&data->refset); + + /* Add the server socket */ + FD_SET(data->server_socket, &data->refset); + + /* Keep track of the max file descriptor */ + data->fdmax = data->server_socket; + return data; +} + +int modbus_tcp_server_stop(modbus_tcp_server_t* srv_ctx) { + + if (srv_ctx == NULL) { + return -1; + } + + srv_ctx->shutdown = 1; + /* send shutdown event to accept() in handle function */ + shutdown(srv_ctx->server_socket, SHUT_RDWR); + return 0; +} + +int modbus_tcp_server_handle(modbus_tcp_server_t* srv_ctx, + modbus_mapping_t* mb_map) { + int retval, rc; + + if ((srv_ctx == NULL) || (srv_ctx->ctx == NULL)) { + errno = EBADF; + return -1; + } + + /* Reset select set and select timeout */ + srv_ctx->rdset = srv_ctx->refset; + + /* Blocking select waiting for connections */ + if (srv_ctx->select_to_sec == MB_TCP_SRV_BLOCKING_TIMEOUT + || srv_ctx->select_to_usec == MB_TCP_SRV_BLOCKING_TIMEOUT) { + retval = select(srv_ctx->fdmax + 1, &srv_ctx->rdset, NULL, NULL, NULL); + } else { + /* Non-blocking select with timeout waiting for connections */ + struct timeval scan_ms; + scan_ms.tv_sec = srv_ctx->select_to_sec; + scan_ms.tv_usec = srv_ctx->select_to_usec; + retval = select(srv_ctx->fdmax + 1, &srv_ctx->rdset, NULL, NULL, + &scan_ms); + } + + /* If the context has been destroyed, bailout */ + if ((srv_ctx == NULL) || (srv_ctx->ctx == NULL)) { + errno = EBADF; + return -1; + } + + if (srv_ctx->shutdown) { + _modbus_tcp_server_stop(srv_ctx); + errno = ECONNRESET; + return -1; + } + + if (retval == 0) { + /* timeout, this is OK */ + } else if (retval == -1) ///< Critical error on select, exit + { + /* select has set errno */ + modbus_tcp_server_stop(srv_ctx); + return -1; + } else { + /* New connection request */ + if (FD_ISSET(srv_ctx->server_socket, &srv_ctx->rdset)) { + socklen_t addrlen; + struct sockaddr_storage clientaddr; + int newfd; + + /* Handle new connections */ + addrlen = sizeof(clientaddr); + memset(&clientaddr, 0, sizeof(clientaddr)); + newfd = accept(srv_ctx->server_socket, + (struct sockaddr *) &clientaddr, &addrlen); + + /* Debug if needed */ + if (srv_ctx->ctx->debug) { + char ipstr[INET6_ADDRSTRLEN + 1] = { 0 }; + int port = 0; + + struct sockaddr_in *s = (struct sockaddr_in *) &clientaddr; + port = ntohs(s->sin_port); + getnameinfo((struct sockaddr *) &clientaddr, sizeof(clientaddr), + ipstr, sizeof(ipstr), NULL, 0, 0); + fprintf(stderr, + "MB TCP server on port %d, Incoming connection from %s -> %d\n", + srv_ctx->port, ipstr, port); + } + + if (newfd == -1) { + if (srv_ctx->ctx->debug) { + perror("Server accept() error"); + fprintf(stderr, " Socket: %d on port: %d\r\n", + srv_ctx->server_socket, srv_ctx->port); + } + /* accept has set errno */ + return -1; + } else { + _modbus_tcp_srv_add_cli(srv_ctx, newfd); + } + } + /* Data request */ + else { + struct modbus_tcp_client_t* sp = NULL; + + for (sp = srv_ctx->conn; sp != NULL; sp = sp->next) { + if (FD_ISSET(sp->socket, &srv_ctx->rdset)) { + /* An already connected master has sent a new query */ + uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; + + modbus_set_socket(srv_ctx->ctx, sp->socket); + rc = modbus_receive(srv_ctx->ctx, query); + + if (rc != -1) { + modbus_reply(srv_ctx->ctx, query, rc, mb_map); + sp->last_update = time(NULL);; + /* considering implementing a callback pointer with a registration function + which the user can use for a 'request from IP on port has been handled' event. */ + } else { + _modbus_tcp_srv_rm_cli(srv_ctx, sp); + } + } + } + } + } + return 0; +} + +int modbus_tcp_server_set_select_timeout(modbus_tcp_server_t* srv_ctx, uint32_t to_sec, uint32_t to_usec) { + + if (srv_ctx == NULL) { + errno = EBADF; + return -1; + } + srv_ctx->select_to_sec = to_sec; + srv_ctx->select_to_usec = to_usec; + return 0; +} + diff --git a/src/modbus-tcp-server.h b/src/modbus-tcp-server.h new file mode 100644 index 000000000..4163c9060 --- /dev/null +++ b/src/modbus-tcp-server.h @@ -0,0 +1,26 @@ +/* + * Copyright © 2016 DEIF A/S + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_TCP_SERVER_H +#define MODBUS_TCP_SERVER_H +#include +#include "modbus.h" + +#define MB_TCP_SRV_BLOCKING_TIMEOUT 0xFFFFFFFF // Use with modbus_tcp_server_set_select_timeout() + +typedef struct _modbus_tcp_server modbus_tcp_server_t; + +MODBUS_BEGIN_DECLS + +MODBUS_API modbus_tcp_server_t* modbus_tcp_server_start(char* ipaddr, uint16_t port, uint16_t max_connections); +MODBUS_API int modbus_tcp_server_stop(modbus_tcp_server_t* mb_srv_ctx); +MODBUS_API int modbus_tcp_server_handle(modbus_tcp_server_t* mb_srv_ctx, modbus_mapping_t* mb_map); + +MODBUS_API int modbus_tcp_server_set_select_timeout(modbus_tcp_server_t* mb_srv_ctx, uint32_t to_sec, uint32_t to_usec); + +MODBUS_END_DECLS + +#endif /* MODBUS_TCP_H */ diff --git a/src/modbus-tcp.h b/src/modbus-tcp.h index d67c2393e..51b359e0a 100644 --- a/src/modbus-tcp.h +++ b/src/modbus-tcp.h @@ -8,6 +8,7 @@ #define MODBUS_TCP_H #include "modbus.h" +#include "modbus-tcp-server.h" MODBUS_BEGIN_DECLS diff --git a/tests/Makefile.am b/tests/Makefile.am index 7302c8d73..92542d378 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -8,6 +8,7 @@ noinst_PROGRAMS = \ random-test-client \ unit-test-server \ unit-test-client \ + tcp-server-test \ version common_ldflags = \ @@ -34,6 +35,9 @@ unit_test_server_LDADD = $(common_ldflags) unit_test_client_SOURCES = unit-test-client.c unit-test.h unit_test_client_LDADD = $(common_ldflags) +tcp_server_test_SOURCES = tcp-server-test.c +tcp_server_test_LDADD = $(common_ldflags) -lpthread + version_SOURCES = version.c version_LDADD = $(common_ldflags) diff --git a/tests/tcp-server-test.c b/tests/tcp-server-test.c new file mode 100644 index 000000000..2d6700be8 --- /dev/null +++ b/tests/tcp-server-test.c @@ -0,0 +1,238 @@ +/* + * Copyright © 2016 DEIF A/S + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ASSERT_VAL(_prefix, _cond) { \ + printf(" - %s",_prefix); \ + if (_cond) { \ + printf("OK\n"); \ + ok_cntr++; \ + } else { \ + printf("ERROR\n"); \ + perror("errno"); \ + err_cntr++; \ + } \ +}; + +#define PRINT_HEADER() printf("\n%s\n",__FUNCTION__); + +#define PRINT_FOOTER() { \ + if(err_cntr == 0) { \ + printf(" # TEST PASSED, OK:%2d, ERROR:%2d\n", ok_cntr, err_cntr); \ + return 0; \ + } \ + else { \ + printf(" # TEST FAILED, OK:%2d, ERROR:%2d\n", ok_cntr, err_cntr); \ + return -1; \ + } \ +}; + +#define MAX_CONNECTIONS 10 +#define MB_MAP_BITS 5 +#define MB_MAP_INPUT_BITS 10 +#define MB_MAP_REGISTERS 15 +#define MB_MAP_INPUT_REGISTERS 20 +#define TEST_PORT 503 + +struct mb_server_data { + modbus_tcp_server_t* mb_srv_ctx; + modbus_mapping_t* mb_map; +}; + +static void* mb_server_thread(void* param) { + struct mb_server_data* data = (struct mb_server_data*)param; + + //printf(" - INFO: modbus_server_thread_starting\n"); + while(modbus_tcp_server_handle(data->mb_srv_ctx, data->mb_map) == 0) { + //handling + } + //printf(" - INFO: modbus_server_thread_stopping\n"); + return NULL; +} + +static int test_start_modbus_server(struct mb_server_data* mb_param) { + + int err_cntr = 0; + int ok_cntr = 0; + int rc = 0; + int time_before = 0; + pthread_t id; + + PRINT_HEADER(); + + /* start context */ + mb_param->mb_srv_ctx = modbus_tcp_server_start("127.0.0.1", TEST_PORT, MAX_CONNECTIONS); + ASSERT_VAL("create_modbus_server...", mb_param->mb_srv_ctx != NULL); + + /* create mapping */ + mb_param->mb_map = modbus_mapping_new( + MB_MAP_BITS,MB_MAP_INPUT_BITS,MB_MAP_REGISTERS,MB_MAP_INPUT_REGISTERS); + ASSERT_VAL("create_modbus_mapping...", mb_param->mb_map != NULL); + + /* Check select timeout before we launch a dedicated task */ + time_before = time(NULL); + modbus_tcp_server_set_select_timeout(mb_param->mb_srv_ctx, 2, 0); + + /* Should sleep 2 sec */ + rc = modbus_tcp_server_handle(mb_param->mb_srv_ctx, mb_param->mb_map); + + ASSERT_VAL("verify_modbus_tcp_server_set_select_timeout...", + ((rc == 0) && ((time(NULL) - time_before) >= 2))); + + /* set back to blocking */ + modbus_tcp_server_set_select_timeout(mb_param->mb_srv_ctx,MB_TCP_SRV_BLOCKING_TIMEOUT,0); + + /* spawn server thread */ + pthread_create(&id, NULL, mb_server_thread, (void*)mb_param); + usleep(1000000); + + PRINT_FOOTER(); +} + + +static int test_multiple_connections_to_modbus_server(struct mb_server_data* mb_param) { + + int err_cntr = 0; + int ok_cntr = 0; + int i = 0; + int rc = 0; + uint16_t mb_register = 0; + modbus_t* mb_cli_list[MAX_CONNECTIONS + 1] = {NULL}; + modbus_t* mb_cli = NULL; + + PRINT_HEADER(); + + /* Create many connections to server */ + for(i = 0; i < MAX_CONNECTIONS +1; i++) { + mb_cli_list[i] = modbus_new_tcp("127.0.0.1",TEST_PORT); + if(mb_cli_list[i] != NULL) { + if(modbus_connect(mb_cli_list[i]) == 0) { + rc ++; + usleep(10000); // modbus server only allows 5 incoming telegrams in receive queue, small delay + } + } + } + ASSERT_VAL("create_max+1_amount_of_modbus_clients...", rc == 11); + + mb_cli = mb_cli_list[0]; + rc = modbus_read_registers(mb_cli, 0, 1, &mb_register); + ASSERT_VAL("verify_first_connect_was_closed_because_we_opend_one_too_many...", rc == -1); + + mb_cli = mb_cli_list[1]; + rc = modbus_read_registers(mb_cli, 0, 1, &mb_register); + ASSERT_VAL("verify_second_connection_works...", rc == 1); + + mb_cli = mb_cli_list[MAX_CONNECTIONS]; + rc = modbus_read_registers(mb_cli, 0, 1, &mb_register); + ASSERT_VAL("verify_last_connection_works...", rc == 1); + + /* Close connections to server */ + for(i = 0; i < MAX_CONNECTIONS + 1; i++) { + modbus_free(mb_cli_list[i]); + } + + PRINT_FOOTER(); +} + +static int test_read_write_to_modbus_server(void) { + int err_cntr = 0; + int ok_cntr = 0; + int rc = 0; + uint16_t mb_register = 0; + uint8_t mb_bit = 0; + modbus_t* mb_cli = NULL; + + PRINT_HEADER(); + + mb_cli = modbus_new_tcp("127.0.0.1",TEST_PORT); + modbus_connect(mb_cli); + + /* Write registers and bits */ + rc = 0; + rc += modbus_write_register(mb_cli, 0, 0xA5A5); //OK + rc += modbus_write_register(mb_cli, MB_MAP_REGISTERS-1, 0xA5A5); //OK + rc += modbus_write_bit(mb_cli, 0, 1); //OK + rc += modbus_write_bit(mb_cli, MB_MAP_BITS-1, 1); //OK + ASSERT_VAL("write_to_correct_register_on_server...", rc == 4); + + rc = 0; + rc += modbus_write_register(mb_cli, 15, 0xA5A5); //FAIL + rc += modbus_write_bit(mb_cli, 5, 1); //FAIL + ASSERT_VAL("write_to_faulty_register_on_server...", rc == -2); + + /* Read registers and bits */ + modbus_read_registers(mb_cli, 0, 1, &mb_register); + ASSERT_VAL("read_and_verify_register_from_server(1)...", mb_register == 0xA5A5); + + modbus_read_registers(mb_cli, MB_MAP_REGISTERS-1, 1, &mb_register); + ASSERT_VAL("read_and_verify_register_from_server(2)...", mb_register == 0xA5A5); + + modbus_read_bits(mb_cli, 0, 1, &mb_bit); + ASSERT_VAL("read_and_verify_bits_from_server(1)...", mb_bit == 1); + + modbus_read_bits(mb_cli, MB_MAP_BITS-1, 1, &mb_bit); + ASSERT_VAL("read_and_verify_bits_from_server(2)...", mb_bit == 1); + modbus_free(mb_cli); + + + PRINT_FOOTER(); +} + +static int test_stop_of_modbus_server(struct mb_server_data* mb_param) { + int err_cntr = 0; + int ok_cntr = 0; + int rc = 0; + uint16_t mb_register = 0; + modbus_t* mb_cli = NULL; + + PRINT_HEADER(); + + mb_cli = modbus_new_tcp("127.0.0.1",TEST_PORT); + modbus_connect(mb_cli); + + /* destroy context */ + rc = modbus_tcp_server_stop(mb_param->mb_srv_ctx); + ASSERT_VAL("stop_server...", rc == 0); + + usleep(1000000); + rc = modbus_read_registers(mb_cli, 1, 1, &mb_register); + ASSERT_VAL("test_mb_read_fails_after_stop...", rc == -1); + modbus_free(mb_cli); + + PRINT_FOOTER(); +} + +int main(void) +{ + int rc = 0; + struct mb_server_data mb_param = {0}; + + printf("\n"); + + rc += test_start_modbus_server(&mb_param); + rc += test_multiple_connections_to_modbus_server(&mb_param); + rc += test_read_write_to_modbus_server(); + rc += test_stop_of_modbus_server(&mb_param); + + if(rc == 0) { + printf("\n### ALL TESTS PASSED ###\n"); + return 0; + } + else { + printf("\n\n### ONE OR MORE TESTS FAILED ### \n"); + return -1; + } +}