xref: /openbmc/phosphor-modbus/tests/modbus_server_tester.cpp (revision a32d241b08af236b5ea1d4e18a390bdc561b435b)
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)
25 {}
26 
27 auto ServerTester::processRequests() -> sdbusplus::async::task<void>
28 {
29     MessageIntf request;
30     co_await fdioInstance.next();
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         co_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         co_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         co_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         co_return;
71     }
72 
73     debug("Second segment sent successfully");
74 
75     co_return;
76 }
77 
78 void ServerTester::processMessage(MessageIntf& request, size_t requestSize,
79                                   MessageIntf& response,
80                                   bool& segmentedResponse)
81 {
82     EXPECT_EQ(request.address, testDeviceAddress) << "Invalid device address";
83 
84     switch (request.functionCode)
85     {
86         case readHoldingRegistersFunctionCode:
87             processReadHoldingRegisters(request, requestSize, response,
88                                         segmentedResponse);
89             break;
90         default:
91             FAIL() << "Server received unknown request function code "
92                    << request.functionCode;
93             break;
94     }
95 }
96 
97 void ServerTester::processReadHoldingRegisters(
98     MessageIntf& request, size_t requestSize, MessageIntf& response,
99     bool& segmentedResponse)
100 {
101     constexpr size_t expectedRequestSize = 8;
102 
103     if (requestSize != expectedRequestSize)
104     {
105         FAIL() << "Invalid readHoldingRegisters request size:" << requestSize
106                << ", drop it";
107         return;
108     }
109 
110     // NOTE: This code deliberately avoids using any packing helpers from
111     // message.hpp. This ensures that message APIs are tested as intended on the
112     // client side.
113     uint16_t registerOffset = request.raw[2] << 8 | request.raw[3];
114     uint16_t registerCount = request.raw[4] << 8 | request.raw[5];
115 
116     EXPECT_EQ(registerCount, testSuccessReadHoldingRegisterCount);
117 
118     if (registerOffset == testSuccessReadHoldingRegisterOffset ||
119         registerOffset == testSuccessReadHoldingRegisterSegmentedOffset)
120     {
121         response << request.raw[0] << request.raw[1]
122                  << uint8_t(2 * registerCount)
123                  << uint16_t(testSuccessReadHoldingRegisterResponse[0])
124                  << uint16_t(testSuccessReadHoldingRegisterResponse[1]);
125         response.appendCRC();
126         segmentedResponse =
127             (registerOffset == testSuccessReadHoldingRegisterSegmentedOffset);
128     }
129     else if (registerOffset == testFailureReadHoldingRegister)
130     {
131         response << request.raw[0]
132                  << (uint8_t)readHoldingRegistersErrorFunctionCode
133                  << uint8_t(RTUIntf::ModbusExceptionCode::illegalFunctionCode);
134         response.appendCRC();
135     }
136     else
137     {
138         FAIL() << "Invalid register offset:" << registerOffset;
139     }
140 }
141 
142 } // namespace phosphor::modbus::test
143