ipmid: add message packing/unpacking unit tests

This adds unit tests for the various types that the message
packer/unpacker handles. This includes tests for simple messages as well
as complex messages. It also includes positive and negative testing to
make sure that failed packing and unpacking gets reported properly.

Change-Id: I9360c867cccbeba6a707dda6df6c5e29fa585c5c
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/test/message/payload.cpp b/test/message/payload.cpp
new file mode 100644
index 0000000..9d20ff0
--- /dev/null
+++ b/test/message/payload.cpp
@@ -0,0 +1,308 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * 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 <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <gtest/gtest.h>
+
+TEST(Payload, InputSize)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    size_t input_size = i.size();
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    ASSERT_EQ(input_size, p.size());
+}
+
+TEST(Payload, OutputSize)
+{
+    ipmi::message::Payload p;
+    ASSERT_EQ(0, p.size());
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    p.pack(i);
+    ASSERT_EQ(i.size(), p.size());
+    p.pack(i);
+    p.pack(i);
+    ASSERT_EQ(3 * i.size(), p.size());
+}
+
+TEST(Payload, Resize)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p;
+    p.pack(i);
+    p.resize(16);
+    ASSERT_EQ(p.size(), 16);
+}
+
+TEST(Payload, Data)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p;
+    p.pack(i);
+    ASSERT_NE(nullptr, p.data());
+}
+
+TEST(PayloadResponse, Append)
+{
+    std::string s("0123456789abcdef");
+    ipmi::message::Payload p;
+    p.append(s.data(), s.data() + s.size());
+    ASSERT_EQ(s.size(), p.size());
+}
+
+TEST(PayloadResponse, AppendDrain)
+{
+    std::string s("0123456789abcdef");
+    ipmi::message::Payload p;
+    bool b = true;
+    // first pack a lone bit
+    p.pack(b);
+    p.append(s.data(), s.data() + s.size());
+    // append will 'drain' first, padding the lone bit into a full byte
+    ASSERT_EQ(s.size() + 1, p.size());
+}
+
+TEST(PayloadResponse, AppendBits)
+{
+    ipmi::message::Payload p;
+    p.appendBits(3, 0b101);
+    ASSERT_EQ(p.bitStream, 0b101);
+    p.appendBits(4, 0b1100);
+    ASSERT_EQ(p.bitStream, 0b1100101);
+    p.appendBits(1, 0b1);
+    ASSERT_EQ(p.bitStream, 0);
+    ASSERT_EQ(p.bitCount, 0);
+    // appended 8 bits, should be one byte
+    ASSERT_EQ(p.size(), 1);
+    std::vector<uint8_t> k1 = {0b11100101};
+    ASSERT_EQ(p.raw, k1);
+    p.appendBits(7, 0b1110111);
+    // appended 7 more bits, should still be one byte
+    ASSERT_EQ(p.size(), 1);
+    p.drain();
+    // drain forces padding; should be two bytes now
+    ASSERT_EQ(p.size(), 2);
+    std::vector<uint8_t> k2 = {0b11100101, 0b01110111};
+    ASSERT_EQ(p.raw, k2);
+}
+
+TEST(PayloadResponse, Drain16Bits)
+{
+    ipmi::message::Payload p;
+    p.bitStream = 0b1011010011001111;
+    p.bitCount = 16;
+    p.drain();
+    ASSERT_EQ(p.size(), 2);
+    ASSERT_EQ(p.bitCount, 0);
+    ASSERT_EQ(p.bitStream, 0);
+    std::vector<uint8_t> k1 = {0b11001111, 0b10110100};
+    ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadResponse, Drain15Bits)
+{
+    ipmi::message::Payload p;
+    p.bitStream = 0b101101001100111;
+    p.bitCount = 15;
+    p.drain();
+    ASSERT_EQ(p.size(), 2);
+    ASSERT_EQ(p.bitCount, 0);
+    ASSERT_EQ(p.bitStream, 0);
+    std::vector<uint8_t> k1 = {0b1100111, 0b1011010};
+    ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadResponse, Drain15BitsWholeBytesOnly)
+{
+    ipmi::message::Payload p;
+    p.bitStream = 0b101101001100111;
+    p.bitCount = 15;
+    p.drain(true);
+    // only the first whole byte should have been 'drained' into p.raw
+    ASSERT_EQ(p.size(), 1);
+    ASSERT_EQ(p.bitCount, 7);
+    ASSERT_EQ(p.bitStream, 0b1011010);
+    std::vector<uint8_t> k1 = {0b1100111};
+    ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadRequest, Pop)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    const auto& [vb, ve] = p.pop<uint8_t>(4);
+    std::vector<uint8_t> v(vb, ve);
+    std::vector<uint8_t> k = {0xbf, 0x04, 0x86, 0x00};
+    ASSERT_EQ(v, k);
+}
+
+TEST(PayloadRequest, FillBits)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0xbf);
+    ASSERT_EQ(p.bitCount, 8);
+    // should still have 5 bits available, no change
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0xbf);
+    ASSERT_EQ(p.bitCount, 8);
+    // discard 5 bits (low order)
+    p.popBits(5);
+    // should add another byte into the stream (high order)
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0x25);
+    ASSERT_EQ(p.bitCount, 11);
+}
+
+TEST(PayloadRequest, FillBitsTooManyBits)
+{
+    std::vector<uint8_t> i = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.fillBits(72);
+    ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, FillBitsNotEnoughBytes)
+{
+    std::vector<uint8_t> i = {1, 2, 3, 4};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.fillBits(48);
+    ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, PopBits)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.fillBits(4);
+    uint8_t v = p.popBits(4);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0x0b);
+    ASSERT_EQ(p.bitCount, 4);
+    ASSERT_EQ(v, 0x0f);
+}
+
+TEST(PayloadRequest, PopBitsNoFillBits)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.popBits(4);
+    ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, DiscardBits)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0xbf);
+    ASSERT_EQ(p.bitCount, 8);
+    p.discardBits();
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0);
+    ASSERT_EQ(p.bitCount, 0);
+}
+
+TEST(PayloadRequest, FullyUnpacked)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    uint32_t v1;
+    p.unpack(v1);
+    // still one remaining byte
+    ASSERT_FALSE(p.fullyUnpacked());
+    p.fillBits(3);
+    p.popBits(3);
+    // still five remaining bits
+    ASSERT_FALSE(p.fullyUnpacked());
+    p.fillBits(5);
+    p.popBits(5);
+    // fully unpacked, should be no errors
+    ASSERT_TRUE(p.fullyUnpacked());
+    p.fillBits(4);
+    // fullyUnpacked fails because an attempt to unpack too many bytes
+    ASSERT_FALSE(p.fullyUnpacked());
+}
+
+TEST(PayloadRequest, ResetInternal)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    p.fillBits(4);
+    p.unpackError = true;
+    p.reset();
+    ASSERT_EQ(p.rawIndex, 0);
+    ASSERT_EQ(p.bitStream, 0);
+    ASSERT_EQ(p.bitCount, 0);
+    ASSERT_FALSE(p.unpackError);
+}
+
+TEST(PayloadRequest, ResetUsage)
+{
+    // Payload.reset is used to rewind the unpacking to the initial
+    // state. This is needed so that OEM commands can unpack the group
+    // number or the IANA to determine which handler needs to be called
+    std::vector<uint8_t> i = {0x04, 0x86};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    uint8_t v1;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint8_t k1 = 0x04;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    // do a reset on the payload
+    p.reset();
+    // unpack a uint16
+    uint16_t v2;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v2), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint16_t k2 = 0x8604;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v2, k2);
+}
+
+TEST(PayloadRequest, PartialPayload)
+{
+    std::vector<uint8_t> i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i));
+    uint8_t v1;
+    ipmi::message::Payload localPayload;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, localPayload), 0);
+    // check that the payload was partially unpacked and not in error
+    ASSERT_FALSE(p.fullyUnpacked());
+    ASSERT_FALSE(p.unpackError);
+    // check that the 'extracted' payload is not fully unpacked
+    ASSERT_FALSE(localPayload.fullyUnpacked());
+    uint8_t k1 = 0xbf;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    uint32_t v2;
+    // unpack using the 'extracted' payload
+    ASSERT_EQ(localPayload.unpack(v2), 0);
+    ASSERT_TRUE(localPayload.fullyUnpacked());
+    uint32_t k2 = 0x02008604;
+    ASSERT_EQ(v2, k2);
+}