Add pattern write and verify to erase

The goals are to write a non-compressible and verifiable pattern to the
drive as a means to validate that the drive is working, and ensure all
blocks have been overwritten.

Tested:
$ systemctl stop emmc.service
$ ./eStoraged  -b /dev/mmcblk0&
$busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.LogicalOverWrite --timeout=1200

$busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.LogicalVerify --timeout=1200

$echo "jebr" > /dev/mmcblk0

$busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.LogicalVerify --timeout=1200
Call failed: The operation failed internally.

Change-Id: Ibc1254279b1f46246eb37056ea6e4e1a57159bb9
Signed-off-by: John Edward Broadbent <jebr@google.com>
diff --git a/include/erase.hpp b/include/erase.hpp
index 699d430..6d5644c 100644
--- a/include/erase.hpp
+++ b/include/erase.hpp
@@ -1,3 +1,4 @@
+#pragma once
 #include <string>
 
 /** @class Erase
@@ -11,6 +12,7 @@
      */
     Erase(std::string_view inDevPath) : devPath(inDevPath)
     {}
+    virtual ~Erase() = default;
 
     /** @brief finds the size of the linux block device in bytes
      *  @return size of a block device using the devPath
diff --git a/include/pattern.hpp b/include/pattern.hpp
new file mode 100644
index 0000000..8c5b7d1
--- /dev/null
+++ b/include/pattern.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "erase.hpp"
+
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+
+#include <span>
+#include <string>
+
+using stdplus::fd::ManagedFd;
+
+class Pattern : public Erase
+{
+  public:
+    /** @brief Creates a pattern erase object.
+     *
+     *  @param[in] inDevPath - the linux device path for the block device.
+     */
+    Pattern(std::string_view inDevPath) : Erase(inDevPath)
+    {}
+
+    /** @brief writes an uncompressible random pattern to the drive
+     * and throws errors accordingly.
+     *
+     *  @param[in] bytes - Size of the block device
+     *  @param[in] managedFd - the file descriptor for the open drive
+     */
+    void writePattern(uint64_t driveSize, ManagedFd& fd);
+
+    /** @brief verifies the uncompressible random pattern is on the drive
+     * and throws errors accordingly.
+     *
+     *  @param[in] bytes - Size of the block device
+     *  @param[in] managedFd - the file descriptor for the open drive
+     */
+    void verifyPattern(uint64_t driveSize, ManagedFd& fd);
+};
diff --git a/src/erase/meson.build b/src/erase/meson.build
index 6aaf3a3..7dbd727 100644
--- a/src/erase/meson.build
+++ b/src/erase/meson.build
@@ -1,6 +1,7 @@
 libeStoragedErase_lib = static_library(
   'libeStoragedErase-lib',
   'verifyDriveGeometry.cpp',
+  'pattern.cpp',
   'erase.cpp',
   include_directories : eStoraged_headers,
   implicit_include_directories: false,
diff --git a/src/erase/pattern.cpp b/src/erase/pattern.cpp
new file mode 100644
index 0000000..649bd37
--- /dev/null
+++ b/src/erase/pattern.cpp
@@ -0,0 +1,99 @@
+#include "pattern.hpp"
+
+#include "erase.hpp"
+
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <array>
+#include <iostream>
+#include <random>
+#include <span>
+#include <string>
+
+constexpr uint32_t seed = 0x6a656272;
+constexpr size_t blockSize = 4096;
+constexpr size_t blockSizeUsing32 = blockSize / sizeof(uint32_t);
+
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using stdplus::fd::ManagedFd;
+
+void Pattern::writePattern(const uint64_t driveSize, ManagedFd& fd)
+{
+    // static seed defines a fixed prng sequnce so it can be verified later,
+    // and validated for entropy
+    uint64_t currentIndex = 0;
+    std::minstd_rand0 generator(seed);
+    std::array<std::byte, blockSize> randArr;
+    while (currentIndex < driveSize)
+    {
+        // generate a 4k block of prng
+        std::array<uint32_t, blockSizeUsing32>* randArrFill =
+            reinterpret_cast<std::array<uint32_t, blockSizeUsing32>*>(&randArr);
+        for (uint32_t i = 0; i < blockSizeUsing32; i++)
+        {
+            (*randArrFill)[i] = generator();
+        }
+        // if we can write all 4k bytes do that, else write the remainder
+        size_t writeSize = currentIndex + blockSize < driveSize
+                               ? blockSize
+                               : driveSize - currentIndex;
+        if (fd.write({randArr.data(), writeSize}).size() != writeSize)
+        {
+            lg2::error("Estoraged erase pattern unable to write sizeof(long)",
+                       "REDFISH_MESSAGE_ID",
+                       std::string("eStorageD.1.0.EraseFailure"),
+                       "REDFISH_MESSAGE_ARGS", std::to_string(fd.get()));
+            throw InternalFailure();
+        }
+        currentIndex = currentIndex + writeSize;
+    }
+}
+
+void Pattern::verifyPattern(const uint64_t driveSize, ManagedFd& fd)
+{
+
+    uint64_t currentIndex = 0;
+    std::minstd_rand0 generator(seed);
+    std::array<std::byte, blockSize> randArr;
+    std::array<std::byte, blockSize> readArr;
+    while (currentIndex < driveSize)
+    {
+        size_t readSize = currentIndex + blockSize < driveSize
+                              ? blockSize
+                              : driveSize - currentIndex;
+        try
+        {
+            std::array<uint32_t, blockSizeUsing32>* randArrFill =
+                reinterpret_cast<std::array<uint32_t, blockSizeUsing32>*>(
+                    &randArr);
+            for (uint32_t i = 0; i < blockSizeUsing32; i++)
+            {
+                (*randArrFill)[i] = generator();
+            }
+            fd.read({readArr.data(), readSize});
+        }
+        catch (...)
+        {
+            lg2::error("Estoraged erase pattern unable to read",
+                       "REDFISH_MESSAGE_ID",
+                       std::string("eStorageD.1.0.EraseFailure"),
+                       "REDFISH_MESSAGE_ARGS", std::to_string(fd.get()));
+            throw InternalFailure();
+        }
+
+        if (!std::equal(randArr.begin(), randArr.begin() + readSize,
+                        readArr.begin(), readArr.begin() + readSize))
+        {
+            lg2::error("Estoraged erase pattern does not match",
+                       "REDFISH_MESSAGE_ID",
+                       std::string("eStorageD.1.0.EraseFailure"));
+            throw InternalFailure();
+        }
+        currentIndex = currentIndex + readSize;
+    }
+}
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 91a7f12..777232a 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -2,6 +2,7 @@
 #include "estoraged.hpp"
 
 #include "cryptsetupInterface.hpp"
+#include "pattern.hpp"
 #include "verifyDriveGeometry.hpp"
 
 #include <libcryptsetup.h>
@@ -70,10 +71,20 @@
         }
         case EraseMethod::LogicalOverWrite:
         {
+            Pattern myErasePattern(devPath);
+            ManagedFd drivefd =
+                stdplus::fd::open(devPath, stdplus::fd::OpenAccess::WriteOnly);
+            myErasePattern.writePattern(myErasePattern.findSizeOfBlockDevice(),
+                                        drivefd);
             break;
         }
         case EraseMethod::LogicalVerify:
         {
+            Pattern myErasePattern(devPath);
+            ManagedFd drivefd =
+                stdplus::fd::open(devPath, stdplus::fd::OpenAccess::ReadOnly);
+            myErasePattern.verifyPattern(myErasePattern.findSizeOfBlockDevice(),
+                                         drivefd);
             break;
         }
         case EraseMethod::VendorSanitize:
