1 /*
2 * SPDX-FileCopyrightText: 2006 Christian Walter
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD
7 */
8 /*
9 * FreeModbus Libary: ESP32 TCP Port
10 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
11 * Parts of crt0.S Copyright (c) 1995, 1996, 1998 Cygnus Support
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. The name of the author may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * File: $Id: port.c,v 1.2 2006/09/04 14:39:20 wolti Exp $
36 */
37
38 /* ----------------------- System includes ----------------------------------*/
39 #include <stdio.h>
40 #include <string.h>
41 #include "esp_err.h"
42 #include "sys/time.h"
43 #include "esp_netif.h"
44
45 /* ----------------------- lwIP includes ------------------------------------*/
46 #include "lwip/err.h"
47 #include "lwip/sockets.h"
48 #include "lwip/netdb.h"
49 #include "net/if.h"
50
51 /* ----------------------- Modbus includes ----------------------------------*/
52 #include "mb.h"
53 #include "mbport.h"
54 #include "port.h"
55 #include "mbframe.h"
56 #include "port_tcp_slave.h"
57 #include "esp_modbus_common.h" // for common types for network options
58
59 #if MB_TCP_ENABLED
60
61 /* ----------------------- Defines -----------------------------------------*/
62 #define MB_TCP_DISCONNECT_TIMEOUT ( CONFIG_FMB_TCP_CONNECTION_TOUT_SEC * 1000000 ) // disconnect timeout in uS
63 #define MB_TCP_RESP_TIMEOUT_MS ( MB_MASTER_TIMEOUT_MS_RESPOND - 2 ) // slave response time limit
64 #define MB_TCP_NET_LISTEN_BACKLOG ( SOMAXCONN )
65
66 /* ----------------------- Prototypes ---------------------------------------*/
67 void vMBPortEventClose( void );
68
69 /* ----------------------- Static variables ---------------------------------*/
70 static const char *TAG = "MB_TCP_SLAVE_PORT";
71 static int xListenSock = -1;
72 static SemaphoreHandle_t xShutdownSemaphore = NULL;
73 static MbSlavePortConfig_t xConfig = { 0 };
74
75 /* ----------------------- Static functions ---------------------------------*/
76 // The helper function to get time stamp in microseconds
xMBTCPGetTimeStamp(void)77 static int64_t xMBTCPGetTimeStamp(void)
78 {
79 int64_t xTimeStamp = esp_timer_get_time();
80 return xTimeStamp;
81 }
82
vxMBTCPPortMStoTimeVal(USHORT usTimeoutMs,struct timeval * pxTimeout)83 static void vxMBTCPPortMStoTimeVal(USHORT usTimeoutMs, struct timeval *pxTimeout)
84 {
85 pxTimeout->tv_sec = usTimeoutMs / 1000;
86 pxTimeout->tv_usec = (usTimeoutMs - (pxTimeout->tv_sec * 1000)) * 1000;
87 }
88
xMBTCPPortRespQueueCreate(void)89 static xQueueHandle xMBTCPPortRespQueueCreate(void)
90 {
91 xQueueHandle xRespQueueHandle = xQueueCreate(2, sizeof(void*));
92 MB_PORT_CHECK((xRespQueueHandle != NULL), NULL, "TCP respond queue creation failure.");
93 return xRespQueueHandle;
94 }
95
vMBTCPPortRespQueueDelete(xQueueHandle xRespQueueHandle)96 static void vMBTCPPortRespQueueDelete(xQueueHandle xRespQueueHandle)
97 {
98 vQueueDelete(xRespQueueHandle);
99 }
100
vxMBTCPPortRespQueueRecv(xQueueHandle xRespQueueHandle)101 static void* vxMBTCPPortRespQueueRecv(xQueueHandle xRespQueueHandle)
102 {
103 void* pvResp = NULL;
104 MB_PORT_CHECK(xRespQueueHandle != NULL, NULL, "Response queue is not initialized.");
105 BaseType_t xStatus = xQueueReceive(xRespQueueHandle,
106 (void*)&pvResp,
107 pdMS_TO_TICKS(MB_TCP_RESP_TIMEOUT_MS));
108 MB_PORT_CHECK((xStatus == pdTRUE), NULL, "Could not get respond confirmation.");
109 MB_PORT_CHECK((pvResp), NULL, "Incorrect response processing.");
110 return pvResp;
111 }
112
vxMBTCPPortRespQueueSend(xQueueHandle xRespQueueHandle,void * pvResp)113 static BOOL vxMBTCPPortRespQueueSend(xQueueHandle xRespQueueHandle, void* pvResp)
114 {
115 MB_PORT_CHECK(xRespQueueHandle != NULL, FALSE, "Response queue is not initialized.");
116 BaseType_t xStatus = xQueueSend(xConfig.xRespQueueHandle,
117 (const void*)&pvResp,
118 pdMS_TO_TICKS(MB_TCP_RESP_TIMEOUT_MS));
119 MB_PORT_CHECK((xStatus == pdTRUE), FALSE, "FAIL to send to response queue.");
120 return TRUE;
121 }
122
123 static void vMBTCPPortServerTask(void *pvParameters);
124
125 /* ----------------------- Begin implementation -----------------------------*/
126 BOOL
xMBTCPPortInit(USHORT usTCPPort)127 xMBTCPPortInit( USHORT usTCPPort )
128 {
129 BOOL bOkay = FALSE;
130
131 xConfig.pxMbClientInfo = calloc(MB_TCP_PORT_MAX_CONN + 1, sizeof(MbClientInfo_t*));
132 if (!xConfig.pxMbClientInfo) {
133 ESP_LOGE(TAG, "TCP client info allocation failure.");
134 return FALSE;
135 }
136 for(int idx = 0; idx < MB_TCP_PORT_MAX_CONN; xConfig.pxMbClientInfo[idx] = NULL, idx++);
137
138 xConfig.xRespQueueHandle = xMBTCPPortRespQueueCreate();
139 if (!xConfig.xRespQueueHandle) {
140 ESP_LOGE(TAG, "Response queue allocation failure.");
141 return FALSE;
142 }
143
144 xConfig.usPort = usTCPPort;
145 xConfig.eMbProto = MB_PROTO_TCP;
146 xConfig.usClientCount = 0;
147 xConfig.pvNetIface = NULL;
148 xConfig.xIpVer = MB_PORT_IPV4;
149 xConfig.pcBindAddr = NULL;
150
151 // Create task for packet processing
152 BaseType_t xErr = xTaskCreate(vMBTCPPortServerTask,
153 "tcp_server_task",
154 MB_TCP_STACK_SIZE,
155 NULL,
156 MB_TCP_TASK_PRIO,
157 &xConfig.xMbTcpTaskHandle);
158 vTaskSuspend(xConfig.xMbTcpTaskHandle);
159 if (xErr != pdTRUE)
160 {
161 ESP_LOGE(TAG, "Server task creation failure.");
162 vTaskDelete(xConfig.xMbTcpTaskHandle);
163 } else {
164 ESP_LOGI(TAG, "Protocol stack initialized.");
165 bOkay = TRUE;
166 }
167 return bOkay;
168 }
169
vMBTCPPortSlaveSetNetOpt(void * pvNetIf,eMBPortIpVer xIpVersion,eMBPortProto xProto,CHAR * pcBindAddrStr)170 void vMBTCPPortSlaveSetNetOpt(void* pvNetIf, eMBPortIpVer xIpVersion, eMBPortProto xProto, CHAR* pcBindAddrStr)
171 {
172 // Set network options
173 xConfig.pvNetIface = pvNetIf;
174 xConfig.eMbProto = xProto;
175 xConfig.xIpVer = xIpVersion;
176 xConfig.pcBindAddr = pcBindAddrStr;
177 }
178
xMBTCPPortAcceptConnection(int xListenSockId,char ** pcIPAddr)179 static int xMBTCPPortAcceptConnection(int xListenSockId, char** pcIPAddr)
180 {
181 MB_PORT_CHECK(pcIPAddr, -1, "Wrong IP address pointer.");
182 MB_PORT_CHECK((xListenSockId > 0), -1, "Incorrect listen socket ID.");
183
184 // Address structure large enough for both IPv4 or IPv6 address
185 struct sockaddr_storage xSrcAddr;
186 CHAR cAddrStr[128];
187 int xSockId = -1;
188 CHAR* pcStr = NULL;
189 socklen_t xSize = sizeof(struct sockaddr_storage);
190
191 // Accept new socket connection if not active
192 xSockId = accept(xListenSockId, (struct sockaddr *)&xSrcAddr, &xSize);
193 if (xSockId < 0) {
194 ESP_LOGE(TAG, "Unable to accept connection: errno=%d", errno);
195 close(xSockId);
196 } else {
197 // Get the sender's ip address as string
198 if (xSrcAddr.ss_family == PF_INET) {
199 inet_ntoa_r(((struct sockaddr_in *)&xSrcAddr)->sin_addr.s_addr, cAddrStr, sizeof(cAddrStr) - 1);
200 }
201 #if CONFIG_LWIP_IPV6
202 else if (xSrcAddr.ss_family == PF_INET6) {
203 inet6_ntoa_r(((struct sockaddr_in6 *)&xSrcAddr)->sin6_addr, cAddrStr, sizeof(cAddrStr) - 1);
204 }
205 #endif
206 ESP_LOGI(TAG, "Socket (#%d), accept client connection from address: %s", xSockId, cAddrStr);
207 pcStr = calloc(1, strlen(cAddrStr) + 1);
208 if (pcStr && pcIPAddr) {
209 memcpy(pcStr, cAddrStr, strlen(cAddrStr));
210 pcStr[strlen(cAddrStr)] = '\0';
211 *pcIPAddr = pcStr; // Set IP address of connected client
212 }
213 }
214 return xSockId;
215 }
216
xMBTCPPortCloseConnection(MbClientInfo_t * pxInfo)217 static BOOL xMBTCPPortCloseConnection(MbClientInfo_t* pxInfo)
218 {
219 MB_PORT_CHECK(pxInfo, FALSE, "Client info is NULL.");
220
221 if (pxInfo->xSockId == -1) {
222 ESP_LOGE(TAG, "Wrong socket info or disconnected socket: %d.", pxInfo->xSockId);
223 return FALSE;
224 }
225 if (shutdown(pxInfo->xSockId, SHUT_RDWR) == -1) {
226 ESP_LOGE(TAG, "Socket (#%d), shutdown failed: errno %d", pxInfo->xSockId, errno);
227 }
228 close(pxInfo->xSockId);
229 pxInfo->xSockId = -1;
230 if (xConfig.usClientCount) {
231 xConfig.usClientCount--; // decrement counter of client connections
232 } else {
233 xConfig.pxCurClientInfo = NULL;
234 }
235 return TRUE;
236 }
237
xMBTCPPortRxPoll(MbClientInfo_t * pxClientInfo,ULONG xTimeoutMs)238 static int xMBTCPPortRxPoll(MbClientInfo_t* pxClientInfo, ULONG xTimeoutMs)
239 {
240 int xRet = ERR_CLSD;
241 struct timeval xTimeVal;
242 fd_set xReadSet;
243 int64_t xStartTimeStamp = 0;
244
245 // Receive data from connected client
246 if (pxClientInfo && pxClientInfo->xSockId > -1) {
247 // Set receive timeout
248 vxMBTCPPortMStoTimeVal(xTimeoutMs, &xTimeVal);
249 xStartTimeStamp = xMBTCPGetTimeStamp();
250 while (1)
251 {
252 FD_ZERO(&xReadSet);
253 FD_SET(pxClientInfo->xSockId, &xReadSet);
254 xRet = select(pxClientInfo->xSockId + 1, &xReadSet, NULL, NULL, &xTimeVal);
255 if (xRet == -1)
256 {
257 // If select an error occurred
258 xRet = ERR_CLSD;
259 break;
260 } else if (xRet == 0) {
261 // timeout occurred
262 if ((xStartTimeStamp + xTimeoutMs * 1000) > xMBTCPGetTimeStamp()) {
263 ESP_LOGD(TAG, "Socket (#%d) Read timeout.", pxClientInfo->xSockId);
264 xRet = ERR_TIMEOUT;
265 break;
266 }
267 }
268 if (FD_ISSET(pxClientInfo->xSockId, &xReadSet)) {
269 // If new buffer received then read Modbus packet into buffer
270 MB_PORT_CHECK((pxClientInfo->usTCPBufPos + pxClientInfo->usTCPFrameBytesLeft < MB_TCP_BUF_SIZE),
271 ERR_BUF, "Socket (#%d), incorrect request buffer size = %d, ignore.",
272 pxClientInfo->xSockId,
273 (pxClientInfo->usTCPBufPos + pxClientInfo->usTCPFrameBytesLeft));
274 int xLength = recv(pxClientInfo->xSockId, &pxClientInfo->pucTCPBuf[pxClientInfo->usTCPBufPos],
275 pxClientInfo->usTCPFrameBytesLeft, MSG_DONTWAIT);
276 if (xLength < 0) {
277 // If an error occurred during receiving
278 ESP_LOGE(TAG, "Receive failed: length=%d, errno=%d", xLength, errno);
279 xRet = (err_t)xLength;
280 break;
281 } else if (xLength == 0) {
282 // Socket connection closed
283 ESP_LOGD(TAG, "Socket (#%d)(%s), connection closed.",
284 pxClientInfo->xSockId, pxClientInfo->pcIpAddr);
285 xRet = ERR_CLSD;
286 break;
287 } else {
288 // New data received
289 pxClientInfo->usTCPBufPos += xLength;
290 pxClientInfo->usTCPFrameBytesLeft -= xLength;
291 if (pxClientInfo->usTCPBufPos >= MB_TCP_FUNC) {
292 // Length is a byte count of Modbus PDU (function code + data) and the
293 // unit identifier.
294 xLength = (int)MB_TCP_GET_FIELD(pxClientInfo->pucTCPBuf, MB_TCP_LEN);
295 // Is the frame already complete.
296 if (pxClientInfo->usTCPBufPos < (MB_TCP_UID + xLength)) {
297 // The incomplete frame is received
298 pxClientInfo->usTCPFrameBytesLeft = xLength + MB_TCP_UID - pxClientInfo->usTCPBufPos;
299 } else if (pxClientInfo->usTCPBufPos == (MB_TCP_UID + xLength)) {
300 #if MB_TCP_DEBUG
301 prvvMBTCPLogFrame(TAG, (UCHAR*)&pxClientInfo->pucTCPBuf[0], pxClientInfo->usTCPBufPos);
302 #endif
303 // Copy TID field from incoming packet
304 pxClientInfo->usTidCnt = MB_TCP_GET_FIELD(pxClientInfo->pucTCPBuf, MB_TCP_TID);
305 xRet = pxClientInfo->usTCPBufPos;
306 break;
307 } else if ((pxClientInfo->usTCPBufPos + xLength) >= MB_TCP_BUF_SIZE) {
308 ESP_LOGE(TAG, "Incorrect buffer received (%u) bytes.", xLength);
309 // This should not happen. We can't deal with such a client and
310 // drop the connection for security reasons.
311 xRet = ERR_BUF;
312 break;
313 }
314 } // if ( pxClientInfo->usTCPBufPos >= MB_TCP_FUNC )
315 } // if data received
316 } // if (FD_ISSET(pxClientInfo->xSockId, &xReadSet))
317 } // while (1)
318 }
319 return (xRet);
320 }
321
322 // Create a listening socket on pcBindIp: Port
323 static int
vMBTCPPortBindAddr(const CHAR * pcBindIp)324 vMBTCPPortBindAddr(const CHAR* pcBindIp)
325 {
326 int xPar, xRet;
327 int xListenSockFd = -1;
328 struct addrinfo xHint;
329 struct addrinfo* pxAddrList;
330 struct addrinfo* pxCurAddr;
331 CHAR* pcStr = NULL;
332
333 memset( &xHint, 0, sizeof( xHint ) );
334
335 // Bind to IPv6 and/or IPv4, but only in the desired protocol
336 // Todo: Find a reason why AF_UNSPEC does not work for IPv6
337 xHint.ai_family = (xConfig.xIpVer == MB_PORT_IPV4) ? AF_INET : AF_INET6;
338 xHint.ai_socktype = (xConfig.eMbProto == MB_PROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
339 // The LWIP has an issue when connection to IPv6 socket
340 xHint.ai_protocol = (xConfig.eMbProto == MB_PROTO_UDP) ? IPPROTO_UDP : IPPROTO_TCP;
341 xHint.ai_flags = AI_NUMERICSERV;
342
343 if (pcBindIp == NULL) {
344 xHint.ai_flags |= AI_PASSIVE;
345 } else {
346 xHint.ai_flags |= AI_CANONNAME;
347 }
348
349 if (asprintf(&pcStr, "%u", xConfig.usPort) == -1) {
350 abort();
351 }
352
353 xRet = getaddrinfo(pcBindIp, pcStr, &xHint, &pxAddrList);
354 free(pcStr);
355
356 if (xRet != 0) {
357 return -1;
358 }
359
360 // Try the sockaddr until a binding succeeds
361 for (pxCurAddr = pxAddrList; pxCurAddr != NULL; pxCurAddr = pxCurAddr->ai_next)
362 {
363 xListenSockFd = (int)socket(pxCurAddr->ai_family, pxCurAddr->ai_socktype,
364 pxCurAddr->ai_protocol);
365 if (xListenSockFd < 0)
366 {
367 continue;
368 }
369
370 xPar = 1;
371 // Allow multi client connections
372 if (setsockopt(xListenSockFd, SOL_SOCKET, SO_REUSEADDR,
373 (const char*)&xPar, sizeof(xPar)) != 0)
374 {
375 close(xListenSockFd);
376 xListenSockFd = -1;
377 continue;
378 }
379
380 if (bind(xListenSockFd, (struct sockaddr *)pxCurAddr->ai_addr,
381 (socklen_t)pxCurAddr->ai_addrlen) != 0 )
382 {
383 close(xListenSockFd);
384 xListenSockFd = -1;
385 continue;
386 }
387
388 // Listen only makes sense for TCP
389 if (xConfig.eMbProto == MB_PROTO_TCP)
390 {
391 if (listen(xListenSockFd, MB_TCP_NET_LISTEN_BACKLOG) != 0)
392 {
393 ESP_LOGE(TAG, "Error occurred during listen: errno=%d", errno);
394 close(xListenSockFd);
395 xListenSockFd = -1;
396 continue;
397 }
398 }
399 // Bind was successful
400 pcStr = (pxCurAddr->ai_canonname == NULL) ? (CHAR*)"\0" : pxCurAddr->ai_canonname;
401 ESP_LOGI(TAG, "Socket (#%d), listener %s on port: %d, errno=%d",
402 xListenSockFd, pcStr, xConfig.usPort, errno);
403 break;
404 }
405
406 freeaddrinfo(pxAddrList);
407 return(xListenSockFd);
408 }
409
410 static void
vMBTCPPortFreeClientInfo(MbClientInfo_t * pxClientInfo)411 vMBTCPPortFreeClientInfo(MbClientInfo_t* pxClientInfo)
412 {
413 if (pxClientInfo) {
414 if (pxClientInfo->pucTCPBuf) {
415 free((void*)pxClientInfo->pucTCPBuf);
416 }
417 if (pxClientInfo->pcIpAddr) {
418 free((void*)pxClientInfo->pcIpAddr);
419 }
420 free((void*)pxClientInfo);
421 }
422 }
423
424
vMBTCPPortServerTask(void * pvParameters)425 static void vMBTCPPortServerTask(void *pvParameters)
426 {
427 int xErr = 0;
428 fd_set xReadSet;
429 int i;
430 CHAR* pcClientIp = NULL;
431 struct timeval xTimeVal;
432
433 // Main connection cycle
434 while (1) {
435 // Create listen socket
436 xListenSock = vMBTCPPortBindAddr(xConfig.pcBindAddr);
437 if (xListenSock < 0) {
438 continue;
439 }
440
441 // Connections handling cycle
442 while (1) {
443 //clear the socket set
444 FD_ZERO(&xReadSet);
445 //add master socket to set
446 FD_SET(xListenSock, &xReadSet);
447 int xMaxSd = xListenSock;
448 xConfig.usClientCount = 0;
449
450 vxMBTCPPortMStoTimeVal(1, &xTimeVal);
451 // Initialize read set and file descriptor according to
452 // all registered connected clients
453 for (i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
454 if ((xConfig.pxMbClientInfo[i] != NULL) && (xConfig.pxMbClientInfo[i]->xSockId > 0)) {
455 // calculate max file descriptor for select
456 xMaxSd = (xConfig.pxMbClientInfo[i]->xSockId > xMaxSd) ?
457 xConfig.pxMbClientInfo[i]->xSockId : xMaxSd;
458 FD_SET(xConfig.pxMbClientInfo[i]->xSockId, &xReadSet);
459 xConfig.usClientCount++;
460 }
461 }
462
463 // Wait for an activity on one of the sockets, timeout is NULL, so wait indefinitely
464 xErr = select(xMaxSd + 1 , &xReadSet , NULL , NULL , NULL);
465 if ((xErr < 0) && (errno != EINTR)) {
466 // First check if the task is not flagged for shutdown
467 if (xListenSock == -1 && xShutdownSemaphore) {
468 xSemaphoreGive(xShutdownSemaphore);
469 vTaskDelete(NULL);
470 }
471 // error occurred during wait for read
472 ESP_LOGE(TAG, "select() errno = %d.", errno);
473 continue;
474 } else if (xErr == 0) {
475 // If timeout happened, something is wrong
476 ESP_LOGE(TAG, "select() timeout, errno = %d.", errno);
477 }
478
479 // If something happened on the master socket, then its an incoming connection.
480 if (FD_ISSET(xListenSock, &xReadSet) && xConfig.usClientCount < MB_TCP_PORT_MAX_CONN) {
481 MbClientInfo_t* pxClientInfo = NULL;
482 // find first empty place to insert connection info
483 for (i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
484 pxClientInfo = xConfig.pxMbClientInfo[i];
485 if (pxClientInfo == NULL) {
486 break;
487 }
488 }
489 // if request for new connection but no space left
490 if (pxClientInfo != NULL) {
491 if (xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] == NULL) {
492 ESP_LOGE(TAG, "Fail to accept connection %d, only %d connections supported.", i + 1, MB_TCP_PORT_MAX_CONN);
493 }
494 xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] = pxClientInfo; // set last connection info
495 } else {
496 // allocate memory for new client info
497 pxClientInfo = calloc(1, sizeof(MbClientInfo_t));
498 if (!pxClientInfo) {
499 ESP_LOGE(TAG, "Client info allocation fail.");
500 vMBTCPPortFreeClientInfo(pxClientInfo);
501 pxClientInfo = NULL;
502 } else {
503 // Accept new client connection
504 pxClientInfo->xSockId = xMBTCPPortAcceptConnection(xListenSock, &pcClientIp);
505 if (pxClientInfo->xSockId < 0) {
506 ESP_LOGE(TAG, "Fail to accept connection for client %d.", (xConfig.usClientCount - 1));
507 // Accept connection fail, then free client info and continue polling.
508 vMBTCPPortFreeClientInfo(pxClientInfo);
509 pxClientInfo = NULL;
510 continue;
511 }
512 pxClientInfo->pucTCPBuf = calloc(MB_TCP_BUF_SIZE, sizeof(UCHAR));
513 if (!pxClientInfo->pucTCPBuf) {
514 ESP_LOGE(TAG, "Fail to allocate buffer for client %d.", (xConfig.usClientCount - 1));
515 vMBTCPPortFreeClientInfo(pxClientInfo);
516 pxClientInfo = NULL;
517 continue;
518 }
519 // Fill the connection info structure
520 xConfig.pxMbClientInfo[i] = pxClientInfo;
521 pxClientInfo->xIndex = i;
522 xConfig.usClientCount++;
523 pxClientInfo->pcIpAddr = pcClientIp;
524 pxClientInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
525 xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] = NULL;
526 pxClientInfo->usTCPFrameBytesLeft = MB_TCP_FUNC;
527 pxClientInfo->usTCPBufPos = 0;
528 }
529 }
530 }
531 // Handle data request from client
532 if (xErr > 0) {
533 // Handling client connection requests
534 for (i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
535 MbClientInfo_t* pxClientInfo = xConfig.pxMbClientInfo[i];
536 if ((pxClientInfo != NULL) && (pxClientInfo->xSockId > 0)) {
537 if (FD_ISSET(pxClientInfo->xSockId, &xReadSet)) {
538 // Other sockets are ready to be read
539 xErr = xMBTCPPortRxPoll(pxClientInfo, MB_TCP_READ_TIMEOUT_MS);
540 // If an invalid data received from socket or connection fail
541 // or if timeout then drop connection and restart
542 if (xErr < 0) {
543 uint64_t xTimeStamp = xMBTCPGetTimeStamp();
544 // If data update is timed out
545 switch(xErr)
546 {
547 case ERR_TIMEOUT:
548 ESP_LOGE(TAG, "Socket (#%d)(%s), data receive timeout, time[us]: %d, close active connection.",
549 pxClientInfo->xSockId, pxClientInfo->pcIpAddr,
550 (int)(xTimeStamp - pxClientInfo->xRecvTimeStamp));
551 break;
552 case ERR_CLSD:
553 ESP_LOGE(TAG, "Socket (#%d)(%s), connection closed by peer.",
554 pxClientInfo->xSockId, pxClientInfo->pcIpAddr);
555 break;
556 case ERR_BUF:
557 default:
558 ESP_LOGE(TAG, "Socket (#%d)(%s), read data error: %d",
559 pxClientInfo->xSockId, pxClientInfo->pcIpAddr, xErr);
560 break;
561 }
562 // Close client connection
563 xMBTCPPortCloseConnection(pxClientInfo);
564
565 // This client does not respond, then unregister it
566 vMBTCPPortFreeClientInfo(pxClientInfo);
567 xConfig.pxMbClientInfo[i] = NULL;
568 xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] = NULL;
569 // If no any active connections, break
570 if (!xConfig.usClientCount) {
571 xConfig.pxCurClientInfo = NULL;
572 break;
573 }
574 } else {
575 pxClientInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
576
577 // set current client info to active client from which we received request
578 xConfig.pxCurClientInfo = pxClientInfo;
579
580 // Complete frame received, inform state machine to process frame
581 xMBPortEventPost(EV_FRAME_RECEIVED);
582
583 ESP_LOGD(TAG, "Socket (#%d)(%s), get packet TID=0x%X, %d bytes.",
584 pxClientInfo->xSockId, pxClientInfo->pcIpAddr,
585 pxClientInfo->usTidCnt, xErr);
586
587 // Wait while response is not processed by stack by timeout
588 UCHAR* pucSentBuffer = vxMBTCPPortRespQueueRecv(xConfig.xRespQueueHandle);
589 if (pucSentBuffer == NULL) {
590 ESP_LOGE(TAG, "Response time exceeds configured %d [ms], ignore packet.",
591 MB_TCP_RESP_TIMEOUT_MS);
592 } else {
593 USHORT usSentTid = MB_TCP_GET_FIELD(pucSentBuffer, MB_TCP_TID);
594 if (usSentTid != pxClientInfo->usTidCnt) {
595 ESP_LOGE(TAG, "Sent TID(%x) != Recv TID(%x), ignore packet.",
596 usSentTid, pxClientInfo->usTidCnt);
597 }
598 }
599
600 // Get time stamp of last data update
601 pxClientInfo->xSendTimeStamp = xMBTCPGetTimeStamp();
602 ESP_LOGD(TAG, "Client %d, Socket(#%d), processing time = %d (us).",
603 pxClientInfo->xIndex, pxClientInfo->xSockId,
604 (int)(pxClientInfo->xSendTimeStamp - pxClientInfo->xRecvTimeStamp));
605 }
606 } else {
607 if (pxClientInfo) {
608 // client is not ready to be read
609 int64_t xTime = xMBTCPGetTimeStamp() - pxClientInfo->xRecvTimeStamp;
610 if (xTime > MB_TCP_DISCONNECT_TIMEOUT) {
611 ESP_LOGE(TAG, "Client %d, Socket(#%d) do not answer for %d (us). Drop connection...",
612 pxClientInfo->xIndex, pxClientInfo->xSockId, (int)(xTime));
613 xMBTCPPortCloseConnection(pxClientInfo);
614
615 // This client does not respond, then delete registered data
616 vMBTCPPortFreeClientInfo(pxClientInfo);
617 xConfig.pxMbClientInfo[i] = NULL;
618 }
619 } else {
620 ESP_LOGE(TAG, "Client %d is disconnected.", i);
621 }
622 }
623 } // if ((pxClientInfo != NULL)
624 } // Handling client connection requests
625 }
626 } // while(1) // Handle connection cycle
627 } // Main connection cycle
628 vTaskDelete(NULL);
629 }
630
631 void
vMBTCPPortClose()632 vMBTCPPortClose( )
633 {
634 // Release resources for the event queue.
635
636 // Try to exit the task gracefully, so select could release its internal callbacks
637 // that were allocated on the stack of the task we're going to delete
638 xShutdownSemaphore = xSemaphoreCreateBinary();
639 vTaskResume(xConfig.xMbTcpTaskHandle);
640 if (xShutdownSemaphore == NULL || // if no semaphore (alloc issues) or couldn't acquire it, just delete the task
641 xSemaphoreTake(xShutdownSemaphore, 2*pdMS_TO_TICKS(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND)) != pdTRUE) {
642 ESP_LOGE(TAG, "Task couldn't exit gracefully within timeout -> abruptly deleting the task");
643 vTaskDelete(xConfig.xMbTcpTaskHandle);
644 }
645 if (xShutdownSemaphore) {
646 vSemaphoreDelete(xShutdownSemaphore);
647 xShutdownSemaphore = NULL;
648 }
649
650 vMBPortEventClose( );
651 }
652
vMBTCPPortEnable(void)653 void vMBTCPPortEnable( void )
654 {
655 vTaskResume(xConfig.xMbTcpTaskHandle);
656 }
657
658 void
vMBTCPPortDisable(void)659 vMBTCPPortDisable( void )
660 {
661 vTaskSuspend(xConfig.xMbTcpTaskHandle);
662 for (int i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
663 MbClientInfo_t* pxClientInfo = xConfig.pxMbClientInfo[i];
664 if ((pxClientInfo != NULL) && (pxClientInfo->xSockId > 0)) {
665 xMBTCPPortCloseConnection(pxClientInfo);
666 vMBTCPPortFreeClientInfo(pxClientInfo);
667 xConfig.pxMbClientInfo[i] = NULL;
668 }
669 }
670 free(xConfig.pxMbClientInfo);
671 close(xListenSock);
672 xListenSock = -1;
673 vMBTCPPortRespQueueDelete(xConfig.xRespQueueHandle);
674 }
675
676 BOOL
xMBTCPPortGetRequest(UCHAR ** ppucMBTCPFrame,USHORT * usTCPLength)677 xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
678 {
679 BOOL xRet = FALSE;
680 if (xConfig.pxCurClientInfo) {
681 *ppucMBTCPFrame = &xConfig.pxCurClientInfo->pucTCPBuf[0];
682 *usTCPLength = xConfig.pxCurClientInfo->usTCPBufPos;
683
684 // Reset the buffer.
685 xConfig.pxCurClientInfo->usTCPBufPos = 0;
686 xConfig.pxCurClientInfo->usTCPFrameBytesLeft = MB_TCP_FUNC;
687 xRet = TRUE;
688 }
689 return xRet;
690 }
691
692 BOOL
xMBTCPPortSendResponse(UCHAR * pucMBTCPFrame,USHORT usTCPLength)693 xMBTCPPortSendResponse( UCHAR * pucMBTCPFrame, USHORT usTCPLength )
694 {
695 BOOL bFrameSent = FALSE;
696 fd_set xWriteSet;
697 fd_set xErrorSet;
698 int xErr = -1;
699 struct timeval xTimeVal;
700
701 if (xConfig.pxCurClientInfo) {
702 FD_ZERO(&xWriteSet);
703 FD_ZERO(&xErrorSet);
704 FD_SET(xConfig.pxCurClientInfo->xSockId, &xWriteSet);
705 FD_SET(xConfig.pxCurClientInfo->xSockId, &xErrorSet);
706 vxMBTCPPortMStoTimeVal(MB_TCP_SEND_TIMEOUT_MS, &xTimeVal);
707 // Check if socket writable
708 xErr = select(xConfig.pxCurClientInfo->xSockId + 1, NULL, &xWriteSet, &xErrorSet, &xTimeVal);
709 if ((xErr == -1) || FD_ISSET(xConfig.pxCurClientInfo->xSockId, &xErrorSet)) {
710 ESP_LOGE(TAG, "Socket(#%d) , send select() error = %d.",
711 xConfig.pxCurClientInfo->xSockId, errno);
712 return FALSE;
713 }
714
715 // Apply TID field from request to the frame before send response
716 pucMBTCPFrame[MB_TCP_TID] = (UCHAR)(xConfig.pxCurClientInfo->usTidCnt >> 8U);
717 pucMBTCPFrame[MB_TCP_TID + 1] = (UCHAR)(xConfig.pxCurClientInfo->usTidCnt & 0xFF);
718
719 // Write message into socket and disable Nagle's algorithm
720 xErr = send(xConfig.pxCurClientInfo->xSockId, pucMBTCPFrame, usTCPLength, TCP_NODELAY);
721 if (xErr < 0) {
722 ESP_LOGE(TAG, "Socket(#%d), fail to send data, errno = %d",
723 xConfig.pxCurClientInfo->xSockId, errno);
724 xConfig.pxCurClientInfo->xError = xErr;
725 } else {
726 bFrameSent = TRUE;
727 vxMBTCPPortRespQueueSend(xConfig.xRespQueueHandle, (void*)pucMBTCPFrame);
728 }
729 } else {
730 ESP_LOGD(TAG, "Port is not active. Release lock.");
731 vxMBTCPPortRespQueueSend(xConfig.xRespQueueHandle, (void*)pucMBTCPFrame);
732 }
733 return bFrameSent;
734 }
735
736 #endif //#if MB_TCP_ENABLED
737