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