1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#ifndef QMODBUSRTUSERIALSERVER_P_H
6#define QMODBUSRTUSERIALSERVER_P_H
7
8#include <QtCore/qbytearray.h>
9#include <QtCore/qdebug.h>
10#include <QtCore/qelapsedtimer.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qmath.h>
13#include <QtSerialBus/qmodbusrtuserialserver.h>
14#include <QtSerialPort/qserialport.h>
15
16#include <private/qmodbusadu_p.h>
17#include <private/qmodbusserver_p.h>
18
19//
20// W A R N I N G
21// -------------
22//
23// This file is not part of the Qt API. It exists purely as an
24// implementation detail. This header file may change from version to
25// version without notice, or even be removed.
26//
27// We mean it.
28//
29
30QT_BEGIN_NAMESPACE
31
32Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
33Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
34
35class QModbusRtuSerialServerPrivate : public QModbusServerPrivate
36{
37 Q_DECLARE_PUBLIC(QModbusRtuSerialServer)
38
39public:
40 void setupSerialPort()
41 {
42 Q_Q(QModbusRtuSerialServer);
43
44 m_serialPort = new QSerialPort(q);
45 QObject::connect(sender: m_serialPort, signal: &QSerialPort::readyRead, context: q, slot: [this]() {
46
47 if (m_interFrameTimer.isValid()
48 && m_interFrameTimer.elapsed() > m_interFrameDelayMilliseconds
49 && !m_requestBuffer.isEmpty()) {
50 // This permits response buffer clearing if it contains garbage
51 // but still permits cases where very slow baud rates can cause
52 // chunked and delayed packets
53 qCDebug(QT_MODBUS_LOW) << "(RTU server) Dropping older ADU fragments due to larger than 3.5 char delay (expected:"
54 << m_interFrameDelayMilliseconds << ", max:"
55 << m_interFrameTimer.elapsed() << ")";
56 m_requestBuffer.clear();
57 }
58
59 m_interFrameTimer.start();
60
61 const qint64 size = m_serialPort->size();
62 m_requestBuffer += m_serialPort->read(maxlen: size);
63
64 const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, m_requestBuffer);
65 qCDebug(QT_MODBUS_LOW) << "(RTU server) Received ADU:" << adu.rawData().toHex();
66
67 // Index -> description
68 // Server address -> 1 byte
69 // FunctionCode -> 1 byte
70 // FunctionCode specific content -> 0-252 bytes
71 // CRC -> 2 bytes
72 Q_Q(QModbusRtuSerialServer);
73 QModbusCommEvent event = QModbusCommEvent::ReceiveEvent;
74 if (q->value(option: QModbusServer::ListenOnlyMode).toBool())
75 event |= QModbusCommEvent::ReceiveFlag::CurrentlyInListenOnlyMode;
76
77 // We expect at least the server address, function code and CRC.
78 if (adu.rawSize() < 4) { // TODO: LRC should be 3 bytes.
79 qCWarning(QT_MODBUS) << "(RTU server) Incomplete ADU received, ignoring";
80
81 // The quantity of CRC errors encountered by the remote device since its last
82 // restart, clear counters operation, or power-up. In case of a message
83 // length < 4 bytes, the receiving device is not able to calculate the CRC.
84 incrementCounter(counter: QModbusServerPrivate::Counter::BusCommunicationError);
85 storeModbusCommEvent(eventByte: event | QModbusCommEvent::ReceiveFlag::CommunicationError);
86 return;
87 }
88
89 // Server address is set to 0, this is a broadcast.
90 m_processesBroadcast = (adu.serverAddress() == 0);
91 if (q->processesBroadcast())
92 event |= QModbusCommEvent::ReceiveFlag::BroadcastReceived;
93
94 const int pduSizeWithoutFcode = QModbusRequest::calculateDataSize(pdu: adu.pdu());
95
96 // server address byte + function code byte + PDU size + 2 bytes CRC
97 if ((pduSizeWithoutFcode < 0) || ((2 + pduSizeWithoutFcode + 2) != adu.rawSize())) {
98 qCWarning(QT_MODBUS) << "(RTU server) ADU does not match expected size, ignoring";
99 // The quantity of messages addressed to the remote device that it could not
100 // handle due to a character overrun condition, since its last restart, clear
101 // counters operation, or power-up. A character overrun is caused by data
102 // characters arriving at the port faster than they can be stored, or by the loss
103 // of a character due to a hardware malfunction.
104 incrementCounter(counter: QModbusServerPrivate::Counter::BusCharacterOverrun);
105 storeModbusCommEvent(eventByte: event | QModbusCommEvent::ReceiveFlag::CharacterOverrun);
106 return;
107 }
108
109 // We received the full message, including checksum. We do not expect more bytes to
110 // arrive, so clear the buffer. All new bytes are considered part of the next message.
111 m_requestBuffer.resize(size: 0);
112
113 if (!adu.matchingChecksum()) {
114 qCWarning(QT_MODBUS) << "(RTU server) Discarding request with wrong CRC, received:"
115 << adu.checksum<quint16>() << ", calculated CRC:"
116 << QModbusSerialAdu::calculateCRC(data: adu.data(), len: adu.size());
117 // The quantity of CRC errors encountered by the remote device since its last
118 // restart, clear counters operation, or power-up.
119 incrementCounter(counter: QModbusServerPrivate::Counter::BusCommunicationError);
120 storeModbusCommEvent(eventByte: event | QModbusCommEvent::ReceiveFlag::CommunicationError);
121 return;
122 }
123
124 // The quantity of messages that the remote device has detected on the communications
125 // system since its last restart, clear counters operation, or power-up.
126 incrementCounter(counter: QModbusServerPrivate::Counter::BusMessage);
127
128 // If we do not process a Broadcast ...
129 if (!q->processesBroadcast()) {
130 // check if the server address matches ...
131 if (q->serverAddress() != adu.serverAddress()) {
132 // no, not our address! Ignore!
133 qCDebug(QT_MODBUS) << "(RTU server) Wrong server address, expected"
134 << q->serverAddress() << "got" << adu.serverAddress();
135 return;
136 }
137 } // else { Broadcast -> Server address will never match, deliberately ignore }
138
139 storeModbusCommEvent(eventByte: event); // store the final event before processing
140
141 const QModbusRequest req = adu.pdu();
142 qCDebug(QT_MODBUS) << "(RTU server) Request PDU:" << req;
143 QModbusResponse response; // If the device ...
144 if (q->value(option: QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
145 // is busy, update the quantity of messages addressed to the remote device for
146 // which it returned a Server Device Busy exception response, since its last
147 // restart, clear counters operation, or power-up.
148 incrementCounter(counter: QModbusServerPrivate::Counter::ServerBusy);
149 response = QModbusExceptionResponse(req.functionCode(),
150 QModbusExceptionResponse::ServerDeviceBusy);
151 } else {
152 // is not busy, update the quantity of messages addressed to the remote device,
153 // or broadcast, that the remote device has processed since its last restart,
154 // clear counters operation, or power-up.
155 incrementCounter(counter: QModbusServerPrivate::Counter::ServerMessage);
156 response = q->processRequest(request: req);
157 }
158 qCDebug(QT_MODBUS) << "(RTU server) Response PDU:" << response;
159
160 event = QModbusCommEvent::SentEvent; // reset event after processing
161 if (q->value(option: QModbusServer::ListenOnlyMode).toBool())
162 event |= QModbusCommEvent::SendFlag::CurrentlyInListenOnlyMode;
163
164 if ((!response.isValid())
165 || q->processesBroadcast()
166 || q->value(option: QModbusServer::ListenOnlyMode).toBool()) {
167 // The quantity of messages addressed to the remote device for which it has
168 // returned no response (neither a normal response nor an exception response),
169 // since its last restart, clear counters operation, or power-up.
170 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNoResponse);
171 storeModbusCommEvent(eventByte: event);
172 return;
173 }
174
175 const QByteArray result = QModbusSerialAdu::create(type: QModbusSerialAdu::Rtu,
176 serverAddress: q->serverAddress(), pdu: response);
177
178 qCDebug(QT_MODBUS_LOW) << "(RTU server) Response ADU:" << result.toHex();
179
180 if (!m_serialPort->isOpen()) {
181 qCDebug(QT_MODBUS) << "(RTU server) Requesting serial port has closed.";
182 q->setError(errorText: QModbusRtuSerialServer::tr(s: "Requesting serial port is closed"),
183 error: QModbusDevice::WriteError);
184 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNoResponse);
185 storeModbusCommEvent(eventByte: event);
186 return;
187 }
188
189 qint64 writtenBytes = m_serialPort->write(data: result);
190 if ((writtenBytes == -1) || (writtenBytes < result.size())) {
191 qCDebug(QT_MODBUS) << "(RTU server) Cannot write requested response to serial port.";
192 q->setError(errorText: QModbusRtuSerialServer::tr(s: "Could not write response to client"),
193 error: QModbusDevice::WriteError);
194 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNoResponse);
195 storeModbusCommEvent(eventByte: event);
196 m_serialPort->clear(directions: QSerialPort::Output);
197 return;
198 }
199
200 if (response.isException()) {
201 switch (response.exceptionCode()) {
202 case QModbusExceptionResponse::IllegalFunction:
203 case QModbusExceptionResponse::IllegalDataAddress:
204 case QModbusExceptionResponse::IllegalDataValue:
205 event |= QModbusCommEvent::SendFlag::ReadExceptionSent;
206 break;
207
208 case QModbusExceptionResponse::ServerDeviceFailure:
209 event |= QModbusCommEvent::SendFlag::ServerAbortExceptionSent;
210 break;
211
212 case QModbusExceptionResponse::ServerDeviceBusy:
213 // The quantity of messages addressed to the remote device for which it
214 // returned a server device busy exception response, since its last restart,
215 // clear counters operation, or power-up.
216 incrementCounter(counter: QModbusServerPrivate::Counter::ServerBusy);
217 event |= QModbusCommEvent::SendFlag::ServerBusyExceptionSent;
218 break;
219
220 case QModbusExceptionResponse::NegativeAcknowledge:
221 // The quantity of messages addressed to the remote device for which it
222 // returned a negative acknowledge (NAK) exception response, since its last
223 // restart, clear counters operation, or power-up.
224 incrementCounter(counter: QModbusServerPrivate::Counter::ServerNAK);
225 event |= QModbusCommEvent::SendFlag::ServerProgramNAKExceptionSent;
226 break;
227
228 default:
229 break;
230 }
231 // The quantity of Modbus exception responses returned by the remote device since
232 // its last restart, clear counters operation, or power-up.
233 incrementCounter(counter: QModbusServerPrivate::Counter::BusExceptionError);
234 } else {
235 switch (quint16(req.functionCode())) {
236 case 0x0a: // Poll 484 (not in the official Modbus specification) *1
237 case 0x0e: // Poll Controller (not in the official Modbus specification) *1
238 case QModbusRequest::GetCommEventCounter: // fall through and bail out
239 break;
240 default:
241 // The device's event counter is incremented once for each successful message
242 // completion. Do not increment for exception responses, poll commands, or fetch
243 // event counter commands. *1 but mentioned here ^^^
244 incrementCounter(counter: QModbusServerPrivate::Counter::CommEvent);
245 break;
246 }
247 }
248 storeModbusCommEvent(eventByte: event); // store the final event after processing
249 });
250
251 QObject::connect(sender: m_serialPort, signal: &QSerialPort::errorOccurred, context: q,
252 slot: [this](QSerialPort::SerialPortError error) {
253 if (error == QSerialPort::NoError)
254 return;
255
256 qCDebug(QT_MODBUS) << "(RTU server) QSerialPort error:" << error
257 << (m_serialPort ? m_serialPort->errorString() : QString());
258
259 Q_Q(QModbusRtuSerialServer);
260
261 switch (error) {
262 case QSerialPort::DeviceNotFoundError:
263 q->setError(errorText: QModbusDevice::tr(s: "Referenced serial device does not exist."),
264 error: QModbusDevice::ConnectionError);
265 break;
266 case QSerialPort::PermissionError:
267 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device due to permissions."),
268 error: QModbusDevice::ConnectionError);
269 break;
270 case QSerialPort::OpenError:
271 case QSerialPort::NotOpenError:
272 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device."),
273 error: QModbusDevice::ConnectionError);
274 break;
275 case QSerialPort::WriteError:
276 q->setError(errorText: QModbusDevice::tr(s: "Write error."), error: QModbusDevice::WriteError);
277 break;
278 case QSerialPort::ReadError:
279 q->setError(errorText: QModbusDevice::tr(s: "Read error."), error: QModbusDevice::ReadError);
280 break;
281 case QSerialPort::ResourceError:
282 q->setError(errorText: QModbusDevice::tr(s: "Resource error."), error: QModbusDevice::ConnectionError);
283 break;
284 case QSerialPort::UnsupportedOperationError:
285 q->setError(errorText: QModbusDevice::tr(s: "Device operation is not supported error."),
286 error: QModbusDevice::ConfigurationError);
287 break;
288 case QSerialPort::TimeoutError:
289 q->setError(errorText: QModbusDevice::tr(s: "Timeout error."), error: QModbusDevice::TimeoutError);
290 break;
291 case QSerialPort::UnknownError:
292 q->setError(errorText: QModbusDevice::tr(s: "Unknown error."), error: QModbusDevice::UnknownError);
293 break;
294 default:
295 qCDebug(QT_MODBUS) << "(RTU server) Unhandled QSerialPort error" << error;
296 break;
297 }
298 });
299
300 QObject::connect(sender: m_serialPort, signal: &QSerialPort::aboutToClose, context: q, slot: [this]() {
301 Q_Q(QModbusRtuSerialServer);
302 // update state if socket closure was caused by remote side
303 if (q->state() != QModbusDevice::ClosingState)
304 q->setState(QModbusDevice::UnconnectedState);
305 });
306 }
307
308 void setupEnvironment()
309 {
310 if (m_serialPort) {
311 m_serialPort->setPortName(m_comPort);
312 m_serialPort->setParity(m_parity);
313 m_serialPort->setBaudRate(baudRate: m_baudRate);
314 m_serialPort->setDataBits(m_dataBits);
315 m_serialPort->setStopBits(m_stopBits);
316 }
317
318 calculateInterFrameDelay();
319
320 m_requestBuffer.clear();
321 }
322
323 QIODevice *device() const override { return m_serialPort; }
324
325 QByteArray m_requestBuffer;
326 bool m_processesBroadcast = false;
327 QSerialPort *m_serialPort = nullptr;
328 QElapsedTimer m_interFrameTimer;
329};
330
331QT_END_NAMESPACE
332
333#endif // QMODBUSRTUSERIALSERVER_P_H
334

source code of qtserialbus/src/serialbus/qmodbusrtuserialserver_p.h