text:
#include <boost/version.hpp>
#if BOOST_VERSION >= 107000
#  ifndef BOOST_ASIO_NO_DYNAMIC_BUFFER_V1
#    define BOOST_ASIO_NO_DYNAMIC_BUFFER_V1
#  endif
#else
#  undef BOOST_ASIO_NO_DYNAMIC_BUFFER_V1
#endif

#include <minapp/handler.hpp>
#include <minapp/session.hpp>
#include <minapp/service.hpp>
#include <minapp/session_manager.hpp>
#include <minapp/spinlock.hpp>

#include <mutex>

#if BOOST_VERSION >= 107400
#include <boost/stl_interfaces/iterator_interface.hpp>
#else
#include <boost/iterator/iterator_facade.hpp>
#endif
#include <boost/range/iterator_range_core.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/write.hpp>
#include <boost/intrusive/set.hpp>

using namespace minapp;

// GCC, clang, MSVC support [ , ## __VA_ARGS__ ] for empty __VA_ARGS__
#undef CALLBACK
#define CALLBACK(...) [self = shared_from_this(), ## __VA_ARGS__](const boost::system::error_code& ec, std::size_t bytes_transferred)

namespace
{
#ifndef BOOST_ASIO_NO_DYNAMIC_BUFFER_V1

    struct dynamic_buffer_adaptor
    {
        triple_buffer& impl;
        const std::size_t maximum;

        using const_buffers_type = triple_buffer::const_buffers_type;
        using mutable_buffers_type = triple_buffer::mutable_buffers_type;

        dynamic_buffer_adaptor(const dynamic_buffer_adaptor&) = delete;
        dynamic_buffer_adaptor(dynamic_buffer_adaptor&&) = default;

        const_buffers_type data() const
        {
            return impl.internal_input_buffer();
        }

        std::size_t size() const
        {
            return impl.internal_input_buffer().size();
        }

        std::size_t max_size() const
        {
            return (std::min)(impl.max_size() - impl.external_input_buffer().size(), maximum);
        }

        std::size_t capacity() const
        {
            // Note that capacity may be greater than max_size
            return impl.capacity() - impl.external_input_buffer().size();
        }

        mutable_buffers_type prepare(std::size_t n)
        {
            // Note that size may be greater than max_size
            if (size() > max_size() || max_size() - size() < n)
                throw std::length_error("dynamic_buffer too long");
            return impl.prepare_output_buffer(n);
        }

        void commit(std::size_t n)
        {
            impl.commit_to_internal_input(n);
        }

        void consume(std::size_t n); // [async_]read_util() do not need consume, and it's requirement
                                     // of DynamicBuffer is not compatible with triple_buffer.
                                     // Declare it to meet the DynamicBuffer requirements and no define
                                     // it to catch error if it is called anywhere.
    };

#else

    struct dynamic_buffer_adaptor
    {
        triple_buffer& impl;
        const std::size_t maximum;

        using const_buffers_type = triple_buffer::const_buffers_type;
        using mutable_buffers_type = triple_buffer::mutable_buffers_type;

        mutable_buffers_type data(std::size_t pos, std::size_t n)
        {
            return boost::asio::buffer(boost::asio::buffer(
                    impl.internal_input_buffer().data(), size()) + pos, n);
        }

        const_buffers_type data(std::size_t pos, std::size_t n) const
        {
            return boost::asio::buffer(boost::asio::buffer(
                    impl.internal_input_buffer().data(), size()) + pos, n);
        }

        std::size_t size() const
        {
            return (std::min)(impl.size() - impl.external_input_buffer().size(), max_size());
        }

        std::size_t max_size() const
        {
            return (std::min)(impl.max_size() - impl.external_input_buffer().size(), maximum);
        }

        std::size_t capacity() const
        {
            return (std::min)(impl.capacity() - impl.external_input_buffer().size(), max_size());
        }

        void grow(std::size_t n)
        {
            if (max_size() - size() < n)
                throw std::length_error("dynamic_buffer too long");
            impl.grow_output_buffer(n);
        }

        void shrink(std::size_t n)
        {
            impl.shrink_output_buffer(n);
        }

        void consume(std::size_t n); // [async_]read_util() do not need consume, and it's requirement
                                     // of DynamicBuffer is not compatible with triple_buffer.
                                     // Declare it to meet the DynamicBuffer requirements and no define
                                     // it to catch error if it is called anywhere.
    };

#endif

    static_assert(boost::asio::is_dynamic_buffer<dynamic_buffer_adaptor>::value,
                  "dynamic_buffer_adaptor should model the boost::asio::DynamicBuffer concept");

    const std::size_t size_msb = std::size_t(-1) - (std::size_t(-1) >> 1u);

    const std::string crlf = { '\r', '\n' };

    std::atomic_ulong id = 1;

    struct session_impl : session, boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::safe_link>>
    {
        explicit session_impl(service_ptr service);
        ~session_impl();
    };

    struct session_id
    {
        using type = unsigned long;
        type operator()(const session& s) const noexcept { return s.id(); }
    };

    struct manager_impl : session_manager
    {
        typedef boost::intrusive::set<session_impl,
                boost::intrusive::constant_time_size<false>,
                boost::intrusive::key_of_value<session_id>> session_set;

        spinlock guard;
        session_set sessions;
    };
}

session_impl::session_impl(service_ptr service) : session(std::move(service))
{
    auto impl = static_cast<manager_impl*>(this->service()->manager().get());
    std::lock_guard<spinlock> guard(impl->guard);
    impl->sessions.insert(*this);
}

session_impl::~session_impl()
{
    auto impl = static_cast<manager_impl*>(this->service()->manager().get());
    std::lock_guard<spinlock> guard(impl->guard);
    impl->sessions.erase(impl->sessions.iterator_to(*this));
}

session::session(service_ptr service)
    : id_(::id.fetch_add(1, std::memory_order_relaxed)),
      service_(std::move(service)), socket_(*service_->context()),
      protocol_(protocol::any), protocol_options_{},
      status_(status::connecting), read_buffer_size_(65536)
{

}

session::~session()
{
    close(true);
}

unsigned long session::id() const
{
    return id_;
}

std::size_t session::read_buffer_size() const
{
    return read_buffer_size_;
}

void session::read_buffer_size(std::size_t sz)
{
    read_buffer_size_ = sz;
}

const std::string& session::delimiter() const
{
    return delimiter_;
}

void session::delimiter(char delim)
{
    delimiter_.clear();
    delimiter_.push_back(delim);
}

void session::delimiter(std::string delim)
{
    delimiter_ = std::move(delim);
}

enum protocol session::protocol() const
{
    return protocol_;
}

enum protocol_options session::protocol_options() const
{
    return protocol_options_;
}

void session::protocol(enum protocol protocol, enum protocol_options options)
{
    protocol_ = protocol;
    protocol_options_ = options;
}

void session::protocol(enum protocol protocol, std::size_t bufsz, enum protocol_options options)
{
    this->protocol(protocol, options);
    this->read_buffer_size(bufsz);
}

enum status session::status() const
{
    return status_;
}

minapp::socket& session::socket()
{
    return socket_;
}

const service_ptr& session::service() const
{
    return service_;
}

handler_ptr session::handler() const
{
    return handler_;
}

handler_ptr session::handler(handler_ptr other)
{
    other = noexcept_handler::wrap(std::move(other));
    std::swap(other, handler_);
    return other;
}

handler_ptr session::use_service_handler()
{
    handler_ptr other = service_->handler();
    std::swap(other, handler_);
    return other;
}

void session::close(bool immediately)
{
    auto current = status_.load(std::memory_order_relaxed);
    const auto target = immediately ? status::closed : status::closing;

    while (current < target && !status_.compare_exchange_weak(
           current, target, std::memory_order_seq_cst, std::memory_order_relaxed));

    if (current >= target) return;

    if (immediately)
    {
        boost::system::error_code ignored;
        socket_.shutdown(socket::shutdown_both, ignored);
        socket_.close(ignored);
        handler()->close(this);
    }
    else
    {
        boost::system::error_code ignored;
        socket_.shutdown(socket::shutdown_receive, ignored);
        write();
    }
}

context& session::execution_context() const
{
    return *service_->context();
}

bool session::check(const boost::system::error_code& ec)
{
    //if (this->status_ >= status::closing) return false;
    if (ec)
    {
        handler()->error(this, ec);
        close(false);
        return false;
    }
    return true;
}

std::future<session_ptr> session::connect(const endpoint& ep)
{
    std::promise<session_ptr> promise;
    std::future<session_ptr> future = promise.get_future();
    socket_.async_connect(ep,
    [self = shared_from_this(), promise = std::move(promise)]
    (const boost::system::error_code& ec) mutable
    {
        self->connect(ec, &promise);
    });

    return future;
}

