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/sample1.json
 nobase_dist_doc_DATA += examples/ddns/template.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/backends.json
 nobase_dist_doc_DATA += examples/kea4/classify.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/hooks.json
 nobase_dist_doc_DATA += examples/kea4/leases-expiration.json
 nobase_dist_doc_DATA += examples/kea4/leases-expiration.json
 nobase_dist_doc_DATA += examples/kea4/multiple-options.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/advanced.json
 nobase_dist_doc_DATA += examples/kea6/backends.json
 nobase_dist_doc_DATA += examples/kea6/backends.json
 nobase_dist_doc_DATA += examples/kea6/classify.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/hooks.json
 nobase_dist_doc_DATA += examples/kea6/leases-expiration.json
 nobase_dist_doc_DATA += examples/kea6/leases-expiration.json
 nobase_dist_doc_DATA += examples/kea6/multiple-options.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
 # Let's pick VoIP phones. Those that send their class identifiers
 # as Aastra, should belong to VoIP class. For a list of all options,
 # 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",
       "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.
 # to that class are allowed for that subnet.
   "subnet4": [
   "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" } ],
         "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
         "subnet": "192.0.2.0/24",
         "subnet": "192.0.2.0/24",
         "client-class": "VoIP",
         "client-class": "VoIP",

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

@@ -37,7 +37,11 @@
         "library": "/opt/lib/security.so"
         "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"
                      "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,
   "renew-timer": 1000,
   "rebind-timer": 2000,
   "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
 # The following list defines subnets. Each subnet consists of at
 # least subnet and pool entries.
 # 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
 # The following configures logging. It assumes that messages with at least

+ 7 - 11
doc/guide/config.xml

@@ -37,27 +37,23 @@
       <itemizedlist>
       <itemizedlist>
         <listitem>
         <listitem>
           <simpara>shell comments: any text after the hash (#)
           <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>
         <listitem>
         <listitem>
           <simpara>C comments: any text after the double slashes (//)
           <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>
         <listitem>
         <listitem>
           <simpara>Multiline comments: any text between /* and */ is
           <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>
         <listitem>
         <listitem>
           <simpara>File inclusion: JSON files can include other JSON
           <simpara>File inclusion: JSON files can include other JSON
           files. This can be done by using &lt;?include
           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>
           feature.</simpara>
         </listitem>
         </listitem>
       </itemizedlist>
       </itemizedlist>

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

@@ -5,3 +5,4 @@
 /spec_config.h
 /spec_config.h
 /spec_config.h.pre
 /spec_config.h.pre
 /s-messages
 /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 += dhcp4_srv.cc dhcp4_srv.h
 libdhcp4_la_SOURCES += dhcp4to6_ipc.cc dhcp4to6_ipc.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
 libdhcp4_la_SOURCES += kea_controller.cc
 
 
 nodist_libdhcp4_la_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 nodist_libdhcp4_la_SOURCES = dhcp4_messages.h dhcp4_messages.cc
@@ -104,3 +108,30 @@ endif
 
 
 kea_dhcp4dir = $(pkgdatadir)
 kea_dhcp4dir = $(pkgdatadir)
 kea_dhcp4_DATA = dhcp4.spec
 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
 directly have their entire implementation in the
 src/bin/dhcp4/json_config_parser.cc.
 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
 @section dhcpv4ConfigInherit DHCPv4 configuration inheritance
 
 
 One notable useful feature of DHCP configuration is its parameter 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);
         parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET);
     } else if (config_id.compare("option-def") == 0) {
     } else if (config_id.compare("option-def") == 0) {
         parser  = new OptionDefListParser(config_id, globalContext());
         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,
         parser  = new StringParser(config_id,
                                     globalContext()->string_values_);
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
     } 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/json_config_parser.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcp4/parser_context.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <exceptions/exceptions.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
         // 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) {
         if (!json) {
             isc_throw(isc::BadValue, "no configuration found");
             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 += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp4/tests\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp4/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 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)/interfaces.txt $(builddir)/logger_lockfile
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
 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 += dora_unittest.cc
 dhcp4_unittests_SOURCES += host_options_unittest.cc
 dhcp4_unittests_SOURCES += host_options_unittest.cc
 dhcp4_unittests_SOURCES += release_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 += out_of_range_unittest.cc
 dhcp4_unittests_SOURCES += decline_unittest.cc
 dhcp4_unittests_SOURCES += decline_unittest.cc
 dhcp4_unittests_SOURCES += kea_controller_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\","
         "   \"name\": \"pxe4\","
         "   \"test\": \"option[93].hex == 0x0001\","
         "   \"test\": \"option[93].hex == 0x0001\","
         "   \"boot-file-name\": \"ipxe.efi\""
         "   \"boot-file-name\": \"ipxe.efi\""
