// Copyright (C) 2014-2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <dhcp/opaque_data_tuple.h>
#include <util/buffer.h>

#include <gtest/gtest.h>

#include <algorithm>
#include <sstream>
#include <vector>

using namespace isc;
using namespace isc::dhcp;
using namespace isc::util;

namespace {

struct OpaqueDataTupleLenientParsing : ::testing::Test {
    void SetUp() override {
        // Retain the current setting for future restoration.
        previous_ = Option::lenient_parsing_;

        // Enable lenient parsing.
        Option::lenient_parsing_ = true;
    }

    void TearDown() override {
        // Restore.
        Option::lenient_parsing_ = previous_;
    }

    bool previous_;
};

// This test checks that when the default constructor is called, the data buffer
// is empty.
TEST(OpaqueDataTuple, constructor) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // There should be no data in the tuple.
    EXPECT_EQ(0, tuple.getLength());
    EXPECT_TRUE(tuple.getData().empty());
    EXPECT_TRUE(tuple.getText().empty());
}

// Test that the constructor which takes the buffer as argument parses the
// wire data.
TEST(OpaqueDataTuple, constructorParse1Byte) {
    OpaqueDataTuple::Buffer wire_data = {
        0x0B,                               // Length is 11
        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
    };

    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data.begin(),
                          wire_data.end());

    EXPECT_EQ(11, tuple.getLength());
    EXPECT_EQ("Hello world", tuple.getText());
}

// Test that the constructor which takes the buffer as argument parses the
// wire data.
TEST(OpaqueDataTuple, constructorParse2Bytes) {
    OpaqueDataTuple::Buffer wire_data = {
        0x00, 0x0B,                         // Length is 11
        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
    };

    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data.begin(),
                          wire_data.end());

    EXPECT_EQ(11, tuple.getLength());
    EXPECT_EQ("Hello world", tuple.getText());
}


// This test checks that it is possible to set the tuple data using raw buffer.
TEST(OpaqueDataTuple, assignData) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Initially the tuple buffer should be empty.
    OpaqueDataTuple::Buffer buf = tuple.getData();
    ASSERT_TRUE(buf.empty());
    // Prepare some input data and assign to the tuple.
    OpaqueDataTuple::Buffer data1 = {
        0xCA, 0xFE, 0xBE, 0xEF
    };
    tuple.assign(data1.begin(), data1.size());
    // Tuple should now hold the data we assigned.
    ASSERT_EQ(data1.size(), tuple.getLength());
    buf = tuple.getData();
    EXPECT_EQ(buf, data1);

    // Prepare the other set of data and assign to the tuple.
    OpaqueDataTuple::Buffer data2 = {
        1, 2, 3, 4, 5, 6
    };
    tuple.assign(data2.begin(), data2.size());
    // The new data should have replaced the old data.
    ASSERT_EQ(data2.size(), tuple.getLength());
    buf = tuple.getData();
    EXPECT_EQ(buf, data2);
}

// This test checks that it is possible to append the data to the tuple using
// raw buffer.
TEST(OpaqueDataTuple, appendData) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Initially the tuple buffer should be empty.
    OpaqueDataTuple::Buffer buf = tuple.getData();
    ASSERT_TRUE(buf.empty());
    // Prepare some input data and append to the empty tuple.
    OpaqueDataTuple::Buffer data1 = {
        0xCA, 0xFE, 0xBE, 0xEF
    };
    tuple.append(data1.begin(), data1.size());
    // The tuple should now hold only the data we appended.
    ASSERT_EQ(data1.size(), tuple.getLength());
    buf = tuple.getData();
    EXPECT_EQ(buf, data1);
    // Prepare the new set of data and append.
    OpaqueDataTuple::Buffer data2 = {
        1, 2, 3, 4, 5, 6
    };
    tuple.append(data2.begin(), data2.size());
    // We expect that the tuple now has both sets of data we appended. In order
    // to verify that, we have to concatenate the input data1 and data2.
    OpaqueDataTuple::Buffer data12(data1.begin(), data1.end());
    data12.insert(data12.end(), data2.begin(), data2.end());
    // The size of the tuple should be a sum of data1 and data2 lengths.
    ASSERT_EQ(data1.size() + data2.size(), tuple.getLength());
    buf = tuple.getData();
    EXPECT_EQ(buf, data12);
}

