/* Copyright 2018 Espressif Systems (Shanghai) PTE LTD
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
/*
  * FreeModbus Libary: ESP32 TCP Port
  * Copyright (C) 2006 Christian Walter <wolti@sil.at>
  * Parts of crt0.S Copyright (c) 1995, 1996, 1998 Cygnus Support
  *
  * This 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 2.1 of the License, or (at your option) any later version.
  *
  * This library 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 library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  * File: $Id: port.h,v 1.2 2006/09/04 14:39:20 wolti Exp $
  */

/* ----------------------- System includes ----------------------------------*/
#include <stdio.h>
#include <string.h>
#include "esp_err.h"

/* ----------------------- lwIP includes ------------------------------------*/
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "esp_netif.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb_m.h"
#include "port.h"
#include "mbport.h"
#include "mbframe.h"
#include "port_tcp_master.h"

#if MB_MASTER_TCP_ENABLED

/* ----------------------- Defines  -----------------------------------------*/
#define MB_TCP_CONNECTION_TIMEOUT_MS    ( 20 )      // Connection timeout in mS
#define MB_TCP_RECONNECT_TIMEOUT        ( 5000000 ) // Connection timeout in uS

#define MB_TCP_MASTER_PORT_TAG          "MB_TCP_MASTER_PORT"
#define MB_EVENT_REQ_DONE_MASK          (   EV_MASTER_PROCESS_SUCCESS | \
                                            EV_MASTER_ERROR_RESPOND_TIMEOUT | \
                                            EV_MASTER_ERROR_RECEIVE_DATA | \
                                            EV_MASTER_ERROR_EXECUTE_FUNCTION )

#define MB_EVENT_REQ_ERR_MASK           ( EV_MASTER_PROCESS_SUCCESS )

#define MB_EVENT_WAIT_TOUT_MS           ( 3000 )

#define MB_TCP_READ_TICK_MS             ( 1 )
#define MB_TCP_READ_BUF_RETRY_CNT       ( 4 )
#define MB_SLAVE_FMT(fmt)               "Slave #%d, Socket(#%d)(%s)"fmt

/* ----------------------- Types & Prototypes --------------------------------*/
void vMBPortEventClose( void );

/* ----------------------- Static variables ---------------------------------*/
static MbPortConfig_t xMbPortConfig;
static EventGroupHandle_t xMasterEventHandle = NULL;
static SemaphoreHandle_t xShutdownSemaphore = NULL;
static EventBits_t xMasterEvent = 0;

/* ----------------------- Static functions ---------------------------------*/
static void vMBTCPPortMasterTask(void *pvParameters);

/* ----------------------- Begin implementation -----------------------------*/

// Waits for stack start event to start Modbus event processing
BOOL xMBTCPPortMasterWaitEvent(EventGroupHandle_t xEventHandle, EventBits_t xEvent, USHORT usTimeout)
{
    xMasterEventHandle = xEventHandle;
    xMasterEvent = xEvent;
    BaseType_t status = xEventGroupWaitBits(xMasterEventHandle,
                                               (BaseType_t)(xEvent),
                                               pdFALSE, // do not clear start bit
                                               pdFALSE,
                                               usTimeout);
    return (BOOL)(status & xEvent);
}

BOOL
xMBMasterTCPPortInit( USHORT usTCPPort )
{
    BOOL bOkay = FALSE;

    xMbPortConfig.pxMbSlaveInfo = calloc(MB_TCP_PORT_MAX_CONN, sizeof(MbSlaveInfo_t*));
    if (!xMbPortConfig.pxMbSlaveInfo) {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "TCP slave info alloc failure.");
        return FALSE;
    }
    for(int idx = 0; idx < MB_TCP_PORT_MAX_CONN; xMbPortConfig.pxMbSlaveInfo[idx] = NULL, idx++);

    xMbPortConfig.xConnectQueue = NULL;
    xMbPortConfig.usPort = usTCPPort;
    xMbPortConfig.usMbSlaveInfoCount = 0;
    xMbPortConfig.ucCurSlaveIndex = 1;

    xMbPortConfig.xConnectQueue = xQueueCreate(2, sizeof(CHAR*));
    if (xMbPortConfig.xConnectQueue == 0)
    {
        // Queue was not created and must not be used.
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "TCP master queue creation failure.");
        return FALSE;
    }

    // Create task for packet processing
    BaseType_t xErr = xTaskCreatePinnedToCore(vMBTCPPortMasterTask,
                                    "tcp_master_task",
                                    MB_TCP_STACK_SIZE,
                                    NULL,
                                    MB_TCP_TASK_PRIO,
                                    &xMbPortConfig.xMbTcpTaskHandle,
                                    MB_PORT_TASK_AFFINITY);
    if (xErr != pdTRUE)
    {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "TCP master task creation failure.");
        (void)vTaskDelete(xMbPortConfig.xMbTcpTaskHandle);
    } else {
        ESP_LOGI(MB_TCP_MASTER_PORT_TAG, "TCP master stack initialized.");
        bOkay = TRUE;
    }

    vTaskSuspend(xMbPortConfig.xMbTcpTaskHandle);
    return bOkay;
}

static MbSlaveInfo_t* vMBTCPPortMasterGetCurrInfo(void)
{
    return xMbPortConfig.pxMbSlaveInfo[xMbPortConfig.ucCurSlaveIndex - 1];
}

