Browse Source

[master] Merge branch 'trac5036' (Dhcp6 bison parser)

Tomek Mrugalski 8 years ago
parent
commit
4c5902da95
36 changed files with 14998 additions and 297 deletions
  1. 1 0
      .gitignore
  2. 14 0
      doc/examples/kea6/advanced.json
  3. 29 30
      doc/examples/kea6/classify.json
  4. 4 4
      doc/examples/kea6/dhcpv4-over-dhcpv6.json
  5. 5 1
      doc/examples/kea6/hooks.json
  6. 2 2
      doc/examples/kea6/multiple-options.json
  7. 2 1
      doc/examples/kea6/mysql-reservations.json
  8. 2 1
      doc/examples/kea6/reservations.json
  9. 50 15
      doc/guide/config.xml
  10. 1 1
      doc/guide/dhcp4-srv.xml
  11. 9 4
      doc/guide/dhcp6-srv.xml
  12. 1 0
      src/bin/dhcp6/.gitignore
  13. 31 0
      src/bin/dhcp6/Makefile.am
  14. 297 0
      src/bin/dhcp6/dhcp6.dox
  15. 4004 0
      src/bin/dhcp6/dhcp6_lexer.cc
  16. 1169 0
      src/bin/dhcp6/dhcp6_lexer.ll
  17. 3568 0
      src/bin/dhcp6/dhcp6_parser.cc
  18. 2155 0
      src/bin/dhcp6/dhcp6_parser.h
  19. 1567 0
      src/bin/dhcp6/dhcp6_parser.yy
  20. 3 1
      src/bin/dhcp6/kea_controller.cc
  21. 193 0
      src/bin/dhcp6/location.hh
  22. 172 0
      src/bin/dhcp6/parser_context.cc
  23. 330 0
      src/bin/dhcp6/parser_context.h
  24. 20 0
      src/bin/dhcp6/parser_context_decl.h
  25. 181 0
      src/bin/dhcp6/position.hh
  26. 158 0
      src/bin/dhcp6/stack.hh
  27. 2 0
      src/bin/dhcp6/tests/Makefile.am
  28. 275 220
      src/bin/dhcp6/tests/config_parser_unittest.cc
  29. 4 2
      src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
  30. 6 4
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  31. 2 1
      src/bin/dhcp6/tests/dhcp6_test_utils.cc
  32. 57 0
      src/bin/dhcp6/tests/dhcp6_test_utils.h
  33. 4 2
      src/bin/dhcp6/tests/hooks_unittest.cc
  34. 198 5
      src/bin/dhcp6/tests/kea_controller_unittest.cc
  35. 479 0
      src/bin/dhcp6/tests/parser_unittest.cc
  36. 3 3
      src/bin/dhcp6/tests/sarr_unittest.cc

+ 1 - 0
.gitignore

@@ -35,6 +35,7 @@ config.h.in~
 /py-compile
 /py-compile
 /stamp-h1
 /stamp-h1
 /test-driver
 /test-driver
+/ylwrap
 
 
 /all.info
 /all.info
 /coverage-cpp-html
 /coverage-cpp-html

+ 14 - 0
doc/examples/kea6/advanced.json

@@ -66,6 +66,20 @@
   "subnet6": [
   "subnet6": [
     {
     {
       "pools": [ { "pool": "2001:db8:1::/80" } ],
       "pools": [ { "pool": "2001:db8:1::/80" } ],
+
+# This defines PD (prefix delegation) pools. In this case
+# we have only one pool. That consists of /64 prefixes
+# being delegated out of large /48 pool. Each delegated
+# prefix will contain an excluded-prefix option.
+      "pd-pools": [
+      {
+          "prefix": "2001:db8:abcd::",
+          "prefix-len": 48,
+          "delegated-len": 64,
+          "excluded-prefix": "2001:db8:abcd:1234::",
+          "excluded-prefix-len": 62
+      }
+      ],
       "subnet": "2001:db8:1::/64",
       "subnet": "2001:db8:1::/64",
       "interface": "ethX"
       "interface": "ethX"
     }
     }

+ 29 - 30
doc/examples/kea6/classify.json

@@ -3,7 +3,7 @@
 
 
 { "Dhcp6":
 { "Dhcp6":
 
 
-{ 
+{
 # Kea is told to listen on ethX interface only.
 # Kea is told to listen on ethX interface only.
   "interfaces-config": {
   "interfaces-config": {
     "interfaces": [ "ethX" ]
     "interfaces": [ "ethX" ]
@@ -26,8 +26,8 @@
       "name": "lab",
       "name": "lab",
       "test": "pkt.iface == 'ethX'",
       "test": "pkt.iface == 'ethX'",
       "option-data": [{
       "option-data": [{
-          "name": "dns-servers",
+	  "name": "dns-servers",
-          "data": "2001:db8::1"
+	  "data": "2001:db8::1"
       }]
       }]
   },
   },
 
 
@@ -40,36 +40,36 @@
 
 
 # Let's pick cable modems. In this simple example we'll assume the device
 # Let's pick cable modems. In this simple example we'll assume the device
 # is a cable modem if it sends a vendor option with enterprise-id equal
 # is a cable modem if it sends a vendor option with enterprise-id equal
-# to 4491.    
+# to 4491.
   {
   {
       "name": "cable-modems",
       "name": "cable-modems",
       "test": "vendor.enterprise == 4491"
       "test": "vendor.enterprise == 4491"
-  },
+  }
 
 
   ],
   ],
-    
+
 
 
 # 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.
-  "subnet6": [ 
+  "subnet6": [
     {
     {
-        "pools": [ { "pool": "2001:db8:1::/80" } ],
+	"pools": [ { "pool": "2001:db8:1::/80" } ],
-        "subnet": "2001:db8:1::/64",
+	"subnet": "2001:db8:1::/64",
-        "client-class": "cable-modems",
+	"client-class": "cable-modems",
-        "interface": "ethX"
+	"interface": "ethX"
     },
     },
 # The following subnet contains a class reservation for a client using
 # The following subnet contains a class reservation for a client using
 # DUID 01:02:03:04:05:0A:0B:0C:0D:0E. This client will always be assigned
 # DUID 01:02:03:04:05:0A:0B:0C:0D:0E. This client will always be assigned
 # to this class.
 # to this class.
     {
     {
-        "pools": [ { "pool": "2001:db8:2::/80" } ],
+	"pools": [ { "pool": "2001:db8:2::/80" } ],
-        "subnet": "2001:db8:2::/64",
+	"subnet": "2001:db8:2::/64",
-        "reservations": [
+	"reservations": [
-        {
+	{
-            "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+	    "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
-            "client-classes": [ "cable-modems" ]
+	    "client-classes": [ "cable-modems" ]
-        } ],
+	} ],
-        "interface": "ethX"
+	"interface": "ethX"
     }
     }
   ]
   ]
 },
 },
@@ -78,18 +78,17 @@
 # informational level (info, warn, error and fatal) should be logged to stdout.
 # informational level (info, warn, error and fatal) should be logged to stdout.
 "Logging": {
 "Logging": {
     "loggers": [
     "loggers": [
-        {
+	{
-            "name": "kea-dhcp6",
+	    "name": "kea-dhcp6",
-            "output_options": [
+	    "output_options": [
-                {
+		{
-                    "output": "stdout"
+		    "output": "stdout"
-                }
+		}
-            ],
+	    ],
-            "debuglevel": 0,
+	    "debuglevel": 0,
-            "severity": "INFO"
+	    "severity": "INFO"
-        }
+	}
     ]
     ]
 }
 }
 
 
 }
 }
-

+ 4 - 4
doc/examples/kea6/dhcpv4-over-dhcpv6.json

@@ -7,7 +7,7 @@
 "Dhcp6":
 "Dhcp6":
 {
 {
   "interfaces-config": {
   "interfaces-config": {
-    # Enable unicast
+# Enable unicast
     "interfaces": [ "eno33554984/2001:db8:1:1::1" ]
     "interfaces": [ "eno33554984/2001:db8:1:1::1" ]
   },
   },
 
 
@@ -27,16 +27,16 @@
        "pools": [ { "pool": "2001:db8:1:1::1:0/112" } ] }
        "pools": [ { "pool": "2001:db8:1:1::1:0/112" } ] }
    ],
    ],
 
 
