modbus_server.c
Go to the documentation of this file.
1 /**
2  * @file modbus_server.c
3  * @brief Modbus/TCP server
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2019 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 1.9.6
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL MODBUS_TRACE_LEVEL
33 
34 //Dependencies
35 #include "modbus/modbus_server.h"
38 #include "debug.h"
39 
40 //Check TCP/IP stack configuration
41 #if (MODBUS_SERVER_SUPPORT == ENABLED)
42 
43 
44 /**
45  * @brief Initialize settings with default values
46  * @param[out] settings Structure that contains Modbus/TCP server settings
47  **/
48 
50 {
51  //The Modbus/TCP server is not bound to any interface
52  settings->interface = NULL;
53 
54  //Modbus/TCP port number
55  settings->port = MODBUS_TCP_PORT;
56  //Default unit identifier
57  settings->unitId = MODBUS_DEFAULT_UNIT_ID;
58 
59 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
60  //TLS initialization callback function
61  settings->tlsInitCallback = NULL;
62 #endif
63 
64  //Lock Modbus table callback function
65  settings->lockCallback = NULL;
66  //Unlock Modbus table callback function
67  settings->unlockCallback = NULL;
68  //Get coil state callback function
69  settings->readCoilCallback = NULL;
70  //Get discrete input state callback function
71  settings->readDiscreteInputCallback = NULL;
72  //Set coil state callback function
73  settings->writeCoilCallback = NULL;
74  //Get register value callback function
75  settings->readRegCallback = NULL;
76  //Get holding register value callback function
77  settings->readHoldingRegCallback = NULL;
78  //Get input register value callback function
79  settings->readInputRegCallback = NULL;
80  //Set register value callback function
81  settings->writeRegCallback = NULL;
82  //PDU processing callback
83  settings->processPduCallback = NULL;
84 }
85 
86 
87 /**
88  * @brief Initialize Modbus/TCP server context
89  * @param[in] context Pointer to the Modbus/TCP server context
90  * @param[in] settings Modbus/TCP server specific settings
91  * @return Error code
92  **/
93 
95  const ModbusServerSettings *settings)
96 {
97  error_t error;
98 
99  //Debug message
100  TRACE_INFO("Initializing Modbus/TCP server...\r\n");
101 
102  //Ensure the parameters are valid
103  if(context == NULL || settings == NULL)
105 
106  //Clear Modbus/TCP server context
107  memset(context, 0, sizeof(ModbusServerContext));
108 
109  //Save user settings
110  context->settings = *settings;
111 
112  //Create an event object to poll the state of sockets
113  if(!osCreateEvent(&context->event))
114  {
115  //Failed to create event
116  return ERROR_OUT_OF_RESOURCES;
117  }
118 
119  //Start of exception handling block
120  do
121  {
122  //Open a TCP socket
124 
125  //Failed to open socket?
126  if(context->socket == NULL)
127  {
128  //Report an error
129  error = ERROR_OPEN_FAILED;
130  //Exit immediately
131  break;
132  }
133 
134  //Force the socket to operate in non-blocking mode
135  error = socketSetTimeout(context->socket, 0);
136  //Any error to report?
137  if(error)
138  break;
139 
140  //Associate the socket with the relevant interface
141  error = socketBindToInterface(context->socket, settings->interface);
142  //Any error to report?
143  if(error)
144  break;
145 
146  //The Modbus/TCP server listens for connection requests on port 502
147  error = socketBind(context->socket, &IP_ADDR_ANY, settings->port);
148  //Any error to report?
149  if(error)
150  break;
151 
152  //Place socket in listening state
153  error = socketListen(context->socket, 0);
154  //Any error to report?
155  if(error)
156  break;
157 
158 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED)
159  //Initialize ticket encryption context
160  error = tlsInitTicketContext(&context->tlsTicketContext);
161  //Any error to report?
162  if(error)
163  return error;
164 #endif
165 
166  //End of exception handling block
167  } while(0);
168 
169  //Check status code
170  if(error)
171  {
172  //Clean up side effects
173  modbusServerDeinit(context);
174  }
175 
176  //Return status code
177  return error;
178 }
179 
180 /**
181  * @brief Start Modbus/TCP server
182  * @param[in] context Pointer to the Modbus/TCP server context
183  * @return Error code
184  **/
185 
187 {
188  OsTask *task;
189 
190  //Debug message
191  TRACE_INFO("Starting Modbus/TCP server...\r\n");
192 
193  //Make sure the Modbus/TCP server context is valid
194  if(context == NULL)
196 
197  //Create the Modbus/TCP server task
198  task = osCreateTask("Modbus/TCP Server", (OsTaskCode) modbusServerTask,
200 
201  //Unable to create the task?
202  if(task == OS_INVALID_HANDLE)
203  return ERROR_OUT_OF_RESOURCES;
204 
205  //Successful processing
206  return NO_ERROR;
207 }
208 
209 
210 /**
211  * @brief Modbus/TCP server task
212  * @param[in] context Pointer to the Modbus/TCP server context
213  **/
214 
216 {
217  error_t error;
218  uint_t i;
219  systime_t timeout;
220  ModbusClientConnection *connection;
222 
223 #if (NET_RTOS_SUPPORT == ENABLED)
224  //Task prologue
225  osEnterTask();
226 
227  //Process events
228  while(1)
229  {
230 #endif
231  //Set polling timeout
232  timeout = MODBUS_SERVER_TICK_INTERVAL;
233 
234  //Clear event descriptor set
235  memset(eventDesc, 0, sizeof(eventDesc));
236 
237  //Specify the events the application is interested in
238  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
239  {
240  //Point to the structure describing the current connection
241  connection = &context->connection[i];
242 
243  //Check the state of the connection
244  if(connection->state == MODBUS_CONNECTION_STATE_CONNECT_TLS)
245  {
246 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
247  //Any data pending in the send buffer?
248  if(tlsIsTxReady(connection->tlsContext))
249  {
250  //Wait until there is more room in the send buffer
251  eventDesc[i].socket = connection->socket;
252  eventDesc[i].eventMask = SOCKET_EVENT_TX_READY;
253  }
254  else
255  {
256  //Wait for data to be available for reading
257  eventDesc[i].socket = connection->socket;
258  eventDesc[i].eventMask = SOCKET_EVENT_RX_READY;
259  }
260 #endif
261  }
262  else if(connection->state == MODBUS_CONNECTION_STATE_RECEIVE)
263  {
264 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED)
265  //Any data available in the receive buffer?
266  if(connection->tlsContext != NULL &&
267  tlsIsRxReady(connection->tlsContext))
268  {
269  //No need to poll the underlying socket for incoming traffic
270  eventDesc[i].eventFlags |= SOCKET_EVENT_RX_READY;
271  timeout = 0;
272  }
273  else
274 #endif
275  {
276  //Wait for data to be available for reading
277  eventDesc[i].socket = connection->socket;
278  eventDesc[i].eventMask = SOCKET_EVENT_RX_READY;
279  }
280  }
281  else if(connection->state == MODBUS_CONNECTION_STATE_SEND ||
282  connection->state == MODBUS_CONNECTION_STATE_SHUTDOWN_TLS)
283  {
284  //Wait until there is more room in the send buffer
285  eventDesc[i].socket = connection->socket;
286  eventDesc[i].eventMask = SOCKET_EVENT_TX_READY;
287  }
288  else if(connection->state == MODBUS_CONNECTION_STATE_SHUTDOWN_TX)
289  {
290  //Wait for the FIN to be acknowledged
291  eventDesc[i].socket = connection->socket;
292  eventDesc[i].eventMask = SOCKET_EVENT_TX_SHUTDOWN;
293  }
294  else if(connection->state == MODBUS_CONNECTION_STATE_SHUTDOWN_RX)
295  {
296  //Wait for a FIN to be received
297  eventDesc[i].socket = connection->socket;
298  eventDesc[i].eventMask = SOCKET_EVENT_RX_SHUTDOWN;
299  }
300  else
301  {
302  //Just for sanity
303  }
304  }
305 
306  //The Modbus/TCP server listens for connection requests on port 502
307  eventDesc[i].socket = context->socket;
308  eventDesc[i].eventMask = SOCKET_EVENT_RX_READY;
309 
310  //Wait for one of the set of sockets to become ready to perform I/O
311  error = socketPoll(eventDesc, MODBUS_SERVER_MAX_CONNECTIONS + 1,
312  &context->event, timeout);
313 
314  //Check status code
315  if(error == NO_ERROR || error == ERROR_TIMEOUT)
316  {
317  //Event-driven processing
318  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
319  {
320  //Point to the structure describing the current connection
321  connection = &context->connection[i];
322 
323  //Loop through active connections only
324  if(connection->state != MODBUS_CONNECTION_STATE_CLOSED)
325  {
326  //Check whether the socket is ready to perform I/O
327  if(eventDesc[i].eventFlags != 0)
328  {
329  //Connection event handler
330  modbusServerProcessConnectionEvents(context, connection);
331  }
332  }
333  }
334 
335  //Any connection request received on port 502?
336  if(eventDesc[i].eventFlags != 0)
337  {
338  //Accept connection request
340  }
341  }
342 
343  //Handle periodic operations
344  modbusServerTick(context);
345 
346 #if (NET_RTOS_SUPPORT == ENABLED)
347  }
348 #endif
349 }
350 
351 
352 /**
353  * @brief Release Modbus/TCP server context
354  * @param[in] context Pointer to the Modbus/TCP server context
355  **/
356 
358 {
359  uint_t i;
360 
361  //Make sure the Modbus/TCP server context is valid
362  if(context != NULL)
363  {
364  //Loop through the connection table
365  for(i = 0; i < MODBUS_SERVER_MAX_CONNECTIONS; i++)
366  {
367  //Close client connection
368  modbusServerCloseConnection(&context->connection[i]);
369  }
370 
371  //Close listening socket
372  socketClose(context->socket);
373 
374 #if (MODBUS_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED)
375  //Release ticket encryption context
376  tlsFreeTicketContext(&context->tlsTicketContext);
377 #endif
378 
379  //Free previously allocated resources
380  osDeleteEvent(&context->event);
381 
382  //Clear Modbus/TCP server context
383  memset(context, 0, sizeof(ModbusServerContext));
384  }
385 }
386 
387 #endif
void modbusServerDeinit(ModbusServerContext *context)
Release Modbus/TCP server context.
uint8_t unitId
Unit identifier.
error_t socketBind(Socket *socket, const IpAddr *localIpAddr, uint16_t localPort)
Associate a local address with a socket.
Definition: socket.c:355
error_t tlsInitTicketContext(TlsTicketContext *ticketContext)
Initialize ticket encryption context.
Definition: tls_ticket.c:50
Modbus/TCP server settings.
void modbusServerCloseConnection(ModbusClientConnection *connection)
Close network connection.
ModbusServerTlsInitCallback tlsInitCallback
TLS initialization callback function.
#define MODBUS_TCP_PORT
Definition: modbus_common.h:38
ModbusServerReadCoilCallback readDiscreteInputCallback
Get discrete input state callback function.
void modbusServerGetDefaultSettings(ModbusServerSettings *settings)
Initialize settings with default values.
Definition: modbus_server.c:49
uint16_t port
Modbus/TCP port number.
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:822
ModbusServerUnlockCallback unlockCallback
Unlock Modbus table callback function.
error_t modbusServerInit(ModbusServerContext *context, const ModbusServerSettings *settings)
Initialize Modbus/TCP server context.
Definition: modbus_server.c:94
ModbusServerReadRegCallback readHoldingRegCallback
Get holding register value callback function.
Structure describing socket events.
Definition: socket.h:307
ModbusServerWriteCoilCallback writeCoilCallback
Set coil state callback function.
#define ModbusClientConnection
ModbusServerLockCallback lockCallback
Lock Modbus table callback function.
NetInterface * interface
Underlying network interface.
#define MODBUS_SERVER_PRIORITY
Definition: modbus_server.h:61
const IpAddr IP_ADDR_ANY
Definition: ip.c:45
Invalid parameter.
Definition: error.h:47
OsTask * osCreateTask(const char_t *name, OsTaskCode taskCode, void *param, size_t stackSize, int_t priority)
Create a new task.
error_t
Error codes.
Definition: error.h:42
ModbusServerReadCoilCallback readCoilCallback
Get coil state callback function.
Modbus/TCP server.
Helper functions for Modbus/TCP server.
void osDeleteEvent(OsEvent *event)
Delete an event object.
ModbusServerProcessPduCallback processPduCallback
PDU processing callback.
Task object.
ModbusServerReadRegCallback readRegCallback
Get register value callback function.
bool_t tlsIsTxReady(TlsContext *context)
Check whether some data is ready for transmission.
Definition: tls.c:2034
Transport protocol abstraction layer.
#define MODBUS_SERVER_STACK_SIZE
Definition: modbus_server.h:54
#define TRACE_INFO(...)
Definition: debug.h:94
void modbusServerAcceptConnection(ModbusServerContext *context)
Accept connection request.
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:95
#define MODBUS_DEFAULT_UNIT_ID
Definition: modbus_common.h:45
ModbusServerReadRegCallback readInputRegCallback
Get input register value callback function.
#define osEnterTask()
uint_t eventFlags
Returned events.
Definition: socket.h:311
error_t socketPoll(SocketEventDesc *eventDesc, uint_t size, OsEvent *extEvent, systime_t timeout)
Wait for one of a set of sockets to become ready to perform I/O.
Definition: socket.c:882
#define socketBindToInterface
Definition: net_legacy.h:270
bool_t tlsIsRxReady(TlsContext *context)
Check whether some data is available in the receive buffer.
Definition: tls.c:2067
#define OS_INVALID_HANDLE
Definition: os_port.h:79
void modbusServerTask(ModbusServerContext *context)
Modbus/TCP server task.
void(* OsTaskCode)(void *param)
Task routine.
void modbusServerProcessConnectionEvents(ModbusServerContext *context, ModbusClientConnection *connection)
Connection event handler.
bool_t osCreateEvent(OsEvent *event)
Create an event object.
void modbusServerTick(ModbusServerContext *context)
Handle periodic operations.
ModbusServerWriteRegCallback writeRegCallback
Set register value callback function.
error_t modbusServerStart(ModbusServerContext *context)
Start Modbus/TCP server.
void tlsFreeTicketContext(TlsTicketContext *ticketContext)
Properly dispose ticket encryption context.
Definition: tls_ticket.c:427
#define MODBUS_SERVER_TICK_INTERVAL
Definition: modbus_server.h:80
Socket * socket
Handle to a socket to monitor.
Definition: socket.h:309
#define MODBUS_SERVER_MAX_CONNECTIONS
Definition: modbus_server.h:66
unsigned int uint_t
Definition: compiler_port.h:45
#define ModbusServerContext
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:219
uint32_t systime_t
Definition: compiler_port.h:46
uint_t eventMask
Requested events.
Definition: socket.h:310
Success.
Definition: error.h:44
Debugging facilities.
error_t socketListen(Socket *socket, uint_t backlog)
Place a socket in the listening state.
Definition: socket.c:444