Generic binary file stream support (big-endian)

Change-Id: I27bd490103fd193e35b0bbda17f7a1c90b566ecc
Signed-off-by: Zane Shelley <zshelle@us.ibm.com>
diff --git a/test/bin_stream_test.cpp b/test/bin_stream_test.cpp
new file mode 100644
index 0000000..209c778
--- /dev/null
+++ b/test/bin_stream_test.cpp
@@ -0,0 +1,71 @@
+#include <util/bin_stream.hpp>
+
+#include "gtest/gtest.h"
+
+enum RegisterId_t : uint32_t; // Defined in hei_types.hpp
+
+namespace util
+{
+
+/** @brief Extracts big-endian data to host RegisterId_t. */
+template <>
+inline BinFileReader& BinFileReader::operator>>(RegisterId_t& r)
+{
+    // A register ID is only 3 bytes, but there isn't a 3-byte integer type.
+    // So extract 3 bytes to a uint32_t and drop the unused byte.
+    uint32_t tmp = 0;
+    read(&tmp, 3);
+    r = static_cast<RegisterId_t>(be32toh(tmp) >> 8);
+    return *this;
+}
+
+/** @brief Inserts host RegisterId_t to big-endian data. */
+template <>
+inline BinFileWriter& BinFileWriter::operator<<(RegisterId_t r)
+{
+    // A register ID is only 3 bytes, but there isn't a 3-byte integer type.
+    // So extract 3 bytes to a uint32_t and drop the unused byte.
+    uint32_t tmp = htobe32(static_cast<uint32_t>(r) << 8);
+    write(&tmp, 3);
+    return *this;
+}
+
+} // namespace util
+
+TEST(BinStream, TestSet1)
+{
+    uint8_t w1      = 0x11;
+    uint16_t w2     = 0x1122;
+    uint32_t w3     = 0x11223344;
+    uint64_t w4     = 0x1122334455667788;
+    RegisterId_t w5 = static_cast<RegisterId_t>(0x123456);
+
+    {
+        // need scope so the writer is closed before the reader opens
+        util::BinFileWriter w{"bin_stream_test.bin"};
+        ASSERT_TRUE(w.good());
+
+        w << w1 << w2 << w3 << w4 << w5;
+        ASSERT_TRUE(w.good());
+    }
+
+    uint8_t r1;
+    uint16_t r2;
+    uint32_t r3;
+    uint64_t r4;
+    RegisterId_t r5;
+
+    {
+        util::BinFileReader r{"bin_stream_test.bin"};
+        ASSERT_TRUE(r.good());
+
+        r >> r1 >> r2 >> r3 >> r4 >> r5;
+        ASSERT_TRUE(r.good());
+    }
+
+    ASSERT_EQ(w1, r1);
+    ASSERT_EQ(w2, r2);
+    ASSERT_EQ(w3, r3);
+    ASSERT_EQ(w4, r4);
+    ASSERT_EQ(w5, r5);
+}
diff --git a/test/meson.build b/test/meson.build
index bf062ad..764ab9a 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,6 +7,7 @@
 
 tests = [
   'hello-world',
+  'bin_stream_test',
 ]
 
 gtest = dependency('gtest', main : true, required : false, method : 'system')
