Browse Source

[master] Merge branch 'trac5017' (dhcp4 bison parser)

# Conflicts:
#	doc/examples/kea4/classify.json
Tomek Mrugalski 8 years ago
parent
commit
1cf853c595
51 changed files with 17798 additions and 2158 deletions
  1. 4 0
      doc/Makefile.am
  2. 107 0
      doc/examples/kea4/advanced.json
  3. 9 3
      doc/examples/kea4/classify.json
  4. 5 1
      doc/examples/kea4/hooks.json
  5. 10 0
      doc/examples/kea4/reservations.json
  6. 28 7
      doc/examples/kea4/several-subnets.json
  7. 7 11
      doc/guide/config.xml
  8. 1 0
      src/bin/dhcp4/.gitignore
  9. 31 0
      src/bin/dhcp4/Makefile.am
  10. 37 0
      src/bin/dhcp4/dhcp4.dox
  11. 4075 0
      src/bin/dhcp4/dhcp4_lexer.cc
  12. 1210 0
      src/bin/dhcp4/dhcp4_lexer.ll
  13. 3503 0
      src/bin/dhcp4/dhcp4_parser.cc
  14. 2128 0
      src/bin/dhcp4/dhcp4_parser.h
  15. 1530 0
      src/bin/dhcp4/dhcp4_parser.yy
  16. 1 2
      src/bin/dhcp4/json_config_parser.cc
  17. 3 1
      src/bin/dhcp4/kea_controller.cc
  18. 193 0
      src/bin/dhcp4/location.hh
  19. 168 0
      src/bin/dhcp4/parser_context.cc
  20. 321 0
      src/bin/dhcp4/parser_context.h
  21. 20 0
      src/bin/dhcp4/parser_context_decl.h
  22. 181 0
      src/bin/dhcp4/position.hh
  23. 158 0
      src/bin/dhcp4/stack.hh
  24. 2 0
      src/bin/dhcp4/tests/Makefile.am
  25. 1 2
      src/bin/dhcp4/tests/classify_unittest.cc
  26. 232 176
      src/bin/dhcp4/tests/config_parser_unittest.cc
  27. 3 1
      src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
  28. 27 15
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  29. 2 1
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  30. 57 0
      src/bin/dhcp4/tests/dhcp4_test_utils.h
  31. 4 2
      src/bin/dhcp4/tests/hooks_unittest.cc
  32. 204 16
      src/bin/dhcp4/tests/kea_controller_unittest.cc
  33. 611 0
      src/bin/dhcp4/tests/parser_unittest.cc
  34. 1 1
      src/bin/dhcp6/Makefile.am
  35. 1569 544
      src/bin/dhcp6/dhcp6_lexer.cc
  36. 44 12
      src/bin/dhcp6/dhcp6_lexer.ll
  37. 1155 1204
      src/bin/dhcp6/dhcp6_parser.cc
  38. 54 70
      src/bin/dhcp6/dhcp6_parser.h
  39. 1 18
      src/bin/dhcp6/dhcp6_parser.yy
  40. 0 3
      src/bin/dhcp6/json_config_parser.cc
  41. 1 1
      src/bin/dhcp6/location.hh
  42. 1 1
      src/bin/dhcp6/parser_context_decl.h
  43. 1 1
      src/bin/dhcp6/position.hh
  44. 1 1
      src/bin/dhcp6/stack.hh
  45. 2 16
      src/bin/dhcp6/tests/config_parser_unittest.cc
  46. 55 9
      src/bin/dhcp6/tests/parser_unittest.cc
  47. 13 8
      src/lib/cc/data.cc
  48. 15 19
      src/lib/cc/tests/data_unittests.cc
  49. 1 1
      src/lib/config/documentation.txt
  50. 3 3
      src/lib/dhcpsrv/parsers/dhcp_parsers.cc
  51. 8 8
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

+ 4 - 0
doc/Makefile.am

@@ -8,8 +8,10 @@ EXTRA_DIST += devel/unit-tests.dox
 
 nobase_dist_doc_DATA  = examples/ddns/sample1.json
 nobase_dist_doc_DATA += examples/ddns/template.json
+nobase_dist_doc_DATA += examples/kea4/advanced.json
 nobase_dist_doc_DATA += examples/kea4/backends.json
 nobase_dist_doc_DATA += examples/kea4/classify.json
+nobase_dist_doc_DATA += examples/kea4/dhcpv4-over-dhcpv6.json
 nobase_dist_doc_DATA += examples/kea4/hooks.json
 nobase_dist_doc_DATA += examples/kea4/leases-expiration.json
 nobase_dist_doc_DATA += examples/kea4/multiple-options.json
@@ -21,6 +23,8 @@ nobase_dist_doc_DATA += examples/kea4/single-subnet.json
 nobase_dist_doc_DATA += examples/kea6/advanced.json
 nobase_dist_doc_DATA += examples/kea6/backends.json
 nobase_dist_doc_DATA += examples/kea6/classify.json
+nobase_dist_doc_DATA += examples/kea6/dhcpv4-over-dhcpv6.json
+nobase_dist_doc_DATA += examples/kea6/duid.json
 nobase_dist_doc_DATA += examples/kea6/hooks.json
 nobase_dist_doc_DATA += examples/kea6/leases-expiration.json
 nobase_dist_doc_DATA += examples/kea6/multiple-options.json

+ 107 - 0
doc/examples/kea4/advanced.json

@@ -0,0 +1,107 @@
+// This is an example configuration file for DHCPv4 server in Kea.
+// It covers some of the more advanced features. This file may not be coherent
+// as its main purpose is to demonstrate the features. They don't necessarily
+// have to make sense used together.
+
+// The new parser supports 3 comment styles:
+
+// This is C++ style.
+
+# This is a bash style.
+
+/* This is a C style comment. */
+
+/* C style comment
+   can span
+   multiple lines */
+
+{ "Dhcp4":
+
+{
+    // Kea is told to listen on ethX interface only.
+    "interfaces-config": {
+        "interfaces": [ "ethX" ],
+
+        // This specifies what type of socket Kea uses. Currently supported
+        // are 'raw' (which is the default) and 'udp'. Raw has the benefit
+        // of receiving all traffic every time and a downside of bypassing
+        // all firewall rules and having marginally bigger performance impact.
+        // 'udp' is generally better if you have only relayed traffic. Kea
+        // than opens up normal UDP socket and the kernel does all the
+        // Ethernet/IP stack processing.
+        "dhcp-socket-type": "udp"
+    },
+
+    // We need to specify the the database used to store leases. As of
+    // September 2016, four database backends are supported: MySQL,
+    // PostgreSQL, Cassandra, and the in-memory database, Memfile.
+    // We'll use memfile  because it doesn't require any prior set up.
+    // For memfile, it's important to always specify lfc-interval, so
+    // the lease file would not grow without bounds and be sanitized
+    // once per hour.
+    "lease-database": {
+        "type": "memfile",
+        "lfc-interval": 3600
+    },
+
+    // Addresses will be assigned with a lifetime of 4000 seconds.
+    // The client is told to start renewing after 1000 seconds. If the server
+    // does not respond within 2000 seconds of the lease being granted, client
+    // is supposed to start REBIND procedure (emergency renewal that allows
+    // switching to a different server).
+    "valid-lifetime": 4000,
+    "renew-timer": 1000,
+    "rebind-timer": 2000,
+
+    // RFC6842 says that the server is supposed to echo back client-id option.
+    // However, some older clients do not support this and are getting confused
+    // when they get their own client-id. Kea can disable RFC6842 support.
+    "echo-client-id": false,
+
+    // Some clients don't use stable client identifier, but rather generate them
+    // during each boot. This may cause a client that reboots frequently to get
+    // multiple leases, which may not be desirable. As such, sometimes admins
+    // prefer to tell their DHCPv4 server to ignore client-id value altogether
+    // and rely exclusively on MAC address. This is a parameter that is defined
+    // globally, but can be overridden on a subnet level.
+    "match-client-id": true,
+
+    // The following list defines subnets. Each subnet consists of at
+    // least subnet and pool entries.
+    "subnet4": [
+        {
+            "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
+            "subnet": "192.0.2.0/24"
+        },
+        {
+            // This particular subnet has match-client-id value changed.
+            // This causes Kea to ignore client-id values in this subnet
+            // and rely exclusively on MAC addresses.
+            "pools": [ { "pool": "192.0.3.100 - 192.0.3.200" } ],
+            "subnet": "192.0.3.0/24",
+            "match-client-id": false
+        },
+        {
+            "pools": [ { "pool": "192.0.4.1 - 192.0.4.254" } ],
+            "subnet": "192.0.4.0/24"
+        }
+    ]
+},
+
+  // The following configures logging. It assumes that messages with at least
+  // informational level (info, warn, error and fatal) should be logged to stdout.
+  "Logging": {
+      "loggers": [
+          {
+            "name": "kea-dhcp4",
+              "output_options": [
+                  {
+                      "output": "stdout"
+                  }
+              ],
+              "severity": "INFO"
+          }
+      ]
+  }
+
+}

+ 9 - 3
doc/examples/kea4/classify.json

@@ -51,10 +51,16 @@
 
 # Let's pick VoIP phones. Those that send their class identifiers
 # as Aastra, should belong to VoIP class. For a list of all options,
-# see www.iana.org/assignments/bootp-dhcp-parameters/
+# see www.iana.org/assignments/bootp-dhcp-parameters/.
+# In this particular class, we want to set specific values
+# of certain DHCPv4 fields. If the incoming packet matches the
+# test, those fields will be set in outgoing responses.
   {
       "name": "VoIP",
-      "test": "substring(option[60].hex,0,6) == 'Aastra'"
+      "test": "substring(option[60].hex,0,6) == 'Aastra'",
+      "next-server": "192.0.2.254",
+      "server-hostname": "hal9000",
+      "boot-file-name": "/dev/null"
   }
 
   ],
@@ -65,7 +71,7 @@
 # to that class are allowed for that subnet.
   "subnet4": [
     {
-# This one is for VoIP devices only.          
+# This one is for VoIP devices only.
         "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
         "subnet": "192.0.2.0/24",
         "client-class": "VoIP",

+ 5 - 1
doc/examples/kea4/hooks.json

@@ -37,7 +37,11 @@
         "library": "/opt/lib/security.so"
      },
      {
-        "library": "/opt/lib/charging.so"
+         "library": "/opt/lib/charging.so",
+         "parameters": {
+             "path": "/var/kea/var",
+             "base-name": "kea-forensic6"
+         }
      }
   ]
 }