// Start Modbus event state machine
static void vMBTCPPortMasterStartPoll(void)
{
    if (xMasterEventHandle) {
        // Set the mbcontroller start flag
        EventBits_t xFlags = xEventGroupSetBits(xMasterEventHandle,
                                                (EventBits_t)xMasterEvent);
        if (!(xFlags & xMasterEvent)) {
            ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Fail to start TCP stack.");
        }
    } else {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Fail to start polling. Incorrect event handle...");
    }
}

// Stop Modbus event state machine
static void vMBTCPPortMasterStopPoll(void)
{
    if (xMasterEventHandle) {
        // Set the mbcontroller start flag
        EventBits_t xFlags = xEventGroupClearBits(xMasterEventHandle,
                                                (EventBits_t)xMasterEvent);
        if (!(xFlags & xMasterEvent)) {
            ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Fail to stop polling.");
        }
    } else {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Fail to stop polling. Incorrect event handle...");
    }
}

// The helper function to get time stamp in microseconds
static int64_t xMBTCPGetTimeStamp(void)
{
    int64_t xTimeStamp = esp_timer_get_time();
    return xTimeStamp;
}

static void vMBTCPPortMasterMStoTimeVal(USHORT usTimeoutMs, struct timeval *tv)
{
    tv->tv_sec = usTimeoutMs / 1000;
    tv->tv_usec = (usTimeoutMs - (tv->tv_sec * 1000)) * 1000;
}

static void xMBTCPPortMasterCheckShutdown(void) {
    // First check if the task is not flagged for shutdown
    if (xShutdownSemaphore) {
        xSemaphoreGive(xShutdownSemaphore);
        vTaskDelete(NULL);
    }
}

static BOOL xMBTCPPortMasterCloseConnection(MbSlaveInfo_t* pxInfo)
{
    if (!pxInfo) {
        return FALSE;
    }
    if (pxInfo->xSockId == -1) {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Wrong socket info or disconnected socket: %d, skip.", pxInfo->xSockId);
        return FALSE;
    }
    if (shutdown(pxInfo->xSockId, SHUT_RDWR) == -1) {
        ESP_LOGV(MB_TCP_MASTER_PORT_TAG, "Shutdown failed sock %d, errno=%d", pxInfo->xSockId, errno);
    }
    close(pxInfo->xSockId);
    pxInfo->xSockId = -1;
    return TRUE;
}

void vMBTCPPortMasterSetNetOpt(void* pvNetIf, eMBPortIpVer xIpVersion, eMBPortProto xProto)
{
    xMbPortConfig.pvNetIface = pvNetIf;
    xMbPortConfig.eMbProto = xProto;
    xMbPortConfig.eMbIpVer = xIpVersion;
}

void vMBTCPPortMasterTaskStart(void)
{
    vTaskResume(xMbPortConfig.xMbTcpTaskHandle);
}

// Function returns time left for response processing according to response timeout
static int64_t xMBTCPPortMasterGetRespTimeLeft(MbSlaveInfo_t* pxInfo)
{
    if (!pxInfo) {
        return 0;
    }
    int64_t xTimeStamp = xMBTCPGetTimeStamp() - pxInfo->xSendTimeStamp;
    return (xTimeStamp > (1000 * MB_MASTER_TIMEOUT_MS_RESPOND)) ? 0 :
                    (MB_MASTER_TIMEOUT_MS_RESPOND - (xTimeStamp / 1000) - 1);
}

// Wait socket ready to read state
static int vMBTCPPortMasterRxCheck(int xSd, fd_set* pxFdSet, int xTimeMs)
{
    fd_set xReadSet = *pxFdSet;
    fd_set xErrorSet = *pxFdSet;
    int xRes = 0;
    struct timeval xTimeout;

    vMBTCPPortMasterMStoTimeVal(xTimeMs, &xTimeout);
    xRes = select(xSd + 1, &xReadSet, NULL, &xErrorSet, &xTimeout);
    if (xRes == 0) {
        // No respond from slave during timeout
        xRes = ERR_TIMEOUT;
    } else if ((xRes < 0) || FD_ISSET(xSd, &xErrorSet)) {
        xRes = -1;
    }

    *pxFdSet = xReadSet;
    return xRes;
}

static int xMBTCPPortMasterGetBuf(MbSlaveInfo_t* pxInfo, UCHAR* pucDstBuf, USHORT usLength)
{
    int xLength = 0;
    UCHAR* pucBuf = pucDstBuf;
    USHORT usBytesLeft = usLength;

    MB_PORT_CHECK((pxInfo && pxInfo->xSockId > -1), -1, "Try to read incorrect socket = #%d.", pxInfo->xSockId);

    // Receive data from connected client
    while (usBytesLeft > 0) {
        xMBTCPPortMasterCheckShutdown();
        // none blocking read from socket with timeout
        xLength = recv(pxInfo->xSockId, pucBuf, usBytesLeft, MSG_DONTWAIT);
        if (xLength < 0) {
            if (errno == EAGAIN) {
                // Read timeout occurred, continue reading
                continue;
            } else if (errno == ENOTCONN) {
                // Socket connection closed
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s) connection closed.",
                                            pxInfo->xSockId, pxInfo->pcIpAddr);
                return ERR_CONN;
            } else {
                // Other error occurred during receiving
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s) receive error, length=%d, errno=%d",
                                            pxInfo->xSockId, pxInfo->pcIpAddr, xLength, errno);
                return -1;
            }
        } else if (xLength) {
            pucBuf += xLength;
            usBytesLeft -= xLength;
        }
        if (xMBTCPPortMasterGetRespTimeLeft(pxInfo) == 0) {
            return ERR_TIMEOUT;
        }
        vTaskDelay(1);
    }
    return usLength;
}

