1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtSerialBus module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #ifndef QMODBUSTCPSERVER_P_H
38 #define QMODBUSTCPSERVER_P_H
39 
40 #include <QtCore/qdatastream.h>
41 #include <QtCore/qdebug.h>
42 #include <QtCore/qloggingcategory.h>
43 #include <QtCore/qobject.h>
44 #include <QtNetwork/qhostaddress.h>
45 #include <QtNetwork/qtcpserver.h>
46 #include <QtNetwork/qtcpsocket.h>
47 #include <QtSerialBus/qmodbustcpserver.h>
48 
49 #include <private/qmodbusserver_p.h>
50 
51 #include <memory>
52 
53 //
54 //  W A R N I N G
55 //  -------------
56 //
57 // This file is not part of the Qt API. It exists purely as an
58 // implementation detail. This header file may change from version to
59 // version without notice, or even be removed.
60 //
61 // We mean it.
62 //
63 
64 QT_BEGIN_NAMESPACE
65 
Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)66 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
67 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
68 
69 class QModbusTcpServerPrivate : public QModbusServerPrivate
70 {
71     Q_DECLARE_PUBLIC(QModbusTcpServer)
72 
73 public:
74     /*
75         This function is a workaround since 2nd level lambda below cannot
76         call protected QModbusTcpServer::processRequest(..) function on VS2013.
77     */
78     QModbusResponse forwardProcessRequest(const QModbusRequest &r)
79     {
80         Q_Q(QModbusTcpServer);
81         if (q->value(QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
82             // If the device is busy, send an exception response without processing.
83             incrementCounter(QModbusServerPrivate::Counter::ServerBusy);
84             return QModbusExceptionResponse(r.functionCode(),
85                 QModbusExceptionResponse::ServerDeviceBusy);
86         }
87         return q->processRequest(r);
88     }
89 
90     /*
91         This function is a workaround since 2nd level lambda below cannot
92         call protected QModbusDevice::setError(..) function on VS2013.
93     */
94     void forwardError(const QString &errorText, QModbusDevice::Error error)
95     {
96         Q_Q(QModbusTcpServer);
97         q->setError(errorText, error);
98     }
99 
100     /*
101         This function is a workaround since 2nd level lambda below
102         cannot call QModbusServer::serverAddress(..) function on VS2013.
103     */
104     bool matchingServerAddress(quint8 unitId) const
105     {
106         Q_Q(const QModbusTcpServer);
107         if (q->serverAddress() == unitId)
108             return true;
109 
110         // No, not our address! Ignore!
111         qCDebug(QT_MODBUS) << "(TCP server) Wrong server unit identifier address, expected"
112             << q->serverAddress() << "got" << unitId;
113         return false;
114     }
115 
116     void setupTcpServer()
117     {
118         m_tcpServer = new QTcpServer(q_func());
119         QObject::connect(m_tcpServer, &QTcpServer::newConnection, q_func(), [this]() {
120             Q_Q(QModbusTcpServer);
121             auto *socket = m_tcpServer->nextPendingConnection();
122             if (!socket)
123                 return;
124 
125             qCDebug(QT_MODBUS) << "(TCP server) Incoming socket from" << socket->peerAddress()
126                                << socket->peerName() << socket->peerPort();
127 
128             if (m_observer && !m_observer->acceptNewConnection(socket)) {
129                 qCDebug(QT_MODBUS) << "(TCP server) Connection rejected by observer";
130                 socket->close();
131                 socket->deleteLater();
132                 return;
133             }
134 
135             connections.append(socket);
136 
137             auto buffer = new QByteArray();
138 
139             QObject::connect(socket, &QObject::destroyed, q, [buffer]() {
140                 // cleanup buffer
141                 delete buffer;
142             });
143             QObject::connect(socket, &QTcpSocket::disconnected, q, [socket, this]() {
144                 connections.removeAll(socket);
145 
146                 Q_Q(QModbusTcpServer);
147                 emit q->modbusClientDisconnected(socket);
148                 socket->deleteLater();
149             });
150             QObject::connect(socket, &QTcpSocket::readyRead, q, [buffer, socket, this]() {
151                 if (!socket)
152                     return;
153 
154                 buffer->append(socket->readAll());
155                 while (!buffer->isEmpty()) {
156                     qCDebug(QT_MODBUS_LOW).noquote() << "(TCP server) Read buffer: 0x"
157                         + buffer->toHex();
158 
159                     if (buffer->size() < mbpaHeaderSize) {
160                         qCDebug(QT_MODBUS) << "(TCP server) ADU too short. Waiting for more data.";
161                         return;
162                     }
163 
164                     quint8 unitId;
165                     quint16 transactionId, bytesPdu, protocolId;
166                     QDataStream input(*buffer);
167                     input >> transactionId >> protocolId >> bytesPdu >> unitId;
168 
169                     qCDebug(QT_MODBUS_LOW) << "(TCP server) Request MBPA:" << "Transaction Id:"
170                         << Qt::hex << transactionId << "Protocol Id:" << protocolId << "PDU bytes:"
171                         << bytesPdu << "Unit Id:" << unitId;
172 
173                     // The length field is the byte count of the following fields, including the Unit
174                     // Identifier and the PDU, so we remove on byte.
175                     bytesPdu--;
176 
177                     if (buffer->size() < mbpaHeaderSize + bytesPdu) {
178                         qCDebug(QT_MODBUS) << "(TCP server) PDU too short. Waiting for more data";
179                         return;
180                     }
181 
182                     QModbusRequest request;
183                     input >> request;
184 
185                     buffer->remove(0, mbpaHeaderSize + bytesPdu);
186 
187                     if (!matchingServerAddress(unitId))
188                         continue;
189 
190                     qCDebug(QT_MODBUS) << "(TCP server) Request PDU:" << request;
191                     const QModbusResponse response = forwardProcessRequest(request);
192                     qCDebug(QT_MODBUS) << "(TCP server) Response PDU:" << response;
193 
194                     QByteArray result;
195                     QDataStream output(&result, QIODevice::WriteOnly);
196                     // The length field is the byte count of the following fields, including the Unit
197                     // Identifier and PDU fields, so we add one byte to the response size.
198                     output << transactionId << protocolId << quint16(response.size() + 1)
199                            << unitId << response;
200 
201                     if (!socket->isOpen()) {
202                         qCDebug(QT_MODBUS) << "(TCP server) Requesting socket has closed.";
203                         forwardError(QModbusTcpServer::tr("Requesting socket is closed"),
204                                      QModbusDevice::WriteError);
205                         return;
206                     }
207 
208                     qint64 writtenBytes = socket->write(result);
209                     if (writtenBytes == -1 || writtenBytes < result.size()) {
210                         qCDebug(QT_MODBUS) << "(TCP server) Cannot write requested response to socket.";
211                         forwardError(QModbusTcpServer::tr("Could not write response to client"),
212                                      QModbusDevice::WriteError);
213                     }
214                 }
215             });
216         });
217 
218         QObject::connect(m_tcpServer, &QTcpServer::acceptError, q_func(),
219                          [this](QAbstractSocket::SocketError /*sError*/) {
220             Q_Q(QModbusTcpServer);
221 
222             qCWarning(QT_MODBUS) << "(TCP server) Accept error";
223             q->setError(m_tcpServer->errorString(), QModbusDevice::ConnectionError);
224         });
225     }
226 
227     QTcpServer *m_tcpServer { nullptr };
228     QVector<QTcpSocket *> connections;
229 
230     std::unique_ptr<QModbusTcpConnectionObserver> m_observer;
231 
232     static const qint8 mbpaHeaderSize = 7;
233     static const qint16 maxBytesModbusADU = 260;
234 };
235 
236 QT_END_NAMESPACE
237 
238 #endif // QMODBUSTCPSERVER_P_H
239 
240