diff options
Diffstat (limited to 'src/lib/dhcp/tests/option_data_types_unittest.cc')
-rw-r--r-- | src/lib/dhcp/tests/option_data_types_unittest.cc | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc new file mode 100644 index 0000000..6a70c04 --- /dev/null +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -0,0 +1,928 @@ +// Copyright (C) 2012-2021 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/option_data_types.h> +#include <gtest/gtest.h> +#include <utility> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Default (zero) prefix tuple. +const PrefixTuple +ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0), + IOAddress(IOAddress::IPV6_ZERO_ADDRESS()))); + +/// @brief Test class for option data type utilities. +class OptionDataTypesTest : public ::testing::Test { +public: + + /// @brief Constructor. + OptionDataTypesTest() { } + + /// @brief Write IP address into a buffer. + /// + /// @param address address to be written. + /// @param [out] buf output buffer. + void writeAddress(const asiolink::IOAddress& address, + std::vector<uint8_t>& buf) { + const std::vector<uint8_t>& vec = address.toBytes(); + buf.insert(buf.end(), vec.begin(), vec.end()); + } + + /// @brief Write integer (signed or unsigned) into a buffer. + /// + /// @param value integer value. + /// @param [out] buf output buffer. + /// @tparam integer type. + template<typename T> + void writeInt(T value, std::vector<uint8_t>& buf) { + switch (sizeof(T)) { + case 4: + buf.push_back((value >> 24) & 0xFF); + /* falls through */ + case 3: + buf.push_back((value >> 16) & 0xFF); + /* falls through */ + case 2: + buf.push_back((value >> 8) & 0xFF); + /* falls through */ + case 1: + buf.push_back(value & 0xFF); + break; + default: + // This loop is incorrectly compiled by some old g++?! + for (int i = 0; i < sizeof(T); ++i) { + buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF); + } + } + } + + /// @brief Write a string into a buffer. + /// + /// @param value string to be written into a buffer. + /// @param buf output buffer. + void writeString(const std::string& value, + std::vector<uint8_t>& buf) { + buf.resize(buf.size() + value.size()); + std::copy_backward(value.c_str(), value.c_str() + value.size(), + buf.end()); + } +}; + +// The goal of this test is to verify that the getLabelCount returns the +// correct number of labels in the domain name specified as a string +// parameter. +TEST_F(OptionDataTypesTest, getLabelCount) { + EXPECT_EQ(0, OptionDataTypeUtil::getLabelCount("")); + EXPECT_EQ(1, OptionDataTypeUtil::getLabelCount(".")); + EXPECT_EQ(2, OptionDataTypeUtil::getLabelCount("example")); + EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com")); + EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com.")); + EXPECT_EQ(4, OptionDataTypeUtil::getLabelCount("myhost.example.com")); + EXPECT_THROW(OptionDataTypeUtil::getLabelCount(".abc."), + isc::dhcp::BadDataTypeCast); +} + +// The goal of this test is to verify that an IPv4 address being +// stored in a buffer (wire format) can be read into IOAddress +// object. +TEST_F(OptionDataTypesTest, readAddress) { + // Create some IPv4 address. + asiolink::IOAddress address("192.168.0.1"); + // And store it in a buffer in a wire format. + std::vector<uint8_t> buf; + writeAddress(address, buf); + + // Now, try to read the IP address with a utility function + // being under test. + asiolink::IOAddress address_out("127.0.0.1"); + EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET)); + + // Check that the read address matches address that + // we used as input. + EXPECT_EQ(address, address_out); + + // Check that an attempt to read the buffer as IPv6 address + // causes an error as the IPv6 address needs at least 16 bytes + // long buffer. + EXPECT_THROW( + OptionDataTypeUtil::readAddress(buf, AF_INET6), + isc::dhcp::BadDataTypeCast + ); + + buf.clear(); + + // Do another test like this for IPv6 address. + address = asiolink::IOAddress("2001:db8:1:0::1"); + writeAddress(address, buf); + EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6)); + EXPECT_EQ(address, address_out); + + // Truncate the buffer and expect an error to be reported when + // trying to read it. + buf.resize(buf.size() - 1); + EXPECT_THROW( + OptionDataTypeUtil::readAddress(buf, AF_INET6), + isc::dhcp::BadDataTypeCast + ); +} + +// The goal of this test is to verify that an IPv6 address +// is properly converted to wire format and stored in a +// buffer. +TEST_F(OptionDataTypesTest, writeAddress) { + // Encode an IPv6 address 2001:db8:1::1 in wire format. + // This will be used as reference data to validate if + // an IPv6 address is stored in a buffer properly. + const uint8_t data[] = { + 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 + }; + std::vector<uint8_t> buf_in(data, data + sizeof(data)); + + // Create IPv6 address object. + asiolink::IOAddress address("2001:db8:1::1"); + // Define the output buffer to write IP address to. + std::vector<uint8_t> buf_out; + // Write the address to the buffer. + ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out)); + // Make sure that input and output buffers have the same size + // so we can compare them. + ASSERT_EQ(buf_in.size(), buf_out.size()); + // And finally compare them. + EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin())); + + buf_out.clear(); + + // Do similar test for IPv4 address. + address = asiolink::IOAddress("192.168.0.1"); + ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out)); + ASSERT_EQ(4, buf_out.size()); + // Verify that the IP address has been written correctly. + EXPECT_EQ(192, buf_out[0]); + EXPECT_EQ(168, buf_out[1]); + EXPECT_EQ(0, buf_out[2]); + EXPECT_EQ(1, buf_out[3]); +} + +// The purpose of this test is to verify that binary data represented +// as a string of hexadecimal digits can be written to a buffer. +TEST_F(OptionDataTypesTest, writeBinary) { + // Prepare the reference data. + const char data[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + std::vector<uint8_t> buf_ref(data, data + sizeof(data)); + // Create empty vector where binary data will be written to. + std::vector<uint8_t> buf; + ASSERT_NO_THROW( + OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf) + ); + // Verify that the buffer contains valid data. + ASSERT_EQ(buf_ref.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +// The purpose of this test is to verify that the tuple value stored +TEST_F(OptionDataTypesTest, readTuple) { + // The string + std::string value = "hello world"; + // Create an input buffer. + std::vector<uint8_t> buf; + // DHCPv4 tuples use 1 byte length + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + std::string result; + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_1_BYTE); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + // Read the tuple from the buffer. + OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE); + ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple4)); + // Check that it is valid. + EXPECT_EQ(value, tuple4.getText()); + + buf.clear(); + + // DHCPv6 tuples use 2 byte length + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), buf); + writeString(value, buf); + + // Read the string from the buffer. + ASSERT_NO_THROW( + result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_2_BYTES); + ); + // Check that it is valid. + EXPECT_EQ(value, result); + + // Read the tuple from the buffer. + OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES); + ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple6)); + // Check that it is valid. + EXPECT_EQ(value, tuple6.getText()); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer (string version) +TEST_F(OptionDataTypesTest, writeTupleString) { + // The string + std::string value = "hello world"; + // Create an output buffer. + std::vector<uint8_t> buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_1_BYTE, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector<uint8_t> expected; + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_2_BYTES, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); +} + +// The purpose of this test is to verify that a tuple value +// are correctly encoded in a buffer (tuple version) +TEST_F(OptionDataTypesTest, writeTuple) { + // The string + std::string value = "hello world"; + // Create a DHCPv4 tuple + OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE); + tuple4.append(value); + // Create an output buffer. + std::vector<uint8_t> buf; + + // Encode it in DHCPv4 + OptionDataTypeUtil::writeTuple(tuple4, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 1, buf.size()); + std::vector<uint8_t> expected; + writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); + + buf.clear(); + + // Create a DHCPv6 tuple + OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES); + tuple6.append(value); + + // Encode it in DHCPv6 + OptionDataTypeUtil::writeTuple(tuple6, buf); + + // Check that it is valid. + ASSERT_EQ(value.size() + 2, buf.size()); + expected.clear(); + writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected); + writeString(value, expected); + EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size())); +} + +// The purpose of this test is to verify that the boolean value stored +// in a buffer is correctly read from this buffer. +TEST_F(OptionDataTypesTest, readBool) { + // Create an input buffer. + std::vector<uint8_t> buf; + // 'true' value is encoded as 1 ('false' is encoded as 0) + buf.push_back(1); + + // Read the value from the buffer. + bool value = false; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readBool(buf); + ); + // Verify the value. + EXPECT_TRUE(value); + // Check if 'false' is read correctly either. + buf[0] = 0; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readBool(buf); + ); + EXPECT_FALSE(value); + + // Check that invalid value causes exception. + buf[0] = 5; + ASSERT_THROW( + OptionDataTypeUtil::readBool(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that boolean values +// are correctly encoded in a buffer as '1' for 'true' and +// '0' for 'false' values. +TEST_F(OptionDataTypesTest, writeBool) { + // Create a buffer we will write to. + std::vector<uint8_t> buf; + // Write the 'true' value to the buffer. + ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf)); + // We should now have 'true' value stored in a buffer. + ASSERT_EQ(1, buf.size()); + EXPECT_EQ(buf[0], 1); + // Let's append another value to make sure that it is not always + // 'true' value being written. + ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf)); + ASSERT_EQ(2, buf.size()); + // Check that the first value has not changed. + EXPECT_EQ(buf[0], 1); + // Check the second value is correct. + EXPECT_EQ(buf[1], 0); +} + +// The purpose of this test is to verify that the integer values +// of different types are correctly read from a buffer. +TEST_F(OptionDataTypesTest, readInt) { + std::vector<uint8_t> buf; + + // Write an 8-bit unsigned integer value to the buffer. + writeInt<uint8_t>(129, buf); + uint8_t valueUint8 = 0; + // Read the value and check that it is valid. + ASSERT_NO_THROW( + valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf); + ); + EXPECT_EQ(129, valueUint8); + + // Try to read 16-bit value from a buffer holding 8-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<uint16_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Clear the buffer for the next check we are going to do. + buf.clear(); + + // Test uint16_t value. + writeInt<uint16_t>(1234, buf); + uint16_t valueUint16 = 0; + ASSERT_NO_THROW( + valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf); + ); + EXPECT_EQ(1234, valueUint16); + + // Try to read 32-bit value from a buffer holding 16-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<uint32_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + buf.clear(); + + // Test uint32_t value. + writeInt<uint32_t>(56789, buf); + uint32_t valueUint32 = 0; + ASSERT_NO_THROW( + valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf); + ); + EXPECT_EQ(56789, valueUint32); + buf.clear(); + + // Test int8_t value. + writeInt<int8_t>(-65, buf); + int8_t valueInt8 = 0; + ASSERT_NO_THROW( + valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf); + ); + EXPECT_EQ(-65, valueInt8); + buf.clear(); + + // Try to read 16-bit value from a buffer holding 8-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<int16_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Test int16_t value. + writeInt<int16_t>(2345, buf); + int32_t valueInt16 = 0; + ASSERT_NO_THROW( + valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf); + ); + EXPECT_EQ(2345, valueInt16); + buf.clear(); + + // Try to read 32-bit value from a buffer holding 16-bit value. + // This should result in an exception. + EXPECT_THROW( + OptionDataTypeUtil::readInt<int32_t>(buf), + isc::dhcp::BadDataTypeCast + ); + + // Test int32_t value. + writeInt<int32_t>(-16543, buf); + int32_t valueInt32 = 0; + ASSERT_NO_THROW( + valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf); + ); + EXPECT_EQ(-16543, valueInt32); + + buf.clear(); +} + +// The purpose of this test is to verify that integer values of different +// types are correctly written to a buffer. +TEST_F(OptionDataTypesTest, writeInt) { + // Prepare the reference buffer. + const uint8_t data[] = { + 0x7F, // 127 + 0x03, 0xFF, // 1023 + 0x00, 0x00, 0x10, 0x00, // 4096 + 0xFF, 0xFF, 0xFC, 0x00, // -1024 + 0x02, 0x00, // 512 + 0x81 // -127 + }; + std::vector<uint8_t> buf_ref(data, data + sizeof(data)); + + // Fill in the buffer with data. Each write operation appends an + // integer value. Eventually the buffer holds all values and should + // match with the reference buffer. + std::vector<uint8_t> buf; + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf)); + ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf)); + + // Make sure that the buffer has the same size as the reference + // buffer. + ASSERT_EQ(buf_ref.size(), buf.size()); + // Compare buffers. + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +// The purpose of this test is to verify that FQDN is read from +// a buffer and returned as a text. The representation of the FQDN +// in the buffer complies with RFC1035, section 3.1. +// This test also checks that if invalid (truncated) FQDN is stored +// in a buffer the appropriate exception is returned when trying to +// read it as a string. +TEST_F(OptionDataTypesTest, readFqdn) { + // The binary representation of the "mydomain.example.com". + // Values: 8, 7, 3 and 0 specify the lengths of subsequent + // labels within the FQDN. + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Make a vector out of the data. + std::vector<uint8_t> buf(data, data + sizeof(data)); + + // Read the buffer as FQDN and verify its correctness. + std::string fqdn; + EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf)); + EXPECT_EQ("mydomain.example.com.", fqdn); + + // By resizing the buffer we simulate truncation. The first + // length field (8) indicate that the first label's size is + // 8 but the actual buffer size is 5. Expect that conversion + // fails. + buf.resize(5); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); + + // Another special case: provide an empty buffer. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that FQDN's syntax is validated +// and that FQDN is correctly written to a buffer in a format described +// in RFC1035 section 3.1. +TEST_F(OptionDataTypesTest, writeFqdn) { + // Create empty buffer. The FQDN will be written to it. + OptionBuffer buf; + // Write a domain name into the buffer in the format described + // in RFC1035 section 3.1. This function should not throw + // exception because domain name is well formed. + EXPECT_NO_THROW( + OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf) + ); + // The length of the data is 22 (8 bytes for "mydomain" label, + // 7 bytes for "example" label, 3 bytes for "com" label and + // finally 4 bytes positions between labels where length + // information is stored. + ASSERT_EQ(22, buf.size()); + + // Verify that length fields between labels hold valid values. + EXPECT_EQ(8, buf[0]); // length of "mydomain" + EXPECT_EQ(7, buf[9]); // length of "example" + EXPECT_EQ(3, buf[17]); // length of "com" + EXPECT_EQ(0, buf[21]); // zero byte at the end. + + // Verify that labels are valid. + std::string label0(buf.begin() + 1, buf.begin() + 9); + EXPECT_EQ("mydomain", label0); + + std::string label1(buf.begin() + 10, buf.begin() + 17); + EXPECT_EQ("example", label1); + + std::string label2(buf.begin() + 18, buf.begin() + 21); + EXPECT_EQ("com", label2); + + // The tested function is supposed to append data to a buffer + // so let's check that it is a case by appending another domain. + OptionDataTypeUtil::writeFqdn("hello.net", buf); + + // The buffer length should be now longer. + ASSERT_EQ(33, buf.size()); + + // Check the length fields for new labels being appended. + EXPECT_EQ(5, buf[22]); + EXPECT_EQ(3, buf[28]); + + // And check that labels are ok. + std::string label3(buf.begin() + 23, buf.begin() + 28); + EXPECT_EQ("hello", label3); + + std::string label4(buf.begin() + 29, buf.begin() + 32); + EXPECT_EQ("net", label4); + + // Check that invalid (empty) FQDN is rejected and expected + // exception type is thrown. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("", buf), + isc::dhcp::BadDataTypeCast + ); + + // Check another invalid domain name (with repeated dot). + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("example..com", buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that the variable length prefix +// can be read from a buffer correctly. +TEST_F(OptionDataTypesTest, readPrefix) { + std::vector<uint8_t> buf; + + // Prefix 2001:db8::/64 + writeInt<uint8_t>(64, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0, buf); + + PrefixTuple prefix(ZERO_PREFIX_TUPLE); + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(64, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8::", prefix.second.toText()); + + buf.clear(); + + // Prefix 2001:db8::/63 + writeInt<uint8_t>(63, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(63, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8::", prefix.second.toText()); + + buf.clear(); + + // Prefix 2001:db8:c0000. Note that the last four bytes are filled with + // 0xFF (all bits set). When the prefix is read those non-significant + // bits (beyond prefix length) should be ignored (read as 0). Only first + // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF. + writeInt<uint8_t>(34, buf); + writeInt<uint32_t>(0x20010db8, buf); + writeInt<uint32_t>(0xFFFFFFFF, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(34, prefix.first.asUnsigned()); + EXPECT_EQ("2001:db8:c000::", prefix.second.toText()); + + buf.clear(); + + // Prefix having a length of 0. + writeInt<uint8_t>(0, buf); + writeInt<uint16_t>(0x2001, buf); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(0, prefix.first.asUnsigned()); + EXPECT_EQ("::", prefix.second.toText()); + + buf.clear(); + + // Prefix having a maximum length of 128. + writeInt<uint8_t>(128, buf); + buf.insert(buf.end(), 16, 0x11); + + ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf)); + EXPECT_EQ(128, prefix.first.asUnsigned()); + EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111", + prefix.second.toText()); + + buf.clear(); + + // Prefix length is greater than 128. This should result in an + // error. + writeInt<uint8_t>(129, buf); + writeInt<uint16_t>(0x3000, buf); + buf.resize(17); + + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer truncated. Prefix length of 10 requires at least 2 bytes, + // but there is only one byte. + writeInt<uint8_t>(10, buf); + writeInt<uint8_t>(1, buf); + + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); +} + +// The purpose of this test is to verify that the variable length prefix +// is written to a buffer correctly. +TEST_F(OptionDataTypesTest, writePrefix) { + // Initialize a buffer and store some value in it. We'll want to make + // sure that the prefix being written will not override this value, but + // will rather be appended. + std::vector<uint8_t> buf(1, 1); + + // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because + // there are only 34 significant bits. All other bits must be zeroed. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34), + IOAddress("2001:db8:FFFF::"), + buf)); + ASSERT_EQ(7, buf.size()); + + EXPECT_EQ(1, static_cast<unsigned>(buf[0])); + EXPECT_EQ(34, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0x20, static_cast<unsigned>(buf[2])); + EXPECT_EQ(0x01, static_cast<unsigned>(buf[3])); + EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4])); + EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5])); + EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6])); + + buf.clear(); + + // Prefix length is 0. The entire prefix should be ignored. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0), + IOAddress("2001:db8:FFFF::"), + buf)); + ASSERT_EQ(1, buf.size()); + EXPECT_EQ(0, static_cast<unsigned>(buf[0])); + + buf.clear(); + + // Prefix having a maximum length of 128. + ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128), + IOAddress("2001:db8::FF"), + buf)); + + // We should now have a 17 bytes long buffer. 1 byte goes for a prefix + // length field, the remaining ones hold the prefix. + ASSERT_EQ(17, buf.size()); + // Because the prefix is 16 bytes long, we can simply use the + // IOAddress convenience function to read it back and compare + // it with the textual representation. This is simpler than + // comparing each byte separately. + IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]); + EXPECT_EQ("2001:db8::ff", prefix_read.toText()); + + buf.clear(); + + // It is illegal to use IPv4 address as prefix. + EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4), + IOAddress("10.0.0.1"), buf), + BadDataTypeCast); +} + +// The purpose of this test is to verify that the +// PSID-len/PSID tuple can be read from a buffer. +TEST_F(OptionDataTypesTest, readPsid) { + std::vector<uint8_t> buf; + + // PSID length is 6 (bits) + writeInt<uint8_t>(6, buf); + // 0xA400 is represented as 1010010000000000b, which is equivalent + // of portset 0x29 (101001b). + writeInt<uint16_t>(0xA400, buf); + + PSIDTuple psid; + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(6, psid.first.asUnsigned()); + EXPECT_EQ(0x29, psid.second.asUint16()); + + buf.clear(); + + // PSID length is 16 (bits) + writeInt<uint8_t>(16, buf); + // 0xF000 is represented as 1111000000000000b, which is equivalent + // of portset 0xF000. + writeInt<uint16_t>(0xF000, buf); + + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(16, psid.first.asUnsigned()); + EXPECT_EQ(0xF000, psid.second.asUint16()); + + buf.clear(); + + // PSID length is 0, in which case PSID should be ignored. + writeInt<uint8_t>(0, buf); + // Let's put some junk into the PSID field to make sure it will + // be ignored. + writeInt<uint16_t>(0x1234, buf); + ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf)); + EXPECT_EQ(0, psid.first.asUnsigned()); + EXPECT_EQ(0, psid.second.asUint16()); + + buf.clear(); + + // PSID length greater than 16 is not allowed. + writeInt<uint8_t>(17, buf); + writeInt<uint16_t>(0, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // PSID length is 3 bits, but the PSID value is 11 (1011b), so it + // is encoded on 4 bits, rather than 3. + writeInt<uint8_t>(3, buf); + writeInt<uint16_t>(0xB000, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer is truncated - 2 bytes instead of 3. + writeInt<uint8_t>(4, buf); + writeInt<uint8_t>(0xF0, buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + // Check for out of range values. + for (int i = 1; i < 16; ++i) { + buf.clear(); + writeInt<uint8_t>(i, buf); + writeInt<uint16_t>(0xFFFF << (15 - i), buf); + EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + } + +} + +// The purpose of this test is to verify that the PSID-len/PSID +// tuple is written to a buffer correctly. +TEST_F(OptionDataTypesTest, writePsid) { + // Let's create a buffer with some data in it. We want to make + // sure that the existing data remain untouched when we write + // PSID to the buffer. + std::vector<uint8_t> buf(1, 1); + // PSID length is 4 (bits), PSID value is 8. + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf)); + ASSERT_EQ(4, buf.size()); + // The byte which existed in the buffer should still hold the + // same value. + EXPECT_EQ(1, static_cast<unsigned>(buf[0])); + // PSID length should be written as specified in the function call. + EXPECT_EQ(4, static_cast<unsigned>(buf[1])); + // The PSID structure is as follows: + // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code + // the PSID. "P" are zero padded bits. The PSID value 8 is coded + // on four useful bits as '1000b'. That means that the PSID value + // encoded in the PSID field is: '1000000000000000b', which is + // 0x8000. The next two EXPECT_EQ statements verify that. + EXPECT_EQ(0x80, static_cast<unsigned>(buf[2])); + EXPECT_EQ(0x00, static_cast<unsigned>(buf[3])); + + // Clear the buffer to make sure we don't append to the + // existing data. + buf.clear(); + + // The PSID length of 0 causes the PSID value (of 6) to be ignored. + // As a result, the buffer should hold only zeros. + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf)); + ASSERT_EQ(3, buf.size()); + EXPECT_EQ(0, static_cast<unsigned>(buf[0])); + EXPECT_EQ(0, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0, static_cast<unsigned>(buf[2])); + + buf.clear(); + + // Another test case, to verify that we can use the maximum length + // of PSID (16 bits). + ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf)); + ASSERT_EQ(3, buf.size()); + // PSID length should be written with no change. + EXPECT_EQ(16, static_cast<unsigned>(buf[0])); + // Check PSID value. + EXPECT_EQ(0x00, static_cast<unsigned>(buf[1])); + EXPECT_EQ(0x05, static_cast<unsigned>(buf[2])); + + // PSID length of 17 exceeds the maximum allowed value of 16. + EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf), + OutOfRange); + + // Check for out of range values. + for (int i = 1; i < 16; ++i) { + EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(i), PSID(1 << i), buf), + BadDataTypeCast); + } +} + +// The purpose of this test is to verify that the string +// can be read from a buffer correctly. +TEST_F(OptionDataTypesTest, readString) { + + // Prepare a buffer with some string in it. + std::vector<uint8_t> buf; + writeString("hello world", buf); + + // Read the string from the buffer. + std::string value; + ASSERT_NO_THROW( + value = OptionDataTypeUtil::readString(buf); + ); + // Check that it is valid. + EXPECT_EQ("hello world", value); + + // Only nulls should throw. + OptionBuffer buffer = { 0, 0 }; + ASSERT_THROW(OptionDataTypeUtil::readString(buffer), isc::OutOfRange); + + // One trailing null should trim off. + buffer = {'o', 'n', 'e', 0 }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(3, value.length()); + EXPECT_EQ(value, std::string("one")); + + // More than one trailing null should trim off. + buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(5, value.length()); + EXPECT_EQ(value, std::string("three")); + + // Embedded null should be left in place. + buffer = { 'e', 'm', 0, 'b', 'e', 'd' }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(6, value.length()); + EXPECT_EQ(value, (std::string{"em\0bed", 6})); + + // Leading null should be left in place. + buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' }; + ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer)); + EXPECT_EQ(8, value.length()); + EXPECT_EQ(value, (std::string{"\0leading", 8})); +} + +// The purpose of this test is to verify that a string can be +// stored in a buffer correctly. +TEST_F(OptionDataTypesTest, writeString) { + // Prepare a buffer with a reference data. + std::vector<uint8_t> buf_ref; + writeString("hello world!", buf_ref); + // Create empty buffer we will write to. + std::vector<uint8_t> buf; + ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf)); + // Compare two buffers. + ASSERT_EQ(buf_ref.size(), buf.size()); + EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin())); +} + +} // anonymous namespace |