diff --git a/util/bin_stream.hpp b/util/bin_stream.hpp
new file mode 100644
index 0000000..bdf10e5
--- /dev/null
+++ b/util/bin_stream.hpp
@@ -0,0 +1,201 @@
+#pragma once
+
+#include <endian.h>
+#include <string.h>
+
+#include <fstream>
+
+namespace util
+{
+
+/**
+ * @brief A streaming utility to read a binary file.
+ * @note  IMPORTANT: Assumes file data is in big-endian format.
+ */
+class BinFileReader
+{
+  public:
+    /**
+     * @brief Constructor.
+     * @param f The name of the target file.
+     */
+    explicit BinFileReader(const char* f) : iv_stream(f, std::ios::binary) {}
+
+    /** @brief Destructor. */
+    ~BinFileReader() = default;
+
+    /** @brief Copy constructor. */
+    BinFileReader(const BinFileReader&) = delete;
+
+    /** @brief Assignment operator. */
+    BinFileReader& operator=(const BinFileReader&) = delete;
+
+  private:
+    /** The input file stream. */
+    std::ifstream iv_stream;
+
+  public:
+    /** @return True, if the state of the stream is good. */
+    bool good()
+    {
+        return iv_stream.good();
+    }
+
+    /**
+     * @brief Extracts n characters from the stream and stores them in the array
+     *        pointed to by s.
+     * @note  This function simply copies a block of data without checking its
+     *        contents or endianness.
+     * @note  After calling, check good() to determine if the operation was
+     *        successful.
+     * @param s Pointer to an array of at least n characters.
+     * @param n Number of characters to extract.
+     */
+    void read(void* s, size_t n)
+    {
+        iv_stream.read(static_cast<char*>(s), n);
+    }
+
+    /**
+     * @brief Input stream operator.
+     * @note  The default template is intentionally not defined so that only
+     *        specializations of this function can be used. This avoids
+     *        accidental usage on objects where endianness is a concern.
+     * @note  This is written as a template so that users can define their own
+     *        specializations for non-standard types.
+     */
+    template <class D>
+    BinFileReader& operator>>(D& r);
+};
+
+/** @brief Extracts big-endian data to host uint8_t. */
+template <>
+inline BinFileReader& BinFileReader::operator>>(uint8_t& r)
+{
+    read(&r, sizeof(r));
+    return *this;
+}
+
+/** @brief Extracts big-endian data to host uint16_t. */
+template <>
+inline BinFileReader& BinFileReader::operator>>(uint16_t& r)
+{
+    read(&r, sizeof(r));
+    r = be16toh(r);
+    return *this;
+}
+
+/** @brief Extracts big-endian data to host uint32_t. */
+template <>
+inline BinFileReader& BinFileReader::operator>>(uint32_t& r)
+{
+    read(&r, sizeof(r));
+    r = be32toh(r);
+    return *this;
+}
+
+/** @brief Extracts big-endian data to host uint64_t. */
+template <>
+inline BinFileReader& BinFileReader::operator>>(uint64_t& r)
+{
+    read(&r, sizeof(r));
+    r = be64toh(r);
+    return *this;
+}
+
+/**
+ * @brief A streaming utility to write a binary file.
+ * @note  IMPORTANT: Assumes file data is in big-endian format.
+ */
+class BinFileWriter
+{
+  public:
+    /**
+     * @brief Constructor.
+     * @param f The name of the target file.
+     */
+    explicit BinFileWriter(const char* f) : iv_stream(f, std::ios::binary) {}
+
+    /** @brief Destructor. */
+    ~BinFileWriter() = default;
+
+    /** @brief Copy constructor. */
+    BinFileWriter(const BinFileWriter&) = delete;
+
+    /** @brief Assignment operator. */
+    BinFileWriter& operator=(const BinFileWriter&) = delete;
+
+  private:
+    /** The output file stream. */
+    std::ofstream iv_stream;
+
+  public:
+    /** @return True, if the state of the stream is good. */
+    bool good()
+    {
+        return iv_stream.good();
+    }
+
+    /**
+     * @brief Inserts the first n characters of the the array pointed to by s
+              into the stream.
+     * @note  This function simply copies a block of data without checking its
+     *        contents or endianness.
+     * @note  After calling, check good() to determine if the operation was
+     *        successful.
+     * @param s Pointer to an array of at least n characters.
+     * @param n Number of characters to insert.
+     */
+    void write(void* s, size_t n)
+    {
+        iv_stream.write(static_cast<char*>(s), n);
+    }
+
+    /**
+     * @brief Output stream operator.
+     * @note  The default template is intentionally not defined so that only
+     *        specializations of this function can be used. This avoids
+     *        accidental usage on objects where endianness is a concern.
+     * @note  This is written as a template so that users can define their own
+     *        specializations for non-standard types.
+     */
+    template <class D>
+    BinFileWriter& operator<<(D r);
+};
+
+/** @brief Inserts host uint8_t to big-endian data. */
+template <>
+inline BinFileWriter& BinFileWriter::operator<<(uint8_t r)
+{
+    write(&r, sizeof(r));
+    return *this;
+}
+
+/** @brief Inserts host uint16_t to big-endian data. */
+template <>
+inline BinFileWriter& BinFileWriter::operator<<(uint16_t r)
+{
+    r = htobe16(r);
+    write(&r, sizeof(r));
+    return *this;
+}
+
+/** @brief Inserts host uint32_t to big-endian data. */
+template <>
+inline BinFileWriter& BinFileWriter::operator<<(uint32_t r)
+{
+    r = htobe32(r);
+    write(&r, sizeof(r));
+    return *this;
+}
+
+/** @brief Inserts host uint64_t to big-endian data. */
+template <>
+inline BinFileWriter& BinFileWriter::operator<<(uint64_t r)
+{
+    r = htobe64(r);
+    write(&r, sizeof(r));
+    return *this;
+}
+
+} // namespace util