+ 10 - 0
doc/examples/kea4/reservations.json

@@ -107,6 +107,16 @@
                      "data": "10.1.1.202,10.1.1.203"
                  }
              ]
+         },
+# This reservation is for a client that needs specific DHCPv4 fields to be
+# set. Three supported fields are next-server, server-hostname and
+# boot-file-name
+         {
+             "client-id": "01:0a:0b:0c:0d:0e:of",
+             "ip-address": "192.0.2.205",
+             "next-server": "192.0.2.1",
+             "server-hostname": "hal9000",
+             "boot-file-name": "/dev/null"
          }
        ]
     }

+ 28 - 7
doc/examples/kea4/several-subnets.json

@@ -27,15 +27,36 @@
   "renew-timer": 1000,
   "rebind-timer": 2000,
 
+# RFC6842 says that the server is supposed to echo back client-id option.
+# However, some older clients do not support this and are getting confused
+# when they get their own client-id. Kea can disable RFC6842 support.
+  "echo-client-id": false,
+
+# Some clients don't use stable client identifier, but rather generate them
+# during each boot. This may cause a client that reboots frequently to get
+# multiple leases, which may not be desirable. As such, sometimes admins
+# prefer to tell their DHCPv4 server to ignore client-id value altogether
+# and rely exclusively on MAC address. This is a parameter that is defined
+# globally, but can be overridden on a subnet level.
+  "match-client-id": true,
+
 # The following list defines subnets. Each subnet consists of at
 # least subnet and pool entries.
-  "subnet4": [ 
-  {    "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
-       "subnet": "192.0.2.0/24"  },
-  {    "pools": [ { "pool": "192.0.3.100 - 192.0.3.200" } ],
-       "subnet": "192.0.3.0/24"  },
-  {    "pools": [ { "pool": "192.0.4.1 - 192.0.4.254" } ],
-       "subnet": "192.0.4.0/24"  } ]
+  "subnet4": [
+  {
+      "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
+      "subnet": "192.0.2.0/24"
+  },
+  {
+# This particular subnet has match-client-id value changed.
+      "pools": [ { "pool": "192.0.3.100 - 192.0.3.200" } ],
+      "subnet": "192.0.3.0/24",
+      "match-client-id": false
+  },
+  {
+      "pools": [ { "pool": "192.0.4.1 - 192.0.4.254" } ],
+      "subnet": "192.0.4.0/24"
+  } ]
 },
 
 # The following configures logging. It assumes that messages with at least

+ 7 - 11
doc/guide/config.xml

@@ -37,27 +37,23 @@
       <itemizedlist>
         <listitem>
           <simpara>shell comments: any text after the hash (#)
-          character is ignored. Dhcp6 allows # in any column, while
-          Dhcp4 and Ddns require hash to be in the first
-          column.</simpara>
+          character is ignored. Both Dhcp4 and Dhcp6 allow # in any column,
+          while Ddns requires hash to be in the first column.</simpara>
         </listitem>
         <listitem>
           <simpara>C comments: any text after the double slashes (//)
-          character is ignored. We're in a process of
-          migrating the configuation parsers and currently only Dhcp6
-          supports this feature.</simpara>
+          character is ignored. Both Dhcp4 and Dhcp6 supports this
+          feature.</simpara>
         </listitem>
         <listitem>
           <simpara>Multiline comments: any text between /* and */ is
-          ignored. This commenting can span multiple lines. We're in a
-          process of migrating the configuation parsers and currently
-          only Dhcp6 supports this feature.</simpara>
+          ignored. This commenting can span multiple lines. Both Dhcp4 and
+          Dhcp6 supports this feature.</simpara>
         </listitem>
         <listitem>
           <simpara>File inclusion: JSON files can include other JSON
           files. This can be done by using &lt;?include
-          "file.json"?&gt;. We're in a process of migrating the
-          configuation parsers and currently only Dhcp6 supports this
+          "file.json"?&gt;. Both Dhcp4 and Dhcp6 supports this
           feature.</simpara>
         </listitem>
       </itemizedlist>

+ 1 - 0
src/bin/dhcp4/.gitignore

@@ -5,3 +5,4 @@
 /spec_config.h
 /spec_config.h.pre
 /s-messages
+/dhcp4_parser.report

+ 31 - 0
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 parser_context_decl.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

+ 37 - 0
src/bin/dhcp4/dhcp4.dox

@@ -41,6 +41,43 @@ all configuration parsers. All DHCPv4 parsers deriving from this class
 directly have their entire implementation in the
 src/bin/dhcp4/json_config_parser.cc.
 
+@section dhcpv4ConfigParserBison Configuration Parser for DHCPv4 (bison)
+
+If you are here only to learn absolute minimum about the new parser, here's how you
+use it:
+
+@code
+ // The following code:
+ json = isc::data::Element::fromJSONFile(file_name, true);
+
+ // can be replaced with this:
+ Parser4Context parser;
+ json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
+@endcode
+
+For an introduction, rationale and issues the new parser tries to address,
+see @ref dhcpv6ConfigParserBison.
+
+The code change for 5017 introduces flex/bison based parser. It is
+essentially defined in two files: dhcp4_lexer.ll, which defines
+regular expressions that are used on the input (be it a file or a
+string in memory). In essence, this code is being called repeatedly
+and each time it returns a token. This repeats until either the
+parsing is complete or syntax error is encountered. For detailed
+discussion, how they operate see @ref dhcpv6ConfigParserBison.
+
+@section dhcpv4ConfigSubParser Parsing Partial Configuration in DHCPv4
+
+See @ref dhcpv6ConfigSubParser.
+
+@section dhcp4ParserIncludes Config File Includes
+
+See @ref dhcp6ParserIncludes.
+
+@section dhcp4ParserConflicts Avoiding syntactical conflicts in parsers
+
+See @ref dhcp6ParserConflicts.
+
 @section dhcpv4ConfigInherit DHCPv4 configuration inheritance
 
 One notable useful feature of DHCP configuration is its parameter inheritance.

File diff suppressed because it is too large
+ 4075 - 0
src/bin/dhcp4/dhcp4_lexer.cc


File diff suppressed because it is too large
+ 1210 - 0
src/bin/dhcp4/dhcp4_lexer.ll


File diff suppressed because it is too large
+ 3503 - 0
src/bin/dhcp4/dhcp4_parser.cc


File diff suppressed because it is too large
+ 2128 - 0
src/bin/dhcp4/dhcp4_parser.h


File diff suppressed because it is too large
+ 1530 - 0
src/bin/dhcp4/dhcp4_parser.yy


+ 1 - 2
src/bin/dhcp4/json_config_parser.cc

@@ -428,8 +428,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
         parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET);
     } else if (config_id.compare("option-def") == 0) {
         parser  = new OptionDefListParser(config_id, globalContext());
-    } else if ((config_id.compare("version") == 0) ||
-               (config_id.compare("next-server") == 0)) {
+    } else if ((config_id.compare("next-server") == 0)) {
         parser  = new StringParser(config_id,
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {

+ 3 - 1
src/bin/dhcp4/kea_controller.cc

@@ -9,6 +9,7 @@
 #include <dhcp4/json_config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcp4/parser_context.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <exceptions/exceptions.h>
 
@@ -52,7 +53,8 @@ void configure(const std::string& file_name) {
         }
 
         // Read contents of the file and parse it as JSON
-        json = isc::data::Element::fromJSONFile(file_name, true);
+        Parser4Context parser;
+        json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
         if (!json) {
             isc_throw(isc::BadValue, "no configuration found");
         }

+ 193 - 0
src/bin/dhcp4/location.hh

@@ -0,0 +1,193 @@
+// Generated 201612201711
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Locations for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file location.hh
+ ** Define the isc::dhcp::location class.
+ */
+
+#ifndef YY_PARSER4_LOCATION_HH_INCLUDED
+# define YY_PARSER4_LOCATION_HH_INCLUDED
+
+# include "position.hh"
+
+#line 14 "dhcp4_parser.yy" // location.cc:296
+namespace isc { namespace dhcp {
+#line 46 "location.hh" // location.cc:296
+  /// Abstract a location.
+  class location
+  {
+  public:
+
+    /// Construct a location from \a b to \a e.
+    location (const position& b, const position& e)
+      : begin (b)
+      , end (e)
+    {
+    }
+
+    /// Construct a 0-width location in \a p.
+    explicit location (const position& p = position ())
+      : begin (p)
+      , end (p)
+    {
+    }
+
+    /// Construct a 0-width location in \a f, \a l, \a c.
+    explicit location (std::string* f,
+                       unsigned int l = 1u,
+                       unsigned int c = 1u)
+      : begin (f, l, c)
+      , end (f, l, c)
+    {
+    }
+
+
+    /// Initialization.
+    void initialize (std::string* f = YY_NULLPTR,
+                     unsigned int l = 1u,
+                     unsigned int c = 1u)
+    {
+      begin.initialize (f, l, c);
+      end = begin;
+    }
+
+    /** \name Line and Column related manipulators
+     ** \{ */
+  public:
+    /// Reset initial location to final location.
+    void step ()
+    {
+      begin = end;
+    }
+
+    /// Extend the current location to the COUNT next columns.
+    void columns (int count = 1)
+    {
+      end += count;
+    }
+
+    /// Extend the current location to the COUNT next lines.
+    void lines (int count = 1)
+    {
+      end.lines (count);
+    }
+    /** \} */
+
+
+  public:
+    /// Beginning of the located region.
+    position begin;
+    /// End of the located region.
+    position end;
+  };
+
+  /// Join two locations, in place.
+  inline location& operator+= (location& res, const location& end)
+  {
+    res.end = end.end;
+    return res;
+  }
+
+  /// Join two locations.
+  inline location operator+ (location res, const location& end)
+  {
+    return res += end;
+  }
+
+  /// Add \a width columns to the end position, in place.
+  inline location& operator+= (location& res, int width)
+  {
+    res.columns (width);
+    return res;
+  }
+
+  /// Add \a width columns to the end position.
+  inline location operator+ (location res, int width)
+  {
+    return res += width;
+  }
+
+  /// Subtract \a width columns to the end position, in place.
+  inline location& operator-= (location& res, int width)
+  {
+    return res += -width;
+  }
+
+  /// Subtract \a width columns to the end position.
+  inline location operator- (location res, int width)
+  {
+    return res -= width;
+  }
+
+  /// Compare two location objects.
+  inline bool
+  operator== (const location& loc1, const location& loc2)
+  {
+    return loc1.begin == loc2.begin && loc1.end == loc2.end;
+  }
+
+  /// Compare two location objects.
+  inline bool
+  operator!= (const location& loc1, const location& loc2)
+  {
+    return !(loc1 == loc2);
+  }
+
+  /** \brief Intercept output stream redirection.
+   ** \param ostr the destination output stream
+   ** \param loc a reference to the location to redirect
+   **
+   ** Avoid duplicate information.
+   */
+  template <typename YYChar>
+  inline std::basic_ostream<YYChar>&
+  operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
+  {
+    unsigned int end_col = 0 < loc.end.column ? loc.end.column - 1 : 0;
+    ostr << loc.begin;
+    if (loc.end.filename
+        && (!loc.begin.filename
+            || *loc.begin.filename != *loc.end.filename))
+      ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
+    else if (loc.begin.line < loc.end.line)
+      ostr << '-' << loc.end.line << '.' << end_col;
+    else if (loc.begin.column < end_col)
+      ostr << '-' << end_col;
+    return ostr;
+  }
+
+#line 14 "dhcp4_parser.yy" // location.cc:296
+} } // isc::dhcp
+#line 192 "location.hh" // location.cc:296
+#endif // !YY_PARSER4_LOCATION_HH_INCLUDED

+ 168 - 0
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 <dhcp4/parser_context.h>
+#include <dhcp4/dhcp4_parser.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <limits>
+
+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__");
+    }
+}
+
+};
+};

