Browse Source

adapted command channel module with boost::asio. ugly hack, but works.

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jinmei-asio@1223 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 15 years ago
parent
commit
f8d7452f6e

+ 1 - 13
src/bin/auth/auth.spec

@@ -4,19 +4,7 @@
     "config_data": [
       { "item_name": "database_file",
         "item_type": "string",
-        "item_optional": False,
-        "item_default": "b10-auth.db"
-      },
-      { "item_name": "zone_list",
-        "item_type": "list",
-        "item_optional": False,
-        "item_default": [],
-        "list_item_spec":
-          { "item_name": "zone_name",
-            "item_type": "string",
-            "item_optional": True,
-            "item_default": ""
-          }
+        "item_optional": True
       }
     ],
     "commands": [

+ 43 - 14
src/bin/auth/auth_srv.cc

@@ -32,6 +32,8 @@
 #include <dns/rrttl.h>
 #include <dns/message.h>
 #include <config/ccsession.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
 
 #include <auth/query.h>
 #include <auth/data_source.h>
@@ -65,18 +67,17 @@ public:
 };
 
 AuthSrvImpl::AuthSrvImpl() {
-    // add static data source
-    data_sources.addDataSrc(ConstDataSrcPtr(new StaticDataSrc));
-
-    // add SQL data source
-    Sqlite3DataSrc* sd = new Sqlite3DataSrc;
-    sd->init();
-    data_sources.addDataSrc(ConstDataSrcPtr(sd));
 }
 
 AuthSrv::AuthSrv()
 {
     impl_ = new AuthSrvImpl;
+    // set empty (sqlite) data source, once ccsession is up
+    // the datasource will be set by the configuration setting
+    // (or the default one if none is set)
+    cur_datasrc_ = ConstDataSrcPtr();
+    // add static data source
+    impl_->data_sources.addDataSrc(ConstDataSrcPtr(new StaticDataSrc));
 }
 
 AuthSrv::~AuthSrv()
@@ -125,24 +126,52 @@ AuthSrv::processMessage(InputBuffer& request_buffer,
     return (0);
 }
 
-void
-AuthSrv::setDbFile(const std::string& db_file)
+ElementPtr
+AuthSrv::setDbFile(const isc::data::ElementPtr config)
 {
-    cout << "Change data source file, call our data source's function to now read " << db_file << endl;
-    impl_->_db_file = db_file;
+    if (config) {
+        impl_->_db_file = config->get("database_file")->stringValue();
+        cout << "[AuthSrv] Data source database file: " << impl_->_db_file << endl;
+    }
+
+    try {
+        // create SQL data source
+        // config may be empty here; in that case it will load the default
+        // database file
+        Sqlite3DataSrc* sd = new Sqlite3DataSrc;
+        sd->init(config);
+
+        if (cur_datasrc_) {
+            impl_->data_sources.removeDataSrc(cur_datasrc_);
+        }
+
+        ConstDataSrcPtr csd = ConstDataSrcPtr(sd);
+        impl_->data_sources.addDataSrc(csd);
+        cur_datasrc_ = csd;
+
+        return isc::config::createAnswer(0);
+    } catch (isc::Exception error) {
+        cout << "[AuthSrv] error: " << error.what() << endl;
+        return isc::config::createAnswer(1, error.what());
+    }
 }
 
 ElementPtr
 AuthSrv::updateConfig(isc::data::ElementPtr new_config)
 {
+    ElementPtr answer = isc::config::createAnswer(0);
     if (new_config) {
         // the ModuleCCSession has already checked if we have
         // the correct ElementPtr type as specified in our .spec file
         if (new_config->contains("database_file")) {
-            // We only get this if the value has actually changed.
-            setDbFile(new_config->get("database_file")->stringValue());
+            answer = setDbFile(new_config);
         }
     }
+
+    // if we have no sqlite3 data source, use the default
+    if (!cur_datasrc_) {
+        setDbFile(ElementPtr());
+    }
     
-    return isc::config::createAnswer(0);
+    return answer;
 }

+ 6 - 1
src/bin/auth/auth_srv.h

@@ -20,6 +20,7 @@
 #include <string>
 
 #include <cc/data.h>
+#include <auth/data_source.h>
 
 namespace isc {
 namespace dns {
@@ -50,10 +51,14 @@ public:
                        isc::dns::MessageRenderer& response_renderer,
                        bool udp_buffer);
     void serve(std::string zone_name);
-    void setDbFile(const std::string& db_file);
+    isc::data::ElementPtr setDbFile(const isc::data::ElementPtr config);
     isc::data::ElementPtr updateConfig(isc::data::ElementPtr config);
 private:
     AuthSrvImpl* impl_;
+    /// We keep a pointer to the currently running sqlite datasource
+    /// so that we can specifically remove that one should the database
+    /// file change
+    isc::auth::ConstDataSrcPtr cur_datasrc_;
 };
 
 #endif // __AUTH_SRV_H

+ 5 - 5
src/bin/auth/main.cc