std::future<session_ptr> session::connect(object::fn<endpoint()> gen)
{
    // boost::generator_iterator and boost::function_input_iterator do not support sentinel iterators.
    struct generator_iterator :
#if BOOST_VERSION >= 107400
        boost::stl_interfaces::iterator_interface<generator_iterator, std::forward_iterator_tag, const endpoint>
    {
        using iterator_interface::operator++;
        const endpoint& operator*() const { return value; }
        generator_iterator& operator++() { value = gen(); return *this; }
        bool operator==(const generator_iterator& other) const { return value == other.value; }
#else
        boost::iterators::iterator_facade<generator_iterator, const endpoint, boost::iterators::single_pass_traversal_tag>
    {
        const endpoint& dereference() const { return value; }
        void increment() { value = gen(); }
        bool equal(const generator_iterator& other) const { return value == other.value; }
#endif
        endpoint value;
        object::fn<endpoint()> gen;

        generator_iterator() : gen(), value() {}
        explicit generator_iterator(object::fn<endpoint()> gen) : value(gen()), gen(std::move(gen)) { }
    };

    std::promise<session_ptr> promise;
    std::future<session_ptr> future = promise.get_future();
    boost::asio::async_connect(socket_, generator_iterator(std::move(gen)), generator_iterator(),
    [self = shared_from_this(), promise = std::move(promise)]
    (const boost::system::error_code& ec, const generator_iterator&) mutable
    {
        self->connect(ec, &promise);
    });

    return future;
}

bool session::connect(const boost::system::error_code& ec, std::promise<session_ptr>* promise)
{
    if (ec)
    {
        handler()->error(this, ec);
        close(true);
        if (promise) promise->set_exception(std::make_exception_ptr(boost::system::system_error(ec)));
        return false;
    }
    else
    {
        status_ = status::connected;
        handler()->connect(this, socket_.remote_endpoint());
        read();
        if (promise) promise->set_value(shared_from_this());
        return true;
    }
}

void session::write(persistent_buffer_list& list)
{
    if (status_ >= status::closing) return;
    write_queue_.manage(list);
    write();
}

void session::write()
{
    if (status_ >= status::closed) return;
    auto marker = write_queue_.mark();
    if (marker <= 0)
    {
        if (marker == 0 && status_ == status::closing)
            close(true);
        return;
    }

    boost::asio::async_write(socket_, boost::make_iterator_range(write_queue_.marked()), CALLBACK()
    {
        if (ec)
        {
            self->handler()->error(self.get(), ec);
            self->close(true);
        }
        else
        {
            self->handler()->write(self.get(), self->write_queue_.marked());
            self->write_queue_.clear_marked();
            self->write();
        }
    });
}

void session::read()
{
    auto current = status::connected;
    if (!status_.compare_exchange_strong(current, status::reading) && current != status::reading)
        return;

    if(!has_options(protocol_options_, protocol_options::do_not_consume_buffer))
        buf_.consume_whole_external_input();
    buf_.mark_current_external_input();

    switch (protocol_)
    {
    case protocol::none:
        current = status::reading;
        status_.compare_exchange_strong(current, status::connected);
        break;

    case protocol::any:
        read_some(read_buffer_size_);
        break;

    case protocol::fixed:
        read_fixed(read_buffer_size_);
        break;

    case protocol::delim:
        read_delim(delimiter_);
        break;

    case protocol::delim_zero:
        read_delim('\0');
        break;

    case protocol::delim_cr:
        read_delim('\r');
        break;

    case protocol::delim_lf:
        read_delim('\n');
        break;

    case protocol::delim_crlf:
        read_delim(crlf);
        break;

    case protocol::prefix_8:
        read_prefix(1);
        break;

    case protocol::prefix_16:
        read_prefix(2);
        break;

    case protocol::prefix_32:
        read_prefix(4);
        break;

    case protocol::prefix_64:
        read_prefix(8);
        break;

    case protocol::prefix_var:
        read_prefix(size_msb);
        break;

    default:
        this->check(make_error_code(boost::system::errc::protocol_not_supported));
    }
}

void session::read_some(std::size_t bufsize)
{
    assert(bufsize <= read_buffer_size_);
    if(buf_.internal_input_buffer().size() > 0)
    {
        buf_.commit_to_external_input(bufsize);
        buf_.move_to_new_external_input_segment();
        handler()->read(this, buf_);
        read();
    }
    else
    {
        socket_.async_read_some(buf_.prepare_output_buffer(bufsize), CALLBACK()
        {
            if (!self->check(ec)) return;
            self->buf_.commit_to_internal_input(bytes_transferred);
            self->buf_.commit_whole_internal_input();
            self->buf_.move_to_new_external_input_segment();
            self->handler()->read(self.get(), self->buf_);
            self->read();
        });
    }
}

void session::read_fixed(std::size_t bufsize)
{
    assert(bufsize <= read_buffer_size_);
    const std::size_t remaining = buf_.internal_input_buffer().size();
    if (remaining >= bufsize)
    {
        buf_.commit_to_external_input(bufsize);
        buf_.move_to_new_external_input_segment();
        handler()->read(this, buf_);
        read();
    }
    else
    {
        boost::asio::async_read(socket_, buf_.prepare_output_buffer(bufsize - remaining), CALLBACK()
        {
            if (!self->check(ec)) return;
            self->buf_.commit_to_internal_input(bytes_transferred);
            self->buf_.commit_whole_internal_input();
            self->buf_.move_to_new_external_input_segment();
            self->handler()->read(self.get(), self->buf_);
            self->read();
        });
    }
}

void session::read_delim(char delim)
{
    std::string s;
    s.push_back(delim);
    read_delim(s);
}

void session::read_delim(const std::string& delim)
{
    std::size_t delim_length = delim.size();
    if (delim_length == 0) return read_some(read_buffer_size_);

    const bool ignore = has_options(protocol_options_, protocol_options::ignore_protocol_bytes);
    auto callback = CALLBACK(delim_length, ignore)
    {
        if (!self->check(ec)) return;

#ifdef BOOST_ASIO_NO_DYNAMIC_BUFFER_V1
        // Unlike DynamicBuffer_v1, the whole output buffer of DynamicBuffer_v2 contains valid data
        // since it had been shrunk before the callback. So commit the whole output buffer.
        self->buf_.commit_to_internal_input(self->buf_.output_buffer().size());
#endif
        // buf has already been committed and bytes_transferred is the length from the beginning of
        // internal input to the delimiter (including), so it may be smaller than the size of internal input.
        self->buf_.commit_to_external_input(bytes_transferred - delim_length * ignore);
        self->buf_.move_to_new_external_input_segment();
        self->handler()->read(self.get(), self->buf_);
        self->buf_.commit_to_external_input(delim_length * ignore);
        self->read();
    };

#ifdef BOOST_ASIO_NO_DYNAMIC_BUFFER_V1
    // DynamicBuffer_v2 treats the output buffer as valid data, so remove it before read.
    buf_.shrink_output_buffer(buf_.output_buffer().size());
#endif

    if (delim_length == 1)
    {
        boost::asio::async_read_until(socket_, dynamic_buffer_adaptor{buf_, read_buffer_size_},
                                      delim[0], std::move(callback));
    }
    else
    {
        boost::asio::async_read_until(socket_, dynamic_buffer_adaptor{buf_, read_buffer_size_},
                                      delim, std::move(callback));
    }
}

void session::read_prefix(std::size_t len)
{
    const std::size_t remaining = buf_.internal_input_buffer().size();
    auto p = (const unsigned char*)buf_.internal_input_buffer().data();
    const bool var = len >= size_msb;
    if(var) len -= size_msb;

    if (len == 0)
    {
        while (len < remaining && (p[len] & 0x80u) != 0) ++len;
        ++len;
    }

    if (remaining < len || (var && (p[len - 1] & 0x80u) != 0 && (++len, true)))
    {
        if (len > read_buffer_size_)
        {
            this->check(make_error_code(boost::system::errc::message_size));
        }
        else
        {
            std::size_t sz = len - remaining;
            if (var) len += size_msb;
            boost::asio::async_read(socket_, buf_.prepare_output_buffer(sz), CALLBACK(len)
            {
                if (!self->check(ec)) return;
                self->buf_.commit_to_internal_input(bytes_transferred);
                self->read_prefix(len);
            });
        }
    }
    else if (len > 8 + var)
    {
        this->check(make_error_code(boost::system::errc::value_too_large));
    }
    else
    {
        std::uintmax_t data_size = 0;

        if(has_options(protocol_options_, protocol_options::use_little_endian))
        {
            if(var)
            {
                for(unsigned i = 0; i < len; ++i)
                    data_size |= static_cast<std::uintmax_t>((*p++) & 0x7fu) << (7 * i);
            }
            else
            {
                for(unsigned i = 0; i < len; ++i)
                    data_size |= static_cast<std::uintmax_t>(*p++) << (8 * i);
            }
        }
        else
        {
            p += len;
            if (var)
            {
                for(unsigned i = 0; i < len; ++i)
                    data_size |= static_cast<std::uintmax_t>((*--p) & 0x7fu) << (7 * i);
            }
            else
            {
                for(unsigned i = 0; i < len; ++i)
                    data_size |= static_cast<std::uintmax_t>(*--p) << (8 * i);
            }
        }

        if (has_options(protocol_options_, protocol_options::include_prefix_in_payload) &&
            ((data_size < len) || (data_size -= len, false)))
        {
            this->check(make_error_code(boost::system::errc::bad_message));
        }
        else if (data_size + len > read_buffer_size_)
        {
            this->check(make_error_code(boost::system::errc::message_size));
        }
        else
        {
            buf_.commit_to_external_input(len);
            if (has_options(protocol_options_, protocol_options::ignore_protocol_bytes))
                buf_.mark_current_external_input();
            read_fixed(data_size);
        }
    }
}

session_manager_ptr session_manager::create()
{
    return std::make_shared<manager_impl>();
}

session_ptr session_manager::create(service_ptr service)
{
    return std::make_shared<session_impl>(std::move(service));
}

session_ptr session_manager::get(unsigned long id)
{
    auto impl = static_cast<manager_impl*>(this);
    std::lock_guard<spinlock> guard(impl->guard);
    auto iter = impl->sessions.find(id);
    if(iter != impl->sessions.end())
        return iter->shared_from_this();
    return session_ptr{};
}

std::size_t session_manager::foreach(object::fn<bool(&)(session*)> f)
{
    std::size_t c = 0;
    auto impl = static_cast<manager_impl*>(this);
    impl->guard.lock();
    auto iter = impl->sessions.begin();
    while (iter != impl->sessions.end())
    {
        auto session = iter->shared_from_this();
        if (!session)
        {
            iter = impl->sessions.upper_bound(iter->id());
        }
        else
        {
            impl->guard.unlock();
            if(!f(session.get())) return c;
            ++c;
            auto id = session->id();
            session = nullptr;
            impl->guard.lock();
            iter = impl->sessions.upper_bound(id);
        }
    }
    impl->guard.unlock();
    return c;
}

max_stars_repo_name: huangqinjin/minapp

max_issues_repo_name: huangqinjin/minapp

max_forks_repo_name: huangqinjin/minapp


text:
#include <boost/python.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <boost/shared_ptr.hpp>

using namespace boost::interprocess;
using namespace boost::python;

void register_shared_memory_object() {
    typedef shared_memory_object T;
    class_<T, boost::shared_ptr<T>, boost::noncopyable> c("shared_memory_object", init<>());
    c.def(
        init<create_only_t, const char *, boost::interprocess::mode_t, optional<const permissions&> >(
            args("tag", "name", "mode", "permissions")
        )
    );
    c.def(
        init<open_or_create_t, const char *, boost::interprocess::mode_t, optional<const permissions&> >(
            args("tag", "name", "mode", "permissions")
        )
    );
    c.def(
        init<open_only_t, const char *, boost::interprocess::mode_t>(
            args("tag", "name", "mode")
        )
    );
    c.def("remove", &T::remove);
    c.staticmethod("remove");

    //TODO: finish "shared_memory_object"
}

ext: cpp


text:
//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include <iostream>
#include <sstream>

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/Definition.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"

#include "smtk/attribute/operators/Export.h"
#include "smtk/attribute/operators/Import.h"
#include "smtk/attribute/operators/Read.h"
#include "smtk/attribute/operators/Write.h"

#include "smtk/common/UUID.h"

#include "smtk/io/AttributeReader.h"
#include "smtk/io/AttributeWriter.h"
#include "smtk/io/Logger.h"

//force to use filesystem version 3
#define BOOST_FILESYSTEM_VERSION 3
#include <boost/filesystem.hpp>
using namespace boost::filesystem;

namespace
{
void cleanup(const std::string& file_path)
{
  //first verify the file exists
  ::boost::filesystem::path path(file_path);
  if (::boost::filesystem::is_regular_file(path))
  {
    //remove the file_path if it exists.
    ::boost::filesystem::remove(path);
  }
}
} // namespace

// This test accepts as input one or more .sbi descriptions of an attribute
// resource. For each input file, it then performs the following:
//
// 1. read the .sbi file into an attribute resource
// 2. write the resource to file as a .sbi file (to ensure the output is of the
//    most recent format)
// 3. write the resource to file and as a .smtk file
// 4. read the generated .smtk file
// 5. write the result as a .sbi file
// 6. check that the .sbi files from steps 2 and 5 are equivalent.
int main(int argc, char** argv)
{
  if (argc == 1)
  {
    std::cerr << "Usage: TestReadWrite /path/to/input1.sbi /path/to/input2.sbi..." << std::endl;
    return 1;
  }

  std::string write_root(SMTK_SCRATCH_DIR);
  std::string unique_str = smtk::common::UUID::random().toString();

  for (int i = 1; i < argc; i++)
  {
    std::string inputFileName(argv[i]);

    std::cerr << "Testing " << inputFileName << std::endl;

    // 1. Read the .sbi file into a new attribute resource
    std::cerr << "\t Reading Original SBI" << std::endl;

    smtk::attribute::ResourcePtr resource;
    {
      auto importer = smtk::attribute::Import::create();
      importer->parameters()->findFile("filename")->setValue(inputFileName);

      auto result = importer->operate();
      if (
        result->findInt("outcome")->value() !=
        static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
      {
        std::cerr << "Import operator failed\n";
        std::cerr << importer->log().convertToString(true) << "\n";
        return -2;
      }

      resource = std::dynamic_pointer_cast<smtk::attribute::Resource>(
        result->findResource("resource")->value());
    }

    // 2. Write the resource out to a new .sbi file (to ensure it is of the
    //    latest format)
    std::cerr << "\t Writing Original SBI" << std::endl;

    std::string sbi1FileName;
    {
      std::stringstream s;
      s << write_root << "/"
        << "originalResource_" << unique_str << ".sbi";
      sbi1FileName = s.str();

      smtk::attribute::Export::Ptr exporter = smtk::attribute::Export::create();
      exporter->parameters()->associate(resource);
      exporter->parameters()->findFile("filename")->setValue(sbi1FileName);

      auto result = exporter->operate();
      if (
        result->findInt("outcome")->value() !=
        static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
      {
        std::cerr << "Export operator failed\n";
        std::cerr << exporter->log().convertToString(true) << "\n";
        return -2;
      }
    }

    // 3. Write the resource out to a .smtk file
    std::cerr << "\t Writing Original SMTK" << std::endl;

    std::string smtkFileName;
    {
      std::stringstream s;
      s << write_root << "/"
        << "jsonResource_" << unique_str << ".smtk";
      smtkFileName = s.str();
      resource->setLocation(smtkFileName);

      smtk::attribute::Write::Ptr writeOp = smtk::attribute::Write::create();
      writeOp->parameters()->associate(resource);
      auto result = writeOp->operate();

      if (
        result->findInt("outcome")->value() !=
        static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
      {
        std::cerr << "Write operation failed\n";
        std::cerr << writeOp->log().convertToString();
        return -2;
      }
    }

    // 4. Read the .smtk file into a new resource
    std::cerr << "\t Reading Original SMTK" << std::endl;

    smtk::attribute::Resource::Ptr copiedResource;
    {
      smtk::attribute::Read::Ptr readOp = smtk::attribute::Read::create();
      readOp->parameters()->findFile("filename")->setValue(smtkFileName);
      auto result = readOp->operate();

      if (
        result->findInt("outcome")->value() !=
        static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
      {
        std::cerr << "Read operation failed\n";
        std::cerr << readOp->log().convertToString();
        cleanup(smtkFileName);
        return -2;
      }
      else if (smtk::io::Logger::instance().numberOfRecords())
      {
        std::cerr << "Log after Read Operation:\n";
        std::cerr << smtk::io::Logger::instance().convertToString();
        smtk::io::Logger::instance().reset();
      }

      copiedResource = std::dynamic_pointer_cast<smtk::attribute::Resource>(
        result->findResource("resource")->value());
    }

    // 5. Write the new resource to a .sbi file
    std::cerr << "\t Writing SBI from SMTK" << std::endl;

    std::string sbi2FileName;
    {
      // Removing the copied resource location in order to match the original .sbi
      copiedResource->setLocation("");
      // Resolving links to update Surrogate instances
      copiedResource->links().resolve(copiedResource);

      std::stringstream s;
      s << write_root << "/"
        << "jsonResource_" << unique_str << ".sbi";
      sbi2FileName = s.str();

      smtk::io::Logger logger;
      smtk::io::AttributeWriter writer;
      if (writer.write(copiedResource, sbi2FileName, logger))
      {
        std::cerr << "Encountered Errors while writing " << sbi2FileName << "\n";
        std::cerr << logger.convertToString();
        cleanup(sbi2FileName);
        return -2;
      }
      else if (smtk::io::Logger::instance().numberOfRecords())
      {
        std::cerr << "Log after Write Operation:\n";
        std::cerr << smtk::io::Logger::instance().convertToString();
        smtk::io::Logger::instance().reset();
      }
    }

    // 6. Compare the .sbi files generated in steps 2 and 5

    int status = 0;
    {
      std::ifstream f1(sbi1FileName, std::ifstream::binary | std::ifstream::ate);
      std::ifstream f2(sbi2FileName, std::ifstream::binary | std::ifstream::ate);

      if (f1.fail() || f2.fail())
      {
        std::cerr << "Could not read in original or generated file\n\n";
        std::cerr << "generated smtk file: " << smtkFileName << "\n";
        std::cerr << "generated sbi1 file: " << sbi1FileName << "\n";
        std::cerr << "generated sbi2 file: " << sbi2FileName << "\n";
        status = -1;
      }

      if (f1.tellg() != f2.tellg())
      {
        std::cerr << "Original and generated file sizes are different\n\n";
        std::cerr << "generated smtk file: " << smtkFileName << "\n";
        std::cerr << "generated sbi1 file: " << sbi1FileName << "\n";
        std::cerr << "generated sbi2 file: " << sbi2FileName << "\n";
        status = -1;
      }

      f1.seekg(0, std::ifstream::beg);
      f2.seekg(0, std::ifstream::beg);
      if (!std::equal(
            std::istreambuf_iterator<char>(f1.rdbuf()),
            std::istreambuf_iterator<char>(),
            std::istreambuf_iterator<char>(f2.rdbuf())))
      {
        std::cerr << "Original and generated files are different\n\n";
        std::cerr << "generated smtk file: " << smtkFileName << "\n";
        std::cerr << "generated sbi1 file: " << sbi1FileName << "\n";
        std::cerr << "generated sbi2 file: " << sbi2FileName << "\n";
        status = -1;
      }
    }

    // Clean up all generated files
    cleanup(smtkFileName);
    cleanup(sbi1FileName);
    cleanup(sbi2FileName);
    if (status != 0)
    {
      return status;
    }
  }
  std::cout << "Pass all file tests" << std::endl;

  return 0;
}

ext: cxx

max_stars_repo_name: jcfr/SMTK

max_issues_repo_name: jcfr/SMTK

max_forks_repo_name: jcfr/SMTK


text:
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <config/module_spec.h>
#include <d2/d2_config.h>
#include <d2/d2_cfg_mgr.h>
#include <d_test_stubs.h>
#include <test_data_files_config.h>
#include <util/encode/base64.h>
#include <dhcpsrv/testutils/config_result_check.h>

#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>

using namespace std;
using namespace isc;
using namespace isc::d2;

namespace {

/// @brief Function to create full path to the spec file
///
/// The full path is dependent upon the value of D2_SRC_DIR which
/// whose value is generated from test_data_files_config.h.in
///
/// @param name file name to which the path should be prepended
std::string specfile(const std::string& name) {
    return (std::string(D2_SRC_DIR) + "/" + name);
}

/// @brief Function to create full path to test data file
///
/// The full path is dependent upon the value of D2_TEST_DATA_DIR which
/// whose value is generated from test_data_files_config.h.in
///
/// @param name file name to which the path should be prepended
std::string testDataFile(const std::string& name) {
    return (std::string(D2_TEST_DATA_DIR) + "/" + name);
}

/// @brief Test fixture class for testing D2CfgMgr class.
/// It maintains an member instance of D2CfgMgr and provides methods for
/// converting JSON strings to configuration element sets, checking parse
/// results, and accessing the configuration context.
class D2CfgMgrTest : public ConfigParseTest {
public:

    /// @brief Constructor
    D2CfgMgrTest():cfg_mgr_(new D2CfgMgr()), d2_params_() {
    }

    /// @brief Destructor
    ~D2CfgMgrTest() {
    }

    /// @brief Configuration manager instance.
    D2CfgMgrPtr cfg_mgr_;

    /// @brief Build JSON configuration string for a D2Params element
    ///
    /// Constructs a JSON string for "params" element using replacable
    /// parameters.
    ///
    /// @param ip_address string to insert as ip_address value
    /// @param port integer to insert as port value
    /// @param dns_server_timeout integer to insert as dns_server_timeout value
    /// @param ncr_protocol string to insert as ncr_protocol value
    /// @param ncr_format string to insert as ncr_format value
    ///
    /// @return std::string containing the JSON configuration text
    std::string makeParamsConfigString(const std::string& ip_address,
                                       const int port,
                                       const int dns_server_timeout,
                                       const std::string& ncr_protocol,
                                       const std::string& ncr_format) {
        std::ostringstream config;
        config <<
            "{"
            " \"ip-address\": \"" << ip_address << "\" , "
            " \"port\": " << port << " , "
            " \"dns-server-timeout\": " << dns_server_timeout << " , "
            " \"ncr-protocol\": \"" << ncr_protocol << "\" , "
            " \"ncr-format\": \"" << ncr_format << "\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
            "}";

        return (config.str());
    }
    /// @brief Enumeration to select between expected configuration outcomes
    enum RunConfigMode {
       SHOULD_PASS,
       SHOULD_FAIL
    };

    /// @brief Parses a configuration string and tests against a given outcome
    ///
    /// Convenience method which accepts JSON text and an expected pass or fail
    /// outcome. It converts the text into an ElementPtr and passes that to
    /// configuration manager's parseConfig method.  It then tests the
    /// parse result against the expected outcome  If they do not match it
    /// the method asserts a failure.   If they do match, it refreshes the
    /// the D2Params pointer with the newly parsed instance.
    ///
    /// @param config_str the JSON configuration text to parse
    /// @param mode indicator if the parsing should fail or not.  It defaults
    /// defaults to SHOULD_PASS.
    ///
    void runConfig(std::string config_str, RunConfigMode mode=SHOULD_PASS) {
        // We assume the config string is valid JSON.
        ASSERT_TRUE(fromJSON(config_str));

        // Parse the configuration and verify we got the expected outcome.
        answer_ = cfg_mgr_->parseConfig(config_set_);
        ASSERT_TRUE(checkAnswer(mode == SHOULD_FAIL));

        // Verify that the D2 context can be retrieved and is not null.
        D2CfgContextPtr context;
        ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());

        // Verify that the global scalars have the proper values.
        d2_params_ = context->getD2Params();
        ASSERT_TRUE(d2_params_);
    }

    /// @brief Check parse result against expected outcome and position info
    ///
    /// This method analyzes the given parsing result against an expected outcome
    /// of SHOULD_PASS or SHOULD_FAIL.  If it is expected to fail, the comment
    /// contained within the result is searched for Element::Position information
    /// which should contain the given file name.  It does not attempt to verify
    /// the numerical values for line number and col.
    ///
    /// @param answer Element set containing an integer result code and string
    /// comment.
    /// @param mode indicator if the parsing should fail or not.
    /// @param file_name name of the file containing the configuration text
    /// parsed. It defaults to "<string>" which is the value present if the
    /// configuration text did not originate from a file. (i.e. one did not use
    /// isc::data::Element::fromJSONFile() to read the JSON text).
    void
    checkAnswerWithError(isc::data::ConstElementPtr answer,
                         RunConfigMode mode, std::string file_name="<string>") {
        int rcode = 0;
        isc::data::ConstElementPtr comment;
        comment = isc::config::parseAnswer(rcode, answer);

        if (mode == SHOULD_PASS) {
            if (rcode == 0) {
                return;
            }

            FAIL() << "Parsing was expected to pass but failed : " << rcode
                   << " comment: " << *comment;
        }

        if (rcode == 0) {
            FAIL() << "Parsing was expected to fail but passed : "
                   << " comment: " << *comment;
        }

        // Parsing was expected to fail, test for position info.
        if (isc::dhcp::test::errorContainsPosition(answer, file_name)) {
            return;
        }

        FAIL() << "Parsing failed as expected but lacks position : " << *comment;
    }

    /// @brief Pointer the D2Params most recently parsed.
    D2ParamsPtr d2_params_;
};

/// @brief Tests that the spec file is valid.
/// Verifies that the DHCP-DDNS configuration specification file
//  is valid.
TEST(D2SpecTest, basicSpec) {
    ASSERT_NO_THROW(isc::config::
                    moduleSpecFromFile(specfile("dhcp-ddns.spec")));
}

/// @brief Convenience function which compares the contents of the given
/// DnsServerInfo against the given set of values.
///
/// It is structured in such a way that each value is checked, and output
/// is generate for all that do not match.
///
/// @param server is a pointer to the server to check against.
/// @param hostname is the value to compare against server's hostname_.
/// @param ip_address is the string value to compare against server's
/// ip_address_.
/// @param port is the value to compare against server's port.
///
/// @return returns true if there is a match across the board, otherwise it
/// returns false.
bool checkServer(DnsServerInfoPtr server, const char* hostname,
                 const char *ip_address, uint32_t port)
{
    // Return value, assume its a match.
    bool result = true;

    if (!server) {
        EXPECT_TRUE(server);
        return false;
    }

    // Check hostname.
    if (server->getHostname() != hostname) {
        EXPECT_EQ(hostname, server->getHostname());
        result = false;
    }

    // Check IP address.
    if (server->getIpAddress().toText() != ip_address) {
        EXPECT_EQ(ip_address, server->getIpAddress().toText());
        result = false;
    }

    // Check port.
    if (server->getPort() !=  port) {
        EXPECT_EQ (port, server->getPort());
        result = false;
    }

    return (result);
}

/// @brief Convenience function which compares the contents of the given
/// TSIGKeyInfo against the given set of values, and that the TSIGKey
/// member points to a key.
///
/// @param key is a pointer to the TSIGKeyInfo instance to verify
/// @param name is the value to compare against key's name_.
/// @param algorithm is the string value to compare against key's algorithm.
/// @param secret is the value to compare against key's secret.
///
/// @return returns true if there is a match across the board, otherwise it
/// returns false.
bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
              const std::string& algorithm, const std::string& secret,
              uint32_t digestbits = 0) {
    // Return value, assume its a match.
    return (((key) &&
        (key->getName() == name) &&
        (key->getAlgorithm() == algorithm)  &&
        (key->getDigestbits() == digestbits) &&
        (key->getSecret() ==  secret)  &&
        (key->getTSIGKey())));
}

/// @brief Test fixture class for testing DnsServerInfo parsing.
class TSIGKeyInfoTest : public ConfigParseTest {
public:

    /// @brief Constructor
    TSIGKeyInfoTest() {
        reset();
    }

    /// @brief Destructor
    ~TSIGKeyInfoTest() {
    }

    /// @brief Wipe out the current storage and parser and replace
    /// them with new ones.
    void reset() {
        keys_.reset(new TSIGKeyInfoMap());
        parser_.reset(new TSIGKeyInfoParser("test", keys_));
    }

    /// @brief Storage for "committing" keys.
    TSIGKeyInfoMapPtr keys_;

    /// @brief Pointer to the current parser instance.
    isc::dhcp::ParserPtr parser_;
};

/// @brief Test fixture class for testing DnsServerInfo parsing.
class DnsServerInfoTest : public ConfigParseTest {
public:

    /// @brief Constructor
    DnsServerInfoTest() {
        reset();
    }

    /// @brief Destructor
    ~DnsServerInfoTest() {
    }

    /// @brief Wipe out the current storage and parser and replace
    /// them with new ones.
    void reset() {
        servers_.reset(new DnsServerInfoStorage());
        parser_.reset(new DnsServerInfoParser("test", servers_));
    }

    /// @brief Storage for "committing" servers.
    DnsServerInfoStoragePtr servers_;

    /// @brief Pointer to the current parser instance.
    isc::dhcp::ParserPtr parser_;
};

/// @brief Test fixture class for testing DDnsDomain parsing.
class DdnsDomainTest : public ConfigParseTest {
public:

    /// @brief Constructor
    DdnsDomainTest() {
        reset();
    }

    /// @brief Destructor
    ~DdnsDomainTest() {
    }

    /// @brief Wipe out the current storage and parser and replace
    /// them with new ones.
    void reset() {
        keys_.reset(new TSIGKeyInfoMap());
        domains_.reset(new DdnsDomainMap());
        parser_.reset(new DdnsDomainParser("test", domains_, keys_));
    }

    /// @brief Add TSIGKeyInfos to the key map
    ///
    /// @param name the name of the key
    /// @param algorithm the algorithm of the key
    /// @param secret the secret value of the key
    void addKey(const std::string& name, const std::string& algorithm,
                const std::string& secret) {
        TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
        (*keys_)[name]=key_info;
    }

    /// @brief Storage for "committing" domains.
    DdnsDomainMapPtr domains_;

    /// @brief Storage for TSIGKeys
    TSIGKeyInfoMapPtr keys_;

    /// @brief Pointer to the current parser instance.
    isc::dhcp::ParserPtr parser_;
};

/// @brief Tests a basic valid configuration for D2Param.
TEST_F(D2CfgMgrTest, validParamsEntry) {
    // Verify that ip_address can be valid v4 address.
    std::string config = makeParamsConfigString ("192.0.0.1", 777, 333,
                                           "UDP", "JSON");
    runConfig(config);

    EXPECT_EQ(isc::asiolink::IOAddress("192.0.0.1"),
              d2_params_->getIpAddress());
    EXPECT_EQ(777, d2_params_->getPort());
    EXPECT_EQ(333, d2_params_->getDnsServerTimeout());
    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_params_->getNcrProtocol());
    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_params_->getNcrFormat());

    // Verify that ip_address can be valid v6 address.
    config = makeParamsConfigString ("3001::5", 777, 333, "UDP", "JSON");
    runConfig(config);

    // Verify that the global scalars have the proper values.
    EXPECT_EQ(isc::asiolink::IOAddress("3001::5"),
              d2_params_->getIpAddress());

    // Verify the configuration summary.
    EXPECT_EQ("listening on 3001::5, port 777, using UDP",
              d2_params_->getConfigSummary());
}

/// @brief Tests default values for D2Params.
/// It verifies that D2Params is populated with default value for optional
/// parameter if not supplied in the configuration.
/// Currently they are all optional.
TEST_F(D2CfgMgrTest, defaultValues) {

    // Check that omitting ip_address gets you its default
    std::string config =
            "{"
            " \"port\": 777 , "
            " \"dns-server-timeout\": 333 , "
            " \"ncr-protocol\": \"UDP\" , "
            " \"ncr-format\": \"JSON\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
            "}";

    runConfig(config);
    EXPECT_EQ(isc::asiolink::IOAddress(D2Params::DFT_IP_ADDRESS),
              d2_params_->getIpAddress());

    // Check that omitting port gets you its default
    config =
            "{"
            " \"ip-address\": \"192.0.0.1\" , "
            " \"dns-server-timeout\": 333 , "
            " \"ncr-protocol\": \"UDP\" , "
            " \"ncr-format\": \"JSON\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
            "}";

    runConfig(config);
    EXPECT_EQ(D2Params::DFT_PORT, d2_params_->getPort());

    // Check that omitting timeout gets you its default
    config =
            "{"
            " \"ip-address\": \"192.0.0.1\" , "
            " \"port\": 777 , "
            " \"ncr-protocol\": \"UDP\" , "
            " \"ncr-format\": \"JSON\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
            "}";

    runConfig(config);
    EXPECT_EQ(D2Params::DFT_DNS_SERVER_TIMEOUT,
              d2_params_->getDnsServerTimeout());

    // Check that protocol timeout gets you its default
    config =
            "{"
            " \"ip-address\": \"192.0.0.1\" , "
            " \"port\": 777 , "
            " \"dns-server-timeout\": 333 , "
            " \"ncr-format\": \"JSON\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
            "}";

    runConfig(config);
    EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(D2Params::DFT_NCR_PROTOCOL),
              d2_params_->getNcrProtocol());

    // Check that format timeout gets you its default
    config =
            "{"
            " \"ip-address\": \"192.0.0.1\" , "
            " \"port\": 777 , "
            " \"dns-server-timeout\": 333 , "
            " \"ncr-protocol\": \"UDP\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {} "
            "}";

    runConfig(config);
    EXPECT_EQ(dhcp_ddns::stringToNcrFormat(D2Params::DFT_NCR_FORMAT),
              d2_params_->getNcrFormat());
}

/// @brief Tests the unsupported scalar parameters and objects are detected.
TEST_F(D2CfgMgrTest, unsupportedTopLevelItems) {
    // Check that an unsupported top level parameter fails.
    std::string config =
            "{"
            " \"ip-address\": \"127.0.0.1\", "
            " \"port\": 777 , "
            " \"dns-server-timeout\": 333 , "
            " \"ncr-protocol\": \"UDP\" , "
            " \"ncr-format\": \"JSON\", "
            "\"tsig-keys\": [], "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {}, "
            "\"bogus-param\" : true "
            "}";

    runConfig(config, SHOULD_FAIL);

    // Check that unsupported top level objects fails.  For
    // D2 these fail as they are not in the parse order.
    config =
            "{"
            " \"ip-address\": \"127.0.0.1\", "
            " \"port\": 777 , "
            " \"dns-server-timeout\": 333 , "
            " \"ncr-protocol\": \"UDP\" , "
            " \"ncr-format\": \"JSON\", "
            "\"tsig-keys\": [], "
            "\"bogus-object-one\" : {}, "
            "\"forward-ddns\" : {}, "
            "\"reverse-ddns\" : {}, "
            "\"bogus-object-two\" : {} "
            "}";

    runConfig(config, SHOULD_FAIL);
}

/// @brief Tests the enforcement of data validation when parsing D2Params.
/// It verifies that:
/// -# ip_address cannot be "0.0.0.0"
/// -# ip_address cannot be "::"
/// -# port cannot be 0
/// -# dns_server_timeout cannat be 0
/// -# ncr_protocol must be valid
/// -# ncr_format must be valid
TEST_F(D2CfgMgrTest, invalidEntry) {
    // Cannot use IPv4 ANY address
    std::string config = makeParamsConfigString ("0.0.0.0", 777, 333,
                                           "UDP", "JSON");
    runConfig(config, SHOULD_FAIL);

    // Cannot use IPv6 ANY address
    config = makeParamsConfigString ("::", 777, 333, "UDP", "JSON");
    runConfig(config, SHOULD_FAIL);

    // Cannot use port  0
    config = makeParamsConfigString ("127.0.0.1", 0, 333, "UDP", "JSON");
    runConfig(config, SHOULD_FAIL);

    // Cannot use dns server timeout of 0
    config = makeParamsConfigString ("127.0.0.1", 777, 0, "UDP", "JSON");
    runConfig(config, SHOULD_FAIL);

    // Invalid protocol
    config = makeParamsConfigString ("127.0.0.1", 777, 333, "BOGUS", "JSON");
    runConfig(config, SHOULD_FAIL);

    // Unsupported protocol
    config = makeParamsConfigString ("127.0.0.1", 777, 333, "TCP", "JSON");
    runConfig(config, SHOULD_FAIL);

    // Invalid format
    config = makeParamsConfigString ("127.0.0.1", 777, 333, "UDP", "BOGUS");
    runConfig(config, SHOULD_FAIL);
}

/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
/// It verifies that:
/// 1. Name cannot be blank.
/// 2. Algorithm cannot be blank.
/// 3. Secret cannot be blank.
TEST_F(TSIGKeyInfoTest, invalidEntry) {
    // Config with a blank name entry.
    std::string config = "{"
                         " \"name\": \"\" , "
                         " \"algorithm\": \"HMAC-MD5\" , "
                         "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                         "}";
    ASSERT_TRUE(fromJSON(config));

    // Verify that build fails on blank name.
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Config with a blank algorithm entry.
    config = "{"
                         " \"name\": \"d2_key_one\" , "
                         " \"algorithm\": \"\" , "
                         "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                         "}";

    ASSERT_TRUE(fromJSON(config));

    // Verify that build fails on blank algorithm.
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Config with an invalid algorithm entry.
    config = "{"
                         " \"name\": \"d2_key_one\" , "
                         " \"algorithm\": \"bogus\" , "
                         "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                         "}";

    ASSERT_TRUE(fromJSON(config));

    // Verify that build fails on blank algorithm.
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Config with a blank secret entry.
    config = "{"
                         " \"name\": \"d2_key_one\" , "
                         " \"algorithm\": \"HMAC-MD5\" , "
                         " \"secret\": \"\" "
                         "}";

    ASSERT_TRUE(fromJSON(config));

    // Verify that build fails blank secret
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Config with an invalid secret entry.
    config = "{"
                         " \"name\": \"d2_key_one\" , "
                         " \"algorithm\": \"HMAC-MD5\" , "
                         " \"secret\": \"bogus\" "
                         "}";

    ASSERT_TRUE(fromJSON(config));

    // Verify that build fails an invalid secret
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
}

