diff -r 000000000000 -r ae252e0fd9b8 mb_tcp.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mb_tcp.c Sun Mar 05 00:05:46 2017 +0000
@@ -0,0 +1,1705 @@
+/*
+ * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt)
+ *
+ * This file is part of the Modbus library for Beremiz and matiec.
+ *
+ * This Modbus library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this Modbus library. If not, see .
+ *
+ * This code is made available on the understanding that it will not be
+ * used in safety-critical situations without a full and competent review.
+ */
+
+
+
+
+
+#include /* File control definitions */
+#include /* Standard input/output */
+#include
+#include
+#include /* POSIX terminal control definitions */
+#include /* Time structures for select() */
+#include /* POSIX Symbolic Constants */
+#include
+#include /* Error definitions */
+#include /* clock_gettime() */
+#include
+#include
+#include /* required for htons() and ntohs() */
+#include /* TCP level socket options */
+#include /* IP level socket options */
+
+#include
+#include /* sched_yield() */
+
+
+
+#include "sin_util.h" /* internet socket utility functions... */
+#include "mb_layer1.h" /* The public interface this file implements... */
+#include "mb_tcp_private.h"
+
+
+
+/************************************/
+/** **/
+/** Include common code... **/
+/** **/
+/************************************/
+
+#include "mb_time_util.h"
+
+
+//#define ERRMSG
+#define ERRMSG_HEAD "Modbus/TCP: "
+
+
+// #define DEBUG /* uncomment to see the data sent and received */
+
+
+#ifdef DEBUG
+#ifndef ERRMSG
+#define ERRMSG
+#endif
+#endif
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Forward Declarations ****/
+/**** and Defaults ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+ /* A Node Descriptor metadata,
+ * Due to the fact that modbus TCP is connection oriented,
+ * and that if the client detects an error the connection
+ * must be shut down and re-established automatically,
+ * the modbus TCP layer needs to keep the address of the remote server.
+ *
+ * We do this by implementing a node descriptor table, in which each
+ * entry will have the remote address, and the file descriptor
+ * of the socket currently in use.
+ *
+ * We do not pass the file descriptor up to the next higher layer. We
+ * send them the node descriptor instead...
+ */
+#define MB_MASTER_NODE 12
+#define MB_LISTEN_NODE 14
+#define MB_SLAVE_NODE 16
+#define MB_FREE_NODE 18
+typedef sa_family_t nd_type_t;
+
+typedef struct {
+ int fd; /* socket descriptor == file descriptor */
+ /* NOTE:
+ * Modbus TCP says that on error, we should close
+ * a connection and retry with a new connection.
+ * Since it takes time for a socket to close
+ * a connection if the remote server is down,
+ * we close the connection on the socket, close the
+ * socket itself, and create a new one for the new
+ * connection. There will be times when the node will
+ * not have any valid socket, and it will have to
+ * be created on the fly.
+ * When the node does not have a valid socket,
+ * fd will be set to -1
+ */
+ int node_type; /* What kind of use we are giving to this node...
+ * If node_type == MB_MASTER_NODE
+ * The node descriptor was initialised by the
+ * modbus_connect() function.
+ * The node descriptor is being used by a master
+ * device, and the addr contains the address of the slave.
+ * Remember that in this case fd may be >= 0 while
+ * we have a valid connection, or it may be < 0 when
+ * the connection needs to be reset.
+ * If node_type == MB_LISTEN_NODE
+ * The node descriptor was initialised by the
+ * modbus_listen() function.
+ * The node is merely used to accept() new connection
+ * requests. The new slave connections will use another
+ * node to transfer data.
+ * In this case fd must be >= 0.
+ * fd < 0 is an ilegal state and should never occur.
+ * If node_type == MB_SLAVE_NODE
+ * The node descriptor was initialised when a new
+ * connection request arrived on a MB_LISTEN type node.
+ * The node descriptor is being used by a slave device,
+ * and is currently being used to connect to a master.
+ * In this case fd must be >= 0.
+ * fd < 0 is an ilegal state and should never occur.
+ * If node_type == FREE_ND
+ * The node descriptor is currently not being used.
+ * In this case fd is set to -1, but is really irrelevant.
+ */
+ struct sockaddr_in addr; /* The internet adress we are using.
+ * If node_type == MB_MASTER_NODE
+ * addr will be the address of the remote slave
+ * If node_type == MB_LISTEN_NODE
+ * addr will be the address of the local listening port and network interface
+ * If node_type == MB_SLAVE_NODE
+ * addr will be the address of the local port and network interface
+ * of the connection to the specific client.
+ */
+ int listen_node; /* When a slave accepts a connection through a MB_LISTEN_NODE, it will
+ * will use an empty node for the new connection, and configure this new node
+ * to use the type MB_SLAVE_NODE.
+ * The listen_node entry is only used by nodes of type MB_SLAVE_NODE.
+ * In this case, listen_node will be the node of type MB_LISTEN_NODE through
+ * which the connection request came through...
+ */
+ int close_on_silence; /* A flag used only by Master Nodes.
+ * When (close_on_silence > 0), then the connection to the
+ * slave device will be shut down whenever the
+ * modbus_tcp_silence_init() function is called.
+ * Remember that the connection will be automatically
+ * re-established the next time the user wishes to communicate
+ * with the same slave (using this same node descripto).
+ * If the user wishes to comply with the sugestion
+ * in the OpenModbus Spec, (s)he should set this flag
+ * if a silence interval longer than 1 second is expected.
+ */
+ int print_connect_error; /* flag to guarantee we only print an error the first time we
+ * attempt to connect to a emote server.
+ * Stops us from generting a cascade of errors while the slave
+ * is down.
+ * Flag will get reset every time we successfully
+ * establish a connection, so a message is once again generated
+ * on the next error.
+ */
+ u8 *recv_buf; /* This node's receive buffer
+ * The library supports multiple simultaneous connections,
+ * and may need to receive multiple frames through mutiple nodes concurrently.
+ * To make the library thread-safe, we use one buffer for each node.
+ */
+} nd_entry_t;
+
+
+/* please make sure to lock the node table mutex before calling this function */
+static int nd_entry_init(nd_entry_t *nde) {
+ nde->addr.sin_family = AF_INET ;
+ nde->node_type = MB_FREE_NODE;
+ nde->fd = -1; /* not currently connected... */
+ /* initialise recv buffer */
+ nde->recv_buf = malloc(sizeof(u8) * RECV_BUFFER_SIZE);
+ if (nde->recv_buf == NULL)
+ return -1;
+ return 0;
+}
+
+/* please make sure to lock the node table mutex before calling this function */
+static int nd_entry_done(nd_entry_t *nde) {
+ free(nde->recv_buf);
+ return 0;
+}
+
+
+
+typedef struct {
+ /* the array of node descriptors, and current size... */
+ nd_entry_t *node; /* array of node entries. if NULL => node table not initialized */
+ int node_count; /* total number of nodes in the node[] array */
+ int free_node_count; /* number of free nodes in the node[] array */
+ pthread_mutex_t mutex;
+} nd_table_t;
+
+
+
+static int nd_table_done(nd_table_t *ndt) {
+ int count;
+
+ if (ndt->node == NULL)
+ return 0;
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ /* initialise the state of each node in the array... */
+ for (count = 0; count < ndt->node_count; count++) {
+ nd_entry_done(&ndt->node[count]);
+ } /* for() */
+
+ free(ndt->node);
+ pthread_mutex_unlock (&ndt->mutex);
+ pthread_mutex_destroy(&ndt->mutex);
+ *ndt = (nd_table_t){.node=NULL, .node_count=0, .free_node_count=0};
+
+ return 0;
+}
+
+
+
+
+#if 1
+/* nd_table_init()
+ * Version 1 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to confirm that it was already correctly
+ * initialized with the requested number of nodes.
+ */
+static int nd_table_init(nd_table_t *ndt, int nd_count) {
+ int count;
+
+ if (ndt->node != NULL) {
+ /* this function has already been called, and the node table is already initialised */
+ return (ndt->node_count == nd_count)?0:-1;
+ }
+
+ /* initialise the node table mutex... */
+ pthread_mutex_init(&ndt->mutex, NULL);
+ if (pthread_mutex_lock(&ndt->mutex) != 0) {
+#ifdef DEBUG
+ perror("pthread_mutex_lock()");
+ fprintf(stderr, "[%lu] Unable to lock newly crated mutex while creating new node table!\n", pthread_self());
+#endif
+ pthread_mutex_destroy(&ndt->mutex);
+ return -1;
+ }
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = malloc(sizeof(nd_entry_t) * nd_count);
+ if (ndt->node == NULL) {
+#ifdef DEBUG
+ perror("malloc()");
+ fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ perror("malloc()");
+ fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n");
+#endif
+ pthread_mutex_unlock (&ndt->mutex);
+ pthread_mutex_destroy(&ndt->mutex);
+ return -1;
+ }
+
+ /* initialise the state of each node in the array... */
+ for (count = 0; count < nd_count; count++) {
+ if (nd_entry_init(&ndt->node[count]) < 0) {
+ pthread_mutex_unlock(&ndt->mutex);
+ nd_table_done(ndt);
+ return -1;
+ }
+ ndt->node_count = count+1;
+ ndt->free_node_count = count+1;
+ } /* for() */
+
+ ndt->node_count = nd_count;
+ ndt->free_node_count = nd_count;
+
+ pthread_mutex_unlock(&ndt->mutex);
+ return nd_count; /* number of succesfully created nodes! */
+}
+
+
+#else
+/* nd_table_init()
+ * Version 2 of the nd_table_init() function.
+ * If called more than once, 2nd and any subsequent calls will
+ * be interpreted as a request to reserve an extra new_nd_count
+ * number of nodes. This will be done using realloc().
+ */
+static int nd_table_init(nd_table_t *ndt, int new_nd_count) {
+ int count;
+
+ if (ndt->node == NULL) {
+ /* Node table nt yet initialized => we must initialise the node table mutex... */
+ pthread_mutex_init(&ndt->mutex, NULL);
+ }
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ /* initialise the node descriptor metadata array... */
+ ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count));
+ if (ndt->node == NULL) {
+#ifdef DEBUG
+ perror("malloc()");
+ fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ perror("malloc()");
+ fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n");
+#endif
+ pthread_mutex_unlock (&ndt->mutex);
+ pthread_mutex_destroy(&ndt->mutex);
+ return -1;
+ }
+
+ /* initialise the state of each newly added node in the array... */
+ for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) {
+ if (nd_entry_init(&ndt->node[count]) < 0) {
+ pthread_mutex_unlock(&ndt->mutex);
+ return -1;
+ }
+ } /* for() */
+ ndt->node_count += new_nd_count;
+ ndt->free_node_count += new_nd_count;
+
+ pthread_mutex_unlock(&ndt->mutex);
+ return new_nd_count; /* number of succesfully created nodes! */
+}
+#endif
+
+
+static int nd_table_get_free_node(nd_table_t *ndt, nd_type_t nd_type) {
+ int count;
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ /* check for free nodes... */
+ if (ndt->free_node_count <= 0) {
+ /* no free nodes... */
+ pthread_mutex_unlock(&ndt->mutex);
+ return -1;
+ }
+
+ /* Decrement the free node counter...*/
+ ndt->free_node_count--;
+
+ /* search for a free node... */
+ for (count = 0; count < ndt->node_count; count++) {
+ if(ndt->node[count].node_type == MB_FREE_NODE) {
+ /* found one!! Allocate it to the new type! */
+ ndt->node[count].node_type = nd_type;
+ pthread_mutex_unlock(&ndt->mutex);
+ return count;
+ }
+ } /* for() */
+
+ /* Strange... We should have free nodes, but we didn't finda any! */
+ /* Let's try to get into a consistent state, and return an error! */
+ ndt->free_node_count = 0;
+ pthread_mutex_unlock(&ndt->mutex);
+ return -1;
+}
+
+
+
+static void nd_table_close_node(nd_table_t *ndt, int nd) {
+
+ /* lock the mutex */
+ while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield();
+
+ if(ndt->node[nd].node_type == MB_FREE_NODE) {
+ /* Node already free... */
+ pthread_mutex_unlock(&ndt->mutex);
+ return;
+ }
+
+ /* Increment the free node counter...*/
+ ndt->free_node_count++;
+ /* Mark the node as being free. */
+ ndt->node[nd].node_type = MB_FREE_NODE;
+
+ pthread_mutex_unlock(&ndt->mutex);
+ return;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Global Library State ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+ /* The node descriptor table... */
+ /* NOTE: The node_table_ Must be initialized correctly here! */
+static nd_table_t nd_table_ = {.node=NULL, .node_count=0, .free_node_count=0};
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Local Utility functions... ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+#define min(a,b) ((ab)?a:b)
+
+/************************************/
+/** **/
+/** Configure socket for Modbus **/
+/** **/
+/************************************/
+
+
+static int configure_socket(int socket_id) {
+
+ /* configure the socket */
+ /* Set it to be non-blocking. This is safe because we always use select() before reading from it!
+ * It is also required for the connect() call. The default timeout in the TCP stack is much too long
+ * (typically blocks for 128 s ??) when the connect does not succedd imediately!
+ */
+ if (fcntl(socket_id, F_SETFL, O_NONBLOCK) < 0) {
+#ifdef ERRMSG
+ perror("fcntl()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'non-blocking' option.\n");
+#endif
+ return -1;
+ }
+
+ /* configure the socket */
+ /* set the TCP no delay flag. */
+ {int bool_opt = 1;
+ if (setsockopt(socket_id, SOL_TCP, TCP_NODELAY,
+ (const void *)&bool_opt, sizeof(bool_opt))
+ < 0) {
+#ifdef ERRMSG
+ perror("setsockopt()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'TCP no delay' option.\n");
+#endif
+ return -1;
+ }
+ }
+
+ /* set the IP low delay option. */
+ {int priority_opt = IPTOS_LOWDELAY;
+ if (setsockopt(socket_id, SOL_IP, IP_TOS,
+ (const void *)&priority_opt, sizeof(priority_opt))
+ < 0) {
+#ifdef ERRMSG
+ perror("setsockopt()");
+ fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'IP low delay' option.\n");
+#endif
+ return -1;
+ }
+ }
+
+#if 0
+ /* send buffer */
+ /* NOTE: For slave devices, that may be receiving multiple
+ * requests before they have a chance to reply to the first,
+ * it probably is a good idea to have a large receive buffer.
+ * So it is best to leave it with the default configuration, as it is
+ * larger than the largest Modbus TCP frame.
+ *
+ * For the send buffer, a smaller buffer should suffice.
+ * However, it probably does not make sense to
+ * waste time asking for a smaller buffer, since the larger
+ * default buffer has already been allocated (the socket has already
+ * been created!)
+ *
+ * We might just as well leave out the configuration of the socket
+ * buffer size...
+ */
+#define SOCK_BUF_SIZE 300 /* The size proposed in the Modbus TCP spec. */
+ {int sock_buf_size;
+ sock_buf_size = SOCK_BUF_SIZE;
+ if (setsockopt(socket_id, SOL_SOCKET, SO_SNDBUF,
+ (const void *)&sock_buf_size, sizeof(sock_buf_size))
+ < 0)
+ return -1;
+ /* recv buffer */
+ sock_buf_size = SOCK_BUF_SIZE;
+ if (setsockopt(socket_id, SOL_SOCKET, SO_RCVBUF,
+ (const void *)&sock_buf_size, sizeof(sock_buf_size))
+ < 0)
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+
+/************************************/
+/** **/
+/** Connect socket to remote host **/
+/** **/
+/************************************/
+
+/* This function will create a new socket, and connect it to a remote host... */
+static inline int open_connection(int nd, const struct timespec *timeout) {
+ int socket_id, con_res;
+
+#ifdef DEBUG
+ printf("[%lu] open_connection(): called, nd = %d\n", pthread_self(), nd);
+#endif
+
+ if (nd_table_.node[nd].fd >= 0)
+ /* nd already connected) */
+ return nd_table_.node[nd].fd;
+
+ if (nd_table_.node[nd].addr.sin_family != AF_INET)
+ /* invalid remote address, or invalid nd */
+ return -1;
+
+ /* lets try to connect... */
+ /* create the socket */
+ if ((socket_id = socket(PF_INET, DEF_TYPE, 0 /* protocol_num */)) < 0) {
+#ifdef DEBUG
+ perror("socket()");
+ fprintf(stderr, "[%lu] Error creating socket\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ perror("socket()");
+ fprintf(stderr, ERRMSG_HEAD "Error creating socket\n");
+#endif
+ return -1;
+ }
+
+ /* configure the socket - includes setting non-blocking option! */
+ if (configure_socket(socket_id) < 0) {
+ close(socket_id);
+ return -1;
+ };
+
+ /* establish the connection to remote host */
+ con_res = connect(socket_id,
+ (struct sockaddr *)&(nd_table_.node[nd].addr),
+ sizeof(nd_table_.node[nd].addr));
+
+ /* The following condition is not strictly necessary
+ * (we could let the code fall through)
+ * but it does make the code easier to read/understand...
+ */
+ if (con_res >= 0)
+ goto success_exit; /* connected succesfully on first try! */
+
+ if (con_res < 0) {
+ if ((errno != EINPROGRESS) && (errno != EALREADY))
+ goto error_exit; /* error in connection request! */
+
+ /* connection request is ongoing */
+ /* EINPROGRESS -> first call to connect, EALREADY -> subsequent calls to connect */
+ /* Must wait for connect to complete at most 'timeout' seconds */
+ {fd_set fdset;
+ int res, so_error;
+ socklen_t len;
+ struct timespec end_time, *et_ptr;
+
+ et_ptr = NULL;
+ if (timeout != NULL) {
+ et_ptr = &end_time;
+ *et_ptr = timespec_add_curtime(*timeout);
+ }
+
+ FD_ZERO(&fdset);
+ FD_SET(socket_id, &fdset);
+
+ res = my_select(socket_id+1, NULL, &fdset, et_ptr);
+ if (res < 0) goto error_exit; /* error on call to select */
+ if (res == 0) goto error_exit; /* timeout */
+ /* (res > 0) -> connection attemt completed. May have been success or failure! */
+
+ len = sizeof(so_error);
+ res = getsockopt(socket_id, SOL_SOCKET, SO_ERROR, &so_error, &len);
+ if (res < 0) goto error_exit; /* error on call to getsockopt */
+ if (so_error != 0) goto error_exit; /* error on connection attempt */
+ goto success_exit; /* succesfully completed connection attempt! */
+ /* goto sucess_exit is not strcitly necessary - we could let the code fall through! */
+ }
+ }
+
+success_exit:
+ nd_table_.node[nd].fd = socket_id;
+ /* Succesfully established connection => print a message next time we have error. */
+ nd_table_.node[nd].print_connect_error = 1;
+
+#ifdef DEBUG
+ printf("[%lu] open_connection(): returning...\n", pthread_self());
+#endif
+ return socket_id;
+
+error_exit:
+#ifdef ERRMSG
+ if (nd_table_.node[nd].print_connect_error > 0) {
+ perror("connect()");
+ fprintf(stderr, ERRMSG_HEAD "Error establishing socket connection.\n");
+ /* do not print more error messages for this node... */
+ nd_table_.node[nd].print_connect_error = 0;
+ }
+#endif
+ close(socket_id);
+ return -1;
+}
+
+
+/* This function will accept a new connection request, and attribute it to a new node... */
+static inline int accept_connection(int nd) {
+ int socket_id, new_nd;
+
+#ifdef DEBUG
+ printf("[%lu] accept_connection(): called, nd = %d\n", pthread_self(), nd);
+#endif
+
+ /* NOTE: We MUST accccept8) all connection requests, even if no new node is available.
+ * => We first accept the connection request, and only later look for a node.
+ * If no node is free/available for this new connections request, the
+ * connection will be accepted and immediately closed.
+ * Reason:
+ * When the library is used for a Modbus/TCP server and no free node is
+ * available, if we do not accept() all newly arrived connection requests
+ * we would enter an infinite loop calling
+ * - select() (in modbus_tcp_read())
+ * - and accept_connection().
+ * Note that select() will continue to return immediately if the
+ * connection request is not accept()ted!
+ */
+ /* lets accept new connection request... */
+ if ((socket_id = accept(nd_table_.node[nd].fd, NULL, NULL)) < 0) {
+#ifdef ERRMSG
+ perror("accept()");
+ fprintf(stderr, ERRMSG_HEAD "Error while waiting for connection request from new client\n");
+#endif
+ /* error establishing new connection... */
+ return -1;
+ }
+
+ /* find a free node */
+ if ((new_nd = nd_table_get_free_node(&nd_table_, MB_SLAVE_NODE)) < 0) {
+ /* no available free nodes for the new connection... */
+ close(socket_id);
+ return -1;
+ }
+
+ /* configure the socket - includes setting the non-blocking option! */
+ if (configure_socket(socket_id) < 0) {
+ nd_table_close_node(&nd_table_, new_nd); /* first free up the un-used node. */
+ close(socket_id);
+ return -1;
+ }
+
+ /* set up the node entry and update the fd sets */
+ nd_table_.node[new_nd].fd = socket_id;
+ nd_table_.node[new_nd].listen_node = nd;
+
+#ifdef DEBUG
+ printf("[%lu] accept_connection(): returning new_nd = %d\n", pthread_self(), new_nd);
+#endif
+ return new_nd;
+}
+
+
+static inline void close_connection(int nd) {
+ if (nd_table_.node[nd].fd >= 0) {
+ /* disconnect the tcp connection */
+ shutdown(nd_table_.node[nd].fd, SHUT_RDWR);
+#ifdef ERRMSG
+ int res =
+#endif
+ close(nd_table_.node[nd].fd);
+#ifdef ERRMSG
+ if (res < 0) {
+ perror("close()");
+ fprintf(stderr, ERRMSG_HEAD "Error closing socket\n");
+ }
+#endif
+ nd_table_.node[nd].fd = -1;
+ }
+
+ if (nd_table_.node[nd].node_type == MB_SLAVE_NODE) {
+ /* If it is a slave node, we will not be receiving any more data over this disconnected node,
+ * (MB_SLAVE_NODE do not get re-connected!), so we free the node...
+ */
+ nd_table_close_node(&nd_table_, nd);
+ }
+}
+
+
+
+/************************************/
+/** **/
+/** Data format conversion **/
+/** **/
+/************************************/
+
+/*
+ * Functions to convert u16 variables
+ * between network and host byte order
+ *
+ * NOTE: Modbus uses MSByte first, just like
+ * tcp/ip, so we use the htons() and
+ * ntoh() functions to guarantee
+ * code portability.
+ */
+
+static inline u16 mb_hton(u16 h_value) {
+/* return h_value; */
+ return htons(h_value);
+}
+
+static inline u16 mb_ntoh(u16 m_value) {
+/* return m_value; */
+ return ntohs(m_value);
+}
+
+static inline u8 msb(u16 value) {
+/* return Most Significant Byte of value; */
+ return (value >> 8) & 0xFF;
+}
+
+static inline u8 lsb(u16 value) {
+/* return Least Significant Byte of value; */
+ return value & 0xFF;
+}
+
+#define u16_v(char_ptr) (*((u16 *)(&(char_ptr))))
+
+
+/************************************/
+/** **/
+/** Build/Check a frame header **/
+/** **/
+/************************************/
+
+/* A modbus TCP frame header has 6 bytes...
+ * header[0-1] -> transaction id
+ * header[2-3] -> must be 0
+ * header[4-5] -> frame data length (must be <= 255)
+ */
+#if TCP_HEADER_LENGTH < 6
+#error This code assumes a header size of 6 bytes, but TCP_HEADER_LENGTH < 6
+#endif
+
+static inline void build_header(u8 *header,
+ u16 transaction_id,
+ u16 byte_count)
+{
+ u16_v(header[0]) = mb_hton(transaction_id);
+ header[2] = 0;
+ header[3] = 0;
+ u16_v(header[4]) = mb_hton(byte_count);
+}
+
+
+static inline int check_header(u8 *header,
+ u16 *transaction_id,
+ u16 *byte_count)
+{
+ if ((header[2] != 0) || (header[3] != 0))
+ return -1;
+
+ *transaction_id = mb_ntoh(*(u16 *)(header + 0));
+ *byte_count = mb_ntoh(*(u16 *)(header + 4));
+
+ if (*byte_count > MAX_L2_FRAME_LENGTH)
+ return -1;
+
+ return 0;
+}
+
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Sending of Modbus TCP Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+// pthread_mutex_t sendmsg_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* NOTE: this function MUST be thread safe!! */
+int modbus_tcp_write(int nd, /* node descriptor */
+ u8 *data,
+ size_t data_length,
+ u16 transaction_id,
+ const struct timespec *transmit_timeout
+ )
+{
+#define data_vector_size 2
+
+ u8 header[TCP_HEADER_LENGTH];
+ struct iovec data_vector[data_vector_size] = {
+ {(void *)header, TCP_HEADER_LENGTH},
+ {NULL, 0}};
+ struct msghdr msg = {NULL, 0, data_vector, data_vector_size, NULL, 0, 0};
+ int res, bytes_sent;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_write(): called... nd=%d\n", pthread_self(), nd);
+#endif
+
+ if ((nd >= nd_table_.node_count) || (nd < 0))
+ /* invalid node descriptor... */
+ return -1;
+
+#ifdef DEBUG
+// printf("[%lu] locking mutex...\n", pthread_self());
+#endif
+// while (pthread_mutex_lock(&sendmsg_mutex) != 0);
+
+ /*************************
+ * prepare the header... *
+ *************************/
+ build_header(header, transaction_id, data_length);
+#ifdef DEBUG
+/* Print the hex value of each character that is about to be
+ * sent over the bus.
+ */
+ { int i;
+ printf("modbus_tcp_write(): sending data...\n");
+ for(i = 0; i < TCP_HEADER_LENGTH; i++)
+ printf("[0x%2X]", header[i]);
+ for(i = 0; i < data_length; i++)
+ printf("[0x%2X]", data[i]);
+ printf("\n");
+ }
+#endif
+
+ /******************************************
+ * do we need to re-establish connection? *
+ ******************************************/
+ if (open_connection(nd, transmit_timeout) < 0) {
+#ifdef DEBUG
+ fprintf(stderr, "[%lu] modbus_tcp_write(): could not establish connection...\n", pthread_self());
+#endif
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "could not establish connection...\n");
+#endif
+ return -1;
+ }
+
+ /**********************
+ * write to output... *
+ **********************/
+ /* TWO ALTERNATIVE IMPLEMENTATIONS !!! */
+#if 0
+ /* write header */
+ bytes_sent = 0;
+ while (1) {
+ res = write(nd_table_.node[nd].fd, header+bytes_sent, TCP_HEADER_LENGTH-bytes_sent);
+ if (res < 0) {
+ if ((errno != EAGAIN ) && (errno != EINTR )) {
+ /* error sending message... */
+ close_connection(nd);
+ return -1;
+ } else {
+ continue;
+ }
+ } else {
+ /* res >= 0 */
+ bytes_sent += res;
+ if (bytes_sent >= TCP_HEADER_LENGTH) {
+ break;
+ }
+ }
+ }
+
+ /* write data */
+ bytes_sent = 0;
+ while (1) {
+ res = write(nd_table_.node[nd].fd, data+bytes_sent, data_length-bytes_sent);
+ if (res < 0) {
+ if ((errno != EAGAIN ) && (errno != EINTR )) {
+ /* error sending message... */
+ close_connection(nd);
+ return -1;
+ } else {
+ continue;
+ }
+ } else {
+ /* res >= 0 */
+ bytes_sent += res;
+ if (bytes_sent >= data_length) {
+ /* query succesfully sent! */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), TCP_HEADER_LENGTH+data_length);
+#endif
+ return data_length;
+ }
+ }
+ }
+
+ /**********************
+ * write to output... *
+ **********************/
+#else
+ /* We are optimising for the most likely case, and in doing that
+ * we are making the least likely case have worse behaviour!
+ * Read on for an explanation...
+ *
+ * - The optimised behaviour for the most likely case:
+ * We have set the NO_DELAY flag on the socket, so the IP datagram
+ * is not delayed and is therefore sent as soon as any data is written to
+ * the socket.
+ * In order to send the whole message in a single IP datagram, we have to
+ * write both the the header and the data with a single call to write()
+ * In order to not to have to copy the data around just to add the
+ * message header, we use sendmsg() instead of write()!
+ *
+ * - The worse behaviour for the least likely case:
+ * If for some reason only part of the data is sent with the first call to
+ * write(), a datagram is sent right away, and the subsequent data will
+ * be sent in another datagram. :-(
+ */
+ /* NOTE: since snedmsg() is not thread safe, we use a mutex to protect access to this function... */
+
+ data_vector[data_vector_size - 1].iov_base = data;
+ data_vector[data_vector_size - 1].iov_len = data_length;
+ data_vector[ 0].iov_base = header;
+ data_vector[ 0].iov_len = TCP_HEADER_LENGTH;
+ bytes_sent = 0;
+ while (1) {
+ int sendmsg_errno;
+ /* Please see the comment just above the main loop!! */
+ res = sendmsg(nd_table_.node[nd].fd, &msg, 0);
+ sendmsg_errno = errno;
+ if (res < 0) {
+ if ((sendmsg_errno != EAGAIN ) && (sendmsg_errno != EINTR )) {
+ /* error sending message... */
+ close_connection(nd);
+ return -1;
+ } else {
+ continue;
+ }
+ } else {
+ /* res >= 0 */
+ bytes_sent += res;
+ if (bytes_sent >= data_length + TCP_HEADER_LENGTH) {
+ /* query succesfully sent! */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), bytes_sent);
+#endif
+// pthread_mutex_unlock(&sendmsg_mutex);
+#ifdef DEBUG
+// printf("[%lu] unlocked mutex...\n", pthread_self());
+#endif
+ return data_length;
+ }
+
+ /* adjust the data_vector... */
+ if (res < data_vector[0].iov_len) {
+ u8* tmp = data_vector[0].iov_base;
+ tmp += res;
+ data_vector[0].iov_len -= res;
+ data_vector[0].iov_base = tmp;
+ } else {
+ u8* tmp = data_vector[1].iov_base;
+ tmp += res;
+ res -= data_vector[0].iov_len;
+ data_vector[0].iov_len = 0;
+ data_vector[1].iov_len -= res;
+ data_vector[1].iov_base = tmp;
+ }
+ }
+ } /* while (1) */
+#endif
+
+ /* humour the compiler... */
+// pthread_mutex_unlock(&sendmsg_mutex);
+#ifdef DEBUG
+// printf("[%lu] unlocked mutex...\n", pthread_self());
+#endif
+ return -1;
+}
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Receiving Modbus TCP Frames ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+/* A helper function to modbus_tcp_read()
+ *
+ * WARNING: The semantics of this function are not what you would expect!
+ *
+ * if (data_already_available != 0)
+ * It assumes that select() has already been called before
+ * this function got called, and we are therefore guaranteed
+ * to have at least one byte to read off the socket (the fd).
+ *
+ * if (data_already_available == 0)
+ * it starts off by calling select()!
+ *
+ *
+ * NOTE: Ususal select semantics for (a: end_time == NULL) and
+ * (b: *end_time == 0) also apply.
+ *
+ * (a) Indefinite timeout
+ * (b) Try once, and and quit if no data available.
+ */
+/* RETURNS: number of bytes read
+ * -1 read error!
+ * -2 timeout
+ */
+static int read_bytes(int fd,
+ u8 *data,
+ int max_data_count,
+ const struct timespec *end_time,
+ int data_already_available)
+{
+ fd_set rfds;
+ int res, data_count;
+
+ data_count = 0;
+
+ while (data_count < max_data_count) {
+ /*============================*
+ * wait for data availability *
+ *============================*/
+ if (data_already_available == 0) {
+ int sel_res;
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+ sel_res = my_select(fd + 1, &rfds, NULL, end_time);
+ if (sel_res < 0)
+ return -1;
+ if (sel_res == 0)
+ /* timeout! */
+ return -2;
+ }
+
+ /*============================*
+ * read the available data... *
+ *============================*/
+ res = read(fd, data + data_count, max_data_count - data_count);
+ if (res == 0) {
+ /* We are guaranteed to have data to read off the fd since we called
+ * select(), but read() returned 0 bytes.
+ * This means that the remote process has closed down the connection,
+ * so we return 0.
+ */
+ return 0;
+ }
+
+ if (res < 0) {
+ if (errno != EINTR)
+ return -1;
+ else
+ res = 0;
+ }
+#ifdef DEBUG
+ {/* display the hex code of each character received */
+ int i;
+ for (i=0; i < res; i++)
+ printf("<0x%2X>", *(data + data_count + i));
+ }
+#endif
+ data_count += res;
+ data_already_available = 0;
+ } /* while ()*/
+
+ /* data read succesfully... */
+ return data_count;
+}
+
+
+
+/***************************************/
+/** **/
+/** Read a Modbus TCP frame **/
+/** off a single identified node. **/
+/** **/
+/***************************************/
+
+/* This private function will read a Modbus TCP frame off a single identified node
+ * that we know before hand that has data ready to be read off it. The data may or may not be
+ * a valid Modbus TCP frame. It is up to this function to figure that out.
+ */
+/* NOTES:
+ * - We re-use the recv_buf_ to load the frame header, so we have to make
+ * sure that the buffer is large enough to take it...
+ */
+ /* RETURNS: number of bytes read
+ * -1 on read from file/node error
+ * -2 on timeout
+ */
+#if RECV_BUFFER_SIZE < TCP_HEADER_LENGTH
+#error The receive buffer is smaller than the frame header length.
+#endif
+
+static int modbus_tcp_read_frame(int nd,
+ u16 *transaction_id,
+ struct timespec *ts_ptr) {
+ int fd, res;
+ u16 frame_length;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): reading off nd=%d\n", pthread_self(), nd);
+#endif
+ /*=========================*
+ * read a Modbus TCP frame *
+ *=========================*/
+ /* assume error... */
+ fd = nd_table_.node[nd].fd;
+
+ /*-------------*
+ * read header *
+ *-------------*/
+ if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, TCP_HEADER_LENGTH, ts_ptr, 1)) != TCP_HEADER_LENGTH) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): frame with insuficient bytes for a valid header...\n", pthread_self());
+#endif
+ if (res < 0) return res;
+ return -1;
+ }
+
+ /* let's check for header consistency... */
+ if (check_header(nd_table_.node[nd].recv_buf, transaction_id, &frame_length) < 0) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): frame with non valid header...\n", pthread_self());
+#endif
+ return -1;
+ }
+
+ /*-----------*
+ * read data *
+ *-----------*/
+ if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, frame_length, ts_ptr, 0)) != frame_length) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read_frame(): frame with non valid frame length...\n", pthread_self());
+#endif
+ if (res < 0) return res;
+ return -1;
+ }
+
+ /* frame received succesfully... */
+#ifdef DEBUG
+ printf("\n");
+#endif
+ return frame_length;
+}
+
+
+
+
+/***************************************/
+/** **/
+/** Read a Modbus TCP frame **/
+/** OR Accept connection requests **/
+/** off possibly multiple node... **/
+/** **/
+/***************************************/
+
+/* The public function that reads a valid modbus frame.
+ * The frame is read from...:
+ * - if (nd >= 0) and (nd is of type MB_MASTER_NODE or MB_SLAVE_NODE)
+ * The frame is read from the node descriptor nd
+ * - if (nd >= 0) and (nd is of type MB_LISTEN_NODE)
+ * The frame is read from the all node descriptors of type MB_SLAVE_NODE that were
+ * opened as a consequence of a connection request to the nd slave.
+ * In this case, new connection requests to nd will also be accepted!
+ * - if (nd == -1)
+ * The frame is read from any valid and initialised node descriptor.
+ * In this case, new connection requests to any nd of type MB_LISTEN_NODE will also be accepted!
+ * In this case, the node where the data is eventually read from is returned in *nd.
+ *
+ * The send_data and send_length parameters are ignored...
+ * (However, these parameters must stay in order to keep the function
+ * interface identical to the ASCII and RTU versons!)
+ *
+ * return value: The length (in bytes) of the valid frame,
+ * -1 on error
+ *
+ * NOTE: Ususal select semantics for (a: recv_timeout == NULL) and
+ * (b: *recv_timeout == 0) also apply.
+ *
+ * (a) Indefinite timeout
+ * (b) Try once, and and quit if no data available.
+ */
+
+ /* RETURNS: number of bytes read
+ * -1 on read from file/node error
+ * -2 on timeout
+ */
+int modbus_tcp_read(int *nd, /* node descriptor */
+ u8 **recv_data_ptr,
+ u16 *transaction_id,
+ const u8 *send_data, /* ignored ! */
+ int send_length, /* ignored ! */
+ const struct timespec *recv_timeout) {
+
+ struct timespec end_time, *ts_ptr;
+ u8 *local_recv_data_ptr;
+ u16 local_transaction_id = 0;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): called... nd=%d\n", pthread_self(), *nd);
+#endif
+
+ if (nd == NULL)
+ return -1;
+
+ if (*nd >= nd_table_.node_count)
+ /* invalid *nd */
+ /* remember that *nd < 0 is valid!! */
+ return -1;
+
+ if (recv_data_ptr == NULL)
+ recv_data_ptr = &local_recv_data_ptr;
+ if (transaction_id == NULL)
+ transaction_id = &local_transaction_id;
+
+ /* We will potentially call read() multiple times to read in a single frame.
+ * We therefore determine the absolute time_out, and use this as a parameter
+ * for each call to read_bytes() instead of using a relative timeout.
+ *
+ * NOTE: see also the timeout related comment in the read_bytes() function!
+ */
+ ts_ptr = NULL;
+ if (recv_timeout != NULL) {
+ ts_ptr = &end_time;
+ *ts_ptr = timespec_add_curtime(*recv_timeout);
+ }
+
+ /* If we must read off a single node... */
+ if (*nd >= 0)
+ /* but the node does not have a valid fd */
+ if ((nd_table_.node[*nd].node_type == MB_FREE_NODE) ||
+ (nd_table_.node[*nd].fd < 0))
+ /* then we return an error... */
+ return -1;
+
+ /* We will loop forever...
+ * We jump out of the loop and return from the function as soon as:
+ * - we receive a valid modbus message;
+ * OR
+ * - we time out.
+ *
+ * NOTE: This loop will close connections through which we receive invalid frames.
+ * This means that the set of nodes through which we may receive data may change with each
+ * loop iteration. => We need to re-calculate the fds in each loop iteration!
+ */
+
+ while (1) {
+ int nd_count, fd_high;
+ fd_set rfds;
+
+ /* We prepare our fd sets here so we can later call select() */
+ FD_ZERO(&rfds);
+ fd_high = -1;
+
+ for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) {
+ if (nd_table_.node[nd_count].node_type != MB_FREE_NODE)
+ {
+ if ((*nd < 0) // we select from all nodes
+ || (*nd == nd_count) // we select from this specific node
+ // we are listening on a MB_LISTEN_NODE, so we must also receive requests sent to slave nodes
+ // whose connection requests arrived through this MB_LISTEN_NDODE
+ || ((nd_table_.node[nd_count].node_type == MB_SLAVE_NODE) && (nd_table_.node[nd_count].listen_node == *nd)))
+ {
+ /* check if valid fd */
+ if (nd_table_.node[nd_count].fd >= 0) {
+ /* Add the descriptor to the fd set... */
+ FD_SET(nd_table_.node[nd_count].fd, &rfds);
+ fd_high = max(fd_high, nd_table_.node[nd_count].fd);
+ }
+ }
+ }
+ } /* for(;;) */
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): while(1) looping. fd_high = %d, nd=%d\n", pthread_self(), fd_high, *nd);
+#endif
+
+ if (fd_high == -1)
+ /* we will not be reading from any node! */
+ return -1;
+
+ /* We now call select and wait for activity on the nodes we are listening to */
+ { int sel_res = my_select(fd_high + 1, &rfds, NULL, ts_ptr);
+ if (sel_res < 0)
+ return -1;
+ if (sel_res == 0)
+ /* timeout! */
+ return -2;
+ }
+
+ /* figure out which nd is ready to be read... */
+ for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) {
+ if ((nd_table_.node[nd_count].node_type != MB_FREE_NODE) &&
+ (nd_table_.node[nd_count].fd >= 0)) {
+ if (FD_ISSET(nd_table_.node[nd_count].fd, &rfds)) {
+ /* Found the node descriptor... */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): my_select() returned due to activity on node nd=%d\n", pthread_self(), nd_count);
+#endif
+ if (nd_table_.node[nd_count].node_type == MB_LISTEN_NODE) {
+ /* We must accept a new connection...
+ * No need to check for errors.
+ * If one occurs, there is nothing we can do...
+ */
+ accept_connection(nd_count);
+ } else {
+ /* it is a MB_SLAVE_NODE or a MB_MASTER_NODE */
+ /* We will read a frame off this nd */
+ int res;
+ res = modbus_tcp_read_frame(nd_count, transaction_id, ts_ptr);
+ if (res > 0) {
+ *nd = nd_count;
+ *recv_data_ptr = nd_table_.node[nd_count].recv_buf;
+ return res;
+ }
+ if (res < 0) {
+ /* We had an error reading the frame...
+ * We handle it by closing the connection, as specified by
+ * the modbus TCP protocol!
+ *
+ * NOTE: The error may have been a timeout, which means this function should return immediately.
+ * However, in this case we let the execution loop once again
+ * in the while(1) loop. My_select() will be called again
+ * and the timeout detected. The timeout error code (-2)
+ * will then be returned correctly!
+ */
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_read(): error reading frame. Closing connection...\n", pthread_self());
+#endif
+ /* We close the socket... */
+ close_connection(nd_count);
+ }
+ }
+ /* we have found the node descriptor, so let's jump out of the for(;;) loop */
+ break;
+ }
+ }
+ } /* for(;;) */
+
+ /* We were unsuccesfull reading a frame, so we try again... */
+ } /* while (1) */
+
+ /* humour the compiler... */
+ return -1;
+}
+
+
+
+
+
+/**************************************************************/
+/**************************************************************/
+/**** ****/
+/**** ****/
+/**** Initialising and Shutting Down Library ****/
+/**** ****/
+/**** ****/
+/**************************************************************/
+/**************************************************************/
+
+
+/* Ugly hack...
+ * Beremiz will be calling modbus_tcp_init() multiple times (through modbus_init() )
+ * (once for each plugin instance)
+ * It will also be calling modbus_tcp_done() the same number of times
+ * We only want to really shutdown the library the last time it is called.
+ * We therefore keep a counter of how many times modbus_tcp_init() is called,
+ * and decrement it in modbus_tcp_done()
+ */
+int modbus_tcp_init_counter = 0;
+
+/******************************/
+/** **/
+/** Load Default Values **/
+/** **/
+/******************************/
+
+static void set_defaults(const char **service) {
+ /* Set the default values, if required... */
+ if (*service == NULL)
+ *service = DEF_SERVICE;
+}
+
+
+/******************************/
+/** **/
+/** Initialise Library **/
+/** **/
+/******************************/
+/* returns the number of nodes succesfully initialised...
+ * returns -1 on error.
+ */
+int modbus_tcp_init(int nd_count,
+ optimization_t opt /* ignored... */,
+ int *extra_bytes) {
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_init(): called...\n", pthread_self());
+ printf("[%lu] creating %d nodes:\n", pthread_self(), nd_count);
+#endif
+
+ modbus_tcp_init_counter++;
+
+ /* set the extra_bytes value... */
+ /* Please see note before the modbus_rtu_write() function for a
+ * better understanding of this extremely ugly hack... This will be
+ * in the mb_rtu.c file!!
+ *
+ * The number of extra bytes that must be allocated to the data buffer
+ * before calling modbus_tcp_write()
+ */
+ if (extra_bytes != NULL)
+ *extra_bytes = 0;
+
+ if (0 == nd_count)
+ /* no need to initialise this layer! */
+ return 0;
+ if (nd_count <= 0)
+ /* invalid node count... */
+ goto error_exit_1;
+
+ /* initialise the node table... */
+ if (nd_table_init(&nd_table_, nd_count) < 0)
+ goto error_exit_1;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_init(): %d node(s) opened succesfully\n", pthread_self(), nd_count);
+#endif
+ return nd_count; /* number of succesfully created nodes! */
+
+/*
+error_exit_2:
+ nd_table_done(&nd_table_);
+*/
+error_exit_1:
+ if (extra_bytes != NULL)
+ *extra_bytes = 0;
+ return -1;
+}
+
+
+
+
+
+
+/******************************/
+/** **/
+/** Open a Master Node **/
+/** **/
+/******************************/
+int modbus_tcp_connect(node_addr_t node_addr) {
+ int node_descriptor;
+ struct sockaddr_in tmp_addr;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_connect(): called...\n", pthread_self());
+ printf("[%lu] %s:%s\n", pthread_self(),
+ node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service);
+#endif
+
+ /* Check for valid address family */
+ if (node_addr.naf != naf_tcp)
+ /* wrong address type... */
+ return -1;
+
+ /* set the default values... */
+ set_defaults(&(node_addr.addr.tcp.service));
+
+ /* Check the parameters we were passed... */
+ if(sin_initaddr(&tmp_addr,
+ node_addr.addr.tcp.host, 0,
+ node_addr.addr.tcp.service, 0,
+ DEF_PROTOCOL)
+ < 0) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Error parsing/resolving address %s:%s\n",
+ node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service);
+#endif
+ return -1;
+ }
+
+ /* find a free node descriptor */
+ if ((node_descriptor = nd_table_get_free_node(&nd_table_, MB_MASTER_NODE)) < 0)
+ /* if no free nodes to initialize, then we are finished... */
+ return -1;
+
+ nd_table_.node[node_descriptor].addr = tmp_addr;
+ nd_table_.node[node_descriptor].fd = -1; /* not currently connected... */
+ nd_table_.node[node_descriptor].close_on_silence = node_addr.addr.tcp.close_on_silence;
+
+ if (nd_table_.node[node_descriptor].close_on_silence < 0)
+ nd_table_.node[node_descriptor].close_on_silence = DEF_CLOSE_ON_SILENCE;
+
+ /* WE have never tried to connect, so print an error the next time we try to connect */
+ nd_table_.node[node_descriptor].print_connect_error = 1;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_connect(): returning nd=%d\n", pthread_self(), node_descriptor);
+#endif
+ return node_descriptor;
+}
+
+
+
+/******************************/
+/** **/
+/** Open a Slave Node **/
+/** **/
+/******************************/
+
+int modbus_tcp_listen(node_addr_t node_addr) {
+ int fd, nd;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_listen(): called...\n", pthread_self());
+ printf("[%lu] %s:%s\n", pthread_self(),
+ node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service);
+#endif
+
+ /* Check for valid address family */
+ if (node_addr.naf != naf_tcp)
+ /* wrong address type... */
+ goto error_exit_0;
+
+ /* set the default values... */
+ set_defaults(&(node_addr.addr.tcp.service));
+
+ /* create a socket and bind it to the appropriate port... */
+ fd = sin_bindsock(node_addr.addr.tcp.host,
+ node_addr.addr.tcp.service,
+ DEF_PROTOCOL);
+ if (fd < 0) {
+#ifdef ERRMSG
+ fprintf(stderr, ERRMSG_HEAD "Could not bind to socket %s:%s\n",
+ ((node_addr.addr.tcp.host==NULL)?"#ANY#":node_addr.addr.tcp.host),
+ node_addr.addr.tcp.service);
+#endif
+ goto error_exit_0;
+ }
+ if (listen(fd, DEF_MAX_PENDING_CONNECTION_REQUESTS) < 0)
+ goto error_exit_0;
+
+ /* find a free node descriptor */
+ if ((nd = nd_table_get_free_node(&nd_table_, MB_LISTEN_NODE)) < 0) {
+ /* if no free nodes to initialize, then we are finished... */
+ goto error_exit_1;
+ }
+
+ /* nd_table_.node[nd].addr = tmp_addr; */ /* does not apply for MB_LISTEN_NODE */
+ nd_table_.node[nd].fd = fd; /* not currently connected... */
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_listen(): returning nd=%d\n", pthread_self(), nd);
+#endif
+ return nd;
+
+error_exit_1:
+ close(fd);
+error_exit_0:
+ return -1;
+}
+
+
+
+/******************************/
+/** **/
+/** Close a node **/
+/** **/
+/******************************/
+
+int modbus_tcp_close(int nd) {
+#ifdef DEBUG
+ fprintf(stderr, "[%lu] modbus_tcp_close(): called... nd=%d\n", pthread_self(), nd);
+#endif
+
+ if ((nd < 0) || (nd >= nd_table_.node_count)) {
+ /* invalid nd */
+#ifdef DEBUG
+ fprintf(stderr, "[%lu] modbus_tcp_close(): invalid node %d. Should be < %d\n", pthread_self(), nd, nd_table_.node_count);
+#endif
+ return -1;
+ }
+
+ if (nd_table_.node[nd].node_type == MB_FREE_NODE)
+ /* already free node */
+ return 0;
+
+ close_connection(nd);
+
+ nd_table_close_node(&nd_table_, nd);
+
+ return 0;
+}
+
+
+
+/**********************************/
+/** **/
+/** Close all open connections **/
+/** **/
+/**********************************/
+
+int modbus_tcp_silence_init(void) {
+ int nd;
+
+#ifdef DEBUG
+ printf("[%lu] modbus_tcp_silence_init(): called...\n", pthread_self());
+#endif
+
+ /* close all master connections that remain open... */
+ for (nd = 0; nd < nd_table_.node_count; nd++)
+ if (nd_table_.node[nd].node_type == MB_MASTER_NODE)
+ if (nd_table_.node[nd].close_on_silence > 0)
+ /* node is is being used for a master device,
+ * and wishes to be closed... ...so we close it!
+ */
+ close_connection(nd);
+
+ return 0;
+}
+
+
+
+/******************************/
+/** **/
+/** Shutdown the Library **/
+/** **/
+/******************************/
+
+int modbus_tcp_done(void) {
+ int i;
+
+ modbus_tcp_init_counter--;
+ if (modbus_tcp_init_counter != 0) return 0; /* ignore this request */
+
+ /* close all the connections... */
+ for (i = 0; i < nd_table_.node_count; i++)
+ modbus_tcp_close(i);
+
+ /* Free memory... */
+ nd_table_done(&nd_table_);
+
+ return 0;
+}
+
+
+
+
+double modbus_tcp_get_min_timeout(int baud,
+ int parity,
+ int data_bits,
+ int stop_bits) {
+ return 0;
+}