Add IPMI interface for CustomAccel service

Change-Id: I28a8976e382b457233ac521e9ab71f75abe029d1
Signed-off-by: Steve Foreman <foremans@google.com>
diff --git a/test/google_accel_oob_unittest.cpp b/test/google_accel_oob_unittest.cpp
new file mode 100644
index 0000000..163b602
--- /dev/null
+++ b/test/google_accel_oob_unittest.cpp
@@ -0,0 +1,239 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "commands.hpp"
+#include "errors.hpp"
+#include "google_accel_oob.hpp"
+#include "handler_mock.hpp"
+
+#include <ipmid/api.h>
+
+#include <gtest/gtest.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+using ::testing::Return;
+
+TEST(GoogleAccelOobTest, DeviceCount_Success)
+{
+    ::testing::StrictMock<HandlerMock> h;
+
+    uint8_t reqBuf[1]; // Could be 0, but zero-length arrays are an extension
+
+    struct Reply
+    {
+        uint32_t count;
+    } __attribute__((packed));
+
+    constexpr uint32_t kTestDeviceCount = 2;
+
+    EXPECT_CALL(h, accelOobDeviceCount()).WillOnce(Return(kTestDeviceCount));
+
+    Resp r = accelOobDeviceCount(reqBuf, &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_OK);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), true);
+    const auto payload_tuple = payload.value();
+    const auto reply_cmd = std::get<0>(payload_tuple);
+    EXPECT_EQ(reply_cmd, SysAccelOobDeviceCount);
+    const auto reply_buff = std::get<1>(payload_tuple);
+    ASSERT_EQ(reply_buff.size(), sizeof(Reply));
+
+    auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+    EXPECT_EQ(reply->count, kTestDeviceCount);
+}
+
+TEST(GoogleAccelOobTest, DeviceName_Success)
+{
+    ::testing::StrictMock<HandlerMock> h;
+
+    struct Request
+    {
+        uint32_t index;
+    } __attribute__((packed));
+
+    struct Reply
+    {
+        uint32_t index;
+        uint8_t length;
+        char name[1];
+    } __attribute__((packed));
+
+    constexpr uint32_t kTestDeviceIndex = 0;
+    const std::string kTestDeviceName("testDeviceName");
+
+    EXPECT_CALL(h, accelOobDeviceName(kTestDeviceIndex))
+        .WillOnce(Return(kTestDeviceName));
+
+    Request reqBuf{kTestDeviceIndex};
+    Resp r = accelOobDeviceName(
+        std::span(reinterpret_cast<const uint8_t*>(&reqBuf), sizeof(Request)),
+        &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_OK);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), true);
+    const auto payload_tuple = payload.value();
+    const auto reply_cmd = std::get<0>(payload_tuple);
+    EXPECT_EQ(reply_cmd, SysAccelOobDeviceName);
+    const auto reply_buff = std::get<1>(payload_tuple);
+    ASSERT_GE(reply_buff.size(), sizeof(Reply));
+
+    auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+    EXPECT_EQ(reply->index, kTestDeviceIndex);
+    EXPECT_EQ(reply->length, kTestDeviceName.length());
+    EXPECT_STREQ(reply->name, kTestDeviceName.c_str());
+}
+
+TEST(GoogleAccelOobTest, Read_Success)
+{
+    ::testing::StrictMock<HandlerMock> h;
+
+    constexpr char kTestDeviceName[] = "testDeviceName";
+    constexpr uint8_t kTestDeviceNameLength =
+        (sizeof(kTestDeviceName) / sizeof(*kTestDeviceName)) - 1;
+    constexpr uint8_t kTestToken = 0xAB;
+    constexpr uint64_t kTestAddress = 0;
+    constexpr uint8_t kTestReadSize = 8;
+    constexpr uint64_t kTestData = 0x12345678;
+
+    struct Request
+    {
+        uint8_t nameLength;
+        char name[kTestDeviceNameLength];
+        uint8_t token;
+        uint64_t address;
+        uint8_t num_bytes;
+    } __attribute__((packed));
+
+    struct Reply
+    {
+        uint8_t nameLength;
+        char name[kTestDeviceNameLength];
+        uint8_t token;
+        uint64_t address;
+        uint8_t num_bytes;
+        uint64_t data;
+    } __attribute__((packed));
+
+    const std::string_view kTestDeviceNameStr(kTestDeviceName,
+                                              kTestDeviceNameLength);
+    EXPECT_CALL(h,
+                accelOobRead(kTestDeviceNameStr, kTestAddress, kTestReadSize))
+        .WillOnce(Return(kTestData));
+
+    Request reqBuf{kTestDeviceNameLength, "", kTestToken, kTestAddress,
+                   kTestReadSize};
+    memcpy(reqBuf.name, kTestDeviceName, kTestDeviceNameLength);
+    Resp r = accelOobRead(
+        std::span(reinterpret_cast<const uint8_t*>(&reqBuf), sizeof(Request)),
+        &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_OK);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), true);
+    const auto payload_tuple = payload.value();
+    const auto reply_cmd = std::get<0>(payload_tuple);
+    EXPECT_EQ(reply_cmd, SysAccelOobRead);
+    const auto reply_buff = std::get<1>(payload_tuple);
+    ASSERT_GE(reply_buff.size(), sizeof(Reply));
+
+    auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+    EXPECT_EQ(reply->nameLength, kTestDeviceNameLength);
+    EXPECT_EQ(std::string_view(reply->name, reply->nameLength),
+              kTestDeviceNameStr);
+    EXPECT_EQ(reply->token, kTestToken);
+    EXPECT_EQ(reply->address, kTestAddress);
+    EXPECT_EQ(reply->num_bytes, kTestReadSize);
+    EXPECT_EQ(reply->data, kTestData);
+}
+
+TEST(GoogleAccelOobTest, Write_Success)
+{
+    ::testing::StrictMock<HandlerMock> h;
+
+    constexpr char kTestDeviceName[] = "testDeviceName";
+    constexpr uint8_t kTestDeviceNameLength =
+        (sizeof(kTestDeviceName) / sizeof(*kTestDeviceName)) - 1;
+    constexpr uint8_t kTestToken = 0xAB;
+    constexpr uint64_t kTestAddress = 0;
+    constexpr uint8_t kTestWriteSize = 8;
+    constexpr uint64_t kTestData = 0x12345678;
+
+    struct Request
+    {
+        uint8_t nameLength;
+        char name[kTestDeviceNameLength];
+        uint8_t token;
+        uint64_t address;
+        uint8_t num_bytes;
+        uint64_t data;
+    } __attribute__((packed));
+
+    struct Reply
+    {
+        uint8_t nameLength;
+        char name[kTestDeviceNameLength];
+        uint8_t token;
+        uint64_t address;
+        uint8_t num_bytes;
+        uint64_t data;
+    } __attribute__((packed));
+
+    const std::string_view kTestDeviceNameStr(kTestDeviceName,
+                                              kTestDeviceNameLength);
+    EXPECT_CALL(h, accelOobWrite(kTestDeviceNameStr, kTestAddress,
+                                 kTestWriteSize, kTestData))
+        .WillOnce(Return());
+
+    Request reqBuf{kTestDeviceNameLength, "",       kTestToken, kTestAddress,
+                   kTestWriteSize,        kTestData};
+    memcpy(reqBuf.name, kTestDeviceName, kTestDeviceNameLength);
+    Resp r = accelOobWrite(
+        std::span(reinterpret_cast<const uint8_t*>(&reqBuf), sizeof(Request)),
+        &h);
+
+    const auto response = std::get<0>(r);
+    EXPECT_EQ(response, IPMI_CC_OK);
+
+    const auto payload = std::get<1>(r);
+    ASSERT_EQ(payload.has_value(), true);
+    const auto payload_tuple = payload.value();
+    const auto reply_cmd = std::get<0>(payload_tuple);
+    EXPECT_EQ(reply_cmd, SysAccelOobWrite);
+    const auto reply_buff = std::get<1>(payload_tuple);
+    ASSERT_GE(reply_buff.size(), sizeof(Reply));
+
+    auto* reply = reinterpret_cast<const Reply*>(reply_buff.data());
+    EXPECT_EQ(reply->nameLength, kTestDeviceNameLength);
+    EXPECT_EQ(std::string_view(reply->name, reply->nameLength),
+              kTestDeviceNameStr);
+    EXPECT_EQ(reply->token, kTestToken);
+    EXPECT_EQ(reply->address, kTestAddress);
+    EXPECT_EQ(reply->num_bytes, kTestWriteSize);
+    EXPECT_EQ(reply->data, kTestData);
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index d393bde..2fb8e2a 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <string>
+#include <string_view>
 #include <tuple>
 
 #include <gmock/gmock.h>
@@ -53,6 +54,14 @@
     MOCK_METHOD((std::tuple<std::uint32_t, std::string>), getI2cEntry,
                 (unsigned int), (const, override));
     MOCK_METHOD(void, hostPowerOffDelay, (std::uint32_t), (const, override));
+
+    MOCK_METHOD(uint32_t, accelOobDeviceCount, (), (const, override));
+    MOCK_METHOD(std::string, accelOobDeviceName, (size_t), (const, override));
+    MOCK_METHOD(uint64_t, accelOobRead, (std::string_view, uint64_t, uint8_t),
+                (const, override));
+    MOCK_METHOD(void, accelOobWrite,
+                (std::string_view, uint64_t, uint8_t, uint64_t),
+                (const, override));
 };
 
 } // namespace ipmi
diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
index 1dda3ce..418ba46 100644
--- a/test/handler_unittest.cpp
+++ b/test/handler_unittest.cpp
@@ -1,4 +1,4 @@
-// Copyright 2021 Google LLC
+// Copyright 2022 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,8 +16,12 @@
 #include "handler.hpp"
 #include "handler_impl.hpp"
 
+#include <systemd/sd-bus.h>
+
 #include <fstream>
 #include <nlohmann/json.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/test/sdbus_mock.hpp>
 #include <string>
 #include <tuple>
 
@@ -106,6 +110,482 @@
     (void)std::remove(testFilename);
 }
 
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::IsNull;
+using ::testing::MatcherCast;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::Return;
+using ::testing::ReturnNull;
+using ::testing::SafeMatcherCast;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+using ::testing::StrNe;
+using ::testing::WithArg;
+
+class MockDbusHandler : public Handler
+{
+  public:
+    MockDbusHandler(sdbusplus::SdBusMock& mock,
+                    const std::string& config = "") :
+        Handler(config),
+        mock_(&mock)
+    {
+    }
+
+  protected:
+    sdbusplus::bus::bus accelOobGetDbus() const override
+    {
+        return sdbusplus::get_mocked_new(
+            const_cast<sdbusplus::SdBusMock*>(mock_));
+    }
+
+  private:
+    sdbusplus::SdBusMock* mock_;
+};
+
+ACTION_TEMPLATE(AssignReadVal, HAS_1_TEMPLATE_PARAMS(typename, T),
+                AND_1_VALUE_PARAMS(val))
+{
+    *static_cast<T*>(arg2) = val;
+}
+
+ACTION_P(TraceDbus, msg)
+{
+    std::fprintf(stderr, "%s\n", msg);
+}
+
+ACTION_P(TraceDbus2, msg)
+{
+    std::fprintf(stderr, "%s(%02x)\n", msg, *static_cast<const uint8_t*>(arg2));
+}
+
+constexpr char object_path[] = "/com/google/customAccel/test/path";
+constexpr char property_grpc[] = "com.google.custom_accel.gRPC";
+constexpr char value_port[] = "Port";
+constexpr uint32_t port = 5000;
+
+constexpr char SD_BUS_TYPE_BYTE_STR[] = {SD_BUS_TYPE_BYTE, '\0'};
+
+// Returns an object that looks like:
+//     "/com/google/customAccel/test/path": {
+//         "com.google.custom_accel.gRPC" : {
+//             "Port" : {
+//                 "type" : "u",
+//                 "data" : 5000
+//             }
+//         }
+//     }
+void ExpectGetManagedObjects(StrictMock<sdbusplus::SdBusMock>& mock,
+                             const char* obj_path = object_path)
+{
+    ::testing::InSequence s;
+
+    // These must be nullptr or sd_bus_message_unref will seg fault.
+    constexpr sd_bus_message* method = nullptr;
+    constexpr sd_bus_message* msg = nullptr;
+
+    EXPECT_CALL(mock, sd_bus_message_new_method_call(
+                          _,         // sd_bus *bus,
+                          NotNull(), // sd_bus_message **m
+                          StrEq("com.google.custom_accel"), StrEq("/"),
+                          StrEq("org.freedesktop.DBus.ObjectManager"),
+                          StrEq("GetManagedObjects")))
+        .WillOnce(DoAll(SetArgPointee<1>(method), Return(0)));
+
+    EXPECT_CALL(mock, sd_bus_call(_,          // sd_bus *bus,
+                                  method,     // sd_bus_message *m
+                                  _,          // uint64_t timeout
+                                  NotNull(),  // sd_bus_error *ret_error
+                                  NotNull())) // sd_bus_message **reply
+        .WillOnce(DoAll(SetArgPointee<3>(SD_BUS_ERROR_NULL),
+                        SetArgPointee<4>(msg), // reply
+                        Return(0)));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+                                                     StrEq("{oa{sa{sv}}}")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(
+                          msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("oa{sa{sv}}")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_read_basic(msg, SD_BUS_TYPE_OBJECT_PATH,
+                                                NotNull()))
+        .WillOnce(DoAll(AssignReadVal<const char*>(obj_path), Return(1)));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+                                                     StrEq("{sa{sv}}")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(
+                          msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("sa{sv}")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock,
+                sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, NotNull()))
+        .WillOnce(DoAll(AssignReadVal<const char*>(property_grpc), Return(1)));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+                                                     StrEq("{sv}")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(
+                          msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("sv")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock,
+                sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, NotNull()))
+        .WillOnce(DoAll(AssignReadVal<const char*>(value_port), Return(1)));
+
+    EXPECT_CALL(
+        mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrNe("u")))
+        .Times(AnyNumber())
+        .WillRepeatedly(Return(0));
+
+    EXPECT_CALL(
+        mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrEq("u")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT,
+                                                     StrEq("u")))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock,
+                sd_bus_message_read_basic(msg, SD_BUS_TYPE_UINT32, NotNull()))
+        .WillOnce(DoAll(AssignReadVal<uint32_t>(port), Return(0)));
+
+    EXPECT_CALL(
+        mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrNe("u")))
+        .Times(AnyNumber())
+        .WillRepeatedly(Return(0));
+
+    EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+        .WillOnce(Return(1))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+        .WillOnce(Return(1))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+        .WillOnce(Return(1))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_message_exit_container(msg)).WillOnce(Return(1));
+}
+
+void ExpectSdBusError(StrictMock<sdbusplus::SdBusMock>& mock)
+{
+    EXPECT_CALL(mock, sd_bus_message_new_method_call(
+                          _,         // sd_bus *bus,
+                          NotNull(), // sd_bus_message **m
+                          StrEq("com.google.custom_accel"), StrEq("/"),
+                          StrEq("org.freedesktop.DBus.ObjectManager"),
+                          StrEq("GetManagedObjects")))
+        .WillOnce(Return(-ENOTCONN));
+}
+
+TEST(HandlerTest, accelOobDeviceCount_Success)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+    ExpectGetManagedObjects(mock);
+    EXPECT_EQ(1, h.accelOobDeviceCount());
+}
+
+TEST(HandlerTest, accelOobDeviceCount_Fail)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+    ExpectSdBusError(mock);
+    EXPECT_THROW(h.accelOobDeviceCount(), IpmiException);
+}
+
+TEST(HandlerTest, accelOobDeviceName_Success)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+    ExpectGetManagedObjects(mock);
+    EXPECT_EQ(std::string("test/path"), h.accelOobDeviceName(0));
+}
+
+TEST(HandlerTest, accelOobDeviceName_Fail)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+    ExpectSdBusError(mock);
+    EXPECT_THROW(h.accelOobDeviceName(0), IpmiException);
+}
+
+TEST(HandlerTest, accelOobDeviceName_OutOfRange)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+    ExpectGetManagedObjects(mock);
+    EXPECT_THROW(h.accelOobDeviceName(1), IpmiException);
+}
+
+TEST(HandlerTest, accelOobDeviceName_InvalidName)
+{
+    constexpr char bad_object_path[] = "/com/google/customAccel2/bad/path";
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+    ExpectGetManagedObjects(mock, bad_object_path);
+    EXPECT_THROW(h.accelOobDeviceName(0), IpmiException);
+}
+
+constexpr uint8_t NUM_BYTES_RETURNED_EQ_NUM_BYTES = 0xff;
+void ExpectRead(StrictMock<sdbusplus::SdBusMock>& mock, uint64_t address,
+                uint8_t num_bytes, uint64_t data, int sd_bus_call_return_value,
+                uint8_t num_bytes_returned = NUM_BYTES_RETURNED_EQ_NUM_BYTES)
+{
+    ::testing::InSequence s;
+
+    // These must be nullptr or sd_bus_message_unref will seg fault.
+    constexpr sd_bus_message* method = nullptr;
+    constexpr sd_bus_message* msg = nullptr;
+
+    EXPECT_CALL(mock, sd_bus_message_new_method_call(
+                          _,         // sd_bus *bus,
+                          NotNull(), // sd_bus_message **m
+                          StrEq("com.google.custom_accel"),
+                          StrEq("/com/google/customAccel/test/path"),
+                          StrEq("com.google.custom_accel.BAR"), StrEq("Read")))
+        .WillOnce(DoAll(SetArgPointee<1>(method), Return(0)));
+
+    EXPECT_CALL(
+        mock, sd_bus_message_append_basic(
+                  method, SD_BUS_TYPE_UINT64,
+                  MatcherCast<const void*>(
+                      SafeMatcherCast<const uint64_t*>(Pointee(Eq(address))))))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock,
+                sd_bus_message_append_basic(
+                    method, SD_BUS_TYPE_UINT64,
+                    MatcherCast<const void*>(SafeMatcherCast<const uint64_t*>(
+                        Pointee(Eq<uint64_t>(num_bytes))))))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(mock, sd_bus_call(_,          // sd_bus *bus,
+                                  method,     // sd_bus_message *m
+                                  _,          // uint64_t timeout
+                                  NotNull(),  // sd_bus_error *ret_error
+                                  NotNull())) // sd_bus_message **reply
+        .WillOnce(DoAll(SetArgPointee<3>(SD_BUS_ERROR_NULL),
+                        SetArgPointee<4>(msg), // reply
+                        Return(sd_bus_call_return_value)));
+
+    if (sd_bus_call_return_value >= 0)
+    {
+        EXPECT_CALL(mock,
+                    sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
+                                                   StrEq(SD_BUS_TYPE_BYTE_STR)))
+            .WillOnce(Return(1));
+
+        if (num_bytes_returned == NUM_BYTES_RETURNED_EQ_NUM_BYTES)
+        {
+            num_bytes_returned = num_bytes;
+        }
+        for (auto i = num_bytes_returned - 1; i >= 0; --i)
+        {
+            EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0))
+                .WillOnce(Return(0));
+
+            const uint8_t byte = (data >> (8 * i)) & 0xff;
+            EXPECT_CALL(mock, sd_bus_message_read_basic(msg, SD_BUS_TYPE_BYTE,
+                                                        NotNull()))
+                .WillOnce(DoAll(AssignReadVal<uint8_t>(byte), Return(1)));
+        }
+
+        EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
+
+        EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
+            .WillOnce(Return(1));
+    }
+}
+
+TEST(HandlerTest, accelOobRead_Success)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t);
+    constexpr int sd_bus_call_return_value = 1;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+
+    ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value);
+    EXPECT_EQ(data, h.accelOobRead("test/path", address, num_bytes));
+}
+
+TEST(HandlerTest, accelOobRead_Fail)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t);
+    constexpr int sd_bus_call_return_value = -ENOTCONN;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+
+    ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value);
+    EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
+                 IpmiException);
+}
+
+TEST(HandlerTest, accelOobRead_TooFewBytesReturned)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t);
+    constexpr int sd_bus_call_return_value = 1;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+    constexpr uint8_t num_bytes_returned = num_bytes - 1;
+
+    ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value,
+               num_bytes_returned);
+    EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
+                 IpmiException);
+}
+
+TEST(HandlerTest, accelOobRead_TooManyBytesReturned)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t);
+    constexpr int sd_bus_call_return_value = 1;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+    constexpr uint8_t num_bytes_returned = sizeof(uint64_t) + 1;
+
+    ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value,
+               num_bytes_returned);
+    EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
+                 IpmiException);
+}
+
+void ExpectWrite(StrictMock<sdbusplus::SdBusMock>& mock, uint64_t address,
+                 uint8_t num_bytes, uint64_t data, int sd_bus_call_return_value)
+{
+    ::testing::InSequence s;
+
+    // These must be nullptr or sd_bus_message_unref will seg fault.
+    constexpr sd_bus_message* method = nullptr;
+
+    EXPECT_CALL(mock, sd_bus_message_new_method_call(
+                          _,         // sd_bus *bus,
+                          NotNull(), // sd_bus_message **m
+                          StrEq("com.google.custom_accel"),
+                          StrEq("/com/google/customAccel/test/path"),
+                          StrEq("com.google.custom_accel.BAR"), StrEq("Write")))
+        .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
+                        SetArgPointee<1>(method), Return(0)));
+
+    EXPECT_CALL(
+        mock, sd_bus_message_append_basic(
+                  method, SD_BUS_TYPE_UINT64,
+                  MatcherCast<const void*>(
+                      SafeMatcherCast<const uint64_t*>(Pointee(Eq(address))))))
+        .WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic(address) -> 1"),
+                        Return(1)));
+
+    EXPECT_CALL(mock,
+                sd_bus_message_open_container(method, SD_BUS_TYPE_ARRAY,
+                                              StrEq(SD_BUS_TYPE_BYTE_STR)))
+        .WillOnce(DoAll(TraceDbus("sd_bus_message_open_container(a, y) -> 0"),
+                        Return(0)));
+
+    for (auto i = 0; i < num_bytes; ++i)
+    {
+        const uint8_t byte = (data >> (8 * i)) & 0xff;
+
+        EXPECT_CALL(
+            mock, sd_bus_message_append_basic(
+                      method, SD_BUS_TYPE_BYTE,
+                      MatcherCast<const void*>(
+                          SafeMatcherCast<const uint8_t*>(Pointee(Eq(byte))))))
+            .WillOnce(
+                DoAll(TraceDbus2("sd_bus_message_append_basic"), Return(1)));
+    }
+
+    EXPECT_CALL(mock, sd_bus_message_close_container(method))
+        .WillOnce(DoAll(TraceDbus("sd_bus_message_close_container() -> 0"),
+                        Return(0)));
+
+    EXPECT_CALL(mock, sd_bus_call(_,         // sd_bus *bus,
+                                  method,    // sd_bus_message *m
+                                  _,         // uint64_t timeout
+                                  NotNull(), // sd_bus_error *ret_error
+                                  IsNull())) // sd_bus_message **reply
+        .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
+                        SetArgPointee<3>(SD_BUS_ERROR_NULL),
+                        Return(sd_bus_call_return_value)));
+}
+
+TEST(HandlerTest, accelOobWrite_Success)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t);
+    constexpr int sd_bus_call_return_value = 1;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+
+    ExpectWrite(mock, address, num_bytes, data, sd_bus_call_return_value);
+    EXPECT_NO_THROW(h.accelOobWrite("test/path", address, num_bytes, data));
+}
+
+TEST(HandlerTest, accelOobRead_TooManyBytesRequested)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t) + 1;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+
+    EXPECT_THROW(h.accelOobWrite("test/path", address, num_bytes, data),
+                 IpmiException);
+}
+
+TEST(HandlerTest, accelOobWrite_Fail)
+{
+    StrictMock<sdbusplus::SdBusMock> mock;
+    MockDbusHandler h(mock);
+
+    constexpr uint64_t address = 0x123456789abcdef;
+    constexpr uint8_t num_bytes = sizeof(uint64_t);
+    constexpr int sd_bus_call_return_value = -ENOTCONN;
+    constexpr uint64_t data = 0x13579bdf02468ace;
+
+    ExpectWrite(mock, address, num_bytes, data, sd_bus_call_return_value);
+    EXPECT_THROW(h.accelOobWrite("test/path", address, num_bytes, data),
+                 IpmiException);
+}
+
 // TODO: Add checks for other functions of handler.
 
 } // namespace ipmi
diff --git a/test/meson.build b/test/meson.build
index a9da8b9..9c579bd 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -21,6 +21,7 @@
   'entity',
   'eth',
   'flash',
+  'google_accel_oob',
   'handler',
   'machine',
   'pcie',