/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
/// when given a valid combination of entries.
TEST_F(TSIGKeyInfoTest, validEntry) {
    // Valid entries for TSIG key, all items are required.
    std::string config = "{"
                         " \"name\": \"d2_key_one\" , "
                         " \"algorithm\": \"HMAC-MD5\" , "
                         " \"digest-bits\": 120 , "
                         " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         "}";
    ASSERT_TRUE(fromJSON(config));

    // Verify that it builds and commits without throwing.
    //ASSERT_NO_THROW(parser_->build(config_set_));
    (parser_->build(config_set_));
    ASSERT_NO_THROW(parser_->commit());

    // Verify the correct number of keys are present
    int count =  keys_->size();
    EXPECT_EQ(1, count);

    // Find the key and retrieve it.
    TSIGKeyInfoMap::iterator gotit = keys_->find("d2_key_one");
    ASSERT_TRUE(gotit != keys_->end());
    TSIGKeyInfoPtr& key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "d2_key_one", "HMAC-MD5",
                         "dGhpcyBrZXkgd2lsbCBtYXRjaA==", 120));
}

/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
/// entries is detected.
TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
    // Construct a list of keys with an invalid key entry.
    std::string config = "["

                         " { \"name\": \"key1\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         " \"digest-bits\": 120 , "
                         "   \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
                         " },"
                         // this entry has an invalid algorithm
                         " { \"name\": \"key2\" , "
                         "   \"algorithm\": \"\" ,"
                         " \"digest-bits\": 120 , "
                         "   \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
                         " },"
                         " { \"name\": \"key3\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         "   \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
                         " }"
                         " ]";

    ASSERT_TRUE(fromJSON(config));

    // Create the list parser.
    isc::dhcp::ParserPtr parser;
    ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));

    // Verify that the list builds without errors.
    EXPECT_THROW(parser->build(config_set_), D2CfgError);
}