static int vMBTCPPortMasterReadPacket(MbSlaveInfo_t* pxInfo)
{
    int xLength = 0;
    int xRet = 0;
    USHORT usTidRcv = 0;

    // Receive data from connected client
    if (pxInfo) {
        MB_PORT_CHECK((pxInfo->xSockId > 0), -1, "Try to read incorrect socket = #%d.", pxInfo->xSockId);
        // Read packet header
        xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[0], MB_TCP_UID);
        if (xRet < 0) {
            pxInfo->xRcvErr = xRet;
            return xRet;
        } else if (xRet != MB_TCP_UID) {
            ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Socket (#%d)(%s), Fail to read modbus header. ret=%d",
                                                                pxInfo->xSockId, pxInfo->pcIpAddr, xRet);
            pxInfo->xRcvErr = ERR_VAL;
            return ERR_VAL;
        }
        // If we have received the MBAP header we can analyze it and calculate
        // the number of bytes left to complete the current request.
        xLength = (int)MB_TCP_GET_FIELD(pxInfo->pucRcvBuf, MB_TCP_LEN);
        xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[MB_TCP_UID], xLength);
        if (xRet < 0) {
            pxInfo->xRcvErr = xRet;
            return xRet;
        } else if (xRet != xLength) {
            // Received incorrect or fragmented packet.
            ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s) incorrect packet, length=%d, TID=0x%02x, errno=%d(%s)",
                                               pxInfo->xSockId, pxInfo->pcIpAddr, pxInfo->usRcvPos,
                                               usTidRcv, errno, strerror(errno));
            pxInfo->xRcvErr = ERR_VAL;
            return ERR_VAL;
        }
        usTidRcv = MB_TCP_GET_FIELD(pxInfo->pucRcvBuf, MB_TCP_TID);

        // Check transaction identifier field in the incoming packet.
        if ((pxInfo->usTidCnt - 1) != usTidRcv) {
            ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Socket (#%d)(%s), incorrect TID(0x%02x)!=(0x%02x) received, discard data.",
                                                pxInfo->xSockId, pxInfo->pcIpAddr, usTidRcv, (pxInfo->usTidCnt - 1));
            pxInfo->xRcvErr = ERR_BUF;
            return ERR_BUF;
        }
        pxInfo->usRcvPos += xRet + MB_TCP_UID;
        ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s) get data, length=%d, TID=0x%02x, errno=%d(%s)",
                                           pxInfo->xSockId, pxInfo->pcIpAddr, pxInfo->usRcvPos,
                                           usTidRcv, errno, strerror(errno));
        pxInfo->xRcvErr = ERR_OK;
        return pxInfo->usRcvPos;
    }
    return -1;
}

static err_t xMBTCPPortMasterSetNonBlocking(MbSlaveInfo_t* pxInfo)
{
    if (!pxInfo) {
        return ERR_CONN;
    }
    // Set non blocking attribute for socket
    ULONG ulFlags = fcntl(pxInfo->xSockId, F_GETFL);
    if (fcntl(pxInfo->xSockId, F_SETFL, ulFlags | O_NONBLOCK) == -1) {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s), fcntl() call error=%d",
                                              pxInfo->xSockId, pxInfo->pcIpAddr, errno);
        return ERR_WOULDBLOCK;
    }
    return ERR_OK;
}

static void vMBTCPPortSetKeepAlive(MbSlaveInfo_t* pxInfo)
{
    int optval = 1;
    setsockopt(pxInfo->xSockId, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
}

// Check connection for timeout helper
static err_t xMBTCPPortMasterCheckAlive(MbSlaveInfo_t* pxInfo, ULONG xTimeoutMs)
{
    fd_set xWriteSet;
    fd_set xErrorSet;
    err_t xErr = -1;
    struct timeval xTimeVal;

    if (pxInfo && pxInfo->xSockId != -1) {
        FD_ZERO(&xWriteSet);
        FD_ZERO(&xErrorSet);
        FD_SET(pxInfo->xSockId, &xWriteSet);
        FD_SET(pxInfo->xSockId, &xErrorSet);
        vMBTCPPortMasterMStoTimeVal(xTimeoutMs, &xTimeVal);
        // Check if the socket is writable
        xErr = select(pxInfo->xSockId + 1, NULL, &xWriteSet, &xErrorSet, &xTimeVal);
        if ((xErr < 0) || FD_ISSET(pxInfo->xSockId, &xErrorSet)) {
            if (errno == EINPROGRESS) {
                xErr = ERR_INPROGRESS;
            } else {
                ESP_LOGV(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(" connection, select write err(errno) = %d(%d)."),
                                                    pxInfo->xIndex, pxInfo->xSockId, pxInfo->pcIpAddr, xErr, errno);
                xErr = ERR_CONN;
            }
        } else if (xErr == 0) {
            ESP_LOGV(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s), connection timeout occurred, err(errno) = %d(%d).",
                                        pxInfo->xSockId, pxInfo->pcIpAddr, xErr, errno);
            return ERR_INPROGRESS;
        } else {
            int xOptErr = 0;
            ULONG ulOptLen = sizeof(xOptErr);
            // Check socket error
            xErr = getsockopt(pxInfo->xSockId, SOL_SOCKET, SO_ERROR, (void*)&xOptErr, (socklen_t*)&ulOptLen);
            if (xOptErr != 0) {
                ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s), sock error occurred (%d).",
                                            pxInfo->xSockId, pxInfo->pcIpAddr, xOptErr);
                return ERR_CONN;
            }
            ESP_LOGV(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s), is alive.",
                                        pxInfo->xSockId, pxInfo->pcIpAddr);
            return ERR_OK;
        }
    } else {
        xErr = ERR_CONN;
    }
    return xErr;
}