-  # This enables DHCPv4-over-DHCPv6 support
+# This enables DHCPv4-over-DHCPv6 support
   "dhcp4o6-port": 6767,
   "dhcp4o6-port": 6767,
 
 
-  # Required by DHCPv4-over-DHCPv6 clients
+# Required by DHCPv4-over-DHCPv6 clients
   "option-data": [
   "option-data": [
    {  "name": "dhcp4o6-server-addr",
    {  "name": "dhcp4o6-server-addr",
       "code": 88,
       "code": 88,
       "space": "dhcp6",
       "space": "dhcp6",
       "csv-format": true,
       "csv-format": true,
-      # Put the server address here
+# Put the server address here
       "data": "2001:db8:1:1::1" }
       "data": "2001:db8:1:1::1" }
    ]
    ]
 },
 },

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

@@ -41,7 +41,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"
+        }
      }
      }
   ]
   ]
 }
 }

+ 2 - 2
doc/examples/kea6/multiple-options.json

@@ -49,7 +49,7 @@
         {
         {
             "code": 12,
             "code": 12,
             "data": "2001:db8:1:0:ff00::1"
             "data": "2001:db8:1:0:ff00::1"
-        },
+        }
       ],
       ],
       "pools": [
       "pools": [
         {
         {
@@ -66,7 +66,7 @@
         }
         }
       ],
       ],
       "subnet": "2001:db8:1::/64",
       "subnet": "2001:db8:1::/64",
-      "interface": "ethX",
+      "interface": "ethX"
     }
     }
   ]
   ]
 },
 },

+ 2 - 1
doc/examples/kea6/mysql-reservations.json

@@ -45,7 +45,8 @@
     "name": "kea",
     "name": "kea",
     "user": "kea",
     "user": "kea",
     "password": "kea",
     "password": "kea",
-    "host": "localhost"
+    "host": "localhost",
+    "readonly": true
   },
   },
 
 
 # Define a subnet with a pool of dynamic addresses and a pool of dynamic
 # Define a subnet with a pool of dynamic addresses and a pool of dynamic

+ 2 - 1
doc/examples/kea6/reservations.json

@@ -81,7 +81,8 @@
               {
               {
                   "name": "nis-servers",
                   "name": "nis-servers",
                   "data": "3000:1::234"
                   "data": "3000:1::234"
-              }]
+              }],
+              "client-classes": [ "special_snowflake", "office" ]
           },
           },
 # This is a bit more advanced reservation. The client with the specified
 # This is a bit more advanced reservation. The client with the specified
 # DUID will get a reserved address, a reserved prefix and a hostname.
 # DUID will get a reserved address, a reserved prefix and a hostname.

+ 50 - 15
doc/guide/config.xml

@@ -6,15 +6,18 @@
 <chapter id="kea-config">
 <chapter id="kea-config">
   <title>Kea Configuration</title>
   <title>Kea Configuration</title>
 
 
-  <para>Kea is designed to allow different methods by which it can be
+  <para>Kea is using JSON structures to handle configuration. Previously
-    configured, each method being implemented by a component known as a
+  we there was a concept of other configuration backends, but that never was
-    configuration backend.  At present, only one such backend is
+  implemented and the idea was abandoned.</para>
-    available, that allowing configuration by means of a JSON file.</para>
+
-
+  <section id="json">
-  <section id="json-backend">
+    <title>JSON Configuration</title>
-    <title>JSON Configuration Backend</title>
+    <para>JSON is notation used throughout the Kea project. The most obvious
-    <para>JSON is the default configuration backend.
+    usage is for configuration file, but it is also used for sending commands
-    It assumes that the servers are started from the command line
+    over Management API (see <xref linkend="ctrl-channel"/>) and for
+    communicating between DHCP servers and DDNS update daemon.</para>
+
+    <para>Typical usage assumes that the servers are started from the command line
     (either directly or using a script, e.g. <filename>keactrl</filename>).
     (either directly or using a script, e.g. <filename>keactrl</filename>).
     The JSON backend uses certain signals to influence Kea. The
     The JSON backend uses certain signals to influence Kea. The
     configuration file is specified upon startup using the -c parameter.</para>
     configuration file is specified upon startup using the -c parameter.</para>
@@ -23,10 +26,42 @@
       <title>JSON Syntax</title>
       <title>JSON Syntax</title>
       <para>Configuration files for DHCPv4, DHCPv6 and DDNS modules are defined
       <para>Configuration files for DHCPv4, DHCPv6 and DDNS modules are defined
       in an extended JSON format. Basic JSON is defined in <ulink
       in an extended JSON format. Basic JSON is defined in <ulink
-      url="http://tools.ietf.org/html/rfc4627">RFC 4627</ulink>.  Kea components
+      url="http://tools.ietf.org/html/rfc7159">RFC 7159</ulink>. Note that Kea
-      use a slightly modified form of JSON in that they allow shell-style
+      1.2 introduces a new parser that is better at following the JSON spec.  In
-      comments in the file: lines with the hash (#) character in the first column
+      particular, the only values allowed for boolean are true or false (all
-      are comment lines and are ignored.</para>
+      lowercase). The capitalized versions (True or False) are not accepted.
+      </para>
+
+      <para>Kea components use an extended JSON with additional features
+      allowed:
+      <itemizedlist>
+        <listitem>
+          <simpara>shell comments: any text after the hash (#)
+          character is ignored. Dhcp6 allows # in any column, while
+          Dhcp4 and Ddns require hash to be in the first
+          column.</simpara>
+        </listitem>
+        <listitem>
+          <simpara>C comments: any text after the double slashes (//)
+          character is ignored. We're in a process of
+          migrating the configuation parsers and currently only Dhcp6
+          supports this feature.</simpara>
+        </listitem>
+        <listitem>
+          <simpara>Multiline comments: any text between /* and */ is
+          ignored. This commenting can span multiple lines. We're in a
+          process of migrating the configuation parsers and currently
+          only Dhcp6 supports this feature.</simpara>
+        </listitem>
+        <listitem>
+          <simpara>File inclusion: JSON files can include other JSON
+          files. This can be done by using &lt;?include
+          "file.json"?&gt;. We're in a process of migrating the
+          configuation parsers and currently only Dhcp6 supports this
+          feature.</simpara>
+        </listitem>
+      </itemizedlist>
+      </para>
 
 
       <para>The configuration file consists of a single object (often colloquially
       <para>The configuration file consists of a single object (often colloquially
       called a map) started with a curly bracket. It comprises the "Dhcp4", "Dhcp6",
       called a map) started with a curly bracket. It comprises the "Dhcp4", "Dhcp6",
@@ -89,7 +124,7 @@
 # The whole configuration structure ends here.
 # The whole configuration structure ends here.
 }
 }
 </screen>
 </screen>
-	</para>
+        </para>
 
 
         <para>More examples are available in the installed
         <para>More examples are available in the installed
         <filename>share/doc/kea/examples</filename> directory.</para>
         <filename>share/doc/kea/examples</filename> directory.</para>
@@ -113,7 +148,7 @@
         valid-lifetime in the Dhcp6 component can be referred to as
         valid-lifetime in the Dhcp6 component can be referred to as
         Dhcp6/valid-lifetime and the pool in the first subnet defined in the DHCPv6
         Dhcp6/valid-lifetime and the pool in the first subnet defined in the DHCPv6
         configuration as Dhcp6/subnet6[0]/pool.</para>
         configuration as Dhcp6/subnet6[0]/pool.</para>
-      
+
       <!-- @todo Add a reference here after #3422 is done -->
       <!-- @todo Add a reference here after #3422 is done -->
     </section>
     </section>
 
 

+ 1 - 1
doc/guide/dhcp4-srv.xml

@@ -2978,7 +2978,7 @@ It is merely echoed by the server
 
 
       <para>
       <para>
         It is possible to store host reservations in MySQL or PostgreSQL database. See
         It is possible to store host reservations in MySQL or PostgreSQL database. See
-        <xref linkend="hosts-storage4"/> for information on how to configure Kea to use
+        <xref linkend="hosts4-storage"/> for information on how to configure Kea to use
         reservations stored in MySQL or PostgreSQL. Kea does not provide any dedicated
         reservations stored in MySQL or PostgreSQL. Kea does not provide any dedicated
         tools for managing reservations in a database. The Kea wiki <ulink
         tools for managing reservations in a database. The Kea wiki <ulink
         url="http://kea.isc.org/wiki/HostReservationsHowTo" /> provides detailed
         url="http://kea.isc.org/wiki/HostReservationsHowTo" /> provides detailed

+ 9 - 4
doc/guide/dhcp6-srv.xml

@@ -4,7 +4,7 @@
 <!ENTITY mdash  "&#x2014;" >
 <!ENTITY mdash  "&#x2014;" >
 ]>
 ]>
 
 
