# undef yywrap
# define yywrap() 1
+namespace {
+
// The location of the current token. The lexer will keep updating it. This
// variable will be useful for logging errors.
-static isc::dhcp::location loc;
+isc::dhcp::location loc;
+
+/// @brief Location stack.
+std::vector<isc::dhcp::location> locs;
+
+/// @brief File name.
+std::string file;
-static bool start_token_flag = false;
+/// @brief File name stack.
+std::vector<std::string> files;
-static isc::dhcp::Parser6Context::ParserType start_token_value;
+/// @brief State stack.
+std::vector<struct yy_buffer_state*> states;
+
+bool start_token_flag = false;
+
+isc::dhcp::Parser6Context::ParserType start_token_value;
+
+};
// To avoid the call to exit... oops!
#define YY_FATAL_ERROR(msg) isc::dhcp::Parser6Context::fatal(msg)
%option yylineno
%x COMMENT
+%x DIR_ENTER DIR_INCLUDE DIR_EXIT
/* These are not token expressions yet, just convenience expressions that
can be used during actual token definitions. Note some can match
isc_throw(isc::BadValue, "Comment not closed. (/* in line " << comment_start_line);
}
+"<?" BEGIN(DIR_ENTER);
+<DIR_ENTER>"include" BEGIN(DIR_INCLUDE);
+<DIR_INCLUDE>\"([^\"\n])+\" {
+ // Include directive.
+
+ // Extract the filename.
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+
+ Parser6Context::includeFile(tmp);
+}
+<DIR_EXIT>"?>" BEGIN(INITIAL);
+
+
{blank}+ {
// Ok, we found a with space. Let's ignore it and update loc variable.
loc.step();
}
. driver.error (loc, "Invalid character: " + std::string(yytext));
-<<EOF>> return isc::dhcp::Dhcp6Parser::make_END(loc);
+<<EOF>> {
+ if (states.empty()) {
+ return isc::dhcp::Dhcp6Parser::make_END(loc);
+ }
+ loc = locs.back();
+ locs.pop_back();
+ file = files.back();
+ files.pop_back();
+ parser6__delete_buffer(YY_CURRENT_BUFFER);
+ parser6__switch_to_buffer(states.back());
+ states.pop_back();
+
+ BEGIN(DIR_EXIT);
+}
+
%%
using namespace isc::dhcp;
start_token_flag = true;
start_token_value = parser_type;
- loc.initialize(&file_);
+ file = "<string>";
+ loc.initialize(&file);
yy_flex_debug = trace_scanning_;
YY_BUFFER_STATE buffer;
buffer = yy_scan_bytes(str.c_str(), str.size());
}
void
-Parser6Context::scanFileBegin(FILE * f, ParserType parser_type) {
+Parser6Context::scanFileBegin(FILE * f,
+ const std::string& filename,
+ ParserType parser_type) {
start_token_flag = true;
start_token_value = parser_type;
- loc.initialize(&file_);
+ file = filename;
+ loc.initialize(&file);
yy_flex_debug = trace_scanning_;
YY_BUFFER_STATE buffer;
// See dhcp6_lexer.cc header for available definitions
buffer = parser6__create_buffer(f, 65536 /*buffer size*/);
if (!buffer) {
- fatal("cannot scan file " + file_);
+ fatal("cannot scan file " + filename);
}
parser6__switch_to_buffer(buffer);
}
void
Parser6Context::includeFile(const std::string& filename) {
- fprintf(stderr, "includeFile(\"%s\")\n", filename.c_str());
+ if (states.size() > 10) {
+ fatal("Too many nested include.");
+ }
+
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ fatal("Can't open include file " + filename);
+ }
+ states.push_back(YY_CURRENT_BUFFER);
+ YY_BUFFER_STATE buffer;
+ buffer = parser6__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal( "Can't scan include file " + filename);
+ }
+ parser6__switch_to_buffer(buffer);
+ files.push_back(file);
+ file = filename;
+ locs.push_back(loc);
+ loc.initialize(&file);
+
+ BEGIN(INITIAL);
}
namespace {
LeaseMgrFactory::destroy();
isc::log::setDefaultLoggingOutput();
static_cast<void>(remove(TEST_FILE));
+ static_cast<void>(remove(TEST_INCLUDE));
};
void writeFile(const std::string& file_name, const std::string& content) {
}
static const char* TEST_FILE;
+ static const char* TEST_INCLUDE;
};
const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
+const char* JSONFileBackendTest::TEST_INCLUDE = "test-include.json";
// This test checks if configuration can be read from a JSON file.
TEST_F(JSONFileBackendTest, jsonFile) {
EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
}
-// This test checks if configuration can be read from a JSON file.
-TEST_F(JSONFileBackendTest, comments) {
+// This test checks if configuration can be read from a JSON file
+// using hash (#) line comments
+TEST_F(JSONFileBackendTest, hashComments) {
string config_hash_comments = "# This is a comment. It should be \n"
"#ignored. Real config starts in line below\n"
"\"valid-lifetime\": 4000 }"
"}";
- /// @todo: Implement C++-style (// ...) comments
- /// @todo: Implement C-style (/* ... */) comments
-
writeFile(TEST_FILE, config_hash_comments);
// Now initialize the server
EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
}
+// This test checks if configuration can be read from a JSON file
+// using C++ line (//) comments.
+TEST_F(JSONFileBackendTest, cppLineComments) {
+
+ string config_cpp_line_comments = "// This is a comment. It should be \n"
+ "//ignored. Real config starts in line below\n"
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "// comments in the middle should be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_cpp_line_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config without
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+ EXPECT_EQ(64, subnets->at(0)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using C block (/* */) comments
+TEST_F(JSONFileBackendTest, cBlockComments) {
+
+ string config_c_block_comments = "/* This is a comment. It should be \n"
+ "ignored. Real config starts in line below*/\n"
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "/* comments in the middle should be ignored, too*/\n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_c_block_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config without
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+ EXPECT_EQ(64, subnets->at(0)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using an include file.
+TEST_F(JSONFileBackendTest, include) {
+
+ string config = "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "<?include \"" + string(TEST_INCLUDE) + "\"?>,"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ string include = "\n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ]\n";
+
+ writeFile(TEST_FILE, config);
+ writeFile(TEST_INCLUDE, include);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config without
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+ EXPECT_EQ(64, subnets->at(0)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file.
// This test checks if configuration detects failure when trying:
// - empty file
// - empty filename