--- /dev/null
+// Copyright (C) 2012 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.
+
+#include <exceptions/exceptions.h>
+
+#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/rdata_serialization.h>
+#include <datasrc/memory/rdataset.h>
+
+#include <util/memory_segment_local.h>
+
+#include <datasrc/rbtree.h>
+#include <datasrc/rbnode_rrset.h>
+#include <datasrc/logger.h>
+#include <datasrc/iterator.h>
+#include <datasrc/data_source.h>
+#include <datasrc/factory.h>
+#include <datasrc/result.h>
+
+#include <dns/name.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrsetlist.h>
+#include <dns/masterload.h>
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+#include <cctype>
+#include <cassert>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc::memory;
+using boost::scoped_ptr;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace {
+// Some type aliases
+
+// A functor type used for loading.
+typedef boost::function<void(ConstRRsetPtr)> LoadCallback;
+
+} // end of anonymous namespace
+
+/// Implementation details for \c InMemoryClient hidden from the public
+/// interface.
+///
+/// For now, \c InMemoryClient only contains a \c ZoneTable object, which
+/// consists of (pointers to) \c InMemoryZoneFinder objects, we may add more
+/// member variables later for new features.
+class InMemoryClient::InMemoryClientImpl {
+public:
+ InMemoryClientImpl(RRClass rrclass) :
+ rrclass_(rrclass),
+ zone_count(0),
+ zone_table_(ZoneTable::create(local_mem_sgmt, rrclass))
+ {}
+ ~InMemoryClientImpl() {
+ ZoneTable::destroy(local_mem_sgmt, zone_table_, rrclass_);
+
+ // see above for the assert().
+ assert(local_mem_sgmt.allMemoryDeallocated());
+ }
+
+ // Memory segment to allocate/deallocate memory for the zone table.
+ // (This will eventually have to be abstract; for now we hardcode the
+ // specific derived segment class).
+ util::MemorySegmentLocal local_mem_sgmt;
+ RRClass rrclass_;
+ unsigned int zone_count;
+ ZoneTable* zone_table_;
+
+ // Common process for zone load.
+ // rrset_installer is a functor that takes another functor as an argument,
+ // and expected to call the latter for each RRset of the zone. How the
+ // sequence of the RRsets is generated depends on the internal
+ // details of the loader: either from a textual master file or from
+ // another data source.
+ // filename is the file name of the master file or empty if the zone is
+ // loaded from another data source.
+ result::Result load(const Name& zone_name, const string& filename,
+ boost::function<void(LoadCallback)> rrset_installer);
+
+ // Add the necessary magic for any wildcard contained in 'name'
+ // (including itself) to be found in the zone.
+ //
+ // In order for wildcard matching to work correctly in find(),
+ // we must ensure that a node for the wildcarding level exists in the
+ // backend RBTree.
+ // E.g. if the wildcard name is "*.sub.example." then we must ensure
+ // that "sub.example." exists and is marked as a wildcard level.
+ // Note: the "wildcarding level" is for the parent name of the wildcard
+ // name (such as "sub.example.").
+ //
+ // We also perform the same trick for empty wild card names possibly
+ // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
+ void addWildcards(util::MemorySegment& mem_sgmt, ZoneTree& domains,
+ const Name& name)
+ {
+ Name wname(name);
+ const unsigned int labels(wname.getLabelCount());
+ const unsigned int origin_labels(origin_.getLabelCount());
+ for (unsigned int l = labels;
+ l > origin_labels;
+ --l, wname = wname.split(1)) {
+ if (wname.isWildcard()) {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_WILDCARD).
+ arg(name);
+ // Ensure a separate level exists for the "wildcarding" name,
+ // and mark the node as "wild".
+ DomainNode* node;
+ DomainTree::Result result(domains.insert(mem_sgmt,
+ wname.split(1),
+ &node));
+ assert(result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS);
+ node->setFlag(domain_flag::WILD);
+
+ // Ensure a separate level exists for the wildcard name.
+ // Note: for 'name' itself we do this later anyway, but the
+ // overhead should be marginal because wildcard names should
+ // be rare.
+ result = domains.insert(mem_sgmt, wname, &node);
+ assert(result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS);
+ }
+ }
+ }
+
+ // A helper predicate used in contextCheck() to check if a given domain
+ // name has a RRset of type different than NSEC.
+ static bool isNotNSEC(const DomainPair& element) {
+ return (element.second->getType() != RRType::NSEC());
+ }
+
+ /*
+ * Does some checks in context of the data that are already in the zone.
+ * Currently checks for forbidden combinations of RRsets in the same
+ * domain (CNAME+anything, DNAME+NS).
+ *
+ * If such condition is found, it throws AddError.
+ */
+ void contextCheck(const AbstractRRset& rrset, const Domain& domain) const {
+ // Ensure CNAME and other type of RR don't coexist for the same
+ // owner name except with NSEC, which is the only RR that can coexist
+ // with CNAME (and also RRSIG, which is handled separately)
+ if (rrset.getType() == RRType::CNAME()) {
+ if (find_if(domain.begin(), domain.end(), isNotNSEC)
+ != domain.end()) {
+ LOG_ERROR(logger, DATASRC_MEM_CNAME_TO_NONEMPTY).
+ arg(rrset.getName());
+ isc_throw(AddError, "CNAME can't be added with other data for "
+ << rrset.getName());
+ }
+ } else if (rrset.getType() != RRType::NSEC() &&
+ domain.find(RRType::CNAME()) != domain.end()) {
+ LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset.getName());
+ isc_throw(AddError, "CNAME and " << rrset.getType() <<
+ " can't coexist for " << rrset.getName());
+ }
+
+ /*
+ * Similar with DNAME, but it must not coexist only with NS and only in
+ * non-apex domains.
+ * RFC 2672 section 3 mentions that it is implied from it and RFC 2181
+ */
+ if (rrset.getName() != origin_ &&
+ // Adding DNAME, NS already there
+ ((rrset.getType() == RRType::DNAME() &&
+ domain.find(RRType::NS()) != domain.end()) ||
+ // Adding NS, DNAME already there
+ (rrset.getType() == RRType::NS() &&
+ domain.find(RRType::DNAME()) != domain.end())))
+ {
+ LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset.getName());
+ isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
+ "domain " << rrset.getName());
+ }
+ }
+
+ // Validate rrset before adding it to the zone. If something is wrong
+ // it throws an exception. It doesn't modify the zone, and provides
+ // the strong exception guarantee.
+ void addValidation(const Name& zone_name, const ConstRRsetPtr rrset) {
+ if (!rrset) {
+ isc_throw(NullRRset, "The rrset provided is NULL");
+ }
+ if (rrset->getRdataCount() == 0) {
+ isc_throw(AddError, "The rrset provided is empty: " <<
+ rrset->getName() << "/" << rrset->getType());
+ }
+ // Check for singleton RRs. It should probably handled at a different
+ // layer in future.
+ if ((rrset->getType() == RRType::CNAME() ||
+ rrset->getType() == RRType::DNAME()) &&
+ rrset->getRdataCount() > 1)
+ {
+ // XXX: this is not only for CNAME or DNAME. We should generalize
+ // this code for all other "singleton RR types" (such as SOA) in a
+ // separate task.
+ LOG_ERROR(logger, DATASRC_MEM_SINGLETON).arg(rrset->getName()).
+ arg(rrset->getType());
+ isc_throw(AddError, "multiple RRs of singleton type for "
+ << rrset->getName());
+ }
+ // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this
+ // implementation requests it be so at the moment.
+ if ((rrset->getType() == RRType::NSEC3() ||
+ rrset->getType() == RRType::NSEC3PARAM()) &&
+ rrset->getRdataCount() > 1) {
+ isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for "
+ << rrset->getName() << " which isn't supported");
+ }
+
+ NameComparisonResult compare(zone_name.compare(rrset->getName()));
+ if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
+ compare.getRelation() != NameComparisonResult::EQUAL)
+ {
+ LOG_ERROR(logger, DATASRC_MEM_OUT_OF_ZONE).arg(rrset->getName()).
+ arg(zone_name);
+ isc_throw(OutOfZone, "The name " << rrset->getName() <<
+ " is not contained in zone " << zone_name);
+ }
+
+ // Some RR types do not really work well with a wildcard.
+ // Even though the protocol specifically doesn't completely ban such
+ // usage, we refuse to load a zone containing such RR in order to
+ // keep the lookup logic simpler and more predictable.
+ // See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
+ // for more technical background. Note also that BIND 9 refuses
+ // NS at a wildcard, so in that sense we simply provide compatible
+ // behavior.
+ if (rrset->getName().isWildcard()) {
+ if (rrset->getType() == RRType::NS()) {
+ LOG_ERROR(logger, DATASRC_MEM_WILDCARD_NS).
+ arg(rrset->getName());
+ isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
+ rrset->getName());
+ }
+ if (rrset->getType() == RRType::DNAME()) {
+ LOG_ERROR(logger, DATASRC_MEM_WILDCARD_DNAME).
+ arg(rrset->getName());
+ isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
+ rrset->getName());
+ }
+ }
+
+ // Owner names of NSEC3 have special format as defined in RFC5155,
+ // and cannot be a wildcard name or must be one label longer than
+ // the zone origin. While the RFC doesn't prohibit other forms of
+ // names, no sane zone would have such names for NSEC3.
+ // BIND 9 also refuses NSEC3 at wildcard.
+ if (rrset->getType() == RRType::NSEC3() &&
+ (rrset->getName().isWildcard() ||
+ rrset->getName().getLabelCount() !=
+ zone_name.getLabelCount() + 1)) {
+ LOG_ERROR(logger, DATASRC_BAD_NSEC3_NAME).
+ arg(rrset->getName());
+ isc_throw(AddError, "Invalid NSEC3 owner name: " <<
+ rrset->getName());
+ }
+ }
+
+ result::Result addRRsig(const ConstRRsetPtr sig_rrset, ZoneData& zone_data)
+ {
+ // Check consistency of the type covered.
+ // We know the RRset isn't empty, so the following check is safe.
+ RdataIteratorPtr rit = sig_rrset->getRdataIterator();
+ const RRType covered = dynamic_cast<const generic::RRSIG&>(
+ rit->getCurrent()).typeCovered();
+ for (rit->next(); !rit->isLast(); rit->next()) {
+ if (dynamic_cast<const generic::RRSIG&>(
+ rit->getCurrent()).typeCovered() != covered) {
+ isc_throw(AddError, "RRSIG contains mixed covered types: "
+ << sig_rrset->toText());
+ }
+ }
+
+ // Find the RRset to be covered; if not found, treat it as an error
+ // for now.
+ ConstRRsetPtr covered_rrset;
+ if (covered != RRType::NSEC3()) {
+ DomainNode* node = NULL;
+ if (zone_data.domains_.find(sig_rrset->getName(), &node) !=
+ DomainTree::EXACTMATCH || node == NULL || !node->getData()) {
+ isc_throw(AddError,
+ "RRSIG is being added, but no RR to be covered: "
+ << sig_rrset->getName());
+ }
+ const Domain::const_iterator it = node->getData()->find(covered);
+ if (it != node->getData()->end()) {
+ covered_rrset = it->second;
+ }
+ } else {
+ // In case of NSEC3 if something is found it must be NSEC3 RRset
+ // under the assumption of our current implementation.
+ if (zone_data.nsec3_data_) {
+ // Convert the first label to upper-cased text. Note that
+ // for a valid NSEC3 RR the label should only consist of
+ // positive 8-bit char values, so using toupper(int) should be
+ // safe (if it's a bogus label for NSEC3 the zone won't work
+ // anyway). Also note the '::' below: g++'s STL implementation
+ // seems to require it to toupper to make this compile.
+ string fst_label =
+ sig_rrset->getName().split(0, 1).toText(true);
+ transform(fst_label.begin(), fst_label.end(),
+ fst_label.begin(), ::toupper);
+
+ NSEC3Map::const_iterator found =
+ zone_data.nsec3_data_->map_.find(fst_label);
+ if (found != zone_data.nsec3_data_->map_.end()) {
+ covered_rrset = found->second;
+ assert(covered_rrset->getType() == covered);
+ }
+ }
+ }
+ if (!covered_rrset) {
+ isc_throw(AddError, "RRSIG is being added, but no RR of "
+ "covered type found: " << sig_rrset->toText());
+ }
+
+ // The current implementation doesn't allow an existing RRSIG to be
+ // overridden (or updated with additional ones).
+ if (covered_rrset->getRRsig()) {
+ isc_throw(AddError,
+ "RRSIG is being added to override an existing one: "
+ << sig_rrset->toText());
+ }
+
+ // All okay, setting the RRSIG.
+ // XXX: we break const-ness of the covered RRsets. In practice the
+ // ownership of these RRsets would have been given to us so it should
+ // be safe, but it's still a very bad practice.
+ // We'll fix this problem anyway when we update the underlying
+ // representation so that it's more space efficient.
+ // Note: there's a slight chance of getting an exception.
+ // As noted in add(), we give up strong exception guarantee in such
+ // cases.
+ boost::const_pointer_cast<AbstractRRset>(covered_rrset)->addRRsig(sig_rrset);
+
+ return (result::SUCCESS);
+ }
+
+ result::Result addNSEC3(const ConstRRsetPtr rrset, ZoneData& zone_data) {
+ // We know rrset has exactly one RDATA
+ const generic::NSEC3& nsec3_rdata =
+ dynamic_cast<const generic::NSEC3&>(
+ rrset->getRdataIterator()->getCurrent());
+
+ NSEC3Data* nsec3_data = zone_data.getNSEC3Data();
+ if (nsec3_data == NULL) {
+ nsec3_data = NSEC3Data::create(local_mem_sgmt, nsec3_rdata);
+ zone_data.setNSEC3Data(nsec3_data);
+ } else {
+ size_t salt_len = nsec3_data->getSaltLen();
+ const uint8_t* salt_data = nsec3_data->getSaltData();
+ const vector<uint8_t>& salt_data_2 = nsec3_rdata.getSalt();
+
+ if ((nsec3_rdata.getHashalg() != nsec3_data->hashalg) ||
+ (nsec3_rdata.getIterations() != nsec3_data->iterations) ||
+ (salt_data_2.size() != salt_len) ||
+ (std::memcmp(&salt_data_2[0], salt_data, salt_len) != 0)) {
+ isc_throw(AddError,
+ "NSEC3 with inconsistent parameters: " <<
+ rrset->toText());
+ }
+ }
+
+ string fst_label = rrset->getName().split(0, 1).toText(true);
+ transform(fst_label.begin(), fst_label.end(), fst_label.begin(),
+ ::toupper);
+
+ ZoneNode *node;
+ nsec3_data->insertName(local_mem_sgmt, Name(fst_label), &node);
+
+ RdataEncoder encoder;
+ RdataSet *set = RdataSet::create(local_mem_sgmt, encoder,
+ rrset, ConstRRsetPtr());
+ RdataSet *old_set = node->setData(set);
+ if (old_set != NULL) {
+ RdataSet::destroy(local_mem_sgmt, rrclass_, old_set);
+ }
+
+ return (result::SUCCESS);
+ }
+
+ /*
+ * Implementation of longer methods. We put them here, because the
+ * access is without the impl_-> and it will get inlined anyway.
+ */
+ // Implementation of InMemoryZoneFinder::add
+ result::Result add(const ConstRRsetPtr& rawrrset,
+ const Name& zone_name, ZoneData& zone_data,
+ vector<RBNodeRRset*>* need_additionals)
+ {
+ // Sanitize input. This will cause an exception to be thrown
+ // if the input RRset is empty.
+ addValidation(zone_name, rawrrset);
+
+ // OK, can add the RRset.
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
+ arg(rawrrset->getName()).arg(rawrrset->getType()).arg(zone_name);
+
+ // ... although instead of loading the RRset directly, we encapsulate
+ // it within an RBNodeRRset. This contains additional information that
+ // speeds up queries.
+ RBNodeRRsetPtr rrset(new RBNodeRRset(rawrrset));
+
+ if (rrset->getType() == RRType::NSEC3()) {
+ return (addNSEC3(rrset, zone_data));
+ }
+
+ // RRSIGs are special in various points, so we handle it in a
+ // separate dedicated method.
+ if (rrset->getType() == RRType::RRSIG()) {
+ return (addRRsig(rrset, zone_data));
+ }
+
+ // Add wildcards possibly contained in the owner name to the domain
+ // tree.
+ // Note: this can throw an exception, breaking strong exception
+ // guarantee. (see also the note for contextCheck() below).
+ addWildcards(zone_data.local_mem_sgmt_, zone_data.domains_,
+ rrset->getName());
+
+ // Get the node
+ DomainNode* node;
+ DomainTree::Result result =
+ zone_data.domains_.insert(zone_data.local_mem_sgmt_,
+ rrset->getName(), &node);
+ // Just check it returns reasonable results
+ assert((result == DomainTree::SUCCESS ||
+ result == DomainTree::ALREADYEXISTS) && node!= NULL);
+
+ // Now get the domain
+ DomainPtr domain;
+ // It didn't exist yet, create it
+ if (node->isEmpty()) {
+ domain.reset(new Domain);
+ node->setData(domain);
+ } else { // Get existing one
+ domain = node->getData();
+ }
+
+ // Checks related to the surrounding data.
+ // Note: when the check fails and the exception is thrown, it may
+ // break strong exception guarantee. At the moment we prefer
+ // code simplicity and don't bother to introduce complicated
+ // recovery code.
+ contextCheck(*rrset, *domain);
+
+ // Try inserting the rrset there
+ if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
+ // Ok, we just put it in
+
+ // If this RRset creates a zone cut at this node, mark the node
+ // indicating the need for callback in find().
+ if (rrset->getType() == RRType::NS() &&
+ rrset->getName() != zone_name) {
+ node->setFlag(DomainNode::FLAG_CALLBACK);
+ // If it is DNAME, we have a callback as well here
+ } else if (rrset->getType() == RRType::DNAME()) {
+ node->setFlag(DomainNode::FLAG_CALLBACK);
+ }
+
+ if (need_additionals != NULL &&
+ (rrset->getType() == RRType::NS() ||
+ rrset->getType() == RRType::MX())) {
+ need_additionals->push_back(rrset.get());
+ }
+
+ // If we've added NSEC3PARAM at zone origin, set up NSEC3 specific
+ // data or check consistency with already set up parameters.
+ if (rrset->getType() == RRType::NSEC3PARAM() &&
+ rrset->getName() == zone_name) {
+ // We know rrset has exactly one RDATA
+ const generic::NSEC3PARAM& param =
+ dynamic_cast<const generic::NSEC3PARAM&>(
+ rrset->getRdataIterator()->getCurrent());
+
+ if (!zone_data.nsec3_data_) {
+ zone_data.nsec3_data_.reset(
+ new ZoneData::NSEC3Data(param));
+ } else if (!zone_data.nsec3_data_->hash_->match(param)) {
+ isc_throw(AddError, "NSEC3PARAM with inconsistent "
+ "parameters: " << rrset->toText());
+ }
+ } else if (rrset->getType() == RRType::NSEC()) {
+ // If it is NSEC signed zone, so we put a flag there
+ // (flag is enough)
+ zone_data.nsec_signed_ = true;
+ }
+ return (result::SUCCESS);
+ } else {
+ // The RRSet of given type was already there
+ return (result::EXIST);
+ }
+ }
+
+ /*
+ * Same as above, but it checks the return value and if it already exists,
+ * it throws.
+ */
+ void addFromLoad(const ConstRRsetPtr& set,
+ const Name& zone_name, ZoneData* zone_data,
+ vector<RBNodeRRset*>* need_additionals)
+ {
+ switch (add(set, zone_name, *zone_data, need_additionals)) {
+ case result::EXIST:
+ LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
+ arg(set->getName()).arg(set->getType());
+ isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
+ set->toText());
+ case result::SUCCESS:
+ return;
+ default:
+ assert(0);
+ }
+ }
+};
+
+result::Result
+InMemoryClient::InMemoryClientImpl::load(
+ const Name& zone_name,
+ const string& filename,
+ boost::function<void(LoadCallback)> rrset_installer)
+{
+ vector<RBNodeRRset*> need_additionals;
+
+ ZoneTable::AddResult result = zone_table_->addZone(local_mem_sgmt,
+ rrclass_,
+ zone_name);
+ if (result.code != result::SUCCESS) {
+ // FIXME: This needs to be fixed for reloads to work. It would
+ // need changes in ZoneTable to allow resetting ZoneData for a
+ // table node, and we should swap the new ZoneData in only after
+ // it is ready.
+ isc_throw(DataSourceError, "Zone probably exists: " + zone_name.toText());
+ }
+
+ scoped_ptr<ZoneData> tmp(result.zone_data);
+
+ rrset_installer(boost::bind(&InMemoryClientImpl::addFromLoad, this,
+ _1, zone_name, tmp.get(), &need_additionals));
+
+ vector<RBNodeRRset*> wild_additionals;
+ for_each(need_additionals.begin(), need_additionals.end(),
+ boost::bind(addAdditional, _1, tmp.get(), &wild_additionals));
+ for_each(wild_additionals.begin(), wild_additionals.end(),
+ boost::bind(addWildAdditional, _1, tmp.get()));
+
+ // If the zone is NSEC3-signed, check if it has NSEC3PARAM
+ if (tmp->nsec3_data_) {
+ // Note: origin_data_ is set on creation of ZoneData, and the load
+ // process only adds new nodes (and their data), so this assertion
+ // should hold.
+ assert(tmp->origin_data_ != NULL && !tmp->origin_data_->isEmpty());
+ if (tmp->origin_data_->getData()->find(RRType::NSEC3PARAM()) ==
+ tmp->origin_data_->getData()->end()) {
+ LOG_WARN(logger, DATASRC_MEM_NO_NSEC3PARAM).
+ arg(origin_).arg(client_.getClass());
+ }
+ }
+
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_ADD_ZONE).
+ arg(zone_name).arg(getClass().toText());
+
+ ++impl_->zone_count;
+ return (result.code);
+}
+
+namespace {
+// A wrapper for dns::masterLoad used by load() below. Essentially it
+// converts the two callback types. Note the mostly redundant wrapper of
+// boost::bind. It converts function<void(ConstRRsetPtr)> to
+// function<void(RRsetPtr)> (masterLoad() expects the latter). SunStudio
+// doesn't seem to do this conversion if we just pass 'callback'.
+void
+masterLoadWrapper(const char* const filename, const Name& origin,
+ const RRClass& zone_class, LoadCallback callback)
+{
+ masterLoad(filename, origin, zone_class, boost::bind(callback, _1));
+}
+
+// The installer called from Impl::load() for the iterator version of load().
+void
+generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
+ ConstRRsetPtr rrset;
+ vector<ConstRRsetPtr> rrsigs; // placeholder for RRSIGs until "commitable".
+
+ // The current internal implementation assumes an RRSIG is always added
+ // after the RRset they cover. So we store any RRSIGs in 'rrsigs' until
+ // it's safe to add them; based on our assumption if the owner name
+ // changes, all covered RRsets of the previous name should have been
+ // installed and any pending RRSIGs can be added at that point. RRSIGs
+ // of the last name from the iterator must be added separately.
+ while ((rrset = iterator->getNextRRset()) != NULL) {
+ if (!rrsigs.empty() && rrset->getName() != rrsigs[0]->getName()) {
+ BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
+ callback(sig_rrset);
+ }
+ rrsigs.clear();
+ }
+ if (rrset->getType() == RRType::RRSIG()) {
+ rrsigs.push_back(rrset);
+ } else {
+ callback(rrset);
+ }
+ }
+
+ BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
+ callback(sig_rrset);
+ }
+}
+}
+
+InMemoryClient::InMemoryClient(RRClass rrclass) :
+ impl_(new InMemoryClientImpl(rrclass))
+{}
+
+InMemoryClient::~InMemoryClient() {
+ delete impl_;
+}
+
+RRClass
+InMemoryClient::getClass() const {
+ return (impl_->rrclass_);
+}
+
+unsigned int
+InMemoryClient::getZoneCount() const {
+ return (impl_->zone_count);
+}
+
+ZoneTable::FindResult
+InMemoryClient::findZone(const isc::dns::Name& zone_name) const {
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_FIND_ZONE).arg(zone_name);
+ ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name));
+ return (result);
+}
+
+result::Result
+InMemoryClient::load(const isc::dns::Name& zone_name,
+ const std::string& filename) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(zone_name).
+ arg(filename);
+
+ return (impl_->load(zone_name, filename,
+ boost::bind(masterLoadWrapper, filename.c_str(),
+ zone_name, getClass(), _1)));
+}
+
+result::Result
+InMemoryClient::load(const isc::dns::Name& zone_name,
+ ZoneIterator& iterator) {
+ return (impl_->load(zone_name, string(),
+ boost::bind(generateRRsetFromIterator,
+ &iterator, _1)));
+}
+
+result::Result
+InMemoryClient::add(const isc::dns::Name& zone_name,
+ const ConstRRsetPtr& rrset) {
+ ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name));
+ if (result.code != result::SUCCESS) {
+ isc_throw(DataSourceError, "No such zone: " + zone_name.toText());
+ }
+
+ return (impl_->add(rrset, zone_name, *result.zone_data, NULL));
+}
+
+#if 0
+
+namespace {
+
+class MemoryIterator : public ZoneIterator {
+private:
+ RBTreeNodeChain<Domain> chain_;
+ Domain::const_iterator dom_iterator_;
+ const DomainTree& tree_;
+ const DomainNode* node_;
+ // Only used when separate_rrs_ is true
+ RdataIteratorPtr rdata_iterator_;
+ bool separate_rrs_;
+ bool ready_;
+public:
+ MemoryIterator(const DomainTree& tree, const Name& origin, bool separate_rrs) :
+ tree_(tree),
+ separate_rrs_(separate_rrs),
+ ready_(true)
+ {
+ // Find the first node (origin) and preserve the node chain for future
+ // searches
+ DomainTree::Result result(tree_.find(origin, &node_, chain_));
+ // It can't happen that the origin is not in there
+ if (result != DomainTree::EXACTMATCH) {
+ isc_throw(Unexpected,
+ "In-memory zone corrupted, missing origin node");
+ }
+ // Initialize the iterator if there's somewhere to point to
+ if (node_ != NULL && node_->getData() != DomainPtr()) {
+ dom_iterator_ = node_->getData()->begin();
+ if (separate_rrs_ && dom_iterator_ != node_->getData()->end()) {
+ rdata_iterator_ = dom_iterator_->second->getRdataIterator();
+ }
+ }
+ }
+
+ virtual ConstRRsetPtr getNextRRset() {
+ if (!ready_) {
+ isc_throw(Unexpected, "Iterating past the zone end");
+ }
+ /*
+ * This cycle finds the first nonempty node with yet unused RRset.
+ * If it is NULL, we run out of nodes. If it is empty, it doesn't
+ * contain any RRsets. If we are at the end, just get to next one.
+ */
+ while (node_ != NULL && (node_->getData() == DomainPtr() ||
+ dom_iterator_ == node_->getData()->end())) {
+ node_ = tree_.nextNode(chain_);
+ // If there's a node, initialize the iterator and check next time
+ // if the map is empty or not
+ if (node_ != NULL && node_->getData() != NULL) {
+ dom_iterator_ = node_->getData()->begin();
+ // New RRset, so get a new rdata iterator
+ if (separate_rrs_) {
+ rdata_iterator_ = dom_iterator_->second->getRdataIterator();
+ }
+ }
+ }
+ if (node_ == NULL) {
+ // That's all, folks
+ ready_ = false;
+ return (ConstRRsetPtr());
+ }
+
+ if (separate_rrs_) {
+ // For separate rrs, reconstruct a new RRset with just the
+ // 'current' rdata
+ RRsetPtr result(new RRset(dom_iterator_->second->getName(),
+ dom_iterator_->second->getClass(),
+ dom_iterator_->second->getType(),
+ dom_iterator_->second->getTTL()));
+ result->addRdata(rdata_iterator_->getCurrent());
+ rdata_iterator_->next();
+ if (rdata_iterator_->isLast()) {
+ // all used up, next.
+ ++dom_iterator_;
+ // New RRset, so get a new rdata iterator, but only if this
+ // was not the final RRset in the chain
+ if (dom_iterator_ != node_->getData()->end()) {
+ rdata_iterator_ = dom_iterator_->second->getRdataIterator();
+ }
+ }
+ return (result);
+ } else {
+ // The iterator points to the next yet unused RRset now
+ ConstRRsetPtr result(dom_iterator_->second);
+
+ // This one is used, move it to the next time for next call
+ ++dom_iterator_;
+
+ return (result);
+ }
+ }
+
+ virtual ConstRRsetPtr getSOA() const {
+ isc_throw(NotImplemented, "Not implemented");
+ }
+};
+
+} // End of anonymous namespace
+
+ZoneIteratorPtr
+InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
+ ZoneTable::FindResult result(impl_->zone_table_->findZone(name));
+ if (result.code != result::SUCCESS) {
+ isc_throw(DataSourceError, "No such zone: " + name.toText());
+ }
+
+ const ZoneData* data = result.zone_data;
+ if (zone == NULL) {
+ /*
+ * TODO: This can happen only during some of the tests and only as
+ * a temporary solution. This should be fixed by #1159 and then
+ * this cast and check shouldn't be necessary. We don't have
+ * test for handling a "can not happen" condition.
+ */
+ isc_throw(Unexpected, "The zone at " + name.toText() +
+ " is not InMemoryZoneFinder");
+ }
+ return (ZoneIteratorPtr(new MemoryIterator(
+ zone->impl_->zone_data_->domains_, name,
+ separate_rrs)));
+}
+
+ZoneUpdaterPtr
+InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
+ isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
+}
+
+pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
+ uint32_t) const
+{
+ isc_throw(isc::NotImplemented, "Journaling isn't supported for "
+ "in memory data source");
+}
+
+#endif
+
+} // end of namespace memory
+} // end of namespace datasrc
+} // end of namespace isc