Parcourir la source

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

Tomek Mrugalski il y a 8 ans
Parent
commit
4c5902da95
36 fichiers modifiés avec 14998 ajouts et 297 suppressions
  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
 /stamp-h1
 /test-driver
+/ylwrap
 
 /all.info
 /coverage-cpp-html

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

@@ -66,6 +66,20 @@
   "subnet6": [
     {
       "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",
       "interface": "ethX"
     }

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

@@ -3,7 +3,7 @@
 
 { "Dhcp6":
 
-{ 
+{
 # Kea is told to listen on ethX interface only.
   "interfaces-config": {
     "interfaces": [ "ethX" ]
@@ -26,8 +26,8 @@
       "name": "lab",
       "test": "pkt.iface == 'ethX'",
       "option-data": [{
-          "name": "dns-servers",
-          "data": "2001:db8::1"
+	  "name": "dns-servers",
+	  "data": "2001:db8::1"
       }]
   },
 
@@ -40,36 +40,36 @@
 
 # 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
-# to 4491.    
+# to 4491.
   {
       "name": "cable-modems",
       "test": "vendor.enterprise == 4491"
-  },
+  }
 
   ],
-    
+
 
 # The following list defines subnets. Each subnet consists of at
 # least subnet and pool entries.
-  "subnet6": [ 
+  "subnet6": [
     {
-        "pools": [ { "pool": "2001:db8:1::/80" } ],
-        "subnet": "2001:db8:1::/64",
-        "client-class": "cable-modems",
-        "interface": "ethX"
+	"pools": [ { "pool": "2001:db8:1::/80" } ],
+	"subnet": "2001:db8:1::/64",
+	"client-class": "cable-modems",
+	"interface": "ethX"
     },
 # 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
 # to this class.
     {
-        "pools": [ { "pool": "2001:db8:2::/80" } ],
-        "subnet": "2001:db8:2::/64",
-        "reservations": [
-        {
-            "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
-            "client-classes": [ "cable-modems" ]
-        } ],
-        "interface": "ethX"
+	"pools": [ { "pool": "2001:db8:2::/80" } ],
+	"subnet": "2001:db8:2::/64",
+	"reservations": [
+	{
+	    "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+	    "client-classes": [ "cable-modems" ]
+	} ],
+	"interface": "ethX"
     }
   ]
 },
@@ -78,18 +78,17 @@
 # informational level (info, warn, error and fatal) should be logged to stdout.
 "Logging": {
     "loggers": [
-        {
-            "name": "kea-dhcp6",
-            "output_options": [
-                {
-                    "output": "stdout"
-                }
-            ],
-            "debuglevel": 0,
-            "severity": "INFO"
-        }
+	{
+	    "name": "kea-dhcp6",
+	    "output_options": [
+		{
+		    "output": "stdout"
+		}
+	    ],
+	    "debuglevel": 0,
+	    "severity": "INFO"
+	}
     ]
 }
 
 }
-

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

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

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

@@ -45,7 +45,8 @@
     "name": "kea",
     "user": "kea",
     "password": "kea",
-    "host": "localhost"
+    "host": "localhost",
+    "readonly": true
   },
 
 # 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",
                   "data": "3000:1::234"
-              }]
+              }],
+              "client-classes": [ "special_snowflake", "office" ]
           },
 # This is a bit more advanced reservation. The client with the specified
 # 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">
   <title>Kea Configuration</title>
 
-  <para>Kea is designed to allow different methods by which it can be
-    configured, each method being implemented by a component known as a
-    configuration backend.  At present, only one such backend is
-    available, that allowing configuration by means of a JSON file.</para>
-
-  <section id="json-backend">
-    <title>JSON Configuration Backend</title>
-    <para>JSON is the default configuration backend.
-    It assumes that the servers are started from the command line
+  <para>Kea is using JSON structures to handle configuration. Previously
+  we there was a concept of other configuration backends, but that never was
+  implemented and the idea was abandoned.</para>
+
+  <section id="json">
+    <title>JSON Configuration</title>
+    <para>JSON is notation used throughout the Kea project. The most obvious
+    usage is for configuration file, but it is also used for sending commands
+    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>).
     The JSON backend uses certain signals to influence Kea. The
     configuration file is specified upon startup using the -c parameter.</para>
@@ -23,10 +26,42 @@
       <title>JSON Syntax</title>
       <para>Configuration files for DHCPv4, DHCPv6 and DDNS modules are defined
       in an extended JSON format. Basic JSON is defined in <ulink
-      url="http://tools.ietf.org/html/rfc4627">RFC 4627</ulink>.  Kea components
-      use a slightly modified form of JSON in that they allow shell-style
-      comments in the file: lines with the hash (#) character in the first column
-      are comment lines and are ignored.</para>
+      url="http://tools.ietf.org/html/rfc7159">RFC 7159</ulink>. Note that Kea
+      1.2 introduces a new parser that is better at following the JSON spec.  In
+      particular, the only values allowed for boolean are true or false (all
+      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
       called a map) started with a curly bracket. It comprises the "Dhcp4", "Dhcp6",
@@ -89,7 +124,7 @@
 # The whole configuration structure ends here.
 }
 </screen>
-	</para>
+        </para>
 
         <para>More examples are available in the installed
         <filename>share/doc/kea/examples</filename> directory.</para>
@@ -113,7 +148,7 @@
         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
         configuration as Dhcp6/subnet6[0]/pool.</para>
-      
+
       <!-- @todo Add a reference here after #3422 is done -->
     </section>
 

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

@@ -2978,7 +2978,7 @@ It is merely echoed by the server
 
       <para>
         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
         tools for managing reservations in a database. The Kea wiki <ulink
         url="http://kea.isc.org/wiki/HostReservationsHowTo" /> provides detailed

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

