modbus_rtu_lib: define read holding register

Add the modbus rtu library commands with initial support for read
holding register. Define a base Message classes which all subsequent and
specific request & response messages can inherit from. Also add the
relevant unit testing for the added command set.

Tested:
```
> meson test -C builddir
ninja: Entering directory `/host/repos/Modbus/phosphor-modbus/builddir'
ninja: no work to do.
1/1 test_modbus_commands        OK              0.01s

Ok:                1
Fail:              0

```

Change-Id: I331b0dee66a0829e9352ae0eac8ac82a9150904c
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/rtu/modbus/modbus_exception.hpp b/rtu/modbus/modbus_exception.hpp
new file mode 100644
index 0000000..9498a1c
--- /dev/null
+++ b/rtu/modbus/modbus_exception.hpp
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <format>
+#include <stdexcept>
+#include <string>
+
+namespace phosphor::modbus::rtu
+{
+
+enum class ModbusExceptionCode
+{
+    // The Modbus function code in the request is not supported or is an invalid
+    // action for the server.
+    illegalFunctionCode = 1,
+
+    // The requested data address is not valid or authorized for the server.
+    illegalDataAddress = 2,
+
+    // The value provided in the request is not an allowable value for the
+    // server, or the data field is structured incorrectly.
+    illegalDataValue = 3,
+
+    // The server encountered an internal error and cannot perform the requested
+    // operation.
+    slaveDeviceFailure = 4,
+
+    //  The server has accepted the request but needs more time to process it.
+    acknowledge = 5,
+
+    // The server is currently busy and cannot respond to the request.
+    slaveDeviceBusy = 6,
+
+    // The server cannot perform the program function received in the query.
+    // This code is returned for an unsuccessful programming request using
+    // function code 13 or 14 decimal. The client should request diagnostic or
+    // error information from the server.
+    negativeAcknowledge = 7,
+
+    // The server attempted to read extended memory, but detected a parity error
+    // in the memory. The client can retry the request, but service may be
+    // required on the server device.
+    memoryParityError = 8,
+    unknownError = 255,
+};
+
+class ModbusException : public std::runtime_error
+{
+  public:
+    const ModbusExceptionCode code;
+
+    explicit ModbusException(uint8_t code, const std::string& message = "") :
+        std::runtime_error(std::format(
+            "{} ({})", toString(static_cast<ModbusExceptionCode>(code)),
+            message)),
+        code(static_cast<ModbusExceptionCode>(code))
+    {}
+
+    static auto toString(ModbusExceptionCode code) -> std::string
+    {
+        switch (code)
+        {
+            case ModbusExceptionCode::illegalFunctionCode:
+                return "Illegal Function Code";
+            case ModbusExceptionCode::illegalDataAddress:
+                return "Illegal Data Address";
+            case ModbusExceptionCode::illegalDataValue:
+                return "Illegal Data Value";
+            case ModbusExceptionCode::slaveDeviceFailure:
+                return "Slave Device Failure";
+            case ModbusExceptionCode::acknowledge:
+                return "Acknowledge";
+            case ModbusExceptionCode::slaveDeviceBusy:
+                return "Slave Device Busy";
+            case ModbusExceptionCode::negativeAcknowledge:
+                return "Negative Acknowledge";
+            case ModbusExceptionCode::memoryParityError:
+                return "Memory Parity Error";
+            default:
+                return "Unknown Modbus Error";
+        }
+    }
+};
+
+class ModbusCRCException : public std::runtime_error
+{
+  public:
+    explicit ModbusCRCException(uint16_t expectedCRC, uint16_t crc) :
+        std::runtime_error(
+            "CRC mismatch, expected: " + std::to_string(expectedCRC) +
+            " received: " + std::to_string(crc))
+    {}
+};
+
+class ModbusBadResponseException : public std::runtime_error
+{
+  public:
+    explicit ModbusBadResponseException(const std::string& fieldName,
+                                        uint32_t expectedValue,
+                                        uint32_t currentValue) :
+        std::runtime_error(
+            "Value mismatch for " + fieldName +
+            ", expected value: " + std::to_string(expectedValue) +
+            ", current value: " + std::to_string(currentValue))
+    {}
+};
+
+} // namespace phosphor::modbus::rtu