// This test checks that it is possible to assign the string to the tuple.
TEST(OpaqueDataTuple, assignString) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Initially, the tuple should be empty.
    ASSERT_EQ(0, tuple.getLength());
    // Assign some string data.
    tuple.assign("Some string");
    // Verify that the data has been assigned.
    EXPECT_EQ(11, tuple.getLength());
    EXPECT_EQ("Some string", tuple.getText());
    // Assign some other string.
    tuple.assign("Different string");
    // The new string should have replaced the old string.
    EXPECT_EQ(16, tuple.getLength());
    EXPECT_EQ("Different string", tuple.getText());
}

// This test checks that it is possible to append the string to the tuple.
TEST(OpaqueDataTuple, appendString) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Initially the tuple should be empty.
    ASSERT_EQ(0, tuple.getLength());
    // Append the string to it.
    tuple.append("First part");
    ASSERT_EQ(10, tuple.getLength());
    ASSERT_EQ("First part", tuple.getText());
    // Now append the other string.
    tuple.append(" and second part");
    EXPECT_EQ(26, tuple.getLength());
    // The resulting data in the tuple should be a concatenation of both
    // strings.
    EXPECT_EQ("First part and second part", tuple.getText());
}

// This test verifies that equals function correctly checks that the tuple
// holds a given string but it doesn't hold the other. This test also
// checks the assignment operator for the tuple.
TEST(OpaqueDataTuple, equals) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Tuple is supposed to be empty so it is not equal xyz.
    EXPECT_FALSE(tuple.equals("xyz"));
    // Assign xyz.
    EXPECT_NO_THROW(tuple = "xyz");
    // The tuple should be equal xyz, but not abc.
    EXPECT_FALSE(tuple.equals("abc"));
    EXPECT_TRUE(tuple.equals("xyz"));
    // Assign abc to the tuple.
    EXPECT_NO_THROW(tuple = "abc");
    // It should be now opposite.
    EXPECT_TRUE(tuple.equals("abc"));
    EXPECT_FALSE(tuple.equals("xyz"));
}

// This test checks that the conversion of the data in the tuple to the string
// is performed correctly.
TEST(OpaqueDataTuple, getText) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Initially the tuple should be empty.
    ASSERT_TRUE(tuple.getText().empty());
    // ASCII representation of 'Hello world'.
    const char as_ascii[] = {
        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
    };
    // Assign it to the tuple.
    tuple.assign(as_ascii, sizeof(as_ascii));
    // Conversion to string should give as the original text.
    EXPECT_EQ("Hello world", tuple.getText());
}

// This test verifies the behavior of (in)equality and assignment operators.
TEST(OpaqueDataTuple, operators) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Tuple should be empty initially.
    ASSERT_EQ(0, tuple.getLength());
    // Check assignment.
    EXPECT_NO_THROW(tuple = "Hello World");
    EXPECT_EQ(11, tuple.getLength());
    EXPECT_TRUE(tuple == "Hello World");
    EXPECT_TRUE(tuple != "Something else");
    // Assign something else to make sure it affects the tuple.
    EXPECT_NO_THROW(tuple = "Something else");
    EXPECT_EQ(14, tuple.getLength());
    EXPECT_TRUE(tuple == "Something else");
    EXPECT_TRUE(tuple != "Hello World");
}

// This test verifies that the tuple is inserted in the textual format to the
// output stream.
TEST(OpaqueDataTuple, operatorOutputStream) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // The tuple should be empty initially.
    ASSERT_EQ(0, tuple.getLength());
    // The tuple is empty, so assigning its content to the output stream should
    // be no-op and result in the same text in the stream.
    std::ostringstream s;
    s << "Some text";
    EXPECT_NO_THROW(s << tuple);
    EXPECT_EQ("Some text", s.str());
    // Now, let's assign some text to the tuple and call operator again.
    // The new text should be added to the stream.
    EXPECT_NO_THROW(tuple = " and some other text");
    EXPECT_NO_THROW(s << tuple);
    EXPECT_EQ(s.str(), "Some text and some other text");
}

// This test verifies that the value of the tuple can be initialized from the
// input stream.
TEST(OpaqueDataTuple, operatorInputStream) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // The tuple should be empty initially.
    ASSERT_EQ(0, tuple.getLength());
    // The input stream has some text. This text should be appended to the
    // tuple.
    std::istringstream s;
    s.str("Some text");
    EXPECT_NO_THROW(s >> tuple);
    EXPECT_EQ("Some text", tuple.getText());
    // Now, let's assign some other text to the stream. This new text should be
    // assigned to the tuple.
    s.str("And some other");
    EXPECT_NO_THROW(s >> tuple);
    EXPECT_EQ("And some other", tuple.getText());
}