// Resolve host name and/or fill the IP address structure
static BOOL xMBTCPPortMasterCheckHost(const CHAR* pcHostStr, ip_addr_t* pxHostAddr)
{
    MB_PORT_CHECK((pcHostStr), FALSE, "Wrong host name or IP.");
    CHAR cStr[45];
    CHAR* pcStr = &cStr[0];
    ip_addr_t xTargetAddr;
    struct addrinfo xHint;
    struct addrinfo* pxAddrList;
    memset(&xHint, 0, sizeof(xHint));
    // Do name resolution for both protocols
    xHint.ai_family = AF_UNSPEC;
    xHint.ai_flags = AI_ADDRCONFIG; // get IPV6 address if supported, otherwise IPV4
    memset(&xTargetAddr, 0, sizeof(xTargetAddr));

    // convert domain name to IP address
    // Todo: check EAI_FAIL error when resolve host name
    int xRet = getaddrinfo(pcHostStr, NULL, &xHint, &pxAddrList);

    if (xRet != 0) {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Incorrect host name or IP: %s", pcHostStr);
        return FALSE;
    }
    if (pxAddrList->ai_family == AF_INET) {
        struct in_addr addr4 = ((struct sockaddr_in *) (pxAddrList->ai_addr))->sin_addr;
        inet_addr_to_ip4addr(ip_2_ip4(&xTargetAddr), &addr4);
        pcStr = ip4addr_ntoa_r(ip_2_ip4(&xTargetAddr), cStr, sizeof(cStr));
    }
#if CONFIG_LWIP_IPV6
    else {
        struct in6_addr addr6 = ((struct sockaddr_in6 *) (pxAddrList->ai_addr))->sin6_addr;
        inet6_addr_to_ip6addr(ip_2_ip6(&xTargetAddr), &addr6);
        pcStr = ip6addr_ntoa_r(ip_2_ip6(&xTargetAddr), cStr, sizeof(cStr));
    }
#endif
    if (pxHostAddr) {
        *pxHostAddr = xTargetAddr;
    }
    ESP_LOGI(MB_TCP_MASTER_PORT_TAG, "Host[IP]: \"%s\"[%s]", pxAddrList->ai_canonname, pcStr);
    freeaddrinfo(pxAddrList);
    return TRUE;
}

BOOL xMBTCPPortMasterAddSlaveIp(const CHAR* pcIpStr)
{
    BOOL xRes = FALSE;
    MB_PORT_CHECK(xMbPortConfig.xConnectQueue != NULL, FALSE, "Wrong slave IP address to add.");
    if (pcIpStr) {
        xRes = xMBTCPPortMasterCheckHost(pcIpStr, NULL);
    }
    if (xRes || !pcIpStr) {
        BaseType_t xStatus = xQueueSend(xMbPortConfig.xConnectQueue, (const void*)&pcIpStr, 100);
        MB_PORT_CHECK((xStatus == pdTRUE), FALSE, "FAIL to add slave IP address: [%s].", pcIpStr);
    }
    return xRes;
}

