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/meson.build b/meson.build
index a427bdb..aa6da2a 100644
--- a/meson.build
+++ b/meson.build
@@ -347,6 +347,7 @@
if get_option('tests').allowed()
subdir('test')
+ subdir('transport/serialbridge')
endif
install_subdir(
@@ -355,3 +356,6 @@
strip_directory: false,
exclude_files: '*.cpp',
)
+
+# HW Transport
+subdir('transport')
diff --git a/meson.options b/meson.options
index 6d75e73..5a21171 100644
--- a/meson.options
+++ b/meson.options
@@ -243,3 +243,11 @@
type: 'feature',
description: 'Support open-power specific functions',
)
+
+# HW transport
+option(
+ 'transport-implementation',
+ type: 'combo',
+ choices: ['null', 'serial'],
+ description: 'transport'
+)
diff --git a/transport/meson.build b/transport/meson.build
new file mode 100644
index 0000000..4011220
--- /dev/null
+++ b/transport/meson.build
@@ -0,0 +1,3 @@
+if get_option('transport-implementation') == 'serial'
+ subdir('serialbridge')
+endif
diff --git a/transport/serialbridge/meson.build b/transport/serialbridge/meson.build
new file mode 100644
index 0000000..2964206
--- /dev/null
+++ b/transport/serialbridge/meson.build
@@ -0,0 +1,38 @@
+CLI11_dep = dependency('CLI11')
+
+deps = [
+ dependency('libsystemd'),
+ dependency('systemd'),
+ sdeventplus_dep,
+ stdplus_dep,
+ sdbusplus_dep,
+ phosphor_logging_dep,
+ CLI11_dep
+]
+
+serialbridged = executable(
+ 'serialbridged',
+ 'serialbridged.cpp',
+ 'serialcmd.cpp',
+ dependencies: deps,
+ install: true,
+ install_dir: get_option('libexecdir')
+)
+
+# Configure and install systemd unit files
+systemd = dependency('systemd')
+if systemd.found()
+ conf_data = configuration_data()
+ conf_data.set('BIN', get_option('prefix') / get_option('libexecdir') / serialbridged.name())
+ configure_file(
+ input: 'serialbridge@.service.in',
+ output: 'serialbridge@.service',
+ configuration: conf_data,
+ install: true,
+ install_dir: systemd.get_variable(pkgconfig: 'systemdsystemunitdir')
+ )
+endif
+
+if not get_option('tests').disabled()
+ subdir('test')
+endif
diff --git a/transport/serialbridge/serialbridge@.service.in b/transport/serialbridge/serialbridge@.service.in
new file mode 100644
index 0000000..a7bfd45
--- /dev/null
+++ b/transport/serialbridge/serialbridge@.service.in
@@ -0,0 +1,18 @@
+[Unit]
+Description=Phosphor IPMI Serial DBus Bridge
+StartLimitBurst=3
+StartLimitIntervalSec=300
+After=phosphor-ipmi-host.service
+
+[Service]
+Restart=always
+RestartSec=10
+TimeoutStartSec=60
+TimeoutStopSec=60
+ExecStartPre=/bin/stty -F /dev/"%i" 115200 litout -crtscts -ixon -echo raw
+ExecStart=@BIN@ -d "%i"
+SyslogIdentifier=serialbridged-%i
+
+[Install]
+WantedBy=multi-user.target
+RequiredBy=
diff --git a/transport/serialbridge/serialbridged.cpp b/transport/serialbridge/serialbridged.cpp
new file mode 100644
index 0000000..b022dc6
--- /dev/null
+++ b/transport/serialbridge/serialbridged.cpp
@@ -0,0 +1,85 @@
+#include "serialcmd.hpp"
+
+#include <systemd/sd-daemon.h>
+
+#include <CLI/CLI.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/slot.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/io.hpp>
+#include <sdeventplus/source/signal.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/signal.hpp>
+
+namespace serialbridge
+{
+
+using sdeventplus::source::IO;
+using sdeventplus::source::Signal;
+using stdplus::fd::OpenAccess;
+using stdplus::fd::OpenFlag;
+using stdplus::fd::OpenFlags;
+
+int execute(const std::string& channel, const bool& verbose)
+{
+ // Set up DBus and event loop
+ auto event = sdeventplus::Event::get_default();
+ auto bus = sdbusplus::bus::new_default();
+ bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ // Configure basic signal handling
+ auto exit_handler = [&event](Signal&, const struct signalfd_siginfo*) {
+ lg2::error("Interrupted, Exiting\n");
+ event.exit(0);
+ };
+ stdplus::signal::block(SIGINT);
+ Signal sig_init(event, SIGINT, exit_handler);
+ stdplus::signal::block(SIGTERM);
+ Signal sig_term(event, SIGTERM, exit_handler);
+
+ // Open an FD for the UART channel
+ stdplus::ManagedFd uart = stdplus::fd::open(
+ std::format("/dev/{}", channel.c_str()),
+ OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::NonBlock));
+ sdbusplus::slot_t slot(nullptr);
+
+ std::unique_ptr<SerialChannel> serialchannel =
+ std::make_unique<SerialChannel>(verbose);
+
+ // Add a reader to the bus for handling inbound IPMI
+ IO ioSource(event, uart.get(), EPOLLIN | EPOLLET,
+ stdplus::exception::ignore(
+ [&serialchannel, &uart, &bus, &slot](IO&, int, uint32_t) {
+ serialchannel->read(uart, bus, slot);
+ }));
+
+ sd_notify(0, "READY=1");
+ return event.loop();
+}
+
+} // namespace serialbridge
+
+int main(int argc, char* argv[])
+{
+ std::string device;
+ bool verbose = 0;
+
+ // Parse input parameter
+ CLI::App app("Serial IPMI Bridge");
+ app.add_option("-d,--device", device, "select uart device");
+ app.add_option("-v,--verbose", verbose, "enable debug message");
+ CLI11_PARSE(app, argc, argv);
+
+ try
+ {
+ return serialbridge::execute(device, verbose);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("FAILED: {MSG}\n", "MSG", e);
+ return 1;
+ }
+}
diff --git a/transport/serialbridge/serialcmd.cpp b/transport/serialbridge/serialcmd.cpp
new file mode 100644
index 0000000..ec84f5c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.cpp
@@ -0,0 +1,340 @@
+#include "serialcmd.hpp"
+
+#include <fmt/format.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/ops.hpp>
+
+#include <numeric>
+#include <ranges>
+#include <unordered_map>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+static const std::unordered_map<uint8_t, uint8_t> characters = {
+ {bmStart, 0xB0}, /* start */
+ {bmStop, 0xB5}, /* stop */
+ {bmHandshake, 0xB6}, /* packet handshake */
+ {bmEscape, 0xBA}, /* data escape */
+ {0x1B, 0x3B} /* escape */
+};
+
+/**
+ * @brief Calculate IPMI checksum
+ */
+uint8_t SerialChannel::calculateChecksum(std::span<uint8_t> data)
+{
+ uint8_t checksum;
+
+ checksum = std::accumulate(data.begin(), data.end(), 0);
+ checksum = (~checksum) + 1;
+
+ // return checksum
+ return checksum;
+}
+
+/**
+ * @brief Return unescaped character for the given one
+ */
+uint8_t SerialChannel::getUnescapedCharacter(uint8_t c)
+{
+ auto search =
+ std::find_if(characters.begin(), characters.end(),
+ [c](const auto& map_set) { return map_set.second == c; });
+
+ if (search == characters.end())
+ {
+ return c;
+ }
+
+ return search->first;
+}
+
+/**
+ * @brief Process IPMI Serial Request State Machine
+ */
+int SerialChannel::consumeIpmiSerialPacket(
+ std::span<uint8_t>& escapedDataBytes,
+ std::vector<uint8_t>& unescapedDataBytes)
+{
+ unescapedDataBytes.reserve(escapedDataBytes.size());
+
+ for (auto c : escapedDataBytes)
+ {
+ if (c == bmStart) // START
+ {
+ msgState = MsgState::msgInProgress;
+ }
+ else if (msgState == MsgState::msgIdle)
+ {
+ continue;
+ }
+ else if (msgState == MsgState::msgInEscape)
+ {
+ uint8_t unescapedCharacter;
+ unescapedCharacter = getUnescapedCharacter(c);
+
+ if (unescapedCharacter == c)
+ {
+ // error, then reset
+ msgState = MsgState::msgIdle;
+ unescapedDataBytes.clear();
+ continue;
+ }
+
+ unescapedDataBytes.push_back(unescapedCharacter);
+ msgState = MsgState::msgInProgress;
+ }
+ else if (c == bmEscape)
+ {
+ msgState = MsgState::msgInEscape;
+ continue;
+ }
+ else if (c == bmStop) // STOP
+ {
+ msgState = MsgState::msgIdle;
+ return true;
+ }
+ else if (c == bmHandshake) // Handshake
+ {
+ unescapedDataBytes.clear();
+ continue;
+ }
+ else if (msgState == MsgState::msgInProgress)
+ {
+ unescapedDataBytes.push_back(c);
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @brief Encapsluate response to avoid escape character
+ */
+uint8_t SerialChannel::processEscapedCharacter(std::vector<uint8_t>& buffer,
+ const std::vector<uint8_t>& data)
+{
+ uint8_t checksum = 0;
+
+ std::ranges::for_each(data.begin(), data.end(),
+ [&buffer, &checksum](const auto& c) {
+ auto search = characters.find(c);
+ if (search != characters.end())
+ {
+ buffer.push_back(bmEscape);
+ buffer.push_back(search->second);
+ }
+ else
+ {
+ buffer.push_back(c);
+ }
+
+ checksum += c;
+ });
+
+ return checksum;
+}
+
+/**
+ * @brief Write function
+ */
+int SerialChannel::write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr,
+ uint8_t seq, sdbusplus::message_t&& m)
+{
+ std::span<uint8_t> out;
+ uint8_t checksum;
+
+ try
+ {
+ if (m.is_method_error())
+ {
+ // Extra copy to workaround lack of `const sd_bus_error` constructor
+ auto error = *m.get_error();
+ throw sdbusplus::exception::SdBusError(&error, "ipmid response");
+ }
+
+ uint8_t netFn = 0xff;
+ uint8_t lun = 0xff;
+ uint8_t cmd = 0xff;
+ uint8_t cc = 0xff;
+ std::vector<uint8_t> data;
+
+ m.read(std::tie(netFn, lun, cmd, cc, data));
+
+ uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
+ uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
+
+ std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
+ std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
+
+ // Reserve the buffer size to avoid relloc and copy
+ responseBuffer.clear();
+ responseBuffer.reserve(
+ sizeof(struct IpmiSerialHeader) + 2 * data.size() +
+ 4); // 4 for bmStart & bmStop & 2 checksums
+
+ // bmStart
+ responseBuffer.push_back(bmStart);
+
+ // Assemble connection header and checksum
+ checksum = processEscapedCharacter(responseBuffer, connectionHeader);
+ responseBuffer.push_back(-checksum); // checksum1
+
+ // Assemble response message and checksum
+ checksum = processEscapedCharacter(responseBuffer, messageHeader);
+ checksum +=
+ processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
+ responseBuffer.push_back(-checksum); // checksum2
+
+ // bmStop
+ responseBuffer.push_back(bmStop);
+
+ out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
+
+ if (verbose)
+ {
+ lg2::info(
+ "Write serial request message with len={LEN}, netfn={NETFN}, "
+ "lun={LUN}, cmd={CMD}, seq={SEQ}",
+ "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
+ cmd, "SEQ", seq);
+
+ std::string msgData = "Tx: ";
+ for (auto c : responseBuffer)
+ {
+ msgData += std::format("{:#x} ", c);
+ }
+ lg2::info(msgData.c_str());
+ }
+
+ stdplus::fd::writeExact(uart, out);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("IPMI Response failure: {MSG}", "MSG", e);
+
+ return -1;
+ }
+
+ return out.size();
+}
+
+/**
+ * @brief Read function
+ */
+void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
+ sdbusplus::slot_t& outstanding)
+{
+ std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
+ auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
+
+ if (ipmiSerialPacket.empty())
+ {
+ return;
+ }
+
+ if (outstanding)
+ {
+ lg2::error("Canceling outstanding request \n");
+ outstanding = sdbusplus::slot_t(nullptr);
+ }
+
+ // process ipmi serial packet
+ if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
+ {
+ lg2::info("Wait for more data ... \n");
+ return;
+ }
+
+ // validate ipmi serial packet length
+ if (requestBuffer.size() <
+ (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
+ {
+ lg2::error("Invalid request length, ignoring \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ // validate checksum1
+ if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
+ ipmiSerialConnectionHeaderLength)))
+ {
+ lg2::error("Invalid request checksum 1 \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ // validate checksum2
+ if (calculateChecksum(std::span<uint8_t>(
+ &requestBuffer[ipmiSerialConnectionHeaderLength],
+ requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
+ {
+ lg2::error("Invalid request checksum 2 \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
+ "/xyz/openbmc_project/Ipmi",
+ "xyz.openbmc_project.Ipmi.Server", "execute");
+
+ std::map<std::string, std::variant<int>> options;
+ struct IpmiSerialHeader* header =
+ reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
+
+ uint8_t rsAddr = header->rsAddr;
+ uint8_t netFn = header->rsNetFnLUN >> netFnShift;
+ uint8_t lun = header->rsNetFnLUN & lunMask;
+ uint8_t rqAddr = header->rqAddr;
+ uint8_t seq = header->rqSeqLUN >> netFnShift;
+ uint8_t cmd = header->cmd;
+
+ std::span reqSpan{requestBuffer.begin(),
+ requestBuffer.end() -
+ ipmiSerialChecksumSize}; // remove checksum 2
+ m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
+ options);
+
+ if (verbose)
+ {
+ lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
+ "lun={LUN}, cmd={CMD}, seq={SEQ}",
+ "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
+ "CMD", cmd, "SEQ", seq);
+
+ std::string msgData = "Rx: ";
+ for (auto c : requestBuffer)
+ {
+ msgData += std::format("{:#x} ", c);
+ }
+ lg2::info(msgData.c_str());
+ }
+
+ outstanding = m.call_async(stdplus::exception::ignore(
+ [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
+ _seq{seq}](sdbusplus::message_t&& m) {
+ outstanding = sdbusplus::slot_t(nullptr);
+
+ if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
+ {
+ lg2::error(
+ "Occur an error while attempting to send the response.");
+ }
+ }));
+
+ requestBuffer.clear();
+
+ return;
+}
+
+} // namespace serialbridge
diff --git a/transport/serialbridge/serialcmd.hpp b/transport/serialbridge/serialcmd.hpp
new file mode 100644
index 0000000..985921c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.hpp
@@ -0,0 +1,64 @@
+#pragma once
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/fd/intf.hpp>
+
+namespace serialbridge
+{
+
+static constexpr auto bmStart = 0xA0;
+static constexpr auto bmStop = 0xA5;
+static constexpr auto bmHandshake = 0xA6;
+static constexpr auto bmEscape = 0xAA;
+
+static constexpr auto ipmiSerialConnectionHeaderLength = 3;
+static constexpr auto ipmiSerialChecksumSize = 1;
+static constexpr auto ipmiSerialMaxBufferSize = 256;
+
+/**
+ * @brief IPMI Serial Message Structure
+ */
+struct IpmiSerialHeader
+{
+ uint8_t rsAddr;
+ uint8_t rsNetFnLUN;
+ uint8_t checksum1;
+ uint8_t rqAddr;
+ uint8_t rqSeqLUN;
+ uint8_t cmd;
+} __attribute__((packed));
+
+class SerialChannel
+{
+ public:
+ static constexpr uint8_t netFnShift = 2;
+ static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
+
+ SerialChannel(bool debug) : verbose(debug), msgState(MsgState::msgIdle) {};
+
+ int write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr, uint8_t seq,
+ sdbusplus::message_t&& m);
+ void read(stdplus::Fd& serial, sdbusplus::bus_t& bus,
+ sdbusplus::slot_t& outstanding);
+ uint8_t calculateChecksum(std::span<uint8_t> data);
+ uint8_t getUnescapedCharacter(uint8_t c);
+ int consumeIpmiSerialPacket(std::span<uint8_t>& escapedDataBytes,
+ std::vector<uint8_t>& unescapedDataBytes);
+ uint8_t processEscapedCharacter(std::vector<uint8_t>& buffer,
+ const std::vector<uint8_t>& data);
+
+ private:
+ bool verbose;
+ enum class MsgState
+ {
+ msgIdle = 0,
+ msgInProgress,
+ msgInEscape,
+ };
+ MsgState msgState;
+ std::vector<uint8_t> requestBuffer;
+ std::vector<uint8_t> responseBuffer;
+};
+
+} // namespace serialbridge
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