Support IPMI Serial Transport

Support IPMI Serial interface (basic mode) based on Sec 14,
IPMI 2.0 spec. Serial transport tested with meta-evb-arm
platform with socat and ipmitool.

HW transport can be selected by meson configure flasg.

Tested:

1. socat -x pty,raw,echo=0,link=/tmp/ttyHOST2BMC tcp:localhost:5066
2. ipmitool -I serial-basic -D /tmp/ttyHOST2BMC:115200 mc info

    Device ID                 : 0
    Device Revision           : 0
    Firmware Revision         : 2.17
    IPMI Version              : 2.0
    Manufacturer ID           : 0
    Manufacturer Name         : Unknown
    Product ID                : 0 (0x0000)
    Product Name              : Unknown (0x0)
    Device Available          : no
    Provides Device SDRs      : no
    Additional Device Support :
    Aux Firmware Rev Info     :
        0x00
        0x00
        0x00
        0x00

Change-Id: I2a56390022fce7a974ed75ed6a72ad3fffed9bb6
Signed-off-by: John Chung <john.chung@arm.com>
diff --git a/transport/serialbridge/test/meson.build b/transport/serialbridge/test/meson.build
new file mode 100644
index 0000000..d1ad4cf
--- /dev/null
+++ b/transport/serialbridge/test/meson.build
@@ -0,0 +1,42 @@
+gtest = dependency('gtest', main: true, disabler: true, required: false)
+gmock = dependency('gmock', disabler: true, required: false)
+if not gtest.found() or not gmock.found()
+    gtest_opts = import('cmake').subproject_options()
+    gtest_opts.add_cmake_defines({'CMAKE_CXX_FLAGS': '-Wno-pedantic'})
+    gtest_proj = import('cmake').subproject(
+        'googletest',
+        options: gtest_opts,
+        required: false
+    )
+    if gtest_proj.found()
+        gtest = declare_dependency(
+            dependencies: [
+                dependency('threads'),
+                gtest_proj.dependency('gtest'),
+                gtest_proj.dependency('gtest_main'),
+            ])
+        gmock = gtest_proj.dependency('gmock')
+    else
+        assert(not get_option('tests').enabled(), 'Googletest is required')
+    endif
+endif
+
+# Build/add serial_unittest to test suite
+test('transport_serial',
+    executable(
+        'transport_serial_unittest',
+        'serial_unittest.cpp',
+        '../serialcmd.cpp',
+        include_directories: root_inc,
+        build_by_default: false,
+        implicit_include_directories: false,
+        dependencies: [
+            sdbusplus_dep,
+            stdplus_dep,
+            phosphor_logging_dep,
+            sdeventplus_dep,
+            gtest,
+            gmock
+        ]
+    )
+)
diff --git a/transport/serialbridge/test/serial_unittest.cpp b/transport/serialbridge/test/serial_unittest.cpp
new file mode 100644
index 0000000..3fb0227
--- /dev/null
+++ b/transport/serialbridge/test/serial_unittest.cpp
@@ -0,0 +1,84 @@
+#include <transport/serialbridge/serialcmd.hpp>
+
+#include <gtest/gtest.h>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+std::unordered_map<uint8_t, uint8_t> testsets = {
+    {bmStart, 0xB0},     /* start */
+    {bmStop, 0xB5},      /* stop */
+    {bmHandshake, 0xB6}, /* packet handshake */
+    {bmEscape, 0xBA},    /* data escape */
+    {0x1B, 0x3B}         /* escape */
+};
+
+TEST(TestSpecialCharact, getUnescapedCharact)
+{
+    uint8_t c;
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    for (const auto& set : testsets)
+    {
+        c = channel->getUnescapedCharacter(set.second);
+        ASSERT_EQ(c, set.first);
+    }
+}
+
+TEST(TestSpecialCharact, processEscapedCharacter)
+{
+    std::vector<uint8_t> buffer;
+    uint8_t unescaped = 0xd0;
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    channel->processEscapedCharacter(buffer, std::vector<uint8_t>{bmStart});
+
+    ASSERT_EQ(buffer.at(0), bmEscape);
+    ASSERT_EQ(buffer.at(1), testsets.at(bmStart));
+
+    buffer.clear();
+    channel->processEscapedCharacter(buffer, std::vector<uint8_t>{unescaped});
+
+    ASSERT_EQ(buffer.at(0), unescaped);
+}
+
+TEST(TestChecksum, calculateChecksum)
+{
+    std::array<uint8_t, 5> dataBytes{0x01, 0x10, 0x60, 0xf0, 0x50};
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    uint8_t checksum =
+        channel->calculateChecksum(std::span<uint8_t>(dataBytes));
+
+    checksum += (~checksum) + 1;
+    ASSERT_EQ(checksum, 0);
+}
+
+TEST(TestIpmiSerialPacket, consumeIpmiSerialPacket)
+{
+    std::vector<uint8_t> dataBytes{bmStart, 0x20, 0x18, 0xc8, 0x81,
+                                   0xc,     0x46, 0x01, 0x2c, bmStop};
+    std::vector<uint8_t> dataBytesSplit1{bmStart, 0x20, 0x18, 0xc8};
+    std::vector<uint8_t> dataBytesSplit2{0x81, 0xc, 0x46, 0x01, 0x2c, bmStop};
+    std::span<uint8_t> input(dataBytes);
+    std::span<uint8_t> input1(dataBytesSplit1);
+    std::span<uint8_t> input2(dataBytesSplit2);
+    std::vector<uint8_t> output;
+
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    auto result = channel->consumeIpmiSerialPacket(input, output);
+
+    ASSERT_EQ(result, true);
+
+    output.clear();
+    result = channel->consumeIpmiSerialPacket(input1, output);
+    ASSERT_EQ(result, false);
+    result = channel->consumeIpmiSerialPacket(input2, output);
+    ASSERT_EQ(result, true);
+}
+
+} // namespace serialbridge