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