-        "},"
-        "],"
+        "}],"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
         "    \"subnet\": \"10.0.0.0/24\", "
         "    \"id\": 1,"
         "    \"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 <config/command_mgr.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -117,7 +118,8 @@ public:
 
 
         ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv4Srv()));
         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);
         ConstElementPtr answer = server_->processConfig(config);
         ASSERT_TRUE(answer);
         ASSERT_TRUE(answer);
 
 

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

@@ -1067,7 +1067,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
         "          \"space\": \"vendor-4491\","
         "          \"space\": \"vendor-4491\","
         "          \"code\": 2,"
         "          \"code\": 2,"
         "          \"data\": \"10.253.175.16\","
         "          \"data\": \"10.253.175.16\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "        }],"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
         "    \"pools\": [ { \"pool\": \"10.254.226.0/25\" } ],"
@@ -1079,7 +1079,8 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1221,7 +1222,8 @@ TEST_F(Dhcpv4SrvTest, nextServerOverride) {
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
 
 
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
 
 
@@ -1269,7 +1271,8 @@ TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config, true));
 
 
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
     EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
 
 
@@ -1358,7 +1361,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
         "          \"space\": \"vendor-4491\","
         "          \"space\": \"vendor-4491\","
         "          \"code\": 2,"
         "          \"code\": 2,"
         "          \"data\": \"192.0.2.1, 192.0.2.2\","
         "          \"data\": \"192.0.2.1, 192.0.2.2\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "        }],"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
         "    \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
@@ -1370,7 +1373,8 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
 
 
     EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
     EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
     ASSERT_TRUE(x);
     ASSERT_TRUE(x);
@@ -1448,7 +1452,7 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
         "          \"code\": ";
         "          \"code\": ";
     string config_postfix = ","
     string config_postfix = ","
         "          \"data\": \"192.0.2.1\","
         "          \"data\": \"192.0.2.1\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "        }],"
         "\"subnet4\": [ { "
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.50\" } ],"
         "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.50\" } ],"
@@ -1468,8 +1472,10 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
     // definition, the config should fail.
     // definition, the config should fail.
     string config_bogus = config_prefix + "99" + config_postfix;
     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);
     NakedDhcpv4Srv srv(0);
 
 