// Unblocking connect function
static err_t xMBTCPPortMasterConnect(MbSlaveInfo_t* pxInfo)
{
    if (!pxInfo) {
        return ERR_CONN;
    }

    err_t xErr = ERR_OK;
    CHAR cStr[128];
    CHAR* pcStr = NULL;
    ip_addr_t xTargetAddr;
    struct addrinfo xHint;
    struct addrinfo* pxAddrList;
    struct addrinfo* pxCurAddr;

    memset(&xHint, 0, sizeof(xHint));
    // Do name resolution for both protocols
    //xHint.ai_family = AF_UNSPEC; Todo: Find a reason why AF_UNSPEC does not work
    xHint.ai_flags = AI_ADDRCONFIG; // get IPV6 address if supported, otherwise IPV4
    xHint.ai_family = (xMbPortConfig.eMbIpVer == MB_PORT_IPV4) ? AF_INET : AF_INET6;
    xHint.ai_socktype = (pxInfo->xMbProto == MB_PROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
    xHint.ai_protocol = (pxInfo->xMbProto == MB_PROTO_UDP) ? IPPROTO_UDP : IPPROTO_TCP;
    memset(&xTargetAddr, 0, sizeof(xTargetAddr));

    if (asprintf(&pcStr, "%u", xMbPortConfig.usPort) == -1) {
        abort();
    }

    // convert domain name to IP address
    int xRet = getaddrinfo(pxInfo->pcIpAddr, pcStr, &xHint, &pxAddrList);
    free(pcStr);
    if (xRet != 0) {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Cannot resolve host: %s", pxInfo->pcIpAddr);
        return ERR_CONN;
    }

    for (pxCurAddr = pxAddrList; pxCurAddr != NULL; pxCurAddr = pxCurAddr->ai_next) {
        if (pxCurAddr->ai_family == AF_INET) {
            struct in_addr addr4 = ((struct sockaddr_in *) (pxCurAddr->ai_addr))->sin_addr;
            inet_addr_to_ip4addr(ip_2_ip4(&xTargetAddr), &addr4);
            pcStr = ip4addr_ntoa_r(ip_2_ip4(&xTargetAddr), cStr, sizeof(cStr));
        }
#if CONFIG_LWIP_IPV6
        else if (pxCurAddr->ai_family == AF_INET6) {
            struct in6_addr addr6 = ((struct sockaddr_in6 *) (pxCurAddr->ai_addr))->sin6_addr;
            inet6_addr_to_ip6addr(ip_2_ip6(&xTargetAddr), &addr6);
            pcStr = ip6addr_ntoa_r(ip_2_ip6(&xTargetAddr), cStr, sizeof(cStr));
            // Set scope id to fix routing issues with local address
            ((struct sockaddr_in6 *) (pxCurAddr->ai_addr))->sin6_scope_id =
                                esp_netif_get_netif_impl_index(xMbPortConfig.pvNetIface);
        }
#endif
        if (pxInfo->xSockId <= 0) {
            pxInfo->xSockId = socket(pxCurAddr->ai_family, pxCurAddr->ai_socktype, pxCurAddr->ai_protocol);
            if (pxInfo->xSockId < 0) {
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Unable to create socket: #%d, errno %d", pxInfo->xSockId, errno);
                xErr = ERR_IF;
                continue;
            }
        } else {
            ESP_LOGV(MB_TCP_MASTER_PORT_TAG, "Socket (#%d)(%s) created.", pxInfo->xSockId, cStr);
        }

        // Set non blocking attribute for socket
        xMBTCPPortMasterSetNonBlocking(pxInfo);

        // Can return EINPROGRESS as an error which means
        // that connection is in progress and should be checked later
        xErr = connect(pxInfo->xSockId, (struct sockaddr*)pxCurAddr->ai_addr, pxCurAddr->ai_addrlen);
        if ((xErr < 0) && (errno == EINPROGRESS || errno == EALREADY)) {
            // The unblocking connect is pending (check status later) or already connected
            ESP_LOGV(MB_TCP_MASTER_PORT_TAG, "Socket(#%d)(%s) connection is pending, errno %d (%s).",
                                        pxInfo->xSockId, cStr, errno, strerror(errno));

            // Set keep alive flag in socket options
            vMBTCPPortSetKeepAlive(pxInfo);
            xErr = xMBTCPPortMasterCheckAlive(pxInfo, MB_TCP_CONNECTION_TIMEOUT_MS);
            continue;
        } else if ((xErr < 0) && (errno == EISCONN)) {
            // Socket already connected
            xErr = ERR_OK;
            continue;
        } else if (xErr != ERR_OK) {
            // Other error occurred during connection
            ESP_LOGV(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(" unable to connect, error=%d, errno %d (%s)"),
                                                pxInfo->xIndex, pxInfo->xSockId, cStr, xErr, errno, strerror(errno));
            xMBTCPPortMasterCloseConnection(pxInfo);
            xErr = ERR_CONN;
        } else {
            ESP_LOGI(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", successfully connected."),
                                                  pxInfo->xIndex, pxInfo->xSockId, cStr);
            continue;
        }
    }
    freeaddrinfo(pxAddrList);
    return xErr;
}

// Find the first slave info whose descriptor is set in xFdSet
static MbSlaveInfo_t* xMBTCPPortMasterGetSlaveReady(fd_set* pxFdSet)
{
    MbSlaveInfo_t* pxInfo = NULL;

    // Slave connection loop
    for (int xIndex = 0; (xIndex < MB_TCP_PORT_MAX_CONN); xIndex++) {
        pxInfo = xMbPortConfig.pxMbSlaveInfo[xIndex];
        if (pxInfo) {
            // Is this response for current processing slave
            if (FD_ISSET(pxInfo->xSockId, pxFdSet)) {
                FD_CLR(pxInfo->xSockId, pxFdSet);
                return pxInfo;
            }
        }
    }
    return (MbSlaveInfo_t*)NULL;
}

static int xMBTCPPortMasterCheckConnState(fd_set* pxFdSet)
{
    fd_set xConnSetCheck = *pxFdSet;
    MbSlaveInfo_t* pxInfo = NULL;
    int64_t xTime = 0;
    int xErr = 0;
    int xCount = 0;
    do {
        xTime = xMBTCPGetTimeStamp();
        pxInfo = xMBTCPPortMasterGetSlaveReady(&xConnSetCheck);
        if (pxInfo) {
            xErr = xMBTCPPortMasterCheckAlive(pxInfo, 0);
            if ((xErr < 0) && (((xTime - pxInfo->xRecvTimeStamp) > MB_TCP_RECONNECT_TIMEOUT) ||
                                ((xTime - pxInfo->xSendTimeStamp) > MB_TCP_RECONNECT_TIMEOUT))) {
                ESP_LOGI(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", slave is down, off_time[r][w](us) = [%ju][%ju]."),
                                                            pxInfo->xIndex,
                                                            pxInfo->xSockId,
                                                            pxInfo->pcIpAddr,
                                                            (int64_t)(xTime - pxInfo->xRecvTimeStamp),
                                                            (int64_t)(xTime - pxInfo->xSendTimeStamp));
                xCount++;
            }
        }
    } while (pxInfo && (xCount < MB_TCP_PORT_MAX_CONN));
    return xCount;
}

static void xMBTCPPortMasterFsmSetError(eMBMasterErrorEventType xErrType, eMBMasterEventType xPostEvent)
{
    vMBMasterPortTimersDisable();
    vMBMasterSetErrorType(xErrType);
    xMBMasterPortEventPost(xPostEvent);
}