@@ -283,15 +283,13 @@ usage() {
 ElementPtr
 my_config_handler(ElementPtr new_config)
 {
-    auth_server->updateConfig(new_config);
-    return createAnswer(0);
+    return auth_server->updateConfig(new_config);
 }
 
 ElementPtr
 my_command_handler(const string& command, const ElementPtr args) {
     ElementPtr answer = createAnswer(0);
 
-    cout << "[XX] Handle command: " << endl << command << endl;
     if (command == "print_message") 
     {
         cout << args << endl;
@@ -356,13 +354,15 @@ main(int argc, char* argv[]) {
         } else {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
-        ModuleCCSession cs = ModuleCCSession(specfile, my_config_handler,
-                                             my_command_handler);
 
         // XXX: in this prototype code we'll ignore any message on the command
         // channel.
 
         boost::asio::io_service io_service;
+
+        ModuleCCSession cs(specfile, io_service, my_config_handler,
+                           my_command_handler);
+
         if (use_ipv4) {
             udp4_server = new UDPServer(io_service, AF_INET, port);
             tcp4_server = new TCPServer(io_service, AF_INET, port);

+ 11 - 3
src/bin/loadzone/b10-loadzone.py.in

@@ -16,7 +16,8 @@
 
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import re, getopt
-import isc
+import isc.auth
+import isc.auth.master
 
 #########################################################################
 # usage: print usage note and exit
@@ -55,13 +56,20 @@ def main():
     zonefile = args[0]
 
     try:
-        zone, zonedata = isc.auth.master.parse(zonefile, initial_origin)
+        zf = isc.auth.master.openzone(zonefile, initial_origin)
     except Exception as e:
         print("Error reading zone file: " + str(e))
         exit(1)
 
     try:
-        isc.auth.sqlite3_ds.load(dbfile, zone, zonedata)
+        zone = isc.auth.master.zonename(zf, initial_origin)
+    except Exception as e:
+        print("Error reading zone file: " + str(e))
+        exit(1)
+
+    try:
+
+        isc.auth.sqlite3_ds.load(dbfile, zone, isc.auth.master.zonedata, zf)
     except Exception as e:
         print("Error loading database: " + str(e))
         exit(1)

+ 1 - 0
src/lib/auth/Makefile.am

@@ -23,6 +23,7 @@ run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD += .libs/libauth.a
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/.libs/libdns.a 
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.a 
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/.libs/libexceptions.a
 endif
 

+ 13 - 0
src/lib/auth/data_source.cc

@@ -782,6 +782,19 @@ MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src)
 }
 
 void
+MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src)
+{
+    std::vector<ConstDataSrcPtr>::iterator it, itr;
+    for (it = data_sources.begin(); it != data_sources.end(); it++) {
+        if (*it == data_src) {
+            itr = it;
+        }
+    }
+
+    data_sources.erase(itr);
+}
+
+void
 MetaDataSrc::findClosestEnclosure(NameMatch& match, const RRClass& qclass) const
 {
     if (qclass == RRClass::ANY()) {

+ 6 - 0
src/lib/auth/data_source.h

@@ -25,6 +25,7 @@
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
+#include <cc/data.h>
 
 namespace isc {
 
@@ -102,6 +103,7 @@ public:
     // Optional 'low-level' methods.  These will have stub implementations
     // in the general DataSrc class but MAY be overwritten by subclasses
     virtual Result init() = 0;
+    virtual Result init(const isc::data::ElementPtr config) = 0;
     virtual Result close() = 0;
 
     // Mandatory 'low-level' methods: These will NOT be implemented by
@@ -181,6 +183,7 @@ public:
     void setClass(const isc::dns::RRClass& c) { rrclass = c; }
 
     Result init() { return NOT_IMPLEMENTED; }
+    Result init(const isc::data::ElementPtr config) { return NOT_IMPLEMENTED; }
     Result close() { return NOT_IMPLEMENTED; }
 
     virtual Result findRRset(const Query& q,
@@ -245,6 +248,9 @@ public:
     //@}
 
     void addDataSrc(ConstDataSrcPtr data_src);
+    void removeDataSrc(ConstDataSrcPtr data_src);
+    size_t dataSrcCount() { return data_sources.size(); };
+    
     void findClosestEnclosure(NameMatch& match,
                               const isc::dns::RRClass& qclass) const;
 

+ 6 - 9
src/lib/auth/data_source_sqlite3.cc

@@ -487,18 +487,15 @@ Sqlite3DataSrc::~Sqlite3DataSrc() {
 }
 
 DataSrc::Result
-Sqlite3DataSrc::init(const string& dbfile) {
-    open(dbfile);
-    cerr << "Schema version: " << getVersion() << endl;
-
+Sqlite3DataSrc::init(const isc::data::ElementPtr config) {
+    if (config and config->contains("database_file")) {
+        open(config->get("database_file")->stringValue());
+    } else {
+        open(DEFAULT_DB_FILE);
+    }
     return (SUCCESS);
 }
 
-DataSrc::Result
-Sqlite3DataSrc::init() {
-    return (init(DEFAULT_DB_FILE));
-}
-
 void
 Sqlite3DataSrc::findClosestEnclosure(NameMatch& match,
                                      const RRClass& qclass) const {

+ 2 - 2
src/lib/auth/data_source_sqlite3.h

@@ -102,8 +102,8 @@ public:
                              std::string& hash,
                              isc::dns::RRsetList& target) const;
 
-    Result init();
-    Result init(const std::string& dbfile);
+    Result init() { return init(isc::data::ElementPtr()); };
+    Result init(const isc::data::ElementPtr config);
     Result close();
 
 private:

+ 9 - 3
src/lib/auth/data_source_sqlite3_unittest.cc

@@ -29,6 +29,7 @@
 #include <dns/rrtype.h>
 #include <dns/rdataclass.h>
 #include <dns/rrsetlist.h>
+#include <cc/data.h>
 
 #include "query.h"
 #include "data_source.h"
@@ -38,15 +39,19 @@ using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::auth;
+using namespace isc::data;
 
 namespace {
-static const char* SQLITE_DBFILE_EXAMPLE = "testdata/test.sqlite3";
-static const char* SQLITE_DBFILE_EXAMPLE2 = "testdata/test2.sqlite3";
+static ElementPtr SQLITE_DBFILE_EXAMPLE = Element::createFromString(
+                     "{ \"database_file\": \"testdata/test.sqlite3\"}");
+static ElementPtr SQLITE_DBFILE_EXAMPLE2 = Element::createFromString(
+                     "{ \"database_file\": \"testdata/test2.sqlite3\"}");
 // The following file must be non existent and mutt be "creatable";
 // the sqlite3 library will try to create a new DB file if it doesn't exist,
 // so to test a failure case the create operation should also fail.
 // The "nodir", a non existent directory, is inserted for this purpose.
-static const char* SQLITE_DBFILE_NOTEXIST = "testdata/nodir/notexist";
+static ElementPtr SQLITE_DBFILE_NOTEXIST = Element::createFromString(
+                     "{ \"database_file\": \"testdata/nodir/notexist\"}");
 
 static const string sigdata_common(" 20100322084538 20100220084538 "
                                    "33495 example.com. FAKEFAKEFAKEFAKE");
@@ -350,6 +355,7 @@ TEST_F(Sqlite3DataSourceTest, reOpen) {
     // Replace the data with a totally different zone.  This should succeed,
     // and shouldn't match any names in the previously managed domains.
     EXPECT_EQ(DataSrc::SUCCESS, data_source.close());
+
     EXPECT_EQ(DataSrc::SUCCESS, data_source.init(SQLITE_DBFILE_EXAMPLE2));
 
     NameMatch name_match(www_name);

+ 1 - 0
src/lib/auth/data_source_static.h

@@ -88,6 +88,7 @@ public:
                             isc::dns::RRsetList& target) const;
 
     Result init();
+    Result init(const isc::data::ElementPtr config) { return init(); };
     Result close();
 private:
     StaticDataSrcImpl* impl_;

+ 1 - 0
src/lib/auth/data_source_static_unittest.cc

@@ -27,6 +27,7 @@
 #include <dns/rrtype.h>
 #include <dns/rdataclass.h>
 #include <dns/rrsetlist.h>
+#include <cc/data.h>
 
 #include "query.h"
 #include "data_source.h"

+ 10 - 0
src/lib/auth/datasrc_unittest.cc

@@ -489,5 +489,15 @@ TEST_F(DataSrcTest, Nsec3Hash) {
     EXPECT_EQ("FHA27EURONFH5640SFJQ8MJAKMCVB7UJ", nsec3.getHash(Name("test2")));
     EXPECT_EQ("A4M93LR7A60IDDQMO6TCVUPCC60CU38A", nsec3.getHash(Name("test3")));
 }
+
+TEST_F(DataSrcTest, AddRemoveDataSrc) {
+    MetaDataSrc ds;
+    ConstDataSrcPtr tsp = ConstDataSrcPtr(new TestDataSrc);
+    EXPECT_EQ(0, ds.dataSrcCount());
+    ds.addDataSrc(tsp);
+    EXPECT_EQ(1, ds.dataSrcCount());
+    ds.removeDataSrc(tsp);
+    EXPECT_EQ(0, ds.dataSrcCount());
+}
 }
 

+ 6 - 0
src/lib/auth/unittest_ds.cc

@@ -96,6 +96,12 @@ RRsetPtr loop2_cname;
 }
 
 DataSrc::Result
+TestDataSrc::init(const isc::data::ElementPtr config)
+{
+    return init();
+}
+
+DataSrc::Result
 TestDataSrc::init() {
     if (initialized) {
         return (SUCCESS);

+ 1 - 0
src/lib/auth/unittest_ds.h

@@ -92,6 +92,7 @@ public:
                              isc::dns::RRsetList& target) const;
 
     Result init();
+    Result init(const isc::data::ElementPtr config);
     Result close() { return (SUCCESS); }
 
 private:

+ 289 - 117
src/lib/cc/session.cc

@@ -14,44 +14,204 @@
 
 // $Id$
 
-#include "data.h"
-#include "session.h"
+#include <stdint.h>
 
 #include <cstdio>
 #include <vector>
 #include <iostream>
 #include <sstream>
 
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/asio.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include "data.h"
+#include "session.h"
+
 using namespace std;
 using namespace isc::cc;
 using namespace isc::data;
 
+// some of the boost::asio names conflict with socket API system calls
+// (e.g. write(2)) so we don't import the entire boost::asio namespace.
+using boost::asio::io_service;
+using boost::asio::ip::tcp;
+
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 
-Session::Session()
-{
-    sock = -1;
-    sequence = 1;
+namespace isc {
+namespace cc {
+
+class SessionImpl {
+public:
+    SessionImpl() : sequence_(-1) {}
+    virtual ~SessionImpl() {}
+    virtual void establish() = 0; 
+    virtual int getSocket() = 0;
+    virtual void disconnect() = 0;
+    virtual void writeData(const void* data, size_t datalen) = 0;
+    virtual size_t readDataLength() = 0;
+    virtual void readData(void* data, size_t datalen) = 0;
+    virtual void startRead(boost::function<void()> user_handler) = 0;
+
+    int sequence_; // the next sequence number to use
+    std::string lname_;
+};
+
+class ASIOSession : public SessionImpl {
+public:
+    ASIOSession(io_service& io_service) :
+        io_service_(io_service), socket_(io_service_), data_length_(0)
+    {}
+    virtual void establish();
+    virtual void disconnect();
+    virtual int getSocket() { return (socket_.native()); }
+    virtual void writeData(const void* data, size_t datalen);
+    virtual size_t readDataLength();
+    virtual void readData(void* data, size_t datalen);
+    virtual void startRead(boost::function<void()> user_handler);
+private:
+    void internalRead(const boost::system::error_code& error,
+                      size_t bytes_transferred);
+
+private:
+    io_service& io_service_;
+    tcp::socket socket_;
+    uint32_t data_length_;
+    boost::function<void()> user_handler_;
+    boost::system::error_code error_;
+};
+
+void
+ASIOSession::establish() {
+    socket_.connect(tcp::endpoint(boost::asio::ip::address_v4::loopback(),
+                                  9912), error_);
+    if (error_) {
+        isc_throw(SessionError, "Unable to connect to message queue");
+    }
 }
 
 void
-Session::disconnect()
-{
-    close(sock);
-    sock = -1;
+ASIOSession::disconnect() {
+    socket_.close();
+    data_length_ = 0;
 }
 
 void
-Session::establish()
+ASIOSession::writeData(const void* data, size_t datalen) {
+    try {
+        boost::asio::write(socket_, boost::asio::buffer(data, datalen));
+    } catch (const boost::system::system_error& boost_ex) {
+        isc_throw(SessionError, "ASIO write failed: " << boost_ex.what());
+    }
+}
+
+size_t
+ASIOSession::readDataLength() {
+    size_t ret_len = data_length_;
+    
+    if (ret_len == 0) {
+        readData(&data_length_, sizeof(data_length_));
+        if (data_length_ == 0) {
+            isc_throw(SessionError, "ASIO read: data length is not ready");
+        }
+        ret_len = ntohl(data_length_);
+    }
+
+    data_length_ = 0;
+    return (ret_len);
+}
+
+void
+ASIOSession::readData(void* data, size_t datalen) {
+    try {
+        boost::asio::read(socket_, boost::asio::buffer(data, datalen));
+    } catch (const boost::system::system_error& boost_ex) {
+        // to hide boost specific exceptions, we catch them explicitly
+        // and convert it to SessionError.
+        isc_throw(SessionError, "ASIO read failed: " << boost_ex.what());
+    }
+}
+
+void
+ASIOSession::startRead(boost::function<void()> user_handler) {
+    data_length_ = 0;
+    user_handler_ = user_handler;
+    async_read(socket_, boost::asio::buffer(&data_length_,
+                                            sizeof(data_length_)),
+               boost::bind(&ASIOSession::internalRead, this,
+                           boost::asio::placeholders::error,
+                           boost::asio::placeholders::bytes_transferred));
+}
+
+void
+ASIOSession::internalRead(const boost::system::error_code& error,
+                          size_t bytes_transferred)
 {
-    int ret;
+    if (!error) {
+        assert(bytes_transferred == sizeof(data_length_));
+        data_length_ = ntohl(data_length_);
+        if (data_length_ == 0) {
+            isc_throw(SessionError, "Invalid message length (0)");
+        }
+        user_handler_();
+    } else {
+        isc_throw(SessionError, "asynchronous read failed");
+    }
+}
+
+class SocketSession : public SessionImpl {
+public:
+    SocketSession() : sock_(-1) {}
+    virtual ~SocketSession() { disconnect(); }
+    virtual int getSocket() { return (sock_); }
+    void establish();
+    virtual void disconnect()
+    {
+        if (sock_ >= 0) {
+            close(sock_);
+        }
+        sock_ = -1;
+    }
+    virtual void writeData(const void* data, size_t datalen);
+    virtual void readData(void* data, size_t datalen);
+    virtual size_t readDataLength();
+    virtual void startRead(boost::function<void()> user_handler)
+    {} // nothing to do for this class
+private:
+    int sock_;
+};
+
+namespace {                     // maybe unnecessary.
+// This is a helper class to make the establish() method (below) exception-safe
+// with the RAII approach.
+class SessionHolder {
+public:
+    SessionHolder(SessionImpl* obj) : impl_obj_(obj) {}
+    ~SessionHolder()
+    {
+        if (impl_obj_ != NULL) {
+            impl_obj_->disconnect();
+        }
+    }
+    void clear() { impl_obj_ = NULL; }
+    SessionImpl* impl_obj_;
+};
+}
+
+void
+SocketSession::establish() {
+    int s;
     struct sockaddr_in sin;
 
-    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-    if (sock < -1)
-        throw SessionError("socket() failed");
+    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (s < 0) {
+        isc_throw(SessionError, "socket() failed");
+    }
 
     sin.sin_family = AF_INET;
     sin.sin_port = htons(9912);
@@ -61,111 +221,138 @@ Session::establish()
     sin.sin_len = sizeof(struct sockaddr_in);
 #endif
 
-    ret = connect(sock, (struct sockaddr *)&sin, sizeof(sin));
-    if (ret < 0)
-        throw SessionError("Unable to connect to message queue");
+    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+        close(s);
+        isc_throw(SessionError, "Unable to connect to message queue");
+    }
+
+    sock_ = s;
+}
+
+void
+SocketSession::writeData(const void* data, const size_t datalen) {
+    int cc = write(sock_, data, datalen);
+    if (cc != datalen) {
+        isc_throw(SessionError, "Write failed: expect " << datalen <<
+                  ", actual " << cc);
+    }
+}
+
+size_t
+SocketSession::readDataLength() {
+    uint32_t length;
+    readData(&length, sizeof(length));
+    return (ntohl(length));
+}
+
+void
+SocketSession::readData(void* data, const size_t datalen) {
+    int cc = read(sock_, data, datalen);
+    if (cc != datalen) {
+        isc_throw(SessionError, "Read failed: expect " << datalen <<
+                  ", actual " << cc);
+    }
+}
+
+Session::Session() : impl_(new SocketSession)
+{}
+
+Session::Session(io_service& io_service) : impl_(new ASIOSession(io_service))
+{}
+
+Session::~Session() {
+    delete impl_;
+}
+
+void
+Session::disconnect() {
+    impl_->disconnect();
+}
+
+int
+Session::getSocket() const {
+    return (impl_->getSocket());
+}
+
+void
+Session::startRead(boost::function<void()> read_callback) {
+    impl_->startRead(read_callback);
+}
+
+void
+Session::establish() {
+    impl_->establish();
+
+    // once established, encapsulate the implementation object so that we
+    // can safely release the internal resource when exception happens
+    // below.
+    SessionHolder session_holder(impl_);
 
     //
     // send a request for our local name, and wait for a response
     //
-    std::string get_lname_str = "{ \"type\": \"getlname\" }";
-    std::stringstream get_lname_stream;
-    get_lname_stream.str(get_lname_str);
-    ElementPtr get_lname_msg = Element::createFromString(get_lname_stream);
+    ElementPtr get_lname_msg =
+        Element::createFromString("{ \"type\": \"getlname\" }");
     sendmsg(get_lname_msg);
 
     ElementPtr routing, msg;
     recvmsg(routing, msg, false);
 
-    lname = msg->get("lname")->stringValue();
-    cout << "My local name is:  " << lname << endl;
+    impl_->lname_ = msg->get("lname")->stringValue();
+    cout << "My local name is:  " << impl_->lname_ << endl;
+
+    // At this point there's no risk of resource leak.
+    session_holder.clear();
 }
 
 //
 // Convert to wire format and send this on the TCP stream with its length prefix
 //
 void
-Session::sendmsg(ElementPtr& msg)
-{
+Session::sendmsg(ElementPtr& msg) {
     std::string header_wire = msg->toWire();
     unsigned int length = 2 + header_wire.length();
     unsigned int length_net = htonl(length);
     unsigned short header_length = header_wire.length();
     unsigned short header_length_net = htons(header_length);
-    unsigned int ret;
-
-    ret = write(sock, &length_net, 4);
-    if (ret != 4)
-        throw SessionError("Short write");
-
-    ret = write(sock, &header_length_net, 2);
-    if (ret != 2)
-        throw SessionError("Short write");
 
-    ret = write(sock, header_wire.c_str(), header_length);
-    if (ret != header_length) {
-        throw SessionError("Short write");
-    }
+    impl_->writeData(&length_net, sizeof(length_net));
+    impl_->writeData(&header_length_net, sizeof(header_length_net));
+    impl_->writeData(header_wire.data(), header_length);
 }
 
 void
-Session::sendmsg(ElementPtr& env, ElementPtr& msg)
-{
+Session::sendmsg(ElementPtr& env, ElementPtr& msg) {
     std::string header_wire = env->toWire();
     std::string body_wire = msg->toWire();
     unsigned int length = 2 + header_wire.length() + body_wire.length();
     unsigned int length_net = htonl(length);
     unsigned short header_length = header_wire.length();
     unsigned short header_length_net = htons(header_length);
-    unsigned int ret;
-
-    ret = write(sock, &length_net, 4);
-    if (ret != 4)
-        throw SessionError("Short write");
 
-    ret = write(sock, &header_length_net, 2);
-    if (ret != 2)
-        throw SessionError("Short write");
-
-    ret = write(sock, header_wire.c_str(), header_length);
-    if (ret != header_length) {
-        throw SessionError("Short write");
-    }
-    ret = write(sock, body_wire.c_str(), body_wire.length());
-    if (ret != body_wire.length()) {
-        throw SessionError("Short write");
-    }
+    impl_->writeData(&length_net, sizeof(length_net));
+    impl_->writeData(&header_length_net, sizeof(header_length_net));
+    impl_->writeData(header_wire.data(), header_length);
+    impl_->writeData(body_wire.data(), body_wire.length());
 }
 
 bool
-Session::recvmsg(ElementPtr& msg, bool nonblock)
-{
-    unsigned int length_net;
-    unsigned short header_length_net;
-    unsigned int ret;
-
-    ret = read(sock, &length_net, 4);
-    if (ret != 4)
-        throw SessionError("Short read");
+Session::recvmsg(ElementPtr& msg, bool nonblock) {
+    size_t length = impl_->readDataLength();
 
-    ret = read(sock, &header_length_net, 2);
-    if (ret != 2)
-        throw SessionError("Short read");
+    unsigned short header_length_net;
+    impl_->readData(&header_length_net, sizeof(header_length_net));
 
-    unsigned int length = ntohl(length_net) - 2;
     unsigned short header_length = ntohs(header_length_net);
     if (header_length != length) {
-        throw SessionError("Received non-empty body where only a header expected");
+        isc_throw(SessionError, "Length parameters invalid: total=" << length
+                  << ", header=" << header_length);
     }
 
     std::vector<char> buffer(length);
-    ret = read(sock, &buffer[0], length);
-    if (ret != length) {
-        throw SessionError("Short read");
-    }
+    impl_->readData(&buffer[0], length);
 
     std::string wire = std::string(&buffer[0], length);
-
     std::stringstream wire_stream;
     wire_stream << wire;
 
@@ -176,36 +363,26 @@ Session::recvmsg(ElementPtr& msg, bool nonblock)
 }
 
 bool
-Session::recvmsg(ElementPtr& env, ElementPtr& msg, bool nonblock)
-{
-    unsigned int length_net;
-    unsigned short header_length_net;
-    unsigned int ret;
-
-    ret = read(sock, &length_net, 4);
-    if (ret != 4)
-        throw SessionError("Short read");
+Session::recvmsg(ElementPtr& env, ElementPtr& msg, bool nonblock) {
+    size_t length = impl_->readDataLength();
 
-    ret = read(sock, &header_length_net, 2);
-    if (ret != 2)
-        throw SessionError("Short read");
+    unsigned short header_length_net;
+    impl_->readData(&header_length_net, sizeof(header_length_net));
 
-    unsigned int length = ntohl(length_net);
     unsigned short header_length = ntohs(header_length_net);
-    if (header_length > length)
-        throw SessionError("Bad header length");
+    if (header_length > length || length < 2) {
+        isc_throw(SessionError, "Length parameters invalid: total=" << length
+                  << ", header=" << header_length);
+    }
 
     // remove the header-length bytes from the total length
     length -= 2;
     std::vector<char> buffer(length);
-    ret = read(sock, &buffer[0], length);
-    if (ret != length) {
-        throw SessionError("Short read");
-    }
+    impl_->readData(&buffer[0], length);
 
     std::string header_wire = std::string(&buffer[0], header_length);
-    std::string body_wire = std::string(&buffer[0] + header_length, length - header_length);
-
+    std::string body_wire = std::string(&buffer[0] + header_length,
+                                        length - header_length);
     std::stringstream header_wire_stream;
     header_wire_stream << header_wire;
     env = Element::fromWire(header_wire_stream, header_length);
@@ -219,8 +396,7 @@ Session::recvmsg(ElementPtr& env, ElementPtr& msg, bool nonblock)
 }
 
 void
-Session::subscribe(std::string group, std::string instance)
-{
+Session::subscribe(std::string group, std::string instance) {
     ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
 
     env->set("type", Element::create("subscribe"));
@@ -231,8 +407,7 @@ Session::subscribe(std::string group, std::string instance)
 }
 
 void
-Session::unsubscribe(std::string group, std::string instance)
-{
+Session::unsubscribe(std::string group, std::string instance) {
     ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
 
     env->set("type", Element::create("unsubscribe"));
@@ -249,43 +424,40 @@ Session::group_sendmsg(ElementPtr msg, std::string group,
     ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
 
     env->set("type", Element::create("send"));
-    env->set("from", Element::create(lname));
+    env->set("from", Element::create(impl_->lname_));
     env->set("to", Element::create(to));
     env->set("group", Element::create(group));
     env->set("instance", Element::create(instance));
-    env->set("seq", Element::create(sequence));
+    env->set("seq", Element::create(impl_->sequence_));
     //env->set("msg", Element::create(msg->toWire()));
 
     sendmsg(env, msg);
 
-    return (sequence++);
+    return (++impl_->sequence_);
 }
 
 bool
-Session::group_recvmsg(ElementPtr& envelope, ElementPtr& msg, bool nonblock)
+Session::group_recvmsg(ElementPtr& envelope, ElementPtr& msg,
+                       bool nonblock)
 {
-    bool got_message = recvmsg(envelope, msg, nonblock);
-    if (!got_message) {
-        return false;
-    }
-
-    return (true);
+    return (recvmsg(envelope, msg, nonblock));
 }
 
 unsigned int
-Session::reply(ElementPtr& envelope, ElementPtr& newmsg)
-{
+Session::reply(ElementPtr& envelope, ElementPtr& newmsg) {
     ElementPtr env = Element::create(std::map<std::string, ElementPtr>());
 
     env->set("type", Element::create("send"));
-    env->set("from", Element::create(lname));
+    env->set("from", Element::create(impl_->lname_));
     env->set("to", Element::create(envelope->get("from")->stringValue()));
     env->set("group", Element::create(envelope->get("group")->stringValue()));
     env->set("instance", Element::create(envelope->get("instance")->stringValue()));
-    env->set("seq", Element::create(sequence));
+    env->set("seq", Element::create(impl_->sequence_));
     env->set("reply", Element::create(envelope->get("seq")->intValue()));
 
     sendmsg(env, newmsg);
 
-    return (sequence++);
+    return (++impl_->sequence_);
+}
+}
 }

+ 27 - 14
src/lib/cc/session.h

@@ -18,39 +18,52 @@
 #define _ISC_SESSION_H 1
 
 #include <string>
-#include <vector>
-#include <map>
+
+#include <boost/function.hpp>
+
+#include <exceptions/exceptions.h>
 
 #include "data.h"
 
+namespace boost {
+namespace asio {
+class io_service;
+}
+}
+
 namespace isc {
     namespace cc {
-        class SessionError : public std::exception {
+        class SessionImpl;
+
+        class SessionError : public isc::Exception {
         public:
-            SessionError(std::string m = "CC Session Error") : msg(m) {}
-            ~SessionError() throw() {}
-            const char* what() const throw() { return msg.c_str(); }
-        private:
-            std::string msg;
+            SessionError(const char* file, size_t line, const char* what) :
+                isc::Exception(file, line, what) {}
         };
 
         class Session {
         private:
-            int sock;
-            int sequence; // the next sequence number to use
+            SessionImpl* impl_;
 
-        public:
-            std::string lname;
+        private:
+            Session(const Session& source);
+            Session& operator=(const Session& source);
 
+        public:
             Session();
+            Session(boost::asio::io_service& ioservice);
+            ~Session();
 
             // XXX: quick hack to allow the user to watch the socket directly.
-            int getSocket() const { return (sock); }
+            int getSocket() const;
+
+            void startRead(boost::function<void()> read_callback);
 
             void establish();
             void disconnect();
             void sendmsg(isc::data::ElementPtr& msg);
-            void sendmsg(isc::data::ElementPtr& env, isc::data::ElementPtr& msg);
+            void sendmsg(isc::data::ElementPtr& env,
+                         isc::data::ElementPtr& msg);
             bool recvmsg(isc::data::ElementPtr& msg,
                          bool nonblock = true);
             bool recvmsg(isc::data::ElementPtr& env,

+ 54 - 8
src/lib/config/ccsession.cc

@@ -31,6 +31,7 @@
 #include <sstream>
 #include <cerrno>
 
+#include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 
 #include <cc/data.h>
@@ -162,11 +163,48 @@ ModuleCCSession::read_module_specification(const std::string& filename) {
     file.close();
 }
 
-ModuleCCSession::ModuleCCSession(std::string spec_file_name,
-                               isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config),
-                               isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args)
-                              ) throw (isc::cc::SessionError):
-    session_(isc::cc::Session())
+void
+ModuleCCSession::startCheck() {
+    // data available on the command channel.  process it in the synchronous
+    // mode.
+    check_command();
+
+    // start asynchronous read again.
+    session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
+}
+
+ModuleCCSession::ModuleCCSession(
+    std::string spec_file_name,
+    boost::asio::io_service& io_service,
+    isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config),
+    isc::data::ElementPtr(*command_handler)(
+        const std::string& command, const isc::data::ElementPtr args)
+    ) throw (isc::cc::SessionError) :
+    session_(io_service)
+{
+    init(spec_file_name, config_handler, command_handler);
+
+    // register callback for asynchronous read
+    session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
+}
+
+ModuleCCSession::ModuleCCSession(
+    std::string spec_file_name,
+    isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config),
+    isc::data::ElementPtr(*command_handler)(
+        const std::string& command, const isc::data::ElementPtr args)
+    ) throw (isc::cc::SessionError)
+{
+    init(spec_file_name, config_handler, command_handler);
+}
+
+void
+ModuleCCSession::init(
+    std::string spec_file_name,
+    isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config),
+    isc::data::ElementPtr(*command_handler)(
+        const std::string& command, const isc::data::ElementPtr args)
+    ) throw (isc::cc::SessionError)
 {
     read_module_specification(spec_file_name);
     sleep(1);
@@ -189,16 +227,24 @@ ModuleCCSession::ModuleCCSession(std::string spec_file_name,
     ElementPtr spec_msg = createCommand("module_spec", module_specification_.getFullSpec());
     session_.group_sendmsg(spec_msg, "ConfigManager");
     session_.group_recvmsg(env, answer, false);
-
+    int rcode;
+    ElementPtr err = parseAnswer(rcode, answer);
+    if (rcode != 0) {
+        std::cerr << "[" << module_name_ << "] Error in specification: " << answer << std::endl;
+    }
+    
     config_ = Element::createFromString("{}");
     // get any stored configuration from the manager
     if (config_handler_) {
         ElementPtr cmd = Element::createFromString("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
         session_.group_sendmsg(cmd, "ConfigManager");
         session_.group_recvmsg(env, answer, false);
-        int rcode;
         ElementPtr new_config = parseAnswer(rcode, answer);
-        handleConfigUpdate(new_config);
+        if (rcode == 0) {
+            handleConfigUpdate(new_config);
+        } else {
+            std::cerr << "[" << module_name_ << "] Error getting config: " << new_config << std::endl;
+        }
     }
 }
 

+ 19 - 0
src/lib/config/ccsession.h

@@ -24,6 +24,12 @@
 #include <cc/session.h>
 #include <cc/data.h>
 
+namespace boost {
+namespace asio {
+class io_service;
+}
+}
+
 namespace isc {
 namespace config {
 
@@ -57,6 +63,11 @@ public:
                     isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config) = NULL,
                     isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args) = NULL
                     ) throw (isc::cc::SessionError);
+    ModuleCCSession(std::string spec_file_name,
+                    boost::asio::io_service& io_service,
+                    isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config) = NULL,
+                    isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args) = NULL
+                    ) throw (isc::cc::SessionError);
     int getSocket();
 
     /**
@@ -91,7 +102,15 @@ public:
     void set_command_handler(isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args)) { command_handler_ = command_handler; };
 
 private:
+    void init(
+        std::string spec_file_name,
+        isc::data::ElementPtr(*config_handler)(
+            isc::data::ElementPtr new_config),
+        isc::data::ElementPtr(*command_handler)(
+            const std::string& command, const isc::data::ElementPtr args)
+        ) throw (isc::cc::SessionError);
     void read_module_specification(const std::string& filename);
+    void startCheck();
     
     std::string module_name_;
     isc::cc::Session session_;

+ 80 - 0
src/lib/dns/rdata/generic/ptr_12.cc

@@ -0,0 +1,80 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <string>
+
+#include "buffer.h"
+#include "name.h"
+#include "messagerenderer.h"
+#include "rdata.h"
+#include "rdataclass.h"
+
+using namespace std;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+PTR::PTR(const string& type_str) :
+    ptr_name_(type_str)
+{}
+
+PTR::PTR(InputBuffer& buffer, size_t rdata_len) :
+    ptr_name_(buffer)
+{
+    // we don't need rdata_len for parsing.  if necessary, the caller will
+    // check consistency.
+}
+
+PTR::PTR(const PTR& source) :
+    ptr_name_(source.ptr_name_)
+{}
+
+std::string
+PTR::toText() const
+{
+    return (ptr_name_.toText());
+}
+
+void
+PTR::toWire(OutputBuffer& buffer) const
+{
+    ptr_name_.toWire(buffer);
+}
+
+void
+PTR::toWire(MessageRenderer& renderer) const
+{
+    renderer.writeName(ptr_name_);
+}
+
+int
+PTR::compare(const Rdata& other) const
+{
+    // The compare method normally begins with this dynamic cast.
+    const PTR& other_ptr = dynamic_cast<const PTR&>(other);
+
+    return (compareNames(ptr_name_, other_ptr.ptr_name_));
+
+}
+
+const Name&
+PTR::getPTRName() const
+{
+    return (ptr_name_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE

+ 54 - 0
src/lib/dns/rdata/generic/ptr_12.h

@@ -0,0 +1,54 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include "name.h"
+#include "rdata.h"
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class PTR : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    ///
+    /// Specialized constructor
+    ///
+    explicit PTR(const Name& ptr_name) : ptr_name_(ptr_name) {}
+    ///
+    /// Specialized methods
+    ///
+    const Name& getPTRName() const;
+private:
+    Name ptr_name_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 1 - 1
src/lib/dns/tests/Makefile.am

@@ -15,7 +15,7 @@ run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_unittest.cc rdata_mx_unittest.cc
-run_unittests_SOURCES += rdata_cname_unittest.cc
+run_unittests_SOURCES += rdata_ptr_unittest.cc rdata_cname_unittest.cc
 run_unittests_SOURCES += rdata_dname_unittest.cc
 run_unittests_SOURCES += rdata_opt_unittest.cc
 run_unittests_SOURCES += rdata_dnskey_unittest.cc

+ 132 - 0
src/lib/dns/tests/rdata_ptr_unittest.cc

@@ -0,0 +1,132 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <dns/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include "unittest_util.h"
+#include "rdata_unittest.h"
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+//
+// This test currently simply copies the NS RDATA tests.
+//
+
+namespace {
+class Rdata_PTR_Test : public RdataTest {
+    // there's nothing to specialize
+};
+
+const generic::PTR rdata_ptr("ns.example.com");
+const generic::PTR rdata_ptr2("ns2.example.com");
+const uint8_t wiredata_ptr[] = {
+    0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+    0x63, 0x6f, 0x6d, 0x00 };
+const uint8_t wiredata_ptr2[] = {
+    // first name: ns.example.com.
+    0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+    0x63, 0x6f, 0x6d, 0x00,
+    // second name: ns2.example.com.  all labels except the first should be
+    // compressed.
+    0x03, 0x6e, 0x73, 0x32, 0xc0, 0x03 };
+
+TEST_F(Rdata_PTR_Test, createFromText)
+{
+    EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("ns.example.com")));
+    // explicitly add a trailing dot.  should be the same RDATA.
+    EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("ns.example.com.")));
+    // should be case sensitive.
+    EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("NS.EXAMPLE.COM")));
+    // RDATA of a class-independent type should be recognized for any
+    // "unknown" class.
+    EXPECT_EQ(0, rdata_ptr.compare(*createRdata(RRType("PTR"), RRClass(65000),
+                                               "ns.example.com")));
+}
+
+TEST_F(Rdata_PTR_Test, createFromWire)
+{
+    EXPECT_EQ(0, rdata_ptr.compare(
+                  *rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+                                        "testdata/rdata_ns_fromWire")));
+    // RDLENGTH is too short
+    EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+                                      "testdata/rdata_ns_fromWire", 18),
+                 InvalidRdataLength);
+    // RDLENGTH is too long
+    EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+                                      "testdata/rdata_ns_fromWire", 36),
+                 InvalidRdataLength);
+    // incomplete name.  the error should be detected in the name constructor
+    EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+                                      "testdata/rdata_ns_fromWire", 71),
+                 IncompleteName);
+
+    EXPECT_EQ(0, generic::PTR("ns2.example.com").compare(
+                  *rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+                                        "testdata/rdata_ns_fromWire", 55)));
+    EXPECT_THROW(*rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+                                       "testdata/rdata_ns_fromWire", 63),
+                 InvalidRdataLength);
+}
+
+TEST_F(Rdata_PTR_Test, toWireBuffer)
+{
+    rdata_ptr.toWire(obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        obuffer.getData(), obuffer.getLength(),
+                        wiredata_ptr, sizeof(wiredata_ptr));
+}
+
+TEST_F(Rdata_PTR_Test, toWireRenderer)
+{
+    rdata_ptr.toWire(renderer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        obuffer.getData(), obuffer.getLength(),
+                        wiredata_ptr, sizeof(wiredata_ptr));
+    rdata_ptr2.toWire(renderer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        obuffer.getData(), obuffer.getLength(),
+                        wiredata_ptr2, sizeof(wiredata_ptr2));
+}
+
+TEST_F(Rdata_PTR_Test, toText)
+{
+    EXPECT_EQ("ns.example.com.", rdata_ptr.toText());
+}
+
+TEST_F(Rdata_PTR_Test, compare)
+{
+    generic::PTR small("a.example");
+    generic::PTR large("example");
+    EXPECT_EQ(true, Name("a.example") > Name("example"));
+    EXPECT_GT(0, small.compare(large));
+}
+
+TEST_F(Rdata_PTR_Test, getPTRName)
+{
+    EXPECT_EQ(Name("ns.example.com"), rdata_ptr.getPTRName());
+}
+}

+ 46 - 36
src/lib/python/isc/auth/master.py

@@ -48,15 +48,15 @@ def cleanup(s):
 # records: generator function to return complete RRs from the zone file,
 # combining lines when necessary because of parentheses
 # input:
-#   zonedata as an array of lines
+#   descriptor for a zone master file (returned from openzone)
 # yields:
 #   complete RR
 #########################################################################
-def records(data):
+def records(input):
     record = []
     complete = True
     paren = 0
-    for line in data:
+    for line in input:
         list = cleanup(line).split()
         for word in list:
             if paren == 0:
@@ -230,7 +230,7 @@ def include(s):
         m = filename.match(rest)
         if m:
             file = m.group(1)
-            return parse(file)
+            return file
 
 #########################################################################
 # four: try parsing on the assumption that the RR type is specified in
@@ -339,28 +339,37 @@ def reset():
     origin=''
 
 #########################################################################
-# do_parse: parse a zone master file and return it as an array of
-# tuples
+# openzone: open a zone master file, set initial origin, return descriptor
 #########################################################################
-def do_parse(file, initial_origin = '.'):
-    global defttl, origin, defclass
+def openzone(filename, initial_origin = '.'):
+    try:
+        zf = open(filename, 'r')
+    except:
+        return
+    origin = initial_origin
+    return zf
 
-    if not origin:
-        origin = initial_origin
+#########################################################################
+# zonedata: generator function to parse a zone master file and return
+# each RR as a (name, ttl, type, class, rdata) tuple
+#########################################################################
+def zonedata(zone):
+    global defttl, origin, defclass
 
-    zone = []
     name = ''
 
-    data = open(file).read().splitlines()
-    for record in records(data):
+    for record in records(zone):
         if directive(record):
             continue
 
         incl = include(record)
         if incl:
-            zone += incl
+            sub = openzone(incl, origin)
+            for name, ttl, rrclass, rrtype, rdata in zonedata(sub):
+                yield (name, ttl, rrclass, rrtype, rdata)
+            sub.close()
             continue
-    
+
         first = record.split()[0]
         if first == '@':
             name = origin
@@ -399,46 +408,47 @@ def do_parse(file, initial_origin = '.'):
         if (ttl == -1):
             raise MasterFileError("No TTL specified; zone rejected")
 
-        zone.append((name, ttl, rrclass, rrtype, rdata))
-
-    return zone
+        yield (name, ttl, rrclass, rrtype, rdata)
 
 #########################################################################
-# parse: call do_parse on behalf of a caller; determine the zone name
-# and return the zone data
-# input:
-#   filename
-# returns:
-#   zonename, data
+# zonename: scans zone data for an SOA record, returns its name, restores
+# the zone file to its prior state
 #########################################################################
-def parse(file, initial_origin = '.'):
-    zone = do_parse(file, initial_origin)
-    zonename = ''
-    for record in zone:
-        if record[3].lower() == 'soa':
-            zonename = record[0]
-    if not zonename:
-        raise MasterFileError("No SOA; zone rejected")
-    return zonename, zone
+def zonename(zone, initial_origin = '.'):
+    global origin
+    old_origin = origin
+    origin = initial_origin
+    old_location = zone.tell()
+    zone.seek(0)
+    for name, ttl, rrclass, rrtype, rdata in zonedata(zone):
+        if rrtype.lower() == 'soa':
+            break
+    zone.seek(old_location)
+    origin = old_origin
+    if rrtype.lower() != 'soa':
+        raise MasterFileError("No SOA found")
+    return name
 
 #########################################################################
 # main: used for testing; parse a zone file and print out each record
 # broken up into separate name, ttl, class, type, and rdata files
 #########################################################################
 def main():
-    print ('---------------------')
     try:
         file = sys.argv[1]
     except:
         file = 'testfile'
-    zone = parse(file)
-    for name, ttl, rrclass, rrtype, rdata in zone:
+    zf = openzone(file, '.')
+    print ('zone name: ' + zonename(zf))
+    print ('---------------------')
+    for name, ttl, rrclass, rrtype, rdata in zonedata(zf):
         print ('name: ' + name)
         print ('ttl: ' + str(ttl))
         print ('rrclass: ' + rrclass)
         print ('rrtype: ' + rrtype)
         print ('rdata: ' + rdata)
         print ('---------------------')
+    zf.close()
 
 # initialize
 reset()

+ 3 - 3
src/lib/python/isc/auth/sqlite3_ds.py

@@ -119,9 +119,9 @@ def reverse_name(name):
 # input:
 #   dbfile: the sqlite3 database fileanme
 #   zone: the zone origin
-#   zonedata: an array of name/ttl/class/rrtype/rdata-text tuples
+#   zonedata: an iterable set of name/ttl/class/rrtype/rdata-text tuples
 #########################################################################
-def load(dbfile, zone, zonedata):
+def load(dbfile, zone, reader, file):
     conn, cur = open(dbfile)
     old_zone_id = get_zoneid(zone, cur)
 
@@ -129,7 +129,7 @@ def load(dbfile, zone, zonedata):
     cur.execute("INSERT INTO zones (name, rdclass) VALUES (?, 'IN')", [temp])
     new_zone_id = cur.lastrowid
 
-    for name, ttl, rdclass, rdtype, rdata in zonedata:
+    for name, ttl, rdclass, rdtype, rdata in reader(file):
         sigtype = ''
         if rdtype.lower() == 'rrsig':
             sigtype = rdata.split()[0]

+ 3 - 3
src/lib/python/isc/cc/message.py

@@ -113,9 +113,9 @@ def _encode_item(item):
 def _encode_bool(item):
     """Encode a boolean value into a bytearray of one byte (0=false)"""
     if item:
-        return b'\x01'
+        return b'1'
     else:
-        return b'\x00'
+        return b'0'
 
 def _encode_array(item):
     """Encode an array, where each value is encoded recursively"""
@@ -203,7 +203,7 @@ def _decode_item(data):
     return (value, data)
 
 def _decode_bool(data):
-    return data == b'0x01'
+    return data == b'1' or data == b'0x01'
 
 def _decode_int(data):
     return int(str(data, 'utf-8'))

+ 59 - 1
src/lib/python/isc/config/ccsession.py

@@ -148,6 +148,8 @@ class ModuleCCSession(ConfigData):
             self._session = cc_session
         self._session.group_subscribe(self._module_name, "*")
 
+        self._remote_module_configs = {}
+
     def start(self):
         """Send the specification for this module to the configuration
            manager, and request the current non-default configuration.
@@ -183,6 +185,21 @@ class ModuleCCSession(ConfigData):
                 cmd, arg = isc.config.ccsession.parse_command(msg)
                 if cmd == COMMAND_CONFIG_UPDATE:
                     new_config = arg
+                    module_name = env['group']
+                    # If the target channel was not this module
+                    # it might be in the remote_module_configs
+                    if module_name != self._module_name:
+                        if module_name in self._remote_module_configs:
+                            # no checking for validity, that's up to the
+                            # module itself.
+                            newc = self._remote_module_configs[module_name].get_local_config()
+                            isc.cc.data.merge(newc, new_config)
+                            self._remote_module_configs[module_name].set_local_config(newc)
+                            print("[XX] updated remote config value: ")
+                            print(newc)
+                            return
+
+                    # ok, so apparently this update is for us.
                     errors = []
                     if not self._config_handler:
                         answer = create_answer(2, self._module_name + " has no config handler")
@@ -202,7 +219,6 @@ class ModuleCCSession(ConfigData):
                     else:
                         answer = create_answer(2, self._module_name + " has no command handler")
             except Exception as exc:
-                print("error! " + str(exc))
                 answer = create_answer(1, str(exc))
                 raise exc
             if answer:
@@ -221,6 +237,47 @@ class ModuleCCSession(ConfigData):
            and return an answer created with create_answer()"""
         self._command_handler = command_handler
 
+    def add_remote_config(self, spec_file_name):
+        """Gives access to the configuration of a different module.
+           These remote module options can at this moment only be
+           accessed through get_remote_config_value(). This function
+           also subscribes to the channel of the remote module name
+           to receive the relevant updates. It is not possible to
+           specify your own handler for this right now.
+           Returns the name of the module."""
+        module_spec = isc.config.module_spec_from_file(spec_file_name)
+        module_cfg = ConfigData(module_spec)
+        module_name = module_spec.get_module_name()
+        self._session.group_subscribe(module_name);
+
+        # Get the current config for that module now
+        self._session.group_sendmsg({ "command": [ "get_config", { "module_name": module_name } ] }, "ConfigManager")
+        answer, env = self._session.group_recvmsg(False)
+        if answer:
+            rcode, value = parse_answer(answer)
+            if rcode == 0:
+                if value != None and self.get_module_spec().validate_config(False, value):
+                    module_cfg.set_local_config(value);
+
+        # all done, add it
+        self._remote_module_configs[module_name] = module_cfg
+        return module_name
+        
+    def remove_remote_config(self, module_name):
+        """Removes the remote configuration access for this module"""
+        if module_name in self._remote_module_configs:
+            del self._remote_module_configs[module_name]
+
+    def get_remote_config_value(self, module_name, identifier):
+        """Returns the current setting for the given identifier at the
+           given module. If the module has not been added with
+           add_remote_config, a ModuleCCSessionError is raised"""
+        if module_name in self._remote_module_configs:
+            return self._remote_module_configs[module_name].get_value(identifier)
+        else:
+            raise ModuleCCSessionError("Remote module " + module_name +
+                                       " not found")
+
     def __send_spec(self):
         """Sends the data specification to the configuration manager"""
         msg = create_command(COMMAND_MODULE_SPEC, self.get_module_spec().get_full_spec())
@@ -245,6 +302,7 @@ class ModuleCCSession(ConfigData):
         else:
             raise ModuleCCSessionError("No answer from configuration manager")
 
+
 class UIModuleCCSession(MultiConfigData):
     """This class is used in a configuration user interface. It contains
        specific functions for getting, displaying, and sending

+ 0 - 1
src/lib/python/isc/config/cfgmgr.py

@@ -287,7 +287,6 @@ class ConfigManager:
                 self.config.data = old_data
                 answer = isc.config.ccsession.create_answer(1, " ".join(err_list))
         else:
-            print(cmd)
             answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments")
         if not answer:
             answer = isc.config.ccsession.create_answer(1, "No answer message from " + cmd[0])

+ 17 - 0
src/lib/python/isc/config/unittests/ccsession_test.py

@@ -343,6 +343,23 @@ class TestModuleCCSession(unittest.TestCase):
         mccs.check_command()
         self.assertEqual(len(fake_session.message_queue), 0)
 
+    def test_remote_module(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        mccs.remove_remote_config("Spec2")
+
+        self.assertRaises(ModuleCCSessionError, mccs.get_remote_config_value, "Spec2", "item1")
+
+        rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+        self.assertEqual("Spec2", rmodname)
+        self.assertRaises(isc.cc.data.DataNotFoundError, mccs.get_remote_config_value, rmodname, "asdf")
+        value, default = mccs.get_remote_config_value(rmodname, "item1")
+        self.assertEqual(1, value)
+        self.assertEqual(True, default)
+
+        mccs.remove_remote_config(rmodname)
+        self.assertRaises(ModuleCCSessionError, mccs.get_remote_config_value, "Spec2", "item1")
+    
 class fakeUIConn():
     def __init__(self):
         self.get_answers = {}