// This test checks that the tuple is correctly encoded in the wire format when
// the size of the length field is 1 byte.
TEST(OpaqueDataTuple, pack1Byte) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
    // Initially, the tuple should be empty.
    ASSERT_EQ(0, tuple.getLength());
    // It turns out that Option 124 can be sent with 0 length Opaque Data
    // See #2021 for more details
    OutputBuffer out_buf(10);
    ASSERT_NO_THROW(tuple.pack(out_buf));
    ASSERT_EQ(1, out_buf.getLength());
    const uint8_t* zero_len = out_buf.getData();
    ASSERT_EQ(0, *zero_len);
    // Reset the output buffer for another test.
    out_buf.clear();
    // Set the data for tuple.
    std::vector<uint8_t> data;
    for (uint8_t i = 0; i < 100; ++i) {
        data.push_back(i);
    }
    tuple.assign(data.begin(), data.size());
    // Packing the data should succeed.
    ASSERT_NO_THROW(tuple.pack(out_buf));
    // The rendered buffer should be 101 bytes long - 1 byte for length,
    // 100 bytes for the actual data.
    ASSERT_EQ(101, out_buf.getLength());
    // Get the rendered data into the vector for convenience.
    std::vector<uint8_t>
        render_data(out_buf.getData(), out_buf.getData() + 101);
    // The first byte is a length byte. It should hold the length of 100.
    EXPECT_EQ(100, render_data[0]);
    // Verify that the rendered data is correct.
    EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
                           data.begin()));
    // Reset the output buffer for another test.
    out_buf.clear();
    // Fill in the tuple buffer so as it reaches maximum allowed length. The
    // maximum length is 255 when the size of the length field is one byte.
    for (uint8_t i = 100; i < 255; ++i) {
        data.push_back(i);
    }
    ASSERT_EQ(255, data.size());
    tuple.assign(data.begin(), data.size());
    // The pack() should be successful again.
    ASSERT_NO_THROW(tuple.pack(out_buf));
    // The rendered buffer should be 256 bytes long. The first byte holds the
    // opaque data length, the remaining bytes hold the actual data.
    ASSERT_EQ(256, out_buf.getLength());
    // Check that the data is correct.
    render_data.assign(out_buf.getData(), out_buf.getData() + 256);
    EXPECT_EQ(255, render_data[0]);
    EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
                           data.begin()));
    // Clear output buffer for another test.
    out_buf.clear();
    // Add one more value to the tuple. Now, the resulting buffer should exceed
    // the maximum length. An attempt to pack() should fail.
    data.push_back(255);
    tuple.assign(data.begin(), data.size());
    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
}

// This test checks that the tuple is correctly encoded in the wire format when
// the size of the length field is 2 bytes.
TEST(OpaqueDataTuple, pack2Bytes) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Initially, the tuple should be empty.
    ASSERT_EQ(0, tuple.getLength());
    // It turns out that Option 124 can be sent with 0 length Opaque Data
    // See #2021 for more details
    OutputBuffer out_buf(10);
    ASSERT_NO_THROW(tuple.pack(out_buf));
    ASSERT_EQ(2, out_buf.getLength());
    const uint8_t* out_data = out_buf.getData();
    ASSERT_LE(sizeof(uint16_t), out_buf.getLength());
    ASSERT_EQ(0, out_data[0]);
    ASSERT_EQ(0, out_data[1]);
    // Reset the output buffer for another test.
    out_buf.clear();
    // Set the data for tuple.
    std::vector<uint8_t> data;
    for (unsigned i = 0; i < 512; ++i) {
        data.push_back(i & 0xff);
    }
    tuple.assign(data.begin(), data.size());
    // Packing the data should succeed.
    ASSERT_NO_THROW(tuple.pack(out_buf));
    // The rendered buffer should be 514 bytes long - 2 bytes for length,
    // 512 bytes for the actual data.
    ASSERT_EQ(514, out_buf.getLength());
    // Get the rendered data into the vector for convenience.
    std::vector<uint8_t>
        render_data(out_buf.getData(), out_buf.getData() + 514);
    // The first two bytes hold the length of 512.
    uint16_t len = (render_data[0] << 8) + render_data[1];
    EXPECT_EQ(512, len);
    // Verify that the rendered data is correct.
    EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(),
                           data.begin()));

    // Without clearing the output buffer, try to do it again. The pack should
    // append the data to the current buffer.
    ASSERT_NO_THROW(tuple.pack(out_buf));
    EXPECT_EQ(1028, out_buf.getLength());

    // Check that we can render the buffer of the maximal allowed size.
    data.assign(65535, 1);
    ASSERT_NO_THROW(tuple.assign(data.begin(), data.size()));
    ASSERT_NO_THROW(tuple.pack(out_buf));

    out_buf.clear();

    // Append one additional byte. The total length of the tuple now exceeds
    // the maximal value. An attempt to render it should throw an exception.
    data.assign(1, 1);
    ASSERT_NO_THROW(tuple.append(data.begin(), data.size()));
    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
}