static void vMBTCPPortMasterTask(void *pvParameters)
{
    CHAR* pcAddrStr = NULL;
    MbSlaveInfo_t* pxInfo;
    MbSlaveInfo_t* pxCurrInfo;
    fd_set xConnSet;
    fd_set xReadSet;
    int xMaxSd = 0;
    err_t xErr = ERR_ABRT;
    USHORT usSlaveConnCnt = 0;
    int64_t xTime = 0;

    // Register each slave in the connection info structure
    while (1) {
        BaseType_t xStatus = xQueueReceive(xMbPortConfig.xConnectQueue, (void*)&pcAddrStr, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS));
        xMBTCPPortMasterCheckShutdown();
        if (xStatus != pdTRUE) {
            ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Fail to register slave IP.");
        } else {
            if (pcAddrStr == NULL && xMbPortConfig.usMbSlaveInfoCount) {
                break;
            }
            if (xMbPortConfig.usMbSlaveInfoCount > MB_TCP_PORT_MAX_CONN) {
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Exceeds maximum connections limit=%d.", MB_TCP_PORT_MAX_CONN);
                break;
            }
            pxInfo = calloc(1, sizeof(MbSlaveInfo_t));
            if (!pxInfo) {
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Slave(#%d), info structure allocation fail.",
                                                    xMbPortConfig.usMbSlaveInfoCount);
                free(pxInfo);
                break;
            }
            pxInfo->pucRcvBuf = calloc(MB_TCP_BUF_SIZE, sizeof(UCHAR));
            if (!pxInfo->pucRcvBuf) {
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Slave(#%d), receive buffer allocation fail.",
                                                    xMbPortConfig.usMbSlaveInfoCount);
                free(pxInfo->pucRcvBuf);
                break;
            }
            pxInfo->usRcvPos = 0;
            pxInfo->pcIpAddr = pcAddrStr;
            pxInfo->xSockId = -1;
            pxInfo->xError = -1;
            pxInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
            pxInfo->xSendTimeStamp = xMBTCPGetTimeStamp();
            pxInfo->xMbProto = MB_PROTO_TCP;
            pxInfo->xIndex = xMbPortConfig.usMbSlaveInfoCount;
            pxInfo->usTidCnt = (USHORT)(xMbPortConfig.usMbSlaveInfoCount << 8U);
            // Register slave
            xMbPortConfig.pxMbSlaveInfo[xMbPortConfig.usMbSlaveInfoCount++] = pxInfo;
            ESP_LOGI(MB_TCP_MASTER_PORT_TAG, "Add slave IP: %s", pcAddrStr);
        }
    }

    // Main connection cycle
    while (1)
    {
        ESP_LOGI(MB_TCP_MASTER_PORT_TAG, "Connecting to slaves...");
        xTime = xMBTCPGetTimeStamp();
        usSlaveConnCnt = 0;
        CHAR ucDot = '.';
        while(usSlaveConnCnt < xMbPortConfig.usMbSlaveInfoCount) {
            usSlaveConnCnt = 0;
            FD_ZERO(&xConnSet);
            ucDot ^= 0x03;
            // Slave connection loop
            for (UCHAR ucCnt = 0; (ucCnt < MB_TCP_PORT_MAX_CONN); ucCnt++) {
                pxInfo = xMbPortConfig.pxMbSlaveInfo[ucCnt];
                // if slave descriptor is NULL then it is end of list or connection closed.
                if (!pxInfo) {
                    ESP_LOGV(MB_TCP_MASTER_PORT_TAG, "Index: %d is not initialized, skip.", ucCnt);
                    if (xMbPortConfig.usMbSlaveInfoCount) {
                        continue;
                    }
                    break;
                }
                putchar(ucDot);
                xErr = xMBTCPPortMasterConnect(pxInfo);
                switch(xErr)
                {
                    case ERR_CONN:
                    case ERR_INPROGRESS:
                        // In case of connection errors remove the socket from set
                        if (FD_ISSET(pxInfo->xSockId, &xConnSet)) {
                            FD_CLR(pxInfo->xSockId, &xConnSet);
                            ESP_LOGE(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(" connect failed, error = %d."),
                                                                            pxInfo->xIndex, pxInfo->xSockId,
                                                                            (char*)pxInfo->pcIpAddr, xErr);
                                if (usSlaveConnCnt) {
                                    usSlaveConnCnt--;
                                }
                        }
                        break;
                    case ERR_OK:
                        // if connection is successful, add the descriptor into set
                        if (!FD_ISSET(pxInfo->xSockId, &xConnSet)) {
                            FD_SET(pxInfo->xSockId, &xConnSet);
                            usSlaveConnCnt++;
                            xMaxSd = (pxInfo->xSockId > xMaxSd) ? pxInfo->xSockId : xMaxSd;
                            ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", connected %d slave(s), error = %d."),
                                                                pxInfo->xIndex, pxInfo->xSockId,
                                                                pxInfo->pcIpAddr,
                                                                usSlaveConnCnt, xErr);
                            // Update time stamp for connected slaves
                            pxInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
                            pxInfo->xSendTimeStamp = xMBTCPGetTimeStamp();
                        }
                        break;
                    default:
                        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", unexpected error = %d."),
                                                            pxInfo->xIndex,
                                                            pxInfo->xSockId,
                                                            pxInfo->pcIpAddr, xErr);
                        break;
                }
                if (pxInfo) {
                    pxInfo->xError = xErr;
                }
                xMBTCPPortMasterCheckShutdown();
            }
        }
        ESP_LOGI(MB_TCP_MASTER_PORT_TAG, "Connected %d slaves, start polling...", usSlaveConnCnt);

        vMBTCPPortMasterStartPoll(); // Send event to start stack

        // Slave receive data loop
        while(usSlaveConnCnt) {
            xReadSet = xConnSet;
            // Check transmission event to clear appropriate bit.
            xMBMasterPortFsmWaitConfirmation(EV_MASTER_FRAME_TRANSMIT, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS));
            // Synchronize state machine with send packet event
            if (xMBMasterPortFsmWaitConfirmation(EV_MASTER_FRAME_SENT, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS))) {
                ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "FSM Synchronized with sent event.");
            }
            // Get slave info for the current slave.
            pxCurrInfo = vMBTCPPortMasterGetCurrInfo();
            if (!pxCurrInfo) {
                ESP_LOGE(MB_TCP_MASTER_PORT_TAG, "Incorrect connection options for slave index: %d.",
                                            xMbPortConfig.ucCurSlaveIndex);
                vMBTCPPortMasterStopPoll();
                xMBTCPPortMasterCheckShutdown();
                break; // incorrect slave descriptor, reconnect.
            }
            xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo);
            ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Set select timeout, left time: %ju ms.",
                                        xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo));
            // Wait respond from current slave during respond timeout
            int xRes = vMBTCPPortMasterRxCheck(pxCurrInfo->xSockId, &xReadSet, xTime);
            if (xRes == ERR_TIMEOUT) {
                // No respond from current slave, process timeout.
                // Need to drop response later if it is received after timeout.
                ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Select timeout, left time: %ju ms.",
                                                    xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo));
                xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo);
                // Wait completion of last transaction
                xMBMasterPortFsmWaitConfirmation(MB_EVENT_REQ_DONE_MASK, pdMS_TO_TICKS(xTime + 1));
                xMBTCPPortMasterCheckShutdown();
                continue;
            } else if (xRes < 0) {
                // Select error (slave connection or r/w failure).
                ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", socket select error. Slave disconnected?"),
                            pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr);
                xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo);
                // Wait completion of last transaction
                xMBMasterPortFsmWaitConfirmation(MB_EVENT_REQ_DONE_MASK, pdMS_TO_TICKS(xTime));
                // Stop polling process
                vMBTCPPortMasterStopPoll();
                xMBTCPPortMasterCheckShutdown();
                // Check disconnected slaves, do not need a result just to print information.
                xMBTCPPortMasterCheckConnState(&xConnSet);
                break;
            } else {
                // Check to make sure that active slave data is ready
                if (FD_ISSET(pxCurrInfo->xSockId, &xReadSet)) {
                    xErr = ERR_BUF;
                    for (int retry = 0; (xErr == ERR_BUF) && (retry < MB_TCP_READ_BUF_RETRY_CNT); retry++) {
                        xErr = vMBTCPPortMasterReadPacket(pxCurrInfo);
                        // The error ERR_BUF means received response to previous request
                        // (due to timeout) with the same socket ID and incorrect TID,
                        // then ignore it and try to get next response buffer.
                    }
                    if (xErr > 0) {
                        // Response received correctly, send an event to stack
                        xMBTCPPortMasterFsmSetError(EV_ERROR_INIT, EV_MASTER_FRAME_RECEIVED);
                        ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", frame received."),
                                    pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr);
                    } else if ((xErr == ERR_TIMEOUT) || (xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo) == 0)) {
                        // Timeout occurred when receiving frame, process respond timeout
                        ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", frame read timeout."),
                                    pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr);
                    } else if (xErr == ERR_BUF) {
                        // After retries a response with incorrect TID received, process failure.
                        xMBTCPPortMasterFsmSetError(EV_ERROR_RECEIVE_DATA, EV_MASTER_ERROR_PROCESS);
                        ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", frame error."),
                                    pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr);
                    } else {
                        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", critical error=%d."),
                                    pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr, xErr);
                        // Stop polling process
                        vMBTCPPortMasterStopPoll();
                        xMBTCPPortMasterCheckShutdown();
                        // Check disconnected slaves, do not need a result just to print information.
                        xMBTCPPortMasterCheckConnState(&xConnSet);
                        break;
                    }
                    xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo);
                    ESP_LOGD(MB_TCP_MASTER_PORT_TAG, "Slave #%d, data processing left time %ju [ms].", pxCurrInfo->xIndex, xTime);
                    // Wait completion of Modbus frame processing before start of new transaction.
                    if (xMBMasterPortFsmWaitConfirmation(MB_EVENT_REQ_DONE_MASK, pdMS_TO_TICKS(xTime))) {
                        ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", data processing completed."),
                                pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr);
                    }
                    xTime = xMBTCPGetTimeStamp() - pxCurrInfo->xSendTimeStamp;
                    ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", processing time[us] = %ju."),
                                                    pxCurrInfo->xIndex, pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr, xTime);
                }
            }
            xMBTCPPortMasterCheckShutdown();
        } // while(usMbSlaveInfoCount)
    } // while (1)
    vTaskDelete(NULL);
}