-  <chapter id="dhcp6">
+ <chapter id="dhcp6">
     <title>The DHCPv6 Server</title>
     <title>The DHCPv6 Server</title>
 
 
     <section id="dhcp6-start-stop">
     <section id="dhcp6-start-stop">
@@ -3496,7 +3496,8 @@ If not specified, the default value is:
       provided by the clients.
       provided by the clients.
       </para>
       </para>
       <para>
       <para>
-      The configuration is controlled by the <command>mac-sources</command>parameter as follows:
+      The configuration is controlled by the <command>mac-sources</command>
+      parameter as follows:
         <screen>
         <screen>
 "Dhcp6": {
 "Dhcp6": {
     <userinput>"mac-sources": [ "method1", "method2", "method3", ... ]</userinput>,
     <userinput>"mac-sources": [ "method1", "method2", "method3", ... ]</userinput>,
@@ -3543,7 +3544,7 @@ If not specified, the default value is:
         that those addresses are based on EUI-64, which contains MAC address. This
         that those addresses are based on EUI-64, which contains MAC address. This
         method is not completely reliable, as clients may use other link-local address
         method is not completely reliable, as clients may use other link-local address
         types.  In particular, privacy extensions, defined in
         types.  In particular, privacy extensions, defined in
-        <ulink utl="http://tools.ietf.org/html/rfc4941">RFC 4941</ulink>, do not use
+        <ulink url="http://tools.ietf.org/html/rfc4941">RFC 4941</ulink>, do not use
         MAC addresses.  Also note that successful extraction requires that the
         MAC addresses.  Also note that successful extraction requires that the
         address's u-bit must be set to 1 and its g-bit set to 0, indicating that it
         address's u-bit must be set to 1 and its g-bit set to 0, indicating that it
         is an interface identifier as per
         is an interface identifier as per
@@ -3600,7 +3601,11 @@ If not specified, the default value is:
         </simpara>
         </simpara>
       </listitem>
       </listitem>
     </itemizedlist>
     </itemizedlist>
-    </para>
+  </para>
+
+  <para>Empty mac-sources is not allowed. If you do not want to specify it,
+  either simply omit mac-sources definition or specify it with the "any" value
+  which is the default.</para>
   </section>
   </section>
 
 
     <section id="dhcp6-decline">
     <section id="dhcp6-decline">

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

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

+ 31 - 0
src/bin/dhcp6/Makefile.am

@@ -64,6 +64,10 @@ libdhcp6_la_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
 libdhcp6_la_SOURCES += json_config_parser.cc json_config_parser.h
 libdhcp6_la_SOURCES += json_config_parser.cc json_config_parser.h
 libdhcp6_la_SOURCES += dhcp6to4_ipc.cc dhcp6to4_ipc.h
 libdhcp6_la_SOURCES += dhcp6to4_ipc.cc dhcp6to4_ipc.h
 
 
+libdhcp6_la_SOURCES += dhcp6_lexer.ll location.hh position.hh stack.hh
+libdhcp6_la_SOURCES += dhcp6_parser.cc dhcp6_parser.h
+libdhcp6_la_SOURCES += parser_context.cc parser_context.h
+
 libdhcp6_la_SOURCES += kea_controller.cc
 libdhcp6_la_SOURCES += kea_controller.cc
 
 
 nodist_libdhcp6_la_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 nodist_libdhcp6_la_SOURCES = dhcp6_messages.h dhcp6_messages.cc
@@ -105,3 +109,30 @@ endif
 
 
 kea_dhcp6dir = $(pkgdatadir)
 kea_dhcp6dir = $(pkgdatadir)
 kea_dhcp6_DATA = dhcp6.spec
 kea_dhcp6_DATA = dhcp6.spec
+
+if GENERATE_PARSER
+
+parser: dhcp6_lexer.cc location.hh position.hh stack.hh dhcp6_parser.cc dhcp6_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 dhcp6_parser.cc dhcp6_parser.h: dhcp6_parser.yy
+	$(YACC) --defines=dhcp6_parser.h --report=all --report-file=dhcp6_parser.report -o dhcp6_parser.cc dhcp6_parser.yy
+
+dhcp6_lexer.cc: dhcp6_lexer.ll
+	$(LEX) --prefix parser6_ -o dhcp6_lexer.cc dhcp6_lexer.ll
+
+else
+
+parser location.hh position.hh stack.hh dhcp6_parser.cc dhcp6_parser.h dhcp6_lexer.cc:
+	@echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif

+ 297 - 0
src/bin/dhcp6/dhcp6.dox

@@ -24,6 +24,18 @@ component implementation.
 
 
 @section dhcpv6ConfigParser Configuration Parsers in DHCPv6
 @section dhcpv6ConfigParser Configuration Parsers in DHCPv6
 
 
+Three minutes overview. 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:
+ Parser6Context parser;
+ json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6);
+@endcode
+
 The common configuration parsers for the DHCP servers are located in the
 The common configuration parsers for the DHCP servers are located in the
 src/lib/dhcpsrv/parsers/ directory. Parsers specific to the DHCPv6 component
 src/lib/dhcpsrv/parsers/ directory. Parsers specific to the DHCPv6 component
 are located in the src/bin/dhcp6/json_config_parser.cc. These parsers derive
 are located in the src/bin/dhcp6/json_config_parser.cc. These parsers derive
@@ -41,6 +53,291 @@ all configuration parsers. All DHCPv6 parsers deriving from this class
 directly have their entire implementation in the
 directly have their entire implementation in the
 src/bin/dhcp6/json_config_parser.cc.
 src/bin/dhcp6/json_config_parser.cc.
 
 
+@section dhcpv6ConfigParserBison Configuration Parser for DHCPv6 (bison)
+
+During 1.2 milestone it has been decided to significantly refactor the
+parsers as their old implementation became unsustainable. For the brief overview
+of the problems, see ticket 5014 (http://kea.isc.org/ticket/5014). In
+general, the following issues of the existing code were noted:
+
+-# parsers are overwhelmingly complex. Even though each parser is relatively
+   simple class, the complexity comes from too large number of interacting parsers.
+-# the code is disorganized, i.e. spread out between multiple directories
+   (src/bin/dhcp6 and src/lib/dhcpsrv/parsers).
+-# The split into build/commit never worked well. In particular, it is not
+   trivial to revert configuration change. This split originates from BIND10
+   days and was inherited from DNS auth that did receive only changes in
+   the configuration, rather than the full configuration. As a result,
+   the split was abused and many of the parsers have commit() being a
+   no-op operation.
+-# There is no way to generate a list of all directives. We do have .spec files,
+   but they're not actually used by the code. The code has the directives
+   spread out in multiple places in multiple files in multiple directories.
+   Answering a simple question ("can I do X in the scope Y?") requires
+   a short session of reverse engineering. What's worse, we have the .spec
+   files that are kinda kept up to date. This is actually more damaging that
+   way, because there's no strict correlation between the code and .spec file.
+   So there may be parameters that are supported, but are not in .spec files.
+   The opposite is also true - .spec files can be buggy and have different
+   parameters. This is particularly true for default values.
+-# It's exceedingly complex to add comments that don't start at the first
+   column or span several lines. Both Tomek and Marcin tried to implement
+   it, but failed miserably. The same is true for including files (have
+   include statement in the config that includes other files)
+-# The current parsers don't handle the default values, i.e. if there's no
+   directive, the parser is not created at all. We have kludgy workarounds
+   for that, but the code for it is in different place than the parser,
+   which leads to the logic being spread out in different places.
+-# syntax checking is poor. The existing JSON parser allowed things like
+   empty option-data entries:
+   @code
+      "option-data": [ {} ]
+   @endcode
+   having trailing commas:
+   @code
+      "option-data": [
+        {
+            "code": 12,
+            "data": "2001:db8:1:0:ff00::1"
+        },
+      ]
+   @endcode
+   or having incorrect types, e.g. specifying timer values as strings.
+
+To solve those issues a two phase approach was proposed:
+
+PHASE 1: replace isc::data::fromJSON with bison-based parser. This will allow
+   to have a single file that defines the actual syntax, much better syntax
+   checking, and provide more flexibility, like various comment types and
+   file inclusions. As a result, the parser still returns JSON structures that
+   are guaranteed to be correct from the grammar perspective. Sticking with
+   the JSON structures also allows us to continue using existing parsers.
+   Furthermore, it is possible to implement default values at this level
+   as simply inserting extra JSON structures in places that are necessary.
+   This part is covered by ticket 5036.
+
+PHASE 2: simplify existing parsers by getting rid of the build/commit split.
+   Get rid of the inheritance contexts. Essentially the parser should
+   take JSON structure as a parameter and return the configuration structure.
+   For example, for options this should essentially look like this:
+   @code
+   CfgOptionPtr parse(ConstElementPtr options)
+   @endcode
+   The whole complexity behind inheriting contexts should be removed
+   from the existing parsers  and implemented in the bison parser.
+   It should return extra JSON elements. The details are TBD, but there is
+   one example for setting up an renew-timer value on the subnet level that
+   is inherited from the global ("Dhcp6") level. This phase is covered by
+   ticket 5039.
+
+The code change for 5036 introduces flex/bison based parser. It is
+essentially defined in two files: dhcp6_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 example, for
+the following text:
+
+@code
+{
+   "Dhcp6":
+   {
+       "renew-timer": 100
+   }
+}
+@endcode
+this code would return the following sentence of tokens: LCURLY_BRACKET,
+DHCP6, COLON, LCURLY_BRACKET, RENEW_TIMER, COLON, INTEGER
+(a token with a value of 100), RCURLY_BRACKET, RCURLY_BRACKET, END
+
+This stream of tokens is being consumed by the parser that is defined
+in dhcp6_parser.yy. This file defines a grammar. Here's very simplified
+version of the Dhcp6 grammar:
+
+@code
+dhcp6_object: DHCP6 COLON LCURLY_BRACKET global_params RCURLY_BRACKET;
+
+global_params: global_param
+             | global_params COMMA global_param
+             ;
+
+// These are the parameters that are allowed in the top-level for
+// Dhcp6.
+global_param: preferred_lifetime
+            | valid_lifetime
+            | renew_timer
+            | rebind_timer
+            | subnet6_list
+            | interfaces_config
+            | lease_database
+            | hosts_database
+            | mac_sources
+            | relay_supplied_options
+            | host_reservation_identifiers
+            | client_classes
+            | option_data_list
+            | hooks_libraries
+            | expired_leases_processing
+            | server_id
+            | dhcp4o6_port
+            ;
+
+renew_timer: RENEW_TIMER COLON INTEGER;
+@endcode
+
+This may be slightly difficult to read at the beginning, but after getting used
+to the notation, it's very powerful and easy to extend. The first line defines
+that dhcp6_object consists of certain tokens (DHCP6, COLON and LCURLY_BRACKET)
+followed by 'global_params' expression, followed by RCURLY_BRACKET.
+
+The 'global_params' is defined recursively. It can either be a single
+'global_param' expression, or (a shorter) global_params followed by a
+comma and global_param.  Bison will apply this and will be able to
+parse comma separated lists of arbitrary lengths.
+
+A single parameter is defined by 'global_param' expression. This
+represents any parameter that may appear in the global scope of Dhcp6
+object. The complete definition for all of them is complex, but the
+example above includes renew_timer definition. It is defined as a
+series of RENEW_TIMER, COLON, INTEGER tokens.
+
+The above is a simplified version of the actual grammar. If used in the version
+above, it would parse the whole file, but would do nothing with that information.
+To build actual structures, bison allows to inject C++ code at any phase of
+the parsing. For example, when the parser detects Dhcp6 object, it wants to
+create a new MapElement. When the whole object is parsed, we can perform
+some sanity checks, inject defaults for parameters that were not defined,
+log and do other stuff.
+
+@code
+dhcp6_object: DHCP6 COLON LCURLY_BRACKET {
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement());
+    ctx.stack_.back()->set("Dhcp6", m);
+    ctx.stack_.push_back(m);
+} global_params RCURLY_BRACKET {
+    // Whole Dhcp6 parsing completed. If we ever want to do any wrap up
+    // (maybe some sanity checking, insert defaults if not specified),
+    // this would be the best place for it.
+    ctx.stack_.pop_back();
+};
+@endcode
+
+The above will do the following in order: consume DHCP6 token, consume COLON token,
+consume LCURLY_BRACKET, execute the code in first { ... }, parse global_params
+and do whatever the code for it tells, parser RCURLY_BRACKET, execute the code
+in the second { ... }.
+
+There is a simple stack defined in ctx.stack_, which is isc::dhcp::Parser6Context
+defined in src/bin/dhcp6/parser_context.h. When walking through the config file, each
+new context (e.g. entering into Dhcp6, Subnet6, Pool), a new Element is added
+to the end of the stack. Once the parsing of a given context is complete, it
+is removed from the stack. At the end of parsing, there should be a single
+element on the stack as the top-level parsing (syntax_map) only inserts the
+MapElement object, but does not remove it.
+
+@section dhcpv6ConfigSubParser Parsing Partial Configuration in DHCPv6
+
+One another important capability required is the ability to parse not only the
+whole configuration, but a subset of it. This is done by introducing artificial
+tokens (e.g. TOPLEVEL_JSON and TOPLEVEL_DHCP6). For complete list of available
+starting contexts, see @ref isc::dhcp::Parser6Context::ParserType. The
+Parse6Context::parse() method takes one parameter that specifies, whether the
+data to be parsed is expected to have a generic JSON or the whole configuration
+(DHCP6). This feature is currently mostly used by unit-tests (which often skip
+the '{ "Dhcp6": {' preamble), but it is expected to be soon used for parsing
+subnets only, host reservations only, options or basically any other elements.
+For example, to add the ability to parse only pools, the following could be added:
+
+@code
+start: TOPLEVEL_GENERIC_JSON sub_json
+     | TOPLEVEL_DHCP6 sub_dhcp6
+     | TOPLEVEL_POOL6 sub_pool6
+     ;
+@endcode
+
+The parser code contains the code definition and the Kea-dhcp6 updated
+to use that new parser. That parser is able to to load all examples
+from doc/example/kea6. It is also able to parser # comments (bash
+style, starting at the beginning or middle of the line), // comments
+(C++ style, can start anywhere) or / * * / comments (C style, can span
+multiple lines).
+
+This parser is currently used in production code. See configure()
+method in kea_controller.cc.
+
+There are several new unit-tests written, but the code mostly reuses existing
+one to verify that existing functionality was not compromised. There are
+several new interesting ones, though. ParserTest.file loads all the
+config file examples we have in doc/examples/kea6. This ensures that the
+parser is able to load them and also checks that our examples are sane.
+
+@section dhcp6ParserIncludes Config File Includes
+
+The new parser provides an ability to include files. The syntax was chosen
+to look similar to how Apache includes PHP scripts in HTML code. This
+particular syntax was chosen to emphasize that the inclusion directive
+is an additional feature and not really a part of JSON syntax.
+
+To include one file from another, user the following syntax:
+
+@code
+{
+    "Dhcp6": {
+        "interfaces-config": {
+            "interfaces": [ "*" ]},
+        "preferred-lifetime": 3000,
+        "rebind-timer": 2000,
+        "renew-timer": 1000,
+        <?include "subnets.json"?>
+        "valid-lifetime": 4000
+     }
+}
+@endcode
+
+The inclusion is implemented as a stack of files. Typically, when a
+single file is parsed, the files_ (a vector of strings) and sfiles_ (a
+vector of FILE*) both contain a single entry. However, when lexer
+detects &lt;?include "filename.json?&gt;, it calls
+@ref isc::dhcp::Parser6Context::includeFile method. Up to ten nesting
+levels are supported. This arbitrarily chosen limit is a protection
+against recursive inclusions.
+
+@section dhcp6ParserConflicts Avoiding syntactical conflicts in parsers
+
+Syntactic parser has a powerful ability to not only parse the string and
+check if it's a valid JSON syntax, but also check that the resulting structures
+match expected syntax (if subnet6 are really an array, not a map, if
+timers are expressed as integers, not as strings etc.).
+
+However, there are times when we need to parse a string as arbitrary
+JSON.  For example, if we're in Dhcp6 and the config contains entries
+for DhcpDdns or Dhcp4. If we were to use naive approach, the lexer
+would go through that content and most likely find some tokens that
+are also used in Dhcp6.  for example 'renew-timer' would be detected
+and the parser would complain that it was not expected. To avoid this
+problem, syntactic context was introduced. When the syntactic parser
+expects certain type of content, it calls @ref
+isc::dhcp::Parser6Context::enter() method to indicate what type of
+content is expected. For example, when Dhcp6 parser discovers
+uninteresting content like Dhcp4, it enters NO_KEYWORD mode. In this
+mode, everything is parsed as generic maps, lists, integers, booleans
+or strings. This results in generic JSON structures without any syntax
+checking. A corresponding/balanced @ref
+isc::dhcp::Parser6Context::leave() call is required before leaving the
+context to come back to the previous 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 DHCP6
+mode). Finally, the syntactic context allows the error message to be more
+descriptive if the input string does not parse properly.
+
+Details of the refactor of the classes derived from DhcpConfigParser are
+documented in http://kea.isc.org/wiki/SimpleParser.
+
 @section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
 @section dhcpv6ConfigInherit DHCPv6 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