+ 321 - 0
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 <string>
+#include <map>
+#include <vector>
+#include <dhcp4/dhcp4_parser.h>
+#include <dhcp4/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// 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 Subnet4 content.
+        PARSER_SUBNET4,
+
+        /// This will parse the input as pool4 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<isc::data::ElementPtr> stack_;
+
+    /// @brief Method called before scanning starts on a string.
+    ///
+    /// @param str string to be parsed
+    /// @param 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 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/Subnet4 structures.
+        SUBNET4,
+
+        /// Used while parsing Dhcp4/option-def structures.
+        OPTION_DEF,
+
+        /// Used while parsing Dhcp4/option-data, Dhcp4/subnet4/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/subnet4/pools structures.
+        POOLS,
+
+        /// Used while parsing Dhcp4/reservations structures.
+        RESERVATIONS,
+
+        /// Used while parsing Dhcp4/subnet4relay 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<std::string> 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<isc::dhcp::location> locs_;
+
+    /// @brief Lexer state stack
+    std::vector<struct yy_buffer_state*> 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<FILE*> 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<ParserContext> 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

+ 20 - 0
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 PARSER4_CONTEXT_DECL_H
+#define PARSER4_CONTEXT_DECL_H
+
+/// @file dhcp4/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

+ 181 - 0
src/bin/dhcp4/position.hh

@@ -0,0 +1,181 @@
+// Generated 201612201711
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Positions for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file position.hh
+ ** Define the isc::dhcp::position class.
+ */
+
+#ifndef YY_PARSER4_POSITION_HH_INCLUDED
+# define YY_PARSER4_POSITION_HH_INCLUDED
+
+# include <algorithm> // std::max
+# include <iostream>
+# include <string>
+
+# ifndef YY_NULLPTR
+#  if defined __cplusplus && 201103L <= __cplusplus
+#   define YY_NULLPTR nullptr
+#  else
+#   define YY_NULLPTR 0
+#  endif
+# endif
+
+#line 14 "dhcp4_parser.yy" // location.cc:296
+namespace isc { namespace dhcp {
+#line 56 "position.hh" // location.cc:296
+  /// Abstract a position.
+  class position
+  {
+  public:
+    /// Construct a position.
+    explicit position (std::string* f = YY_NULLPTR,
+                       unsigned int l = 1u,
+                       unsigned int c = 1u)
+      : filename (f)
+      , line (l)
+      , column (c)
+    {
+    }
+
+
+    /// Initialization.
+    void initialize (std::string* fn = YY_NULLPTR,
+                     unsigned int l = 1u,
+                     unsigned int c = 1u)
+    {
+      filename = fn;
+      line = l;
+      column = c;
+    }
+
+    /** \name Line and Column related manipulators
+     ** \{ */
+    /// (line related) Advance to the COUNT next lines.
+    void lines (int count = 1)
+    {
+      if (count)
+        {
+          column = 1u;
+          line = add_ (line, count, 1);
+        }
+    }
+
+    /// (column related) Advance to the COUNT next columns.
+    void columns (int count = 1)
+    {
+      column = add_ (column, count, 1);
+    }
+    /** \} */
+
+    /// File name to which this position refers.
+    std::string* filename;
+    /// Current line number.
+    unsigned int line;
+    /// Current column number.
+    unsigned int column;
+
+  private:
+    /// Compute max(min, lhs+rhs) (provided min <= lhs).
+    static unsigned int add_ (unsigned int lhs, int rhs, unsigned int min)
+    {
+      return (0 < rhs || -static_cast<unsigned int>(rhs) < lhs
+              ? rhs + lhs
+              : min);
+    }
+  };
+
+  /// Add \a width columns, in place.
+  inline position&
+  operator+= (position& res, int width)
+  {
+    res.columns (width);
+    return res;
+  }
+
+  /// Add \a width columns.
+  inline position
+  operator+ (position res, int width)
+  {
+    return res += width;
+  }
+
+  /// Subtract \a width columns, in place.
+  inline position&
+  operator-= (position& res, int width)
+  {
+    return res += -width;
+  }
+
+  /// Subtract \a width columns.
+  inline position
+  operator- (position res, int width)
+  {
+    return res -= width;
+  }
+
+  /// Compare two position objects.
+  inline bool
+  operator== (const position& pos1, const position& pos2)
+  {
+    return (pos1.line == pos2.line
+            && pos1.column == pos2.column
+            && (pos1.filename == pos2.filename
+                || (pos1.filename && pos2.filename
+                    && *pos1.filename == *pos2.filename)));
+  }
+
+  /// Compare two position objects.
+  inline bool
+  operator!= (const position& pos1, const position& pos2)
+  {
+    return !(pos1 == pos2);
+  }
+
+  /** \brief Intercept output stream redirection.
+   ** \param ostr the destination output stream
+   ** \param pos a reference to the position to redirect
+   */
+  template <typename YYChar>
+  inline std::basic_ostream<YYChar>&
+  operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
+  {
+    if (pos.filename)
+      ostr << *pos.filename << ':';
+    return ostr << pos.line << '.' << pos.column;
+  }
+
+#line 14 "dhcp4_parser.yy" // location.cc:296
+} } // isc::dhcp
+#line 180 "position.hh" // location.cc:296
+#endif // !YY_PARSER4_POSITION_HH_INCLUDED

+ 158 - 0
src/bin/dhcp4/stack.hh

@@ -0,0 +1,158 @@
+// Generated 201612201711
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Stack handling for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file stack.hh
+ ** Define the isc::dhcp::stack class.
+ */
+
+#ifndef YY_PARSER4_STACK_HH_INCLUDED
+# define YY_PARSER4_STACK_HH_INCLUDED
+
+# include <vector>
+
+#line 14 "dhcp4_parser.yy" // stack.hh:132
+namespace isc { namespace dhcp {
+#line 46 "stack.hh" // stack.hh:132
+  template <class T, class S = std::vector<T> >
+  class stack
+  {
+  public:
+    // Hide our reversed order.
+    typedef typename S::reverse_iterator iterator;
+    typedef typename S::const_reverse_iterator const_iterator;
+
+    stack ()
+      : seq_ ()
+    {
+      seq_.reserve (200);
+    }
+
+    stack (unsigned int n)
+      : seq_ (n)
+    {}
+
+    inline
+    T&
+    operator[] (unsigned int i)
+    {
+      return seq_[seq_.size () - 1 - i];
+    }
+
+    inline
+    const T&
+    operator[] (unsigned int i) const
+    {
+      return seq_[seq_.size () - 1 - i];
+    }
+
+    /// Steal the contents of \a t.
+    ///
+    /// Close to move-semantics.
+    inline
+    void
+    push (T& t)
+    {
+      seq_.push_back (T());
+      operator[](0).move (t);
+    }
+
+    inline
+    void
+    pop (unsigned int n = 1)
+    {
+      for (; n; --n)
+        seq_.pop_back ();
+    }
+
+    void
+    clear ()
+    {
+      seq_.clear ();
+    }
+
+    inline
+    typename S::size_type
+    size () const
+    {
+      return seq_.size ();
+    }
+
+    inline
+    const_iterator
+    begin () const
+    {
+      return seq_.rbegin ();
+    }
+
+    inline
+    const_iterator
+    end () const
+    {
+      return seq_.rend ();
+    }
+
+  private:
+    stack (const stack&);
+    stack& operator= (const stack&);
+    /// The wrapped container.
+    S seq_;
+  };
+
+  /// Present a slice of the top of a stack.
+  template <class T, class S = stack<T> >
+  class slice
+  {
+  public:
+    slice (const S& stack, unsigned int range)
+      : stack_ (stack)
+      , range_ (range)
+    {}
+
+    inline
+    const T&
+    operator [] (unsigned int i) const
+    {
+      return stack_[range_ - i];
+    }
+
+  private:
+    const S& stack_;
+    unsigned int range_;
+  };
+
+#line 14 "dhcp4_parser.yy" // stack.hh:132
+} } // isc::dhcp
+#line 156 "stack.hh" // stack.hh:132
+
+#endif // !YY_PARSER4_STACK_HH_INCLUDED

+ 2 - 0
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

+ 1 - 2
src/bin/dhcp4/tests/classify_unittest.cc

@@ -69,8 +69,7 @@ const char* CONFIGS[] = {
         "   \"name\": \"pxe4\","
         "   \"test\": \"option[93].hex == 0x0001\","
         "   \"boot-file-name\": \"ipxe.efi\""
-        "},"
-        "],"
+        "}],"
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
         "    \"id\": 1,"

File diff suppressed because it is too large
+ 232 - 176
src/bin/dhcp4/tests/config_parser_unittest.cc


+ 3 - 1
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -10,6 +10,7 @@
 #include <config/command_mgr.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -117,7 +118,8 @@ public:
 
         ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv4Srv()));
 
-        ConstElementPtr config = Element::fromJSON(config_txt);
+        ConstElementPtr config;
+        ASSERT_NO_THROW(config = parseDHCP4(config_txt));
         ConstElementPtr answer = server_->processConfig(config);
         ASSERT_TRUE(answer);
 