/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
/// entries is detected.
TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
    // Construct a list of keys with an invalid key entry.
    std::string config = "["

                         " { \"name\": \"key1\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         " \"digest-bits\": 120 , "
                         "   \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
                         " },"
                         " { \"name\": \"key2\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         " \"digest-bits\": 120 , "
                         "   \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
                         " },"
                         " { \"name\": \"key1\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         "   \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
                         " }"
                         " ]";

    ASSERT_TRUE(fromJSON(config));

    // Create the list parser.
    isc::dhcp::ParserPtr parser;
    ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));

    // Verify that the list builds without errors.
    EXPECT_THROW(parser->build(config_set_), D2CfgError);
}

/// @brief Verifies a valid list of TSIG Keys parses correctly.
/// Also verifies that all of the supported algorithm names work.
TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
    // Construct a valid list of keys.
    std::string config = "["

                         " { \"name\": \"key1\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         " \"digest-bits\": 80 , "
                         "  \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         " },"
                         " { \"name\": \"key2\" , "
                         "   \"algorithm\": \"HMAC-SHA1\" ,"
                         " \"digest-bits\": 80 , "
                         "  \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         " },"
                         " { \"name\": \"key3\" , "
                         "   \"algorithm\": \"HMAC-SHA256\" ,"
                         " \"digest-bits\": 128 , "
                         "  \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         " },"
                         " { \"name\": \"key4\" , "
                         "   \"algorithm\": \"HMAC-SHA224\" ,"
                         " \"digest-bits\": 112 , "
                         "  \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         " },"
                         " { \"name\": \"key5\" , "
                         "   \"algorithm\": \"HMAC-SHA384\" ,"
                         " \"digest-bits\": 192 , "
                         "  \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         " },"
                         " { \"name\": \"key6\" , "
                         "   \"algorithm\": \"HMAC-SHA512\" ,"
                         " \"digest-bits\": 256 , "
                         "   \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
                         " }"
                         " ]";

    ASSERT_TRUE(fromJSON(config));

    // Verify that the list builds and commits without errors.
    // Create the list parser.
    isc::dhcp::ParserPtr parser;
    ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
    ASSERT_NO_THROW(parser->build(config_set_));
    ASSERT_NO_THROW(parser->commit());

    std::string ref_secret = "dGhpcyBrZXkgd2lsbCBtYXRjaA==";
    // Verify the correct number of keys are present
    int count =  keys_->size();
    ASSERT_EQ(6, count);

    // Find the 1st key and retrieve it.
    TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
    ASSERT_TRUE(gotit != keys_->end());
    TSIGKeyInfoPtr& key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR,
                         ref_secret, 80));

    // Find the 2nd key and retrieve it.
    gotit = keys_->find("key2");
    ASSERT_TRUE(gotit != keys_->end());
    key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR,
                         ref_secret, 80));

    // Find the 3rd key and retrieve it.
    gotit = keys_->find("key3");
    ASSERT_TRUE(gotit != keys_->end());
    key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "key3", TSIGKeyInfo::HMAC_SHA256_STR,
                         ref_secret, 128));

    // Find the 4th key and retrieve it.
    gotit = keys_->find("key4");
    ASSERT_TRUE(gotit != keys_->end());
    key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
                         ref_secret, 112));

    // Find the 5th key and retrieve it.
    gotit = keys_->find("key5");
    ASSERT_TRUE(gotit != keys_->end());
    key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
                         ref_secret, 192));

    // Find the 6th key and retrieve it.
    gotit = keys_->find("key6");
    ASSERT_TRUE(gotit != keys_->end());
    key = gotit->second;

    // Verify the key contents.
    EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
                         ref_secret, 256));
}

