Browse Source

merged branch jelte_datadef back
Configuration syntax has changed; see README
Also the parkinglot.db format has changed and 'old' configuration will be
thrown away.
Merge back done to facilitate refactoring of names to conform to style guidelines (doing that in a separate branch could create too many conflicts)
next up: tests and documentation


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/parkinglot@405 e5f2f494-b856-4b98-b285-d166d9295462

Jelte Jansen 15 years ago
parent
commit
f772eb96a9

+ 89 - 0
README

@@ -0,0 +1,89 @@
+
+BUILDING
+
+Simple build instructions:
+
+autoreconf
+./configure
+make
+
+
+RUNNING
+
+At the moment there is no install yet, you can run the bind10 parkinglot
+server from the source tree:
+./src/bin/bind10/bind10
+
+The server will listen on port 5300 for DNS requests.
+
+
+CONFIGURATION
+
+Commands can be given through the tool bigtool;
+cd src/bin/bigtool
+sh run_bigtool
+
+The server must be running for bigtool to work.
+
+The following configuration commands are available
+
+help: show the different command modules
+<module> help: show the commands for module
+<module> <command> help: show info for the command
+
+
+config show [identifier]: Show the currently set values. If no identifier is
+                          given, the current location is used. If a config
+                          option is a list or a map, the value is not
+                          shown directly, but must be requested separately.
+config go [identifier]:   Go to the given location within the configuration.
+config set [identifier] <value>: Set a configuration value.
+config unset [identifier]: Remove a value (reverts to default if the option
+                           is mandatory).
+config add [identifier] <value>: add a value to a list
+config remove [identifier] <value>: remove a value from a list 
+config revert:	Revert all changes that have not been committed
+config commit: Commit all changes
+
+
+EXAMPLE SESSION
+
+~> sh run_bigtool
+> config show
+ParkingLot/	module	
+> config show ParkingLot/
+port:	5300	integer	(default)
+zones/	list	
+a_records/	list	(default)
+aaaa_records/	list	(default)
+ns_records/	list	(default)
+> config go ParkingLot/
+/ParkingLot> config show
+port:	5300	integer	(default)
+zones/	list	
+a_records/	list	(default)
+aaaa_records/	list	(default)
+ns_records/	list	(default)
+/ParkingLot> config show zones
+/ParkingLot> config add zone tjeb.nl
+Error: /ParkingLot/zone not found
+/ParkingLot> config add zones tjeb.nl
+/ParkingLot> config show zones
+zone_name:	tjeb.nl	string	
+/ParkingLot> config show
+port:	5300	integer	(default)
+zones/	list	(modified)
+a_records/	list	(default)
+aaaa_records/	list	(default)
+ns_records/	list	(default)
+/ParkingLot> config go /
+> config show ParkingLot/port
+port:	5300	integer	(default)
+> config go ParkingLot/a_records/
+/ParkingLot/a_records> config show
+address:	127.0.0.1	string	
+/ParkingLot/a_records> config add "127.0.0.2"
+/ParkingLot/a_records> config show
+address:	127.0.0.2	string	
+/ParkingLot/a_records> 
+

+ 1 - 0
configure.ac

@@ -73,6 +73,7 @@ AC_OUTPUT([src/bin/bind-cfgd/bind-cfgd
            src/bin/bind10/bind10
 	   src/bin/msgq/msgq
 	   src/bin/msgq/msgq_test
+           src/bin/parkinglot/config.h
           ], [
            chmod +x src/bin/bind-cfgd/bind-cfgd
            chmod +x src/bin/bigtool/run_bigtool

+ 56 - 18
src/bin/bigtool/run_bigtool.py

@@ -4,20 +4,6 @@ import ISC
 
 
 def _prepare_fake_data(bigtool):
-    add_cmd = CommandInfo(name = "add", desc = "add one zone")
-    remove_cmd = CommandInfo(name = 'remove', desc = 'remove one zone')
-    list_cmd = CommandInfo(name = 'list', desc = 'list all zones', 
-                           need_inst_param = False)                                                                                           
-
-    zone_module = ModuleInfo(name = "zone", 
-                             inst_name = "zone_name", 
-                             inst_type = STRING_TYPE, 
-                             inst_desc = "the name of one zone",
-                             desc = "manage all the zones")
-    zone_module.add_command(add_cmd)
-    zone_module.add_command(remove_cmd)
-    zone_module.add_command(list_cmd)
-
     shutdown_param = ParamInfo(name = "module_name", desc = "the name of module")
     shutdown_cmd = CommandInfo(name = 'shutdown', desc = "stop bind10",
                                need_inst_param = False)
@@ -25,18 +11,70 @@ def _prepare_fake_data(bigtool):
     boss_module = ModuleInfo(name = "boss", desc = "boss of bind10")
     boss_module.add_command(shutdown_cmd)               
 
-    bigtool.add_module_info(zone_module)
     bigtool.add_module_info(boss_module)
+
+def prepare_commands(bigtool, command_spec):
+    for module_name in command_spec.keys():
+        bigtool.prepare_module_commands(module_name, command_spec[module_name])
+
+def prepare_config_commands(bigtool):
+    module = ModuleInfo(name = "config", desc = "Configuration commands")
+    cmd = CommandInfo(name = "show", desc = "Show configuration", need_inst_param = False)
+    param = ParamInfo(name = "identifier", type = "string", optional=True)
+    cmd.add_param(param)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "add", desc = "Add entry to configuration list", need_inst_param = False)
+    param = ParamInfo(name = "identifier", type = "string", optional=True)
+    cmd.add_param(param)
+    param = ParamInfo(name = "value", type = "string", optional=False)
+    cmd.add_param(param)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list", need_inst_param = False)
+    param = ParamInfo(name = "identifier", type = "string", optional=True)
+    cmd.add_param(param)
+    param = ParamInfo(name = "value", type = "string", optional=False)
+    cmd.add_param(param)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "set", desc = "Set a configuration value", need_inst_param = False)
+    param = ParamInfo(name = "identifier", type = "string", optional=True)
+    cmd.add_param(param)
+    param = ParamInfo(name = "value", type = "string", optional=False)
+    cmd.add_param(param)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "unset", desc = "Unset a configuration value", need_inst_param = False)
+    param = ParamInfo(name = "identifier", type = "string", optional=False)
+    cmd.add_param(param)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "commit", desc = "Commit all local changes", need_inst_param = False)
+    module.add_command(cmd)
+
+    cmd = CommandInfo(name = "go", desc = "Go to a specific configuration part", need_inst_param = False)
+    param = ParamInfo(name = "identifier", type="string", optional=False)
+    cmd.add_param(param)
+    module.add_command(cmd)
+
+    bigtool.add_module_info(module)
     
 
 if __name__ == '__main__':
     try:
         cc = ISC.CC.Session()
-        #cc.group_subscribe("BigTool", "*", "meonly")
-        #cc.group_subscribe("ConfigManager", "*", "meonly")
-        #cc.group_subscribe("Boss", "*", "meonly")
+        cc.group_subscribe("BigTool", "*")
+        cc.group_subscribe("Boss", "*")
 
         tool = BigTool(cc)
+        cc.group_sendmsg({ "command": ["get_commands"] }, "ConfigManager")
+        command_spec, env =  cc.group_recvmsg(False)
+        prepare_commands(tool, command_spec["result"][1])
+        prepare_config_commands(tool)
         _prepare_fake_data(tool)   
         tool.cmdloop()
     except ISC.CC.SessionError:

+ 2 - 0
src/bin/msgq/msgq.py

@@ -197,6 +197,8 @@ class MsgQ:
     def process_command(self, fd, sock, routing, data):
         """Process a single command.  This will split out into one of the
            other functions, above."""
+        print("[XX] got command: ")
+        print(routing)
         cmd = routing["type"]
         if cmd == 'send':
             self.process_command_send(sock, routing, data)

+ 101 - 54
src/bin/parkinglot/ccsession.cc

@@ -14,38 +14,103 @@
 
 // $Id$
 
+// 
+// todo: generalize this and make it into a specific API for all modules
+//       to use (i.e. connect to cc, send config and commands, get config,
+//               react on config change announcements)
+//
+
+
 #include <stdexcept>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/time.h>
 
 #include <iostream>
+#include <fstream>
 #include <sstream>
 
 #include <boost/foreach.hpp>
 
 #include <cc/cpp/data.h>
+#include <cc/cpp/data_def.h>
 #include <cc/cpp/session.h>
 
 #include "common.h"
 #include "ccsession.h"
+#include "config.h"
 
 using namespace std;
 
 using ISC::Data::Element;
 using ISC::Data::ElementPtr;
+using ISC::Data::DataDefinition;
+using ISC::Data::ParseError;
+using ISC::Data::DataDefinitionError;
+
+void
+CommandSession::read_data_definition(const std::string& filename) {
+    std::ifstream file;
+
+    // this file should be declared in a @something@ directive
+    file.open(PARKINGLOT_SPECFILE_LOCATION);
+    if (!file) {
+        cout << "error opening " << PARKINGLOT_SPECFILE_LOCATION << endl;
+        exit(1);
+    }
 
-CommandSession::CommandSession() :
+    try {
+        data_definition_ = DataDefinition(file, true);
+    } catch (ParseError pe) {
+        cout << "Error parsing definition file: " << pe.what() << endl;
+        exit(1);
+    } catch (DataDefinitionError dde) {
+        cout << "Error reading definition file: " << dde.what() << endl;
+        exit(1);
+    }
+    file.close();
+}
+
+CommandSession::CommandSession(std::string module_name,
+                               std::string spec_file_name,
+                               ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config),
+                               ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command)
+                              ) :
+    module_name_(module_name),
     session_(ISC::CC::Session())
 {
-    try {
-        session_.establish();
-        session_.subscribe("ParkingLot", "*");
-        session_.subscribe("Boss", "ParkingLot");
-        //session_.subscribe("ConfigManager", "*", "meonly");
-        //session_.subscribe("statistics", "*", "meonly");
-    } catch (...) {
-        throw std::runtime_error("SessionManager: failed to open sessions");
+    config_handler_ = config_handler;
+    command_handler_ = command_handler;
+
+    // todo: workaround, let boss wait until msgq is started
+    // and remove sleep here
+    sleep(1);
+
+    ElementPtr answer, env;
+
+    session_.establish();
+    session_.subscribe(module_name, "*");
+    session_.subscribe("Boss", "*");
+    session_.subscribe("statistics", "*");
+    read_data_definition(spec_file_name);
+    sleep(1);
+    // send the data specification
+    session_.group_sendmsg(data_definition_.getDefinition(), "ConfigManager");
+    session_.group_recvmsg(env, answer, false);
+    
+    // get any stored configuration from the manager
+    if (config_handler_) {
+        ElementPtr cmd = Element::create_from_string("{ \"command\": [ \"get_config\", \"" + module_name + "\" ] }");
+        session_.group_sendmsg(cmd, "ConfigManager");
+        session_.group_recvmsg(env, answer, false);
+        cout << "[XX] got config: " << endl << answer->str() << endl;
+        if (answer->contains("result") &&
+            answer->get("result")->get(0)->int_value() == 0 &&
+            answer->get("result")->size() > 1) {
+            config_handler(answer->get("result")->get(1));
+        } else {
+            cout << "[XX] no result in answer" << endl;
+        }
     }
 }
 
@@ -55,55 +120,37 @@ CommandSession::getSocket()
     return (session_.getSocket());
 }
 
