PEL: Add Stream class to manipulate PEL data

This stream inserts data into and extracts data from the vector<uint8_t>
that it is given in its contructor.  That vector is how PEL data is
stored.  This object takes care of the endian conversion for fields that
require it, as PEL data is big endian.

On writes, it will expand the vector if necessary.

An exception will be thrown an invalid access is attempted, such as
trying to extract a value when at the end of the data.

It provides >> and << operators for common data types, as well as
read()/write() functions when using other types.

Example:

std::vector<uint8_t> data;
Stream stream{data};

uin32_t value = 0x12345678;
stream << value;

stream.offset(0);

uint32_t newValue;
stream >> newValue;

assert(value == newValue);

uint8_t buf[3000] = {0};
stream.write(buf, 3000);

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I8dc5566371749b45a260389a564836433323eef8
diff --git a/extensions/openpower-pels/stream.hpp b/extensions/openpower-pels/stream.hpp
new file mode 100644
index 0000000..eb91d4c
--- /dev/null
+++ b/extensions/openpower-pels/stream.hpp
@@ -0,0 +1,305 @@
+#pragma once
+
+#include <arpa/inet.h>
+#include <byteswap.h>
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace detail
+{
+/**
+ * @brief A host-to-network implementation for uint64_t
+ *
+ * @param[in] value - the value to convert to
+ * @return uint64_t - the byteswapped value
+ */
+inline uint64_t htonll(uint64_t value)
+{
+    return bswap_64(value);
+}
+
+/**
+ * @brief A network-to-host implementation for uint64_t
+ *
+ * @param[in] value - the value to convert to
+ * @return uint64_t - the byteswapped value
+ */
+inline uint64_t ntohll(uint64_t value)
+{
+    return bswap_64(value);
+}
+} // namespace detail
+
+/**
+ * @class Stream
+ *
+ * This class is used for getting data types into and out of a vector<uint8_t>
+ * that contains data in network byte (big endian) ordering.
+ */
+class Stream
+{
+  public:
+    Stream() = delete;
+    ~Stream() = default;
+    Stream(const Stream&) = default;
+    Stream& operator=(const Stream&) = default;
+    Stream(Stream&&) = default;
+    Stream& operator=(Stream&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] data - the vector of data
+     */
+    explicit Stream(std::vector<uint8_t>& data) : _data(data), _offset(0)
+    {
+    }
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] data - the vector of data
+     * @param[in] offset - the starting offset
+     */
+    Stream(std::vector<uint8_t>& data, std::size_t offset) :
+        _data(data), _offset(offset)
+    {
+        if (_offset >= _data.size())
+        {
+            throw std::out_of_range("Offset out of range");
+        }
+    }
+
+    /**
+     * @brief Extraction operator for a uint8_t
+     *
+     * @param[out] value - filled in with the value
+     * @return Stream&
+     */
+    Stream& operator>>(uint8_t& value)
+    {
+        read(&value, 1);
+        return *this;
+    }
+
+    /**
+     * @brief Extraction operator for a char
+     *
+     * @param[out] value -filled in with the value
+     * @return Stream&
+     */
+    Stream& operator>>(char& value)
+    {
+        read(&value, 1);
+        return *this;
+    }
+
+    /**
+     * @brief Extraction operator for a uint16_t
+     *
+     * @param[out] value -filled in with the value
+     * @return Stream&
+     */
+    Stream& operator>>(uint16_t& value)
+    {
+        read(&value, 2);
+        value = htons(value);
+        return *this;
+    }
+
+    /**
+     * @brief Extraction operator for a uint32_t
+     *
+     * @param[out] value -filled in with the value
+     * @return Stream&
+     */
+    Stream& operator>>(uint32_t& value)
+    {
+        read(&value, 4);
+        value = htonl(value);
+        return *this;
+    }
+
+    /**
+     * @brief Extraction operator for a uint64_t
+     *
+     * @param[out] value -filled in with the value
+     * @return Stream&
+     */
+    Stream& operator>>(uint64_t& value)
+    {
+        read(&value, 8);
+        value = detail::htonll(value);
+        return *this;
+    }
+
+    /**
+     * @brief Insert operator for a uint8_t
+     *
+     * @param[in] value - the value to write to the stream
+     * @return Stream&
+     */
+    Stream& operator<<(uint8_t& value)
+    {
+        write(&value, 1);
+        return *this;
+    }
+
+    /**
+     * @brief Insert operator for a char
+     *
+     * @param[in] value - the value to write to the stream
+     * @return Stream&
+     */
+    Stream& operator<<(char& value)
+    {
+        write(&value, 1);
+        return *this;
+    }
+
+    /**
+     * @brief Insert operator for a uint16_t
+     *
+     * @param[in] value - the value to write to the stream
+     * @return Stream&
+     */
+    Stream& operator<<(uint16_t& value)
+    {
+        uint16_t data = ntohs(value);
+        write(&data, 2);
+        return *this;
+    }
+
+    /**
+     * @brief Insert operator for a uint32_t
+     *
+     * @param[in] value - the value to write to the stream
+     * @return Stream&
+     */
+    Stream& operator<<(uint32_t& value)
+    {
+        uint32_t data = ntohl(value);
+        write(&data, 4);
+        return *this;
+    }
+
+    /**
+     * @brief Insert operator for a uint64_t
+     *
+     * @param[in] value - the value to write to the stream
+     * @return Stream&
+     */
+    Stream& operator<<(uint64_t& value)
+    {
+        uint64_t data = detail::ntohll(value);
+        write(&data, 8);
+        return *this;
+    }
+
+    /**
+     * @brief Sets the offset of the stream
+     *
+     * @param[in] newOffset - the new offset
+     */
+    void offset(std::size_t newOffset)
+    {
+        if (newOffset >= _data.size())
+        {
+            throw std::out_of_range("new offset out of range");
+        }
+
+        _offset = newOffset;
+    }
+
+    /**
+     * @brief Returns the current offset of the stream
+     *
+     * @return size_t - the offset
+     */
+    std::size_t offset() const
+    {
+        return _offset;
+    }
+
+    /**
+     * @brief Returns the remaining bytes left between the current offset
+     *        and the data size.
+     *
+     * @return size_t - the remaining size
+     */
+    std::size_t remaining() const
+    {
+        assert(_data.size() >= _offset);
+        return _data.size() - _offset;
+    }
+
+    /**
+     * @brief Reads a specified number of bytes out of a stream
+     *
+     * @param[out] out - filled in with the data
+     * @param[in] size - the size to read
+     */
+    void read(void* out, std::size_t size)
+    {
+        rangeCheck(size);
+        memcpy(out, &_data[_offset], size);
+        _offset += size;
+    }
+
+    /**
+     * @brief Writes a specified number of bytes into the stream
+     *
+     * @param[in] in - the data to write
+     * @param[in] size - the size to write
+     */
+    void write(void* in, std::size_t size)
+    {
+        size_t newSize = _offset + size;
+        if (newSize > _data.size())
+        {
+            _data.resize(newSize, 0);
+        }
+        memcpy(&_data[_offset], in, size);
+        _offset += size;
+    }
+
+  private:
+    /**
+     * @brief Throws an exception if the size passed in plus the current
+     *        offset is bigger than the current data size.
+     * @param[in] size - the size to check
+     */
+    void rangeCheck(std::size_t size)
+    {
+        if (_offset + size > _data.size())
+        {
+            std::string msg{"Attempted stream overflow: offset "};
+            msg += std::to_string(_offset) + " buffer size " +
+                   std::to_string(_data.size()) + " op size " +
+                   std::to_string(size);
+            throw std::out_of_range(msg.c_str());
+        }
+    }
+
+    /**
+     * @brief The data that the stream accesses.
+     */
+    std::vector<uint8_t>& _data;
+
+    /**
+     * @brief The current offset of the stream.
+     */
+    std::size_t _offset;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 98a0acd..295069a 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -1,10 +1,17 @@
 TESTS += $(check_PROGRAMS)
 
 check_PROGRAMS += \