// This test verifies that the tuple is decoded from the wire format.
TEST(OpaqueDataTuple, unpack1Byte) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
    OpaqueDataTuple::Buffer wire_data = {
        0x0B,                               // Length is 11
        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
    };

    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
    EXPECT_EQ(11, tuple.getLength());
    EXPECT_EQ("Hello world", tuple.getText());
}

// This test verifies that the tuple having a length of 0, is decoded from
// the wire format.
TEST(OpaqueDataTuple, unpack1ByteZeroLength) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
    EXPECT_NO_THROW(tuple = "Hello world");
    ASSERT_NE(tuple.getLength(), 0);

    OpaqueDataTuple::Buffer wire_data = {
        0
    };
    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));

    EXPECT_EQ(0, tuple.getLength());
}

// This test verifies that the tuple having a length of 0, followed by no
// data, is decoded from the wire format.
TEST(OpaqueDataTuple, unpack1ByteZeroLengthNoData) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
    OpaqueDataTuple::Buffer wire_data = {0};
    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
}

// This test verifies that the tuple having a length of 0, followed by no
// data, is decoded from the wire format.
TEST(OpaqueDataTuple, unpack2ByteZeroLengthNoData) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    OpaqueDataTuple::Buffer wire_data = {0, 0};
    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
}

// This test verifies that exception is thrown if the empty buffer is being
// parsed.
TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
    OpaqueDataTuple::Buffer wire_data = {};
    EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
                 OpaqueDataTupleError);
}

// This test verifies that exception is thrown when parsing truncated buffer.
TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) {
   OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
    OpaqueDataTuple::Buffer wire_data = {
        10, 2, 3
    };
    EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
                 OpaqueDataTupleError);
}

// This test verifies that the tuple is decoded from the wire format.
TEST(OpaqueDataTuple, unpack2Byte) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    OpaqueDataTuple::Buffer wire_data;
    // Set tuple length to 400 (0x190).
    wire_data.push_back(1);
    wire_data.push_back(0x90);
    // Fill in the buffer with some data.
    for (int i = 0; i < 400; ++i) {
        wire_data.push_back(i);
    }
    // The unpack should succeed.
    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
    // The decoded length should be 400.
    ASSERT_EQ(400, tuple.getLength());
    // And the data should match.
    EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(),
                           tuple.getData().begin()));
}

// This test verifies that the tuple having a length of 0, is decoded from
// the wire format.
TEST(OpaqueDataTuple, unpack2ByteZeroLength) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Set some data for the tuple.
    EXPECT_NO_THROW(tuple = "Hello world");
    ASSERT_NE(tuple.getLength(), 0);
    // The buffer holds just a length field with the value of 0.
    OpaqueDataTuple::Buffer wire_data = {
        0, 0
    };
    // The empty tuple should be successfully decoded.
    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
    // The data should be replaced with an empty buffer.
    EXPECT_EQ(0, tuple.getLength());
}

// This test verifies that exception is thrown if the empty buffer is being
// parsed.
TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    OpaqueDataTuple::Buffer wire_data = {};
    // Pass empty buffer (first iterator equal to second iterator).
    // This should not be accepted.
    EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
                 OpaqueDataTupleError);
}

// This test verifies that exception is thrown when parsing truncated buffer.
TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) {
   OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
   // Specify the data with the length of 10, but limit the buffer size to
   // 2 bytes.
   OpaqueDataTuple::Buffer wire_data = {
       0, 10, 2, 3
   };
   // This should fail because the buffer is truncated.
   EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
                OpaqueDataTupleError);
}

// Test that an exception is not thrown when parsing in lenient mode.
TEST_F(OpaqueDataTupleLenientParsing, unpack) {
    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
    // Specify the data with the length of 10, but limit the buffer size to 2.
   OpaqueDataTuple::Buffer wire_data = {
        0, 10, 2, 3
    };
    EXPECT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
    EXPECT_EQ(tuple.getData(), OpaqueDataTuple::Buffer({2, 3}));
}

} // anonymous namespace