extern void vMBMasterPortEventClose(void);
extern void vMBMasterPortTimerClose(void);

void
vMBMasterTCPPortDisable(void)
{
    for (USHORT ucCnt = 0; ucCnt < MB_TCP_PORT_MAX_CONN; ucCnt++) {
        MbSlaveInfo_t* pxInfo = xMbPortConfig.pxMbSlaveInfo[ucCnt];
        if (pxInfo) {
            xMBTCPPortMasterCloseConnection(pxInfo);
            if (pxInfo->pucRcvBuf) {
                free(pxInfo->pucRcvBuf);
            }
            free(pxInfo);
            xMbPortConfig.pxMbSlaveInfo[ucCnt] = NULL;
        }
    }
}

void
vMBMasterTCPPortClose(void)
{
    // Try to exit the task gracefully, so select could release its internal callbacks
    // that were allocated on the stack of the task we're going to delete
    xShutdownSemaphore = xSemaphoreCreateBinary();
    // if no semaphore (alloc issues) or couldn't acquire it, just delete the task
    if (xShutdownSemaphore == NULL || xSemaphoreTake(xShutdownSemaphore, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS)) != pdTRUE) {
        ESP_LOGW(MB_TCP_MASTER_PORT_TAG, "Modbus port task couldn't exit gracefully within timeout -> abruptly deleting the task.");
        vTaskDelete(xMbPortConfig.xMbTcpTaskHandle);
    }
    if (xShutdownSemaphore) {
        vSemaphoreDelete(xShutdownSemaphore);
        xShutdownSemaphore = NULL;
    }
    vMBMasterTCPPortDisable();
    free(xMbPortConfig.pxMbSlaveInfo);
    vQueueDelete(xMbPortConfig.xConnectQueue);
    vMBMasterPortTimerClose();
    // Release resources for the event queue.
    vMBMasterPortEventClose();
}