-	additional_data_test
+	additional_data_test \
+	stream_test
 
 additional_data_test_SOURCES = %reldir%/additional_data_test.cpp
 additional_data_test_CPPFLAGS = $(test_cppflags)
 additional_data_test_CXXFLAGS = $(test_cxxflags)
 additional_data_test_LDADD = $(test_ldadd)
 additional_data_test_LDFLAGS = $(test_ldflags)
+
+stream_test_SOURCES = %reldir%/stream_test.cpp
+stream_test_CPPFLAGS = $(test_cppflags)
+stream_test_CXXFLAGS = $(test_cxxflags)
+stream_test_LDADD = $(test_ldadd)
+stream_test_LDFLAGS = $(test_ldflags)
\ No newline at end of file
diff --git a/test/openpower-pels/stream_test.cpp b/test/openpower-pels/stream_test.cpp
new file mode 100644
index 0000000..dd5ca93
--- /dev/null
+++ b/test/openpower-pels/stream_test.cpp
@@ -0,0 +1,160 @@
+#include "extensions/openpower-pels/stream.hpp"
+
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(StreamTest, TestExtract)
+{
+    std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                              0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                              0x08, 'h',  'e',  'l',  'l',  'o'};
+    Stream stream{data};
+
+    {
+        uint8_t v;
+        stream >> v;
+        EXPECT_EQ(v, 0x11);
+    }
+    {
+        uint16_t v;
+        stream >> v;
+        EXPECT_EQ(v, 0x2233);
+    }
+    {
+        uint32_t v;
+        stream >> v;
+        EXPECT_EQ(v, 0x44556677);
+    }
+    {
+        uint64_t v;
+        stream >> v;
+        EXPECT_EQ(v, 0x0102030405060708);
+    }
+    {
+        char v[6] = {0};
+        stream.read(v, 5);
+        EXPECT_EQ(memcmp(v, "hello", 5), 0);
+    }
+
+    EXPECT_EQ(stream.remaining(), 0);
+
+    // At the end, so should throw.
+    uint8_t v;
+    EXPECT_THROW(stream >> v, std::out_of_range);
+}
+
+TEST(StreamTest, InputTestNoExpansion)
+{
+    std::vector<uint8_t> data(256, 0);
+    Stream stream(data);
+    uint8_t v1 = 0x11;
+    uint16_t v2 = 0x2233;
+    uint64_t v3 = 0x445566778899AABB;
+    uint32_t v4 = 0xCCDDEEFF;
+
+    stream << v3 << v2 << v4 << v1;
+
+    uint8_t e1;
+    uint16_t e2;
+    uint64_t e3;
+    uint32_t e4;
+
+    stream.offset(0);
+    stream >> e3 >> e2 >> e4 >> e1;
+
+    EXPECT_EQ(v1, e1);
+    EXPECT_EQ(v2, e2);
+    EXPECT_EQ(v3, e3);
+    EXPECT_EQ(v4, e4);
+}
+
+TEST(StreamTest, InputTestExpansion)
+{
+    // The stream will expand the underlying vector
+    std::vector<uint8_t> data;
+    Stream stream(data);
+
+    uint32_t v1 = 0xAABBCCDD;
+    stream << v1;
+
+    stream.offset(0);
+    uint32_t e1;
+    stream >> e1;
+    EXPECT_EQ(data.size(), 4);
+    EXPECT_EQ(v1, e1);
+
+    stream.offset(2);
+
+    uint64_t v2 = 0x0102030405060708;
+    stream << v2;
+
+    EXPECT_EQ(data.size(), 10);
+    uint64_t e2;
+    stream.offset(2);
+    stream >> e2;
+
+    EXPECT_EQ(v2, e2);
+
+    auto origSize = data.size();
+    uint8_t v3 = 0xCC;
+    stream << v3;
+
+    EXPECT_EQ(origSize + 1, data.size());
+    stream.offset(stream.offset() - 1);
+    uint8_t e3;
+    stream >> e3;
+    EXPECT_EQ(v3, e3);
+}
+
+TEST(StreamTest, ReadWriteTest)
+{
+    std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                              0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+    Stream stream{data};
+    uint8_t buf[data.size()];
+
+    stream.read(buf, data.size());
+
+    for (size_t i = 0; i < data.size(); i++)
+    {
+        EXPECT_EQ(buf[i], data[i]);
+
+        // for the next test
+        buf[i] = 0x20 + i;
+    }
+
+    stream.offset(6);
+    stream.write(buf, 6);
+    for (size_t i = 0; i < 6; i++)
+    {
+        EXPECT_EQ(buf[i], data[i + 6]);
+    }
+}
+
+TEST(StreamTest, TestOffsets)
+{
+    std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+    Stream stream{data, 3};
+
+    {
+        uint8_t v;
+        stream >> v;
+        EXPECT_EQ(v, 0x44);
+        EXPECT_EQ(stream.offset(), 4);
+    }
+
+    stream.offset(6);
+
+    {
+        uint8_t v;
+        stream >> v;
+        EXPECT_EQ(v, 0x77);
+        EXPECT_EQ(stream.offset(), 7);
+        EXPECT_EQ(stream.remaining(), 0);
+    }
+
+    EXPECT_THROW(stream.offset(100), std::out_of_range);
+}