+ 27 - 15
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -1067,7 +1067,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
         "          \"space\": \"vendor-4491\","
         "          \"code\": 2,"
         "          \"data\": \"10.253.175.16\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
@@ -1079,7 +1079,8 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1221,7 +1222,8 @@ TEST_F(Dhcpv4SrvTest, nextServerOverride) {
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
 
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
 
@@ -1269,7 +1271,8 @@ TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
 
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
 
@@ -1358,7 +1361,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
         "          \"space\": \"vendor-4491\","
         "          \"code\": 2,"
         "          \"data\": \"192.0.2.1, 192.0.2.2\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
@@ -1370,7 +1373,8 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
 
     EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
     ASSERT_TRUE(x);
@@ -1448,7 +1452,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
         "          \"code\": ";
     string config_postfix = ","
         "          \"data\": \"192.0.2.1\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.50\" } ],"
@@ -1468,8 +1472,10 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
     // definition, the config should fail.
     string config_bogus = config_prefix + "99" + config_postfix;
 
-    ElementPtr json_bogus = Element::fromJSON(config_bogus);
-    ElementPtr json_valid = Element::fromJSON(config_valid);
+    ConstElementPtr json_bogus;
+    ASSERT_NO_THROW(json_bogus = parseDHCP4(config_bogus));
+    ConstElementPtr json_valid;
+    ASSERT_NO_THROW(json_valid = parseDHCP4(config_valid));
 
     NakedDhcpv4Srv srv(0);
 
@@ -1543,7 +1549,8 @@ TEST_F(Dhcpv4SrvTest, matchClassification) {
         "             \"data\": \"true\" } ], "
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1629,7 +1636,8 @@ TEST_F(Dhcpv4SrvTest, matchClassificationOptionName) {
         "{   \"name\": \"router\", "
         "    \"test\": \"option[host-name].text == 'foo'\" } ] }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1679,7 +1687,8 @@ TEST_F(Dhcpv4SrvTest, matchClassificationOptionDef) {
         "    \"code\": 250, "
         "    \"type\": \"string\" } ] }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1734,7 +1743,8 @@ TEST_F(Dhcpv4SrvTest, subnetClassPriority) {
         "             \"data\": \"true\" } ], "
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1806,7 +1816,8 @@ TEST_F(Dhcpv4SrvTest, subnetGlobalPriority) {
         "    {    \"name\": \"ip-forwarding\", "
         "         \"data\": \"true\" } ] }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1877,7 +1888,8 @@ TEST_F(Dhcpv4SrvTest, classGlobalPriority) {
         "             \"data\": \"true\" } ], "
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted

+ 2 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -599,7 +599,8 @@ Dhcpv4SrvTest::configure(const std::string& config, const bool commit) {
 void
 Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
                          const bool commit) {
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseJSON(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted

+ 57 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -21,6 +21,7 @@
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/parser_context.h>
 #include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <list>
@@ -478,6 +479,62 @@ public:
     NakedDhcpv4Srv srv_;
 };
 
+/// @brief Runs parser in JSON mode, useful for parser testing
+///
+/// @param in string to be parsed
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ConstElementPtr
+parseJSON(const std::string& in)
+{
+    isc::dhcp::Parser4Context ctx;
+    return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_JSON));
+}
+
+/// @brief Runs parser in Dhcp4 mode
+///
+/// This is a simplified Dhcp4 mode, so no outer { } and "Dhcp4" is
+/// needed. This format is used by most of the tests.
+///
+/// @param in string to be parsed
+/// @param verbose display the exception message when it fails
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ConstElementPtr
+parseDHCP4(const std::string& in, bool verbose = false)
+{
+    try {
+        isc::dhcp::Parser4Context ctx;
+        return (ctx.parseString(in, isc::dhcp::Parser4Context::SUBPARSER_DHCP4));
+    }
+    catch (const std::exception& ex) {
+        if (verbose) {
+            std::cout << "EXCEPTION: " << ex.what() << std::endl;
+        }
+        throw;
+    }
+}
+
+/// @brief Runs parser in option definition mode
+///
+/// This function parses specified text as JSON that defines option definitions.
+///
+/// @param in string to be parsed
+/// @param verbose display the exception message when it fails
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ConstElementPtr
+parseOPTION_DEF(const std::string& in, bool verbose = false)
+{
+    try {
+        isc::dhcp::Parser4Context ctx;
+        return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_OPTION_DEF));
+    }
+    catch (const std::exception& ex) {
+        if (verbose) {
+            std::cout << "EXCEPTION: " << ex.what() << std::endl;
+        }
+        throw;
+    }
+}
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 4 - 2
src/bin/dhcp4/tests/hooks_unittest.cc

@@ -1161,7 +1161,8 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1239,7 +1240,8 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted

+ 204 - 16
src/bin/dhcp4/tests/kea_controller_unittest.cc

@@ -12,6 +12,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/parser_context.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
@@ -64,6 +65,7 @@ public:
         LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
+        static_cast<void>(remove(TEST_INCLUDE));
     };
 
     /// @brief writes specified content to a well known file
@@ -71,11 +73,12 @@ public:
     /// Writes specified content to TEST_FILE. Tests will
     /// attempt to read that file.
     ///
+    /// @param file_name name of file to be written
     /// @param content content to be written to file