@@ -1543,7 +1549,8 @@ TEST_F(Dhcpv4SrvTest, matchClassification) {
         "             \"data\": \"true\" } ], "
         "             \"data\": \"true\" } ], "
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1629,7 +1636,8 @@ TEST_F(Dhcpv4SrvTest, matchClassificationOptionName) {
         "{   \"name\": \"router\", "
         "{   \"name\": \"router\", "
         "    \"test\": \"option[host-name].text == 'foo'\" } ] }";
         "    \"test\": \"option[host-name].text == 'foo'\" } ] }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1679,7 +1687,8 @@ TEST_F(Dhcpv4SrvTest, matchClassificationOptionDef) {
         "    \"code\": 250, "
         "    \"code\": 250, "
         "    \"type\": \"string\" } ] }";
         "    \"type\": \"string\" } ] }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1734,7 +1743,8 @@ TEST_F(Dhcpv4SrvTest, subnetClassPriority) {
         "             \"data\": \"true\" } ], "
         "             \"data\": \"true\" } ], "
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1806,7 +1816,8 @@ TEST_F(Dhcpv4SrvTest, subnetGlobalPriority) {
         "    {    \"name\": \"ip-forwarding\", "
         "    {    \"name\": \"ip-forwarding\", "
         "         \"data\": \"true\" } ] }";
         "         \"data\": \"true\" } ] }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1877,7 +1888,8 @@ TEST_F(Dhcpv4SrvTest, classGlobalPriority) {
         "             \"data\": \"true\" } ], "
         "             \"data\": \"true\" } ], "
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
         "    \"test\": \"option[12].text == 'foo'\" } ] }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // 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
 void
 Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
 Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
                          const bool commit) {
                          const bool commit) {
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseJSON(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // 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.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/parser_context.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <cc/command_interpreter.h>
 #include <cc/command_interpreter.h>
 #include <list>
 #include <list>
@@ -478,6 +479,62 @@ public:
     NakedDhcpv4Srv srv_;
     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::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc 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 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // Configure the server and make sure the config is accepted
@@ -1239,7 +1240,8 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP4(config));
     ConstElementPtr status;
     ConstElementPtr status;
 
 
     // Configure the server and make sure the config is accepted
     // 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/hwaddr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/parser_context.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
@@ -64,6 +65,7 @@ public:
         LeaseMgrFactory::destroy();
         LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
         static_cast<void>(remove(TEST_FILE));
+        static_cast<void>(remove(TEST_INCLUDE));
     };
     };
 
 
     /// @brief writes specified content to a well known file
     /// @brief writes specified content to a well known file
@@ -71,11 +73,12 @@ public:
     /// Writes specified content to TEST_FILE. Tests will
     /// Writes specified content to TEST_FILE. Tests will
     /// attempt to read that file.
     /// attempt to read that file.
     ///
     ///
+    /// @param file_name name of file to be written
     /// @param content content to be written to file
     /// @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());
         EXPECT_TRUE(out.is_open());
         out << content;
         out << content;
         out.close();
         out.close();
@@ -98,9 +101,11 @@ public:
 
 
     /// Name of a config file used during tests
     /// Name of a config file used during tests
     static const char* TEST_FILE;
     static const char* TEST_FILE;
+    static const char* TEST_INCLUDE;
 };
 };
 
 
 const char* JSONFileBackendTest::TEST_FILE  = "test-config.json";
 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.
 // This test checks if configuration can be read from a JSON file.
 TEST_F(JSONFileBackendTest, jsonFile) {
 TEST_F(JSONFileBackendTest, jsonFile) {
@@ -128,7 +133,7 @@ TEST_F(JSONFileBackendTest, jsonFile) {
         "\"valid-lifetime\": 4000 }"
         "\"valid-lifetime\": 4000 }"
         "}";
         "}";
 
 
-    writeFile(config);
+    writeFile(TEST_FILE, config);
 
 
     // Now initialize the server
     // Now initialize the server
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -179,8 +184,9 @@ TEST_F(JSONFileBackendTest, jsonFile) {
     EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
     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"
     string config_hash_comments = "# This is a comment. It should be \n"
         "#ignored. Real config starts in line below\n"
         "#ignored. Real config starts in line below\n"
@@ -198,10 +204,56 @@ TEST_F(JSONFileBackendTest, comments) {
         "\"valid-lifetime\": 4000 }"
         "\"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
     // Now initialize the server
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -230,6 +282,142 @@ TEST_F(JSONFileBackendTest, comments) {
     EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
     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:
 // This test checks if configuration detects failure when trying:
 // - empty file
 // - empty file
 // - empty filename
 // - empty filename
@@ -263,15 +451,15 @@ TEST_F(JSONFileBackendTest, configBroken) {
     EXPECT_THROW(srv->init(""), BadValue);
     EXPECT_THROW(srv->init(""), BadValue);
 
 
     // Try to configure it using empty file. Should fail.
     // Try to configure it using empty file. Should fail.
-    writeFile(config_empty);
+    writeFile(TEST_FILE, config_empty);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 
 
     // Now try to load a config that does not have Dhcp4 component.
     // 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);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 
 
     // Now try to load a config with Dhcp4 full of nonsense.
     // 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);
     EXPECT_THROW(srv->init(TEST_FILE), BadValue);
 }
 }
 
 
@@ -347,7 +535,7 @@ TEST_F(JSONFileBackendTest, timers) {
         "\"subnet4\": [ ],"
         "\"subnet4\": [ ],"
         "\"valid-lifetime\": 4000 }"
         "\"valid-lifetime\": 4000 }"
         "}";
         "}";
-    writeFile(config);
+    writeFile(TEST_FILE, config);
 
 
     // Create an instance of the server and intialize it.
     // Create an instance of the server and intialize it.
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -416,7 +604,7 @@ TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
         "\"subnet4\": [ ],"
         "\"subnet4\": [ ],"
         "\"valid-lifetime\": 4000 }"
         "\"valid-lifetime\": 4000 }"
         "}";
         "}";
-    writeFile(config);
+    writeFile(TEST_FILE, config);
 
 
     // Create an instance of the server and intialize it.
     // Create an instance of the server and intialize it.
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
     boost::scoped_ptr<ControlledDhcpv4Srv> srv;
@@ -518,7 +706,7 @@ void
 JSONFileBackendMySQLTest::
 JSONFileBackendMySQLTest::
 testBackendReconfiguration(const std::string& backend_first,
 testBackendReconfiguration(const std::string& backend_first,
                            const std::string& backend_second) {
                            const std::string& backend_second) {
-    writeFile(createConfiguration(backend_first));
+    writeFile(TEST_FILE, createConfiguration(backend_first));
 
 
     // Create an instance of the server and intialize it.
     // Create an instance of the server and intialize it.
     boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
     boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
@@ -533,7 +721,7 @@ testBackendReconfiguration(const std::string& backend_first,
               LeaseMgrFactory::instance().getType());
               LeaseMgrFactory::instance().getType());
 
 
     // New configuration modifies the lease database backend type.
     // 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
     // Explicitly calling signal handler for SIGHUP to trigger server
     // reconfiguration.
     // 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_lexer.ll location.hh position.hh stack.hh
 libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h
 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
 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
    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
    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\" {
 \"control-socket\" {
     switch(driver.ctx_) {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::DHCP6:
@@ -926,6 +917,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     std::string decoded;
     std::string decoded;
     decoded.reserve(len);
     decoded.reserve(len);
     for (size_t pos = 0; pos < len; ++pos) {
     for (size_t pos = 0; pos < len; ++pos) {
+        int b = 0;
         char c = raw[pos];
         char c = raw[pos];
         switch (c) {
         switch (c) {
         case '"':
         case '"':
@@ -960,8 +952,42 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
                 decoded.push_back('\t');
                 decoded.push_back('\t');
                 break;
                 break;
             case 'u':
             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:
             default:
                 // impossible condition
                 // impossible condition
                 driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
                 driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
@@ -1041,6 +1067,12 @@ null {
    return isc::dhcp::Dhcp6Parser::make_NULL_TYPE(driver.loc_);
    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));
 <*>.   driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
 
 
 <<EOF>> {
 <<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
     union union_type
     {
     {
       // value
       // value
-      // version_value
       char dummy1[sizeof(ElementPtr)];
       char dummy1[sizeof(ElementPtr)];
 
 
       // "boolean"
       // "boolean"
@@ -415,34 +414,33 @@ namespace isc { namespace dhcp {
         TOKEN_TIME = 328,
         TOKEN_TIME = 328,
         TOKEN_ENTERPRISE_ID = 329,
         TOKEN_ENTERPRISE_ID = 329,
         TOKEN_DHCP4O6_PORT = 330,
         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
     static inline
     symbol_type
     symbol_type
-    make_VERSION (const location_type& l);
-
-    static inline
-    symbol_type
     make_CONTROL_SOCKET (const location_type& l);
     make_CONTROL_SOCKET (const location_type& l);
 
 
     static inline
     static inline
@@ -1170,12 +1164,12 @@ namespace isc { namespace dhcp {
     enum
     enum
     {
     {
       yyeof_ = 0,
       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.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyterror_ = 1,
       yyerrcode_ = 256,
       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,
       65,    66,    67,    68,    69,    70,    71,    72,    73,    74,
       75,    76,    77,    78,    79,    80,    81,    82,    83,    84,
       75,    76,    77,    78,    79,    80,    81,    82,    83,    84,
       85,    86,    87,    88,    89,    90,    91,    92,    93,    94,
       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;
     const token_number_type undef_token_ = 2;
 
 
     if (static_cast<int>(t) <= yyeof_)
     if (static_cast<int>(t) <= yyeof_)
@@ -1262,24 +1256,23 @@ namespace isc { namespace dhcp {
   {
   {
       switch (other.type_get ())
       switch (other.type_get ())
     {
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.copy< ElementPtr > (other.value);
         value.copy< ElementPtr > (other.value);
         break;
         break;
 
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.copy< bool > (other.value);
         value.copy< bool > (other.value);
         break;
         break;
 
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.copy< double > (other.value);
         value.copy< double > (other.value);
         break;
         break;
 
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.copy< int64_t > (other.value);
         value.copy< int64_t > (other.value);
         break;
         break;
 
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.copy< std::string > (other.value);
         value.copy< std::string > (other.value);
         break;
         break;
 
 
@@ -1300,24 +1293,23 @@ namespace isc { namespace dhcp {
     (void) v;
     (void) v;
       switch (this->type_get ())
       switch (this->type_get ())
     {
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.copy< ElementPtr > (v);
         value.copy< ElementPtr > (v);
         break;
         break;
 
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.copy< bool > (v);
         value.copy< bool > (v);
         break;
         break;
 
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.copy< double > (v);
         value.copy< double > (v);
         break;
         break;
 
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.copy< int64_t > (v);
         value.copy< int64_t > (v);
         break;
         break;
 
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.copy< std::string > (v);
         value.copy< std::string > (v);
         break;
         break;
 
 
@@ -1397,24 +1389,23 @@ namespace isc { namespace dhcp {
     // Type destructor.
     // Type destructor.
     switch (yytype)
     switch (yytype)
     {
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.template destroy< ElementPtr > ();
         value.template destroy< ElementPtr > ();
         break;
         break;
 
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.template destroy< bool > ();
         value.template destroy< bool > ();
         break;
         break;
 
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.template destroy< double > ();
         value.template destroy< double > ();
         break;
         break;
 
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.template destroy< int64_t > ();
         value.template destroy< int64_t > ();
         break;
         break;
 
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.template destroy< std::string > ();
         value.template destroy< std::string > ();
         break;
         break;
 
 
@@ -1441,24 +1432,23 @@ namespace isc { namespace dhcp {
     super_type::move(s);
     super_type::move(s);
       switch (this->type_get ())
       switch (this->type_get ())
     {
     {
-      case 117: // value
-      case 341: // version_value
+      case 116: // value
         value.move< ElementPtr > (s.value);
         value.move< ElementPtr > (s.value);
         break;
         break;
 
 
-      case 103: // "boolean"
+      case 102: // "boolean"
         value.move< bool > (s.value);
         value.move< bool > (s.value);
         break;
         break;
 
 
-      case 102: // "floating point"
+      case 101: // "floating point"
         value.move< double > (s.value);
         value.move< double > (s.value);
         break;
         break;
 
 
-      case 101: // "integer"
+      case 100: // "integer"
         value.move< int64_t > (s.value);
         value.move< int64_t > (s.value);
         break;
         break;
 
 
-      case 100: // "constant string"
+      case 99: // "constant string"
         value.move< std::string > (s.value);
         value.move< std::string > (s.value);
         break;
         break;
 
 
@@ -1527,7 +1517,7 @@ namespace isc { namespace dhcp {
      325,   326,   327,   328,   329,   330,   331,   332,   333,   334,
      325,   326,   327,   328,   329,   330,   331,   332,   333,   334,
      335,   336,   337,   338,   339,   340,   341,   342,   343,   344,
      335,   336,   337,   338,   339,   340,   341,   342,   343,   344,
      345,   346,   347,   348,   349,   350,   351,   352,   353,   354,
      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]);
     return static_cast<token_type> (yytoken_number_[type]);
   }
   }
@@ -1977,12 +1967,6 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp6Parser::symbol_type
   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)
   Dhcp6Parser::make_CONTROL_SOCKET (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_CONTROL_SOCKET, 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
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
 } } // 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
    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
    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"
   ENTERPRISE_ID "enterprise-id"
 
 
   DHCP4O6_PORT "dhcp4o6-port"
   DHCP4O6_PORT "dhcp4o6-port"
-  VERSION "version"
 
 
   CONTROL_SOCKET "control-socket"
   CONTROL_SOCKET "control-socket"
   SOCKET_TYPE "socket-type"
   SOCKET_TYPE "socket-type"
@@ -186,7 +185,6 @@ using namespace std;
 %token <bool> BOOLEAN "boolean"
 %token <bool> BOOLEAN "boolean"
 
 
 %type <ElementPtr> value
 %type <ElementPtr> value
-%type <ElementPtr> version_value
 
 
 %printer { yyoutput << $$; } <*>;
 %printer { yyoutput << $$; } <*>;
 
 
@@ -374,7 +372,6 @@ global_param: preferred_lifetime
             | expired_leases_processing
             | expired_leases_processing
             | server_id
             | server_id
             | dhcp4o6_port
             | dhcp4o6_port
-            | version
             | control_socket
             | control_socket
             | dhcp_ddns
             | dhcp_ddns
             | unknown_map_entry
             | unknown_map_entry
@@ -1370,20 +1367,6 @@ dhcp4o6_port: DHCP4O6_PORT COLON INTEGER {
     ctx.stack_.back()->set("dhcp4o6-port", time);
     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: 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);
         parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET6);
     } else if (config_id.compare("option-def") == 0) {
     } else if (config_id.compare("option-def") == 0) {
         parser  = new OptionDefListParser(config_id, globalContext());
         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) {
     } else if (config_id.compare("lease-database") == 0) {
         parser = new DbAccessParser(config_id, DbAccessParser::LEASE_DB);
         parser = new DbAccessParser(config_id, DbAccessParser::LEASE_DB);
     } else if (config_id.compare("hosts-database") == 0) {
     } 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Locations for Bison parsers in C++
 // Locations for Bison parsers in C++

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

@@ -7,7 +7,7 @@
 #ifndef PARSER6_CONTEXT_DECL_H
 #ifndef PARSER6_CONTEXT_DECL_H
 #define 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 isc {
 namespace dhcp {
 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Positions for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Stack handling for Bison parsers in C++
 // 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
     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
 /// The goal of this test is to verify that the code accepts only
 /// valid commands and malformed or unsupported parameters are rejected.
 /// valid commands and malformed or unsupported parameters are rejected.
 TEST_F(Dhcp6ParserTest, bogusCommand) {
 TEST_F(Dhcp6ParserTest, bogusCommand) {
@@ -1579,8 +1565,8 @@ TEST_F(Dhcp6ParserTest, pdPoolPrefixExclude) {
         "] }";
         "] }";
 
 
     // Convert the JSON string into Elements.
     // 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.
     // Verify that DHCP6 configuration processing succeeds.
     // Returned value must be non-empty ConstElementPtr to config result.
     // 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(reference_json = Element::fromJSON(txt, true));
     ASSERT_NO_THROW({
     ASSERT_NO_THROW({
             try {
             try {
-        Parser6Context ctx;
-        test_json = ctx.parseString(txt, parser_type);
+                Parser6Context ctx;
+                test_json = ctx.parseString(txt, parser_type);
             } catch (const std::exception &e) {
             } catch (const std::exception &e) {
                 cout << "EXCEPTION: " << e.what() << endl;
                 cout << "EXCEPTION: " << e.what() << endl;
                 throw;
                 throw;
@@ -49,8 +49,8 @@ void testParser2(const std::string& txt, Parser6Context::ParserType parser_type)
 
 
     ASSERT_NO_THROW({
     ASSERT_NO_THROW({
             try {
             try {
-        Parser6Context ctx;
-        test_json = ctx.parseString(txt, parser_type);
+                Parser6Context ctx;
+                test_json = ctx.parseString(txt, parser_type);
             } catch (const std::exception &e) {
             } catch (const std::exception &e) {
                 cout << "EXCEPTION: " << e.what() << endl;
                 cout << "EXCEPTION: " << e.what() << endl;
                 throw;
                 throw;
@@ -330,13 +330,22 @@ TEST(ParserTest, errors) {
               Parser6Context::PARSER_JSON,
               Parser6Context::PARSER_JSON,
               "Can't open include file /foo/bar");
               "Can't open include file /foo/bar");
 
 
-    // case sensitivity
+    // JSON keywords
     testError("{ \"foo\": True }",
     testError("{ \"foo\": True }",
               Parser6Context::PARSER_JSON,
               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");
               "<string>:1.10: Invalid character: T");
-    testError("{ \"foo\": NULL  }",
+    testError("{ \"foo\": nul }",
               Parser6Context::PARSER_JSON,
               Parser6Context::PARSER_JSON,
-              "<string>:1.10: Invalid character: N");
+              "<string>:1.10: Invalid character: n");
 
 
     // numbers
     // numbers
     testError("123",
     testError("123",
@@ -386,9 +395,9 @@ TEST(ParserTest, errors) {
     testError("\"a\\x01b\"",
     testError("\"a\\x01b\"",
               Parser6Context::PARSER_JSON,
               Parser6Context::PARSER_JSON,
               "<string>:1.1-8: Bad escape in \"a\\x01b\"");
               "<string>:1.1-8: Bad escape in \"a\\x01b\"");
-    testError("\"a\\u0062\"",
+    testError("\"a\\u0162\"",
               Parser6Context::PARSER_JSON,
               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\"",
     testError("\"a\\u062z\"",
               Parser6Context::PARSER_JSON,
               Parser6Context::PARSER_JSON,
               "<string>:1.1-9: Bad escape in \"a\\u062z\"");
               "<string>:1.1-9: Bad escape in \"a\\u062z\"");
@@ -476,4 +485,41 @@ TEST(ParserTest, errors) {
               "\"preferred_lifetime\" in Dhcp6 map.");
               "\"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 <map>
 #include <cstdio>
 #include <cstdio>
 #include <iostream>
 #include <iostream>
+#include <iomanip>
 #include <string>
 #include <string>
 #include <sstream>
 #include <sstream>
 #include <fstream>
 #include <fstream>
 #include <cerrno>
 #include <cerrno>
 
 
-#include <boost/algorithm/string.hpp> // for iequals
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 
 
 #include <cmath>
 #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.
     // This will move the pos to the end of the value.
     const std::string word = wordFromStringstream(in, pos);
     const std::string word = wordFromStringstream(in, pos);
 
 
-    if (boost::iequals(word, "True")) {
+    if (word == "true") {
         return (Element::create(true, Element::Position(file, line,
         return (Element::create(true, Element::Position(file, line,
                                                         start_pos)));
                                                         start_pos)));
-    } else if (boost::iequals(word, "False")) {
+    } else if (word == "false") {
         return (Element::create(false, Element::Position(file, line,
         return (Element::create(false, Element::Position(file, line,
                                                          start_pos)));
                                                          start_pos)));
     } else {
     } else {
@@ -479,7 +479,7 @@ fromStringstreamNull(std::istream& in, const std::string& file,
     const uint32_t start_pos = pos;
     const uint32_t start_pos = pos;
     // This will move the pos to the end of the value.
     // This will move the pos to the end of the value.
     const std::string word = wordFromStringstream(in, pos);
     const std::string word = wordFromStringstream(in, pos);
-    if (boost::iequals(word, "null")) {
+    if (word == "null") {
         return (Element::create(Element::Position(file, line, start_pos)));
         return (Element::create(Element::Position(file, line, start_pos)));
     } else {
     } else {
         throwJSONError(std::string("Bad null value: ") + word, file,
         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;
                 el_read = true;
                 break;
                 break;
             case 't':
             case 't':
-            case 'T':
             case 'f':
             case 'f':
-            case 'F':
                 in.putback(c);
                 in.putback(c);
                 --pos;
                 --pos;
                 element = fromStringstreamBool(in, file, line, pos);
                 element = fromStringstreamBool(in, file, line, pos);
                 el_read = true;
                 el_read = true;
                 break;
                 break;
             case 'n':
             case 'n':
-            case 'N':
                 in.putback(c);
                 in.putback(c);
                 --pos;
                 --pos;
                 element = fromStringstreamNull(in, file, line, pos);
                 element = fromStringstreamNull(in, file, line, pos);
@@ -795,7 +792,15 @@ StringElement::toJSON(std::ostream& ss) const {
             ss << '\\' << 't';
             ss << '\\' << 't';
             break;
             break;
         default:
         default:
-            ss << c;
+            if ((c >= 0) && (c < 0x20)) {
+                ss << "\\u"
+                   << hex
+                   << setw(4)
+                   << setfill('0')
+                   << (static_cast<unsigned>(c) & 0xff);
+            } else {
+                ss << c;
+            }
         }
         }
     }
     }
     ss << "\"";
     ss << "\"";

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

@@ -131,11 +131,13 @@ TEST(Element, from_and_to_json) {
     sv.push_back("{1}");
     sv.push_back("{1}");
     //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
     //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
     //std::cout << ep << std::endl;
     //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("{ \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("{ \"a\": None}");
     sv.push_back("");
     sv.push_back("");
+    sv.push_back("NULL");
     sv.push_back("nul");
     sv.push_back("nul");
     sv.push_back("hello\"foobar\"");
     sv.push_back("hello\"foobar\"");
     sv.push_back("\"foobar\"hello");
     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.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("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 \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 \t  \b \f }")->str());
     EXPECT_EQ("[  ]", Element::fromJSON("[  \n  \r \f \t  \b  ]")->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("2"));
     EXPECT_NE(*efs("1"), *efs("\"1\""));
     EXPECT_NE(*efs("1"), *efs("\"1\""));
     EXPECT_NE(*efs("1"), *efs("[]"));
     EXPECT_NE(*efs("1"), *efs("[]"));
-    EXPECT_NE(*efs("1"), *efs("True"));
+    EXPECT_NE(*efs("1"), *efs("true"));
     EXPECT_NE(*efs("1"), *efs("{}"));
     EXPECT_NE(*efs("1"), *efs("{}"));
 
 
     EXPECT_EQ(*efs("1.1"), *efs("1.1"));
     EXPECT_EQ(*efs("1.1"), *efs("1.1"));
     EXPECT_NE(*efs("1.0"), *efs("1"));
     EXPECT_NE(*efs("1.0"), *efs("1"));
     EXPECT_NE(*efs("1.1"), *efs("\"1\""));
     EXPECT_NE(*efs("1.1"), *efs("\"1\""));
     EXPECT_NE(*efs("1.1"), *efs("[]"));
     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_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_EQ(*efs("\"foo\""), *efs("\"foo\""));
     EXPECT_NE(*efs("\"foo\""), *efs("\"bar\""));
     EXPECT_NE(*efs("\"foo\""), *efs("\"bar\""));
     EXPECT_NE(*efs("\"foo\""), *efs("1"));
     EXPECT_NE(*efs("\"foo\""), *efs("1"));
     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_NE(*efs("\"foo\""), *efs("{}"));
     EXPECT_NE(*efs("\"foo\""), *efs("{}"));
 
 
     EXPECT_EQ(*efs("[]"), *efs("[]"));
     EXPECT_EQ(*efs("[]"), *efs("[]"));
     EXPECT_EQ(*efs("[ 1, 2, 3 ]"), *efs("[ 1, 2, 3 ]"));
     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"));
     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
 "config_data" contains a list of elements of the form
 { "item_name": "name"
 { "item_name": "name"
   "item_type": "integer|real|boolean|string|list|map"
   "item_type": "integer|real|boolean|string|list|map"
-  "item_optional": True|False
+  "item_optional": true|false
   "item_default": <depends on type>
   "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().
     // Invoke common code for all specializations of build().
     buildCommon(value);
     buildCommon(value);
     // The Config Manager checks if user specified a
     // 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
     // We should have a boolean Element, use value directly
     try {
     try {
         value_ = value->boolValue();
         value_ = value->boolValue();
@@ -1475,11 +1475,11 @@ D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
         mode_str  = string_values_->getOptionalParam("replace-client-name",
         mode_str  = string_values_->getOptionalParam("replace-client-name",
                                                      D2ClientConfig::
                                                      D2ClientConfig::
                                                      DFT_REPLACE_CLIENT_NAME_MODE);
                                                      DFT_REPLACE_CLIENT_NAME_MODE);
-        if (boost::iequals(mode_str, "FALSE")) {
+        if (boost::iequals(mode_str, "false")) {
             // @todo add a debug log
             // @todo add a debug log
             replace_client_name_mode = D2ClientConfig::RCM_NEVER;
             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
             // @todo add a debug log
             replace_client_name_mode = D2ClientConfig::RCM_WHEN_PRESENT;
             replace_client_name_mode = D2ClientConfig::RCM_WHEN_PRESENT;
         } else {
         } else {

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

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