BOOL
xMBMasterTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
{
    MbSlaveInfo_t* pxInfo = vMBTCPPortMasterGetCurrInfo();
    *ppucMBTCPFrame = pxInfo->pucRcvBuf;
    *usTCPLength = pxInfo->usRcvPos;

    // Reset the buffer.
    pxInfo->usRcvPos = 0;
    // Save slave receive timestamp
    if (pxInfo->xRcvErr == ERR_OK && *usTCPLength > 0) {
        pxInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
        return TRUE;
    }
    return FALSE;
}

int xMBMasterTCPPortWritePoll(MbSlaveInfo_t* pxInfo, const UCHAR * pucMBTCPFrame, USHORT usTCPLength, ULONG xTimeout)
{
    // Check if the socket is alive (writable and SO_ERROR == 0)
    int xRes = (int)xMBTCPPortMasterCheckAlive(pxInfo, xTimeout);
    if ((xRes < 0) && (xRes != ERR_INPROGRESS))
    {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", is not writable, error: %d, errno %d"),
                                    pxInfo->xIndex, pxInfo->xSockId, pxInfo->pcIpAddr, xRes, errno);
        return xRes;
    }
    xRes = send(pxInfo->xSockId, pucMBTCPFrame, usTCPLength, TCP_NODELAY);
    if (xRes < 0) {
        ESP_LOGE(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", send data error: %d, errno %d"),
                                        pxInfo->xIndex, pxInfo->xSockId, pxInfo->pcIpAddr, xRes, errno);
    }
    return xRes;
}

BOOL
xMBMasterTCPPortSendResponse( UCHAR * pucMBTCPFrame, USHORT usTCPLength )
{
    BOOL bFrameSent = FALSE;
    xMbPortConfig.ucCurSlaveIndex = ucMBMasterGetDestAddress();
    MbSlaveInfo_t* pxInfo = vMBTCPPortMasterGetCurrInfo();

    // If socket active then send data
    if (pxInfo->xSockId > -1) {
        // Apply TID field to the frame before send
        pucMBTCPFrame[MB_TCP_TID] = (UCHAR)(pxInfo->usTidCnt >> 8U);
        pucMBTCPFrame[MB_TCP_TID + 1] = (UCHAR)(pxInfo->usTidCnt & 0xFF);
        int xRes = xMBMasterTCPPortWritePoll(pxInfo, pucMBTCPFrame, usTCPLength, MB_TCP_SEND_TIMEOUT_MS);
        if (xRes < 0) {
            ESP_LOGE(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", send data failure, err(errno) = %d(%d)."),
                                           pxInfo->xIndex, pxInfo->xSockId, pxInfo->pcIpAddr, xRes, errno);
            bFrameSent = FALSE;
            pxInfo->xError = xRes;
        } else {
            bFrameSent = TRUE;
            ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", send data successful: TID=0x%02x, %d (bytes), errno %d"),
                                                pxInfo->xIndex, pxInfo->xSockId, pxInfo->pcIpAddr, pxInfo->usTidCnt, xRes, errno);
            pxInfo->xError = 0;
            pxInfo->usRcvPos = 0;
            if (pxInfo->usTidCnt < (USHRT_MAX - 1)) {
                pxInfo->usTidCnt++;
            } else {
                pxInfo->usTidCnt = (USHORT)(pxInfo->xIndex << 8U);
            }
        }
        pxInfo->xSendTimeStamp = xMBTCPGetTimeStamp();
    } else {
        ESP_LOGD(MB_TCP_MASTER_PORT_TAG, MB_SLAVE_FMT(", send to died slave, error = %d"),
                                                  pxInfo->xIndex, pxInfo->xSockId, pxInfo->pcIpAddr, pxInfo->xError);
    }
    vMBMasterPortTimersRespondTimeoutEnable();
    xMBMasterPortEventPost(EV_MASTER_FRAME_SENT);
    return bFrameSent;
}

// Timer handler to check timeout of socket response
BOOL MB_PORT_ISR_ATTR
xMBMasterTCPTimerExpired(void)
{
    BOOL xNeedPoll = FALSE;

    vMBMasterPortTimersDisable();
    // If timer mode is respond timeout, the master event then turns EV_MASTER_EXECUTE status.
    if (xMBMasterGetCurTimerMode() == MB_TMODE_RESPOND_TIMEOUT) {
        vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT);
        xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
    }

    return xNeedPoll;
}

#endif //#if MB_MASTER_TCP_ENABLED