+ 4004 - 0
src/bin/dhcp6/dhcp6_lexer.cc


File diff suppressed because it is too large
+ 1169 - 0
src/bin/dhcp6/dhcp6_lexer.ll


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


File diff suppressed because it is too large
+ 2155 - 0
src/bin/dhcp6/dhcp6_parser.h


File diff suppressed because it is too large
+ 1567 - 0
src/bin/dhcp6/dhcp6_parser.yy


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

@@ -11,6 +11,7 @@
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_log.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -56,7 +57,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);
+        Parser6Context parser;
+        json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6);
         if (!json) {
         if (!json) {
             isc_throw(isc::BadValue, "no configuration found");
             isc_throw(isc::BadValue, "no configuration found");
         }
         }

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

@@ -0,0 +1,193 @@
+// Generated 201612101015
+// 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_PARSER6_LOCATION_HH_INCLUDED
+# define YY_PARSER6_LOCATION_HH_INCLUDED
+
+# include "position.hh"
+
+#line 14 "dhcp6_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 "dhcp6_parser.yy" // location.cc:296
+} } // isc::dhcp
+#line 192 "location.hh" // location.cc:296
+#endif // !YY_PARSER6_LOCATION_HH_INCLUDED

+ 172 - 0
src/bin/dhcp6/parser_context.cc

