1 #include "modbus_server_tester.hpp"
2
3 #include "modbus/modbus.hpp"
4 #include "modbus/modbus_commands.hpp"
5 #include "modbus/modbus_exception.hpp"
6
7 #include <phosphor-logging/lg2.hpp>
8 #include <sdbusplus/async.hpp>
9
10 #include <gtest/gtest.h>
11
12 namespace phosphor::modbus::test
13 {
14
15 PHOSPHOR_LOG2_USING;
16
17 namespace RTUIntf = phosphor::modbus::rtu;
18 using namespace std::literals;
19
20 constexpr uint8_t readHoldingRegistersFunctionCode = 0x3;
21 constexpr uint8_t readHoldingRegistersErrorFunctionCode = 0x83;
22
ServerTester(sdbusplus::async::context & ctx,int fd)23 ServerTester::ServerTester(sdbusplus::async::context& ctx, int fd) :
24 fd(fd), fdioInstance(ctx, fd), mutex("TestMutex")
25 {}
26
processRequestsInternal()27 auto ServerTester::processRequestsInternal() -> void
28 {
29 MessageIntf request;
30
31 auto ret = read(fd, request.raw.data(), request.raw.size());
32 // Request message need to be at least 4 bytes long - address(1),
33 // function code(1), ..., CRC(2)
34 if (ret < 4)
35 {
36 error("Invalid Server message size {SIZE}, drop it", "SIZE", ret);
37 return;
38 }
39
40 MessageIntf response;
41 bool segmentedResponse = false;
42 processMessage(request, ret, response, segmentedResponse);
43
44 if (!segmentedResponse)
45 {
46 ret = write(fd, response.raw.data(), response.len);
47 if (ret < 0)
48 {
49 error("Failed to send response {ERROR}", "ERROR", strerror(errno));
50 }
51 return;
52 }
53
54 // Segmented response
55 ret = write(fd, response.raw.data(), response.len - 2);
56 if (ret < 0)
57 {
58 error("Failed to send 1st segment response {ERROR}", "ERROR",
59 strerror(errno));
60 return;
61 }
62
63 debug("First segment sent successfully");
64
65 ret = write(fd, response.raw.data() + response.len - 2, 2);
66 if (ret < 0)
67 {
68 error("Failed to send 2nd segment response {ERROR}", "ERROR",
69 strerror(errno));
70 return;
71 }
72
73 debug("Second segment sent successfully");
74 }
75
processRequests()76 auto ServerTester::processRequests() -> void
77 {
78 fd_set readFds;
79 FD_ZERO(&readFds);
80 FD_SET(fd, &readFds);
81
82 struct timeval timeout;
83 timeout.tv_sec = 0;
84 timeout.tv_usec = 500000;
85
86 int readyFds = select(fd + 1, &readFds, nullptr, nullptr, &timeout);
87
88 if (readyFds == -1)
89 {
90 // Error handling
91 error("Error in select {ERROR}", "ERROR", strerror(errno));
92 }
93 else if (readyFds == 0)
94 {
95 // Timeout occurred, no data to read
96 warning("Timeout occurred, no data to read");
97 }
98 else
99 {
100 if (FD_ISSET(fd, &readFds))
101 {
102 // Data is available to read
103 processRequestsInternal();
104 }
105 }
106 }
107
processMessage(MessageIntf & request,size_t requestSize,MessageIntf & response,bool & segmentedResponse)108 void ServerTester::processMessage(MessageIntf& request, size_t requestSize,
109 MessageIntf& response,
110 bool& segmentedResponse)
111 {
112 EXPECT_EQ(request.address, testDeviceAddress) << "Invalid device address";
113
114 switch (request.functionCode)
115 {
116 case readHoldingRegistersFunctionCode:
117 processReadHoldingRegisters(request, requestSize, response,
118 segmentedResponse);
119 break;
120 default:
121 FAIL() << "Server received unknown request function code "
122 << request.functionCode;
123 break;
124 }
125 }
126
checkRequestSize(size_t requestSize,size_t expectedSize)127 static inline void checkRequestSize(size_t requestSize, size_t expectedSize)
128 {
129 EXPECT_EQ(requestSize, expectedSize) << "Invalid request size";
130 }
131
processReadHoldingRegisters(MessageIntf & request,size_t requestSize,MessageIntf & response,bool & segmentedResponse)132 void ServerTester::processReadHoldingRegisters(
133 MessageIntf& request, size_t requestSize, MessageIntf& response,
134 bool& segmentedResponse)
135 {
136 constexpr size_t expectedRequestSize = 8;
137
138 if (requestSize != expectedRequestSize)
139 {
140 FAIL() << "Invalid readHoldingRegisters request size:" << requestSize
141 << ", drop it";
142 return;
143 }
144
145 // NOTE: This code deliberately avoids using any packing helpers from
146 // message.hpp. This ensures that message APIs are tested as intended on the
147 // client side.
148 uint16_t registerOffset = request.raw[2] << 8 | request.raw[3];
149 uint16_t registerCount = request.raw[4] << 8 | request.raw[5];
150
151 if (registerOffset == testFailureReadHoldingRegister)
152 {
153 response << request.raw[0]
154 << (uint8_t)readHoldingRegistersErrorFunctionCode
155 << uint8_t(RTUIntf::ModbusExceptionCode::illegalFunctionCode);
156 response.appendCRC();
157 }
158 else
159 {
160 auto expectedResponseIter =
161 testReadHoldingRegisterMap.find(registerOffset);
162 if (expectedResponseIter == testReadHoldingRegisterMap.end())
163 {
164 FAIL() << "Invalid register offset:" << registerOffset;
165 return;
166 }
167
168 checkRequestSize(registerCount,
169 std::get<0>(expectedResponseIter->second));
170
171 auto& expectedResponse = std::get<1>(expectedResponseIter->second);
172
173 response << request.raw[0] << request.raw[1]
174 << uint8_t(2 * registerCount);
175 for (size_t i = 0; i < registerCount; i++)
176 {
177 response << uint16_t(expectedResponse[i]);
178 }
179 response.appendCRC();
180
181 segmentedResponse =
182 (registerOffset == testSuccessReadHoldingRegisterSegmentedOffset);
183 }
184 }
185
186 } // namespace phosphor::modbus::test
187