/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
/// It verifies that:
/// 1. Specifying both a hostname and an ip address is not allowed.
/// 2. Specifying both blank a hostname and blank ip address is not allowed.
/// 3. Specifying a negative port number is not allowed.
TEST_F(DnsServerInfoTest, invalidEntry) {
    // Create a config in which both host and ip address are supplied.
    // Verify that build fails.
    std::string config = "{ \"hostname\": \"pegasus.tmark\", "
                         "  \"ip-address\": \"127.0.0.1\" } ";
    ASSERT_TRUE(fromJSON(config));
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Neither host nor ip address supplied
    // Verify that builds fails.
    config = "{ \"hostname\": \"\", "
             "  \"ip-address\": \"\" } ";
    ASSERT_TRUE(fromJSON(config));
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Create a config with a negative port number.
    // Verify that build fails.
    config = "{ \"ip-address\": \"192.168.5.6\" ,"
             "  \"port\": -100 }";
    ASSERT_TRUE(fromJSON(config));
    EXPECT_THROW (parser_->build(config_set_), isc::BadValue);
}

/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
/// when given a valid combination of entries.
/// It verifies that:
/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
TEST_F(DnsServerInfoTest, validEntry) {
    /// @todo When resolvable hostname is supported you'll need this test.
    /// // Valid entries for dynamic host
    /// std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
    /// ASSERT_TRUE(fromJSON(config));

    /// // Verify that it builds and commits without throwing.
    /// ASSERT_NO_THROW(parser_->build(config_set_));
    /// ASSERT_NO_THROW(parser_->commit());

    /// //Verify the correct number of servers are present
    /// int count =  servers_->size();
    /// EXPECT_EQ(1, count);

    /// Verify the server exists and has the correct values.
    /// DnsServerInfoPtr server = (*servers_)[0];
    /// EXPECT_TRUE(checkServer(server, "pegasus.tmark",
    ///                         DnsServerInfo::EMPTY_IP_STR,
    ///                         DnsServerInfo::STANDARD_DNS_PORT));

    /// // Start over for a new test.
    /// reset();

    // Valid entries for static ip
    std::string config = " { \"ip-address\": \"127.0.0.1\" , "
                         "  \"port\": 100 }";
    ASSERT_TRUE(fromJSON(config));

    // Verify that it builds and commits without throwing.
    ASSERT_NO_THROW(parser_->build(config_set_));
    ASSERT_NO_THROW(parser_->commit());

    // Verify the correct number of servers are present
    int count =  servers_->size();
    EXPECT_EQ(1, count);

    // Verify the server exists and has the correct values.
    DnsServerInfoPtr server = (*servers_)[0];
    EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));

    // Start over for a new test.
    reset();

    // Valid entries for static ip, no port
    config = " { \"ip-address\": \"192.168.2.5\" }";
    ASSERT_TRUE(fromJSON(config));

    // Verify that it builds and commits without throwing.
    ASSERT_NO_THROW(parser_->build(config_set_));
    ASSERT_NO_THROW(parser_->commit());

    // Verify the correct number of servers are present
    count =  servers_->size();
    EXPECT_EQ(1, count);

    // Verify the server exists and has the correct values.
    server = (*servers_)[0];
    EXPECT_TRUE(checkServer(server, "", "192.168.2.5",
                            DnsServerInfo::STANDARD_DNS_PORT));
}

