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