-    void writeFile(const std::string& content) {
-        static_cast<void>(remove(TEST_FILE));
+    void writeFile(const std::string& file_name, const std::string& content) {
+        static_cast<void>(remove(file_name.c_str()));
 
-        ofstream out(TEST_FILE, ios::trunc);
+        ofstream out(file_name.c_str(), ios::trunc);
         EXPECT_TRUE(out.is_open());
         out << content;
         out.close();
@@ -98,9 +101,11 @@ public:
 
     /// Name of a config file used during tests
     static const char* TEST_FILE;
+    static const char* TEST_INCLUDE;
 };
 
 const char* JSONFileBackendTest::TEST_FILE  = "test-config.json";
+const char* JSONFileBackendTest::TEST_INCLUDE = "test-include.json";
 
 // This test checks if configuration can be read from a JSON file.
 TEST_F(JSONFileBackendTest, jsonFile) {
@@ -128,7 +133,7 @@ TEST_F(JSONFileBackendTest, jsonFile) {
         "\"valid-lifetime\": 4000 }"
         "}";
 
-    writeFile(config);
+    writeFile(TEST_FILE, config);
 
     // Now initialize the server
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -179,8 +184,9 @@ TEST_F(JSONFileBackendTest, jsonFile) {
     EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
 }
 
-// This test checks if configuration can be read from a JSON file.
-TEST_F(JSONFileBackendTest, comments) {
+// This test checks if configuration can be read from a JSON file
+// using hash (#) line comments
+TEST_F(JSONFileBackendTest, hashComments) {
 
     string config_hash_comments = "# This is a comment. It should be \n"
         "#ignored. Real config starts in line below\n"
@@ -198,10 +204,56 @@ TEST_F(JSONFileBackendTest, comments) {
         "\"valid-lifetime\": 4000 }"
         "}";
 
-    /// @todo: Implement C++-style (// ...) comments
-    /// @todo: Implement C-style (/* ... */) comments
+    writeFile(TEST_FILE, config_hash_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using config with comments.
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(22, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+}
 
-    writeFile(config_hash_comments);
+// This test checks if configuration can be read from a JSON file
+// using C++ line (//) comments.
+TEST_F(JSONFileBackendTest, cppLineComments) {
+
+    string config_cpp_line_comments = "// This is a comment. It should be \n"
+        "//ignored. Real config starts in line below\n"
+        "{ \"Dhcp4\": {"
+        "\"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "// comments in the middle should be ignored, too\n"
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+        "    \"subnet\": \"192.0.2.0/22\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    writeFile(TEST_FILE, config_cpp_line_comments);
 
     // Now initialize the server
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -230,6 +282,142 @@ TEST_F(JSONFileBackendTest, comments) {
     EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
 }
 
+// This test checks if configuration can be read from a JSON file
+// using C block (/* */) comments
+TEST_F(JSONFileBackendTest, cBlockComments) {
+
+    string config_c_block_comments = "/* This is a comment. It should be \n"
+      "ignored. Real config starts in line below*/\n"
+        "{ \"Dhcp4\": {"
+        "\"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "/* comments in the middle should be ignored, too*/\n"
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+        "    \"subnet\": \"192.0.2.0/22\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    writeFile(TEST_FILE, config_c_block_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using config with comments.
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(22, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using an include file.
+TEST_F(JSONFileBackendTest, include) {
+
+    string config_hash_comments = "{ \"Dhcp4\": {"
+        "\"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "<?include \"" + string(TEST_INCLUDE) + "\"?>,"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    string include = "\n"
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+        "    \"subnet\": \"192.0.2.0/22\" "
+        " } ]\n";
+
+    writeFile(TEST_FILE, config_hash_comments);
+    writeFile(TEST_INCLUDE, include);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using config with comments.
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(22, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
+}
+
+// This test checks if recursive include of a file is detected
+TEST_F(JSONFileBackendTest, recursiveInclude) {
+
+    string config_recursive_include = "{ \"Dhcp4\": {"
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ <?include \"" + string(TEST_INCLUDE) + "\"?> ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ],"
+        "    \"subnet\": \"192.0.2.0/22\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    string include = "\"eth\", <?include \"" + string(TEST_INCLUDE) + "\"?>";
+    string msg = "configuration error using file '" + string(TEST_FILE) +
+        "': Too many nested include.";
+
+    writeFile(TEST_FILE, config_recursive_include);
+    writeFile(TEST_INCLUDE, include);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv4Srv(0))
+    );
+
+    // And configure it using config with comments.
+    try {
+        srv->init(TEST_FILE);
+        FAIL() << "Expected Dhcp4ParseError but nothing was raised";
+    }
+    catch (const Exception& ex) {
+        EXPECT_EQ(msg, ex.what());
+    }
+}
+
 // This test checks if configuration detects failure when trying:
 // - empty file
 // - empty filename
@@ -263,15 +451,15 @@ TEST_F(JSONFileBackendTest, configBroken) {
     EXPECT_THROW(srv->init(""), BadValue);
 
     // Try to configure it using empty file. Should fail.
-    writeFile(config_empty);
+    writeFile(TEST_FILE, config_empty);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 
     // Now try to load a config that does not have Dhcp4 component.
-    writeFile(config_v4);
+    writeFile(TEST_FILE, config_v4);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 
     // Now try to load a config with Dhcp4 full of nonsense.
-    writeFile(config_nonsense);
+    writeFile(TEST_FILE, config_nonsense);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 }
 
@@ -347,7 +535,7 @@ TEST_F(JSONFileBackendTest, timers) {
         "\"subnet4\": [ ],"
         "\"valid-lifetime\": 4000 }"
         "}";
-    writeFile(config);
+    writeFile(TEST_FILE, config);
 
     // Create an instance of the server and intialize it.
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -416,7 +604,7 @@ TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
         "\"subnet4\": [ ],"
         "\"valid-lifetime\": 4000 }"
         "}";
-    writeFile(config);
+    writeFile(TEST_FILE, config);
 
     // Create an instance of the server and intialize it.
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -518,7 +706,7 @@ void
 JSONFileBackendMySQLTest::
 testBackendReconfiguration(const std::string& backend_first,
                            const std::string& backend_second) {
-    writeFile(createConfiguration(backend_first));
+    writeFile(TEST_FILE, createConfiguration(backend_first));
 
     // Create an instance of the server and intialize it.
     boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
@@ -533,7 +721,7 @@ testBackendReconfiguration(const std::string& backend_first,
               LeaseMgrFactory::instance().getType());
 
     // New configuration modifies the lease database backend type.
-    writeFile(createConfiguration(backend_second));
+    writeFile(TEST_FILE, createConfiguration(backend_second));
 
     // Explicitly calling signal handler for SIGHUP to trigger server
     // reconfiguration.

+ 611 - 0
src/bin/dhcp4/tests/parser_unittest.cc

@@ -0,0 +1,611 @@
+// 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 <gtest/gtest.h>
+#include <cc/data.h>
+#include <dhcp4/parser_context.h>
+#include <fstream>
+#include <cstdio>
+#include <exceptions/exceptions.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+/// @brief compares two JSON trees
+///
+/// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
+///
+/// @param a first to be compared
+/// @param b second to be compared
+void compareJSON(ConstElementPtr a, ConstElementPtr b) {
+    ASSERT_TRUE(a);
+    ASSERT_TRUE(b);
+    EXPECT_EQ(a->str(), b->str());
+}
+
+/// @brief Tests if the input string can be parsed with specific parser
+///
+/// The input text will be passed to bison parser of specified type.
+/// Then the same input text is passed to legacy JSON parser and outputs
+/// from both parsers are compared. The legacy comparison can be disabled,
+/// if the feature tested is not supported by the old parser (e.g.
+/// new comment styles)
+///
+/// @param txt text to be compared
+/// @param parser_type bison parser type to be instantiated
+/// @param compare whether to compare the output with legacy JSON parser
+void testParser(const std::string& txt, Parser4Context::ParserType parser_type,
+                bool compare = true) {
+    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;
+            }
+
+    });
+
+    if (!compare) {
+        return;
+    };
+
+    // Now compare if both representations are the same.
+    ElementPtr reference_json;
+    ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+    compareJSON(reference_json, test_json);
+}
+
+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\": \"192.0.2.1 - 192.0.2.100\" } ],"
+                  "  \"subnet\": \"192.0.2.0/24\", "
+                  "  \"interface\": \"test\" } ],\n"
+                   "\"valid-lifetime\": 4000 } }";
+     testParser(txt, Parser4Context::PARSER_DHCP4);
+}
+
+TEST(ParserTest, bashComments) {
+    string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "# this is a comment\n"
+                "\"rebind-timer\": 2000, \n"
+                "# lots of comments here\n"
+                "# and here\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet4\": [ { "
+                "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+                "    \"subnet\": \"192.0.2.0/24\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+TEST(ParserTest, cComments) {
+    string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"rebind-timer\": 2000, // everything after // is ignored\n"
+                "\"renew-timer\": 1000, // this will be ignored, too\n"
+                "\"subnet4\": [ { "
+                "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+                "    \"subnet\": \"192.0.2.0/24\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+TEST(ParserTest, bashCommentsInline) {
+    string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"rebind-timer\": 2000, # everything after # is ignored\n"
+                "\"renew-timer\": 1000, # this will be ignored, too\n"
+                "\"subnet4\": [ { "
+                "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+                "    \"subnet\": \"192.0.2.0/24\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+TEST(ParserTest, multilineComments) {
+    string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "   /* 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"
+                "\"subnet4\": [ { "
+                "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+                "    \"subnet\": \"192.0.2.0/24\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, Parser4Context::PARSER_DHCP4, false);
+}
+
+/// @brief removes comments from a JSON file
+///
+/// This is rather naive implementation, but it's probably sufficient for
+/// testing. It won't be able to pick any trickier cases, like # or //
+/// appearing in strings, nested C++ comments etc.
+///
+/// @param input_file file to be stripped of comments
+/// @return a new file that has comments stripped from it
+std::string decommentJSONfile(const std::string& input_file) {
+    ifstream f(input_file);
+    if (!f.is_open()) {
+        isc_throw(isc::BadValue, "can't open input file for reading: " + input_file);
+    }
+
+    string outfile;
+    size_t last_slash = input_file.find_last_of("/");
+    if (last_slash != string::npos) {
+        outfile = input_file.substr(last_slash + 1);
+    } else {
+        outfile = input_file;
+    }
+    outfile += "-decommented";
+
+    ofstream out(outfile);
+    if (!out.is_open()) {
+        isc_throw(isc::BadValue, "can't open output file for writing: " + input_file);
+    }
+
+    bool in_comment = false;
+    string line;
+    while (std::getline(f, line)) {
+        // First, let's get rid of the # comments
+        size_t hash_pos = line.find("#");
+        if (hash_pos != string::npos) {
+            line = line.substr(0, hash_pos);
+        }
+
+        // Second, let's get rid of the // comments
+        size_t dblslash_pos = line.find("//");
+        if (dblslash_pos != string::npos) {
+            line = line.substr(0, dblslash_pos);
+        }
+
+        // Now the tricky part: c comments.
+        size_t begin_pos = line.find("/*");
+        size_t end_pos = line.find("*/");
+        if (in_comment && end_pos == string::npos) {
+            // we continue through multiline comment
+            line = "";
+        } else {
+
+            if (begin_pos != string::npos) {
+                in_comment = true;
+                if (end_pos != string::npos) {
+                    // sigle line comment. Let's get rid of the content in between
+                    line = line.replace(begin_pos, end_pos + 2, end_pos + 2 - begin_pos, ' ');
+                    in_comment = false;
+                } else {
+                    line = line.substr(0, begin_pos);
+                }
+            } else {
+                if (in_comment && end_pos != string::npos) {
+                    line = line.replace(0, end_pos +2 , end_pos + 2, ' ');
+                    in_comment = false;
+                }
+            }
+        }
+
+        // Finally, write the line to the output file.
+        out << line << endl;
+    }
+    f.close();
+    out.close();
+
+    return (outfile);
+}
+
+/// @brief Loads specified example config file
+///
+/// This test loads specified example file twice: first, using the legacy
+/// JSON file and then second time using bison parser. Two created Element
+/// trees are then compared. The input is decommented before it is passed
+/// to legacy parser (as its support for comments is very limited).
+///
+/// @param fname name of the file to be loaded
+void testFile(const std::string& fname) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    string decommented = decommentJSONfile(fname);
+
+    cout << "Attempting to load file " << fname << " (" << decommented
+         << ")" << endl;
+
+    EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true));
+
+    // remove the temporary file
+    EXPECT_NO_THROW(::remove(decommented.c_str()));
+
+    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);
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with Parser4. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+    vector<string> configs = { "advanced.json" ,
+                               "backends.json",
+                               "classify.json",
+                               "dhcpv4-over-dhcpv6.json",
+                               "hooks.json",
+                               "leases-expiration.json",
+                               "multiple-options.json",
+                               "mysql-reservations.json",
+                               "pgsql-reservations.json",
+                               "reservations.json",
+                               "several-subnets.json",
+                               "single-subnet.json" };
+
+    for (int i = 0; i<configs.size(); i++) {
+        testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
+    }
+}
+
+void testError(const std::string& txt,
+               Parser4Context::ParserType parser_type,
+               const std::string& msg)
+{
+    try {
+        Parser4Context ctx;
+        ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+        FAIL() << "Expected Dhcp4ParseError but nothing was raised (expected: "
+               << msg << ")";
+    }
+    catch (const Dhcp4ParseError& ex) {
+        EXPECT_EQ(msg, ex.what());
+    }
+    catch (...) {
+        FAIL() << "Expected Dhcp4ParseError but something else was raised";
+    }
+}
+
+// Check errors
+TEST(ParserTest, errors) {
+    // no input
+    testError("", Parser4Context::PARSER_JSON,
+              "<string>:1.1: syntax error, unexpected end of file");
+    testError(" ", Parser4Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\n", Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("\t", Parser4Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\r", Parser4Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+
+    // comments
+    testError("# nothing\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError(" #\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("// nothing\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* nothing */\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:3.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n\n",
+              Parser4Context::PARSER_JSON,
+              "<string>: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,
+              "<string>:1.3-8: Invalid character: *");
+    testError("{ /* // *// }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3-11: Invalid character: /");
+    testError("{ /* // *///  }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting }");
+
+    // includes
+    testError("<?\n",
+              Parser4Context::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include\n",
+              Parser4Context::PARSER_JSON,
+              "Directive not closed.");
+    string file = string(CFG_EXAMPLES) + "/" + "single-subnet.json";
+    testError("<?include \"" + file + "\"\n",
+              Parser4Context::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include \"/foo/bar\" ?>/n",
+              Parser4Context::PARSER_JSON,
+              "Can't open include file /foo/bar");
+
+    // JSON keywords
+    testError("{ \"foo\": True }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10-13: JSON true reserved keyword is lower case only");
+    testError("{ \"foo\": False }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10-14: JSON false reserved keyword is lower case only");
+    testError("{ \"foo\": NULL }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10-13: JSON null reserved keyword is lower case only");
+    testError("{ \"foo\": Tru }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10: Invalid character: T");
+    testError("{ \"foo\": nul }",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.10: Invalid character: n");
+
+    // numbers
+    testError("123",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-3: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-456",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-4: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-0001",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-5: syntax error, unexpected integer, "
+              "expecting {");
+    testError("1234567890123456789012345678901234567890",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-40: Failed to convert "
+              "1234567890123456789012345678901234567890"
+              " to an integer.");
+    testError("-3.14e+0",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-8: syntax error, unexpected floating point, "
+              "expecting {");
+    testError("1e50000",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-7: Failed to convert 1e50000 "
+              "to a floating point.");
+
+    // strings
+    testError("\"aabb\"",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-6: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("{ \"aabb\"err",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.9: Invalid character: e");
+    testError("{ err\"aabb\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: Invalid character: e");
+    testError("\"a\n\tb\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-6: Invalid control in \"a\n\tb\"");
+    testError("\"a\\n\\tb\"",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1-8: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("\"a\\x01b\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-8: Bad escape in \"a\\x01b\"");
+    testError("\"a\\u0162\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0162\"");
+    testError("\"a\\u062z\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-9: Bad escape in \"a\\u062z\"");
+    testError("\"abc\\\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1-6: Overflow escape in \"abc\\\"");
+
+    // from data_unittest.c
+    testError("\\a",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\\"\\\"",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+
+    // want a map
+    testError("[]\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("[]\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("{ 123 }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3-5: syntax error, unexpected integer, "
+              "expecting }");
+    testError("{ 123 }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.3-5: syntax error, unexpected integer");
+    testError("{ \"foo\" }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.9: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"foo\" }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.9: syntax error, unexpected }, expecting :");
+    testError("{ \"foo\":null }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.3-7: got unexpected keyword "
+              "\"foo\" in toplevel map.");
+    testError("{ \"Dhcp4\" }\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:1.11: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"Dhcp6\":[]\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting \",\" or }");
+    testError("{}{}\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected {, "
+              "expecting end of file");
+
+    // bad commas
+    testError("{ , }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ , \"foo\":true }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ \"foo\":true, }\n",
+              Parser4Context::PARSER_JSON,
+              "<string>:1.15: syntax error, unexpected }, "
+              "expecting constant string");
+
+    // bad type
+    testError("{ \"Dhcp4\":{\n"
+              "  \"valid-lifetime\":false }}\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:2.20-24: syntax error, unexpected boolean, "
+              "expecting integer");
+
+    // unknown keyword
+    testError("{ \"Dhcp4\":{\n"
+              " \"valid_lifetime\":600 }}\n",
+              Parser4Context::PARSER_DHCP4,
+              "<string>:2.2-17: got unexpected keyword "
+              "\"valid_lifetime\" in Dhcp4 map.");
+}
+
+// Check unicode escapes
+TEST(ParserTest, unicodeEscapes) {
+    ConstElementPtr result;
+    string json;
+
+    // check we can reread output
+    for (char c = -128; c < 127; ++c) {
+        string ins(" ");
+        ins[1] = c;
+        ConstElementPtr e(new StringElement(ins));
+        json = e->str();
+        ASSERT_NO_THROW(
+        try {
+            Parser4Context ctx;
+            result = ctx.parseString(json, Parser4Context::PARSER_JSON);
+        } catch (const std::exception &x) {
+            cout << "EXCEPTION: " << x.what() << endl;
+            throw;
+        });
+        ASSERT_EQ(Element::string, result->getType());
+        EXPECT_EQ(ins, result->stringValue());
+    }
+}
+
+// This test checks that all representations of a slash is recognized properly.
+TEST(ParserTest, unicodeSlash) {
+    // check the 4 possible encodings of solidus '/'
+    ConstElementPtr result;
+    string json = "\"/\\/\\u002f\\u002F\"";
+    ASSERT_NO_THROW(
+    try {
+        Parser4Context ctx;
+        result = ctx.parseString(json, Parser4Context::PARSER_JSON);
+    } catch (const std::exception &x) {
+        cout << "EXCEPTION: " << x.what() << endl;
+        throw;
+    });
+    ASSERT_EQ(Element::string, result->getType());
+    EXPECT_EQ("////", result->stringValue());
+}
+
+};

+ 1 - 1
src/bin/dhcp6/Makefile.am

@@ -66,7 +66,7 @@ libdhcp6_la_SOURCES += dhcp6to4_ipc.cc dhcp6to4_ipc.h
 
 libdhcp6_la_SOURCES += dhcp6_lexer.ll location.hh position.hh stack.hh
 libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h
-libdhcp6_la_SOURCES += parser_context.cc parser_context.h
+libdhcp6_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 
 libdhcp6_la_SOURCES += kea_controller.cc
 

File diff suppressed because it is too large
+ 1569 - 544
src/bin/dhcp6/dhcp6_lexer.cc


+ 44 - 12
src/bin/dhcp6/dhcp6_lexer.ll

@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+/* 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
@@ -853,15 +853,6 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
-\"version\" {
-    switch(driver.ctx_) {
-    case isc::dhcp::Parser6Context::DHCP6:
-        return isc::dhcp::Dhcp6Parser::make_VERSION(driver.loc_);
-    default:
-        return isc::dhcp::Dhcp6Parser::make_STRING("version", driver.loc_);
-    }
-}
-
 \"control-socket\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
@@ -926,6 +917,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     std::string decoded;
     decoded.reserve(len);
     for (size_t pos = 0; pos < len; ++pos) {
+        int b = 0;
         char c = raw[pos];
         switch (c) {
         case '"':
@@ -960,8 +952,42 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
                 decoded.push_back('\t');
                 break;
             case 'u':
-                // not yet implemented
-                driver.error(driver.loc_, "Unsupported unicode escape in \"" + raw + "\"");
+                // support only \u0000 to \u00ff
+                ++pos;
+                if (pos + 4 > len) {
+                    // impossible condition
+                    driver.error(driver.loc_,
+                                 "Overflow unicode escape in \"" + raw + "\"");
+                }
+                if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+                    driver.error(driver.loc_, "Unsupported unicode escape in \"" + raw + "\"");
+                }
+                pos += 2;
+                c = raw[pos];
+                if ((c >= '0') && (c <= '9')) {
+                    b = (c - '0') << 4;
+                } else if ((c >= 'A') && (c <= 'F')) {
+                    b = (c - 'A' + 10) << 4;
+                } else if ((c >= 'a') && (c <= 'f')) {
+                    b = (c - 'a' + 10) << 4;
+                } else {
+                    // impossible condition
+                    driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+                }
+                pos++;
+                c = raw[pos];
+                if ((c >= '0') && (c <= '9')) {
+                    b |= c - '0';
+                } else if ((c >= 'A') && (c <= 'F')) {
+                    b |= c - 'A' + 10;
+                } else if ((c >= 'a') && (c <= 'f')) {
+                    b |= c - 'a' + 10;
+                } else {
+                    // impossible condition
+                    driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+                }
+                decoded.push_back(static_cast<char>(b & 0xff));
+                break;
             default:
                 // impossible condition
                 driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
@@ -1041,6 +1067,12 @@ null {
    return isc::dhcp::Dhcp6Parser::make_NULL_TYPE(driver.loc_);
 }
 
+(?i:true) driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+
+(?i:false) driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+
+(?i:null) driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+
 <*>.   driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
 
 <<EOF>> {

File diff suppressed because it is too large
+ 1155 - 1204
src/bin/dhcp6/dhcp6_parser.cc


+ 54 - 70
src/bin/dhcp6/dhcp6_parser.h

@@ -305,7 +305,6 @@ namespace isc { namespace dhcp {
     union union_type
     {
       // value
-      // version_value
       char dummy1[sizeof(ElementPtr)];
 
       // "boolean"
@@ -415,34 +414,33 @@ namespace isc { namespace dhcp {
         TOKEN_TIME = 328,
         TOKEN_ENTERPRISE_ID = 329,
         TOKEN_DHCP4O6_PORT = 330,
-        TOKEN_VERSION = 331,
-        TOKEN_CONTROL_SOCKET = 332,
-        TOKEN_SOCKET_TYPE = 333,
-        TOKEN_SOCKET_NAME = 334,
-        TOKEN_DHCP_DDNS = 335,
-        TOKEN_LOGGING = 336,
-        TOKEN_LOGGERS = 337,
-        TOKEN_OUTPUT_OPTIONS = 338,
-        TOKEN_OUTPUT = 339,
-        TOKEN_DEBUGLEVEL = 340,
-        TOKEN_SEVERITY = 341,
-        TOKEN_DHCP4 = 342,
-        TOKEN_DHCPDDNS = 343,
-        TOKEN_TOPLEVEL_JSON = 344,
-        TOKEN_TOPLEVEL_DHCP6 = 345,
-        TOKEN_SUB_DHCP6 = 346,
-        TOKEN_SUB_INTERFACES6 = 347,
-        TOKEN_SUB_SUBNET6 = 348,
-        TOKEN_SUB_POOL6 = 349,
-        TOKEN_SUB_PD_POOL = 350,
-        TOKEN_SUB_RESERVATION = 351,
-        TOKEN_SUB_OPTION_DEF = 352,
-        TOKEN_SUB_OPTION_DATA = 353,
-        TOKEN_SUB_HOOKS_LIBRARY = 354,
-        TOKEN_STRING = 355,
-        TOKEN_INTEGER = 356,
-        TOKEN_FLOAT = 357,
-        TOKEN_BOOLEAN = 358
+        TOKEN_CONTROL_SOCKET = 331,
+        TOKEN_SOCKET_TYPE = 332,
+        TOKEN_SOCKET_NAME = 333,
+        TOKEN_DHCP_DDNS = 334,
+        TOKEN_LOGGING = 335,
+        TOKEN_LOGGERS = 336,
+        TOKEN_OUTPUT_OPTIONS = 337,
+        TOKEN_OUTPUT = 338,
+        TOKEN_DEBUGLEVEL = 339,
+        TOKEN_SEVERITY = 340,
+        TOKEN_DHCP4 = 341,
+        TOKEN_DHCPDDNS = 342,
+        TOKEN_TOPLEVEL_JSON = 343,
+        TOKEN_TOPLEVEL_DHCP6 = 344,
+        TOKEN_SUB_DHCP6 = 345,
+        TOKEN_SUB_INTERFACES6 = 346,
+        TOKEN_SUB_SUBNET6 = 347,
+        TOKEN_SUB_POOL6 = 348,
+        TOKEN_SUB_PD_POOL = 349,
+        TOKEN_SUB_RESERVATION = 350,
+        TOKEN_SUB_OPTION_DEF = 351,
+        TOKEN_SUB_OPTION_DATA = 352,
+        TOKEN_SUB_HOOKS_LIBRARY = 353,
+        TOKEN_STRING = 354,
+        TOKEN_INTEGER = 355,
+        TOKEN_FLOAT = 356,
+        TOKEN_BOOLEAN = 357
       };
     };
 
@@ -855,10 +853,6 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
-    make_VERSION (const location_type& l);
-
-    static inline
-    symbol_type
     make_CONTROL_SOCKET (const location_type& l);
 
     static inline
@@ -1170,12 +1164,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 638,     ///< Last index in yytable_.
-      yynnts_ = 274,  ///< Number of nonterminal symbols.
+      yylast_ = 630,     ///< Last index in yytable_.
+      yynnts_ = 271,  ///< Number of nonterminal symbols.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 104  ///< Number of tokens.
+      yyntokens_ = 103  ///< Number of tokens.
     };
 
 
@@ -1227,9 +1221,9 @@ namespace isc { namespace dhcp {
       65,    66,    67,    68,    69,    70,    71,    72,    73,    74,
       75,    76,    77,    78,    79,    80,    81,    82,    83,    84,
       85,    86,    87,    88,    89,    90,    91,    92,    93,    94,
-      95,    96,    97,    98,    99,   100,   101,   102,   103
+      95,    96,    97,    98,    99,   100,   101,   102
     };
-    const unsigned int user_token_number_max_ = 358;
+    const unsigned int user_token_number_max_ = 357;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1262,24 +1256,23 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1300,24 +1293,23 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.copy< ElementPtr > (v);
         break;
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1397,24 +1389,23 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1441,24 +1432,23 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1527,7 +1517,7 @@ namespace isc { namespace dhcp {
      325,   326,   327,   328,   329,   330,   331,   332,   333,   334,
      335,   336,   337,   338,   339,   340,   341,   342,   343,   344,
      345,   346,   347,   348,   349,   350,   351,   352,   353,   354,
-     355,   356,   357,   358
+     355,   356,   357
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -1977,12 +1967,6 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
-  Dhcp6Parser::make_VERSION (const location_type& l)
-  {
-    return symbol_type (token::TOKEN_VERSION, l);
-  }
-
-  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_CONTROL_SOCKET (const location_type& l)
   {
     return symbol_type (token::TOKEN_CONTROL_SOCKET, l);
@@ -2147,7 +2131,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2151 "dhcp6_parser.h" // lalr1.cc:377
+#line 2135 "dhcp6_parser.h" // lalr1.cc:377
 
 
 

+ 1 - 18
src/bin/dhcp6/dhcp6_parser.yy

@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+/* 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
@@ -127,7 +127,6 @@ using namespace std;
   ENTERPRISE_ID "enterprise-id"
 
   DHCP4O6_PORT "dhcp4o6-port"
-  VERSION "version"
 
   CONTROL_SOCKET "control-socket"
   SOCKET_TYPE "socket-type"
@@ -186,7 +185,6 @@ using namespace std;
 %token <bool> BOOLEAN "boolean"
 
 %type <ElementPtr> value
-%type <ElementPtr> version_value
 
 %printer { yyoutput << $$; } <*>;
 
@@ -374,7 +372,6 @@ global_param: preferred_lifetime
             | expired_leases_processing
             | server_id
             | dhcp4o6_port
-            | version
             | control_socket
             | dhcp_ddns
             | unknown_map_entry
@@ -1370,20 +1367,6 @@ dhcp4o6_port: DHCP4O6_PORT COLON INTEGER {
     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 {

+ 0 - 3
src/bin/dhcp6/json_config_parser.cc

@@ -707,9 +707,6 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
         parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET6);
     } else if (config_id.compare("option-def") == 0) {
         parser  = new OptionDefListParser(config_id, globalContext());
-    } else if (config_id.compare("version") == 0) {
-        parser  = new StringParser(config_id,
-                                   globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
         parser = new DbAccessParser(config_id, DbAccessParser::LEASE_DB);
     } else if (config_id.compare("hosts-database") == 0) {

+ 1 - 1
src/bin/dhcp6/location.hh

@@ -1,4 +1,4 @@
-// Generated 201612101015
+// Generated 201612201711
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Locations for Bison parsers in C++

+ 1 - 1
src/bin/dhcp6/parser_context_decl.h

@@ -7,7 +7,7 @@
 #ifndef PARSER6_CONTEXT_DECL_H
 #define PARSER6_CONTEXT_DECL_H
 
-/// @file parser_context_decl.h Forward declaration of the ParserContext class
+/// @file dhcp6/parser_context_decl.h Forward declaration of the ParserContext class
 
 namespace isc {
 namespace dhcp {

+ 1 - 1
src/bin/dhcp6/position.hh

@@ -1,4 +1,4 @@
-// Generated 201612101015
+// Generated 201612201711
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 1 - 1
src/bin/dhcp6/stack.hh

@@ -1,4 +1,4 @@
-// Generated 201612101015
+// Generated 201612201711
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Stack handling for Bison parsers in C++

+ 2 - 16
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -732,20 +732,6 @@ public:
     isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 
-// Goal of this test is a verification if a very simple config update
-// with just a bumped version number. That's the simplest possible
-// config update.
-TEST_F(Dhcp6ParserTest, version) {
-
-    ConstElementPtr json;
-    ASSERT_NO_THROW(json = parseDHCP6("{\"version\": 0}"));
-
-    ConstElementPtr x;
-    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    // returned value must be 0 (configuration accepted)
-    checkResult(x, 0);
-}
-
 /// The goal of this test is to verify that the code accepts only
 /// valid commands and malformed or unsupported parameters are rejected.
 TEST_F(Dhcp6ParserTest, bogusCommand) {
@@ -1579,8 +1565,8 @@ TEST_F(Dhcp6ParserTest, pdPoolPrefixExclude) {
         "] }";
 
     // Convert the JSON string into Elements.
-    ElementPtr json;
-    ASSERT_NO_THROW(json = Element::fromJSON(config));
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP6(config));
 
     // Verify that DHCP6 configuration processing succeeds.
     // Returned value must be non-empty ConstElementPtr to config result.

+ 55 - 9
src/bin/dhcp6/tests/parser_unittest.cc

@@ -31,8 +31,8 @@ void testParser(const std::string& txt, Parser6Context::ParserType parser_type)
     ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
     ASSERT_NO_THROW({
             try {
-        Parser6Context ctx;
-        test_json = ctx.parseString(txt, parser_type);
+                Parser6Context ctx;
+                test_json = ctx.parseString(txt, parser_type);
             } catch (const std::exception &e) {
                 cout << "EXCEPTION: " << e.what() << endl;
                 throw;
@@ -49,8 +49,8 @@ void testParser2(const std::string& txt, Parser6Context::ParserType parser_type)
 
     ASSERT_NO_THROW({
             try {
-        Parser6Context ctx;
-        test_json = ctx.parseString(txt, parser_type);
+                Parser6Context ctx;
+                test_json = ctx.parseString(txt, parser_type);
             } catch (const std::exception &e) {
                 cout << "EXCEPTION: " << e.what() << endl;
                 throw;
@@ -330,13 +330,22 @@ TEST(ParserTest, errors) {
               Parser6Context::PARSER_JSON,
               "Can't open include file /foo/bar");
 
-    // case sensitivity
+    // JSON keywords
     testError("{ \"foo\": True }",
               Parser6Context::PARSER_JSON,
+              "<string>:1.10-13: JSON true reserved keyword is lower case only");
+    testError("{ \"foo\": False }",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.10-14: JSON false reserved keyword is lower case only");
+    testError("{ \"foo\": NULL }",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.10-13: JSON null reserved keyword is lower case only");
+    testError("{ \"foo\": Tru }",
+              Parser6Context::PARSER_JSON,
               "<string>:1.10: Invalid character: T");
-    testError("{ \"foo\": NULL  }",
+    testError("{ \"foo\": nul }",
               Parser6Context::PARSER_JSON,
-              "<string>:1.10: Invalid character: N");
+              "<string>:1.10: Invalid character: n");
 
     // numbers
     testError("123",
@@ -386,9 +395,9 @@ TEST(ParserTest, errors) {
     testError("\"a\\x01b\"",
               Parser6Context::PARSER_JSON,
               "<string>:1.1-8: Bad escape in \"a\\x01b\"");
-    testError("\"a\\u0062\"",
+    testError("\"a\\u0162\"",
               Parser6Context::PARSER_JSON,
-              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0062\"");
+              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0162\"");
     testError("\"a\\u062z\"",
               Parser6Context::PARSER_JSON,
               "<string>:1.1-9: Bad escape in \"a\\u062z\"");
@@ -476,4 +485,41 @@ TEST(ParserTest, errors) {
               "\"preferred_lifetime\" in Dhcp6 map.");
 }
 
+// Check unicode escapes
+TEST(ParserTest, unicodeEscapes) {
+    ConstElementPtr result;
+    string json;
+
+    // check we can reread output
+    for (char c = -128; c < 127; ++c) {
+        string ins(" ");
+        ins[1] = c;
+        ConstElementPtr e(new StringElement(ins));
+        json = e->str();
+        ASSERT_NO_THROW(
+        try {
+            Parser6Context ctx;
+            result = ctx.parseString(json, Parser6Context::PARSER_JSON);
+        } catch (const std::exception &x) {
+            cout << "EXCEPTION: " << x.what() << endl;
+            throw;
+        });
+        ASSERT_EQ(Element::string, result->getType());
+        EXPECT_EQ(ins, result->stringValue());
+    }
+
+    // check the 4 possible encodings of solidus '/'
+    json = "\"/\\/\\u002f\\u002F\"";
+    ASSERT_NO_THROW(
+    try {
+        Parser6Context ctx;
+        result = ctx.parseString(json, Parser6Context::PARSER_JSON);
+    } catch (const std::exception &x) {
+        cout << "EXCEPTION: " << x.what() << endl;
+        throw;
+    });
+    ASSERT_EQ(Element::string, result->getType());
+    EXPECT_EQ("////", result->stringValue());
+}       
+
 };

+ 13 - 8
src/lib/cc/data.cc

@@ -14,12 +14,12 @@
 #include <map>
 #include <cstdio>
 #include <iostream>
+#include <iomanip>
 #include <string>
 #include <sstream>
 #include <fstream>
 #include <cerrno>
 
-#include <boost/algorithm/string.hpp> // for iequals
 #include <boost/lexical_cast.hpp>
 
 #include <cmath>
@@ -457,10 +457,10 @@ fromStringstreamBool(std::istream& in, const std::string& file,
     // This will move the pos to the end of the value.
     const std::string word = wordFromStringstream(in, pos);
 
-    if (boost::iequals(word, "True")) {
+    if (word == "true") {
         return (Element::create(true, Element::Position(file, line,
                                                         start_pos)));
-    } else if (boost::iequals(word, "False")) {
+    } else if (word == "false") {
         return (Element::create(false, Element::Position(file, line,
                                                          start_pos)));
     } else {
@@ -479,7 +479,7 @@ fromStringstreamNull(std::istream& in, const std::string& file,
     const uint32_t start_pos = pos;
     // This will move the pos to the end of the value.
     const std::string word = wordFromStringstream(in, pos);
-    if (boost::iequals(word, "null")) {
+    if (word == "null") {
         return (Element::create(Element::Position(file, line, start_pos)));
     } else {
         throwJSONError(std::string("Bad null value: ") + word, file,
@@ -658,16 +658,13 @@ Element::fromJSON(std::istream& in, const std::string& file, int& line,
                 el_read = true;
                 break;
             case 't':
-            case 'T':
             case 'f':
-            case 'F':
                 in.putback(c);
                 --pos;
                 element = fromStringstreamBool(in, file, line, pos);
                 el_read = true;
                 break;
             case 'n':
-            case 'N':
                 in.putback(c);
                 --pos;
                 element = fromStringstreamNull(in, file, line, pos);
@@ -795,7 +792,15 @@ StringElement::toJSON(std::ostream& ss) const {
             ss << '\\' << 't';
             break;
         default:
-            ss << c;
+            if ((c >= 0) && (c < 0x20)) {
+                ss << "\\u"
+                   << hex
+                   << setw(4)
+                   << setfill('0')
+                   << (static_cast<unsigned>(c) & 0xff);
+            } else {
+                ss << c;
+            }
         }
     }
     ss << "\"";

+ 15 - 19
src/lib/cc/tests/data_unittests.cc

@@ -131,11 +131,13 @@ TEST(Element, from_and_to_json) {
     sv.push_back("{1}");
     //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
     //std::cout << ep << std::endl;
-    sv.push_back("\n\nTru");
+    sv.push_back("\n\nTrue");
+    sv.push_back("\n\ntru");
     sv.push_back("{ \n \"aaa\nbbb\"err:");
-    sv.push_back("{ \t\n \"aaa\nbbb\"\t\n\n:\n True, \"\\\"");
+    sv.push_back("{ \t\n \"aaa\nbbb\"\t\n\n:\n true, \"\\\"");
     sv.push_back("{ \"a\": None}");
     sv.push_back("");
+    sv.push_back("NULL");
     sv.push_back("nul");
     sv.push_back("hello\"foobar\"");
     sv.push_back("\"foobar\"hello");
@@ -178,12 +180,6 @@ TEST(Element, from_and_to_json) {
     EXPECT_EQ("0.01", Element::fromJSON("1.0e-2")->str());
     EXPECT_EQ("0.012", Element::fromJSON("1.2e-2")->str());
     EXPECT_EQ("0.012", Element::fromJSON("1.2E-2")->str());
-    EXPECT_EQ("null", Element::fromJSON("Null")->str());
-    EXPECT_EQ("null", Element::fromJSON("NULL")->str());
-    EXPECT_EQ("false", Element::fromJSON("False")->str());
-    EXPECT_EQ("false", Element::fromJSON("FALSE")->str());
-    EXPECT_EQ("true", Element::fromJSON("True")->str());
-    EXPECT_EQ("true", Element::fromJSON("TRUE")->str());
     EXPECT_EQ("\"\"", Element::fromJSON("  \n \t \r \f \b \"\" \n \f \t \r \b")->str());
     EXPECT_EQ("{  }", Element::fromJSON("{  \n  \r \t  \b \f }")->str());
     EXPECT_EQ("[  ]", Element::fromJSON("[  \n  \r \f \t  \b  ]")->str());
@@ -709,35 +705,35 @@ TEST(Element, equals) {
     EXPECT_NE(*efs("1"), *efs("2"));
     EXPECT_NE(*efs("1"), *efs("\"1\""));
     EXPECT_NE(*efs("1"), *efs("[]"));
-    EXPECT_NE(*efs("1"), *efs("True"));
+    EXPECT_NE(*efs("1"), *efs("true"));
     EXPECT_NE(*efs("1"), *efs("{}"));
 
     EXPECT_EQ(*efs("1.1"), *efs("1.1"));
     EXPECT_NE(*efs("1.0"), *efs("1"));
     EXPECT_NE(*efs("1.1"), *efs("\"1\""));
     EXPECT_NE(*efs("1.1"), *efs("[]"));
-    EXPECT_NE(*efs("1.1"), *efs("True"));
+    EXPECT_NE(*efs("1.1"), *efs("true"));
     EXPECT_NE(*efs("1.1"), *efs("{}"));
 
-    EXPECT_EQ(*efs("True"), *efs("True"));
-    EXPECT_NE(*efs("True"), *efs("False"));
-    EXPECT_NE(*efs("True"), *efs("1"));
-    EXPECT_NE(*efs("True"), *efs("\"1\""));
-    EXPECT_NE(*efs("True"), *efs("[]"));
-    EXPECT_NE(*efs("True"), *efs("{}"));
+    EXPECT_EQ(*efs("true"), *efs("true"));
+    EXPECT_NE(*efs("true"), *efs("false"));
+    EXPECT_NE(*efs("true"), *efs("1"));
+    EXPECT_NE(*efs("true"), *efs("\"1\""));
+    EXPECT_NE(*efs("true"), *efs("[]"));
+    EXPECT_NE(*efs("true"), *efs("{}"));
 
     EXPECT_EQ(*efs("\"foo\""), *efs("\"foo\""));
     EXPECT_NE(*efs("\"foo\""), *efs("\"bar\""));
     EXPECT_NE(*efs("\"foo\""), *efs("1"));
     EXPECT_NE(*efs("\"foo\""), *efs("\"1\""));
-    EXPECT_NE(*efs("\"foo\""), *efs("True"));
+    EXPECT_NE(*efs("\"foo\""), *efs("true"));
     EXPECT_NE(*efs("\"foo\""), *efs("[]"));
     EXPECT_NE(*efs("\"foo\""), *efs("{}"));
 
     EXPECT_EQ(*efs("[]"), *efs("[]"));
     EXPECT_EQ(*efs("[ 1, 2, 3 ]"), *efs("[ 1, 2, 3 ]"));
-    EXPECT_EQ(*efs("[ \"a\", [ True, 1], 2.2 ]"), *efs("[ \"a\", [ True, 1], 2.2 ]"));
-    EXPECT_NE(*efs("[ \"a\", [ True, 1], 2.2 ]"), *efs("[ \"a\", [ True, 2], 2.2 ]"));
+    EXPECT_EQ(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 1], 2.2 ]"));
+    EXPECT_NE(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 2], 2.2 ]"));
     EXPECT_NE(*efs("[]"), *efs("[1]"));
     EXPECT_NE(*efs("[]"), *efs("1"));
     EXPECT_NE(*efs("[]"), *efs("\"1\""));

+ 1 - 1
src/lib/config/documentation.txt

@@ -33,7 +33,7 @@ To add a simple configuration option, let's say an int, we make it the following
 "config_data" contains a list of elements of the form
 { "item_name": "name"
   "item_type": "integer|real|boolean|string|list|map"
-  "item_optional": True|False
+  "item_optional": true|false
   "item_default": <depends on type>
 }
 

+ 3 - 3
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -117,7 +117,7 @@ template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
     // Invoke common code for all specializations of build().
     buildCommon(value);
     // The Config Manager checks if user specified a
-    // valid value for a boolean parameter: True or False.
+    // valid value for a boolean parameter: true or false.
     // We should have a boolean Element, use value directly
     try {
         value_ = value->boolValue();
@@ -1475,11 +1475,11 @@ D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
         mode_str  = string_values_->getOptionalParam("replace-client-name",
                                                      D2ClientConfig::
                                                      DFT_REPLACE_CLIENT_NAME_MODE);
-        if (boost::iequals(mode_str, "FALSE")) {
+        if (boost::iequals(mode_str, "false")) {
             // @todo add a debug log
             replace_client_name_mode = D2ClientConfig::RCM_NEVER;
         }
-        else if (boost::iequals(mode_str, "TRUE")) {
+        else if (boost::iequals(mode_str, "true")) {
             // @todo add a debug log
             replace_client_name_mode = D2ClientConfig::RCM_WHEN_PRESENT;
         } else {

+ 8 - 8
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -483,7 +483,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
+        "      \"array\": false,"
         "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"\""
@@ -604,7 +604,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
         "    \"space\": \"isc\","
         "    \"code\": 100,"
         "    \"data\": \"192.0.2.0\","
-        "    \"csv-format\": True"
+        "    \"csv-format\": true"
         " } ]"
         "}";
 
@@ -733,7 +733,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
         "    \"name\": \"swap-server\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 16,"
-        "    \"csv-format\": True,"
+        "    \"csv-format\": true,"
         "    \"data\": \"192.0.2.0\""
         " } ]"
         "}";
@@ -753,7 +753,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
         "    \"name\": \"swap-server\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 16,"
-        "    \"csv-format\": False,"
+        "    \"csv-format\": false,"
         "    \"data\": \"C0000200\""
         " } ]"
         "}";
@@ -840,7 +840,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
         "    \"name\": \"foo-name\","
         "    \"space\": \"dhcp6\","
         "    \"code\": 25000,"
-        "    \"csv-format\": True,"
+        "    \"csv-format\": true,"
         "    \"data\": \"0\""
         " } ]"
         "}";
@@ -855,7 +855,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
         "    \"name\": \"foo-name\","
         "    \"space\": \"dhcp6\","
         "    \"code\": 25000,"
-        "    \"csv-format\": False,"
+        "    \"csv-format\": false,"
         "    \"data\": \"0\""
         " } ]"
         "}";
@@ -974,7 +974,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
         "      \"name\": \"foo-name\","
         "      \"code\": 2345,"
         "      \"type\": \"ipv6-address\","
-        "      \"array\": True,"
+        "      \"array\": true,"
         "      \"space\": \"dhcp6\""
         "  } ],"
         "  \"option-data\": [ {"
@@ -1000,7 +1000,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
         "      \"name\": \"foo-name\","
         "      \"code\": 2345,"
         "      \"type\": \"ipv6-address\","
-        "      \"array\": True,"
+        "      \"array\": true,"
         "      \"space\": \"dhcp6\""
         "  } ],"
         "  \"option-data\": [ {"