/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
/// entries is detected.
TEST_F(ConfigParseTest, invalidServerList) {
    // Construct a list of servers with an invalid server entry.
    std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
                        "{ \"ip-address\": \"\" }, "
                        "{ \"ip-address\": \"127.0.0.2\" } ]";
    ASSERT_TRUE(fromJSON(config));

    // Create the server storage and list parser.
    DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
    isc::dhcp::ParserPtr parser;
    ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));

    // Verify that build fails.
    EXPECT_THROW(parser->build(config_set_), D2CfgError);
}

/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
/// a valid configuration.
TEST_F(ConfigParseTest, validServerList) {
    // Create a valid list of servers.
    std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
                        "{ \"ip-address\": \"127.0.0.2\" }, "
                        "{ \"ip-address\": \"127.0.0.3\" } ]";
    ASSERT_TRUE(fromJSON(config));

    // Create the server storage and list parser.
    DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
    isc::dhcp::ParserPtr parser;
    ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));

    // Verfiy that the list builds and commits without error.
    ASSERT_NO_THROW(parser->build(config_set_));
    ASSERT_NO_THROW(parser->commit());

    // Verify that the server storage contains the correct number of servers.
    int count =  servers->size();
    EXPECT_EQ(3, count);

    // Verify the first server exists and has the correct values.
    DnsServerInfoPtr server = (*servers)[0];
    EXPECT_TRUE(checkServer(server, "", "127.0.0.1",
                            DnsServerInfo::STANDARD_DNS_PORT));

    // Verify the second server exists and has the correct values.
    server = (*servers)[1];
    EXPECT_TRUE(checkServer(server, "", "127.0.0.2",
                            DnsServerInfo::STANDARD_DNS_PORT));

    // Verify the third server exists and has the correct values.
    server = (*servers)[2];
    EXPECT_TRUE(checkServer(server, "", "127.0.0.3",
                            DnsServerInfo::STANDARD_DNS_PORT));
}

/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
/// It verifies that:
/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
/// 2. The name entry is not optional.
/// 3. The server list man not be empty.
/// 4. That a mal-formed server entry is detected.
/// 5. That an undefined key name is detected.
TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
    // Verify that attempting to construct the parser with null storage fails.
    DdnsDomainMapPtr domains;
    ASSERT_THROW(isc::dhcp::ParserPtr(
                 new DdnsDomainParser("test", domains, keys_)), D2CfgError);

    // Create a domain configuration without a name
    std::string config = "{  \"key-name\": \"d2_key.tmark.org\" , "
                         "  \"dns-servers\" : [ "
                         "  {  \"ip-address\": \"127.0.0.1\" , "
                         "    \"port\": 100 },"
                         "  { \"ip-address\": \"127.0.0.2\" , "
                         "    \"port\": 200 },"
                         "  {  \"ip-address\": \"127.0.0.3\" , "
                         "    \"port\": 300 } ] } ";
    ASSERT_TRUE(fromJSON(config));

    // Verify that the domain configuration builds fails.
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Create a domain configuration with an empty server list.
    config = "{ \"name\": \"tmark.org\" , "
             "  \"key-name\": \"d2_key.tmark.org\" , "
             "  \"dns-servers\" : [ "
             "   ] } ";
    ASSERT_TRUE(fromJSON(config));

    // Verify that the domain configuration build fails.
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);

    // Create a domain configuration with a mal-formed server entry.
    config = "{ \"name\": \"tmark.org\" , "
             "  \"key-name\": \"d2_key.tmark.org\" , "
             "  \"dns-servers\" : [ "
             "  {  \"ip-address\": \"127.0.0.3\" , "
             "    \"port\": -1 } ] } ";
    ASSERT_TRUE(fromJSON(config));

    // Verify that the domain configuration build fails.
    EXPECT_THROW(parser_->build(config_set_), isc::BadValue);

    // Create a domain configuration without an defined key name
    config = "{ \"name\": \"tmark.org\" , "
             "  \"key-name\": \"d2_key.tmark.org\" , "
             "  \"dns-servers\" : [ "
             "  {  \"ip-address\": \"127.0.0.3\" , "
             "    \"port\": 300 } ] } ";
    ASSERT_TRUE(fromJSON(config));

    // Verify that the domain configuration build fails.
    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
}

/// @brief Verifies the basics of parsing DdnsDomains.
/// It verifies that:
/// 1. Valid construction of DdnsDomainParser functions.
/// 2. Given a valid, configuration entry, DdnsDomainParser parses
/// correctly.
/// (It indirectly verifies the operation of DdnsDomainMap).
TEST_F(DdnsDomainTest, ddnsDomainParsing) {
    // Create a valid domain configuration entry containing three valid
    // servers.
    std::string config =
                        "{ \"name\": \"tmark.org\" , "
                        "  \"key-name\": \"d2_key.tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  {  \"ip-address\": \"127.0.0.1\" , "
                        "    \"port\": 100 },"
                        "  { \"ip-address\": \"127.0.0.2\" , "
                        "    \"port\": 200 },"
                        "  {  \"ip-address\": \"127.0.0.3\" , "
                        "    \"port\": 300 } ] } ";
    ASSERT_TRUE(fromJSON(config));

    // Add a TSIG key to the test key map, so key validation will pass.
    addKey("d2_key.tmark.org", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");

    // Verify that the domain configuration builds and commits without error.
    ASSERT_NO_THROW(parser_->build(config_set_));
    ASSERT_NO_THROW(parser_->commit());

    // Verify that the domain storage contains the correct number of domains.
    int count =  domains_->size();
    EXPECT_EQ(1, count);

    // Verify that the expected domain exists and can be retrieved from
    // the storage.
    DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
    ASSERT_TRUE(gotit != domains_->end());
    DdnsDomainPtr& domain = gotit->second;

    // Verify the name and key_name values.
    EXPECT_EQ("tmark.org", domain->getName());
    EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
    ASSERT_TRUE(domain->getTSIGKeyInfo());
    ASSERT_TRUE(domain->getTSIGKeyInfo()->getTSIGKey());

    // Verify that the server list exists and contains the correct number of
    // servers.
    const DnsServerInfoStoragePtr& servers = domain->getServers();
    EXPECT_TRUE(servers);
    count =  servers->size();
    EXPECT_EQ(3, count);

    // Fetch each server and verify its contents.
    DnsServerInfoPtr server = (*servers)[0];
    EXPECT_TRUE(server);

    EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));

    server = (*servers)[1];
    EXPECT_TRUE(server);

    EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));

    server = (*servers)[2];
    EXPECT_TRUE(server);

    EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
}

