// Copyright (C) 2015-2017 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc; using namespace isc::dhcp; using namespace isc::dhcp::test; using namespace isc::util; namespace { /// @brief Name of the file holding DUID generated during a test. const std::string DEFAULT_DUID_FILE = "duid-factory-test.duid"; /// @brief Test fixture class for @c DUIDFactory. class DUIDFactoryTest : public ::testing::Test { public: /// @brief Constructor. /// /// Creates fake interface configuration. It also creates an instance /// of the @c DUIDFactory object used throughout the tests. DUIDFactoryTest(); /// @brief Destructor. virtual ~DUIDFactoryTest(); /// @brief Returns absolute path to a test DUID storage. /// /// @param duid_file_name Name of the file holding test DUID. std::string absolutePath(const std::string& duid_file_name) const; /// @brief Removes default DUID file used in the tests. /// /// This method is called from both constructor and destructor. void removeDefaultFile() const; /// @brief Returns contents of the DUID file. std::string readDefaultFile() const; /// @brief Converts string of hexadecimal digits to vector. /// /// @param hex String representation. /// @return Vector created from the converted string. std::vector toVector(const std::string& hex) const; /// @brief Converts vector to string of hexadecimal digits. /// /// @param vec Input vector. /// @return String of hexadecimal digits converted from vector. std::string toString(const std::vector& vec) const; /// @brief Converts current time to a string of hexadecimal digits. /// /// @return Time represented as text. std::string timeAsHexString() const; /// @brief Tests creation of a DUID-LLT. /// /// @param expected_htype Expected link layer type as string. /// @param expected_time Expected time as string. /// @param time_equal Indicates if @c expected time should be /// compared for equality with the time being part of a DUID /// (if true), or the time being part of the DUID should be /// less or equal current time (if false). /// @param expected_hwaddr Expected link layer type as string. void testLLT(const std::string& expected_htype, const std::string& expected_time, const bool time_equal, const std::string& expected_hwaddr); /// @brief Tests creation of a DUID-LLT. /// /// @param expected_htype Expected link layer type as string. /// @param expected_time Expected time as string. /// @param time_equal Indicates if @c expected time should be /// compared for equality with the time being part of a DUID /// (if true), or the time being part of the DUID should be /// less or equal current time (if false). /// @param expected_hwaddr Expected link layer type as string. /// @param factory_ref Reference to DUID factory. void testLLT(const std::string& expected_htype, const std::string& expected_time, const bool time_equal, const std::string& expected_hwaddr, DUIDFactory& factory_ref); /// @brief Tests creation of a DUID-EN. /// /// @param expected_enterprise_id Expected enterprise id as string. /// @param expected_identifier Expected variable length identifier /// as string. If empty string specified the test method only checks /// that generated identifier consists of some random values. void testEN(const std::string& expected_enterprise_id, const std::string& expected_identifier = ""); /// @brief Tests creation of a DUID-EN. /// /// @param expected_enterprise_id Expected enterprise id as string. /// @param expected_identifier Expected variable length identifier /// as string. If empty string specified the test method only checks /// that generated identifier consists of some random values. /// @param factory_ref Reference to DUID factory. void testEN(const std::string& expected_enterprise_id, const std::string& expected_identifier, DUIDFactory& factory_ref); /// @brief Tests creation of a DUID-LL. /// /// @param expected_htype Expected link layer type as string. /// @param expected_hwaddr Expected link layer type as string. void testLL(const std::string& expected_htype, const std::string& expected_hwaddr); /// @brief Tests creation of a DUID-LL. /// /// @param expected_htype Expected link layer type as string. /// @param expected_hwaddr Expected link layer type as string. /// @param factory_ref Reference to DUID factory. void testLL(const std::string& expected_htype, const std::string& expected_hwaddr, DUIDFactory& factory_ref); /// @brief Returns reference to a default factory. DUIDFactory& factory() { return (factory_); } private: /// @brief Creates fake interface configuration. IfaceMgrTestConfig iface_mgr_test_config_; /// @brief Holds default instance of the @c DUIDFactory class, being /// used throughout the tests. DUIDFactory factory_; }; DUIDFactoryTest::DUIDFactoryTest() : iface_mgr_test_config_(true), factory_(absolutePath(DEFAULT_DUID_FILE)) { removeDefaultFile(); } DUIDFactoryTest::~DUIDFactoryTest() { removeDefaultFile(); } std::string DUIDFactoryTest::absolutePath(const std::string& duid_file_name) const { std::ostringstream s; s << TEST_DATA_BUILDDIR << "/" << duid_file_name; return (s.str()); } void DUIDFactoryTest::removeDefaultFile() const { static_cast(remove(absolutePath(DEFAULT_DUID_FILE).c_str())); } std::string DUIDFactoryTest::readDefaultFile() const { return (isc::test::readFile(absolutePath(DEFAULT_DUID_FILE))); } std::vector DUIDFactoryTest::toVector(const std::string& hex) const { std::vector vec; try { util::encode::decodeHex(hex, vec); } catch (...) { ADD_FAILURE() << "toVector: the following string " << hex << " is not a valid hex string"; } return (vec); } std::string DUIDFactoryTest::toString(const std::vector& vec) const { try { return (util::encode::encodeHex(vec)); } catch (...) { ADD_FAILURE() << "toString: unable to encode vector to" " hexadecimal string"; } return (""); } std::string DUIDFactoryTest::timeAsHexString() const { time_t current_time = time(NULL) - DUID_TIME_EPOCH; std::ostringstream s; s << std::hex << std::setw(8) << std::setfill('0') << current_time; return (boost::to_upper_copy(s.str())); } void DUIDFactoryTest::testLLT(const std::string& expected_htype, const std::string& expected_time, const bool time_equal, const std::string& expected_hwaddr) { testLLT(expected_htype, expected_time, time_equal, expected_hwaddr, factory()); } void DUIDFactoryTest::testLLT(const std::string& expected_htype, const std::string& expected_time, const bool time_equal, const std::string& expected_hwaddr, DUIDFactory& factory_ref) { DuidPtr duid = factory_ref.get(); ASSERT_TRUE(duid); ASSERT_GE(duid->getDuid().size(), 14); std::string duid_text = toString(duid->getDuid()); // DUID type LLT EXPECT_EQ("0001", duid_text.substr(0, 4)); // Link layer type HTYPE_ETHER EXPECT_EQ(expected_htype, duid_text.substr(4, 4)); // Verify if time is correct. if (time_equal) { // Strict time check. EXPECT_EQ(expected_time, duid_text.substr(8, 8)); } else { // Timestamp equal or less current time. EXPECT_LE(duid_text.substr(8, 8), expected_time); } // MAC address of the interface. EXPECT_EQ(expected_hwaddr, duid_text.substr(16)); // Compare DUID with the one stored in the file. EXPECT_EQ(duid->toText(), readDefaultFile()); } void DUIDFactoryTest::testEN(const std::string& expected_enterprise_id, const std::string& expected_identifier) { testEN(expected_enterprise_id, expected_identifier, factory()); } void DUIDFactoryTest::testEN(const std::string& expected_enterprise_id, const std::string& expected_identifier, DUIDFactory& factory_ref) { DuidPtr duid = factory_ref.get(); ASSERT_TRUE(duid); ASSERT_GE(duid->getDuid().size(), 8); std::string duid_text = toString(duid->getDuid()); // DUID type EN. EXPECT_EQ("0002", duid_text.substr(0, 4)); // Verify enterprise ID. EXPECT_EQ(expected_enterprise_id, duid_text.substr(4, 8)); // If no expected identifier, we should at least check that the // generated identifier contains some random non-zero digits. if (expected_identifier.empty()) { EXPECT_FALSE(isRangeZero(duid->getDuid().begin(), duid->getDuid().end())); } else { // Check if identifier matches. EXPECT_EQ(expected_identifier, duid_text.substr(12)); } // Compare DUID with the one stored in the file. EXPECT_EQ(duid->toText(), readDefaultFile()); } void DUIDFactoryTest::testLL(const std::string& expected_htype, const std::string& expected_hwaddr) { testLL(expected_htype, expected_hwaddr, factory()); } void DUIDFactoryTest::testLL(const std::string& expected_htype, const std::string& expected_hwaddr, DUIDFactory& factory_ref) { DuidPtr duid = factory_ref.get(); ASSERT_TRUE(duid); ASSERT_GE(duid->getDuid().size(), 8); std::string duid_text = toString(duid->getDuid()); // DUID type LL EXPECT_EQ("0003", duid_text.substr(0, 4)); // Link layer type. EXPECT_EQ(expected_htype, duid_text.substr(4, 4)); // MAC address of the interface. EXPECT_EQ(expected_hwaddr, duid_text.substr(8)); // Compare DUID with the one stored in the file. EXPECT_EQ(duid->toText(), readDefaultFile()); } // This test verifies that the factory class will generate the entire // DUID-LLT if there are no explicit values specified for the // time, link layer type and link layer address. TEST_F(DUIDFactoryTest, createLLT) { // Use 0 values for time and link layer type and empty vector for // the link layer address. The createLLT function will need to // use current time, HTYPE_ETHER and MAC address of one of the // interfaces. ASSERT_NO_THROW(factory().createLLT(0, 0, std::vector())); testLLT("0001", timeAsHexString(), false, "080808080808"); } // This test verifies that the factory class creates a DUID-LLT from // the explicitly specified time, when link layer type and address are // generated. TEST_F(DUIDFactoryTest, createLLTExplicitTime) { ASSERT_NO_THROW(factory().createLLT(0, 0xABCDEF, std::vector())); testLLT("0001", "00ABCDEF", true, "080808080808"); } // This test verifies that the factory class creates DUID-LLT with // the link layer type of the interface which link layer address // is used to generate the DUID. TEST_F(DUIDFactoryTest, createLLTExplicitHtype) { ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0, std::vector())); testLLT("0001", timeAsHexString(), false, "080808080808"); } // This test verifies that the factory class creates DUID-LLT from // explicitly specified link layer address, when other parameters // are generated. TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) { ASSERT_NO_THROW(factory().createLLT(0, 0, toVector("121212121212"))); testLLT("0001", timeAsHexString(), false, "121212121212"); } // This test verifies that the factory function creates DUID-LLT from // all values explicitly specified. TEST_F(DUIDFactoryTest, createLLTAllExplicitParameters) { ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA, toVector("24242424242424242424"))); testLLT("0008", "FAFAFAFA", true, "24242424242424242424"); } // This test verifies that the createLLT function will try to reuse existing // DUID for the non-explicitly specified values. TEST_F(DUIDFactoryTest, createLLTReuse) { // Create DUID-LLT and store it in a file. ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA, toVector("242424242424"))); // Create another factory class, which uses the same file. DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); // Create DUID-LLT without specifying hardware type, time and // link layer address. The factory function should use the // values in the existing DUID. ASSERT_NO_THROW(factory2.createLLT(0, 0, std::vector())); testLLT("0008", "FAFAFAFA", true, "242424242424", factory2); // Try to reuse only a time value. DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory3.createLLT(HTYPE_ETHER, 0, toVector("121212121212"))); testLLT("0001", "FAFAFAFA", true, "121212121212", factory3); // Reuse only a hardware type. DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory4.createLLT(0, 0x23432343, toVector("455445544554"))); testLLT("0001", "23432343", true, "455445544554", factory4); // Reuse link layer address. Note that in this case the hardware // type is set to the type of the interface from which hardware // address is obtained and the explicit value is ignored. DUIDFactory factory5(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory5.createLLT(HTYPE_FDDI, 0x11111111, std::vector())); testLLT("0001", "11111111", true, "455445544554", factory5); } // This test verifies that the DUID-EN can be generated entirely. Such // generated DUID contains ISC enterprise id and the random identifier. TEST_F(DUIDFactoryTest, createEN) { ASSERT_NO_THROW(factory().createEN(0, std::vector())); testEN("000009BF"); } // This test verifies that the DUID-EN may contain custom enterprise id. TEST_F(DUIDFactoryTest, createENExplicitEnterpriseId) { ASSERT_NO_THROW(factory().createEN(0xABCDEFAB, std::vector())); testEN("ABCDEFAB"); } // This test verifies that DUID-EN may contain custom variable length // identifier and default enterprise id. TEST_F(DUIDFactoryTest, createENExplicitIdentifier) { ASSERT_NO_THROW(factory().createEN(0, toVector("1212121212121212"))); testEN("000009BF", "1212121212121212"); } // This test verifies that DUID-EN can be created from explicit enterprise id // and identifier. TEST_F(DUIDFactoryTest, createENAllExplicitParameters) { ASSERT_NO_THROW(factory().createEN(0x01020304, toVector("ABCD"))); testEN("01020304", "ABCD"); } // This test verifies that the createEN function will try to reuse existing // DUID for the non-explicitly specified values. TEST_F(DUIDFactoryTest, createENReuse) { // Create DUID-EN and store it in a file. ASSERT_NO_THROW(factory().createEN(0xFAFAFAFA, toVector("242424242424"))); // Create another factory class, which uses the same file. DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory2.createEN(0, std::vector())); testEN("FAFAFAFA", "242424242424", factory2); // Reuse only enterprise id. DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory3.createEN(0, toVector("121212121212"))); testEN("FAFAFAFA", "121212121212", factory3); // Reuse only variable length identifier. DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory4.createEN(0x1234, std::vector())); testEN("00001234", "121212121212", factory4); } // This test verifies that the DUID-LL is generated when neither link layer // type nor address is specified. TEST_F(DUIDFactoryTest, createLL) { ASSERT_NO_THROW(factory().createLL(0, std::vector())); testLL("0001", "080808080808"); } // This test verifies that the DUID-LL is generated and the link layer type // used is taken from the interface used to generate link layer address. TEST_F(DUIDFactoryTest, createLLExplicitHtype) { ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, std::vector())); testLL("0001", "080808080808"); } // This test verifies that DUID-LL is created from explicitly provided // link layer type and address. TEST_F(DUIDFactoryTest, createLLAllExplicitParameters) { ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424"))); testLL("0008", "242424242424"); } // This test verifies that DUID-LLT is created when caller wants to obtain // it and it doesn't exist. TEST_F(DUIDFactoryTest, createLLTIfNotExists) { DuidPtr duid; ASSERT_NO_THROW(duid = factory().get()); ASSERT_TRUE(duid); EXPECT_EQ(DUID::DUID_LLT, duid->getType()); } // This test verifies that DUID-EN when there is no suitable interface to // use to create DUID-LLT. TEST_F(DUIDFactoryTest, createENIfNotExists) { // Remove interfaces. The DUID-LLT is a default type but it requires // that an interface with a suitable link-layer address is present // in the system. By removing the interfaces we cause the factory // to fail to generate DUID-LLT. It should fall back to DUID-EN. IfaceMgr::instance().clearIfaces(); DuidPtr duid; ASSERT_NO_THROW(duid = factory().get()); ASSERT_TRUE(duid); EXPECT_EQ(DUID::DUID_EN, duid->getType()); } // This test verifies that the createLL function will try to reuse existing // DUID for the non-explicitly specified values. TEST_F(DUIDFactoryTest, createLLReuse) { // Create DUID-EN and store it in a file. ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424"))); // Create another factory class, which uses the same file. DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE)); // Create DUID-LL without specifying hardware type, time and // link layer address. The factory function should use the // values in the existing DUID. ASSERT_NO_THROW(factory2.createLL(0, std::vector())); testLL("0008", "242424242424", factory2); // Reuse only hardware type DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory3.createLL(0, toVector("121212121212"))); testLL("0008", "121212121212", factory3); // Reuse link layer address. Note that when the link layer address is // reused, the explicit value of hardware type is reused too and the // explicit value of hardware type is ignored. DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE)); ASSERT_NO_THROW(factory4.createLL(HTYPE_ETHER, std::vector())); testLL("0008", "121212121212", factory4); } // This test verifies that it is possible to override a DUID. TEST_F(DUIDFactoryTest, override) { // Create default DUID-LLT. ASSERT_NO_THROW(static_cast(factory().get())); testLLT("0001", timeAsHexString(), false, "080808080808"); ASSERT_NO_THROW(factory().createEN(0, toVector("12131415"))); testEN("000009BF", "12131415"); } } // End anonymous namespace