xref: /openbmc/phosphor-modbus/tests/modbus_server_tester.cpp (revision e92aba4516471f5a01d4ab1f93eb9919ec05c21f)
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 
processRequests()27 auto ServerTester::processRequests() -> sdbusplus::async::task<void>
28 {
29     // Acquire lock to guard against concurrent access to fdioInstance
30     sdbusplus::async::lock_guard lg{mutex};
31     co_await lg.lock();
32 
33     MessageIntf request;
34     co_await fdioInstance.next();
35     auto ret = read(fd, request.raw.data(), request.raw.size());
36     // Request message need to be at least 4 bytes long - address(1),
37     // function code(1), ..., CRC(2)
38     if (ret < 4)
39     {
40         error("Invalid Server message size {SIZE}, drop it", "SIZE", ret);
41         co_return;
42     }
43 
44     MessageIntf response;
45     bool segmentedResponse = false;
46     processMessage(request, ret, response, segmentedResponse);
47 
48     if (!segmentedResponse)
49     {
50         ret = write(fd, response.raw.data(), response.len);
51         if (ret < 0)
52         {
53             error("Failed to send response {ERROR}", "ERROR", strerror(errno));
54         }
55         co_return;
56     }
57 
58     // Segmented response
59     ret = write(fd, response.raw.data(), response.len - 2);
60     if (ret < 0)
61     {
62         error("Failed to send 1st segment response {ERROR}", "ERROR",
63               strerror(errno));
64         co_return;
65     }
66 
67     debug("First segment sent successfully");
68 
69     ret = write(fd, response.raw.data() + response.len - 2, 2);
70     if (ret < 0)
71     {
72         error("Failed to send 2nd segment response {ERROR}", "ERROR",
73               strerror(errno));
74         co_return;
75     }
76 
77     debug("Second segment sent successfully");
78 
79     co_return;
80 }
81 
processMessage(MessageIntf & request,size_t requestSize,MessageIntf & response,bool & segmentedResponse)82 void ServerTester::processMessage(MessageIntf& request, size_t requestSize,
83                                   MessageIntf& response,
84                                   bool& segmentedResponse)
85 {
86     EXPECT_EQ(request.address, testDeviceAddress) << "Invalid device address";
87 
88     switch (request.functionCode)
89     {
90         case readHoldingRegistersFunctionCode:
91             processReadHoldingRegisters(request, requestSize, response,
92                                         segmentedResponse);
93             break;
94         default:
95             FAIL() << "Server received unknown request function code "
96                    << request.functionCode;
97             break;
98     }
99 }
100 
checkRequestSize(size_t requestSize,size_t expectedSize)101 static inline void checkRequestSize(size_t requestSize, size_t expectedSize)
102 {
103     EXPECT_EQ(requestSize, expectedSize) << "Invalid request size";
104 }
105 
processReadHoldingRegisters(MessageIntf & request,size_t requestSize,MessageIntf & response,bool & segmentedResponse)106 void ServerTester::processReadHoldingRegisters(
107     MessageIntf& request, size_t requestSize, MessageIntf& response,
108     bool& segmentedResponse)
109 {
110     constexpr size_t expectedRequestSize = 8;
111 
112     if (requestSize != expectedRequestSize)
113     {
114         FAIL() << "Invalid readHoldingRegisters request size:" << requestSize
115                << ", drop it";
116         return;
117     }
118 
119     // NOTE: This code deliberately avoids using any packing helpers from
120     // message.hpp. This ensures that message APIs are tested as intended on the
121     // client side.
122     uint16_t registerOffset = request.raw[2] << 8 | request.raw[3];
123     uint16_t registerCount = request.raw[4] << 8 | request.raw[5];
124 
125     if (registerOffset == testFailureReadHoldingRegister)
126     {
127         response << request.raw[0]
128                  << (uint8_t)readHoldingRegistersErrorFunctionCode
129                  << uint8_t(RTUIntf::ModbusExceptionCode::illegalFunctionCode);
130         response.appendCRC();
131     }
132     else
133     {
134         auto expectedResponseIter =
135             testReadHoldingRegisterMap.find(registerOffset);
136         if (expectedResponseIter == testReadHoldingRegisterMap.end())
137         {
138             FAIL() << "Invalid register offset:" << registerOffset;
139             return;
140         }
141 
142         checkRequestSize(registerCount,
143                          std::get<0>(expectedResponseIter->second));
144 
145         auto& expectedResponse = std::get<1>(expectedResponseIter->second);
146 
147         response << request.raw[0] << request.raw[1]
148                  << uint8_t(2 * registerCount);
149         for (size_t i = 0; i < registerCount; i++)
150         {
151             response << uint16_t(expectedResponse[i]);
152         }
153         response.appendCRC();
154 
155         segmentedResponse =
156             (registerOffset == testSuccessReadHoldingRegisterSegmentedOffset);
157     }
158 }
159 
160 } // namespace phosphor::modbus::test
161