/// @brief Tests the fundamentals of parsing DdnsDomain lists.
/// This test verifies that given a valid domain list configuration
/// it will accurately parse and populate each domain in the list.
TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
    // Create a valid domain list configuration, with two domains
    // that have three servers each.
    std::string config =
                        "[ "
                        "{ \"name\": \"tmark.org\" , "
                        "  \"key-name\": \"d2_key.tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" , "
                        "    \"port\": 100 },"
                        "  { \"ip-address\": \"127.0.0.2\" , "
                        "    \"port\": 200 },"
                        "  { \"ip-address\": \"127.0.0.3\" , "
                        "    \"port\": 300 } ] } "
                        ", "
                        "{ \"name\": \"billcat.net\" , "
                        "  \"key-name\": \"d2_key.billcat.net\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.4\" , "
                        "    \"port\": 400 },"
                        "  { \"ip-address\": \"127.0.0.5\" , "
                        "    \"port\": 500 },"
                        "  { \"ip-address\": \"127.0.0.6\" , "
                        "    \"port\": 600 } ] } "
                        "] ";

    ASSERT_TRUE(fromJSON(config));

    // Add keys to key map so key validation passes.
    addKey("d2_key.tmark.org", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
    addKey("d2_key.billcat.net", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");

    // Create the list parser
    isc::dhcp::ParserPtr list_parser;
    ASSERT_NO_THROW(list_parser.reset(
                    new DdnsDomainListParser("test", domains_, keys_)));

    // Verify that the domain configuration builds and commits without error.
    ASSERT_NO_THROW(list_parser->build(config_set_));
    ASSERT_NO_THROW(list_parser->commit());

    // Verify that the domain storage contains the correct number of domains.
    int count =  domains_->size();
    EXPECT_EQ(2, count);

    // Verify that the first domain exists and can be retrieved.
    DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
    ASSERT_TRUE(gotit != domains_->end());
    DdnsDomainPtr& domain = gotit->second;

    // Verify the name and key_name values of the first domain.
    EXPECT_EQ("tmark.org", domain->getName());
    EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
    ASSERT_TRUE(domain->getTSIGKeyInfo());
    ASSERT_TRUE(domain->getTSIGKeyInfo()->getTSIGKey());

    // Verify the each of the first domain's servers
    DnsServerInfoStoragePtr servers = domain->getServers();
    EXPECT_TRUE(servers);
    count =  servers->size();
    EXPECT_EQ(3, count);

    DnsServerInfoPtr server = (*servers)[0];
    EXPECT_TRUE(server);
    EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));

    server = (*servers)[1];
    EXPECT_TRUE(server);
    EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));

    server = (*servers)[2];
    EXPECT_TRUE(server);
    EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));

    // Verify second domain
    gotit = domains_->find("billcat.net");
    ASSERT_TRUE(gotit != domains_->end());
    domain = gotit->second;

    // Verify the name and key_name values of the second domain.
    EXPECT_EQ("billcat.net", domain->getName());
    EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
    ASSERT_TRUE(domain->getTSIGKeyInfo());
    ASSERT_TRUE(domain->getTSIGKeyInfo()->getTSIGKey());

    // Verify the each of second domain's servers
    servers = domain->getServers();
    EXPECT_TRUE(servers);
    count =  servers->size();
    EXPECT_EQ(3, count);

    server = (*servers)[0];
    EXPECT_TRUE(server);
    EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));

    server = (*servers)[1];
    EXPECT_TRUE(server);
    EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));

    server = (*servers)[2];
    EXPECT_TRUE(server);
    EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
}

/// @brief Tests that a domain list configuration cannot contain duplicates.
TEST_F(DdnsDomainTest, duplicateDomain) {
    // Create a domain list configuration that contains two domains with
    // the same name.
    std::string config =
                        "[ "
                        "{ \"name\": \"tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.3\" , "
                        "    \"port\": 300 } ] } "
                        ", "
                        "{ \"name\": \"tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.3\" , "
                        "    \"port\": 300 } ] } "
                        "] ";
    ASSERT_TRUE(fromJSON(config));

    // Create the list parser
    isc::dhcp::ParserPtr list_parser;
    ASSERT_NO_THROW(list_parser.reset(
                    new DdnsDomainListParser("test", domains_, keys_)));

    // Verify that the parse build fails.
    EXPECT_THROW(list_parser->build(config_set_), D2CfgError);
}

/// @brief Tests construction of D2CfgMgr
/// This test verifies that a D2CfgMgr constructs properly.
TEST(D2CfgMgr, construction) {
    boost::scoped_ptr<D2CfgMgr> cfg_mgr;

    // Verify that configuration manager constructions without error.
    ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));

    // Verify that the context can be retrieved and is not null.
    D2CfgContextPtr context;
    ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
    EXPECT_TRUE(context);

    // Verify that the forward manager can be retrieved and is not null.
    EXPECT_TRUE(context->getForwardMgr());

    // Verify that the reverse manager can be retrieved and is not null.
    EXPECT_TRUE(context->getReverseMgr());

    // Verify that the manager can be destructed without error.
    EXPECT_NO_THROW(cfg_mgr.reset());
}

/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
/// This tests passes the configuration into an instance of D2CfgMgr just
/// as it would be done by d2_process in response to a configuration update
/// event.
TEST_F(D2CfgMgrTest, fullConfig) {
    // Create a configuration with all of application level parameters, plus
    // both the forward and reverse ddns managers.  Both managers have two
    // domains with three servers per domain.
    std::string config = "{ "
                        "\"ip-address\" : \"192.168.1.33\" , "
                        "\"port\" : 88 , "
                        " \"dns-server-timeout\": 333 , "
                        " \"ncr-protocol\": \"UDP\" , "
                        " \"ncr-format\": \"JSON\", "
                        "\"tsig-keys\": ["
                        "{"
                        "  \"name\": \"d2_key.tmark.org\" , "
                        "  \"algorithm\": \"hmac-md5\" , "
                        "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                        "},"
                        "{"
                        "  \"name\": \"d2_key.billcat.net\" , "
                        "  \"algorithm\": \"hmac-md5\" , "
                        " \"digest-bits\": 120 , "
                        "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                        "}"
                        "],"
                        "\"forward-ddns\" : {"
                        "\"ddns-domains\": [ "
                        "{ \"name\": \"tmark.org\" , "
                        "  \"key-name\": \"d2_key.tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } , "
                        "  { \"ip-address\": \"127.0.0.2\" } , "
                        "  { \"ip-address\": \"127.0.0.3\"} "
                        "  ] } "
                        ", "
                        "{ \"name\": \"billcat.net\" , "
                        "  \"key-name\": \"d2_key.billcat.net\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.4\" } , "
                        "  { \"ip-address\": \"127.0.0.5\" } , "
                        "  { \"ip-address\": \"127.0.0.6\" } "
                        "  ] } "
                        "] },"
                        "\"reverse-ddns\" : {"
                        "\"ddns-domains\": [ "
                        "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
                        "  \"key-name\": \"d2_key.tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.1.1\" } , "
                        "  { \"ip-address\": \"127.0.2.1\" } , "
                        "  { \"ip-address\": \"127.0.3.1\" } "
                        "  ] } "
                        ", "
                        "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
                        "  \"key-name\": \"d2_key.billcat.net\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.4.1\" }, "
                        "  { \"ip-address\": \"127.0.5.1\" } , "
                        "  { \"ip-address\": \"127.0.6.1\" } "
                        "  ] } "
                        "] } }";

    ASSERT_TRUE(fromJSON(config));

    // Verify that we can parse the configuration.
    answer_ = cfg_mgr_->parseConfig(config_set_);
    ASSERT_TRUE(checkAnswer(0));

    // Verify that the D2 context can be retrieved and is not null.
    D2CfgContextPtr context;
    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());

    // Verify that the global scalars have the proper values.
    D2ParamsPtr& d2_params = context->getD2Params();
    ASSERT_TRUE(d2_params);

    EXPECT_EQ(isc::asiolink::IOAddress("192.168.1.33"),
              d2_params->getIpAddress());
    EXPECT_EQ(88, d2_params->getPort());
    EXPECT_EQ(333, d2_params->getDnsServerTimeout());
    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_params->getNcrProtocol());
    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_params->getNcrFormat());

    // Verify that the forward manager can be retrieved.
    DdnsDomainListMgrPtr mgr = context->getForwardMgr();
    ASSERT_TRUE(mgr);

    // Verify that the forward manager has the correct number of domains.
    DdnsDomainMapPtr domains = mgr->getDomains();
    ASSERT_TRUE(domains);
    int count =  domains->size();
    EXPECT_EQ(2, count);

    // Verify that the server count in each of the forward manager domains.
    // NOTE that since prior tests have validated server parsing, we are are
    // assuming that the servers did in fact parse correctly if the correct
    // number of them are there.
    DdnsDomainMapPair domain_pair;
    BOOST_FOREACH(domain_pair, (*domains)) {
        DdnsDomainPtr domain = domain_pair.second;
        DnsServerInfoStoragePtr servers = domain->getServers();
        count = servers->size();
        EXPECT_TRUE(servers);
        EXPECT_EQ(3, count);
    }

    // Verify that the reverse manager can be retrieved.
    mgr = context->getReverseMgr();
    ASSERT_TRUE(mgr);

    // Verify that the reverse manager has the correct number of domains.
    domains = mgr->getDomains();
    count =  domains->size();
    EXPECT_EQ(2, count);

    // Verify that the server count in each of the reverse manager domains.
    // NOTE that since prior tests have validated server parsing, we are are
    // assuming that the servers did in fact parse correctly if the correct
    // number of them are there.
    BOOST_FOREACH(domain_pair, (*domains)) {
        DdnsDomainPtr domain = domain_pair.second;
        DnsServerInfoStoragePtr servers = domain->getServers();
        count = servers->size();
        EXPECT_TRUE(servers);
        EXPECT_EQ(3, count);
    }

    // Test directional update flags.
    EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
    EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());

    // Verify that parsing the exact same configuration a second time
    // does not cause a duplicate value errors.
    answer_ = cfg_mgr_->parseConfig(config_set_);
    ASSERT_TRUE(checkAnswer(0));
}

