xref: /openbmc/phosphor-modbus/tests/modbus_server_tester.cpp (revision 2fa10f4278efa4a353cbe9a844ca1432df405efd)
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