@@ -4,7 +4,7 @@
 <!ENTITY mdash  "&#x2014;" >
 ]>
 
-  <chapter id="dhcp6">
+ <chapter id="dhcp6">
     <title>The DHCPv6 Server</title>
 
     <section id="dhcp6-start-stop">
@@ -3496,7 +3496,8 @@ If not specified, the default value is:
       provided by the clients.
       </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>
 "Dhcp6": {
     <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
         method is not completely reliable, as clients may use other link-local address
         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
         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
@@ -3600,7 +3601,11 @@ If not specified, the default value is:
         </simpara>
       </listitem>
     </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 id="dhcp6-decline">

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

@@ -5,3 +5,4 @@
 /spec_config.h
 /spec_config.h.pre
 /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 += 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
 
 nodist_libdhcp6_la_SOURCES = dhcp6_messages.h dhcp6_messages.cc
@@ -105,3 +109,30 @@ endif
 
 kea_dhcp6dir = $(pkgdatadir)
 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
 
+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
 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
@@ -41,6 +53,291 @@ all configuration parsers. All DHCPv6 parsers deriving from this class
 directly have their entire implementation in the
 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
 
 One notable useful feature of DHCP configuration is its parameter inheritance.

Fichier diff supprimé car celui-ci est trop grand
+ 4004 - 0
src/bin/dhcp6/dhcp6_lexer.cc


Fichier diff supprimé car celui-ci est trop grand
+ 1169 - 0
src/bin/dhcp6/dhcp6_lexer.ll


Fichier diff supprimé car celui-ci est trop grand
+ 3568 - 0
src/bin/dhcp6/dhcp6_parser.cc


Fichier diff supprimé car celui-ci est trop grand
+ 2155 - 0
src/bin/dhcp6/dhcp6_parser.h


Fichier diff supprimé car celui-ci est trop grand
+ 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 <dhcp6/json_config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
 #include <dhcp6/dhcp6_log.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
-        json = isc::data::Element::fromJSONFile(file_name, true);
+        Parser6Context parser;
+        json = parser.parseFile(file_name, Parser6Context::PARSER_DHCP6);
         if (!json) {
             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 += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 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)/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 += dhcp6to4_ipc_unittest.cc
 dhcp6_unittests_SOURCES += classify_unittests.cc
+dhcp6_unittests_SOURCES += parser_unittest.cc
 
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 

Fichier diff supprimé car celui-ci est trop grand
+ 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()));
 
-        ConstElementPtr config = Element::fromJSON(config_txt);
+        ConstElementPtr config;
+        ASSERT_NO_THROW(config = parseDHCP6(config_txt));
         ConstElementPtr answer = server_->processConfig(config);
         ASSERT_TRUE(answer);
 
@@ -321,7 +322,8 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
         " } ],"
         "\"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.
     CfgMgr::instance().clear();

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

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

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

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

+ 57 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -26,6 +26,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
 #include <hooks/hooks_manager.h>
 
 #include <list>
@@ -638,6 +639,62 @@ public:
     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 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 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6(config));
     ConstElementPtr status;
 
     // Configure the server and make sure the config is accepted
@@ -1273,7 +1274,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet6SselectChange) {
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
-    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr json;
+    EXPECT_NO_THROW(json = parseDHCP6(config));
     ConstElementPtr status;
 
     // 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/iface_mgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
@@ -59,6 +60,7 @@ public:
         LeaseMgrFactory::destroy();
         isc::log::setDefaultLoggingOutput();
         static_cast<void>(remove(TEST_FILE));
+        static_cast<void>(remove(TEST_INCLUDE));
     };
 
     void writeFile(const std::string& file_name, const std::string& content) {
@@ -86,9 +88,11 @@ public:
     }
 
     static const char* TEST_FILE;
+    static const char* TEST_INCLUDE;
 };
 
 const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
+const char* JSONFileBackendTest::TEST_INCLUDE = "test-include.json";
 
 // This test checks if configuration can be read from a JSON file.
 TEST_F(JSONFileBackendTest, jsonFile) {
@@ -167,8 +171,9 @@ TEST_F(JSONFileBackendTest, jsonFile) {
     EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
 }
 
-// This test checks if configuration can be read from a JSON file.
-TEST_F(JSONFileBackendTest, comments) {
+// This test checks if configuration can be read from a JSON file
+// using hash (#) line comments
+TEST_F(JSONFileBackendTest, hashComments) {
 
     string config_hash_comments = "# This is a comment. It should be \n"
         "#ignored. Real config starts in line below\n"
@@ -187,9 +192,6 @@ TEST_F(JSONFileBackendTest, comments) {
         "\"valid-lifetime\": 4000 }"
         "}";
 
-    /// @todo: Implement C++-style (// ...) comments
-    /// @todo: Implement C-style (/* ... */) comments
-
     writeFile(TEST_FILE, config_hash_comments);
 
     // Now initialize the server
@@ -219,6 +221,197 @@ TEST_F(JSONFileBackendTest, comments) {
     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:
 // - empty file
 // - 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\" } ],"
         "    \"subnet\": \"2001:db8:1::/48\", "
         "    \"interface\": \"eth0\","
-        "    \"rapid-commit\": True"
+        "    \"rapid-commit\": true"
         " },"
         " {"
         "    \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],"
         "    \"subnet\": \"2001:db8:2::/48\", "
         "    \"interface\": \"eth1\","
-        "    \"rapid-commit\": False"
+        "    \"rapid-commit\": false"
         " } ],"
         "\"valid-lifetime\": 4000,"
         " \"dhcp-ddns\" : {"
-        "     \"enable-updates\" : True, "
+        "     \"enable-updates\" : true, "
         "     \"qualifying-suffix\" : \"example.com\" }"
     "}",