/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
/// This test uses a valid configuration to exercise the D2CfgMgr
/// forward FQDN-to-domain matching.
/// It verifies that:
/// 1. Given an FQDN which exactly matches a domain's name, that domain is
/// returned as match.
/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
/// 3. Given a FQDN that matches no domain name, returns the wild card domain
/// as a match.
TEST_F(D2CfgMgrTest, forwardMatch) {
    // Create  configuration with one domain, one sub domain, and the wild
    // card.
    std::string config = "{ "
                        "\"ip-address\" : \"192.168.1.33\" , "
                        "\"port\" : 88 , "
                        "\"tsig-keys\": [] ,"
                        "\"forward-ddns\" : {"
                        "\"ddns-domains\": [ "
                        "{ \"name\": \"tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] } "
                        ", "
                        "{ \"name\": \"one.tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.2\" } "
                        "  ] } "
                        ", "
                        "{ \"name\": \"*\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.3\" } "
                        "  ] } "
                        "] }, "
                        "\"reverse-ddns\" : {} "
                        "}";

    ASSERT_TRUE(fromJSON(config));
    // Verify that we can parse the configuration.
    answer_ = cfg_mgr_->parseConfig(config_set_);
    ASSERT_TRUE(checkAnswer(0));

    // Verify that the D2 context can be retrieved and is not null.
    D2CfgContextPtr context;
    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());

    // Test directional update flags.
    EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
    EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());

    DdnsDomainPtr match;
    // Verify that an exact match works.
    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
    EXPECT_EQ("tmark.org", match->getName());

    // Verify that search is case insensisitive.
    EXPECT_TRUE(cfg_mgr_->matchForward("TMARK.ORG", match));
    EXPECT_EQ("tmark.org", match->getName());

    // Verify that an exact match works.
    EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
    EXPECT_EQ("one.tmark.org", match->getName());

    // Verify that a FQDN for sub-domain matches.
    EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
    EXPECT_EQ("tmark.org", match->getName());

    // Verify that a FQDN for sub-domain matches.
    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
    EXPECT_EQ("one.tmark.org", match->getName());

    // Verify that an FQDN with no match, returns the wild card domain.
    EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
    EXPECT_EQ("*", match->getName());

    // Verify that an attempt to match an empty FQDN throws.
    ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
}

/// @brief Tests domain matching when there is no wild card domain.
/// This test verifies that matches are found only for FQDNs that match
/// some or all of a domain name.  FQDNs without matches should not return
/// a match.
TEST_F(D2CfgMgrTest, matchNoWildcard) {
    // Create a configuration with one domain, one sub-domain, and NO wild card.
    std::string config = "{ "
                        "\"ip-address\" : \"192.168.1.33\" , "
                        "\"port\" : 88 , "
                        "\"tsig-keys\": [] ,"
                        "\"forward-ddns\" : {"
                        "\"ddns-domains\": [ "
                        "{ \"name\": \"tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] } "
                        ", "
                        "{ \"name\": \"one.tmark.org\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.2\" } "
                        "  ] } "
                        "] }, "
                        "\"reverse-ddns\" : {} "
                        " }";

    ASSERT_TRUE(fromJSON(config));

    // Verify that we can parse the configuration.
    answer_ = cfg_mgr_->parseConfig(config_set_);
    ASSERT_TRUE(checkAnswer(0));

    // Verify that the D2 context can be retrieved and is not null.
    D2CfgContextPtr context;
    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());

    DdnsDomainPtr match;
    // Verify that full or partial matches, still match.
    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
    EXPECT_EQ("tmark.org", match->getName());

    EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
    EXPECT_EQ("tmark.org", match->getName());

    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
    EXPECT_EQ("one.tmark.org", match->getName());

    // Verify that a FQDN with no match, fails to match.
    EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
}

/// @brief Tests domain matching when there is ONLY a wild card domain.
/// This test verifies that any FQDN matches the wild card.
TEST_F(D2CfgMgrTest, matchAll) {
    std::string config = "{ "
                        "\"ip-address\" : \"192.168.1.33\" , "
                        "\"port\" : 88 , "
                        "\"tsig-keys\": [] ,"
                        "\"forward-ddns\" : {"
                        "\"ddns-domains\": [ "
                        "{ \"name\": \"*\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] } "
                        "] }, "
                        "\"reverse-ddns\" : {} "
                        "}";

    ASSERT_TRUE(fromJSON(config));

    // Verify that we can parse the configuration.
    answer_ = cfg_mgr_->parseConfig(config_set_);
    ASSERT_TRUE(checkAnswer(0));

    // Verify that the D2 context can be retrieved and is not null.
    D2CfgContextPtr context;
    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());

    // Verify that wild card domain is returned for any FQDN.
    DdnsDomainPtr match;
    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
    EXPECT_EQ("*", match->getName());
    EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
    EXPECT_EQ("*", match->getName());

    // Verify that an attempt to match an empty FQDN still throws.
    ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);

}

/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
/// This test uses a valid configuration to exercise the D2CfgMgr's
/// reverse FQDN-to-domain matching.
/// It verifies that:
/// 1. Given an FQDN which exactly matches a domain's name, that domain is
/// returned as match.
/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
/// 3. Given a FQDN that matches no domain name, returns the wild card domain
/// as a match.
TEST_F(D2CfgMgrTest, matchReverse) {
    std::string config = "{ "
                        "\"ip-address\" : \"192.168.1.33\" , "
                        "\"port\" : 88 , "
                        "\"tsig-keys\": [] ,"
                        "\"forward-ddns\" : {}, "
                        "\"reverse-ddns\" : {"
                        "\"ddns-domains\": [ "
                        "{ \"name\": \"5.100.168.192.in-addr.arpa.\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] }, "
                        "{ \"name\": \"100.200.192.in-addr.arpa.\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] }, "
                        "{ \"name\": \"170.192.in-addr.arpa.\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] }, "
                        // Note mixed case to test case insensitivity.
                        "{ \"name\": \"2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] },"
                        "{ \"name\": \"*\" , "
                        "  \"dns-servers\" : [ "
                        "  { \"ip-address\": \"127.0.0.1\" } "
                        "  ] } "
                        "] } }";

    ASSERT_TRUE(fromJSON(config));

    // Verify that we can parse the configuration.
    answer_ = cfg_mgr_->parseConfig(config_set_);
    ASSERT_TRUE(checkAnswer(0));

    // Verify that the D2 context can be retrieved and is not null.
    D2CfgContextPtr context;
    ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());

    // Test directional update flags.
    EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
    EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());

    DdnsDomainPtr match;

    // Verify an exact match.
    EXPECT_TRUE(cfg_mgr_->matchReverse("192.168.100.5", match));
    EXPECT_EQ("5.100.168.192.in-addr.arpa.", match->getName());

    // Verify a sub-domain match.
    EXPECT_TRUE(cfg_mgr_->matchReverse("192.200.100.27", match));
    EXPECT_EQ("100.200.192.in-addr.arpa.", match->getName());

    // Verify a sub-domain match.
    EXPECT_TRUE(cfg_mgr_->matchReverse("192.170.50.30", match));
    EXPECT_EQ("170.192.in-addr.arpa.", match->getName());

    // Verify a wild card match.
    EXPECT_TRUE(cfg_mgr_->matchReverse("1.1.1.1", match));
    EXPECT_EQ("*", match->getName());

    // Verify a IPv6 match.
    EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:302:99::",match));
    EXPECT_EQ("2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.", match->getName());

    // Verify a IPv6 wild card match.
    EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:99:302::",match));
    EXPECT_EQ("*", match->getName());

    // Verify that an attempt to match an invalid IP address throws.
    ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
}

/// @brief Tests D2 config parsing against a wide range of config permutations.
/// It iterates over all of the test configurations described in given file.
/// The file content is JSON specialized to this test. The format of the file
/// is:
///
/// @code
/// # The file must open with a list. It's name is arbitrary.
///
/// { "test_list" :
/// [
///
/// #    Test one starts here:
///      {
///
/// #    Each test has:
/// #      1. description - optional text description
/// #      2. should-fail - bool indicator if parsing is expected to file
/// #         (defaults to false)
/// #       3. data - configuration text to parse
/// #
///      "description" : "<text describing test>",
///      "should_fail" : <true|false> ,
///      "data" :
///          {
/// #        configuration elements here
///          "bool_val" : false,
///          "some_map" :  {}
/// #         :
///          }
///      }
///
/// #    Next test would start here
///      ,
///      {
///      }
///
/// ]}
///
/// @endcode
///
/// (The file supports comments per Element::fromJSONFile())
///
TEST_F(D2CfgMgrTest, configPermutations) {
    std::string test_file = testDataFile("d2_cfg_tests.json");
    isc::data::ConstElementPtr tests;

    // Read contents of the file and parse it as JSON. Note it must contain
    // all valid JSON, we aren't testing JSON parsing.
    try {
        tests = isc::data::Element::fromJSONFile(test_file, true);
    } catch (const std::exception& ex) {
        FAIL() << "ERROR parsing file : " << test_file << " : " << ex.what();
    }

    // Read in each test For each test, read:
    //  1. description - optional text description
    //  2. should-fail - bool indicator if parsing is expected to file (defaults
    //     to false
    //  3. data - configuration text to parse
    //
    // Next attempt to parse the configuration by passing it into
    // D2CfgMgr::parseConfig().  Then check the parsing outcome against the
    // expected outcome as given by should-fail.
    isc::data::ConstElementPtr test;
    ASSERT_TRUE(tests->get("test-list"));
    BOOST_FOREACH(test, tests->get("test-list")->listValue()) {

        // Grab the description.
        std::string description = "<no desc>";
        isc::data::ConstElementPtr elem = test->get("description");
        if (elem) {
            elem->getValue(description);
        }

        // Grab the outcome flag, should-fail, defaults to false if it's
        // not specified.
        bool should_fail = false;
        elem = test->get("should-fail");
        if (elem)  {
            elem->getValue(should_fail);
        }

        // Grab the test's configuration data.
        isc::data::ConstElementPtr data = test->get("data");
        ASSERT_TRUE(data) << "No data for test: "
                          << " : " << test->getPosition();

        // Attempt to parse the configuration. We verify that we get the expected
        // outcome, and if it was supposed to fail if the explanation contains
        // position information.
        checkAnswerWithError(cfg_mgr_->parseConfig(data),
                             (should_fail ? SHOULD_FAIL : SHOULD_PASS),
                             test_file);
    }
}

} // end of anonymous namespace

ext: cc

max_stars_repo_name: sebschrader/debian-pkg-isc-kea

max_issues_repo_name: sebschrader/debian-pkg-isc-kea

max_forks_repo_name: sebschrader/debian-pkg-isc-kea