-std::pair<std::string, std::string>
-CommandSession::getCommand(int counter) {
-    ElementPtr cmd, routing, data, ep;
-    string s;
-
-    session_.group_recvmsg(routing, data, false);
-    string channel = routing->get("group")->string_value();
-
-    if (channel == "statistics") {
-        cmd = data->get("command");
-        if (cmd != NULL && cmd->string_value() == "getstat") {
-            struct timeval now;
-            ElementPtr resp = Element::create(std::map<std::string,
-                                              ElementPtr>());
-            gettimeofday(&now, NULL);
-            resp->set("sent", Element::create(now.tv_sec +
-                                              (double)now.tv_usec / 1000000));
-            resp->set("counter", Element::create(counter));
-            session_.group_sendmsg(resp, "statistics");
+int
+CommandSession::check_command()
+{
+    cout << "[XX] check for command" << endl;
+    ElementPtr cmd, routing, data;
+    if (session_.group_recvmsg(routing, data, true)) {
+        /* ignore result messages (in case we're out of sync, to prevent
+         * pingpongs */
+        if (!data->get_type() == Element::map || data->contains("result")) {
+            return 0;
         }
-    } else {
-        cmd = data->get("zone_added");
-        if (cmd != NULL)
-            return std::pair<string, string>("addzone", cmd->string_value());
-        cmd = data->get("zone_deleted");
-        if (cmd != NULL) {
-            return std::pair<string, string>("delzone", cmd->string_value());
+        cout << "[XX] got something!" << endl << data->str() << endl;
+        ElementPtr answer;
+        if (data->contains("config_update")) {
+            if (config_handler_) {
+                // handle config update
+                answer = config_handler_(data->get("config_update"));
+            } else {
+                answer = Element::create_from_string("{ \"result\": [0] }");
+            }
         }
-        cmd = data->get("command");
-        if (cmd != NULL) {
-            if (cmd->get_type() == Element::string && cmd->string_value() == "shutdown") {
-                return std::pair<string, string>("shutdown", "");
+        if (data->contains("command")) {
+            if (command_handler_) {
+                answer = command_handler_(data->get("command"));
+            } else {
+                answer = Element::create_from_string("{ \"result\": [0] }");
             }
         }
+        session_.reply(routing, answer);
     }
-
-    return std::pair<string, string>("unknown", "");
+    
+    return 0;
 }
 
-std::vector<std::string>
-CommandSession::getZones() {
-    ElementPtr cmd, result, env;
-    std::vector<std::string> zone_names;
-    cmd = Element::create_from_string("{ \"command\": [ \"zone\", \"list\" ] }");
-    sleep(1);
-    session_.group_sendmsg(cmd, "ConfigManager");
-    session_.group_recvmsg(env, result, false);
-    BOOST_FOREACH(ElementPtr zone_name, result->get("result")->list_value()) {
-        zone_names.push_back(zone_name->string_value());
-    }
-    return zone_names;
-}

+ 54 - 3
src/bin/parkinglot/ccsession.h

@@ -20,15 +20,66 @@
 #include <string>
 
 #include <cc/cpp/session.h>
+#include <cc/cpp/data_def.h>
+#include <cc/cpp/data.h>
 
 class CommandSession {
 public:
-    CommandSession();
+    /**
+     * Initialize a config/command session
+     * @param module_name: The name of this module. This is not a
+     *                     reference because we expect static strings
+     *                     to be passed here.
+     * @param spec_file_name: The name of the file containing the data
+     *                        definition.
+     */
+    CommandSession(std::string module_name, std::string spec_file_name,
+                   ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config) = NULL,
+                   ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command) = NULL
+                  );
     int getSocket();
-    std::pair<std::string, std::string> getCommand(int counter);
-    std::vector<std::string> getZones();
+
+    /**
+     * Check if there is a command or config change on the command
+     * session. If so, the appropriate handler is called if set.
+     * If not set, a default answer is returned.
+     * This is a non-blocking read; if there is nothing this function
+     * will return 0.
+     */
+    int check_command();
+
+    /**
+     * The config handler function should expect an ElementPtr containing
+     * the full configuration where non-default values have been set.
+     * Later we might want to think about more granular control
+     * (i.e. this does not scale to for instance lists containing
+     * 100000 zones, where the whole list is passed every time a single
+     * thing changes)
+     */
+    void set_config_handler(ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config)) { config_handler_ = config_handler; };
+
+    /**
+     * Set a command handler; the function that is passed takes an
+     * ElementPtr, pointing to a list element, containing
+     * [ module_name, command_name, arg1, arg2, ... ]
+     * The returned ElementPtr should look like
+     * { "result": [ return_value, result_value ] }
+     * result value here is optional and depends on the command
+     *
+     * This protocol is very likely to change.
+     */
+    void set_command_handler(ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command)) { command_handler_ = command_handler; };
+    
 private:
+    void read_data_definition(const std::string& filename);
+    
+    std::string module_name_;
     ISC::CC::Session session_;
+    ISC::Data::DataDefinition data_definition_;
+    ISC::Data::ElementPtr config_;
+
+    ISC::Data::ElementPtr(*config_handler_)(ISC::Data::ElementPtr new_config);
+    ISC::Data::ElementPtr(*command_handler_)(ISC::Data::ElementPtr command);
 };
 
 #endif // __CCSESSION_H

+ 1 - 0
src/bin/parkinglot/config.h.in

@@ -0,0 +1 @@
+#define PARKINGLOT_SPECFILE_LOCATION "@abs_top_srcdir@/src/bin/parkinglot/parkinglot.spec"

+ 68 - 10
src/bin/parkinglot/main.cc

@@ -31,6 +31,7 @@
 #include <dns/message.h>
 
 #include <cc/cpp/session.h>
+#include <cc/cpp/data.h>
 
 #include "zoneset.h"
 #include "parkinglot.h"
@@ -38,10 +39,18 @@
 
 #include "common.h"
 
+#include <boost/foreach.hpp>
+
 using namespace std;
 
-const string PROGRAM = "parkinglot";
-const int DNSPORT = 53;
+const string PROGRAM = "ParkingLot";
+const string SPECFILE = "parkinglot.spec";
+const int DNSPORT = 5300;
+
+/* need global var for config/command handlers.
+ * todo: turn this around, and put handlers in the parkinglot
+ * class itself? */
+ParkingLot plot = ParkingLot(DNSPORT);
 
 static void
 usage() {
@@ -49,6 +58,58 @@ usage() {
     exit(1);
 }
 
+ISC::Data::ElementPtr
+my_config_handler(ISC::Data::ElementPtr config)
+{
+    cout << "[XX] Handle config: " << endl << config->str() << endl;
+    if (config->contains("zones")) {
+        plot.clear_zones();
+        BOOST_FOREACH(ISC::Data::ElementPtr zone_el, config->get("zones")->list_value()) {
+            plot.serve(zone_el->string_value());
+        }
+    }
+    if (config->contains("port")) {
+        // todo: what to do with port change. restart automatically?
+        // ignore atm
+    }
+    if (config->contains("a_records")) {
+        plot.clearARecords();
+        BOOST_FOREACH(ISC::Data::ElementPtr rel, config->get("a_records")->list_value()) {
+            plot.addARecord(rel->string_value());
+        }
+    }
+    if (config->contains("aaaa_records")) {
+        plot.clearAAAARecords();
+        BOOST_FOREACH(ISC::Data::ElementPtr rel, config->get("aaaa_records")->list_value()) {
+            plot.addAAAARecord(rel->string_value());
+        }
+    }
+    if (config->contains("ns_records")) {
+        plot.clearNSRecords();
+        BOOST_FOREACH(ISC::Data::ElementPtr rel, config->get("ns_records")->list_value()) {
+            plot.addNSRecord(rel->string_value());
+        }
+    }
+    return ISC::Data::Element::create_from_string("{ \"result\": [0] }");
+}
+
+ISC::Data::ElementPtr
+my_command_handler(ISC::Data::ElementPtr command)
+{
+    ISC::Data::ElementPtr answer = ISC::Data::Element::create_from_string("{ \"result\": [0] }");
+
+    cout << "[XX] Handle command: " << endl << command->str() << endl;
+    if (command->get(1)->string_value() == "print_message") {
+        cout << command->get(2)->string_value() << endl;
+        /* let's add that message to our answer as well */
+        cout << "[XX] answer was: " << answer->str() << endl;
+        answer->get("result")->add(command->get(2));
+        cout << "[XX] answer now: " << answer->str() << endl;
+    }
+
+    return answer;
+}
+
 int
 main(int argc, char* argv[]) {
     int ch;
@@ -69,18 +130,15 @@ main(int argc, char* argv[]) {
         usage();
 
     // initialize parking lot
-    ParkingLot plot(port);
+    //plot = ParkingLot(port);
 
     // initialize command channel
-    CommandSession cs;
-
+    CommandSession cs = CommandSession(PROGRAM, SPECFILE, my_config_handler, my_command_handler);
+    
     // main server loop
     fd_set fds;
     int ps = plot.getSocket();
     int ss = cs.getSocket();
-    BOOST_FOREACH(std::string zone, cs.getZones()) {
-        plot.serve(zone);
-    }
     int nfds = max(ps, ss) + 1;
     int counter = 0;
 
@@ -99,9 +157,9 @@ main(int argc, char* argv[]) {
             plot.processMessage();
         }
 
+        /* isset not really necessary, but keep it for now */
         if (FD_ISSET(ss, &fds)) {
-            pair<string,string> cmd = cs.getCommand(counter);
-            plot.command(cmd);
+            cs.check_command();
         }
     }
 

+ 64 - 16
src/bin/parkinglot/parkinglot.cc

@@ -28,21 +28,60 @@
 #include <dns/rrset.h>
 #include <dns/message.h>
 
+#include <cc/cpp/data.h>
+
 #include "common.h"
 #include "parkinglot.h"
 
+#include <boost/foreach.hpp>
+
 using namespace std;
 
 using namespace isc::dns;
 using namespace isc::dns::Rdata::IN;
 using namespace isc::dns::Rdata::Generic;
+using namespace ISC::Data;
+
+void
+ParkingLot::addARecord(std::string data) {
+    a_records.push_back(Rdata::RdataPtr(new A(data)));
+}
+
+void
+ParkingLot::addAAAARecord(std::string data) {
+    aaaa_records.push_back(Rdata::RdataPtr(new AAAA(data)));
+}
+
+void
+ParkingLot::addNSRecord(std::string data) {
+    ns_records.push_back(Rdata::RdataPtr(new NS(data)));
+}
+
+void
+ParkingLot::setSOARecord(isc::dns::Rdata::RdataPtr soa_record) {
+}
+
+void
+ParkingLot::setDefaultZoneData() {
+    clearARecords();
+    clearAAAARecords();
+    clearNSRecords();
+
+    addARecord("127.0.0.1");
+    addAAAARecord("::1");
+    addNSRecord("ns1.parking.example");
+    addNSRecord("ns2.parking.example");
+    addNSRecord("ns3.parking.example");
+}
 
 ParkingLot::ParkingLot(int port) {
-    ns1 = Rdata::RdataPtr(new NS("ns1.parking.example"));
+    setDefaultZoneData();
+    /*ns1 = Rdata::RdataPtr(new NS("ns1.parking.example"));
     ns2 = Rdata::RdataPtr(new NS("ns2.parking.example"));
     ns3 = Rdata::RdataPtr(new NS("ns3.parking.example"));
     a = Rdata::RdataPtr(new A("127.0.0.1"));
     aaaa = Rdata::RdataPtr(new AAAA("::1"));
+    */
     soa = Rdata::RdataPtr(new SOA("parking.example", "noc.parking.example",
                                         1, 1800, 900, 604800, TTL(86400)));
 
@@ -149,23 +188,26 @@ ParkingLot::processMessage() {
             msg.setRcode(Message::RCODE_NOERROR);
             RRset* nsset = new RRset(query->getName(), RRClass::IN,
                                      RRType::NS, TTL(3600));
-
-            nsset->addRdata(ns1);
-            nsset->addRdata(ns2);
-            nsset->addRdata(ns3);
+            BOOST_FOREACH(isc::dns::Rdata::RdataPtr ns, ns_records) {
+                nsset->addRdata(ns);
+            }
 
             if (query->getType() == RRType::NS)
                 msg.addRRset(SECTION_ANSWER, RRsetPtr(nsset));
             else if (query->getType() == RRType::A) {
                 msg.addRRset(SECTION_AUTHORITY, RRsetPtr(nsset));
-                RR arr(query->getName(), RRClass::IN, RRType::A, TTL(3600), a);
 
-                msg.addRR(SECTION_ANSWER, arr);
+                BOOST_FOREACH(isc::dns::Rdata::RdataPtr a, a_records) {
+                    RR arr(query->getName(), RRClass::IN, RRType::A, TTL(3600), a);
+                    msg.addRR(SECTION_ANSWER, arr);
+                }
             } else if (query->getType() == RRType::AAAA) {
                 msg.addRRset(SECTION_AUTHORITY, RRsetPtr(nsset));
-                RR aaaarr(query->getName(), RRClass::IN, RRType::AAAA,
-                          TTL(3600), aaaa);
-                msg.addRR(SECTION_ANSWER, aaaarr);
+                BOOST_FOREACH(isc::dns::Rdata::RdataPtr aaaa, aaaa_records) {
+                    RR aaaarr(query->getName(), RRClass::IN, RRType::AAAA,
+                              TTL(3600), aaaa);
+                    msg.addRR(SECTION_ANSWER, aaaarr);
+                }
             } else {
                 RR soarr(query->getName(), RRClass::IN, RRType::SOA,
                          TTL(3600), soa);
@@ -184,13 +226,19 @@ ParkingLot::processMessage() {
 }
 
 void
-ParkingLot::command(pair<string,string> cmd) {
-    if (cmd.first == "addzone")
-        serve(cmd.second);
-    else if (cmd.first == "delzone")
-        zones.forget(cmd.second);
-    else if (cmd.first == "shutdown")
+ParkingLot::command(pair<string,ElementPtr> cmd) {
+    if (cmd.first == "shutdown")
         exit(0);
+    else if (cmd.first == "config_update") {
+        // what to do with port settings?
+        ElementPtr zonelist_el = (cmd.second)->get("zones");
+        // We could walk through both lists and remove and serve
+        // accordingly, or simply clear all and add everything
+        zones.clear_zones();
+        BOOST_FOREACH(ElementPtr zone, zonelist_el->list_value()) {
+            zones.serve(zone->string_value());
+        }
+    }
 }
 
 void

+ 18 - 3
src/bin/parkinglot/parkinglot.h

@@ -18,6 +18,7 @@
 #define __PARKINGLOT_H 1
 
 #include "zoneset.h"
+#include <cc/cpp/data.h>
 
 class ParkingLot {
 public:
@@ -25,11 +26,25 @@ public:
     virtual ~ParkingLot() {}
     int getSocket() { return (sock); }
     void processMessage();
-    void command(std::pair<std::string,std::string>);
+    void command(std::pair<std::string,ISC::Data::ElementPtr>);
     void serve(std::string zone_name);
-    
+    void clear_zones() { zones.clear_zones(); };
+
+    void clearARecords() { a_records.clear(); };
+    void clearAAAARecords() { aaaa_records.clear(); };
+    void clearNSRecords() { ns_records.clear(); };
+
+    void addARecord(std::string data);
+    void addAAAARecord(std::string data);
+    void addNSRecord(std::string data);
+
+    void setSOARecord(isc::dns::Rdata::RdataPtr soa_record);
+
 private:
-    isc::dns::Rdata::RdataPtr ns1, ns2, ns3, a, aaaa, soa;
+    void setDefaultZoneData();
+
+    std::vector<isc::dns::Rdata::RdataPtr> a_records, aaaa_records, ns_records;
+    isc::dns::Rdata::RdataPtr soa;
     ZoneSet zones;
     int sock;
 };

+ 74 - 0
src/bin/parkinglot/parkinglot.spec

@@ -0,0 +1,74 @@
+{
+  "data_specification": {
+    "module_name": "ParkingLot",
+    "config_data": [
+      {
+        "item_name": "port",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 5300
+      },
+      {
+        "item_name": "zones",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [ ],
+        "list_item_spec": {
+          "item_name": "zone_name",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      },
+      {
+        "item_name": "a_records",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [ "127.0.0.1" ],
+        "list_item_spec": {
+          "item_name": "address",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      },
+      {
+        "item_name": "aaaa_records",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [ "::1" ],
+        "list_item_spec": {
+          "item_name": "address",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      },
+      {
+        "item_name": "ns_records",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [ "ns1.parking.example", "ns2.parking.example" ],
+        "list_item_spec": {
+          "item_name": "address",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      }
+    ],
+    "commands": [
+      {
+        "command_name": "print_message",
+        "command_description": "Print the given message to stdout",
+        "command_args": [ {
+          "item_name": "message",
+          "item_type": "string",
+          "item_optional": False,
+          "item_default": ""
+        } ]
+      }
+    ]
+  }
+}
+

+ 3 - 0
src/bin/parkinglot/zoneset.h

@@ -29,6 +29,9 @@ class ZoneSet : std::set<std::string> {
             std::cout << "no longer serving: " << s << std::endl;
             this->erase(s);
         }
+        void clear_zones() {
+            this->clear();
+        }
         bool contains(std::string s) {
             return (this->find(s) != this->end());
         }

+ 65 - 9
src/lib/bigtool/bigtool.py

@@ -2,11 +2,11 @@ import sys
 import readline
 from cmd import Cmd
 from exception import *
-from moduleinfo import ModuleInfo
-from moduleinfo import ParamInfo
+from moduleinfo import *
 from command import BigToolCmd
 from xml.dom import minidom
 import ISC
+import ISC.CC.data
 
 try:
     from collections import OrderedDict
@@ -29,12 +29,18 @@ class BigTool(Cmd):
 
     def __init__(self, session = None):
         Cmd.__init__(self)
-        self.prompt = '> '
+        self.location = ""
+        self.prompt_end = '> '
+        self.prompt = self.prompt_end
         self.ruler = '-'
         self.modules = OrderedDict()
         self.add_module_info(ModuleInfo("help", desc = "Get help for bigtool"))
         self.cc = session
+        self.config_data = ISC.CC.data.UIConfigData("", session)
 
+    def postcmd(self, stop, line):
+        self.prompt = self.location + self.prompt_end
+        return stop
 
     def validate_cmd(self, cmd):
         if not cmd.module in self.modules:
@@ -68,6 +74,15 @@ class BigTool(Cmd):
                 # the 'name' must be an integer (ie. the position of
                 # an unnamed argument
                 if type(name) == int:
+                    # lump all extraneous arguments together as one big final one
+                    # todo: check if last param type is a string?
+                    if (param_count > 2):
+                        while (param_count > len(command_info.params) - 1):
+                            params[param_count - 2] += params[param_count - 1]
+                            del(params[param_count - 1])
+                            param_count = len(params)
+                            cmd.params = params.copy()
+
                     # (-1, help is always in the all_params list)
                     if name >= len(all_params) - 1:
                         # add to last known param
@@ -87,13 +102,16 @@ class BigTool(Cmd):
             for name in manda_params:
                 if not name in params and not param_nr in params:
                     raise CmdMissParamSyntaxError(cmd.module, cmd.command, name)
-                              
+                
+                param_nr += 1
                 param_nr += 1
 
     def _handle_cmd(self, cmd):
         #to do, consist xml package and send to bind10
         if cmd.command == "help" or ("help" in cmd.params.keys()):
             self._handle_help(cmd)
+        elif cmd.module == "config":
+            self.apply_config_cmd(cmd)
         else:
             self.apply_cmd(cmd)
 
@@ -123,7 +141,9 @@ class BigTool(Cmd):
                 
     
     def onecmd(self, line):
-        if line == 'EOF'or line.lower() == "quit":
+        # check if there's anything on the cc first
+        self.check_cc_messages()
+        if line == 'EOF' or line.lower() == "quit":
             return True
             
         if line == 'h':
@@ -143,6 +163,11 @@ class BigTool(Cmd):
                 else:                       
                     hints = self._get_param_startswith(cmd.module, cmd.command,
                                                        text)
+                    if cmd.module == "config":
+                        # grm text has been stripped of slashes...
+                        my_text = self.location + "/" + cur_line.rpartition(" ")[2]
+                        list = self.config_data.config.get_item_list(my_text.rpartition("/")[0])
+                        hints.extend([val for val in list if val.startswith(text)])
             except CmdModuleNameFormatError:
                 if not text:
                     hints = list(self.modules.keys())
@@ -162,9 +187,9 @@ class BigTool(Cmd):
 
             except BigToolException:
                 hints = []
-            
+
             self.hint = hints
-            self._append_space_to_hint()
+            #self._append_space_to_hint()
 
         if state < len(self.hint):
             return self.hint[state]
@@ -206,8 +231,35 @@ class BigTool(Cmd):
 
         return []
         
+    def prepare_module_commands(self, module_name, module_commands):
+        module = ModuleInfo(name = module_name,
+                            desc = "same here")
+        for command in module_commands:
+            cmd = CommandInfo(name = command["command_name"],
+                              desc = command["command_description"],
+                              need_inst_param = False)
+            for arg in command["command_args"]:
+                param = ParamInfo(name = arg["item_name"],
+                                  type = arg["item_type"],
+                                  optional = bool(arg["item_optional"]))
+                if ("item_default" in arg):
+                    param.default = arg["item_default"]
+                cmd.add_param(param)
+            module.add_command(cmd)
+        self.add_module_info(module)
+
+    def check_cc_messages(self):
+        (message, env) = self.cc.group_recvmsg(True)
+        while message:
+            if 'commands_update' in message:
+                self.prepare_module_commands(message['commands_update'][0], message['commands_update'][1])
+            elif 'specification_update' in message:
+                self.config_data.config.specification[message['specification_update'][0]] = message['specification_update'][1]
+            (message, env) = self.cc.group_recvmsg(True)
 
     def _parse_cmd(self, line):
+        # check if there's anything on the cc first
+        self.check_cc_messages()
         try:
             cmd = BigToolCmd(line)
             self.validate_cmd(cmd)
@@ -304,7 +356,7 @@ class BigTool(Cmd):
         if not self.cc:
             return
         
-        groupName = (cmd.module == 'boss') and 'Boss' or 'ConfigManager'
+        groupName = cmd.module
         content = [cmd.module, cmd.command]
         values = cmd.params.values()
         if len(values) > 0:
@@ -312,9 +364,12 @@ class BigTool(Cmd):
 
         msg = {"command":content}
         print("begin to send the message...")
+
+        # XXTODO: remove this with new msgq
+        #self.cc.group_subscribe(groupName)
         
         try:   
-            self.cc.group_sendmsg(msg, groupName, instance = 'boss')
+            self.cc.group_sendmsg(msg, groupName)
             print("waiting for %s reply..." % groupName)
 
             reply, env = self.cc.group_recvmsg(False)
@@ -324,3 +379,4 @@ class BigTool(Cmd):
 
 
 
+

+ 1 - 0
src/lib/bigtool/moduleinfo.py

@@ -124,6 +124,7 @@ class CommandInfo:
         else:
             raise KeyError(str(pos) + " is not an integer")
     
+
     def need_instance_param(self):
         return self.need_inst_param
 

+ 129 - 42
src/lib/bind-cfgd/python/bind-cfgd.py

@@ -1,59 +1,88 @@
 import ISC
-import pickle
 import signal
+import ast
+import pprint
+import os
+from ISC.CC import data
+
+class ConfigManagerData:
+    CONFIG_VERSION = 1
+    DB_FILENAME = "/tmp/parkinglot.db"
     
-class ConfigData:
     def __init__(self):
-        self.zones = {}
+        self.data = {}
+        self.data['version'] = ConfigManagerData.CONFIG_VERSION
+
+    def set_data_definition(self, module_name, module_data_definition):
+        self.zones[module_name] = module_data_definition
+        self.data_definitions[module_name] = module_data_definition
 
-    def add_zone(self, zone_name, zone_file):
-        self.zones[zone_name] = zone_file
+    def read_from_file():
+        config = ConfigManagerData()
+        try:
+            file = open(ConfigManagerData.DB_FILENAME, 'r')
+            file_config = ast.literal_eval(file.read())
+            if 'version' in file_config and \
+                file_config['version'] == ConfigManagerData.CONFIG_VERSION:
+                config.data = file_config
+            else:
+                # of course we can put in a migration path here for old data
+                print("[bind-cfgd] Old version of data found, starting with empty configuration")
+            file.close()
+        except IOError as ioe:
+            print("No config file found, starting with empty config")
+        except EOFError as eofe:
+            print("Config file empty, starting with empty config")
+        except:
+            print("Config file unreadable, starting with empty config")
 
-    def remove_zone(self, zone_name):
-        del self.zones[zone_name]
+        return config
+        
+    def write_to_file(self):
+        try:
+            tmp_filename = self.DB_FILENAME + ".tmp"
+            file = open(tmp_filename, 'w');
+            pp = pprint.PrettyPrinter(indent=4)
+            s = pp.pformat(self.data)
+            file.write(s)
+            file.write("\n")
+            file.close()
+            os.rename(tmp_filename, self.DB_FILENAME)
+        except IOError as ioe:
+            print("Unable to write config file; configuration not stored")
 
 class ConfigManager:
     def __init__(self):
+        self.commands = {}
+        self.data_definitions = {}
+        self.config = ConfigManagerData()
         self.cc = ISC.CC.Session()
         self.cc.group_subscribe("ConfigManager")
         self.cc.group_subscribe("Boss", "ConfigManager")
-        self.config = ConfigData()
-        self.db_filename = "/tmp/parkinglot.db"
         self.running = False
 
     def notify_boss(self):
         self.cc.group_sendmsg({"running": "configmanager"}, "Boss")
 
-    def add_zone(self, zone_name):
-        self.config.add_zone(zone_name, "todo")
-        self.write_config()
-        print("sending update zone add")
-        self.cc.group_sendmsg({"zone_added": zone_name }, "ParkingLot")
+    def set_config(self, module_name, data_specification):
+        self.data_definitions[module_name] = data_specification
+        
+    def remove_config(self, module_name):
+        self.data_definitions[module_name]
+
+    def set_commands(self, module_name, commands):
+        self.commands[module_name] = commands
 
-    def remove_zone(self, zone_name):
-        self.config.remove_zone(zone_name)
-        print("sending update zone del")
-        self.cc.group_sendmsg({"zone_deleted": zone_name }, "ParkingLot")
+    def remove_commands(self, module_name):
+        del self.commands[module_name]
 
     def read_config(self):
         print("Reading config")
-        try:
-            file = open(self.db_filename, 'rb');
-            self.config = pickle.load(file)
-            file.close()
-        except IOError as ioe:
-            print("No config file found, starting with empty config")
-        except EOFError as eofe:
-            print("Config file empty, starting with empty config")
-
+        self.config = ConfigManagerData.read_from_file()
+        
     def write_config(self):
         print("Writing config")
-        try:
-            file = open(self.db_filename, 'wb');
-            pickle.dump(self.config, file)
-            file.close()
-        except IOError as ioe:
-            print("Unable to write config file; configuration not stored")
+        self.config.write_to_file()
 
     def handle_msg(self, msg):
         """return answer message"""
@@ -61,7 +90,60 @@ class ConfigManager:
         if "command" in msg:
             cmd = msg["command"]
             try:
-                if cmd[0] == "zone" and cmd[1] == "add":
+                if cmd[0] == "get_commands":
+                    answer["result"] = [ 0, self.commands ]
+                elif cmd[0] == "get_data_spec":
+                    if len(cmd) > 1 and cmd[1] != "":
+                        try:
+                            answer["result"] = [0, self.data_definitions[cmd[1]]]
+                        except KeyError as ke:
+                            answer["result"] = [1, "No specification for module " + cmd[1]]
+                    else:
+                        answer["result"] = [0, self.data_definitions]
+                elif cmd[0] == "get_config":
+                    # we may not have any configuration here
+                    conf_part = None
+                    if len(cmd) > 1:
+                        try:
+                            conf_part = data.find(self.config.data, cmd[1])
+                        except data.DataNotFoundError as dnfe:
+                            pass
+                    else:
+                        conf_part = self.config.data
+                    if conf_part:
+                        answer["result"] = [ 0, conf_part ]
+                    else:
+                        answer["result"] = [ 0 ]
+                elif cmd[0] == "set_config":
+                    if len(cmd) == 3:
+                        # todo: use api (and check types?)
+                        if cmd[1] != "":
+                            conf_part = data.find_no_exc(self.config.data, cmd[1])
+                            if not conf_part:
+                                conf_part = data.set(self.config.data, cmd[1], "")
+                        else:
+                            conf_part = self.config.data
+                        data.merge(conf_part, cmd[2])
+                        print("[XX bind-cfgd] new config (part):")
+                        print(conf_part)
+                        # send out changed info
+                        self.cc.group_sendmsg({ "config_update": conf_part }, cmd[1])
+                        answer["result"] = [ 0 ]
+                    elif len(cmd) == 2:
+                        print("[XX bind-cfgd] old config:")
+                        print(self.config.data)
+                        print("[XX bind-cfgd] updating with:")
+                        print(cmd[1])
+                        data.merge(self.config.data, cmd[1])
+                        print("[XX bind-cfgd] new config:")
+                        print(self.config.data)
+                        # send out changed info
+                        for module in self.config.data:
+                            self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
+                        answer["result"] = [ 0 ]
+                    else:
+                        answer["result"] = [ 1, "Wrong number of arguments" ]
+                elif cmd[0] == "zone" and cmd[1] == "add":
                     self.add_zone(cmd[2])
                     answer["result"] = [ 0 ]
                 elif cmd[0] == "zone" and cmd[1] == "remove":
@@ -73,7 +155,7 @@ class ConfigManager:
                         # a separate exception for that?
                         answer["result"] = [ 1, "Unknown zone" ]
                 elif cmd[0] == "zone" and cmd[1] == "list":
-                    answer["result"]     = list(self.config.zones.keys())
+                    answer["result"]     = []#list(self.config.zones.keys())
                 elif cmd == "shutdown":
                     print("[bind-cfgd] Received shutdown command")
                     self.running = False
@@ -82,7 +164,18 @@ class ConfigManager:
                     answer["result"] = [ 1, "Unknown command: " + str(cmd) ]
             except IndexError as ie:
                 print("[bind-cfgd] missing argument")
-                answer["result"] = [ 1, "Missing argument in command" ]
+                answer["result"] = [ 1, "Missing argument in command: " + str(ie) ]
+                raise ie
+        elif "data_specification" in msg:
+            # todo: validate? (no direct access to spec as
+            spec = msg["data_specification"]
+            if "config_data" in spec:
+                self.set_config(spec["module_name"], spec["config_data"])
+                self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "BigTool")
+            if "commands" in spec:
+                self.set_commands(spec["module_name"], spec["commands"])
+                self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "BigTool")
+            answer["result"] = [ 0 ]
         else:
             print("[bind-cfgd] unknown message: " + str(msg))
             answer["result"] = [ 1, "Unknown module: " + str(msg) ]
@@ -93,13 +186,8 @@ class ConfigManager:
         while (self.running):
             msg, env = self.cc.group_recvmsg(False)
             if msg:
-                print("[bind-cfgd] received message: ")
-                print(msg)
                 answer = self.handle_msg(msg);
-                print("[bind-cfgd] sending answer: ")
-                print(answer)
                 self.cc.group_reply(env, answer)
-                print("[bind-cfgd] answer sent")
             else:
                 self.running = False
 
@@ -116,7 +204,6 @@ if __name__ == "__main__":
         signal.signal(signal.SIGINT, signal_handler)
         signal.signal(signal.SIGTERM, signal_handler)
         cm.read_config()
-        # do loading here if necessary
         cm.notify_boss()
         cm.run()
         cm.write_config()

+ 5 - 2
src/lib/cc/cpp/Makefile.am

@@ -1,7 +1,10 @@
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib/cc/cpp -I$(top_srcdir)/ext
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib/cc/cpp -I$(top_srcdir)/ext -Wall -Werror
 
+bin_PROGRAMS = test
+test_SOURCES = test.cc
+test_LDADD = libcc.a
 lib_LIBRARIES = libcc.a
-libcc_a_SOURCES = data.cc data.h session.cc session.h
+libcc_a_SOURCES = data.cc data.h data_def.h data_def.cc session.cc session.h
 
 TESTS =
 if HAVE_GTEST

+ 101 - 52
src/lib/cc/cpp/data.cc

@@ -32,6 +32,13 @@ std::ostream& operator <<(std::ostream &out, const ISC::Data::ElementPtr& e) {
     return out << e->str();
 }
 
+const char*
+ParseError::what() const throw() {
+    stringstream ss;
+    ss << msg << " line " << line << " pos " << pos;
+    return ss.str().c_str();
+}
+
 //
 // factory functions
 //
@@ -113,83 +120,113 @@ char_in(char c, const char *chars)
 }
 
 static void
-skip_chars(std::stringstream &in, const char *chars)
+skip_chars(std::istream &in, const char *chars, int& line, int& pos)
 {
     char c = in.peek();
     while (char_in(c, chars) && c != EOF) {
+        if (c == '\n') {
+            line++;
+            pos = 1;
+        } else {
+            pos++;
+        }
         in.get();
         c = in.peek();
     }
 }
+/*static void
+skip_chars(std::istream &in, const char *chars)
+{
+    int l = 0, p = 0;
+    skip_chars(in, chars, l, p);
+}*/
 
 // skip on the input stream to one of the characters in chars
 // if another character is found this function returns false
 // unles that character is specified in the optional may_skip
 //
 // the character found is left on the stream
-static bool
-skip_to(std::stringstream &in, const char *chars, const char *may_skip="")
+static void
+skip_to(std::istream &in, int& line, int& pos, const char* chars, const char* may_skip="")
 {
     char c = in.get();
+    pos++;
     while (c != EOF) {
+        if (c == '\n') {
+            pos = 1;
+            line++;
+        }
         if (char_in(c, may_skip)) {
             c = in.get();
+            pos++;
         } else if (char_in(c, chars)) {
             while(char_in(in.peek(), may_skip)) {
+                if (in.peek() == '\n') {
+                    pos = 1;
+                    line++;
+                }
                 in.get();
+                pos++;
             }
             in.putback(c);
-            return true;
+            pos--;
+            return;
         } else {
-            // TODO: provide feeback mechanism?
-            cout << "error, '" << c << "' read; one of \"" << chars << "\" expected" << endl;
-            in.putback(c);
-            return false;
+            throw ParseError(std::string("'") + c + "' read, one of \"" + chars + "\" expected", line, pos);
         }
     }
-    // TODO: provide feeback mechanism?
-    cout << "error, EOF read; one of \"" << chars << "\" expected" << endl;
-            in.putback(c);
-    return false;
+    throw ParseError(std::string("EOF read, one of \"") + chars + "\" expected", line, pos);
 }
 
+/*static bool
+skip_to(std::istream &in, const char *chars, const char *may_skip="") {
+    int line = 0, pos = 0;
+    return skip_to(in, line, pos, chars, may_skip);
+}*/
+
 static std::string
-str_from_stringstream(std::stringstream &in)
+str_from_stringstream(std::istream &in, int& line, int& pos) throw (ParseError)
 {
     char c = 0;
     std::stringstream ss;
     c = in.get();
+    pos++;
     if (c == '"') {
         c = in.get();
+        pos++;
     } else {
-        return "badstring";
+        throw ParseError("String expected", line, pos);
     }
     while (c != EOF && c != '"') {
         ss << c;
         if (c == '\\' && in.peek() == '"') {
             ss << in.get();
+            pos++;
         }
         c = in.get();
+        pos++;
     }
     return ss.str();
 }
 
 static std::string
-word_from_stringstream(std::stringstream &in)
+word_from_stringstream(std::istream &in, int& line, int& pos)
 {
     std::stringstream ss;
     while (isalpha(in.peek())) {
         ss << (char) in.get();
     }
+    pos += ss.str().size();
     return ss.str();
 }
 
 
 static ElementPtr
-from_stringstream_int_or_double(std::stringstream &in)
+from_stringstream_int_or_double(std::istream &in, int &line, int &pos)
 {
     int i;
     in >> i;
+    // TODO count pos
     if (in.peek() == '.') {
         double d;
         in >> d;
@@ -201,9 +238,9 @@ from_stringstream_int_or_double(std::stringstream &in)
 }
 
 static ElementPtr
-from_stringstream_bool(std::stringstream &in)
+from_stringstream_bool(std::istream &in, int& line, int& pos)
 {
-    std::string word = word_from_stringstream(in);
+    std::string word = word_from_stringstream(in, line, pos);
     if (boost::iequals(word, "True")) {
         return Element::create(true);
     } else if (boost::iequals(word, "False")) {
@@ -214,65 +251,78 @@ from_stringstream_bool(std::stringstream &in)
 }
 
 static ElementPtr
-from_stringstream_string(std::stringstream &in)
+from_stringstream_string(std::istream &in, int& line, int& pos)
 {
-    return Element::create(str_from_stringstream(in));
+    return Element::create(str_from_stringstream(in, line, pos));
 }
 
 static ElementPtr
-from_stringstream_list(std::stringstream &in)
+from_stringstream_list(std::istream &in, int& line, int& pos)
 {
     char c = 0;
     std::vector<ElementPtr> v;
     ElementPtr cur_list_element;
+    //cout << "reading list at line " << line << " pos " << pos << endl;
 
-    skip_chars(in, " \t\n");
+    skip_chars(in, " \t\n", line, pos);
     while (c != EOF && c != ']') {
-        cur_list_element = Element::create_from_string(in);
-        v.push_back(cur_list_element);
-        if (!skip_to(in, ",]", " \t\n")) {
-            return ElementPtr();
+        //cout << "at line " << line << " pos " << pos << " cur c: " << c << " next c: " << char(in.peek()) << endl;
+        if (in.peek() != ']') {
+            cur_list_element = Element::create_from_string(in, line, pos);
+            v.push_back(cur_list_element);
+            skip_to(in, line, pos, ",]", " \t\n");
         }
         c = in.get();
+        pos++;
     }
     return Element::create(v);
 }
 
 static ElementPtr
-from_stringstream_map(std::stringstream &in)
+from_stringstream_map(std::istream &in, int& line, int& pos)
 {
     char c = 0;
     std::map<std::string, ElementPtr> m;
     std::pair<std::string, ElementPtr> p;
     std::string cur_map_key;
     ElementPtr cur_map_element;
-    skip_chars(in, " \t\n");
+    skip_chars(in, " \t\n", line, pos);
     while (c != EOF && c != '}') {
-        p.first = str_from_stringstream(in);
-        if (!skip_to(in, ":", " \t\n")) {
-            return ElementPtr();
-        } else {
-            // skip the :
-            in.get();
-        }
-        p.second = Element::create_from_string(in);
-        if (!p.second) { return ElementPtr(); };
+        p.first = str_from_stringstream(in, line, pos);
+        skip_to(in, line, pos, ":", " \t\n");
+        // skip the :
+        in.get();
+        pos++;
+        p.second = Element::create_from_string(in, line, pos);
+        if (!p.second) {
+            throw ParseError(std::string("missing map value for ") + p.first, line, pos);
+        };
         m.insert(p);
-        skip_to(in, ",}", " \t\n");
+        skip_to(in, line, pos, ",}", " \t\n");
         c = in.get();
+        pos++;
     }
     return Element::create(m);
 }
 
 ElementPtr
-Element::create_from_string(std::stringstream &in)
+Element::create_from_string(std::istream &in) throw(ParseError)
+{
+    int line = 1, pos = 1;
+    return create_from_string(in, line, pos);
+}
+
+ElementPtr
+Element::create_from_string(std::istream &in, int& line, int& pos) throw(ParseError)
 {
     char c = 0;
     ElementPtr element;
     bool el_read = false;
-    skip_chars(in, " \n\t");
+    skip_chars(in, " \n\t", line, pos);
     while (c != EOF && !el_read) {
         c = in.get();
+        pos++;
+        //std::cout << c << std::endl;
         switch(c) {
             case '1':
             case '2':
@@ -285,7 +335,7 @@ Element::create_from_string(std::stringstream &in)
             case '9':
             case '0':
                 in.putback(c);
-                element = from_stringstream_int_or_double(in);
+                element = from_stringstream_int_or_double(in, line, pos);
                 el_read = true;
                 break;
             case 't':
@@ -293,35 +343,33 @@ Element::create_from_string(std::stringstream &in)
             case 'f':
             case 'F':
                 in.putback(c);
-                element = from_stringstream_bool(in);
+                element = from_stringstream_bool(in, line, pos);
                 el_read = true;
                 break;
             case '"':
                 in.putback('"');
-                element = from_stringstream_string(in);
+                element = from_stringstream_string(in, line, pos);
                 el_read = true;
                 break;
             case '[':
-                element = from_stringstream_list(in);
+                element = from_stringstream_list(in, line, pos);
                 el_read = true;
                 break;
             case '{':
-                element = from_stringstream_map(in);
+                element = from_stringstream_map(in, line, pos);
                 el_read = true;
                 break;
+            case EOF:
+                break;
             default:
-                // TODO this might not be a fatal error
-                // provide feedback mechanism?
-                cout << "error: unexpected '" << c << "'" << endl;
-                return ElementPtr();
+                throw ParseError(std::string("error: unexpected character ") + c, line, pos);
                 break;
         }
     }
     if (el_read) {
         return element;
     } else {
-        // throw exception?
-        return ElementPtr();
+        throw ParseError("nothing read");
     }
 }
 
@@ -574,7 +622,8 @@ decode_bool(std::stringstream& in, int& item_length)
 ElementPtr
 decode_int(std::stringstream& in, int& item_length)
 {
-    return from_stringstream_int_or_double(in);
+    int skip, me;
+    return from_stringstream_int_or_double(in, skip, me);
 }
 
 ElementPtr

+ 19 - 1
src/lib/cc/cpp/data.h

@@ -20,6 +20,17 @@ namespace ISC { namespace Data {
         std::string msg;
     };
 
+    class ParseError : public std::exception {
+    public:
+        ParseError(std::string m = "Parse error in element data", int l = 0, int p = 0) : msg(m), line(l), pos(p) {}
+        ~ParseError() throw() {}
+        const char* what() const throw();
+    private:
+        std::string msg;
+        int line;
+        int pos;
+    };
+
     class DecodeError : public std::exception {
     public:
         DecodeError(std::string m = "Wire-format data is invalid") : msg(m) {}
@@ -88,11 +99,13 @@ namespace ISC { namespace Data {
         virtual void set(const int i, ElementPtr element) { throw TypeError(); };
         virtual void add(ElementPtr element) { throw TypeError(); };
         virtual void remove(const int i) { throw TypeError(); };
+        virtual size_t size() { throw TypeError(); };
 
         // for maps
         virtual ElementPtr get(const std::string& name) { throw TypeError(); } ;
         virtual void set(const std::string& name, ElementPtr element) { throw TypeError(); };
         virtual void remove(const std::string& name) { throw TypeError(); };
+        virtual bool contains(const std::string& s) { throw TypeError(); }
         virtual ElementPtr find(const std::string& identifier) { throw TypeError(); };
         virtual bool find(const std::string& id, ElementPtr& t) { return false; };
 
@@ -145,8 +158,11 @@ namespace ISC { namespace Data {
         // the memory could not be allocated
         // example:
         // ElementPtr my_element = Element::create_from_string("{\"foo\": [ 1, 2, false ] }");
-        static ElementPtr create_from_string(std::stringstream& in);
+        //static ElementPtr create_from_string(std::stringstream& in);
         static ElementPtr create_from_string(const std::string& in);
+        static ElementPtr create_from_string(std::istream& in) throw(ParseError);
+        // make this one private?
+        static ElementPtr create_from_string(std::istream& in, int& line, int &pos) throw(ParseError);
         
         //static ElementPtr create_from_xml(std::stringstream& in);
 
@@ -222,6 +238,7 @@ namespace ISC { namespace Data {
         std::string str();
         std::string str_xml(size_t prefix = 0);
         std::string to_wire(int omit_length = 1);
+        size_t size() { return l.size(); }
     };
 
     class MapElement : public Element {
@@ -235,6 +252,7 @@ namespace ISC { namespace Data {
         ElementPtr get(const std::string& s) { return m[s]; };
         void set(const std::string& s, ElementPtr p) { m[s] = p; };
         void remove(const std::string& s) { m.erase(s); }
+        bool contains(const std::string& s) { return m.find(s) != m.end(); }
         std::string str();
         std::string str_xml(size_t prefix = 0);
         std::string to_wire(int omit_length = 1);

+ 255 - 0
src/lib/cc/cpp/data_def.cc

@@ -0,0 +1,255 @@
+
+#include "data_def.h"
+
+#include <sstream>
+
+#include <boost/foreach.hpp>
+
+// todo: add more context to thrown DataDefinitionErrors?
+
+using namespace ISC::Data;
+
+// todo: is there a direct way to get a std::string from an enum label?
+static std::string
+get_type_string(Element::types type)
+{
+    switch(type) {
+    case Element::integer:
+        return std::string("integer");
+    case Element::real:
+        return std::string("real");
+    case Element::boolean:
+        return std::string("boolean");
+    case Element::string:
+        return std::string("string");
+    case Element::list:
+        return std::string("list");
+    case Element::map:
+        return std::string("map");
+    default:
+        return std::string("unknown");
+    }
+}
+
+static Element::types
+get_type_value(const std::string& type_name) {
+    if (type_name == "integer") {
+        return Element::integer;
+    } else if (type_name == "real") {
+        return Element::real;
+    } else if (type_name == "boolean") {
+        return Element::boolean;
+    } else if (type_name == "string") {
+        return Element::string;
+    } else if (type_name == "list") {
+        return Element::list;
+    } else if (type_name == "map") {
+        return Element::map;
+    } else {
+        throw DataDefinitionError(type_name + " is not a valid type name");
+    }
+}
+
+static void
+check_leaf_item(const ElementPtr& spec, const std::string& name, Element::types type, bool mandatory)
+{
+    if (spec->contains(name)) {
+        if (spec->get(name)->get_type() == type) {
+            return;
+        } else {
+            throw DataDefinitionError(name + " not of type " + get_type_string(type));
+        }
+    } else if (mandatory) {
+        // todo: want parent item name, and perhaps some info about location
+        // in list? or just catch and throw new...
+        // or make this part non-throwing and check return value...
+        throw DataDefinitionError(name + " missing in " + spec->str());
+    }
+}
+
+static void check_config_item_list(const ElementPtr& spec);
+
+static void
+check_config_item(const ElementPtr& spec) {
+    check_leaf_item(spec, "item_name", Element::string, true);
+    check_leaf_item(spec, "item_type", Element::string, true);
+    check_leaf_item(spec, "item_optional", Element::boolean, true);
+    check_leaf_item(spec, "item_default",
+                    get_type_value(spec->get("item_type")->string_value()),
+                    !spec->get("item_optional")->bool_value()
+                   );
+
+    // if list, check the list definition
+    if (get_type_value(spec->get("item_type")->string_value()) == Element::list) {
+        check_leaf_item(spec, "list_item_spec", Element::map, true);
+        check_config_item(spec->get("list_item_spec"));
+    }
+    // todo: add stuff for type map
+    if (get_type_value(spec->get("item_type")->string_value()) == Element::map) {
+        check_leaf_item(spec, "map_item_spec", Element::list, true);
+        check_config_item_list(spec);
+    }
+}
+
+static void
+check_config_item_list(const ElementPtr& spec) {
+    if (spec->get_type() != Element::list) {
+        throw DataDefinitionError("config_data is not a list of elements");
+    }
+    BOOST_FOREACH(ElementPtr item, spec->list_value()) {
+        check_config_item(item);
+    }
+}
+
+static void
+check_command(const ElementPtr& spec) {
+    check_leaf_item(spec, "command_name", Element::string, true);
+    check_leaf_item(spec, "command_args", Element::list, true);
+    check_config_item_list(spec->get("command_args"));
+}
+
+static void
+check_command_list(const ElementPtr& spec) {
+    if (spec->get_type() != Element::list) {
+        throw DataDefinitionError("commands is not a list of elements");
+    }
+    BOOST_FOREACH(ElementPtr item, spec->list_value()) {
+        check_command(item);
+    }
+}
+
+static void
+check_data_specification(const ElementPtr& spec) {
+    check_leaf_item(spec, "module_name", Element::string, true);
+    // not mandatory; module could just define commands and have
+    // no config
+    if (spec->contains("config_data")) {
+        check_config_item_list(spec->get("config_data"));
+    }
+    if (spec->contains("commands")) {
+        check_command_list(spec->get("commands"));
+    }
+}
+
+// checks whether the given element is a valid data definition
+// throws a DataDefinitionError if the specification is bad
+static void
+check_definition(const ElementPtr& def)
+{
+    if (!def->contains("data_specification")) {
+        throw DataDefinitionError("Data specification does not contain data_specification element");
+    } else {
+        check_data_specification(def->get("data_specification"));
+    }    
+}
+
+DataDefinition::DataDefinition(std::istream& in, const bool check)
+                               throw(ParseError, DataDefinitionError) {
+    definition = Element::create_from_string(in);
+    // make sure the whole structure is complete and valid
+    if (check) {
+        check_definition(definition);
+    }
+}
+
+//
+// helper functions for validation
+//
+static bool
+check_type(ElementPtr spec, ElementPtr element)
+{
+    std::string cur_item_type;
+    cur_item_type = spec->get("item_type")->string_value();
+    if (cur_item_type == "any") {
+        return true;
+    }
+    switch (element->get_type()) {
+        case Element::integer:
+            return cur_item_type == "integer";
+            break;
+        case Element::real:
+            return cur_item_type == "real";
+            break;
+        case Element::boolean:
+            return cur_item_type == "boolean";
+            break;
+        case Element::string:
+            return cur_item_type == "string";
+            break;
+        case Element::list:
+            return cur_item_type == "list";
+            break;
+        case Element::map:
+            return cur_item_type == "map";
+            break;
+    }
+    return false;
+}
+
+bool
+DataDefinition::validate_item(const ElementPtr spec, const ElementPtr data) {
+    std::cout << "Validating type of " << data << std::endl;
+    if (!check_type(spec, data)) {
+        std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
+        std::cout << spec << std::endl;
+        return false;
+    }
+    if (data->get_type() == Element::list) {
+        BOOST_FOREACH(ElementPtr list_el, data->list_value()) {
+            if (!validate_spec(spec->get("list_item_spec"), list_el)) {
+                return false;
+            }
+        }
+    }
+    if (data->get_type() == Element::map) {
+        if (!validate_spec_list(spec->get("map_item_spec"), data)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// spec is a map with item_name etc, data is a map
+bool
+DataDefinition::validate_spec(const ElementPtr spec, const ElementPtr data) {
+    std::string item_name = spec->get("item_name")->string_value();
+    bool optional = spec->get("item_optional")->bool_value();
+    ElementPtr data_el;
+    
+    std::cout << "check for item with name " << item_name << std::endl;
+    data_el = data->get(item_name);
+    if (data_el) {
+        if (!validate_item(spec, data_el)) {
+            return false;
+        }
+    } else {
+        if (!optional) {
+            std::cout << "non-optional value not found" << std::endl;
+            return false;
+        }
+    }
+    return true;
+}
+
+// spec is a list of maps, data is a map
+bool
+DataDefinition::validate_spec_list(const ElementPtr spec, const ElementPtr data) {
+    ElementPtr cur_data_el;
+    std::string cur_item_name;
+    BOOST_FOREACH(ElementPtr cur_spec_el, spec->list_value()) {
+        if (!validate_spec(cur_spec_el, data)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// TODO
+// this function does *not* check if the specification is in correct
+// form, we should do that in the constructor
+bool
+DataDefinition::validate(const ElementPtr data) {
+    ElementPtr spec = definition->find("data_specification/config_data");
+    return validate_spec_list(spec, data);
+}
+

+ 42 - 0
src/lib/cc/cpp/data_def.h

@@ -0,0 +1,42 @@
+#ifndef _DATA_DEF_H
+#define _DATA_DEF_H 1
+
+#include "data.h"
+
+#include <sstream>
+
+namespace ISC { namespace Data {
+
+    class DataDefinitionError : public std::exception {
+    public:
+        DataDefinitionError(std::string m = "Data definition is invalid") : msg(m) {}
+        ~DataDefinitionError() throw() {}
+        const char* what() const throw() { return msg.c_str(); }
+    private:
+        std::string msg;
+    };
+
+    class DataDefinition {
+    public:
+        explicit DataDefinition() {};
+        explicit DataDefinition(ElementPtr e) : definition(e) {};
+        // todo: make check default false, or leave out option completely and always check?
+        explicit DataDefinition(std::istream& in, const bool check = true)
+                                throw(ParseError, DataDefinitionError);
+
+        const ElementPtr getDefinition() { return definition; };
+        // returns true if the given element conforms to this data
+        // definition scheme
+        bool validate(const ElementPtr data);
+
+    private:
+        bool validate_item(const ElementPtr spec, const ElementPtr data);
+        bool validate_spec(const ElementPtr spec, const ElementPtr data);
+        bool validate_spec_list(const ElementPtr spec, const ElementPtr data);
+
+        ElementPtr definition;
+    };
+
+} }
+
+#endif // _DATA_DEF_H

+ 12 - 0
src/lib/cc/cpp/parkinglot.data

@@ -0,0 +1,12 @@
+{
+  "port": 5300,
+  "zones": [
+    {
+      "zone_name": "tjeb.nl"
+    },
+    {
+      "zone_name": "jinmei.org"
+    }
+  ]
+}
+

+ 46 - 0
src/lib/cc/cpp/parkinglot.spec

@@ -0,0 +1,46 @@
+{
+  "data_specification": {
+    "module_name": "ParkingLot",
+    "config_data": [
+      {
+        "item_name": "port",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 5300
+      },
+      {
+        "item_name": "zones",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [ ],
+        "list_item_spec": {
+          "item_name": "zone_name",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        }
+      }
+    ],
+    "commands": [
+      {
+        "command_name": "zone_add",
+        "command_args": [ {
+          "item_name": "zone_name",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        } ]
+      },
+      {
+        "command_name": "zone_delete",
+        "command_args": [ {
+          "item_name": "zone_name",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": ""
+        } ]
+      }
+    ]
+  }
+}
+

+ 3 - 2
src/lib/cc/cpp/session.cc

@@ -226,7 +226,8 @@ Session::unsubscribe(std::string group, std::string instance)
 }
 
 unsigned int
-Session::group_sendmsg(ElementPtr& msg, std::string group, std::string instance, std::string to)
+Session::group_sendmsg(ElementPtr msg, std::string group,
+                       std::string instance, std::string to)
 {
     ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
 
@@ -265,7 +266,7 @@ Session::reply(ElementPtr& envelope, ElementPtr& newmsg)
     env->set("group", Element::create(envelope->get("group")->string_value()));
     env->set("instance", Element::create(envelope->get("instance")->string_value()));
     env->set("seq", Element::create(sequence));
-    env->set("reply", Element::create(envelope->get("seq")->string_value()));
+    env->set("reply", Element::create(envelope->get("seq")->int_value()));
 
     sendmsg(env, newmsg);
 

+ 3 - 3
src/lib/cc/cpp/session.h

@@ -30,8 +30,8 @@ namespace ISC {
 
             Session();
 
-	    // XXX: quick hack to allow the user to watch the socket directly.
-	    int getSocket() const { return (sock); }
+            // XXX: quick hack to allow the user to watch the socket directly.
+            int getSocket() const { return (sock); }
 
             void establish();
             void disconnect();
@@ -46,7 +46,7 @@ namespace ISC {
                            std::string instance = "*");
             void unsubscribe(std::string group,
                              std::string instance = "*");
-            unsigned int group_sendmsg(ISC::Data::ElementPtr& msg,
+            unsigned int group_sendmsg(ISC::Data::ElementPtr msg,
                                        std::string group,
                                        std::string instance = "*",
                                        std::string to = "*");

+ 56 - 0
src/lib/cc/cpp/test.cc

@@ -0,0 +1,56 @@
+
+#include "data.h"
+#include "data_def.h"
+
+#include <fstream>
+
+using namespace std;
+using namespace ISC::Data;
+
+int
+main(int argc, char **argv) {
+    std::ifstream file;
+    std::stringstream ss;
+    DataDefinition def;
+    ElementPtr data;
+
+    file.open("parkinglot.spec");
+    if (!file) {
+        cout << "error opening parkinglot.spec" << endl;
+        return 1;
+    }
+
+    try {
+        def = DataDefinition(file);
+        cout << "Definition: " << endl;
+        cout << def.getDefinition() << endl;
+    } catch (ParseError pe) {
+        cout << "Error parsing definition file: " << pe.what() << endl;
+        return 1;
+    } catch (DataDefinitionError dde) {
+        cout << "Error reading definition file: " << dde.what() << endl;
+        return 1;
+    }
+    file.close();
+
+    file.open("parkinglot.data");
+    if (!file) {
+        cout << "error opening parkinglot.data" << endl;
+        return 1;
+    }
+    try {
+        data = Element::create_from_string(file);
+        cout << "Data: " << endl;
+        cout << data << endl;
+    } catch (ParseError pe) {
+        cout << "Error parsing data file: " << pe.what() << endl;
+        return 1;
+    }
+
+    if (def.validate(data)) {
+        cout << "Data validated" << endl;
+    } else {
+        cout << "Error in data validation" << endl;
+    }
+    file.close();
+}

+ 371 - 0
src/lib/cc/python/ISC/CC/data.py

@@ -0,0 +1,371 @@
+# data, data_definition, config_data, module_config_data and ui_config_data classes
+# we might want to split these up :)
+import ast
+
+class DataNotFoundError(Exception): pass
+class DataTypeError(Exception): pass
+
+def merge(orig, new):
+    """Merges the contents of new into orig, think recursive update()
+       orig and new must both be dicts. If an element value is None in
+       new it will be removed in orig."""
+    for kn in new.keys():
+        if kn in orig:
+            if new[kn]:
+                if type(new[kn]) == dict:
+                    merge(orig[kn], new[kn])
+                else:
+                    orig[kn] = new[kn]
+            else:
+                orig.remove(kn)
+        else:
+            orig[kn] = new[kn]
+
+def find(element, identifier):
+    """Returns the subelement in the given data element, raises DataNotFoundError if not found"""
+    id_parts = identifier.split("/")
+    id_parts[:] = (value for value in id_parts if value != "")
+    cur_el = element
+    for id in id_parts:
+        if type(cur_el) == dict and id in cur_el.keys():
+            cur_el = cur_el[id]
+        else:
+            raise DataNotFoundError(identifier + " in " + str(element))
+    return cur_el
+
+def set(element, identifier, value):
+    id_parts = identifier.split("/")
+    id_parts[:] = (value for value in id_parts if value != "")
+    cur_el = element
+    for id in id_parts[:-1]:
+        if id in cur_el.keys():
+            cur_el = cur_el[id]
+        else:
+            cur_el[id] = {}
+            cur_el = cur_el[id]
+    cur_el[id_parts[-1]] = value
+    return element
+
+def unset(element, identifier):
+    id_parts = identifier.split("/")
+    id_parts[:] = (value for value in id_parts if value != "")
+    cur_el = element
+    for id in id_parts[:-1]:
+        if id in cur_el.keys():
+            cur_el = cur_el[id]
+        else:
+            cur_el[id] = {}
+            cur_el = cur_el[id]
+    cur_el[id_parts[-1]] = None
+    return element
+
+def find_no_exc(element, identifier):
+    """Returns the subelement in the given data element, returns None if not found"""
+    id_parts = identifier.split("/")
+    id_parts[:] = (value for value in id_parts if value != "")
+    cur_el = element
+    for id in id_parts:
+        if type(cur_el) == dict and id in cur_el.keys():
+            cur_el = cur_el[id]
+        else:
+            return None
+    return cur_el
+
+def find_spec(element, identifier):
+    """find the data definition for the given identifier
+       returns either a map with 'item_name' etc, or a list of those"""
+    id_parts = identifier.split("/")
+    id_parts[:] = (value for value in id_parts if value != "")
+    cur_el = element
+    for id in id_parts:
+        if type(cur_el) == dict and id in cur_el.keys():
+            cur_el = cur_el[id]
+        elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
+            pass
+        elif type(cur_el) == list:
+            found = False
+            for cur_el_item in cur_el:
+                if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys():
+                    cur_el = cur_el_item
+                    found = True
+            if not found:
+                raise DataNotFoundError(id + " in " + str(cur_el))
+        else:
+            raise DataNotFoundError(id + " in " + str(cur_el))
+    return cur_el
+
+def check_type(specification, value):
+    """Returns true if the value is of the correct type given the
+       specification"""
+    if type(specification) == list:
+        data_type = "list"
+    else:
+        data_type = specification['item_type']
+
+    if data_type == "integer" and type(value) != int:
+        raise DataTypeError(str(value) + " should be an integer")
+    elif data_type == "real" and type(value) != double:
+        raise DataTypeError(str(value) + " should be a real")
+    elif data_type == "boolean" and type(value) != boolean:
+        raise DataTypeError(str(value) + " should be a boolean")
+    elif data_type == "string" and type(value) != str:
+        raise DataTypeError(str(value) + " should be a string")
+    elif data_type == "list":
+        if type(value) != list:
+            raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__))
+        else:
+            # todo: check subtypes etc
+            for element in value:
+                check_type(specification['list_item_spec'], element)
+    elif data_type == "map" and type(value) != dict:
+        # todo: check subtypes etc
+        raise DataTypeError(str(value) + " should be a map")
+
+def spec_name_list(spec, prefix="", recurse=False):
+    """Returns a full list of all possible item identifiers in the
+       specification (part)"""
+    result = []
+    if prefix != "" and not prefix.endswith("/"):
+        prefix += "/"
+    if type(spec) == dict:
+        for name in spec:
+            result.append(prefix + name + "/")
+            if recurse:
+                result.extend(spec_name_list(spec[name],name, recurse))
+    elif type(spec) == list:
+        for list_el in spec:
+            if 'item_name' in list_el:
+                if list_el['item_type'] == dict:
+                    if recurse:
+                        result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
+                else:
+                    name = list_el['item_name']
+                    if list_el['item_type'] in ["list", "map"]:
+                        name += "/"
+                    result.append(name)
+
+    return result
+
+def parse_value_str(value_str):
+    try:
+        return ast.literal_eval(value_str)
+    except ValueError as ve:
+        # simply return the string itself
+        return value_str
+    except SyntaxError as ve:
+        # simply return the string itself
+        return value_str
+
+class ConfigData:
+    def __init__(self, specification):
+        self.specification = specification
+        self.data = {}
+
+    def get_item_list(self, identifier = None):
+        if identifier:
+            spec = find_spec(self.specification, identifier)
+            return spec_name_list(spec, identifier + "/")
+        return spec_name_list(self.specification)
+
+    def get_value(self, identifier):
+        """Returns a tuple where the first item is the value at the
+           given identifier, and the second item is a bool which is
+           true if the value is an unset default"""
+        value = find_no_exc(self.data, identifier)
+        if value:
+            return value, False
+        spec = find_spec(self.specification, identifier)
+        if spec and 'item_default' in spec:
+            return spec['item_default'], True
+        return None, False
+
+class UIConfigData():
+    def __init__(self, name, cc):
+        self.module_name = name
+        data_spec = self.get_data_specification(cc)
+        self.config = ConfigData(data_spec)
+        self.get_config_data(cc)
+        self.config_changes = {}
+    
+    def get_config_data(self, cc):
+        cc.group_sendmsg({ "command": ["get_config", self.module_name] }, "ConfigManager")
+        answer, env = cc.group_recvmsg(False)
+        if 'result' in answer.keys() and type(answer['result']) == list:
+            # TODO: with the new cc implementation, replace "1" by 1
+            if answer['result'][0] == "1":
+                # todo: exception
+                print("Error: " + str(answer['result'][1]))
+            else:
+                self.config.data = answer['result'][1]
+        else:
+            # XX todo: raise exc
+            print("Error: unexpected answer from config manager:")
+            print(answer)
+
+    def send_changes(self, cc):
+        """Sends the changes configuration values to the config manager.
+           If the command succeeds, the changes are re-requested and
+           the changed list is reset"""
+        if self.module_name and self.module_name != "":
+            cc.group_sendmsg({ "command": [ "set_config", self.module_name, self.config_changes ]}, "ConfigManager")
+        else:
+            cc.group_sendmsg({ "command": [ "set_config", self.config_changes ]}, "ConfigManager")
+        answer, env = cc.group_recvmsg(False)
+        if 'result' in answer and type(answer['result']) == list:
+            if answer['result'][0] == 0:
+                # ok
+                self.get_config_data(cc)
+                self.config_changes = {}
+            else:
+                print("Error committing changes: " + answer['result'][1])
+        else:
+            print("Error: unexpected answer: " + str(answer))
+    
+    def get_data_specification(self, cc):
+        cc.group_sendmsg({ "command": ["get_data_spec", self.module_name] }, "ConfigManager")
+        answer, env = cc.group_recvmsg(False)
+        if 'result' in answer.keys() and type(answer['result']) == list:
+            # TODO: with the new cc implementation, replace "1" by 1
+            if answer['result'][0] == "1":
+                # todo: exception
+                print("Error: " + str(answer['result'][1]))
+                return None
+            else:
+                return answer['result'][1]
+        else:
+            # XX todo: raise exc
+            print("Error: unexpected answer from config manager:")
+            print(answer)
+        return None
+
+    def set(self, identifier, value):
+        # check against definition
+        spec = find_spec(identifier)
+        check_type(spec, value)
+        set(self.config_changes, identifier, value)
+
+    def get_value(self, identifier):
+        """Returns a three-tuple, where the first item is the value
+           (or None), the second is a boolean specifying whether
+           the value is the default value, and the third is a boolean
+           specifying whether the value is an uncommitted change"""
+        value = find_no_exc(self.config_changes, identifier)
+        if value:
+            return value, False, True
+        value, default = self.config.get_value(identifier)
+        if value:
+            return value, default, False
+        return None, False, False
+
+    def get_value_map_single(self, identifier, entry):
+        """Returns a single entry for a value_map, where the value is
+           not a part of a bigger map"""
+        result_part = {}
+        result_part['name'] = entry['item_name']
+        result_part['type'] = entry['item_type']
+        value, default, modified = self.get_value(identifier)
+        # should we check type and only set int, double, bool and string here?
+        result_part['value'] = value
+        result_part['default'] = default
+        result_part['modified'] = modified
+        return result_part
+
+    def get_value_map(self, identifier, entry):
+        """Returns a single entry for a value_map, where the value is
+           a part of a bigger map"""
+        result_part = {}
+        result_part['name'] = entry['item_name']
+        result_part['type'] = entry['item_type']
+        value, default, modified = self.get_value(identifier + "/" + entry['item_name'])
+        # should we check type and only set int, double, bool and string here?
+        result_part['value'] = value
+        result_part['default'] = default
+        result_part['modified'] = modified
+        return result_part
+
+    def get_value_maps(self, identifier = None):
+        """Returns a list of maps, containing the following values:
+           name: name of the entry (string)
+           type: string containing the type of the value (or 'module')
+           value: value of the entry if it is a string, int, double or bool
+           modified: true if the value is a local change
+           default: true if the value has been changed
+           Throws DataNotFoundError if the identifier is bad
+        """
+        spec = find_spec(self.config.specification, identifier)
+        result = []
+        if type(spec) == dict:
+            # either the top-level list of modules or a spec map
+            if 'item_name' in spec:
+                result_part = self.get_value_map_single(identifier, spec)
+                if result_part['type'] == "list":
+                    values = self.get_value(identifier)[0]
+                    if values:
+                        for value in values:
+                            result_part2 = {}
+                            li_spec = spec['list_item_spec']
+                            result_part2['name'] = li_spec['item_name']
+                            result_part2['value'] = value
+                            result_part2['type'] = li_spec['item_type']
+                            result_part2['default'] = False
+                            result_part2['modified'] = False
+                            result.append(result_part2)
+                else:
+                    result.append(result_part)
+                
+            else:
+                for name in spec:
+                    result_part = {}
+                    result_part['name'] = name
+                    result_part['type'] = "module"
+                    result_part['value'] = None
+                    result_part['default'] = False
+                    result_part['modified'] = False
+                    result.append(result_part)
+        elif type(spec) == list:
+            for entry in spec:
+                if type(entry) == dict and 'item_name' in entry:
+                    result.append(self.get_value_map(identifier, entry))
+        return result
+
+    def add(self, identifier, value_str):
+        data_spec = find_spec(self.config.specification, identifier)
+        value = parse_value_str(value_str)
+        check_type(data_spec, [value])
+        cur_list = find_no_exc(self.config_changes, identifier)
+        if not cur_list:
+            cur_list = find_no_exc(self.config.data, identifier)
+        if not cur_list:
+            cur_list = []
+        if value not in cur_list:
+            cur_list.append(value)
+        set(self.config_changes, identifier, cur_list)
+
+    def remove(self, identifier, value_str):
+        data_spec = find_spec(self.config.specification, identifier)
+        value = parse_value_str(value_str)
+        check_type(data_spec, [value])
+        cur_list = find_no_exc(self.config_changes, identifier)
+        if not cur_list:
+            cur_list = find_no_exc(self.config.data, identifier)
+        if not cur_list:
+            cur_list = []
+        if value in cur_list:
+            cur_list.remove(value)
+        set(self.config_changes, identifier, cur_list)
+
+    def set(self, identifier, value_str):
+        data_spec = find_spec(self.config.specification, identifier)
+        value = parse_value_str(value_str)
+        check_type(data_spec, value)
+        set(self.config_changes, identifier, value)
+
+    def unset(self, identifier):
+        # todo: check whether the value is optional?
+        unset(self.config_changes, identifier)
+
+    def revert(self):
+        self.config_changes = {}
+
+    def commit(self, cc):
+        self.send_changes(cc)

+ 4 - 0
src/lib/cc/python/ISC/CC/session.py

@@ -39,6 +39,8 @@ class Session:
 
             self.sendmsg({ "type": "getlname" })
             env, msg = self.recvmsg(False)
+            if not env:
+                raise ProtocolError("Could not get local name")
             self._lname = msg["lname"]
             if not self._lname:
                 raise ProtocolError("Could not get local name")
@@ -55,6 +57,8 @@ class Session:
         self._closed = True
 
     def sendmsg(self, env, msg = None):
+        XXmsg = msg
+        XXenv = env
         if self._closed:
             raise SessionError("Session has been closed.")
         if type(env) == dict: