From: Tomek Mrugalski Date: Wed, 14 Dec 2016 23:15:49 +0000 (+0100) Subject: [5017] Dhcp4 bison parser implemented. X-Git-Tag: trac5088_base~1^2~33 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e38b37ac0326e0743d296cf3edbce058556fbf65;p=thirdparty%2Fkea.git [5017] Dhcp4 bison parser implemented. --- diff --git a/doc/examples/kea4/classify.json b/doc/examples/kea4/classify.json index 68cca11d19..a8f5b208f6 100644 --- a/doc/examples/kea4/classify.json +++ b/doc/examples/kea4/classify.json @@ -55,7 +55,7 @@ { "name": "VoIP", "test": "substring(option[60].hex,0,6) == 'Aastra'" - }, + } ], diff --git a/doc/examples/kea4/dhcpv4-over-dhcpv6.json b/doc/examples/kea4/dhcpv4-over-dhcpv6.json index 1b4d7ed30d..5a41f8afc5 100644 --- a/doc/examples/kea4/dhcpv4-over-dhcpv6.json +++ b/doc/examples/kea4/dhcpv4-over-dhcpv6.json @@ -19,13 +19,13 @@ "subnet4": [ { "subnet": "10.10.10.0/24", - # Don't forget the "4o6-" before "interface" here! +# Don't forget the "4o6-" before "interface" here! "4o6-interface": "eno33554984", "4o6-subnet": "2001:db8:1:1::/64", "pools": [ { "pool": "10.10.10.100 - 10.10.10.199" } ] } ], - # This enables DHCPv4-over-DHCPv6 support +# This enables DHCPv4-over-DHCPv6 support "dhcp4o6-port": 6767 }, diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am index 8360677861..451bbb1055 100644 --- a/src/bin/dhcp4/Makefile.am +++ b/src/bin/dhcp4/Makefile.am @@ -63,6 +63,10 @@ libdhcp4_la_SOURCES += dhcp4_log.cc dhcp4_log.h libdhcp4_la_SOURCES += dhcp4_srv.cc dhcp4_srv.h libdhcp4_la_SOURCES += dhcp4to6_ipc.cc dhcp4to6_ipc.h +libdhcp4_la_SOURCES += dhcp4_lexer.ll location.hh position.hh stack.hh +libdhcp4_la_SOURCES += dhcp4_parser.cc dhcp4_parser.h +libdhcp4_la_SOURCES += parser_context.cc parser_context.h + libdhcp4_la_SOURCES += kea_controller.cc nodist_libdhcp4_la_SOURCES = dhcp4_messages.h dhcp4_messages.cc @@ -104,3 +108,30 @@ endif kea_dhcp4dir = $(pkgdatadir) kea_dhcp4_DATA = dhcp4.spec + +if GENERATE_PARSER + +parser: dhcp4_lexer.cc location.hh position.hh stack.hh dhcp4_parser.cc dhcp4_parser.h + @echo "Flex/bison files regenerated" + +# --- Flex/Bison stuff below -------------------------------------------------- +# When debugging grammar issues, it's useful to add -v to bison parameters. +# bison will generate parser.output file that explains the whole grammar. +# It can be used to manually follow what's going on in the parser. +# This is especially useful if yydebug_ is set to 1 as that variable +# will cause parser to print out its internal state. +# Call flex with -s to check that the default rule can be suppressed +# Call bison with -W to get warnings like unmarked empty rules +# Note C++11 deprecated register still used by flex < 2.6.0 +location.hh position.hh stack.hh dhcp4_parser.cc dhcp4_parser.h: dhcp4_parser.yy + $(YACC) --defines=dhcp4_parser.h --report=all --report-file=dhcp4_parser.report -o dhcp4_parser.cc dhcp4_parser.yy + +dhcp4_lexer.cc: dhcp4_lexer.ll + $(LEX) --prefix parser4_ -o dhcp4_lexer.cc dhcp4_lexer.ll + +else + +parser location.hh position.hh stack.hh dhcp4_parser.cc dhcp4_parser.h dhcp4_lexer.cc: + @echo Parser generation disabled. Configure with --enable-generate-parser to enable it. + +endif diff --git a/src/bin/dhcp4/dhcp4_lexer.ll b/src/bin/dhcp4/dhcp4_lexer.ll new file mode 100644 index 0000000000..3ddf0629a0 --- /dev/null +++ b/src/bin/dhcp4/dhcp4_lexer.ll @@ -0,0 +1,1105 @@ +/* Copyright (C) 2015-2016 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/. */ + +%{ /* -*- C++ -*- */ +#include +#include +#include +#include +#include +#include +#include +#include + +// Work around an incompatibility in flex (at least versions +// 2.5.31 through 2.5.33): it generates code that does +// not conform to C89. See Debian bug 333231 +// . +# undef yywrap +# define yywrap() 1 + +namespace { + +bool start_token_flag = false; + +isc::dhcp::Parser4Context::ParserType start_token_value; +unsigned int comment_start_line = 0; + +}; + +// To avoid the call to exit... oops! +#define YY_FATAL_ERROR(msg) isc::dhcp::Parser4Context::fatal(msg) +%} + +/* noyywrap disables automatic rewinding for the next file to parse. Since we + always parse only a single string, there's no need to do any wraps. And + using yywrap requires linking with -lfl, which provides the default yywrap + implementation that always returns 1 anyway. */ +%option noyywrap + +/* nounput simplifies the lexer, by removing support for putting a character + back into the input stream. We never use such capability anyway. */ +%option nounput + +/* batch means that we'll never use the generated lexer interactively. */ +%option batch + +/* avoid to get static global variables to remain with C++. */ +/* in last resort %option reentrant */ + +/* Enables debug mode. To see the debug messages, one needs to also set + yy_flex_debug to 1, then the debug messages will be printed on stderr. */ +%option debug + +/* I have no idea what this option does, except it was specified in the bison + examples and Postgres folks added it to remove gcc 4.3 warnings. Let's + be on the safe side and keep it. */ +%option noinput + +%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 + incorrect inputs (e.g., IP addresses) which must be checked. */ +int \-?[0-9]+ +blank [ \t\r] + +UnicodeEscapeSequence u[0-9A-Fa-f]{4} +JSONEscapeCharacter ["\\/bfnrt] +JSONEscapeSequence {JSONEscapeCharacter}|{UnicodeEscapeSequence} +JSONStandardCharacter [^\x00-\x1f"\\] +JSONStringCharacter {JSONStandardCharacter}|\\{JSONEscapeSequence} +JSONString \"{JSONStringCharacter}*\" + +/* for errors */ + +BadUnicodeEscapeSequence u[0-9A-Fa-f]{0,3}[^0-9A-Fa-f] +BadJSONEscapeSequence [^"\\/bfnrtu]|{BadUnicodeEscapeSequence} +ControlCharacter [\x00-\x1f] +ControlCharacterFill [^"\\]|\\{JSONEscapeSequence} + +%{ +// This code run each time a pattern is matched. It updates the location +// by moving it ahead by yyleng bytes. yyleng specifies the length of the +// currently matched token. +#define YY_USER_ACTION driver.loc_.columns(yyleng); +%} + +%% + +%{ + // This part of the code is copied over to the verbatim to the top + // of the generated yylex function. Explanation: + // http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html + + // Code run each time yylex is called. + driver.loc_.step(); + + if (start_token_flag) { + start_token_flag = false; + switch (start_token_value) { + case Parser4Context::PARSER_JSON: + default: + return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_JSON(driver.loc_); + case Parser4Context::PARSER_DHCP4: + return isc::dhcp::Dhcp4Parser::make_TOPLEVEL_DHCP4(driver.loc_); + case Parser4Context::SUBPARSER_DHCP4: + return isc::dhcp::Dhcp4Parser::make_SUB_DHCP4(driver.loc_); + case Parser4Context::PARSER_INTERFACES: + return isc::dhcp::Dhcp4Parser::make_SUB_INTERFACES4(driver.loc_); + case Parser4Context::PARSER_SUBNET4: + return isc::dhcp::Dhcp4Parser::make_SUB_SUBNET4(driver.loc_); + case Parser4Context::PARSER_POOL4: + return isc::dhcp::Dhcp4Parser::make_SUB_POOL4(driver.loc_); + case Parser4Context::PARSER_HOST_RESERVATION: + return isc::dhcp::Dhcp4Parser::make_SUB_RESERVATION(driver.loc_); + case Parser4Context::PARSER_OPTION_DATA: + return isc::dhcp::Dhcp4Parser::make_SUB_OPTION_DATA(driver.loc_); + case Parser4Context::PARSER_HOOKS_LIBRARY: + return isc::dhcp::Dhcp4Parser::make_SUB_HOOKS_LIBRARY(driver.loc_); + } + } +%} + +#.* ; + +"//"(.*) ; + +"/*" { + BEGIN(COMMENT); + comment_start_line = driver.loc_.end.line;; +} + +"*/" BEGIN(INITIAL); +. ; +<> { + isc_throw(Dhcp4ParseError, "Comment not closed. (/* in line " << comment_start_line); +} + +""include" BEGIN(DIR_INCLUDE); +\"([^\"\n])+\" { + // Include directive. + + // Extract the filename. + std::string tmp(yytext+1); + tmp.resize(tmp.size() - 1); + + driver.includeFile(tmp); +} +<> { + isc_throw(Dhcp4ParseError, "Directive not closed."); +} +"?>" BEGIN(INITIAL); + + +<*>{blank}+ { + // Ok, we found a with space. Let's ignore it and update loc variable. + driver.loc_.step(); +} + +<*>[\n]+ { + // Newline found. Let's update the location and continue. + driver.loc_.lines(yyleng); + driver.loc_.step(); +} + + +\"Dhcp6\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CONFIG: + return isc::dhcp::Dhcp4Parser::make_DHCP6(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp6", driver.loc_); + } +} + +\"interfaces-config\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_INTERFACES_CONFIG(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("interfaces-config", driver.loc_); + } +} + +\"interfaces\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::INTERFACES_CONFIG: + return isc::dhcp::Dhcp4Parser::make_INTERFACES(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("interfaces", driver.loc_); + } +} + +\"lease-database\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_LEASE_DATABASE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("lease-database", driver.loc_); + } +} + +\"hosts-database\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_HOSTS_DATABASE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("hosts-database", driver.loc_); + } +} + +\"readonly\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + return isc::dhcp::Dhcp4Parser::make_READONLY(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("readonly", driver.loc_); + } +} + +\"type\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + case isc::dhcp::Parser4Context::OPTION_DEF: + case isc::dhcp::Parser4Context::SERVER_ID: + return isc::dhcp::Dhcp4Parser::make_TYPE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("type", driver.loc_); + } +} + +\"user\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + return isc::dhcp::Dhcp4Parser::make_USER(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("user", driver.loc_); + } +} + +\"password\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + return isc::dhcp::Dhcp4Parser::make_PASSWORD(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("password", driver.loc_); + } +} + +\"host\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + return isc::dhcp::Dhcp4Parser::make_HOST(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("host", driver.loc_); + } +} + +\"persist\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + case isc::dhcp::Parser4Context::SERVER_ID: + return isc::dhcp::Dhcp4Parser::make_PERSIST(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("persist", driver.loc_); + } +} + +\"lfc-interval\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + return isc::dhcp::Dhcp4Parser::make_LFC_INTERVAL(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("lfc-interval", driver.loc_); + } +} + +\"valid-lifetime\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_VALID_LIFETIME(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("valid-lifetime", driver.loc_); + } +} + +\"renew-timer\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_RENEW_TIMER(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("renew-timer", driver.loc_); + } +} + +\"rebind-timer\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_REBIND_TIMER(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("rebind-timer", driver.loc_); + } +} + +\"decline-probation-period\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_DECLINE_PROBATION_PERIOD(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("decline-probation-period", driver.loc_); + } +} + +\"subnet4\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_SUBNET4(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("subnet4", driver.loc_); + } +} + +\"option-def\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_OPTION_DEF(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("option-def", driver.loc_); + } +} + +\"option-data\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + case isc::dhcp::Parser4Context::SUBNET4: + case isc::dhcp::Parser4Context::POOLS: + case isc::dhcp::Parser4Context::RESERVATIONS: + case isc::dhcp::Parser4Context::CLIENT_CLASSES: + case isc::dhcp::Parser4Context::CLIENT_CLASS: + return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_); + } +} + +\"name\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LEASE_DATABASE: + case isc::dhcp::Parser4Context::HOSTS_DATABASE: + case isc::dhcp::Parser4Context::OPTION_DEF: + case isc::dhcp::Parser4Context::OPTION_DATA: + case isc::dhcp::Parser4Context::CLIENT_CLASSES: + case isc::dhcp::Parser4Context::CLIENT_CLASS: + case isc::dhcp::Parser4Context::LOGGERS: + return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("name", driver.loc_); + } +} + +\"data\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DATA: + return isc::dhcp::Dhcp4Parser::make_DATA(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("data", driver.loc_); + } +} + +\"pools\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_POOLS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("pools", driver.loc_); + } +} + +\"pool\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::POOLS: + return isc::dhcp::Dhcp4Parser::make_POOL(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("pool", driver.loc_); + } +} + +\"subnet\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_SUBNET(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("subnet", driver.loc_); + } +} + +\"interface\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_INTERFACE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("interface", driver.loc_); + } +} + +\"interface-id\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_INTERFACE_ID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("interface-id", driver.loc_); + } +} + +\"id\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_ID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("id", driver.loc_); + } +} + +\"rapid-commit\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_RAPID_COMMIT(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("rapid-commit", driver.loc_); + } +} + +\"reservation-mode\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_RESERVATION_MODE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("reservation-mode", driver.loc_); + } +} + +\"code\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DEF: + case isc::dhcp::Parser4Context::OPTION_DATA: + return isc::dhcp::Dhcp4Parser::make_CODE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("code", driver.loc_); + } +} + +\"host-reservation-identifiers\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_HOST_RESERVATION_IDENTIFIERS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("host-reservation-identifiers", driver.loc_); + } +} + +\"Logging\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CONFIG: + return isc::dhcp::Dhcp4Parser::make_LOGGING(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("Logging", driver.loc_); + } +} + +\"loggers\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LOGGING: + return isc::dhcp::Dhcp4Parser::make_LOGGERS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("loggers", driver.loc_); + } +} + +\"output_options\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LOGGERS: + return isc::dhcp::Dhcp4Parser::make_OUTPUT_OPTIONS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("output_options", driver.loc_); + } +} + +\"output\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OUTPUT_OPTIONS: + return isc::dhcp::Dhcp4Parser::make_OUTPUT(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("output", driver.loc_); + } +} + +\"debuglevel\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LOGGERS: + return isc::dhcp::Dhcp4Parser::make_DEBUGLEVEL(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("debuglevel", driver.loc_); + } +} + +\"severity\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::LOGGERS: + return isc::dhcp::Dhcp4Parser::make_SEVERITY(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("severity", driver.loc_); + } +} + +\"client-classes\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASSES(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("client-classes", driver.loc_); + } +} + +\"client-class\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + case isc::dhcp::Parser4Context::CLIENT_CLASSES: + return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("client-class", driver.loc_); + } +} + +\"test\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CLIENT_CLASSES: + case isc::dhcp::Parser4Context::CLIENT_CLASS: + return isc::dhcp::Dhcp4Parser::make_TEST(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("test", driver.loc_); + } +} + +\"reservations\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_RESERVATIONS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("reservations", driver.loc_); + } +} + +\"duid\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS: + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_DUID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("duid", driver.loc_); + } +} + +\"hw-address\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS: + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_HW_ADDRESS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("hw-address", driver.loc_); + } +} + +\"client-id\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS: + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_CLIENT_ID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("client-id", driver.loc_); + } +} + +\"circuit-id\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOST_RESERVATION_IDENTIFIERS: + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_CIRCUIT_ID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("circuit-id", driver.loc_); + } +} + +\"hostname\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_HOSTNAME(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("hostname", driver.loc_); + } +} + +\"space\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DEF: + case isc::dhcp::Parser4Context::OPTION_DATA: + return isc::dhcp::Dhcp4Parser::make_SPACE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("space", driver.loc_); + } +} + +\"csv-format\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DATA: + return isc::dhcp::Dhcp4Parser::make_CSV_FORMAT(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("csv-format", driver.loc_); + } +} + +\"record-types\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DEF: + return isc::dhcp::Dhcp4Parser::make_RECORD_TYPES(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("record-types", driver.loc_); + } +} + +\"encapsulate\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DEF: + return isc::dhcp::Dhcp4Parser::make_ENCAPSULATE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("encapsulate", driver.loc_); + } +} + +\"array\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::OPTION_DEF: + return isc::dhcp::Dhcp4Parser::make_ARRAY(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("array", driver.loc_); + } +} + +\"relay\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_RELAY(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("relay", driver.loc_); + } +} + +\"ip-address\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::RELAY: + case isc::dhcp::Parser4Context::RESERVATIONS: + return isc::dhcp::Dhcp4Parser::make_IP_ADDRESS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("ip-address", driver.loc_); + } +} + +\"hooks-libraries\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_HOOKS_LIBRARIES(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("hooks-libraries", driver.loc_); + } +} + + +\"parameters\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOOKS_LIBRARIES: + return isc::dhcp::Dhcp4Parser::make_PARAMETERS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("parameters", driver.loc_); + } +} + +\"library\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::HOOKS_LIBRARIES: + return isc::dhcp::Dhcp4Parser::make_LIBRARY(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("library", driver.loc_); + } +} + +\"server-id\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_SERVER_ID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("server-id", driver.loc_); + } +} + +\"identifier\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SERVER_ID: + return isc::dhcp::Dhcp4Parser::make_IDENTIFIER(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("identifier", driver.loc_); + } +} + +\"htype\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SERVER_ID: + return isc::dhcp::Dhcp4Parser::make_HTYPE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("htype", driver.loc_); + } +} + +\"time\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SERVER_ID: + return isc::dhcp::Dhcp4Parser::make_TIME(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("time", driver.loc_); + } +} + +\"enterprise-id\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SERVER_ID: + return isc::dhcp::Dhcp4Parser::make_ENTERPRISE_ID(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("enterprise-id", driver.loc_); + } +} + +\"expired-leases-processing\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_EXPIRED_LEASES_PROCESSING(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("expired-leases-processing", driver.loc_); + } +} + +\"dhcp4o6-port\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_DHCP4O6_PORT(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("dhcp4o6-port", driver.loc_); + } +} + +\"version\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_VERSION(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("version", driver.loc_); + } +} + +\"control-socket\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_CONTROL_SOCKET(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("control-socket", driver.loc_); + } +} + +\"socket-type\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CONTROL_SOCKET: + return isc::dhcp::Dhcp4Parser::make_SOCKET_TYPE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("socket-type", driver.loc_); + } +} + +\"socket-name\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CONTROL_SOCKET: + return isc::dhcp::Dhcp4Parser::make_SOCKET_NAME(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("socket-name", driver.loc_); + } +} + +\"dhcp-ddns\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::DHCP4: + return isc::dhcp::Dhcp4Parser::make_DHCP_DDNS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("dhcp-ddns", driver.loc_); + } +} + +\"Dhcp4\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CONFIG: + return isc::dhcp::Dhcp4Parser::make_DHCP4(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp4", driver.loc_); + } +} + +\"DhcpDdns\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::CONFIG: + return isc::dhcp::Dhcp4Parser::make_DHCPDDNS(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("DhcpDdns", driver.loc_); + } +} + +\"4o6-interface\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_INTERFACE(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface", driver.loc_); + } +} + +\"4o6-subnet\" { + switch(driver.ctx_) { + case isc::dhcp::Parser4Context::SUBNET4: + return isc::dhcp::Dhcp4Parser::make_SUBNET_4O6_SUBNET(driver.loc_); + default: + return isc::dhcp::Dhcp4Parser::make_STRING("4o6-interface", driver.loc_); + } +} + + +{JSONString} { + // A string has been matched. It contains the actual string and single quotes. + // We need to get those quotes out of the way and just use its content, e.g. + // for 'foo' we should get foo + std::string raw(yytext+1); + size_t len = raw.size() - 1; + raw.resize(len); + std::string decoded; + decoded.reserve(len); + for (size_t pos = 0; pos < len; ++pos) { + char c = raw[pos]; + switch (c) { + case '"': + // impossible condition + driver.error(driver.loc_, "Bad quote in \"" + raw + "\""); + case '\\': + ++pos; + if (pos >= len) { + // impossible condition + driver.error(driver.loc_, "Overflow escape in \"" + raw + "\""); + } + c = raw[pos]; + switch (c) { + case '"': + case '\\': + case '/': + decoded.push_back(c); + break; + case 'b': + decoded.push_back('\b'); + break; + case 'f': + decoded.push_back('\f'); + break; + case 'n': + decoded.push_back('\n'); + break; + case 'r': + decoded.push_back('\r'); + break; + case 't': + decoded.push_back('\t'); + break; + case 'u': + // not yet implemented + driver.error(driver.loc_, "Unsupported unicode escape in \"" + raw + "\""); + default: + // impossible condition + driver.error(driver.loc_, "Bad escape in \"" + raw + "\""); + } + break; + default: + if ((c >= 0) && (c < 0x20)) { + // impossible condition + driver.error(driver.loc_, "Invalid control in \"" + raw + "\""); + } + decoded.push_back(c); + } + } + + return isc::dhcp::Dhcp4Parser::make_STRING(decoded, driver.loc_); +} + +\"{JSONStringCharacter}*{ControlCharacter}{ControlCharacterFill}*\" { + // Bad string with a forbidden control character inside + driver.error(driver.loc_, "Invalid control in " + std::string(yytext)); +} + +\"{JSONStringCharacter}*\\{BadJSONEscapeSequence}[^\x00-\x1f"]*\" { + // Bad string with a bad escape inside + driver.error(driver.loc_, "Bad escape in " + std::string(yytext)); +} + +\"{JSONStringCharacter}*\\\" { + // Bad string with an open escape at the end + driver.error(driver.loc_, "Overflow escape in " + std::string(yytext)); +} + +"[" { return isc::dhcp::Dhcp4Parser::make_LSQUARE_BRACKET(driver.loc_); } +"]" { return isc::dhcp::Dhcp4Parser::make_RSQUARE_BRACKET(driver.loc_); } +"{" { return isc::dhcp::Dhcp4Parser::make_LCURLY_BRACKET(driver.loc_); } +"}" { return isc::dhcp::Dhcp4Parser::make_RCURLY_BRACKET(driver.loc_); } +"," { return isc::dhcp::Dhcp4Parser::make_COMMA(driver.loc_); } +":" { return isc::dhcp::Dhcp4Parser::make_COLON(driver.loc_); } + +{int} { + // An integer was found. + std::string tmp(yytext); + int64_t integer = 0; + try { + // In substring we want to use negative values (e.g. -1). + // In enterprise-id we need to use values up to 0xffffffff. + // To cover both of those use cases, we need at least + // int64_t. + integer = boost::lexical_cast(tmp); + } catch (const boost::bad_lexical_cast &) { + driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer."); + } + + // The parser needs the string form as double conversion is no lossless + return isc::dhcp::Dhcp4Parser::make_INTEGER(integer, driver.loc_); +} + +[-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)? { + // A floating point was found. + std::string tmp(yytext); + double fp = 0.0; + try { + fp = boost::lexical_cast(tmp); + } catch (const boost::bad_lexical_cast &) { + driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point."); + } + + return isc::dhcp::Dhcp4Parser::make_FLOAT(fp, driver.loc_); +} + +true|false { + string tmp(yytext); + return isc::dhcp::Dhcp4Parser::make_BOOLEAN(tmp == "true", driver.loc_); +} + +null { + return isc::dhcp::Dhcp4Parser::make_NULL_TYPE(driver.loc_); +} + +<*>. driver.error (driver.loc_, "Invalid character: " + std::string(yytext)); + +<> { + if (driver.states_.empty()) { + return isc::dhcp::Dhcp4Parser::make_END(driver.loc_); + } + driver.loc_ = driver.locs_.back(); + driver.locs_.pop_back(); + driver.file_ = driver.files_.back(); + driver.files_.pop_back(); + if (driver.sfile_) { + fclose(driver.sfile_); + driver.sfile_ = 0; + } + if (!driver.sfiles_.empty()) { + driver.sfile_ = driver.sfiles_.back(); + driver.sfiles_.pop_back(); + } + parser4__delete_buffer(YY_CURRENT_BUFFER); + parser4__switch_to_buffer(driver.states_.back()); + driver.states_.pop_back(); + + BEGIN(DIR_EXIT); +} + +%% + +using namespace isc::dhcp; + +void +Parser4Context::scanStringBegin(const std::string& str, ParserType parser_type) +{ + start_token_flag = true; + start_token_value = parser_type; + + file_ = ""; + sfile_ = 0; + loc_.initialize(&file_); + yy_flex_debug = trace_scanning_; + YY_BUFFER_STATE buffer; + buffer = yy_scan_bytes(str.c_str(), str.size()); + if (!buffer) { + fatal("cannot scan string"); + // fatal() throws an exception so this can't be reached + } +} + +void +Parser4Context::scanFileBegin(FILE * f, + const std::string& filename, + ParserType parser_type) +{ + start_token_flag = true; + start_token_value = parser_type; + + file_ = filename; + sfile_ = f; + loc_.initialize(&file_); + yy_flex_debug = trace_scanning_; + YY_BUFFER_STATE buffer; + + // See dhcp6_lexer.cc header for available definitions + buffer = parser4__create_buffer(f, 65536 /*buffer size*/); + if (!buffer) { + fatal("cannot scan file " + filename); + } + parser4__switch_to_buffer(buffer); +} + +void +Parser4Context::scanEnd() { + if (sfile_) + fclose(sfile_); + sfile_ = 0; + static_cast(parser4_lex_destroy()); + // Close files + while (!sfiles_.empty()) { + FILE* f = sfiles_.back(); + if (f) { + fclose(f); + } + sfiles_.pop_back(); + } + // Delete states + while (!states_.empty()) { + parser4__delete_buffer(states_.back()); + states_.pop_back(); + } +} + +void +Parser4Context::includeFile(const std::string& filename) { + 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); + } + if (sfile_) { + sfiles_.push_back(sfile_); + } + sfile_ = f; + states_.push_back(YY_CURRENT_BUFFER); + YY_BUFFER_STATE buffer; + buffer = parser4__create_buffer(f, 65536 /*buffer size*/); + if (!buffer) { + fatal( "Can't scan include file " + filename); + } + parser4__switch_to_buffer(buffer); + files_.push_back(file_); + file_ = filename; + locs_.push_back(loc_); + loc_.initialize(&file_); + + BEGIN(INITIAL); +} + +namespace { +/// To avoid unused function error +class Dummy { + // cppcheck-suppress unusedPrivateFunction + void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); } +}; +} diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy new file mode 100644 index 0000000000..a7fe3c8f16 --- /dev/null +++ b/src/bin/dhcp4/dhcp4_parser.yy @@ -0,0 +1,1466 @@ +/* Copyright (C) 2015-2016 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/. */ + +%skeleton "lalr1.cc" /* -*- C++ -*- */ +%require "3.0.0" +%defines +%define parser_class_name {Dhcp4Parser} +%define api.prefix {parser4_} +%define api.token.constructor +%define api.value.type variant +%define api.namespace {isc::dhcp} +%define parse.assert +%code requires +{ +#include +#include +#include +#include +#include + +using namespace isc::dhcp; +using namespace isc::data; +using namespace std; +} +// The parsing context. +%param { isc::dhcp::Parser4Context& ctx } +%locations +%define parse.trace +%define parse.error verbose +%code +{ +#include +} + + +%define api.token.prefix {TOKEN_} +// Tokens in an order which makes sense and related to the intented use. +// Actual regexps for tokens are defined in dhcp4_lexer.ll. +%token + END 0 "end of file" + COMMA "," + COLON ":" + LSQUARE_BRACKET "[" + RSQUARE_BRACKET "]" + LCURLY_BRACKET "{" + RCURLY_BRACKET "}" + NULL_TYPE "null" + + DHCP4 "Dhcp4" + INTERFACES_CONFIG "interfaces-config" + INTERFACES "interfaces" + + LEASE_DATABASE "lease-database" + HOSTS_DATABASE "hosts-database" + TYPE "type" + USER "user" + PASSWORD "password" + HOST "host" + PERSIST "persist" + LFC_INTERVAL "lfc-interval" + READONLY "readonly" + + VALID_LIFETIME "valid-lifetime" + RENEW_TIMER "renew-timer" + REBIND_TIMER "rebind-timer" + DECLINE_PROBATION_PERIOD "decline-probation-period" + SUBNET4 "subnet4" + SUBNET_4O6_INTERFACE "4o6-interface" + SUBNET_4O6_SUBNET "4o6-subnet" + OPTION_DEF "option-def" + OPTION_DATA "option-data" + NAME "name" + DATA "data" + CODE "code" + SPACE "space" + CSV_FORMAT "csv-format" + RECORD_TYPES "record-types" + ENCAPSULATE "encapsulate" + ARRAY "array" + + POOLS "pools" + POOL "pool" + + SUBNET "subnet" + INTERFACE "interface" + INTERFACE_ID "interface-id" + ID "id" + RAPID_COMMIT "rapid-commit" + RESERVATION_MODE "reservation-mode" + + HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers" + + CLIENT_CLASSES "client-classes" + TEST "test" + CLIENT_CLASS "client-class" + + RESERVATIONS "reservations" + DUID "duid" + HW_ADDRESS "hw-address" + CIRCUIT_ID "circuit-id" + CLIENT_ID "client-id" + HOSTNAME "hostname" + + RELAY "relay" + IP_ADDRESS "ip-address" + + HOOKS_LIBRARIES "hooks-libraries" + LIBRARY "library" + PARAMETERS "parameters" + + EXPIRED_LEASES_PROCESSING "expired-leases-processing" + + SERVER_ID "server-id" + IDENTIFIER "identifier" + HTYPE "htype" + TIME "time" + ENTERPRISE_ID "enterprise-id" + + DHCP4O6_PORT "dhcp4o6-port" + VERSION "version" + + CONTROL_SOCKET "control-socket" + SOCKET_TYPE "socket-type" + SOCKET_NAME "socket-name" + + DHCP_DDNS "dhcp-ddns" + + /// @todo: Implement proper parsing for those parameters in Dhcp4/dhcp-ddns/*. + /// This should be part of the #5043 ticket. Listing the keywords here for + /// completeness. + + // These are tokens defined in Dhcp4/dhcp-ddns/* + // They're not + // ENABLE_UPDATES "enable-updates" + // SERVER_IP "server-ip" + // SENDER_IP "sender-ip" + // SENDER_PORT "sender-port" + // MAX_QUEUE_SIZE "max-queue-size" + // NCR_PROTOCOL "ncr-protocol" + // NCR_FORMAT "ncr-format" + // ALWAYS_INCLUDE_FQDN "always-include-fqdn" + // OVERRDIDE_NO_UPDATE "override-no-update" + // OVERRDIDE_CLIENT_UPDATE "override-client-update" + // REPLACE_CLIENT_NAME "replace-client-name" + // GENERATED_PREFIX "generated-prefix" + // QUALIFYING_SUFFIX "qualifying-suffix" + + LOGGING "Logging" + LOGGERS "loggers" + OUTPUT_OPTIONS "output_options" + OUTPUT "output" + DEBUGLEVEL "debuglevel" + SEVERITY "severity" + + DHCP6 "Dhcp6" + DHCPDDNS "DhcpDdns" + + // Not real tokens, just a way to signal what the parser is expected to + // parse. + TOPLEVEL_JSON + TOPLEVEL_DHCP4 + SUB_DHCP4 + SUB_INTERFACES4 + SUB_SUBNET4 + SUB_POOL4 + SUB_RESERVATION + SUB_OPTION_DEF + SUB_OPTION_DATA + SUB_HOOKS_LIBRARY +; + +%token STRING "constant string" +%token INTEGER "integer" +%token FLOAT "floating point" +%token BOOLEAN "boolean" + +%type value +%type version_value + +%printer { yyoutput << $$; } <*>; + +%% + +// The whole grammar starts with a map, because the config file +// constists of Dhcp, Logger and DhcpDdns entries in one big { }. +// We made the same for subparsers at the exception of the JSON value. +%start start; + +start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json + | TOPLEVEL_DHCP4 { ctx.ctx_ = ctx.CONFIG; } syntax_map + | SUB_DHCP4 { ctx.ctx_ = ctx.DHCP4; } sub_dhcp4 + | SUB_INTERFACES4 { ctx.ctx_ = ctx.INTERFACES_CONFIG; } sub_interfaces4 + | SUB_SUBNET4 { ctx.ctx_ = ctx.SUBNET4; } sub_subnet4 + | SUB_POOL4 { ctx.ctx_ = ctx.POOLS; } sub_pool4 + | SUB_RESERVATION { ctx.ctx_ = ctx.RESERVATIONS; } sub_reservation + | SUB_OPTION_DEF { ctx.ctx_ = ctx.OPTION_DEF; } sub_option_def + | SUB_OPTION_DATA { ctx.ctx_ = ctx.OPTION_DATA; } sub_option_data + | SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library + ; + +// ---- generic JSON parser --------------------------------- + +// Note that ctx_ is NO_KEYWORD here + +// Values rule +value: INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); } + | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); } + | BOOLEAN { $$ = ElementPtr(new BoolElement($1, ctx.loc2pos(@1))); } + | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); } + | NULL_TYPE { $$ = ElementPtr(new NullElement(ctx.loc2pos(@1))); } + | map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); } + | list_generic { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); } + ; + +sub_json: value { + // Push back the JSON value on the stack + ctx.stack_.push_back($1); +}; + +map2: LCURLY_BRACKET { + // This code is executed when we're about to start parsing + // the content of the map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} map_content RCURLY_BRACKET { + // map parsing completed. If we ever want to do any wrap up + // (maybe some sanity checking), this would be the best place + // for it. +}; + +// Assignments rule +map_content: %empty // empty map + | not_empty_map + ; + +not_empty_map: STRING COLON value { + // map containing a single entry + ctx.stack_.back()->set($1, $3); + } + | not_empty_map COMMA STRING COLON value { + // map consisting of a shorter map followed by + // comma and string:value + ctx.stack_.back()->set($3, $5); + } + ; + +list_generic: LSQUARE_BRACKET { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(l); +} list_content RSQUARE_BRACKET { + // list parsing complete. Put any sanity checking here +}; + +// This one is used in syntax parser. +list2: LSQUARE_BRACKET { + // List parsing about to start +} list_content RSQUARE_BRACKET { + // list parsing complete. Put any sanity checking here + //ctx.stack_.pop_back(); +}; + +list_content: %empty // Empty list + | not_empty_list + ; + +not_empty_list: value { + // List consisting of a single element. + ctx.stack_.back()->add($1); + } + | not_empty_list COMMA value { + // List ending with , and a value. + ctx.stack_.back()->add($3); + } + ; + +// ---- generic JSON parser ends here ---------------------------------- + +// ---- syntax checking parser starts here ----------------------------- + +// Unknown keyword in a map +unknown_map_entry: STRING COLON { + const std::string& where = ctx.contextName(); + const std::string& keyword = $1; + error(@1, + "got unexpected keyword \"" + keyword + "\" in " + where + " map."); +}; + + +// This defines the top-level { } that holds Dhcp6, Dhcp4, DhcpDdns or Logging +// objects. +syntax_map: LCURLY_BRACKET { + // This code is executed when we're about to start parsing + // the content of the map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} global_objects RCURLY_BRACKET { + // map parsing completed. If we ever want to do any wrap up + // (maybe some sanity checking), this would be the best place + // for it. +}; + +// This represents top-level entries: Dhcp6, Dhcp4, DhcpDdns, Logging +global_objects: global_object + | global_objects COMMA global_object + ; + +// This represents a single top level entry, e.g. Dhcp6 or DhcpDdns. +global_object: dhcp4_object + | logging_object + | dhcp6_json_object + | dhcpddns_json_object + | unknown_map_entry + ; + +dhcp4_object: DHCP4 { + // This code is executed when we're about to start parsing + // the content of the map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("Dhcp4", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.DHCP4); +} COLON LCURLY_BRACKET global_params RCURLY_BRACKET { + // map parsing completed. If we ever want to do any wrap up + // (maybe some sanity checking), this would be the best place + // for it. + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// subparser: similar to the corresponding rule but without parent +// so the stack is empty at the rule entry. +sub_dhcp4: LCURLY_BRACKET { + // Parse the Dhcp6 map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} global_params RCURLY_BRACKET { + // parsing completed +}; + +global_params: global_param + | global_params COMMA global_param + ; + +// These are the parameters that are allowed in the top-level for +// Dhcp4. +global_param: valid_lifetime + | renew_timer + | rebind_timer + | decline_probation_period + | subnet4_list + | interfaces_config + | lease_database + | hosts_database + | host_reservation_identifiers + | client_classes + | option_def_list + | option_data_list + | hooks_libraries + | expired_leases_processing + | server_id + | dhcp4o6_port + | version + | control_socket + | dhcp_ddns + | unknown_map_entry + ; + +valid_lifetime: VALID_LIFETIME COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("valid-lifetime", prf); +}; + +renew_timer: RENEW_TIMER COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("renew-timer", prf); +}; + +rebind_timer: REBIND_TIMER COLON INTEGER { + ElementPtr prf(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("rebind-timer", prf); +}; + +decline_probation_period: DECLINE_PROBATION_PERIOD COLON INTEGER { + ElementPtr dpp(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("decline-probation-period", dpp); +}; + +interfaces_config: INTERFACES_CONFIG { + ElementPtr i(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("interfaces-config", i); + ctx.stack_.push_back(i); + ctx.enter(ctx.INTERFACES_CONFIG); +} COLON LCURLY_BRACKET interface_config_map RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +sub_interfaces4: LCURLY_BRACKET { + // Parse the interfaces-config map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} interface_config_map RCURLY_BRACKET { + // parsing completed +}; + +interface_config_map: INTERFACES { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("interfaces", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.NO_KEYWORD); +} COLON list2 { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +lease_database: LEASE_DATABASE { + ElementPtr i(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("lease-database", i); + ctx.stack_.push_back(i); + ctx.enter(ctx.LEASE_DATABASE); +} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +hosts_database: HOSTS_DATABASE { + ElementPtr i(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("hosts-database", i); + ctx.stack_.push_back(i); + ctx.enter(ctx.HOSTS_DATABASE); +} COLON LCURLY_BRACKET database_map_params RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +database_map_params: database_map_param + | database_map_params COMMA database_map_param + ; + +database_map_param: type + | user + | password + | host + | name + | persist + | lfc_interval + | readonly + | unknown_map_entry +; + +type: TYPE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr prf(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("type", prf); + ctx.leave(); +}; + +user: USER { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr user(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("user", user); + ctx.leave(); +}; + +password: PASSWORD { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr pwd(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("password", pwd); + ctx.leave(); +}; + +host: HOST { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr h(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("host", h); + ctx.leave(); +}; + +name: NAME { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr name(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("name", name); + ctx.leave(); +}; + +persist: PERSIST COLON BOOLEAN { + ElementPtr n(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("persist", n); +}; + +lfc_interval: LFC_INTERVAL COLON INTEGER { + ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("lfc-interval", n); +}; + +readonly: READONLY COLON BOOLEAN { + ElementPtr n(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("readonly", n); +}; + +duid_id : DUID { + ElementPtr duid(new StringElement("duid", ctx.loc2pos(@1))); + ctx.stack_.back()->add(duid); +}; + +host_reservation_identifiers: HOST_RESERVATION_IDENTIFIERS { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("host-reservation-identifiers", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.HOST_RESERVATION_IDENTIFIERS); +} COLON LSQUARE_BRACKET host_reservation_identifiers_list RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +host_reservation_identifiers_list: host_reservation_identifier + | host_reservation_identifiers_list COMMA host_reservation_identifier + ; + +host_reservation_identifier: duid_id + | hw_address_id + | circuit_id + | client_id + ; + +hw_address_id : HW_ADDRESS { + ElementPtr hwaddr(new StringElement("hw-address", ctx.loc2pos(@1))); + ctx.stack_.back()->add(hwaddr); +}; + +circuit_id : CIRCUIT_ID { + ElementPtr circuit(new StringElement("circuit-id", ctx.loc2pos(@1))); + ctx.stack_.back()->add(circuit); +}; + +client_id : CLIENT_ID { + ElementPtr client(new StringElement("client-id", ctx.loc2pos(@1))); + ctx.stack_.back()->add(client); +}; + +hooks_libraries: HOOKS_LIBRARIES { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("hooks-libraries", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.HOOKS_LIBRARIES); +} COLON LSQUARE_BRACKET hooks_libraries_list RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +hooks_libraries_list: %empty + | not_empty_hooks_libraries_list + ; + +not_empty_hooks_libraries_list: hooks_library + | not_empty_hooks_libraries_list COMMA hooks_library + ; + +hooks_library: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} hooks_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +sub_hooks_library: LCURLY_BRACKET { + // Parse the hooks-libraries list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} hooks_params RCURLY_BRACKET { + // parsing completed +}; + +hooks_params: hooks_param + | hooks_params COMMA hooks_param + | unknown_map_entry + ; + +hooks_param: library + | parameters + ; + +library: LIBRARY { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr lib(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("library", lib); + ctx.leave(); +}; + +parameters: PARAMETERS { + ctx.enter(ctx.NO_KEYWORD); +} COLON value { + ctx.stack_.back()->set("parameters", $4); + ctx.leave(); +}; + +// --- expired-leases-processing ------------------------ +expired_leases_processing: EXPIRED_LEASES_PROCESSING { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("expired-leases-processing", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.NO_KEYWORD); +} COLON LCURLY_BRACKET expired_leases_params RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +expired_leases_params: expired_leases_param + | expired_leases_params COMMA expired_leases_param + ; + +// This is a bit of a simplification. But it can also serve as an example. +// Instead of explicitly listing all allowed expired leases parameters, we +// simply say that all of them as integers. +expired_leases_param: STRING COLON INTEGER { + ElementPtr value(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set($1, value); +}; + +// --- subnet4 ------------------------------------------ +// This defines subnet4 as a list of maps. +// "subnet4": [ ... ] +subnet4_list: SUBNET4 { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("subnet4", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.SUBNET4); +} COLON LSQUARE_BRACKET subnet4_list_content RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// This defines the ... in "subnet6": [ ... ] +// It can either be empty (no subnets defined), have one subnet +// or have multiple subnets separate by comma. +subnet4_list_content: %empty + | not_empty_subnet4_list + ; + +not_empty_subnet4_list: subnet4 + | not_empty_subnet4_list COMMA subnet4 + ; + +// --- Subnet definitions ------------------------------- + +// This defines a single subnet, i.e. a single map with +// subnet6 array. +subnet4: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} subnet4_params RCURLY_BRACKET { + // Once we reached this place, the subnet parsing is now complete. + // If we want to, we can implement default values here. + // In particular we can do things like this: + // if (!ctx.stack_.back()->get("interface")) { + // ctx.stack_.back()->set("interface", StringElement("loopback")); + // } + // + // We can also stack up one level (Dhcp6) and copy over whatever + // global parameters we want to: + // if (!ctx.stack_.back()->get("renew-timer")) { + // ElementPtr renew = ctx_stack_[...].get("renew-timer"); + // if (renew) { + // ctx.stack_.back()->set("renew-timer", renew); + // } + // } + ctx.stack_.pop_back(); +}; + +sub_subnet4: LCURLY_BRACKET { + // Parse the subnet6 list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} subnet4_params RCURLY_BRACKET { + // parsing completed +}; + +// This defines that subnet can have one or more parameters. +subnet4_params: subnet4_param + | subnet4_params COMMA subnet4_param + ; + +// This defines a list of allowed parameters for each subnet. +subnet4_param: valid_lifetime + | renew_timer + | rebind_timer + | option_data_list + | pools_list + | subnet + | interface + | interface_id + | id + | rapid_commit + | client_class + | reservations + | reservation_mode + | relay + | subnet_4o6_interface + | subnet_4o6_subnet + | unknown_map_entry + ; + +subnet: SUBNET { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr subnet(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("subnet", subnet); + ctx.leave(); +}; + +subnet_4o6_interface: SUBNET_4O6_INTERFACE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr iface(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("4o6-interface", iface); + ctx.leave(); +}; + +subnet_4o6_subnet: SUBNET_4O6_SUBNET { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr iface(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("4o6-subnet", iface); + ctx.leave(); +}; + +interface: INTERFACE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr iface(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("interface", iface); + ctx.leave(); +}; + +interface_id: INTERFACE_ID { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr iface(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("interface-id", iface); + ctx.leave(); +}; + +client_class: CLIENT_CLASS { + ctx.enter(ctx.CLIENT_CLASS); +} COLON STRING { + ElementPtr cls(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("client-class", cls); + ctx.leave(); +}; + +reservation_mode: RESERVATION_MODE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr rm(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("reservation-mode", rm); + ctx.leave(); +}; + +id: ID COLON INTEGER { + ElementPtr id(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("id", id); +}; + +rapid_commit: RAPID_COMMIT COLON BOOLEAN { + ElementPtr rc(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("rapid-commit", rc); +}; + +// ---- option-def -------------------------- + +// This defines the "option-def": [ ... ] entry that may appear +// at a global option. +option_def_list: OPTION_DEF { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("option-def", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.OPTION_DEF); +} COLON LSQUARE_BRACKET option_def_list_content RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// This defines the content of option-def. It may be empty, +// have one entry or multiple entries separated by comma. +option_def_list_content: %empty + | not_empty_option_def_list + ; + +not_empty_option_def_list: option_def_entry + | not_empty_option_def_list COMMA option_def_entry + ; + +// This defines the content of a single entry { ... } within +// option-def list. +option_def_entry: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} option_def_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +// This defines the top level scope when the parser is told to parse a single +// option definition. It's almost exactly the same as option_def_entry, except +// that it does leave its value on stack. +sub_option_def: LCURLY_BRACKET { + // Parse the option-def list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} option_def_params RCURLY_BRACKET { + // parsing completed +}; + +// This defines parameters specified inside the map that itself +// is an entry in option-def list. +option_def_params: %empty + | not_empty_option_def_params + ; + +not_empty_option_def_params: option_def_param + | not_empty_option_def_params COMMA option_def_param + ; + +option_def_param: option_def_name + | option_def_code + | option_def_type + | option_def_record_types + | option_def_space + | option_def_encapsulate + | option_def_array + | unknown_map_entry + ; + +option_def_name: name; + +code: CODE COLON INTEGER { + ElementPtr code(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("code", code); +}; + +option_def_code: code; + +option_def_type: type; + +option_def_record_types: RECORD_TYPES { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr rtypes(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("record-types", rtypes); + ctx.leave(); +}; + +space: SPACE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr space(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("space", space); + ctx.leave(); +}; + +option_def_space: space; + +option_def_encapsulate: ENCAPSULATE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr encap(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("encapsulate", encap); + ctx.leave(); +}; + +option_def_array: ARRAY COLON BOOLEAN { + ElementPtr array(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("array", array); +}; + +// ---- option-data -------------------------- + +// This defines the "option-data": [ ... ] entry that may appear +// in several places, but most notably in subnet4 entries. +option_data_list: OPTION_DATA { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("option-data", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.OPTION_DATA); +} COLON LSQUARE_BRACKET option_data_list_content RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// This defines the content of option-data. It may be empty, +// have one entry or multiple entries separated by comma. +option_data_list_content: %empty + | not_empty_option_data_list + ; + +// This defines the content of option-data list. It can either +// be a single value or multiple entries separated by comma. +not_empty_option_data_list: option_data_entry + | not_empty_option_data_list COMMA option_data_entry + ; + +// This defines th content of a single entry { ... } within +// option-data list. +option_data_entry: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} option_data_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +// This defines the top level scope when the parser is told to parse a single +// option data. It's almost exactly the same as option_data_entry, except +// that it does leave its value on stack. +sub_option_data: LCURLY_BRACKET { + // Parse the option-data list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} option_data_params RCURLY_BRACKET { + // parsing completed +}; + +// This defines parameters specified inside the map that itself +// is an entry in option-data list. It can either be empty +// or have a non-empty list of parameters. +option_data_params: %empty + | not_empty_option_data_params + ; + +// Those parameters can either be a single parameter or +// a list of parameters separated by comma. +not_empty_option_data_params: option_data_param + | not_empty_option_data_params COMMA option_data_param + ; + +// Each single option-data parameter can be one of the following +// expressions. +option_data_param: option_data_name + | option_data_data + | option_data_code + | option_data_space + | option_data_csv_format + | unknown_map_entry + ; + +option_data_name: name; + +option_data_data: DATA { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr data(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("data", data); + ctx.leave(); +}; + +option_data_code: code; + +option_data_space: space; + +option_data_csv_format: CSV_FORMAT COLON BOOLEAN { + ElementPtr space(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("csv-format", space); +}; + +// ---- pools ------------------------------------ + +// This defines the "pools": [ ... ] entry that may appear in subnet4. +pools_list: POOLS { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("pools", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.POOLS); +} COLON LSQUARE_BRACKET pools_list_content RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// Pools may be empty, contain a single pool entry or multiple entries +// separate by commas. +pools_list_content: %empty + | not_empty_pools_list + ; + +not_empty_pools_list: pool_list_entry + | not_empty_pools_list COMMA pool_list_entry + ; + +pool_list_entry: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} pool_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +sub_pool4: LCURLY_BRACKET { + // Parse the pool list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} pool_params RCURLY_BRACKET { + // parsing completed +}; + +pool_params: pool_param + | pool_params COMMA pool_param + ; + +pool_param: pool_entry + | option_data_list + | unknown_map_entry + ; + +pool_entry: POOL { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr pool(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("pool", pool); + ctx.leave(); +}; + +// --- end of pools definition ------------------------------- + +// --- reservations ------------------------------------------ +reservations: RESERVATIONS { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("reservations", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.RESERVATIONS); +} COLON LSQUARE_BRACKET reservations_list RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +reservations_list: %empty + | not_empty_reservations_list + ; + +not_empty_reservations_list: reservation + | not_empty_reservations_list COMMA reservation + ; + +reservation: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} reservation_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +sub_reservation: LCURLY_BRACKET { + // Parse the reservations list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} reservation_params RCURLY_BRACKET { + // parsing completed +}; + +reservation_params: %empty + | not_empty_reservation_params + ; + +not_empty_reservation_params: reservation_param + | not_empty_reservation_params COMMA reservation_param + ; + +// @todo probably need to add mac-address as well here +reservation_param: duid + | reservation_client_classes + | client_id_value + | circuit_id_value + | ip_address + | hw_address + | hostname + | option_data_list + | unknown_map_entry + ; + +ip_address: IP_ADDRESS { +} COLON STRING { + ElementPtr addr(new StringElement($4, ctx.loc2pos(@1))); + ctx.stack_.back()->set("ip-address", addr); +}; + +duid: DUID { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr d(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("duid", d); + ctx.leave(); +}; + +hw_address: HW_ADDRESS { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr hw(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("hw-address", hw); + ctx.leave(); +}; + +client_id_value: CLIENT_ID { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr hw(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("client-id", hw); + ctx.leave(); +}; + +circuit_id_value: CIRCUIT_ID { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr hw(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("circuit-id", hw); + ctx.leave(); +}; + + +hostname: HOSTNAME { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr host(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("hostname", host); + ctx.leave(); +}; + +reservation_client_classes: CLIENT_CLASSES { + ElementPtr c(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("client-classes", c); + ctx.stack_.push_back(c); + ctx.enter(ctx.NO_KEYWORD); +} COLON list2 { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// --- end of reservations definitions ----------------------- + +// --- relay ------------------------------------------------- +relay: RELAY { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("relay", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.RELAY); +} COLON LCURLY_BRACKET relay_map RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +relay_map: IP_ADDRESS { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr ip(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("ip-address", ip); + ctx.leave(); +}; + +// --- end of relay definitions ------------------------------ + +// --- client classes ---------------------------------------- +client_classes: CLIENT_CLASSES { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("client-classes", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.CLIENT_CLASSES); +} COLON LSQUARE_BRACKET client_classes_list RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +client_classes_list: client_class + | client_classes_list COMMA client_class + ; + +client_class: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} client_class_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +client_class_params: %empty + | not_empty_client_class_params + ; + +not_empty_client_class_params: client_class_param + | not_empty_client_class_params COMMA client_class_param + ; + +client_class_param: client_class_name + | client_class_test + | option_data_list + | unknown_map_entry + ; + +client_class_name: name; + +client_class_test: TEST { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr test(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("test", test); + ctx.leave(); +}; + +// --- end of client classes --------------------------------- + +// --- server-id --------------------------------------------- +server_id: SERVER_ID { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("server-id", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.SERVER_ID); +} COLON LCURLY_BRACKET server_id_params RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +server_id_params: server_id_param + | server_id_params COMMA server_id_param + ; + +server_id_param: type + | identifier + | time + | htype + | enterprise_id + | persist + | unknown_map_entry + ; + +htype: HTYPE COLON INTEGER { + ElementPtr htype(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("htype", htype); +}; + +identifier: IDENTIFIER { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr id(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("identifier", id); + ctx.leave(); +}; + +time: TIME COLON INTEGER { + ElementPtr time(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("time", time); +}; + +enterprise_id: ENTERPRISE_ID COLON INTEGER { + ElementPtr time(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("enterprise-id", time); +}; + +// --- end of server-id -------------------------------------- + +dhcp4o6_port: DHCP4O6_PORT COLON INTEGER { + ElementPtr time(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("dhcp4o6-port", time); +}; + +// code says it is a string, unit test a number +version: VERSION { + ctx.enter(ctx.NO_KEYWORD); +} COLON version_value { + ctx.stack_.back()->set("version", $4); + ctx.leave(); +}; + +version_value: + INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); } + | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); } + | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); } + ; + +// --- control socket ---------------------------------------- + +control_socket: CONTROL_SOCKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("control-socket", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.CONTROL_SOCKET); +} COLON LCURLY_BRACKET control_socket_params RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +control_socket_params: control_socket_param + | control_socket_params COMMA control_socket_param + ; + +control_socket_param: socket_type + | socket_name + ; + +socket_type: SOCKET_TYPE { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr stype(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("socket-type", stype); + ctx.leave(); +}; + +socket_name: SOCKET_NAME { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr name(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("socket-name", name); + ctx.leave(); +}; + +// --- dhcp ddns --------------------------------------------- + +dhcp_ddns: DHCP_DDNS { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("dhcp-ddns", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.NO_KEYWORD); +} COLON LCURLY_BRACKET not_empty_map RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// JSON entries for Dhcp4 and DhcpDdns + +dhcp6_json_object: DHCP6 { + ctx.enter(ctx.NO_KEYWORD); +} COLON value { + ctx.stack_.back()->set("Dhcp6", $4); + ctx.leave(); +}; + +dhcpddns_json_object: DHCPDDNS { + ctx.enter(ctx.NO_KEYWORD); +} COLON value { + ctx.stack_.back()->set("DhcpDdns", $4); + ctx.leave(); +}; + +// --- logging entry ----------------------------------------- + +// This defines the top level "Logging" object. It parses +// the following "Logging": { ... }. The ... is defined +// by logging_params +logging_object: LOGGING { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("Logging", m); + ctx.stack_.push_back(m); + ctx.enter(ctx.LOGGING); +} COLON LCURLY_BRACKET logging_params RCURLY_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// This defines the list of allowed parameters that may appear +// in the top-level Logging object. It can either be a single +// parameter or several parameters separated by commas. +logging_params: logging_param + | logging_params COMMA logging_param + ; + +// There's currently only one parameter defined, which is "loggers". +logging_param: loggers; + +// "loggers", the only parameter currently defined in "Logging" object, +// is "Loggers": [ ... ]. +loggers: LOGGERS { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("loggers", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.LOGGERS); +} COLON LSQUARE_BRACKET loggers_entries RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +// These are the parameters allowed in loggers: either one logger +// entry or multiple entries separate by commas. +loggers_entries: logger_entry + | loggers_entries COMMA logger_entry + ; + +// This defines a single entry defined in loggers in Logging. +logger_entry: LCURLY_BRACKET { + ElementPtr l(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(l); + ctx.stack_.push_back(l); +} logger_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +logger_params: logger_param + | logger_params COMMA logger_param + ; + +logger_param: name + | output_options_list + | debuglevel + | severity + | unknown_map_entry + ; + +debuglevel: DEBUGLEVEL COLON INTEGER { + ElementPtr dl(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("debuglevel", dl); +}; +severity: SEVERITY { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr sev(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("severity", sev); + ctx.leave(); +}; + +output_options_list: OUTPUT_OPTIONS { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("output_options", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.OUTPUT_OPTIONS); +} COLON LSQUARE_BRACKET output_options_list_content RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +output_options_list_content: output_entry + | output_options_list_content COMMA output_entry + ; + +output_entry: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} output_params RCURLY_BRACKET { + ctx.stack_.pop_back(); +}; + +output_params: output_param + | output_params COMMA output_param + ; + +output_param: OUTPUT { + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr sev(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("output", sev); + ctx.leave(); +}; + +%% + +void +isc::dhcp::Dhcp4Parser::error(const location_type& loc, + const std::string& what) +{ + ctx.error(loc, what); +} diff --git a/src/bin/dhcp4/parser_context.cc b/src/bin/dhcp4/parser_context.cc new file mode 100644 index 0000000000..250f4dbc8f --- /dev/null +++ b/src/bin/dhcp4/parser_context.cc @@ -0,0 +1,168 @@ +// Copyright (C) 2016 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 + +namespace isc { +namespace dhcp { + +Parser4Context::Parser4Context() + : trace_scanning_(false), trace_parsing_(false) +{ +} + +Parser4Context::~Parser4Context() +{ +} + +isc::data::ConstElementPtr +Parser4Context::parseString(const std::string& str, ParserType parser_type) +{ + scanStringBegin(str, parser_type); + return (parseCommon()); +} + +isc::data::ConstElementPtr +Parser4Context::parseFile(const std::string& filename, ParserType parser_type) { + FILE* f = fopen(filename.c_str(), "r"); + if (!f) { + isc_throw(Dhcp4ParseError, "Unable to open file " << filename); + } + scanFileBegin(f, filename, parser_type); + return (parseCommon()); +} + +isc::data::ConstElementPtr +Parser4Context::parseCommon() { + isc::dhcp::Dhcp4Parser parser(*this); + // Uncomment this to get detailed parser logs. + // trace_parsing_ = true; + parser.set_debug_level(trace_parsing_); + try { + int res = parser.parse(); + if (res != 0) { + isc_throw(Dhcp4ParseError, "Parser abort"); + } + scanEnd(); + } + catch (...) { + scanEnd(); + throw; + } + if (stack_.size() == 1) { + return (stack_[0]); + } else { + isc_throw(Dhcp4ParseError, "Expected exactly one terminal Element expected, found " + << stack_.size()); + } +} + + +void +Parser4Context::error(const isc::dhcp::location& loc, const std::string& what) +{ + isc_throw(Dhcp4ParseError, loc << ": " << what); +} + +void +Parser4Context::error (const std::string& what) +{ + isc_throw(Dhcp4ParseError, what); +} + +void +Parser4Context::fatal (const std::string& what) +{ + isc_throw(Dhcp4ParseError, what); +} + +isc::data::Element::Position +Parser4Context::loc2pos(isc::dhcp::location& loc) +{ + const std::string& file = *loc.begin.filename; + const uint32_t line = loc.begin.line; + const uint32_t pos = loc.begin.column; + return (isc::data::Element::Position(file, line, pos)); +} + +void +Parser4Context::enter(const ParserContext& ctx) +{ + cstack_.push_back(ctx_); + ctx_ = ctx; +} + +void +Parser4Context::leave() +{ +#if 1 + if (cstack_.empty()) { + fatal("unbalanced syntactic context"); + } +#endif + ctx_ = cstack_.back(); + cstack_.pop_back(); +} + +const std::string +Parser4Context::contextName() +{ + switch (ctx_) { + case NO_KEYWORD: + return ("__no keyword__"); + case CONFIG: + return ("toplevel"); + case DHCP4: + return ("Dhcp4"); + case LOGGING: + return ("Logging"); + case INTERFACES_CONFIG: + return ("interfaces-config"); + case LEASE_DATABASE: + return ("lease-database"); + case HOSTS_DATABASE: + return ("hosts-database"); + case HOST_RESERVATION_IDENTIFIERS: + return ("host-reservation-identifiers"); + case HOOKS_LIBRARIES: + return ("hooks-librairies"); + case SUBNET4: + return ("subnet4"); + case OPTION_DEF: + return ("option-def"); + case OPTION_DATA: + return ("option-data"); + case CLIENT_CLASSES: + return ("client-classes"); + case SERVER_ID: + return ("server-id"); + case CONTROL_SOCKET: + return ("control-socket"); + case POOLS: + return ("pools"); + case RESERVATIONS: + return ("reservations"); + case RELAY: + return ("relay"); + case CLIENT_CLASS: + return ("client-class"); + case LOGGERS: + return ("loggers"); + case OUTPUT_OPTIONS: + return ("output-options"); + default: + return ("__unknown__"); + } +} + +}; +}; diff --git a/src/bin/dhcp4/parser_context.h b/src/bin/dhcp4/parser_context.h new file mode 100644 index 0000000000..14e4015613 --- /dev/null +++ b/src/bin/dhcp4/parser_context.h @@ -0,0 +1,321 @@ +// Copyright (C) 2015-2016 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/. + +#ifndef PARSER_CONTEXT_H +#define PARSER_CONTEXT_H +#include +#include +#include +#include +#include +#include + +// Tell Flex the lexer's prototype ... +#define YY_DECL isc::dhcp::Dhcp4Parser::symbol_type parser4_lex (Parser4Context& driver) + +// ... and declare it for the parser's sake. +YY_DECL; + +namespace isc { +namespace dhcp { + +/// @brief Evaluation error exception raised when trying to parse. +/// +/// @todo: This probably should be common for Dhcp4 and Dhcp6. +class Dhcp4ParseError : public isc::Exception { +public: + Dhcp4ParseError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Evaluation context, an interface to the expression evaluation. +class Parser4Context +{ +public: + + /// @brief Defines currently supported scopes + /// + /// Dhcp4Parser is able to parse several types of scope. Usually, + /// when it parses a config file, it expects the data to have a map + /// with Dhcp4 in it and all the parameters within that Dhcp4 map. + /// However, sometimes the parser is expected to parse only a subset + /// of that information. For example, it may be asked to parse + /// a structure that is host-reservation only, without the global + /// 'Dhcp4' or 'reservations' around it. In such case the parser + /// is being told to start parsing as PARSER_HOST_RESERVATION4. + typedef enum { + /// This parser will parse the content as generic JSON. + PARSER_JSON, + + /// This parser will parse the content as Dhcp4 config wrapped in a map + /// (that's the regular config file) + PARSER_DHCP4, + + /// This parser will parse the content of Dhcp4 (without outer { } and + /// without "Dhcp4"). It is mostly used in unit-tests as most of the + /// unit-tests do not define the outer map and Dhcp4 entity, just the + /// contents of it. + SUBPARSER_DHCP4, + + /// This will parse the input as interfaces content. + PARSER_INTERFACES, + + /// This will parse the input as Subnet6 content. + PARSER_SUBNET4, + + /// This will parse the input as pool6 content. + PARSER_POOL4, + + /// This will parse the input as host-reservation. + PARSER_HOST_RESERVATION, + + /// This will parse the input as option definition. + PARSER_OPTION_DEF, + + /// This will parse the input as option data. + PARSER_OPTION_DATA, + + /// This will parse the input as hooks-library. + PARSER_HOOKS_LIBRARY + } ParserType; + + /// @brief Default constructor. + Parser4Context(); + + /// @brief destructor + virtual ~Parser4Context(); + + /// @brief JSON elements being parsed. + std::vector stack_; + + /// @brief Method called before scanning starts on a string. + /// + /// @param str string to be parsed + /// @param parser_type specifies expected content + void scanStringBegin(const std::string& str, ParserType type); + + /// @brief Method called before scanning starts on a file. + /// + /// @param f stdio FILE pointer + /// @param filename file to be parsed + /// @param parser_type specifies expected content + void scanFileBegin(FILE* f, const std::string& filename, ParserType type); + + /// @brief Method called after the last tokens are scanned. + void scanEnd(); + + /// @brief Divert input to an include file. + /// + /// @param filename file to be included + void includeFile(const std::string& filename); + + /// @brief Run the parser on the string specified. + /// + /// This method parses specified string. Depending on the value of + /// parser_type, parser may either check only that the input is valid + /// JSON, or may do more specific syntax checking. See @ref ParserType + /// for supported syntax checkers. + /// + /// @param str string to be parsed + /// @param parser_type specifies expected content (usually DHCP4 or generic JSON) + /// @return Element structure representing parsed text. + isc::data::ConstElementPtr parseString(const std::string& str, + ParserType parser_type); + + /// @brief Run the parser on the file specified. + /// + /// This method parses specified file. Depending on the value of + /// parser_type, parser may either check only that the input is valid + /// JSON, or may do more specific syntax checking. See @ref ParserType + /// for supported syntax checkers. + /// + /// @param filename file to be parsed + /// @param parser_type specifies expected content (usually DHCP4 or generic JSON) + /// @return Element structure representing parsed text. + isc::data::ConstElementPtr parseFile(const std::string& filename, + ParserType parser_type); + + /// @brief Error handler + /// + /// @param loc location within the parsed file when experienced a problem. + /// @param what string explaining the nature of the error. + /// @throw Dhcp4ParseError + void error(const isc::dhcp::location& loc, const std::string& what); + + /// @brief Error handler + /// + /// This is a simplified error reporting tool for possible future + /// cases when the Dhcp4Parser is not able to handle the packet. + /// + /// @param what string explaining the nature of the error. + /// @throw Dhcp4ParseError + void error(const std::string& what); + + /// @brief Fatal error handler + /// + /// This is for should not happen but fatal errors. + /// Used by YY_FATAL_ERROR macro so required to be static. + /// + /// @param what string explaining the nature of the error. + /// @throw Dhcp4ParseError + static void fatal(const std::string& what); + + /// @brief Converts bison's position to one understandable by isc::data::Element + /// + /// Convert a bison location into an element position + /// (take the begin, the end is lost) + /// + /// @param loc location in bison format + /// @return Position in format accepted by Element + isc::data::Element::Position loc2pos(isc::dhcp::location& loc); + + /// @brief Defines syntactic contexts for lexical tie-ins + typedef enum { + ///< This one is used in pure JSON mode. + NO_KEYWORD, + + ///< Used while parsing top level (that contains Dhcp4, Logging and others) + CONFIG, + + ///< Used while parsing content of Dhcp4. + DHCP4, + + // not yet DHCP6, + // not yet DHCP_DDNS, + + ///< Used while parsing content of Logging + LOGGING, + + /// Used while parsing Dhcp4/interfaces structures. + INTERFACES_CONFIG, + + /// Used while parsing Dhcp4/lease-database structures. + LEASE_DATABASE, + + /// Used while parsing Dhcp4/hosts-database structures. + HOSTS_DATABASE, + + /// Used while parsing Dhcp4/host-reservation-identifiers. + HOST_RESERVATION_IDENTIFIERS, + + /// Used while parsing Dhcp4/hooks-libraries. + HOOKS_LIBRARIES, + + /// Used while parsing Dhcp4/Subnet6 structures. + SUBNET4, + + /// Used while parsing Dhcp4/option-def structures. + OPTION_DEF, + + /// Used while parsing Dhcp4/option-data, Dhcp4/subnet6/option-data + /// or anywhere option-data is present (client classes, host + /// reservations and possibly others). + OPTION_DATA, + + /// Used while parsing Dhcp4/client-classes structures. + CLIENT_CLASSES, + + /// Used while parsing Dhcp4/server-id structures. + SERVER_ID, + + /// Used while parsing Dhcp4/control-socket structures. + CONTROL_SOCKET, + + /// Used while parsing Dhcp4/subnet6/pools structures. + POOLS, + + /// Used while parsing Dhcp4/reservations structures. + RESERVATIONS, + + /// Used while parsing Dhcp4/subnet6/relay structures. + RELAY, + + /// Used while parsing Dhcp4/client-classes structures. + CLIENT_CLASS, + + /// Used while parsing Logging/loggers structures. + LOGGERS, + + /// Used while parsing Logging/loggers/output_options structures. + OUTPUT_OPTIONS + } ParserContext; + + /// @brief File name + std::string file_; + + /// @brief File name stack + std::vector files_; + + /// @brief Location of the current token + /// + /// The lexer will keep updating it. This variable will be useful + /// for logging errors. + isc::dhcp::location loc_; + + /// @brief Location stack + std::vector locs_; + + /// @brief Lexer state stack + std::vector states_; + + /// @brief sFile (aka FILE) + FILE* sfile_; + + /// @brief sFile (aka FILE) stack + /// + /// This is a stack of files. Typically there's only one file (the + /// one being currently parsed), but there may be more if one + /// file includes another. + std::vector sfiles_; + + /// @brief Current syntactic context + ParserContext ctx_; + + /// @brief Enter a new syntactic context + /// + /// Entering a new syntactic context is useful in several ways. + /// First, it allows the parser to avoid conflicts. Second, it + /// allows the lexer to return different tokens depending on + /// context (e.g. if "renew-timer" string is detected, the lexer + /// will return STRING token if in JSON mode or RENEW_TIMER if + /// in DHCP4 mode. Finally, the syntactic context allows the + /// error message to be more descriptive if the input string + /// does not parse properly. + /// + /// @param ctx the syntactic context to enter into + void enter(const ParserContext& ctx); + + /// @brief Leave a syntactic context + /// + /// @throw isc::Unexpected if unbalanced + void leave(); + + /// @brief Get the syntactix context name + /// + /// @return printable name of the context. + const std::string contextName(); + + private: + /// @brief Flag determining scanner debugging. + bool trace_scanning_; + + /// @brief Flag determing parser debugging. + bool trace_parsing_; + + /// @brief Syntactic context stack + std::vector cstack_; + + /// @brief Common part of parseXXX + /// + /// @return Element structure representing parsed text. + isc::data::ConstElementPtr parseCommon(); +}; + +}; // end of isc::eval namespace +}; // end of isc namespace + +#endif diff --git a/src/bin/dhcp4/parser_context_decl.h b/src/bin/dhcp4/parser_context_decl.h new file mode 100644 index 0000000000..367f163492 --- /dev/null +++ b/src/bin/dhcp4/parser_context_decl.h @@ -0,0 +1,20 @@ +// Copyright (C) 2016 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/. + +#ifndef PARSER6_CONTEXT_DECL_H +#define PARSER6_CONTEXT_DECL_H + +/// @file parser_context_decl.h Forward declaration of the ParserContext class + +namespace isc { +namespace dhcp { + +class Parser4Context; + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index f0b5babb3d..ff78e5765e 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -23,6 +23,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin AM_CPPFLAGS += $(BOOST_INCLUDES) AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp4/tests\" AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" +AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea4\" CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt @@ -88,6 +89,7 @@ dhcp4_unittests_SOURCES += inform_unittest.cc dhcp4_unittests_SOURCES += dora_unittest.cc dhcp4_unittests_SOURCES += host_options_unittest.cc dhcp4_unittests_SOURCES += release_unittest.cc +dhcp4_unittests_SOURCES += parser_unittest.cc dhcp4_unittests_SOURCES += out_of_range_unittest.cc dhcp4_unittests_SOURCES += decline_unittest.cc dhcp4_unittests_SOURCES += kea_controller_unittest.cc diff --git a/src/bin/dhcp4/tests/parser_unittest.cc b/src/bin/dhcp4/tests/parser_unittest.cc new file mode 100644 index 0000000000..46b0dfea68 --- /dev/null +++ b/src/bin/dhcp4/tests/parser_unittest.cc @@ -0,0 +1,475 @@ +// Copyright (C) 2016 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 + +using namespace isc::data; +using namespace std; + +namespace { + +void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) { + ASSERT_TRUE(a); + ASSERT_TRUE(b); + if (print) { + // std::cout << "JSON A: -----" << endl << a->str() << std::endl; + // std::cout << "JSON B: -----" << endl << b->str() << std::endl; + // cout << "---------" << endl << endl; + } + EXPECT_EQ(a->str(), b->str()); +} + +void testParser(const std::string& txt, Parser4Context::ParserType parser_type) { + ElementPtr reference_json; + ConstElementPtr test_json; + + ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true)); + ASSERT_NO_THROW({ + try { + Parser4Context ctx; + test_json = ctx.parseString(txt, parser_type); + } catch (const std::exception &e) { + cout << "EXCEPTION: " << e.what() << endl; + throw; + } + + }); + + // Now compare if both representations are the same. + compareJSON(reference_json, test_json); +} + +void testParser2(const std::string& txt, Parser4Context::ParserType parser_type) { + ConstElementPtr test_json; + + ASSERT_NO_THROW({ + try { + Parser4Context ctx; + test_json = ctx.parseString(txt, parser_type); + } catch (const std::exception &e) { + cout << "EXCEPTION: " << e.what() << endl; + throw; + } + }); + /// @todo: Implement actual validation here. since the original + /// Element::fromJSON does not support several comment types, we don't + /// have anything to compare with. + /// std::cout << "Original text:" << txt << endl; + /// std::cout << "Parsed text :" << test_json->str() << endl; +} + +TEST(ParserTest, mapInMap) { + string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, listInList) { + string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], " + "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, nestedMaps) { + string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, nestedLists) { + string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, listsInMaps) { + string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], " + "\"cygnus\": [ \"deneb\", \"albireo\"] } }"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, mapsInLists) { + string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 }," + " { \"body\": \"mars\", \"gravity\": 0.376 } ]"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, types) { + string txt = "{ \"string\": \"foo\"," + "\"integer\": 42," + "\"boolean\": true," + "\"map\": { \"foo\": \"bar\" }," + "\"list\": [ 1, 2, 3 ]," + "\"null\": null }"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, keywordJSON) { + string txt = "{ \"name\": \"user\"," + "\"type\": \"password\"," + "\"user\": \"name\"," + "\"password\": \"type\" }"; + testParser(txt, Parser4Context::PARSER_JSON); +} + +TEST(ParserTest, keywordDhcp4) { + string txt = "{ \"Dhcp4\": { \"interfaces-config\": {" + " \"interfaces\": [ \"type\", \"htype\" ] },\n" + "\"rebind-timer\": 2000, \n" + "\"renew-timer\": 1000, \n" + "\"subnet4\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"test\" } ],\n" + "\"valid-lifetime\": 4000 } }"; + testParser2(txt, Parser4Context::PARSER_DHCP4); +} + +TEST(ParserTest, bashComments) { + string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"preferred-lifetime\": 3000,\n" + "# this is a comment\n" + "\"rebind-timer\": 2000, \n" + "# lots of comments here\n" + "# and here\n" + "\"renew-timer\": 1000, \n" + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + testParser2(txt, Parser4Context::PARSER_DHCP4); +} + +TEST(ParserTest, cComments) { + string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"preferred-lifetime\": 3000, // this is a comment \n" + "\"rebind-timer\": 2000, // everything after // is ignored\n" + "\"renew-timer\": 1000, // this will be ignored, too\n" + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + testParser2(txt, Parser4Context::PARSER_DHCP4); +} + +TEST(ParserTest, bashCommentsInline) { + string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"preferred-lifetime\": 3000, # this is a comment \n" + "\"rebind-timer\": 2000, # everything after # is ignored\n" + "\"renew-timer\": 1000, # this will be ignored, too\n" + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + testParser2(txt, Parser4Context::PARSER_DHCP4); +} + +TEST(ParserTest, multilineComments) { + string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"preferred-lifetime\": 3000, /* this is a C style comment\n" + "that\n can \n span \n multiple \n lines */ \n" + "\"rebind-timer\": 2000,\n" + "\"renew-timer\": 1000, \n" + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + testParser2(txt, Parser4Context::PARSER_DHCP4); +} + + +void testFile(const std::string& fname, bool print) { + ElementPtr reference_json; + ConstElementPtr test_json; + + cout << "Attempting to load file " << fname << endl; + + EXPECT_NO_THROW(reference_json = Element::fromJSONFile(fname, true)); + + EXPECT_NO_THROW( + try { + Parser4Context ctx; + test_json = ctx.parseFile(fname, Parser4Context::PARSER_DHCP4); + } catch (const std::exception &x) { + cout << "EXCEPTION: " << x.what() << endl; + throw; + }); + + ASSERT_TRUE(reference_json); + ASSERT_TRUE(test_json); + + compareJSON(reference_json, test_json, print); + + +} + +// This test loads all available existing files. Each config is loaded +// twice: first with the existing Element::fromJSONFile() and then +// the second time with Parser6. Both JSON trees are then compared. +TEST(ParserTest, file) { + vector configs; + configs.push_back("backends.json"); + configs.push_back("classify.json"); + configs.push_back("dhcpv4-over-dhcpv6.json"); + configs.push_back("hooks.json"); + configs.push_back("leases-expiration.json"); + configs.push_back("multiple-options.json"); + configs.push_back("mysql-reservations.json"); + configs.push_back("pgsql-reservations.json"); + configs.push_back("reservations.json"); + configs.push_back("several-subnets.json"); + configs.push_back("single-subnet.json"); + + for (int i = 0; i:1.1: syntax error, unexpected end of file"); + testError(" ", Parser4Context::PARSER_JSON, + ":1.2: syntax error, unexpected end of file"); + testError("\n", Parser4Context::PARSER_JSON, + ":2.1: syntax error, unexpected end of file"); + testError("\t", Parser4Context::PARSER_JSON, + ":1.2: syntax error, unexpected end of file"); + testError("\r", Parser4Context::PARSER_JSON, + ":1.2: syntax error, unexpected end of file"); + + // comments + testError("# nothing\n", + Parser4Context::PARSER_JSON, + ":2.1: syntax error, unexpected end of file"); + testError(" #\n", + Parser4Context::PARSER_JSON, + ":2.1: syntax error, unexpected end of file"); + testError("// nothing\n", + Parser4Context::PARSER_JSON, + ":2.1: syntax error, unexpected end of file"); + testError("/* nothing */\n", + Parser4Context::PARSER_JSON, + ":2.1: syntax error, unexpected end of file"); + testError("/* no\nthing */\n", + Parser4Context::PARSER_JSON, + ":3.1: syntax error, unexpected end of file"); + testError("/* no\nthing */\n\n", + Parser4Context::PARSER_JSON, + ":4.1: syntax error, unexpected end of file"); + testError("/* nothing\n", + Parser4Context::PARSER_JSON, + "Comment not closed. (/* in line 1"); + testError("\n\n\n/* nothing\n", + Parser4Context::PARSER_JSON, + "Comment not closed. (/* in line 4"); + testError("{ /* */*/ }\n", + Parser4Context::PARSER_JSON, + ":1.3-8: Invalid character: *"); + testError("{ /* // *// }\n", + Parser4Context::PARSER_JSON, + ":1.3-11: Invalid character: /"); + testError("{ /* // */// }\n", + Parser4Context::PARSER_JSON, + ":2.1: syntax error, unexpected end of file, " + "expecting }"); + + // includes + testError("/n", + Parser4Context::PARSER_JSON, + "Can't open include file /foo/bar"); + + // case sensitivity + testError("{ \"foo\": True }", + Parser4Context::PARSER_JSON, + ":1.10: Invalid character: T"); + testError("{ \"foo\": NULL }", + Parser4Context::PARSER_JSON, + ":1.10: Invalid character: N"); + + // numbers + testError("123", + Parser4Context::PARSER_DHCP4, + ":1.1-3: syntax error, unexpected integer, " + "expecting {"); + testError("-456", + Parser4Context::PARSER_DHCP4, + ":1.1-4: syntax error, unexpected integer, " + "expecting {"); + testError("-0001", + Parser4Context::PARSER_DHCP4, + ":1.1-5: syntax error, unexpected integer, " + "expecting {"); + testError("1234567890123456789012345678901234567890", + Parser4Context::PARSER_JSON, + ":1.1-40: Failed to convert " + "1234567890123456789012345678901234567890" + " to an integer."); + testError("-3.14e+0", + Parser4Context::PARSER_DHCP4, + ":1.1-8: syntax error, unexpected floating point, " + "expecting {"); + testError("1e50000", + Parser4Context::PARSER_JSON, + ":1.1-7: Failed to convert 1e50000 " + "to a floating point."); + + // strings + testError("\"aabb\"", + Parser4Context::PARSER_DHCP4, + ":1.1-6: syntax error, unexpected constant string, " + "expecting {"); + testError("{ \"aabb\"err", + Parser4Context::PARSER_JSON, + ":1.9: Invalid character: e"); + testError("{ err\"aabb\"", + Parser4Context::PARSER_JSON, + ":1.3: Invalid character: e"); + testError("\"a\n\tb\"", + Parser4Context::PARSER_JSON, + ":1.1-6: Invalid control in \"a\n\tb\""); + testError("\"a\\n\\tb\"", + Parser4Context::PARSER_DHCP4, + ":1.1-8: syntax error, unexpected constant string, " + "expecting {"); + testError("\"a\\x01b\"", + Parser4Context::PARSER_JSON, + ":1.1-8: Bad escape in \"a\\x01b\""); + testError("\"a\\u0062\"", + Parser4Context::PARSER_JSON, + ":1.1-9: Unsupported unicode escape in \"a\\u0062\""); + testError("\"a\\u062z\"", + Parser4Context::PARSER_JSON, + ":1.1-9: Bad escape in \"a\\u062z\""); + testError("\"abc\\\"", + Parser4Context::PARSER_JSON, + ":1.1-6: Overflow escape in \"abc\\\""); + + // from data_unittest.c + testError("\\a", + Parser4Context::PARSER_JSON, + ":1.1: Invalid character: \\"); + testError("\\", + Parser4Context::PARSER_JSON, + ":1.1: Invalid character: \\"); + testError("\\\"\\\"", + Parser4Context::PARSER_JSON, + ":1.1: Invalid character: \\"); + + // want a map + testError("[]\n", + Parser4Context::PARSER_DHCP4, + ":1.1: syntax error, unexpected [, " + "expecting {"); + testError("[]\n", + Parser4Context::PARSER_DHCP4, + ":1.1: syntax error, unexpected [, " + "expecting {"); + testError("{ 123 }\n", + Parser4Context::PARSER_JSON, + ":1.3-5: syntax error, unexpected integer, " + "expecting }"); + testError("{ 123 }\n", + Parser4Context::PARSER_DHCP4, + ":1.3-5: syntax error, unexpected integer"); + testError("{ \"foo\" }\n", + Parser4Context::PARSER_JSON, + ":1.9: syntax error, unexpected }, " + "expecting :"); + testError("{ \"foo\" }\n", + Parser4Context::PARSER_DHCP4, + ":1.9: syntax error, unexpected }, expecting :"); + testError("{ \"foo\":null }\n", + Parser4Context::PARSER_DHCP4, + ":1.3-7: got unexpected keyword " + "\"foo\" in toplevel map."); + testError("{ \"Dhcp4\" }\n", + Parser4Context::PARSER_DHCP4, + ":1.11: syntax error, unexpected }, " + "expecting :"); + testError("{ \"Dhcp6\":[]\n", + Parser4Context::PARSER_DHCP4, + ":2.1: syntax error, unexpected end of file, " + "expecting \",\" or }"); + testError("{}{}\n", + Parser4Context::PARSER_JSON, + ":1.3: syntax error, unexpected {, " + "expecting end of file"); + + // bad commas + testError("{ , }\n", + Parser4Context::PARSER_JSON, + ":1.3: syntax error, unexpected \",\", " + "expecting }"); + testError("{ , \"foo\":true }\n", + Parser4Context::PARSER_JSON, + ":1.3: syntax error, unexpected \",\", " + "expecting }"); + testError("{ \"foo\":true, }\n", + Parser4Context::PARSER_JSON, + ":1.15: syntax error, unexpected }, " + "expecting constant string"); + + // bad type + testError("{ \"Dhcp4\":{\n" + " \"valid-lifetime\":false }}\n", + Parser4Context::PARSER_DHCP4, + ":2.20-24: syntax error, unexpected boolean, " + "expecting integer"); + + // unknown keyword + testError("{ \"Dhcp4\":{\n" + " \"valid_lifetime\":600 }}\n", + Parser4Context::PARSER_DHCP4, + ":2.2-17: got unexpected keyword " + "\"valid_lifetime\" in Dhcp4 map."); +} + +};