diff --git a/src/test/erase/pattern_test.cpp b/src/test/erase/pattern_test.cpp
new file mode 100644
index 0000000..55e83a5
--- /dev/null
+++ b/src/test/erase/pattern_test.cpp
@@ -0,0 +1,81 @@
+#include "estoraged_conf.hpp"
+#include "pattern.hpp"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <system_error>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using stdplus::fd::ManagedFd;
+
+class MockManagedFd : public ManagedFd
+{
+  public:
+    MockManagedFd(int fd) : ManagedFd(std::move(fd))
+    {}
+    ~MockManagedFd()
+    {}
+};
+
+TEST(pattern, patternPass)
+{
+    uint64_t size = 4096;
+    int fds[2];
+    Pattern pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    MockManagedFd writeFd(fds[1]);
+    EXPECT_NO_THROW(pass.writePattern(size, writeFd));
+    MockManagedFd verifyFd(fds[0]);
+    EXPECT_NO_THROW(pass.verifyPattern(size, verifyFd));
+}
+
+/* This test that pattern writes the correct number of bytes even if
+ * size of the drive is not divisable by the block size
+ */
+TEST(pattern, patternPassNotDivisible)
+{
+    uint64_t size = 4097;
+    // 4097 is not divisible by the block size, and we expect no errors
+    int fds[2];
+    Pattern pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    MockManagedFd writeFd(fds[1]);
+    EXPECT_NO_THROW(pass.writePattern(size, writeFd));
+    MockManagedFd verifyFd(fds[0]);
+    EXPECT_NO_THROW(pass.verifyPattern(size, verifyFd));
+}
+
+TEST(pattern, patternsDontMatch)
+{
+    uint64_t size = 4096;
+    int fds[2];
+    Pattern pass("fileName");
+    if (pipe(fds))
+    {
+        FAIL();
+    }
+    int dummyValue = 88;
+    if (::write(fds[1], &dummyValue, sizeof(dummyValue)) != sizeof(dummyValue))
+    {
+        FAIL();
+    }
+    MockManagedFd writeFd(fds[1]);
+    pass.writePattern(size - sizeof(dummyValue), writeFd);
+    MockManagedFd verifyFd(fds[0]);
+    EXPECT_THROW(pass.verifyPattern(size, verifyFd), InternalFailure);
+}
diff --git a/src/test/meson.build b/src/test/meson.build
index 8f05182..0373840 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -3,6 +3,7 @@
 
 tests = [
   'erase/verifyGeometry_test',
+  'erase/pattern_test',
   'estoraged_test',
 ]