@@ -0,0 +1,172 @@
+// 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 <dhcp6/parser_context.h>
+#include <dhcp6/dhcp6_parser.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <limits>
+
+namespace isc {
+namespace dhcp {
+
+Parser6Context::Parser6Context()
+  : trace_scanning_(false), trace_parsing_(false)
+{
+}
+
+Parser6Context::~Parser6Context()
+{
+}
+
+isc::data::ConstElementPtr
+Parser6Context::parseString(const std::string& str, ParserType parser_type)
+{
+    scanStringBegin(str, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ConstElementPtr
+Parser6Context::parseFile(const std::string& filename, ParserType parser_type) {
+    FILE* f = fopen(filename.c_str(), "r");
+    if (!f) {
+        isc_throw(Dhcp6ParseError, "Unable to open file " << filename);
+    }
+    scanFileBegin(f, filename, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ConstElementPtr
+Parser6Context::parseCommon() {
+    isc::dhcp::Dhcp6Parser 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(Dhcp6ParseError, "Parser abort");
+        }
+        scanEnd();
+    }
+    catch (...) {
+        scanEnd();
+        throw;
+    }
+    if (stack_.size() == 1) {
+        return (stack_[0]);
+    } else {
+        isc_throw(Dhcp6ParseError, "Expected exactly one terminal Element expected, found "
+                  << stack_.size());
+    }
+}
+
+
+void
+Parser6Context::error(const isc::dhcp::location& loc, const std::string& what)
+{
+    isc_throw(Dhcp6ParseError, loc << ": " << what);
+}
+
+void
+Parser6Context::error (const std::string& what)
+{
+    isc_throw(Dhcp6ParseError, what);
+}
+
+void
+Parser6Context::fatal (const std::string& what)
+{
+    isc_throw(Dhcp6ParseError, what);
+}
+
+isc::data::Element::Position
+Parser6Context::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
+Parser6Context::enter(const ParserContext& ctx)
+{
+    cstack_.push_back(ctx_);
+    ctx_ = ctx;
+}
+
+void
+Parser6Context::leave()
+{
+#if 1
+    if (cstack_.empty()) {
+        fatal("unbalanced syntactic context");
+    }
+#endif
+    ctx_ = cstack_.back();
+    cstack_.pop_back();
+}
+
+const std::string
+Parser6Context::contextName()
+{
+    switch (ctx_) {
+    case NO_KEYWORD:
+        return ("__no keyword__");
+    case CONFIG:
+        return ("toplevel");
+    case DHCP6:
+        return ("Dhcp6");
+    case LOGGING:
+        return ("Logging");
+    case INTERFACES_CONFIG:
+        return ("interfaces-config");
+    case LEASE_DATABASE:
+        return ("lease-database");
+    case HOSTS_DATABASE:
+        return ("hosts-database");
+    case MAC_SOURCES:
+        return ("mac-sources");
+    case HOST_RESERVATION_IDENTIFIERS:
+        return ("host-reservation-identifiers");
+    case HOOKS_LIBRARIES:
+        return ("hooks-librairies");
+    case SUBNET6:
+        return ("subnet6");
+    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 PD_POOLS:
+        return ("pd-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__");
+    }
+}
+
+};
+};

+ 330 - 0
src/bin/dhcp6/parser_context.h

@@ -0,0 +1,330 @@
+// 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 <dhcp6/dhcp6_parser.h>
+#include <dhcp6/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL isc::dhcp::Dhcp6Parser::symbol_type parser6_lex (Parser6Context& 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 Dhcp6ParseError : public isc::Exception {
+public:
+    Dhcp6ParseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class Parser6Context
+{
+public:
+
+    /// @brief Defines currently supported scopes
+    ///
+    /// Dhcp6Parser is able to parse several types of scope. Usually,
+    /// when it parses a config file, it expects the data to have a map
+    /// with Dhcp6 in it and all the parameters within that Dhcp6 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
+    /// 'Dhcp6' or 'reservations' around it. In such case the parser
+    /// is being told to start parsing as PARSER_HOST_RESERVATION6.
+    typedef enum {
+        /// This parser will parse the content as generic JSON.
+        PARSER_JSON,
+
+        /// This parser will parse the content as Dhcp6 config wrapped in a map
+        /// (that's the regular config file)
+        PARSER_DHCP6,
+
+        /// This parser will parse the content of Dhcp6 (without outer { } and
+        /// without "Dhcp6"). It is mostly used in unit-tests as most of the
+        /// unit-tests do not define the outer map and Dhcp6 entity, just the
+        /// contents of it.
+        SUBPARSER_DHCP6,
+
+        /// This will parse the input as interfaces content.
+        PARSER_INTERFACES,
+
+        /// This will parse the input as Subnet6 content.
+        PARSER_SUBNET6,
+
+        /// This will parse the input as pool6 content.
+        PARSER_POOL6,
+
+        /// This will parse the input as pd-pool content.
+        PARSER_PD_POOL,
+
+        /// 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.
+    Parser6Context();
+
+    /// @brief destructor
+    virtual ~Parser6Context();
+
+    /// @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 parser_type specifies expected content
+    void scanStringBegin(const std::string& str, ParserType type);
+
+    /// @brief Method called before scanning starts on a file.
+    ///
+    /// @param f stdio FILE pointer
+    /// @param filename file to be parsed
+    /// @param parser_type specifies expected content
+    void scanFileBegin(FILE* f, const std::string& filename, ParserType type);
+
+    /// @brief Method called after the last tokens are scanned.
+    void scanEnd();
+
+    /// @brief Divert input to an include file.
+    ///
+    /// @param filename file to be included
+    void includeFile(const std::string& filename);
+
+    /// @brief Run the parser on the string specified.
+    ///
+    /// This method parses specified string. Depending on the value of
+    /// parser_type, parser may either check only that the input is valid
+    /// JSON, or may do more specific syntax checking. See @ref ParserType
+    /// for supported syntax checkers.
+    ///
+    /// @param str string to be parsed
+    /// @param parser_type specifies expected content (usually DHCP6 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 DHCP6 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 Dhcp6ParseError
+    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 Dhcp6Parser is not able to handle the packet.
+    ///
+    /// @param what string explaining the nature of the error.
+    /// @throw Dhcp6ParseError
+    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 Dhcp6ParseError
+    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 Dhcp6, Logging and others)
+        CONFIG,
+
+        ///< Used while parsing content of Dhcp6.
+        DHCP6,
+
+        // not yet DHCP4,
+        // not yet DHCP_DDNS,
+
+        ///< Used while parsing content of Logging
+        LOGGING,
+
+        /// Used while parsing Dhcp6/interfaces structures.
+        INTERFACES_CONFIG,
+
+        /// Used while parsing Dhcp6/lease-database structures.
+        LEASE_DATABASE,
+
+        /// Used while parsing Dhcp6/hosts-database structures.
+        HOSTS_DATABASE,
+
+        /// Used while parsing Dhcp6/mac-sources structures.
+        MAC_SOURCES,
+
+        /// Used while parsing Dhcp6/host-reservation-identifiers.
+        HOST_RESERVATION_IDENTIFIERS,
+
+        /// Used while parsing Dhcp6/hooks-libraries.
+        HOOKS_LIBRARIES,
+
+        /// Used while parsing Dhcp6/Subnet6 structures.
+        SUBNET6,
+
+        /// Used while parsing Dhcp6/option-def structures.
+        OPTION_DEF,
+
+        /// Used while parsing Dhcp6/option-data, Dhcp6/subnet6/option-data
+        /// or anywhere option-data is present (client classes, host
+        /// reservations and possibly others).
+        OPTION_DATA,
+
+        /// Used while parsing Dhcp6/client-classes structures.
+        CLIENT_CLASSES,
+
+        /// Used while parsing Dhcp6/server-id structures.
+        SERVER_ID,
+
+        /// Used while parsing Dhcp6/control-socket structures.
+        CONTROL_SOCKET,
+
+        /// Used while parsing Dhcp6/subnet6/pools structures.
+        POOLS,
+
+        /// Used while parsing Dhcp6/subnet6/pd-pools structures.
+        PD_POOLS,
+
+        /// Used while parsing Dhcp6/reservations structures.
+        RESERVATIONS,
+
+        /// Used while parsing Dhcp6/subnet6/relay structures.
+        RELAY,
+
+        /// Used while parsing Dhcp6/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 DHCP6 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/dhcp6/parser_context_decl.h

@@ -0,0 +1,20 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PARSER6_CONTEXT_DECL_H
+#define PARSER6_CONTEXT_DECL_H
+
+/// @file parser_context_decl.h Forward declaration of the ParserContext class
+
+namespace isc {
+namespace dhcp {
+
+class Parser6Context;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif

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

@@ -0,0 +1,181 @@
+// Generated 201612101015
+// 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_PARSER6_POSITION_HH_INCLUDED
+# define YY_PARSER6_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 "dhcp6_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 "dhcp6_parser.yy" // location.cc:296
+} } // isc::dhcp
+#line 180 "position.hh" // location.cc:296
+#endif // !YY_PARSER6_POSITION_HH_INCLUDED

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

@@ -0,0 +1,158 @@
+// Generated 201612101015
+// 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_PARSER6_STACK_HH_INCLUDED
+# define YY_PARSER6_STACK_HH_INCLUDED
+
+# include <vector>
+
+#line 14 "dhcp6_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 "dhcp6_parser.yy" // stack.hh:132
+} } // isc::dhcp
+#line 156 "stack.hh" // stack.hh:132
+
+#endif // !YY_PARSER6_STACK_HH_INCLUDED

+ 2 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -24,6 +24,7 @@ AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/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/kea6\"
 
 
 CLEANFILES  = $(builddir)/logger_lockfile
 CLEANFILES  = $(builddir)/logger_lockfile
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
 CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
@@ -93,6 +94,7 @@ dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h
 dhcp6_unittests_SOURCES += kea_controller_unittest.cc
 dhcp6_unittests_SOURCES += kea_controller_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6to4_ipc_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6to4_ipc_unittest.cc
 dhcp6_unittests_SOURCES += classify_unittests.cc
 dhcp6_unittests_SOURCES += classify_unittests.cc
+dhcp6_unittests_SOURCES += parser_unittest.cc
 
 
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 
 

File diff suppressed because it is too large
+ 275 - 220
src/bin/dhcp6/tests/config_parser_unittest.cc


+ 4 - 2
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -145,7 +145,8 @@ public:
 
 
         ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
         ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
 
 
-        ConstElementPtr config = Element::fromJSON(config_txt);
+        ConstElementPtr config;
+        ASSERT_NO_THROW(config = parseDHCP6(config_txt));
         ConstElementPtr answer = server_->processConfig(config);
         ConstElementPtr answer = server_->processConfig(config);
         ASSERT_TRUE(answer);
         ASSERT_TRUE(answer);
 
 
@@ -321,7 +322,8 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr config = Element::fromJSON(config_txt);
+    ConstElementPtr config;
+    ASSERT_NO_THROW(config = parseDHCP6(config_txt));
 
 
     // Make sure there are no subnets configured.
     // Make sure there are no subnets configured.
     CfgMgr::instance().clear();
     CfgMgr::instance().clear();

+ 6 - 4
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -75,7 +75,7 @@ const char* CONFIGS[] = {
     "        {"
     "        {"
     "          \"name\": \"subscriber-id\","
     "          \"name\": \"subscriber-id\","
     "          \"data\": \"1234\","
     "          \"data\": \"1234\","
-    "          \"csv-format\": False"
+    "          \"csv-format\": false"
     "        } ]"
     "        } ]"
     " } ],"
     " } ],"
     "\"valid-lifetime\": 4000 }",
     "\"valid-lifetime\": 4000 }",
@@ -1682,7 +1682,7 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
         "          \"code\": ";
         "          \"code\": ";
     string config_postfix = ","
     string config_postfix = ","
         "          \"data\": \"normal_erouter_v6.cm\","
         "          \"data\": \"normal_erouter_v6.cm\","
-        "          \"csv-format\": True"
+        "          \"csv-format\": true"
         "        }],"
         "        }],"
         "\"subnet6\": [ { "
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
@@ -1704,8 +1704,10 @@ TEST_F(Dhcpv6SrvTest, 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);
+    ConstElementPtr json_bogus;
-    ElementPtr json_valid = Element::fromJSON(config_valid);
+    ASSERT_NO_THROW(json_bogus = parseDHCP6(config_bogus));
+    ConstElementPtr json_valid;
+    ASSERT_NO_THROW(json_valid = parseDHCP6(config_valid));
 
 
     NakedDhcpv6Srv srv(0);
     NakedDhcpv6Srv srv(0);
 
 

+ 2 - 1
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -693,7 +693,8 @@ Dhcpv6SrvTest::configure(const std::string& config) {
 
 
 void
 void
 Dhcpv6SrvTest::configure(const std::string& config, NakedDhcpv6Srv& srv) {
 Dhcpv6SrvTest::configure(const std::string& config, NakedDhcpv6Srv& srv) {
-    ElementPtr json = data::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/dhcp6/tests/dhcp6_test_utils.h

@@ -26,6 +26,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
 
 
 #include <list>
 #include <list>
@@ -638,6 +639,62 @@ public:
     NakedDhcpv6Srv srv_;
     NakedDhcpv6Srv 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::Parser6Context ctx;
+    return (ctx.parseString(in, isc::dhcp::Parser6Context::PARSER_JSON));
+}
+
+/// @brief Runs parser in Dhcp6 mode
+///
+/// This is a simplified Dhcp6 mode, so no outer { } and "Dhcp6" 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
+parseDHCP6(const std::string& in, bool verbose = false)
+{
+    try {
+        isc::dhcp::Parser6Context ctx;
+        return (ctx.parseString(in, isc::dhcp::Parser6Context::SUBPARSER_DHCP6));
+    }
+    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::Parser6Context ctx;
+        return (ctx.parseString(in, isc::dhcp::Parser6Context::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/dhcp6/tests/hooks_unittest.cc

@@ -1197,7 +1197,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet6Select) {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6(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
@@ -1273,7 +1274,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet6SselectChange) {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }";
         "\"valid-lifetime\": 4000 }";
 
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6(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

+ 198 - 5
src/bin/dhcp6/tests/kea_controller_unittest.cc

@@ -12,6 +12,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/duid.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
@@ -59,6 +60,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));
     };
     };
 
 
     void writeFile(const std::string& file_name, const std::string& content) {
     void writeFile(const std::string& file_name, const std::string& content) {
@@ -86,9 +88,11 @@ public:
     }
     }
 
 
     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) {
@@ -167,8 +171,9 @@ TEST_F(JSONFileBackendTest, jsonFile) {
     EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
     EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
 }
 }
 
 
-// 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, comments) {
+// 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"
@@ -187,9 +192,6 @@ 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);
     writeFile(TEST_FILE, config_hash_comments);
 
 
     // Now initialize the server
     // Now initialize the server
@@ -219,6 +221,197 @@ TEST_F(JSONFileBackendTest, comments) {
     EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
     EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
 }
 }
 
 
+// This test checks if configuration can be read from a JSON file
+// using C++ line (//) comments.
+TEST_F(JSONFileBackendTest, cppLineComments) {
+
+    string config_cpp_line_comments = "// This is a comment. It should be \n"
+        "//ignored. Real config starts in line below\n"
+        "{ \"Dhcp6\": {"
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "// comments in the middle should be ignored, too\n"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+    writeFile(TEST_FILE, config_cpp_line_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // And configure it using config without
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using C block (/* */) comments
+TEST_F(JSONFileBackendTest, cBlockComments) {
+
+    string config_c_block_comments = "/* This is a comment. It should be \n"
+        "ignored. Real config starts in line below*/\n"
+        "{ \"Dhcp6\": {"
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "/* comments in the middle should be ignored, too*/\n"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+
+      writeFile(TEST_FILE, config_c_block_comments);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // And configure it using config without
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using an include file.
+TEST_F(JSONFileBackendTest, include) {
+
+    string config = "{ \"Dhcp6\": {"
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "<?include \"" + string(TEST_INCLUDE) + "\"?>,"
+        "\"valid-lifetime\": 4000 }"
+        "}";
+    string include = "\n"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " } ]\n";
+
+    writeFile(TEST_FILE, config);
+    writeFile(TEST_INCLUDE, include);
+
+    // Now initialize the server
+    boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // And configure it using config without
+    EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+    // Now check if the configuration has been applied correctly.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(1, subnets->size());
+
+    // Check subnet 1.
+    EXPECT_EQ("2001:db8:1::", subnets->at(0)->get().first.toText());
+    EXPECT_EQ(64, subnets->at(0)->get().second);
+
+    // Check pools in the first subnet.
+    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
+    ASSERT_EQ(1, pools1.size());
+    EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+    EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+    EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if recursive include of a file is detected
+TEST_F(JSONFileBackendTest, recursiveInclude) {
+
+    string config_recursive_include = "{ \"Dhcp6\": {"
+        "\"interfaces-config\": {"
+        "  \"interfaces\": [ <?include \"" + string(TEST_INCLUDE) + "\"?> ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " } ],"
+        "\"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<ControlledDhcpv6Srv> srv;
+    ASSERT_NO_THROW(
+        srv.reset(new ControlledDhcpv6Srv(0))
+    );
+
+    // And configure it using config
+    try {
+        srv->init(TEST_FILE);
+        FAIL() << "Expected Dhcp6ParseError but nothing was raised";
+    }
+    catch (const Exception& ex) {
+        EXPECT_EQ(msg, ex.what());
+    }
+}
+
+// This test checks if configuration can be read from a JSON file.
 // 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

+ 479 - 0
src/bin/dhcp6/tests/parser_unittest.cc

@@ -0,0 +1,479 @@
+// 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 <dhcp6/parser_context.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) {
+    ASSERT_TRUE(a);
+    ASSERT_TRUE(b);
+    if (print) {
+        // std::cout << "JSON A: -----" << endl << a->str() << std::endl;
+        // std::cout << "JSON B: -----" << endl << b->str() << std::endl;
+        // cout << "---------" << endl << endl;
+    }
+    EXPECT_EQ(a->str(), b->str());
+}
+
+void testParser(const std::string& txt, Parser6Context::ParserType parser_type) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+    ASSERT_NO_THROW({
+            try {
+        Parser6Context ctx;
+        test_json = ctx.parseString(txt, parser_type);
+            } catch (const std::exception &e) {
+                cout << "EXCEPTION: " << e.what() << endl;
+                throw;
+            }
+
+    });
+
+    // Now compare if both representations are the same.
+    compareJSON(reference_json, test_json);
+}
+
+void testParser2(const std::string& txt, Parser6Context::ParserType parser_type) {
+    ConstElementPtr test_json;
+
+    ASSERT_NO_THROW({
+            try {
+        Parser6Context ctx;
+        test_json = ctx.parseString(txt, parser_type);
+            } catch (const std::exception &e) {
+                cout << "EXCEPTION: " << e.what() << endl;
+                throw;
+            }
+    });
+    /// @todo: Implement actual validation here. since the original
+    /// Element::fromJSON does not support several comment types, we don't
+    /// have anything to compare with.
+    /// std::cout << "Original text:" << txt << endl;
+    /// std::cout << "Parsed text  :" << test_json->str() << endl;
+}
+
+TEST(ParserTest, mapInMap) {
+    string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+    string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+                 "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+    string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+    string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+    string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelguese\" ], "
+                    "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+    string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+                 " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+    string txt = "{ \"string\": \"foo\","
+                   "\"integer\": 42,"
+                   "\"boolean\": true,"
+                   "\"map\": { \"foo\": \"bar\" },"
+                   "\"list\": [ 1, 2, 3 ],"
+                   "\"null\": null }";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+    string txt = "{ \"name\": \"user\","
+                   "\"type\": \"password\","
+                   "\"user\": \"name\","
+                   "\"password\": \"type\" }";
+    testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordDhcp6) {
+     string txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
+                  " \"interfaces\": [ \"type\", \"htype\" ] },\n"
+                  "\"preferred-lifetime\": 3000,\n"
+                  "\"rebind-timer\": 2000, \n"
+                  "\"renew-timer\": 1000, \n"
+                  "\"subnet6\": [ { "
+                  "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                  "    \"subnet\": \"2001:db8:1::/48\", "
+                  "    \"interface\": \"test\" } ],\n"
+                   "\"valid-lifetime\": 4000 } }";
+     testParser2(txt, Parser6Context::PARSER_DHCP6);
+}
+
+TEST(ParserTest, bashComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000,\n"
+                "# this is a comment\n"
+                "\"rebind-timer\": 2000, \n"
+                "# lots of comments here\n"
+                "# and here\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser6Context::PARSER_DHCP6);
+}
+
+TEST(ParserTest, cComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, // this is a comment \n"
+                "\"rebind-timer\": 2000, // everything after // is ignored\n"
+                "\"renew-timer\": 1000, // this will be ignored, too\n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser6Context::PARSER_DHCP6);
+}
+
+TEST(ParserTest, bashCommentsInline) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, # this is a comment \n"
+                "\"rebind-timer\": 2000, # everything after # is ignored\n"
+                "\"renew-timer\": 1000, # this will be ignored, too\n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser6Context::PARSER_DHCP6);
+}
+
+TEST(ParserTest, multilineComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
+                "that\n can \n span \n multiple \n lines */ \n"
+                "\"rebind-timer\": 2000,\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser2(txt, Parser6Context::PARSER_DHCP6);
+}
+
+
+void testFile(const std::string& fname, bool print) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    cout << "Attempting to load file " << fname << endl;
+
+    EXPECT_NO_THROW(reference_json = Element::fromJSONFile(fname, true));
+
+    EXPECT_NO_THROW(
+    try {
+        Parser6Context ctx;
+        test_json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6);
+    } catch (const std::exception &x) {
+        cout << "EXCEPTION: " << x.what() << endl;
+        throw;
+    });
+
+    ASSERT_TRUE(reference_json);
+    ASSERT_TRUE(test_json);
+
+    compareJSON(reference_json, test_json, print);
+
+
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with Parser6. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+    vector<string> configs;
+    configs.push_back("advanced.json");
+    configs.push_back("backends.json");
+    configs.push_back("classify.json");
+    configs.push_back("dhcpv4-over-dhcpv6.json");
+    configs.push_back("duid.json");
+    configs.push_back("hooks.json");
+    configs.push_back("leases-expiration.json");
+    configs.push_back("multiple-options.json");
+    configs.push_back("mysql-reservations.json");
+    configs.push_back("pgsql-reservations.json");
+    configs.push_back("reservations.json");
+    configs.push_back("several-subnets.json");
+    configs.push_back("simple.json");
+    configs.push_back("stateless.json");
+
+    for (int i = 0; i<configs.size(); i++) {
+        testFile(string(CFG_EXAMPLES) + "/" + configs[i], false);
+    }
+}
+
+void testError(const std::string& txt,
+               Parser6Context::ParserType parser_type,
+               const std::string& msg)
+{
+    try {
+        Parser6Context ctx;
+        ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+        FAIL() << "Expected Dhcp6ParseError but nothing was raised (expected: "
+               << msg << ")";
+    }
+    catch (const Dhcp6ParseError& ex) {
+        EXPECT_EQ(msg, ex.what());
+    }
+    catch (...) {
+        FAIL() << "Expected Dhcp6ParseError but something else was raised";
+    }
+}
+
+// Check errors
+TEST(ParserTest, errors) {
+    // no input
+    testError("", Parser6Context::PARSER_JSON,
+              "<string>:1.1: syntax error, unexpected end of file");
+    testError(" ", Parser6Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\n", Parser6Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("\t", Parser6Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\r", Parser6Context::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+
+    // comments
+    testError("# nothing\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError(" #\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("// nothing\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* nothing */\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:3.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:4.1: syntax error, unexpected end of file");
+    testError("/* nothing\n",
+              Parser6Context::PARSER_JSON,
+              "Comment not closed. (/* in line 1");
+    testError("\n\n\n/* nothing\n",
+              Parser6Context::PARSER_JSON,
+              "Comment not closed. (/* in line 4");
+    testError("{ /* */*/ }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3-8: Invalid character: *");
+    testError("{ /* // *// }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3-11: Invalid character: /");
+    testError("{ /* // *///  }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting }");
+
+    // includes
+    testError("<?\n",
+              Parser6Context::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include\n",
+              Parser6Context::PARSER_JSON,
+              "Directive not closed.");
+    string file = string(CFG_EXAMPLES) + "/" + "stateless.json";
+    testError("<?include \"" + file + "\"\n",
+              Parser6Context::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include \"/foo/bar\" ?>/n",
+              Parser6Context::PARSER_JSON,
+              "Can't open include file /foo/bar");
+
+    // case sensitivity
+    testError("{ \"foo\": True }",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.10: Invalid character: T");
+    testError("{ \"foo\": NULL  }",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.10: Invalid character: N");
+
+    // numbers
+    testError("123",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1-3: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-456",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1-4: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-0001",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1-5: syntax error, unexpected integer, "
+              "expecting {");
+    testError("1234567890123456789012345678901234567890",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-40: Failed to convert "
+              "1234567890123456789012345678901234567890"
+              " to an integer.");
+    testError("-3.14e+0",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1-8: syntax error, unexpected floating point, "
+              "expecting {");
+    testError("1e50000",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-7: Failed to convert 1e50000 "
+              "to a floating point.");
+
+    // strings
+    testError("\"aabb\"",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1-6: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("{ \"aabb\"err",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.9: Invalid character: e");
+    testError("{ err\"aabb\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3: Invalid character: e");
+    testError("\"a\n\tb\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-6: Invalid control in \"a\n\tb\"");
+    testError("\"a\\n\\tb\"",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1-8: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("\"a\\x01b\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-8: Bad escape in \"a\\x01b\"");
+    testError("\"a\\u0062\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0062\"");
+    testError("\"a\\u062z\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-9: Bad escape in \"a\\u062z\"");
+    testError("\"abc\\\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1-6: Overflow escape in \"abc\\\"");
+
+    // from data_unittest.c
+    testError("\\a",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\\"\\\"",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+
+    // want a map
+    testError("[]\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("[]\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("{ 123 }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3-5: syntax error, unexpected integer, "
+              "expecting }");
+    testError("{ 123 }\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.3-5: syntax error, unexpected integer");
+    testError("{ \"foo\" }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.9: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"foo\" }\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.9: syntax error, unexpected }, expecting :");
+    testError("{ \"foo\":null }\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.3-7: got unexpected keyword "
+              "\"foo\" in toplevel map.");
+    testError("{ \"Dhcp6\" }\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:1.11: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"Dhcp4\":[]\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting \",\" or }");
+    testError("{}{}\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected {, "
+              "expecting end of file");
+
+    // bad commas
+    testError("{ , }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ , \"foo\":true }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ \"foo\":true, }\n",
+              Parser6Context::PARSER_JSON,
+              "<string>:1.15: syntax error, unexpected }, "
+              "expecting constant string");
+
+    // bad type
+    testError("{ \"Dhcp6\":{\n"
+              "  \"preferred-lifetime\":false }}\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:2.24-28: syntax error, unexpected boolean, "
+              "expecting integer");
+
+    // unknown keyword
+    testError("{ \"Dhcp6\":{\n"
+              " \"preferred_lifetime\":600 }}\n",
+              Parser6Context::PARSER_DHCP6,
+              "<string>:2.2-21: got unexpected keyword "
+              "\"preferred_lifetime\" in Dhcp6 map.");
+}
+
+};

+ 3 - 3
src/bin/dhcp6/tests/sarr_unittest.cc

@@ -85,17 +85,17 @@ const char* CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
         "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
         "    \"subnet\": \"2001:db8:1::/48\", "
         "    \"subnet\": \"2001:db8:1::/48\", "
         "    \"interface\": \"eth0\","
         "    \"interface\": \"eth0\","
-        "    \"rapid-commit\": True"
+        "    \"rapid-commit\": true"
         " },"
         " },"
         " {"
         " {"
         "    \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],"
         "    \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],"
         "    \"subnet\": \"2001:db8:2::/48\", "
         "    \"subnet\": \"2001:db8:2::/48\", "
         "    \"interface\": \"eth1\","
         "    \"interface\": \"eth1\","
-        "    \"rapid-commit\": False"
+        "    \"rapid-commit\": false"
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000,"
         "\"valid-lifetime\": 4000,"
         " \"dhcp-ddns\" : {"
         " \"dhcp-ddns\" : {"
-        "     \"enable-updates\" : True, "
+        "     \"enable-updates\" : true, "
         "     \"qualifying-suffix\" : \"example.com\" }"
         "     \"qualifying-suffix\" : \"example.com\" }"
     "}",
     "}",