// Copyright (C) 2016-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 #include #include #include using namespace isc; using namespace isc::data; using namespace isc::asiolink; using isc::dhcp::DhcpConfigError; /// This list defines required keywords. const SimpleRequiredKeywords REQUIRED_KEYWORDS = { "foobar" }; /// This table defines keywords and types. const SimpleKeywords KEYWORDS = { { "id", Element::integer }, { "prefix", Element::string }, { "map", Element::map }, { "any", Element::any } }; /// This table defines sample default values. Although these are DHCPv6 /// specific, the mechanism is generic and can be used by any other component. const SimpleDefaults SAMPLE_DEFAULTS = { { "renew-timer", Element::integer, "900" }, { "rebind-timer", Element::integer, "1800" }, { "preferred-lifetime", Element::integer, "3600" }, { "valid-lifetime", Element::integer, "7200" } }; /// This list defines parameters that can be inherited from one scope /// to another. Although these are DHCPv6 specific, the mechanism is generic and /// can be used by any other component. const ParamsList SAMPLE_INHERITS = { "renew-timer", "rebind-timer", "preferred-lifetime", "valid-lifetime" }; /// @brief Simple Parser test fixture class class SimpleParserTest : public ::testing::Test { public: /// @brief Checks if specified map has an integer parameter with expected value /// /// @param map map to be checked /// @param param_name name of the parameter to be checked /// @param exp_value expected value of the parameter. void checkIntegerValue(const ConstElementPtr& map, const std::string& param_name, int64_t exp_value) { // First check if the passed element is a map. ASSERT_EQ(Element::map, map->getType()); // Now try to get the element being checked ConstElementPtr elem = map->get(param_name); ASSERT_TRUE(elem); // Now check if it's indeed integer ASSERT_EQ(Element::integer, elem->getType()); // Finally, check if its value meets expectation. EXPECT_EQ(exp_value, elem->intValue()); } }; class SimpleParserClassTest : public SimpleParser { public: /// @brief Instantiation of getAndConvert /// /// @param scope specified parameter will be extracted from this scope /// @param name name of the parameter for error report /// @return a bool value bool getAsBool(ConstElementPtr scope, const std::string& name) { return (getAndConvert(scope, name, "boolean")); } /// @brief Convert to boolean /// /// @param str the string "false" or "true" /// @return false for "false" and true for "true" /// @thrown isc::OutOfRange if not "false" or "true' static bool toBool(const std::string& str) { if (str == "false") { return (false); } else if (str == "true") { return (true); } else { isc_throw(TypeError, "not a boolean: " << str); } } }; // This test checks if the checkRequired method works as expected. TEST_F(SimpleParserTest, checkRequired) { ConstElementPtr empty = Element::fromJSON("{ }"); EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, empty), DhcpConfigError); ConstElementPtr other = Element::fromJSON("{ \"foo\": 1, \"bar\": 2 }"); EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, other), DhcpConfigError); ConstElementPtr good = Element::fromJSON("{ \"foobar\": 2 }"); EXPECT_NO_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, good)); } // This test checks if the checkKeywords method works as expected. TEST_F(SimpleParserTest, checkKeywords) { ConstElementPtr empty = Element::fromJSON("{ }"); EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, empty)); ConstElementPtr id = Element::fromJSON("{ \"id\": 1 }"); EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, id)); ConstElementPtr any = Element::fromJSON("{ \"any\": 1 }"); EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, any)); ConstElementPtr bad_id = Element::fromJSON("{ \"id\": true }"); EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_id), DhcpConfigError); ConstElementPtr bad_prefix = Element::fromJSON("{ \"prefix\": 12 }"); EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_prefix), DhcpConfigError); ConstElementPtr bad_map = Element::fromJSON("{ \"map\": [ ] }"); EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_map), DhcpConfigError); ConstElementPtr spurious = Element::fromJSON("{ \"spurious\": 1 }"); EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, spurious), DhcpConfigError); // Bad type has precedence. ConstElementPtr bad = Element::fromJSON("{ \"spurious\": 1, \"id\": true }"); try { SimpleParser::checkKeywords(KEYWORDS, bad); ADD_FAILURE() << "expect exception"; } catch (const DhcpConfigError& ex) { EXPECT_EQ("'id' parameter is not an integer", std::string(ex.what())); } catch (...) { ADD_FAILURE() << "expect DhcpConfigError"; } } // This test checks if the parameters can be inherited from the global // scope to the subnet scope. TEST_F(SimpleParserTest, deriveParams) { ElementPtr global = Element::fromJSON("{ \"renew-timer\": 1," " \"rebind-timer\": 2," " \"preferred-lifetime\": 3," " \"valid-lifetime\": 4" "}"); ElementPtr subnet = Element::fromJSON("{ \"renew-timer\": 100 }"); // we should inherit 3 parameters. Renew-timer should remain intact, // as it was already defined in the subnet scope. size_t num; EXPECT_NO_THROW(num = SimpleParser::deriveParams(global, subnet, SAMPLE_INHERITS)); EXPECT_EQ(3, num); // Check the values. 3 of them are inherited, while the fourth one // was already defined in the subnet, so should not be inherited. checkIntegerValue(subnet, "renew-timer", 100); checkIntegerValue(subnet, "rebind-timer", 2); checkIntegerValue(subnet, "preferred-lifetime", 3); checkIntegerValue(subnet, "valid-lifetime", 4); } // This test checks if global defaults are properly set for DHCPv6. TEST_F(SimpleParserTest, setDefaults) { ElementPtr empty = Element::fromJSON("{ }"); size_t num = 0; EXPECT_NO_THROW(num = SimpleParser::setDefaults(empty, SAMPLE_DEFAULTS)); // We expect at least 4 parameters to be inserted. EXPECT_GE(num, 3); checkIntegerValue(empty, "valid-lifetime", 7200); checkIntegerValue(empty, "preferred-lifetime", 3600); checkIntegerValue(empty, "rebind-timer", 1800); checkIntegerValue(empty, "renew-timer", 900); } // This test checks if global defaults are properly set for DHCPv6. TEST_F(SimpleParserTest, setListDefaults) { ElementPtr empty = Element::fromJSON("[{}, {}, {}]"); size_t num; EXPECT_NO_THROW(num = SimpleParser::setListDefaults(empty, SAMPLE_DEFAULTS)); // We expect at least 12 parameters to be inserted (3 entries, with // 4 parameters inserted in each) EXPECT_EQ(12, num); ASSERT_EQ(Element::list, empty->getType()); ASSERT_EQ(3, empty->size()); ConstElementPtr first = empty->get(0); ConstElementPtr second = empty->get(1); ConstElementPtr third = empty->get(2); checkIntegerValue(first, "valid-lifetime", 7200); checkIntegerValue(first, "preferred-lifetime", 3600); checkIntegerValue(first, "rebind-timer", 1800); checkIntegerValue(first, "renew-timer", 900); checkIntegerValue(second, "valid-lifetime", 7200); checkIntegerValue(second, "preferred-lifetime", 3600); checkIntegerValue(second, "rebind-timer", 1800); checkIntegerValue(second, "renew-timer", 900); checkIntegerValue(third, "valid-lifetime", 7200); checkIntegerValue(third, "preferred-lifetime", 3600); checkIntegerValue(third, "rebind-timer", 1800); checkIntegerValue(third, "renew-timer", 900); } // This test exercises the getIntType template TEST_F(SimpleParserTest, getIntType) { SimpleParserClassTest parser; // getIntType checks it can be found ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }"); EXPECT_THROW(parser.getUint8(not_found, "foo"), DhcpConfigError); // getIntType checks if it is an integer ElementPtr not_int = Element::fromJSON("{ \"foo\": \"xyz\" }"); EXPECT_THROW(parser.getUint8(not_int, "foo"), DhcpConfigError); // getIntType checks bounds ElementPtr negative = Element::fromJSON("{ \"foo\": -1 }"); EXPECT_THROW(parser.getUint8(negative, "foo"), DhcpConfigError); ElementPtr too_large = Element::fromJSON("{ \"foo\": 1024 }"); EXPECT_THROW(parser.getUint8(too_large, "foo"), DhcpConfigError); // checks if getIntType can return the expected value ElementPtr hundred = Element::fromJSON("{ \"foo\": 100 }"); uint8_t val = 0; EXPECT_NO_THROW(val = parser.getUint8(hundred, "foo")); EXPECT_EQ(100, val); } // This test exercises the getInteger with range checking TEST_F(SimpleParserTest, getInteger) { // The value specified is 100. ElementPtr json = Element::fromJSON("{ \"bar\": 100 }"); int64_t x = -1; // Positive case: we expect value in range 0..200. All ok. EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 0, 200)); EXPECT_EQ(100, x); // Border checks: 100 for 100..200 range is still ok. EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 100, 200)); // Border checks: 100 for 1..100 range is still ok. EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 1, 100)); // Out of expected range. Should throw. EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 101, 200), OutOfRange); EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 1, 99), OutOfRange); } // This test exercises the getAndConvert template TEST_F(SimpleParserTest, getAndConvert) { SimpleParserClassTest parser; // getAndConvert checks it can be found ElementPtr not_found = Element::fromJSON("{ \"bar\": \"true\" }"); EXPECT_THROW(parser.getAsBool(not_found, "foo"), DhcpConfigError); // getAndConvert checks if it is a string ElementPtr not_bool = Element::fromJSON("{ \"foo\": 1 }"); EXPECT_THROW(parser.getAsBool(not_bool, "foo"), DhcpConfigError); // checks if getAndConvert can return the expected value ElementPtr a_bool = Element::fromJSON("{ \"foo\": \"false\" }"); bool val = true; EXPECT_NO_THROW(val = parser.getAsBool(a_bool, "foo")); EXPECT_FALSE(val); // getAndConvert checks conversion ElementPtr bad_bool = Element::fromJSON("{ \"foo\": \"bar\" }"); EXPECT_THROW(parser.getAsBool(bad_bool, "bar"), DhcpConfigError); } // This test exercises the getIOAddress TEST_F(SimpleParserTest, getIOAddress) { SimpleParserClassTest parser; // getAddress checks it can be found ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }"); EXPECT_THROW(parser.getAddress(not_found, "foo"), DhcpConfigError); // getAddress checks if it is a string ElementPtr not_addr = Element::fromJSON("{ \"foo\": 1234 }"); EXPECT_THROW(parser.getAddress(not_addr, "foo"), DhcpConfigError); // checks if getAddress can return the expected value of v4 address ElementPtr v4 = Element::fromJSON("{ \"foo\": \"192.0.2.1\" }"); IOAddress val("::"); EXPECT_NO_THROW(val = parser.getAddress(v4, "foo")); EXPECT_EQ("192.0.2.1" , val.toText()); // checks if getAddress can return the expected value of v4 address ElementPtr v6 = Element::fromJSON("{ \"foo\": \"2001:db8::1\" }"); EXPECT_NO_THROW(val = parser.getAddress(v6, "foo")); EXPECT_EQ("2001:db8::1" , val.toText()); } // This test exercises getDouble() TEST_F(SimpleParserTest, getDouble) { SimpleParserClassTest parser; std::string json = "{\n" " \"string\" : \"12.3\",\n" " \"bool\" : true, \n" " \"int\" : 777, \n" " \"map\" : {}, \n" " \"list\" : [], \n" " \"zero\" : 0.0, \n" " \"fraction\" : .75, \n" " \"negative\" : -1.45, \n" " \"positive\" : 346.7 \n" "}\n"; // Create our test set of parameters. ElementPtr elems; ASSERT_NO_THROW(elems = Element::fromJSON(json)) << " invalid JSON, test is broken"; // Verify that a non-existant element is caught. EXPECT_THROW(parser.getDouble(elems, "not-there"), DhcpConfigError); // Verify that wrong element types are caught. EXPECT_THROW(parser.getDouble(elems, "string"), DhcpConfigError); EXPECT_THROW(parser.getDouble(elems, "int"), DhcpConfigError); EXPECT_THROW(parser.getDouble(elems, "bool"), DhcpConfigError); EXPECT_THROW(parser.getDouble(elems, "map"), DhcpConfigError); EXPECT_THROW(parser.getDouble(elems, "list"), DhcpConfigError); // Verify valid values are correct. double value; EXPECT_NO_THROW(value = parser.getDouble(elems, "zero")); EXPECT_EQ(0.0, value); EXPECT_NO_THROW(value = parser.getDouble(elems, "fraction")); EXPECT_EQ(.75, value); EXPECT_NO_THROW(value = parser.getDouble(elems, "negative")); EXPECT_EQ(-1.45, value); EXPECT_NO_THROW(value = parser.getDouble(elems, "positive")); EXPECT_EQ(346.7, value); }