summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/tests/protocol_util_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcp/tests/protocol_util_unittest.cc
parentInitial commit. (diff)
downloadisc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz
isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcp/tests/protocol_util_unittest.cc')
-rw-r--r--src/lib/dhcp/tests/protocol_util_unittest.cc677
1 files changed, 677 insertions, 0 deletions
diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc
new file mode 100644
index 0000000..55a2221
--- /dev/null
+++ b/src/lib/dhcp/tests/protocol_util_unittest.cc
@@ -0,0 +1,677 @@
+// Copyright (C) 2013-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+ /*/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+};*/
+
+/// The purpose of this test is to verify that the IP header checksum
+/// is calculated correctly.
+TEST(ProtocolUtilTest, checksum) {
+ // IPv4 header to be used to calculate checksum.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ 0x06, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xac, 0x10, 0x0a, 0x63, // Source IP address.
+ 0xac, 0x10, 0x0a, 0x0c // Destination IP address.
+ };
+ // Calculate size of the header array.
+ const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]);
+ // Get the actual checksum.
+ uint16_t chksum = ~calcChecksum(hdr, hdr_size);
+ // The 0xb1e6 value has been calculated by other means.
+ EXPECT_EQ(0xb1e6, chksum);
+ // Tested function may also take the initial value of the sum.
+ // Let's set it to 2 and see whether it is included in the
+ // calculation.
+ chksum = ~calcChecksum(hdr, hdr_size, 2);
+ // The checksum value should change.
+ EXPECT_EQ(0xb1e4, chksum);
+}
+
+// The purpose of this test is to verify that the Ethernet frame header
+// can be decoded correctly. In particular it verifies that the source
+// HW address can be extracted from it.
+TEST(ProtocolUtilTest, decodeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Prepare a buffer holding Ethernet frame header and 4 bytes of
+ // dummy data.
+ OutputBuffer buf(1);
+ buf.writeData(dest_hw_addr, sizeof(dest_hw_addr));
+ buf.writeData(src_hw_addr, sizeof(src_hw_addr));
+ buf.writeUint16(ETHERNET_TYPE_IP);
+ // Append dummy data. We will later check that this data is not
+ // removed or corrupted when reading the ethernet header.
+ buf.writeUint32(0x01020304);
+
+ // Create a buffer with truncated ethernet frame header..
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6);
+ // But provide valid packet object to make sure that the function
+ // under test does not throw due to NULL pointer packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because header data is truncated.
+ EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt),
+ InvalidPacketHeader);
+
+ // Get not truncated buffer.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // But provide NULL packet object instead.
+ pkt.reset();
+ // It should throw again but a different exception.
+ EXPECT_THROW(decodeEthernetHeader(in_buf, pkt),
+ BadValue);
+ // Now provide, correct data.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ // It should not throw now.
+ ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt));
+ // Verify that the destination HW address has been initialized...
+ HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(checked_dest_hwaddr);
+ // and is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_);
+ ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr),
+ checked_dest_hwaddr->hwaddr_.begin()));
+
+ // Verify that the HW address of the source has been initialized.
+ HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(checked_src_hwaddr);
+ // And that it is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_);
+ ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr),
+ checked_src_hwaddr->hwaddr_.begin()));
+
+ // The entire ethernet packet header should have been read. This means
+ // that the internal buffer pointer should now point to its tail.
+ ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition());
+ // And the dummy data should be still readable and correct.
+ uint32_t dummy_data = in_buf.readUint32();
+ EXPECT_EQ(0x01020304, dummy_data);
+}
+
+/// The purpose of this test is to verify that the IP and UDP header
+/// is decoded correctly and appropriate values of IP addresses and
+/// ports are assigned to a Pkt4 object.
+TEST(ProtocolUtilTest, decodeIpUdpHeader) {
+ // IPv4 header to be parsed.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ IPPROTO_UDP, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xc0, 0x00, 0x02, 0x63, // Source IP address.
+ 0xc0, 0x00, 0x02, 0x0c, // Destination IP address.
+ 0x27, 0x54, // Source port
+ 0x27, 0x53, // Destination port
+ 0x00, 0x08, // UDP length
+ 0x00, 0x00 // Checksum
+ };
+
+ // Write header data to the buffer.
+ OutputBuffer buf(1);
+ buf.writeData(hdr, sizeof(hdr));
+ // Append some dummy data.
+ buf.writeUint32(0x01020304);
+
+ // Create an input buffer holding truncated headers.
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10);
+ // Create non NULL Pkt4 object to make sure that the function under
+ // test does not throw due to invalid Pkt4 object.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because buffer holds truncated data.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader);
+
+ // Create a valid input buffer (not truncated).
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // Set NULL Pkt4 object to verify that function under test will
+ // return exception as expected.
+ pkt.reset();
+ // And check whether it throws exception.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue);
+
+ // Now, let's provide valid arguments and make sure it doesn't throw.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ ASSERT_TRUE(pkt);
+ EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt));
+
+ // Verify the source address and port.
+ EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText());
+ EXPECT_EQ(10068, pkt->getRemotePort());
+
+ // Verify the destination address and port.
+ EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText());
+ EXPECT_EQ(10067, pkt->getLocalPort());
+
+ // Verify that the dummy data has not been corrupted and that the
+ // internal read pointer has been moved to the tail of the UDP
+ // header.
+ ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition());
+ EXPECT_EQ(0x01020304, in_buf.readUint32());
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses.
+TEST(ProtocolUtilTest, writeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+
+ // Set invalid length (7) of the hw address. Fill it with
+ // values of 1.
+ std::vector<uint8_t> invalid_length_addr(7, 1);
+ HWAddrPtr remote_hw_addr(new HWAddr(invalid_length_addr, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+ // HW address is too long, so it should fail.
+ EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue);
+
+ // Finally, set a valid HW address.
+ remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise valid destination
+ // HW address. Instead of using memory comparison functions
+ // we check bytes one-by-one. In case of mismatch we will
+ // get exact values that are mismatched. If memcmp was used
+ // the error message would not indicate the values of
+ // mismatched bytes.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(dest_hw_addr[i], buf[i]);
+ }
+
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses with the broadcast flag set.
+TEST(ProtocolUtilTest, writeEthernetHeaderBroadcast) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+ HWAddrPtr remote_hw_addr(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Set the broadcast flags.
+ pkt->setFlags(pkt->getFlags() | Pkt4::FLAG_BROADCAST_MASK);
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise broadcast destination
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(255, buf[i]);
+ }
+
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses with the broadcast flag set but the packet
+/// was relayed.
+TEST(ProtocolUtilTest, writeEthernetHeaderBroadcastRelayed) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+ HWAddrPtr remote_hw_addr(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Set the broadcast flags.
+ pkt->setFlags(pkt->getFlags() | Pkt4::FLAG_BROADCAST_MASK);
+
+ // Set a gateway address: the broadcast flag is now for
+ // the relay, no longer for the server.
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise valid destination
+ // HW address. Instead of using memory comparison functions
+ // we check bytes one-by-one. In case of mismatch we will
+ // get exact values that are mismatched. If memcmp was used
+ // the error message would not indicate the values of
+ // mismatched bytes.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(dest_hw_addr[i], buf[i]);
+ }
+
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+TEST(ProtocolUtilTest, writeIpUdpHeader) {
+ // Create DHCPv4 packet. Some values held by this object are
+ // used by the utility function under test to figure out the
+ // contents of the IP and UDP headers, e.g. source and
+ // destination IP address or port number.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set local and remote address and port.
+ pkt->setLocalAddr(IOAddress("192.0.2.1"));
+ pkt->setRemoteAddr(IOAddress("192.0.2.111"));
+ pkt->setLocalPort(DHCP4_SERVER_PORT);
+ pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+ // Pack the contents of the packet.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Create output buffer. The headers will be written to it.
+ OutputBuffer buf(1);
+ // Write some dummy data to the buffer. We will check that the
+ // function under test appends to this data, not overrides it.
+ buf.writeUint16(0x0102);
+
+ // Write both IP and UDP headers.
+ writeIpUdpHeader(pkt, buf);
+
+ // The resulting size of the buffer must be 30. The 28 bytes are
+ // consumed by the IP and UDP headers. The other 2 bytes are dummy
+ // data at the beginning of the buffer.
+ ASSERT_EQ(30, buf.getLength());
+
+ // Make sure that the existing data in the buffer was not corrupted
+ // by the function under test.
+ EXPECT_EQ(0x01, buf[0]);
+ EXPECT_EQ(0x02, buf[1]);
+
+ // Copy the contents of the buffer to InputBuffer object. This object
+ // exposes convenient functions for reading.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+
+ // Check dummy data.
+ uint16_t dummy_data = in_buf.readUint16();
+ EXPECT_EQ(0x0102, dummy_data);
+
+ // The IP version and IHL are stored in the same octet (4 bits each).
+ uint8_t ver_len = in_buf.readUint8();
+ // The most significant bits define IP version.
+ uint8_t ip_ver = ver_len >> 4;
+ EXPECT_EQ(4, ip_ver);
+ // The least significant bits define header length (in 32-bits chunks).
+ uint8_t ip_len = ver_len & 0x0F;
+ EXPECT_EQ(5, ip_len);
+
+ // Get Differentiated Services Codepoint and Explicit Congestion
+ // Notification field.
+ uint8_t dscp_ecn = in_buf.readUint8();
+ EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn);
+
+ // Total length of IP packet. Includes UDP header and payload.
+ uint16_t total_len = in_buf.readUint16();
+ EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len);
+
+ // Identification field.
+ uint16_t ident = in_buf.readUint16();
+ EXPECT_EQ(0, ident);
+
+ // Fragmentation.
+ uint16_t fragment = in_buf.readUint16();
+ // Setting second most significant bit means no fragmentation.
+ EXPECT_EQ(0x4000, fragment);
+
+ // Get TTL
+ uint8_t ttl = in_buf.readUint8();
+ // Expect non-zero TTL.
+ EXPECT_GE(ttl, 1);
+
+ // Protocol type is UDP.
+ uint8_t proto = in_buf.readUint8();
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP), proto);
+
+ // Check that the checksum is correct. The reference checksum value
+ // has been calculated manually.
+ uint16_t ip_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x755c, ip_checksum);
+
+ // Validate source address.
+ // Initializing it to IPv6 address guarantees that it is not initialized
+ // to the value that we expect to be read from a header since the value
+ // read from a header will be IPv4.
+ IOAddress src_addr("::1");
+ // Read src address as an array of bytes because it is easily convertible
+ // to IOAddress object.
+ uint8_t src_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(src_addr_data, 4);
+ src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.1"), src_addr);
+
+ // Validate destination address.
+ IOAddress dest_addr("::1");
+ uint8_t dest_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(dest_addr_data, 4);
+ dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.111"), dest_addr);
+
+ // UDP header starts here.
+
+ // Check source port.
+ uint16_t src_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getLocalPort(), src_port);
+
+ // Check destination port.
+ uint16_t dest_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getRemotePort(), dest_port);
+
+ // UDP header and data length.
+ uint16_t udp_len = in_buf.readUint16();
+ EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len);
+
+ // Verify UDP checksum. The reference checksum has been calculated manually.
+ uint16_t udp_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x8817, udp_checksum);
+}
+
+/// Test that checks the RAII implementation of ScopedEnableOptionsCopy works
+/// as expected, restoring the copy retrieve options flag.
+TEST(ScopedEnableOptionsCopy, enableOptionsCopy) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543));
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ pkt->addOption(option);
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ {
+ ScopedEnableOptionsCopy<Pkt4> oc(pkt);
+ ASSERT_TRUE(pkt->isCopyRetrievedOptions());
+ OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME);
+ ASSERT_NE(option, option_copy);
+ option = option_copy;
+ }
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ {
+ try {
+ ScopedEnableOptionsCopy<Pkt4> oc(pkt);
+ ASSERT_TRUE(pkt->isCopyRetrievedOptions());
+ OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME);
+ ASSERT_NE(option, option_copy);
+ option = option_copy;
+ throw 0;
+ } catch (...) {
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ }
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ }
+}
+
+/// Test that checks the RAII implementation of ScopedPkt4OptionsCopy works
+/// as expected, restoring the initial Pkt4 options.
+TEST(ScopedOptionsCopy, pkt4OptionsCopy) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543));
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ pkt->addOption(option);
+ OptionCollection options = pkt->options_;
+ size_t count = options.size();
+ ASSERT_NE(0, count);
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ std::string expected = pkt->toText();
+ pkt->pack();
+ OutputBuffer buf = pkt->getBuffer();
+ {
+ ScopedPkt4OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME));
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option);
+ {
+ try {
+ ScopedPkt4OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME));
+ throw 0;
+ } catch (...) {
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+}
+
+/// Test that checks the RAII implementation of ScopedPkt6OptionsCopy works
+/// as expected, restoring the initial Pkt6 options.
+TEST(ScopedOptionsCopy, pkt6OptionsCopy) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 2543));
+ OptionPtr option = Option::create(Option::V6, D6O_BOOTFILE_URL);
+ pkt->addOption(option);
+ OptionCollection options = pkt->options_;
+ size_t count = options.size();
+ ASSERT_NE(0, count);
+ ASSERT_EQ(option, pkt->getOption(D6O_BOOTFILE_URL));
+ std::string expected = pkt->toText();
+ pkt->pack();
+ OutputBuffer buf = pkt->getBuffer();
+ {
+ ScopedPkt6OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(D6O_BOOTFILE_URL));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(D6O_BOOTFILE_URL);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL));
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option);
+ {
+ try {
+ ScopedPkt6OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(D6O_BOOTFILE_URL));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(D6O_BOOTFILE_URL);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL));
+ throw 0;
+ } catch (...) {
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option);
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option);
+ }
+}
+
+/// Test that checks the RAII implementation of ScopedSubOptionsCopy works
+/// as expected, restoring the initial option suboptions.
+TEST(ScopedOptionsCopy, subOptionsCopy) {
+ OptionPtr initial = Option::create(Option::V4, 231);
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ initial->addOption(option);
+ OptionCollection options = initial->getOptions();
+ size_t count = options.size();
+ ASSERT_NE(0, count);
+ ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME));
+ {
+ ScopedSubOptionsCopy oc(initial);
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME));
+ initial->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(initial->getOptions().size(), count - 1);
+ ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME));
+ }
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option);
+ {
+ try {
+ ScopedSubOptionsCopy oc(initial);
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME));
+ initial->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(initial->getOptions().size(), count - 1);
+ ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME));
+ throw 0;
+ } catch (...) {
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+}
+
+} // anonymous namespace