xref: /openbmc/phosphor-modbus/tests/modbus_server_tester.cpp (revision cad9ecf69472f03f9ece64eff5d2d94bc51bcf90)
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 
23 ServerTester::ServerTester(sdbusplus::async::context& ctx, int fd) :
24     fd(fd), fdioInstance(ctx, fd), mutex("TestMutex")
25 {}
26 
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 
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 
101 static inline void checkRequestSize(size_t requestSize, size_t expectedSize)
102 {
103     EXPECT_EQ(requestSize, expectedSize) << "Invalid request size";
104 }
105 
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 == testSuccessReadHoldingRegisterOffset ||
126         registerOffset == testSuccessReadHoldingRegisterSegmentedOffset)
127     {
128         checkRequestSize(registerCount, testSuccessReadHoldingRegisterCount);
129 
130         response << request.raw[0] << request.raw[1]
131                  << uint8_t(2 * registerCount)
132                  << uint16_t(testSuccessReadHoldingRegisterResponse[0])
133                  << uint16_t(testSuccessReadHoldingRegisterResponse[1]);
134         response.appendCRC();
135         segmentedResponse =
136             (registerOffset == testSuccessReadHoldingRegisterSegmentedOffset);
137     }
138     else if (registerOffset == testReadHoldingRegisterModelOffset)
139     {
140         checkRequestSize(registerCount, testReadHoldingRegisterModelCount);
141 
142         response << request.raw[0] << request.raw[1]
143                  << uint8_t(2 * testReadHoldingRegisterModelCount);
144         for (size_t i = 0; i < testReadHoldingRegisterModelCount; i++)
145         {
146             response << uint16_t(testReadHoldingRegisterModel[i]);
147         }
148         response.appendCRC();
149     }
150     else if (registerOffset == testFailureReadHoldingRegister)
151     {
152         checkRequestSize(registerCount, testSuccessReadHoldingRegisterCount);
153 
154         response << request.raw[0]
155                  << (uint8_t)readHoldingRegistersErrorFunctionCode
156                  << uint8_t(RTUIntf::ModbusExceptionCode::illegalFunctionCode);
157         response.appendCRC();
158     }
159     else
160     {
161         FAIL() << "Invalid register offset:" << registerOffset;
162     }
163 }
164 
165 } // namespace phosphor::modbus::test
166