mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
Merge branch 'master' into trac2450
This commit is contained in:
51
ChangeLog
51
ChangeLog
@@ -1,3 +1,54 @@
|
||||
515. [bug] jinmei
|
||||
The in-memory data source now accepts an RRSIG provided without
|
||||
a covered RRset in loading. A subsequent query for its owner name
|
||||
of the covered type would generally result in NXRRSET; if the
|
||||
covered RRset is of type NSEC3, the corresponding NSEC3 processing
|
||||
would result in SERVFAIL.
|
||||
(Trac #2420, git 6744c100953f6def5500bcb4bfc330b9ffba0f5f)
|
||||
|
||||
514. [bug] jelte
|
||||
b10-msgq now handles socket errors more gracefully when sending data
|
||||
to clients. It no longer exits with 'broken pipe' errors, and is
|
||||
also better at resending data on temporary error codes from send().
|
||||
(Trac #2398, git 9f6b45ee210a253dca608848a58c824ff5e0d234)
|
||||
|
||||
513. [func] marcin
|
||||
Implemented the OptionCustom class for DHCPv4 and DHCPv6.
|
||||
This class represents an option which has a defined
|
||||
structure: a set of data fields of specific types and order.
|
||||
It is used to represent those options that can't be
|
||||
represented by any other specialized class.
|
||||
(Trac #2312, git 28d885b457dda970d9aecc5de018ec1120143a10)
|
||||
|
||||
512. [func] jelte
|
||||
Added a new tool b10-certgen, to check and update the self-signed
|
||||
SSL certificate used by b10-cmdctl. The original certificate
|
||||
provided has been removed, and a fresh one is generated upon first
|
||||
build. See the b10-certgen manpage for information on how to update
|
||||
existing installed certificates.
|
||||
(Trac #1044, git 510773dd9057ccf6caa8241e74a7a0b34ca971ab)
|
||||
|
||||
511. [bug] stephen
|
||||
Fixed a race condition in the DHCP tests whereby the test program
|
||||
spawned a subprocess and attempted to read (without waiting) from
|
||||
the interconnecting pipe before the subprocess had written
|
||||
anything. The lack of output was being interpreted as a test
|
||||
failure.
|
||||
(Trac #2410, git f53e65cdceeb8e6da4723730e4ed0a17e4646579)
|
||||
|
||||
510. [func] marcin
|
||||
DHCP option instances can be created using a collection of strings.
|
||||
Each string represents a value of a particular data field within
|
||||
an option. The data field values, given as strings, are validated
|
||||
against the actual types of option fields specified in the options
|
||||
definitions.
|
||||
(Trac #2490, git 56cfd6612fcaeae9acec4a94e1e5f1a88142c44d)
|
||||
|
||||
509. [func] muks
|
||||
Log messages now include the pid of the process that logged the
|
||||
message.
|
||||
(Trac #1745, git fc8bbf3d438e8154e7c2bdd322145a7f7854dc6a)
|
||||
|
||||
508. [bug] stephen
|
||||
Split the DHCP library into two directories, each with its own
|
||||
Makefile. This properly solves the problem whereby a "make"
|
||||
|
@@ -20,6 +20,13 @@ dist_doc_DATA = AUTHORS COPYING ChangeLog README
|
||||
|
||||
.PHONY: check-valgrind check-valgrind-suppress
|
||||
|
||||
install-exec-hook:
|
||||
-@echo -e "\033[1;33m" # switch to yellow color text
|
||||
@echo "NOTE: BIND 10 does not automatically start DNS services when it is run"
|
||||
@echo " in its default configuration. Please see the Guide for information"
|
||||
@echo " on how to configure these services to be started automatically."
|
||||
-@echo -e "\033[m" # switch back to normal
|
||||
|
||||
check-valgrind:
|
||||
if HAVE_VALGRIND
|
||||
@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --num-callers=48 --leak-check=full --fullpath-after=" \
|
||||
|
6
README
6
README
@@ -58,5 +58,7 @@ For operating system specific tips see the wiki at:
|
||||
|
||||
Please see the wiki and the doc/ directory for various documentation.
|
||||
|
||||
The BIND 10 suite is started by running "bind10". Note that the
|
||||
default configuration does not run any DNS or DHCP servers.
|
||||
The BIND 10 suite is started by running "bind10". Note that the default
|
||||
configuration does not start any DNS or DHCP services. Please see the
|
||||
Guide for information on how to configure these services to be started
|
||||
automatically.
|
||||
|
@@ -705,7 +705,6 @@ fi
|
||||
AC_SUBST(BOTAN_LDFLAGS)
|
||||
AC_SUBST(BOTAN_LIBS)
|
||||
AC_SUBST(BOTAN_INCLUDES)
|
||||
|
||||
# Even though chances are high we already performed a real compilation check
|
||||
# in the search for the right (pkg)config data, we try again here, to
|
||||
# be sure.
|
||||
|
@@ -425,11 +425,12 @@ var/
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>In another console, enable the authoritative DNS service
|
||||
(by using the <command>bindctl</command> utility to configure
|
||||
the <command>b10-auth</command> component to run):
|
||||
<screen>$ <userinput>bindctl</userinput></screen>
|
||||
(Login with the provided default username and password.)
|
||||
<para>DNS and DHCP components are not started in the default
|
||||
configuration. In another console, enable the authoritative
|
||||
DNS service (by using the <command>bindctl</command> utility
|
||||
to configure the <command>b10-auth</command> component to
|
||||
run): <screen>$ <userinput>bindctl</userinput></screen>
|
||||
(Login with the provided default username and password.)
|
||||
<screen>
|
||||
> <userinput>config add Boss/components b10-auth</userinput>
|
||||
> <userinput>config set Boss/components/b10-auth/special auth</userinput>
|
||||
|
@@ -364,16 +364,24 @@ class TestConfigCommands(unittest.TestCase):
|
||||
socket_err_output = io.StringIO()
|
||||
sys.stdout = socket_err_output
|
||||
self.assertEqual(1, self.tool.run())
|
||||
self.assertEqual("Failed to send request, the connection is closed\n",
|
||||
socket_err_output.getvalue())
|
||||
|
||||
# First few lines may be some kind of heading, or a warning that
|
||||
# Python readline is unavailable, so we do a sub-string check.
|
||||
self.assertIn("Failed to send request, the connection is closed",
|
||||
socket_err_output.getvalue())
|
||||
|
||||
socket_err_output.close()
|
||||
|
||||
# validate log message for http.client.CannotSendRequest
|
||||
cannot_send_output = io.StringIO()
|
||||
sys.stdout = cannot_send_output
|
||||
self.assertEqual(1, self.tool.run())
|
||||
self.assertEqual("Can not send request, the connection is busy\n",
|
||||
cannot_send_output.getvalue())
|
||||
|
||||
# First few lines may be some kind of heading, or a warning that
|
||||
# Python readline is unavailable, so we do a sub-string check.
|
||||
self.assertIn("Can not send request, the connection is busy",
|
||||
cannot_send_output.getvalue())
|
||||
|
||||
cannot_send_output.close()
|
||||
|
||||
def test_apply_cfg_command_int(self):
|
||||
|
1
src/bin/cfgmgr/local_plugins/.gitignore
vendored
Normal file
1
src/bin/cfgmgr/local_plugins/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/datasrc.spec
|
2
src/bin/cmdctl/.gitignore
vendored
2
src/bin/cmdctl/.gitignore
vendored
@@ -4,3 +4,5 @@
|
||||
/cmdctl.spec.pre
|
||||
/run_b10-cmdctl.sh
|
||||
/b10-cmdctl.8
|
||||
/cmdctl-keyfile.pem
|
||||
/cmdctl-certfile.pem
|
||||
|
@@ -4,6 +4,8 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
|
||||
|
||||
pkglibexec_SCRIPTS = b10-cmdctl
|
||||
|
||||
bin_PROGRAMS = b10-certgen
|
||||
|
||||
nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
|
||||
pylogmessagedir = $(pyexecdir)/isc/log_messages/
|
||||
|
||||
@@ -25,15 +27,18 @@ CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
|
||||
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
|
||||
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
|
||||
|
||||
man_MANS = b10-cmdctl.8
|
||||
DISTCLEANFILES = $(man_MANS)
|
||||
EXTRA_DIST += $(man_MANS) b10-cmdctl.xml cmdctl_messages.mes
|
||||
man_MANS = b10-cmdctl.8 b10-certgen.1
|
||||
DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
|
||||
EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
|
||||
|
||||
if GENERATE_DOCS
|
||||
|
||||
b10-cmdctl.8: b10-cmdctl.xml
|
||||
@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-cmdctl.xml
|
||||
|
||||
b10-certgen.1: b10-certgen.xml
|
||||
@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-certgen.xml
|
||||
|
||||
else
|
||||
|
||||
$(man_MANS):
|
||||
@@ -54,12 +59,23 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
|
||||
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
|
||||
chmod a+x $@
|
||||
|
||||
b10_certgen_SOURCES = b10-certgen.cc
|
||||
b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
|
||||
b10_certgen_LDFLAGS = $(BOTAN_LIBS)
|
||||
|
||||
# Generate the initial certificates immediately
|
||||
cmdctl-certfile.pem: b10-certgen
|
||||
./b10-certgen -q -w
|
||||
|
||||
cmdctl-keyfile.pem: b10-certgen
|
||||
./b10-certgen -q -w
|
||||
|
||||
if INSTALL_CONFIGURATIONS
|
||||
|
||||
# Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
|
||||
# because these file will contain sensitive information.
|
||||
install-data-local:
|
||||
$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
|
||||
$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
|
||||
for f in $(CMDCTL_CONFIGURATIONS) ; do \
|
||||
if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then \
|
||||
${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ; \
|
||||
|
429
src/bin/cmdctl/b10-certgen.cc
Normal file
429
src/bin/cmdctl/b10-certgen.cc
Normal file
@@ -0,0 +1,429 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <botan/botan.h>
|
||||
#include <botan/x509self.h>
|
||||
#include <botan/x509stor.h>
|
||||
#include <botan/rsa.h>
|
||||
#include <botan/dsa.h>
|
||||
#include <botan/data_src.h>
|
||||
using namespace Botan;
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <getopt.h>
|
||||
|
||||
// For cleaner 'does not exist or is not readable' output than
|
||||
// botan provides
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
// This is a simple tool that creates a self-signed PEM certificate
|
||||
// for use with BIND 10. It creates a simple certificate for initial
|
||||
// setup. Currently, all values are hardcoded defaults. For future
|
||||
// versions, we may want to add more options for administrators.
|
||||
|
||||
// It will create a PEM file containing a certificate with the following
|
||||
// values:
|
||||
// common name: localhost
|
||||
// organization: BIND10
|
||||
// country code: US
|
||||
|
||||
// Additional error return codes; these are specifically
|
||||
// chosen to be distinct from validation error codes as
|
||||
// provided by Botan. Their main use is to distinguish
|
||||
// error cases in the unit tests.
|
||||
const int DECODING_ERROR = 100;
|
||||
const int BAD_OPTIONS = 101;
|
||||
const int READ_ERROR = 102;
|
||||
const int WRITE_ERROR = 103;
|
||||
const int UNKNOWN_ERROR = 104;
|
||||
const int NO_SUCH_FILE = 105;
|
||||
const int FILE_PERMISSION_ERROR = 106;
|
||||
|
||||
void
|
||||
usage() {
|
||||
std::cout << "Usage: b10-certgen [OPTION]..." << std::endl;
|
||||
std::cout << "Validate, create, or update a self-signed certificate for "
|
||||
"use with b10-cmdctl" << std::endl;
|
||||
std::cout << "" << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate"
|
||||
<< std::endl;
|
||||
std::cout << "-f, --force\t\t\toverwrite existing certficate even if it"
|
||||
<< std::endl <<"\t\t\t\tis valid" << std::endl;
|
||||
std::cout << "-h, --help\t\t\tshow this help" << std::endl;
|
||||
std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key"
|
||||
<< std::endl;
|
||||
std::cout << "-w, --write\t\t\tcreate a new certificate if the given file"
|
||||
<< std::endl << "\t\t\t\tdoes not exist, or if is is not valid"
|
||||
<< std::endl;
|
||||
std::cout << "-q, --quiet\t\t\tprint no output when creating or validating"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/// \brief Returns true if the given file exists
|
||||
///
|
||||
/// \param filename The file to check
|
||||
/// \return true if file exists
|
||||
bool
|
||||
fileExists(const std::string& filename) {
|
||||
return (access(filename.c_str(), F_OK) == 0);
|
||||
}
|
||||
|
||||
/// \brief Returns true if the given file exists and is readable
|
||||
///
|
||||
/// \param filename The file to check
|
||||
/// \return true if file exists and is readable
|
||||
bool
|
||||
fileIsReadable(const std::string& filename) {
|
||||
return (access(filename.c_str(), R_OK) == 0);
|
||||
}
|
||||
|
||||
/// \brief Returns true if the given file exists and is writable
|
||||
///
|
||||
/// \param filename The file to check
|
||||
/// \return true if file exists and is writable
|
||||
bool
|
||||
fileIsWritable(const std::string& filename) {
|
||||
return (access(filename.c_str(), W_OK) == 0);
|
||||
}
|
||||
|
||||
/// \brief Helper function for readable error output;
|
||||
///
|
||||
/// Returns string representation of X509 result code
|
||||
/// This does not appear to be provided by Botan itself
|
||||
///
|
||||
/// \param code An \c X509_Code instance
|
||||
/// \return A human-readable c string
|
||||
const char*
|
||||
X509CodeToString(const X509_Code& code) {
|
||||
// note that this list provides more than we would
|
||||
// need in this context, it is just the enum from
|
||||
// the source code of Botan.
|
||||
switch (code) {
|
||||
case VERIFIED:
|
||||
return ("verified");
|
||||
case UNKNOWN_X509_ERROR:
|
||||
return ("unknown x509 error");
|
||||
case CANNOT_ESTABLISH_TRUST:
|
||||
return ("cannot establish trust");
|
||||
case CERT_CHAIN_TOO_LONG:
|
||||
return ("certificate chain too long");
|
||||
case SIGNATURE_ERROR:
|
||||
return ("signature error");
|
||||
case POLICY_ERROR:
|
||||
return ("policy error");
|
||||
case INVALID_USAGE:
|
||||
return ("invalid usage");
|
||||
case CERT_FORMAT_ERROR:
|
||||
return ("certificate format error");
|
||||
case CERT_ISSUER_NOT_FOUND:
|
||||
return ("certificate issuer not found");
|
||||
case CERT_NOT_YET_VALID:
|
||||
return ("certificate not yet valid");
|
||||
case CERT_HAS_EXPIRED:
|
||||
return ("certificate has expired");
|
||||
case CERT_IS_REVOKED:
|
||||
return ("certificate has been revoked");
|
||||
case CRL_FORMAT_ERROR:
|
||||
return ("crl format error");
|
||||
case CRL_NOT_YET_VALID:
|
||||
return ("crl not yet valid");
|
||||
case CRL_HAS_EXPIRED:
|
||||
return ("crl has expired");
|
||||
case CA_CERT_CANNOT_SIGN:
|
||||
return ("CA cert cannot sign");
|
||||
case CA_CERT_NOT_FOR_CERT_ISSUER:
|
||||
return ("CA certificate not for certificate issuer");
|
||||
case CA_CERT_NOT_FOR_CRL_ISSUER:
|
||||
return ("CA certificate not for crl issuer");
|
||||
default:
|
||||
return ("Unknown X509 code");
|
||||
}
|
||||
}
|
||||
|
||||
class CertificateTool {
|
||||
public:
|
||||
CertificateTool(bool quiet) : quiet_(quiet) {}
|
||||
|
||||
int
|
||||
createKeyAndCertificate(const std::string& key_file_name,
|
||||
const std::string& cert_file_name) {
|
||||
try {
|
||||
AutoSeeded_RNG rng;
|
||||
|
||||
// Create and store a private key
|
||||
print("Creating key file " + key_file_name);
|
||||
RSA_PrivateKey key(rng, 2048);
|
||||
std::ofstream key_file(key_file_name.c_str());
|
||||
if (!key_file.good()) {
|
||||
print(std::string("Error writing to ") + key_file_name +
|
||||
": " + std::strerror(errno));
|
||||
return (WRITE_ERROR);
|
||||
}
|
||||
key_file << PKCS8::PEM_encode(key, rng, "");
|
||||
if (!key_file.good()) {
|
||||
print(std::string("Error writing to ") + key_file_name +
|
||||
": " + std::strerror(errno));
|
||||
return (WRITE_ERROR);
|
||||
}
|
||||
key_file.close();
|
||||
|
||||
// Certificate options, currently hardcoded.
|
||||
// For a future version we may want to make these
|
||||
// settable.
|
||||
X509_Cert_Options opts;
|
||||
opts.common_name = "localhost";
|
||||
opts.organization = "UNKNOWN";
|
||||
opts.country = "XX";
|
||||
|
||||
opts.CA_key();
|
||||
|
||||
print("Creating certificate file " + cert_file_name);
|
||||
|
||||
// The exact call changed aftert 1.8, adding the
|
||||
// hash function option
|
||||
#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
|
||||
X509_Certificate cert =
|
||||
X509::create_self_signed_cert(opts, key, "SHA-256", rng);
|
||||
#else
|
||||
X509_Certificate cert =
|
||||
X509::create_self_signed_cert(opts, key, rng);
|
||||
#endif
|
||||
|
||||
std::ofstream cert_file(cert_file_name.c_str());
|
||||
if (!cert_file.good()) {
|
||||
print(std::string("Error writing to ") + cert_file_name +
|
||||
": " + std::strerror(errno));
|
||||
return (WRITE_ERROR);
|
||||
}
|
||||
cert_file << cert.PEM_encode();
|
||||
if (!cert_file.good()) {
|
||||
print(std::string("Error writing to ") + cert_file_name +
|
||||
": " + std::strerror(errno));
|
||||
return (WRITE_ERROR);
|
||||
}
|
||||
cert_file.close();
|
||||
} catch(std::exception& e) {
|
||||
std::cout << "Error creating key or certificate: " << e.what()
|
||||
<< std::endl;
|
||||
return (UNKNOWN_ERROR);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
validateCertificate(const std::string& certfile) {
|
||||
// Since we are dealing with a self-signed certificate here, we
|
||||
// also use the certificate to check itself; i.e. we add it
|
||||
// as a trusted certificate, then validate the certficate itself.
|
||||
//const X509_Certificate cert(certfile);
|
||||
try {
|
||||
X509_Store store;
|
||||
DataSource_Stream in(certfile);
|
||||
store.add_trusted_certs(in);
|
||||
|
||||
const X509_Code result = store.validate_cert(certfile);
|
||||
|
||||
if (result == VERIFIED) {
|
||||
print(certfile + " is valid");
|
||||
} else {
|
||||
print(certfile + " failed to verify: " +
|
||||
X509CodeToString(result));
|
||||
}
|
||||
return (result);
|
||||
} catch (const Botan::Decoding_Error& bde) {
|
||||
print(certfile + " failed to verify: " + bde.what());
|
||||
return (DECODING_ERROR);
|
||||
} catch (const Botan::Stream_IO_Error& bsie) {
|
||||
print(certfile + " not read: " + bsie.what());
|
||||
return (READ_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Runs the tool
|
||||
///
|
||||
/// \param create_cert Create certificate if true, validate if false.
|
||||
/// Does nothing if certificate exists and is valid.
|
||||
/// \param force_create Create new certificate even if it is valid.
|
||||
/// \param certfile Certificate file to read to or write from.
|
||||
/// \param keyfile Key file to write if certificate is created.
|
||||
/// Ignored if create_cert is false
|
||||
/// \return zero on success, non-zero on failure
|
||||
int
|
||||
run(bool create_cert, bool force_create, const std::string& certfile,
|
||||
const std::string& keyfile)
|
||||
{
|
||||
if (create_cert) {
|
||||
// Unless force is given, only create it if the current
|
||||
// one is not OK
|
||||
|
||||
// First do some basic permission checks; both files
|
||||
// should either not exist, or be both readable
|
||||
// and writable
|
||||
// The checks are done one by one so all errors can
|
||||
// be enumerated in one go
|
||||
if (fileExists(certfile)) {
|
||||
if (!fileIsReadable(certfile)) {
|
||||
print(certfile + " not readable: " + std::strerror(errno));
|
||||
create_cert = false;
|
||||
}
|
||||
if (!fileIsWritable(certfile)) {
|
||||
print(certfile + " not writable: " + std::strerror(errno));
|
||||
create_cert = false;
|
||||
}
|
||||
}
|
||||
// The key file really only needs write permissions (for
|
||||
// b10-certgen that is)
|
||||
if (fileExists(keyfile)) {
|
||||
if (!fileIsWritable(keyfile)) {
|
||||
print(keyfile + " not writable: " + std::strerror(errno));
|
||||
create_cert = false;
|
||||
}
|
||||
}
|
||||
if (!create_cert) {
|
||||
print("Not creating new certificate, "
|
||||
"check file permissions");
|
||||
return (FILE_PERMISSION_ERROR);
|
||||
}
|
||||
|
||||
// If we reach this, we know that if they exist, we can both
|
||||
// read and write them, so now it's up to content checking
|
||||
// and/or force_create
|
||||
|
||||
if (force_create || !fileExists(certfile) ||
|
||||
validateCertificate(certfile) != VERIFIED) {
|
||||
return (createKeyAndCertificate(keyfile, certfile));
|
||||
} else {
|
||||
print("Not creating a new certificate (use -f to force)");
|
||||
}
|
||||
} else {
|
||||
if (!fileExists(certfile)) {
|
||||
print(certfile + ": " + std::strerror(errno));
|
||||
return (NO_SUCH_FILE);
|
||||
}
|
||||
if (!fileIsReadable(certfile)) {
|
||||
print(certfile + " not readable: " + std::strerror(errno));
|
||||
return (FILE_PERMISSION_ERROR);
|
||||
}
|
||||
int result = validateCertificate(certfile);
|
||||
if (result != 0) {
|
||||
print("Running with -w would overwrite the certificate");
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
private:
|
||||
/// Prints the message to stdout unless quiet_ is true
|
||||
void print(const std::string& msg) {
|
||||
if (!quiet_) {
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool quiet_;
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
Botan::LibraryInitializer init;
|
||||
|
||||
// create or check certificate
|
||||
bool create_cert = false;
|
||||
// force creation even if not necessary
|
||||
bool force_create = false;
|
||||
// don't print any output
|
||||
bool quiet = false;
|
||||
|
||||
// default certificate file
|
||||
std::string certfile("cmdctl-certfile.pem");
|
||||
// default key file
|
||||
std::string keyfile("cmdctl-keyfile.pem");
|
||||
|
||||
// whether or not the above values have been
|
||||
// overridden (used in command line checking)
|
||||
bool certfile_default = true;
|
||||
bool keyfile_default = true;
|
||||
|
||||
// It would appear some environments insist on
|
||||
// char* here (Sunstudio on Solaris), so we const_cast
|
||||
// them to get rid of compiler warnings.
|
||||
const struct option long_options[] = {
|
||||
{ const_cast<char*>("certfile"), required_argument, NULL, 'c' },
|
||||
{ const_cast<char*>("force"), no_argument, NULL, 'f' },
|
||||
{ const_cast<char*>("help"), no_argument, NULL, 'h' },
|
||||
{ const_cast<char*>("keyfile"), required_argument, NULL, 'k' },
|
||||
{ const_cast<char*>("write"), no_argument, NULL, 'w' },
|
||||
{ const_cast<char*>("quiet"), no_argument, NULL, 'q' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
int opt, option_index;
|
||||
while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options,
|
||||
&option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'c':
|
||||
certfile = optarg;
|
||||
certfile_default = false;
|
||||
break;
|
||||
case 'f':
|
||||
force_create = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
return (0);
|
||||
break;
|
||||
case 'k':
|
||||
keyfile = optarg;
|
||||
keyfile_default = false;
|
||||
break;
|
||||
case 'w':
|
||||
create_cert = true;
|
||||
break;
|
||||
case 'q':
|
||||
quiet = true;
|
||||
break;
|
||||
default:
|
||||
// A message will have already been output about the error.
|
||||
return (BAD_OPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
std::cout << "Error: extraneous arguments" << std::endl << std::endl;
|
||||
usage();
|
||||
return (BAD_OPTIONS);
|
||||
}
|
||||
|
||||
// Some sanity checks on option combinations
|
||||
if (create_cert && (certfile_default ^ keyfile_default)) {
|
||||
std::cout << "Error: keyfile and certfile must both be specified "
|
||||
"if one of them is when calling b10-certgen in write "
|
||||
"mode." << std::endl;
|
||||
return (BAD_OPTIONS);
|
||||
}
|
||||
if (!create_cert && !keyfile_default) {
|
||||
std::cout << "Error: keyfile is not used when not in write mode"
|
||||
<< std::endl;
|
||||
return (BAD_OPTIONS);
|
||||
}
|
||||
|
||||
// Initialize the tool and perform the appropriate action(s)
|
||||
CertificateTool tool(quiet);
|
||||
return (tool.run(create_cert, force_create, certfile, keyfile));
|
||||
}
|
214
src/bin/cmdctl/b10-certgen.xml
Normal file
214
src/bin/cmdctl/b10-certgen.xml
Normal file
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
|
||||
[<!ENTITY mdash "—">]>
|
||||
<!--
|
||||
- Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
-
|
||||
- Permission to use, copy, modify, and/or distribute this software for any
|
||||
- purpose with or without fee is hereby granted, provided that the above
|
||||
- copyright notice and this permission notice appear in all copies.
|
||||
-
|
||||
- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
- OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
- PERFORMANCE OF THIS SOFTWARE.
|
||||
-->
|
||||
|
||||
<refentry>
|
||||
|
||||
<refentryinfo>
|
||||
<date>November 15, 2012</date>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>b10-certgen</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
<refmiscinfo>BIND10</refmiscinfo>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>b10-certgen</refname>
|
||||
<refpurpose>X509 Certificate generation tool for use with b10-cmdctl</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<docinfo>
|
||||
<copyright>
|
||||
<year>2012</year>
|
||||
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
|
||||
</copyright>
|
||||
</docinfo>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>b10-certgen</command>
|
||||
<group choice="opt">
|
||||
<arg choice="[OPTION]..."><option>-</option></arg>
|
||||
</group>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>DESCRIPTION</title>
|
||||
<para>The <command>b10-certgen</command> tool validates, creates, or
|
||||
updates a self-signed X509 certificate for use in b10-cmdctl.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The connection between <command>bindctl</command> and
|
||||
<command>b10-cmdctl</command> is done over HTTPS, and therefore
|
||||
<command>b10-cmdctl</command> needs a certificate. Since these
|
||||
certificates have expiry dates, they also need to be regenerated at
|
||||
some point.
|
||||
|
||||
There are many tools to do so, but for ease of use, <command>
|
||||
b10-certgen</command> can create a simple self-signed certificate.
|
||||
|
||||
By default, it will not create anything, but it will merely check an
|
||||
existing certificate (if not specified, cmdctl-certfile.pem, in the
|
||||
current working directory). And print whether it is valid, and
|
||||
whether it would update if the option '-w' is given.
|
||||
|
||||
With that option, the certificate could then be replaced by a newly
|
||||
created one. If the certificate is still valid, it would still not
|
||||
be overwritten (however, if it is found to be invalid, for example
|
||||
because it has expired, it would create a new one).
|
||||
|
||||
A new certificate is always created if the certificate file does
|
||||
not exist, or if creation is forced (with the -f option).
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>ARGUMENTS</title>
|
||||
|
||||
<para>The arguments are as follows:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term>
|
||||
<option>-c <replaceable>file</replaceable></option>,
|
||||
<option>--certfile=<replaceable>file</replaceable></option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
File to read the certificate from, or write the certificate to.
|
||||
If <option>-w</option> and <option>-c</option> are used,
|
||||
<option>-k</option> is mandatory as well.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>
|
||||
<option>-f</option>,
|
||||
<option>--force</option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Force updating of certificate when <option>-w</option> is used,
|
||||
even if the existing certificate is still valid.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>
|
||||
<option>-h</option>,
|
||||
<option>--help</option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Print the command line arguments and exit.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>
|
||||
<option>-k <replaceable>file</replaceable></option>,
|
||||
<option>--keyfile=<replaceable>file</replaceable></option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
File to write the private key to. This option is only valid when <option>-w</option> is used, and if this option is used, <option>-c</option> is mandatory as well.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>
|
||||
<option>-w</option>,
|
||||
<option>--write</option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Check the given certificate file. If it does not exist, a new
|
||||
private key and certificate are created. If it does exist, the
|
||||
certificate is validated. If it is not valid (for instance
|
||||
because it has expired), it is overwritten with a newly created
|
||||
certificate. If it is valid, nothing happens (use
|
||||
<option>-f</option> to force an update in that case).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>
|
||||
<option>-q</option>,
|
||||
<option>--quiet</option>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Don't print informational messages (only command-line errors are
|
||||
printed). Useful in scripts when only the return code is needed.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>SEE ALSO</title>
|
||||
<para>
|
||||
<citerefentry>
|
||||
<refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
|
||||
</citerefentry>,
|
||||
<citetitle>BIND 10 Guide</citetitle>.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>HISTORY</title>
|
||||
<para>
|
||||
The <command>b10-certgen</command> tool was first implemented
|
||||
in November 2012 for the ISC BIND 10 project.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>EXAMPLE</title>
|
||||
<para>
|
||||
To update an expired certificate in BIND 10 that has been installed to
|
||||
/usr/local:
|
||||
<screen>
|
||||
$> cd /usr/local/etc/bind10-devel/
|
||||
|
||||
$> b10-certgen
|
||||
cmdctl-certfile.pem failed to verify: certificate has expired
|
||||
Running with -w would overwrite the certificate
|
||||
|
||||
$> b10-certgen --write
|
||||
cmdctl-certfile.pem failed to verify: certificate has expired
|
||||
Creating key file cmdctl-keyfile.pem
|
||||
Creating certificate file cmdctl-certfile.pem
|
||||
|
||||
$> b10-certgen --write
|
||||
cmdctl-certfile.pem is valid
|
||||
Not creating a new certificate (use -f to force)
|
||||
</screen>
|
||||
</para>
|
||||
</refsect1>
|
||||
</refentry><!--
|
||||
- Local variables:
|
||||
- mode: sgml
|
||||
- End:
|
||||
-->
|
@@ -1,15 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDpICWxJGKMvUhLFPbf5n8ZWogqjYcQqqoHqHVRHYjyiey6FZdt
|
||||
ZkY2s1gYh0G0NXtimlIgic+vEcFe7vdmyKntW7DYDaqAj0KrED7RKAj8324jNbSJ
|
||||
HtLP4evvJep3vsoNtTvNuceQJ46vukxyxgg3DuC9kVqPuD8CZ1Rq4ATyiwIDAQAB
|
||||
AoGBAOJlOtV+DUq6Y2Ou91VXRiU8GzKgAQP5iWgoe84Ljbxkn4XThBxVD2j94Fbp
|
||||
u7AjpDCMx6cbzpoo9w6XqaGizAmAehIfTE3eFYs74N/FM09Wg2OSDyxMY0jgyECU
|
||||
A4ukjlPwcGDbmgbmlY3i+FVHp+zCgtZEsMC1IAosMac1BoX5AkEA/lrXWaVtH8bo
|
||||
mut3GBaXvubZMdaUr0BUd5a9q+tt4dQcKG1kFqgCNKhNhBIcpiMVcz+jGmOuopNA
|
||||
8dnUGqv3FQJBAOqiJ54ZvOTWNDpJIe02wIXRxRmc1xhHFCqYP23KxBVrAcTYB19J
|
||||
lesov/hEbnGLCbKS/naZJ1zrTImUPNRLqx8CQCzDtA7U7GWhTiKluioFH+O7IRKC
|
||||
X1yQh80cPHlbT9VkzSfYSLssCmdWD35k6aHbntTPqFbmoD+AhveJjKi9BxkCQDwX
|
||||
1c+/RcrSNcQr0N2hZUOgyztZGRnlsnuKTMyA3yGhK23P6mt0PEpjQG+Ej0jTVGOB
|
||||
FF0pspQwy4R9C+tPif8CQH36NNlXBfVNmT7kDtyLmaE6pID0vY9duX56BJbU1R0x
|
||||
SQ8/LcfJagk6gvp08OyYCPA+WZ7u/bas9R/nMTCLivc=
|
||||
-----END RSA PRIVATE KEY-----
|
@@ -1,6 +1,9 @@
|
||||
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
|
||||
PYTESTS = cmdctl_test.py
|
||||
PYTESTS = cmdctl_test.py b10-certgen_test.py
|
||||
EXTRA_DIST = $(PYTESTS)
|
||||
EXTRA_DIST += testdata/expired-certfile.pem
|
||||
EXTRA_DIST += testdata/mangled-certfile.pem
|
||||
EXTRA_DIST += testdata/noca-certfile.pem
|
||||
|
||||
# If necessary (rare cases), explicitly specify paths to dynamic libraries
|
||||
# required by loadable python modules.
|
||||
@@ -9,10 +12,12 @@ if SET_ENV_LIBRARY_PATH
|
||||
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
|
||||
endif
|
||||
|
||||
CLEANFILES = test-keyfile.pem test-certfile.pem
|
||||
|
||||
# test using command-line arguments, so use check-local target instead of TESTS
|
||||
check-local:
|
||||
if ENABLE_PYTHON_COVERAGE
|
||||
touch $(abs_top_srcdir)/.coverage
|
||||
touch $(abs_top_srcdir)/.coverage
|
||||
rm -f .coverage
|
||||
${LN_S} $(abs_top_srcdir)/.coverage .coverage
|
||||
endif
|
||||
|
254
src/bin/cmdctl/tests/b10-certgen_test.py
Normal file
254
src/bin/cmdctl/tests/b10-certgen_test.py
Normal file
@@ -0,0 +1,254 @@
|
||||
# Copyright (C) 2012 Internet Systems Consortium.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
|
||||
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
# Note: the main code is in C++, but what we are mostly testing is
|
||||
# options and behaviour (output/file creation, etc), which is easier
|
||||
# to test in python.
|
||||
|
||||
import unittest
|
||||
import os
|
||||
from subprocess import call
|
||||
import subprocess
|
||||
import ssl
|
||||
import stat
|
||||
|
||||
def run(command):
|
||||
"""
|
||||
Small helper function that returns a tuple of (rcode, stdout, stderr) after
|
||||
running the given command (an array of command and arguments, as passed on
|
||||
to subprocess).
|
||||
"""
|
||||
subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = subp.communicate()
|
||||
return (subp.returncode, stdout, stderr)
|
||||
|
||||
class FileDeleterContext:
|
||||
"""
|
||||
Simple Context Manager that deletes a given set of files when the context
|
||||
is left.
|
||||
"""
|
||||
def __init__(self, files):
|
||||
self.files = files
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
for f in self.files:
|
||||
if os.path.exists(f):
|
||||
os.unlink(f)
|
||||
|
||||
class FilePermissionContext:
|
||||
"""
|
||||
Simple Context Manager that temporarily modifies file permissions for
|
||||
a given file
|
||||
"""
|
||||
def __init__(self, f, unset_flags = [], set_flags = []):
|
||||
"""
|
||||
Initialize file permission context.
|
||||
See the stat module for possible flags to set or unset.
|
||||
The flags are changed when the context is entered (i.e.
|
||||
you can create the context first without any change)
|
||||
The flags are changed back when the context is left.
|
||||
|
||||
Parameters:
|
||||
f: string, file to change permissions for
|
||||
unset_flags: list of flags to unset
|
||||
set_flags: list of flags to set
|
||||
"""
|
||||
self.file = f
|
||||
self.orig_mode = os.stat(f).st_mode
|
||||
new_mode = self.orig_mode
|
||||
for flag in unset_flags:
|
||||
new_mode = new_mode & ~flag
|
||||
for flag in set_flags:
|
||||
new_mode = new_mode | flag
|
||||
self.new_mode = new_mode
|
||||
|
||||
def __enter__(self):
|
||||
os.chmod(self.file, self.new_mode)
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
os.chmod(self.file, self.orig_mode)
|
||||
|
||||
def read_file_data(filename):
|
||||
"""
|
||||
Simple text file reader that returns its contents as an array
|
||||
"""
|
||||
with open(filename) as f:
|
||||
return f.readlines()
|
||||
|
||||
class TestCertGenTool(unittest.TestCase):
|
||||
TOOL = '../b10-certgen'
|
||||
|
||||
def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
|
||||
"""
|
||||
Runs the given command, and checks return code, and outputs (if provided).
|
||||
Arguments:
|
||||
expected_returncode, return code of the command
|
||||
expected_stdout, (multiline) string that is checked agains stdout.
|
||||
May be None, in which case the check is skipped.
|
||||
expected_stderr, (multiline) string that is checked agains stderr.
|
||||
May be None, in which case the check is skipped.
|
||||
"""
|
||||
(returncode, stdout, stderr) = run(command)
|
||||
self.assertEqual(expected_returncode, returncode, " ".join(command))
|
||||
if expected_stdout is not None:
|
||||
self.assertEqual(expected_stdout, stdout.decode())
|
||||
if expected_stderr is not None:
|
||||
self.assertEqual(expected_stderr, stderr.decode())
|
||||
|
||||
def validate_certificate(self, expected_result, certfile):
|
||||
"""
|
||||
Validate a certificate, using the quiet option of the tool; it runs
|
||||
the check option (-c) for the given base name of the certificate (-f
|
||||
<certfile>), and compares the return code to the given
|
||||
expected_result value
|
||||
"""
|
||||
self.run_check(expected_result, '', '',
|
||||
[self.TOOL, '-q', '-c', certfile])
|
||||
# Same with long options
|
||||
self.run_check(expected_result, '', '',
|
||||
[self.TOOL, '--quiet', '--certfile', certfile])
|
||||
|
||||
|
||||
def test_basic_creation(self):
|
||||
"""
|
||||
Tests whether basic creation with no arguments (except output
|
||||
file name) successfully creates a key and certificate
|
||||
"""
|
||||
keyfile = 'test-keyfile.pem'
|
||||
certfile = 'test-certfile.pem'
|
||||
command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
|
||||
self.creation_helper(command, certfile, keyfile)
|
||||
# Do same with long options
|
||||
command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ]
|
||||
self.creation_helper(command, certfile, keyfile)
|
||||
|
||||
def creation_helper(self, command, certfile, keyfile):
|
||||
"""
|
||||
Helper method for test_basic_creation.
|
||||
Performs the actual checks
|
||||
"""
|
||||
with FileDeleterContext([keyfile, certfile]):
|
||||
self.assertFalse(os.path.exists(keyfile))
|
||||
self.assertFalse(os.path.exists(certfile))
|
||||
self.run_check(0, '', '', command)
|
||||
self.assertTrue(os.path.exists(keyfile))
|
||||
self.assertTrue(os.path.exists(certfile))
|
||||
|
||||
# Validate the certificate that was just created
|
||||
self.validate_certificate(0, certfile)
|
||||
|
||||
# When run with the same options, it should *not* create it again,
|
||||
# as the current certificate should still be valid
|
||||
certdata = read_file_data(certfile)
|
||||
keydata = read_file_data(keyfile)
|
||||
|
||||
self.run_check(0, '', '', command)
|
||||
|
||||
self.assertEqual(certdata, read_file_data(certfile))
|
||||
self.assertEqual(keydata, read_file_data(keyfile))
|
||||
|
||||
# but if we add -f, it should force a new creation
|
||||
command.append('-f')
|
||||
self.run_check(0, '', '', command)
|
||||
self.assertNotEqual(certdata, read_file_data(certfile))
|
||||
self.assertNotEqual(keydata, read_file_data(keyfile))
|
||||
|
||||
def test_check_bad_certificates(self):
|
||||
"""
|
||||
Tests a few pre-created certificates with the -c option
|
||||
"""
|
||||
if ('CMDCTL_SRC_PATH' in os.environ):
|
||||
path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
|
||||
else:
|
||||
path = "testdata/"
|
||||
self.validate_certificate(10, path + 'expired-certfile.pem')
|
||||
self.validate_certificate(100, path + 'mangled-certfile.pem')
|
||||
self.validate_certificate(17, path + 'noca-certfile.pem')
|
||||
|
||||
def test_bad_options(self):
|
||||
"""
|
||||
Tests some combinations of commands that should fail.
|
||||
"""
|
||||
# specify -c but not -k
|
||||
self.run_check(101,
|
||||
'Error: keyfile and certfile must both be specified '
|
||||
'if one of them is when calling b10-certgen in write '
|
||||
'mode.\n',
|
||||
'', [self.TOOL, '-w', '-c', 'foo'])
|
||||
self.run_check(101,
|
||||
'Error: keyfile and certfile must both be specified '
|
||||
'if one of them is when calling b10-certgen in write '
|
||||
'mode.\n',
|
||||
'', [self.TOOL, '-w', '-k', 'foo'])
|
||||
self.run_check(101,
|
||||
'Error: keyfile is not used when not in write mode\n',
|
||||
'', [self.TOOL, '-k', 'foo'])
|
||||
# Extraneous argument
|
||||
self.run_check(101, None, None, [self.TOOL, 'foo'])
|
||||
# No such file
|
||||
self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
|
||||
|
||||
def test_permissions(self):
|
||||
"""
|
||||
Test some combinations of correct and bad permissions.
|
||||
"""
|
||||
keyfile = 'mod-keyfile.pem'
|
||||
certfile = 'mod-certfile.pem'
|
||||
command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
|
||||
# Delete them at the end
|
||||
with FileDeleterContext([keyfile, certfile]):
|
||||
# Create the two files first
|
||||
self.run_check(0, '', '', command)
|
||||
self.validate_certificate(0, certfile)
|
||||
|
||||
# Make the key file unwritable
|
||||
with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]):
|
||||
self.run_check(106, '', '', command)
|
||||
# Should have no effect on validation
|
||||
self.validate_certificate(0, certfile)
|
||||
|
||||
# Make the cert file unwritable
|
||||
with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]):
|
||||
self.run_check(106, '', '', command)
|
||||
# Should have no effect on validation
|
||||
self.validate_certificate(0, certfile)
|
||||
|
||||
# Make the key file unreadable (this should not matter)
|
||||
with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]):
|
||||
self.run_check(0, '', '', command)
|
||||
|
||||
# unreadable key file should also not have any effect on
|
||||
# validation
|
||||
self.validate_certificate(0, certfile)
|
||||
|
||||
# Make the cert file unreadable (this should matter)
|
||||
with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]):
|
||||
self.run_check(106, '', '', command)
|
||||
|
||||
# Unreadable cert file should also fail validation
|
||||
self.validate_certificate(106, certfile)
|
||||
|
||||
# Not directly a permission problem, but trying to check or create
|
||||
# in a nonexistent directory returns different error codes
|
||||
self.validate_certificate(105, 'fakedir/cert')
|
||||
self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c',
|
||||
'fakedir/cert', '-k', 'fakedir/key' ])
|
||||
|
||||
if __name__== '__main__':
|
||||
unittest.main()
|
||||
|
21
src/bin/cmdctl/tests/testdata/mangled-certfile.pem
vendored
Normal file
21
src/bin/cmdctl/tests/testdata/mangled-certfile.pem
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
|
||||
VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
|
||||
A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
|
||||
MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
|
||||
NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
|
||||
ZWlqaW5nMraWDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
|
||||
CxMFY25uaWMxeZaRBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
|
||||
YW5nbGlrdW5AY25UAwMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
|
||||
JbEkYoy9SEsU9t/mfxLAICqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
|
||||
UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
|
||||
O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
|
||||
BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
|
||||
tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
|
||||
amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
|
||||
BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
|
||||
Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
|
||||
9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
|
||||
jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
|
||||
EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
|
||||
-----END CERTIFICATE-----
|
19
src/bin/cmdctl/tests/testdata/noca-certfile.pem
vendored
Normal file
19
src/bin/cmdctl/tests/testdata/noca-certfile.pem
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDBjCCAe6gAwIBAgIRALUIj3nnW5uDE/+fglPvUDwwDQYJKoZIhvcNAQELBQAw
|
||||
HjELMAkGA1UEBhMCVVMxDzANBgNVBAMTBkJJTkQxMDAeFw0xMjExMTQxMjQ5MjVa
|
||||
Fw0xMzExMTQxMjQ5MjVaMB4xCzAJBgNVBAYTAlVTMQ8wDQYDVQQDEwZCSU5EMTAw
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkIOPfs3Aw9kNDu1JqA2w3
|
||||
84/n9oUgAwAlHVmuJv7ZDw1MDaIKHjsh3DW09z+nv67GVksI7pFtAw5O4mnTDxpa
|
||||
JT0NKzhvYGfe8VdV/hWDogTIdk1QBJNZ2/id8z0h8z5001sARXPf+4mHBJslenH3
|
||||
YtZs22BG5RBLULtZ/2Nr7JkdfLlc6D5PCoDG22r1OiFkYVdCWfLDjisVIbSYPBtY
|
||||
BlKAIrvbmOtWcaGM+vQAhl0T5N8WRCKhaQH0DEmzQNckkYd7rSECo57KYiuvOdzp
|
||||
d+3bWTgGGy2ff0o3LZypv0O5s0TDC2H6hYtN4bUbcChUJbFu9b5sVZaOEVZtUsyD
|
||||
AgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQW
|
||||
BBSqGzsEDNs9E7gBL5pD6XVAwUo4DTANBgkqhkiG9w0BAQsFAAOCAQEAMTNB8NCU
|
||||
dnLFZ0jNpvecbECkX/OWGlBYU4/CsoNiibwp4CtUYS2A4NFVjWAyuzLSHhRQi0vJ
|
||||
CCWLpKL4VTkaDN5Oft42iUhvEXMnriJqpfXHnjCiBwFFSPl5WKfMIaRNK+tF4zbB
|
||||
F+FGNEEmYG3t/ni82orDLq4oy+7CoQwzZNzj5yoV6q7O9kLR9OMPNwJrc27A4erB
|
||||
7VMRZslSrNA4uA6YhMZl8iEvO1H801ct0zTxawrCihPOZOCSLew35xjztO7d3YH8
|
||||
YavOu5kzeu7AgZ2n75H/qU47ZgBjbonn9Osvrct+RIwZuWTB2bDML8JhNaZCq0aA
|
||||
TDBC0QWqIYypLg==
|
||||
-----END CERTIFICATE-----
|
@@ -45,11 +45,30 @@ class TestDhcpv4Daemon(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def readPipe(self, pipe_fd):
|
||||
"""
|
||||
Reads bytes from a pipe and returns a character string. If nothing is
|
||||
read, or if there is an error, an empty string is returned.
|
||||
|
||||
pipe_fd - Pipe file descriptor to read
|
||||
"""
|
||||
try:
|
||||
data = os.read(pipe_fd, 16384)
|
||||
# Make sure we have a string
|
||||
if (data is None):
|
||||
data = ""
|
||||
else:
|
||||
data = str(data)
|
||||
except OSError:
|
||||
data = ""
|
||||
|
||||
return data
|
||||
|
||||
def runCommand(self, params, wait=1):
|
||||
"""
|
||||
This method runs dhcp4 and returns a tuple: (returncode, stdout, stderr)
|
||||
This method runs a command and returns a tuple: (returncode, stdout, stderr)
|
||||
"""
|
||||
## @todo: Convert this into generic method and reuse it in dhcp6
|
||||
## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
|
||||
|
||||
print("Running command: %s" % (" ".join(params)))
|
||||
|
||||
@@ -89,46 +108,48 @@ class TestDhcpv4Daemon(unittest.TestCase):
|
||||
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
|
||||
# There's potential problem if b10-dhcp4 prints out more
|
||||
# than 16kB of text
|
||||
try:
|
||||
output = os.read(self.stdout_pipes[0], 16384)
|
||||
except OSError:
|
||||
print("No data available from stdout")
|
||||
output = ""
|
||||
# As we don't know how long the subprocess will take to start and
|
||||
# produce output, we'll loop and sleep for 250 ms between each
|
||||
# iteration. To avoid an infinite loop, we'll loop for a maximum
|
||||
# of five seconds: that should be enough.
|
||||
for count in range(20):
|
||||
# Read something from stderr and stdout (these reads don't block).
|
||||
output = self.readPipe(self.stdout_pipes[0])
|
||||
error = self.readPipe(self.stderr_pipes[0])
|
||||
|
||||
# read can return None. Make sure we have a string
|
||||
if (output is None):
|
||||
output = ""
|
||||
# If the process has already exited, or if it has output something,
|
||||
# quit the loop now.
|
||||
if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
|
||||
break
|
||||
|
||||
try:
|
||||
error = os.read(self.stderr_pipes[0], 16384)
|
||||
except OSError:
|
||||
print("No data available on stderr")
|
||||
error = ""
|
||||
# Process still running, try again in 250 ms.
|
||||
time.sleep(0.25)
|
||||
|
||||
# read can return None. Make sure we have a string
|
||||
if (error is None):
|
||||
error = ""
|
||||
|
||||
|
||||
try:
|
||||
if (not pi.process.poll()):
|
||||
# let's be nice at first...
|
||||
# Exited loop, kill the process if it is still running
|
||||
if pi.process.poll() is None:
|
||||
try:
|
||||
pi.process.terminate()
|
||||
except OSError:
|
||||
print("Ignoring failed kill attempt. Process is dead already.")
|
||||
except OSError:
|
||||
print("Ignoring failed kill attempt. Process is dead already.")
|
||||
|
||||
# call this to get returncode, process should be dead by now
|
||||
rc = pi.process.wait()
|
||||
|
||||
# Clean up our stdout/stderr munging.
|
||||
os.dup2(self.stdout_old, sys.stdout.fileno())
|
||||
os.close(self.stdout_old)
|
||||
os.close(self.stdout_pipes[0])
|
||||
|
||||
os.dup2(self.stderr_old, sys.stderr.fileno())
|
||||
os.close(self.stderr_old)
|
||||
os.close(self.stderr_pipes[0])
|
||||
|
||||
# Free up resources (file descriptors) from the ProcessInfo object
|
||||
# TODO: For some reason, this gives an error if the process has ended,
|
||||
# although it does cause all descriptors still allocated to the
|
||||
# object to be freed.
|
||||
pi = None
|
||||
|
||||
print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
|
||||
% (rc, len(output), len(error)) )
|
||||
|
||||
|
@@ -698,11 +698,8 @@ private:
|
||||
// We have exactly one option definition for the particular option code
|
||||
// use it to create the option instance.
|
||||
const OptionDefinitionPtr& def = *(range.first);
|
||||
// getFactory should never return NULL pointer.
|
||||
Option::Factory* factory = def->getFactory();
|
||||
assert(factory != NULL);
|
||||
try {
|
||||
OptionPtr option = factory(Option::V6, option_code, binary);
|
||||
OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
|
||||
Subnet::OptionDescriptor desc(option, false);
|
||||
option_descriptor_.option = option;
|
||||
option_descriptor_.persistent = false;
|
||||
|
@@ -56,12 +56,6 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
|
||||
|
||||
// Initialize objects required for DHCP server operation.
|
||||
try {
|
||||
// Initialize standard DHCPv6 option definitions. This function
|
||||
// may throw bad_alloc if system goes out of memory during the
|
||||
// creation if option definitions. It may also throw isc::Unexpected
|
||||
// if definitions are wrong. This would mean error in implementation.
|
||||
initStdOptionDefs();
|
||||
|
||||
// Port 0 is used for testing purposes. It means that the server should
|
||||
// not open any sockets at all. Some tests, e.g. configuration parser,
|
||||
// require Dhcpv6Srv object, but they don't really need it to do
|
||||
@@ -622,10 +616,5 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
|
||||
return (UNKNOWN);
|
||||
}
|
||||
|
||||
void
|
||||
Dhcpv6Srv::initStdOptionDefs() {
|
||||
LibDHCP::initStdOptionDefs(Option::V6);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
@@ -241,17 +241,6 @@ protected:
|
||||
/// interfaces for new DUID generation are detected.
|
||||
void setServerID();
|
||||
|
||||
/// @brief Initializes option definitions for standard options.
|
||||
///
|
||||
/// Each standard option's format is described by the
|
||||
/// dhcp::OptionDefinition object. This function creates such objects
|
||||
/// for each standard DHCPv6 option.
|
||||
///
|
||||
/// @todo list thrown exceptions.
|
||||
/// @todo extend this function to cover all standard options. Currently
|
||||
/// it is limited to critical options only.
|
||||
void initStdOptionDefs();
|
||||
|
||||
private:
|
||||
/// @brief Allocation Engine.
|
||||
/// Pointer to the allocation engine that we are currently using
|
||||
|
@@ -46,11 +46,6 @@ public:
|
||||
// srv_(0) means to not open any sockets. We don't want to
|
||||
// deal with sockets here, just check if configuration handling
|
||||
// is sane.
|
||||
|
||||
// Create instances of option definitions and put them into storage.
|
||||
// This is normally initialized by the server when calling run()
|
||||
// run() function.
|
||||
LibDHCP::initStdOptionDefs(Option::V6);
|
||||
}
|
||||
|
||||
~Dhcp6ParserTest() {
|
||||
|
@@ -45,6 +45,25 @@ class TestDhcpv6Daemon(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def readPipe(self, pipe_fd):
|
||||
"""
|
||||
Reads bytes from a pipe and returns a character string. If nothing is
|
||||
read, or if there is an error, an empty string is returned.
|
||||
|
||||
pipe_fd - Pipe file descriptor to read
|
||||
"""
|
||||
try:
|
||||
data = os.read(pipe_fd, 16384)
|
||||
# Make sure we have a string
|
||||
if (data is None):
|
||||
data = ""
|
||||
else:
|
||||
data = str(data)
|
||||
except OSError:
|
||||
data = ""
|
||||
|
||||
return data
|
||||
|
||||
def runCommand(self, params, wait=1):
|
||||
"""
|
||||
This method runs a command and returns a tuple: (returncode, stdout, stderr)
|
||||
@@ -89,45 +108,48 @@ class TestDhcpv6Daemon(unittest.TestCase):
|
||||
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
|
||||
# There's potential problem if b10-dhcp4 prints out more
|
||||
# than 16k of text
|
||||
try:
|
||||
output = os.read(self.stdout_pipes[0], 16384)
|
||||
except OSError:
|
||||
print("No data available from stdout")
|
||||
output = ""
|
||||
# As we don't know how long the subprocess will take to start and
|
||||
# produce output, we'll loop and sleep for 250 ms between each
|
||||
# iteration. To avoid an infinite loop, we'll loop for a maximum
|
||||
# of five seconds: that should be enough.
|
||||
for count in range(20):
|
||||
# Read something from stderr and stdout (these reads don't block).
|
||||
output = self.readPipe(self.stdout_pipes[0])
|
||||
error = self.readPipe(self.stderr_pipes[0])
|
||||
|
||||
# read can return None. Make sure we have a string
|
||||
if (output is None):
|
||||
output = ""
|
||||
# If the process has already exited, or if it has output something,
|
||||
# quit the loop now.
|
||||
if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
|
||||
break
|
||||
|
||||
try:
|
||||
error = os.read(self.stderr_pipes[0], 16384)
|
||||
except OSError:
|
||||
print("No data available on stderr")
|
||||
error = ""
|
||||
# Process still running, try again in 250 ms.
|
||||
time.sleep(0.25)
|
||||
|
||||
# read can return None. Make sure we have a string
|
||||
if (error is None):
|
||||
error = ""
|
||||
|
||||
try:
|
||||
if (not pi.process.poll()):
|
||||
# let's be nice at first...
|
||||
# Exited loop, kill the process if it is still running
|
||||
if pi.process.poll() is None:
|
||||
try:
|
||||
pi.process.terminate()
|
||||
except OSError:
|
||||
print("Ignoring failed kill attempt. Process is dead already.")
|
||||
except OSError:
|
||||
print("Ignoring failed kill attempt. Process is dead already.")
|
||||
|
||||
# call this to get returncode, process should be dead by now
|
||||
rc = pi.process.wait()
|
||||
|
||||
# Clean up our stdout/stderr munging.
|
||||
os.dup2(self.stdout_old, sys.stdout.fileno())
|
||||
os.close(self.stdout_old)
|
||||
os.close(self.stdout_pipes[0])
|
||||
|
||||
os.dup2(self.stderr_old, sys.stderr.fileno())
|
||||
os.close(self.stderr_old)
|
||||
os.close(self.stderr_pipes[0])
|
||||
|
||||
# Free up resources (file descriptors) from the ProcessInfo object
|
||||
# TODO: For some reason, this gives an error if the process has ended,
|
||||
# although it does cause all descriptors still allocated to the
|
||||
# object to be freed.
|
||||
pi = None
|
||||
|
||||
print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
|
||||
% (rc, len(output), len(error)) )
|
||||
|
||||
|
@@ -127,6 +127,7 @@ class MsgQ:
|
||||
self.subs = SubscriptionManager()
|
||||
self.lnames = {}
|
||||
self.sendbuffs = {}
|
||||
self.running = False
|
||||
|
||||
def setup_poller(self):
|
||||
"""Set up the poll thing. Internal function."""
|
||||
@@ -315,6 +316,8 @@ class MsgQ:
|
||||
elif cmd == 'ping':
|
||||
# Command for testing purposes
|
||||
self.process_command_ping(sock, routing, data)
|
||||
elif cmd == 'stop':
|
||||
self.stop()
|
||||
else:
|
||||
sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
|
||||
|
||||
@@ -336,14 +339,34 @@ class MsgQ:
|
||||
self.send_prepared_msg(sock, self.preparemsg(env, msg))
|
||||
|
||||
def __send_data(self, sock, data):
|
||||
"""
|
||||
Send a piece of data to the given socket.
|
||||
Parameters:
|
||||
sock: The socket to send to
|
||||
data: The list of bytes to send
|
||||
Returns:
|
||||
An integer or None. If an integer (which can be 0), it signals
|
||||
the number of bytes sent. If None, the socket appears to have
|
||||
been closed on the other end, and it has been killed on this
|
||||
side too.
|
||||
"""
|
||||
try:
|
||||
# We set the socket nonblocking, MSG_DONTWAIT doesn't exist
|
||||
# on some OSes
|
||||
sock.setblocking(0)
|
||||
return sock.send(data)
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
|
||||
if e.errno in [ errno.EAGAIN,
|
||||
errno.EWOULDBLOCK,
|
||||
errno.EINTR ]:
|
||||
return 0
|
||||
elif e.errno in [ errno.EPIPE,
|
||||
errno.ECONNRESET,
|
||||
errno.ENOBUFS ]:
|
||||
print("[b10-msgq] " + errno.errorcode[e.errno] +
|
||||
" on send, dropping message and closing connection")
|
||||
self.kill_socket(sock.fileno(), sock)
|
||||
return None
|
||||
else:
|
||||
raise e
|
||||
finally:
|
||||
@@ -356,20 +379,12 @@ class MsgQ:
|
||||
if fileno in self.sendbuffs:
|
||||
amount_sent = 0
|
||||
else:
|
||||
try:
|
||||
amount_sent = self.__send_data(sock, msg)
|
||||
except socket.error as sockerr:
|
||||
# in the case the other side seems gone, kill the socket
|
||||
# and drop the send action
|
||||
if sockerr.errno == errno.EPIPE:
|
||||
print("[b10-msgq] SIGPIPE on send, dropping message " +
|
||||
"and closing connection")
|
||||
self.kill_socket(fileno, sock)
|
||||
return
|
||||
else:
|
||||
raise
|
||||
amount_sent = self.__send_data(sock, msg)
|
||||
if amount_sent is None:
|
||||
# Socket has been killed, drop the send
|
||||
return
|
||||
|
||||
# Still something to send
|
||||
# Still something to send, add it to outgoing queue
|
||||
if amount_sent < len(msg):
|
||||
now = time.clock()
|
||||
# Append it to buffer (but check the data go away)
|
||||
@@ -394,17 +409,18 @@ class MsgQ:
|
||||
(_, msg) = self.sendbuffs[fileno]
|
||||
sock = self.sockets[fileno]
|
||||
amount_sent = self.__send_data(sock, msg)
|
||||
# Keep the rest
|
||||
msg = msg[amount_sent:]
|
||||
if len(msg) == 0:
|
||||
# If there's no more, stop requesting for write availability
|
||||
if self.poller:
|
||||
self.poller.register(fileno, select.POLLIN)
|
||||
if amount_sent is not None:
|
||||
# Keep the rest
|
||||
msg = msg[amount_sent:]
|
||||
if len(msg) == 0:
|
||||
# If there's no more, stop requesting for write availability
|
||||
if self.poller:
|
||||
self.poller.register(fileno, select.POLLIN)
|
||||
else:
|
||||
self.delete_kqueue_socket(sock, True)
|
||||
del self.sendbuffs[fileno]
|
||||
else:
|
||||
self.delete_kqueue_socket(sock, True)
|
||||
del self.sendbuffs[fileno]
|
||||
else:
|
||||
self.sendbuffs[fileno] = (time.clock(), msg)
|
||||
self.sendbuffs[fileno] = (time.clock(), msg)
|
||||
|
||||
def newlname(self):
|
||||
"""Generate a unique connection identifier for this socket.
|
||||
@@ -458,6 +474,7 @@ class MsgQ:
|
||||
|
||||
def run(self):
|
||||
"""Process messages. Forever. Mostly."""
|
||||
self.running = True
|
||||
|
||||
if self.poller:
|
||||
self.run_poller()
|
||||
@@ -465,8 +482,10 @@ class MsgQ:
|
||||
self.run_kqueue()
|
||||
|
||||
def run_poller(self):
|
||||
while True:
|
||||
while self.running:
|
||||
try:
|
||||
# Poll with a timeout so that every once in a while,
|
||||
# the loop checks for self.running.
|
||||
events = self.poller.poll()
|
||||
except select.error as err:
|
||||
if err.args[0] == errno.EINTR:
|
||||
@@ -480,11 +499,15 @@ class MsgQ:
|
||||
else:
|
||||
if event & select.POLLOUT:
|
||||
self.__process_write(fd)
|
||||
if event & select.POLLIN:
|
||||
elif event & select.POLLIN:
|
||||
self.process_socket(fd)
|
||||
else:
|
||||
print("[b10-msgq] Error: Unknown even in run_poller()")
|
||||
|
||||
def run_kqueue(self):
|
||||
while True:
|
||||
while self.running:
|
||||
# Check with a timeout so that every once in a while,
|
||||
# the loop checks for self.running.
|
||||
events = self.kqueue.control(None, 10)
|
||||
if not events:
|
||||
raise RuntimeError('serve: kqueue returned no events')
|
||||
@@ -502,6 +525,9 @@ class MsgQ:
|
||||
self.kill_socket(event.ident,
|
||||
self.sockets[event.ident])
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
def shutdown(self):
|
||||
"""Stop the MsgQ master."""
|
||||
if self.verbose:
|
||||
|
@@ -6,7 +6,10 @@ import socket
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
import errno
|
||||
import threading
|
||||
import isc.cc
|
||||
import collections
|
||||
|
||||
#
|
||||
# Currently only the subscription part and some sending is implemented...
|
||||
@@ -112,6 +115,85 @@ class TestSubscriptionManager(unittest.TestCase):
|
||||
msgq = MsgQ("/does/not/exist")
|
||||
self.assertRaises(socket.error, msgq.setup)
|
||||
|
||||
class DummySocket:
|
||||
"""
|
||||
Dummy socket class.
|
||||
This one does nothing at all, but some calls are used.
|
||||
It is mainly intended to override the listen socket for msgq, which
|
||||
we do not need in these tests.
|
||||
"""
|
||||
def fileno():
|
||||
return -1
|
||||
|
||||
def close():
|
||||
pass
|
||||
|
||||
class BadSocket:
|
||||
"""
|
||||
Special socket wrapper class. Once given a socket in its constructor,
|
||||
it completely behaves like that socket, except that its send() call
|
||||
will only actually send one byte per call, and optionally raise a given
|
||||
exception at a given time.
|
||||
"""
|
||||
def __init__(self, real_socket, raise_on_send=0, send_exception=None):
|
||||
"""
|
||||
Parameters:
|
||||
real_socket: The actual socket to wrap
|
||||
raise_on_send: integer. If higher than 0, and send_exception is
|
||||
not None, send_exception will be raised on the
|
||||
'raise_on_send'th call to send().
|
||||
send_exception: if not None, this exception will be raised
|
||||
(if raise_on_send is not 0)
|
||||
"""
|
||||
self.socket = real_socket
|
||||
self.send_count = 0
|
||||
self.raise_on_send = raise_on_send
|
||||
self.send_exception = send_exception
|
||||
|
||||
# completely wrap all calls and member access
|
||||
# (except explicitely overridden ones)
|
||||
def __getattr__(self, name, *args):
|
||||
attr = getattr(self.socket, name)
|
||||
if isinstance(attr, collections.Callable):
|
||||
def callable_attr(*args):
|
||||
return attr.__call__(*args)
|
||||
return callable_attr
|
||||
else:
|
||||
return attr
|
||||
|
||||
def send(self, data):
|
||||
self.send_count += 1
|
||||
if self.send_exception is not None and\
|
||||
self.send_count == self.raise_on_send:
|
||||
raise self.send_exception
|
||||
|
||||
if len(data) > 0:
|
||||
return self.socket.send(data[:1])
|
||||
else:
|
||||
return 0
|
||||
|
||||
class MsgQThread(threading.Thread):
|
||||
"""
|
||||
Very simple thread class that runs msgq.run() when started,
|
||||
and stores the exception that msgq.run() raises, if any.
|
||||
"""
|
||||
def __init__(self, msgq):
|
||||
threading.Thread.__init__(self)
|
||||
self.msgq_ = msgq
|
||||
self.caught_exception = None
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.msgq_.run()
|
||||
except Exception as exc:
|
||||
# Store the exception to make the test fail if necessary
|
||||
self.caught_exception = exc
|
||||
|
||||
def stop(self):
|
||||
self.msgq_.stop()
|
||||
|
||||
|
||||
class SendNonblock(unittest.TestCase):
|
||||
"""
|
||||
Tests that the whole thing will not get blocked if someone does not read.
|
||||
@@ -191,9 +273,6 @@ class SendNonblock(unittest.TestCase):
|
||||
msgq = MsgQ()
|
||||
# msgq.run needs to compare with the listen_socket, so we provide
|
||||
# a replacement
|
||||
class DummySocket:
|
||||
def fileno():
|
||||
return -1
|
||||
msgq.listen_socket = DummySocket
|
||||
(queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
def run():
|
||||
@@ -245,5 +324,137 @@ class SendNonblock(unittest.TestCase):
|
||||
data = data + data
|
||||
self.send_many(data)
|
||||
|
||||
def do_send(self, write, read, control_write, control_read,
|
||||
expect_arrive=True, expect_send_exception=None):
|
||||
"""
|
||||
Makes a msgq object that is talking to itself,
|
||||
run it in a separate thread so we can use and
|
||||
test run().
|
||||
It is given two sets of connected sockets; write/read, and
|
||||
control_write/control_read. The former may be throwing errors
|
||||
and mangle data to test msgq. The second is mainly used to
|
||||
send msgq the stop command.
|
||||
(Note that the terms 'read' and 'write' are from the msgq
|
||||
point of view, so the test itself writes to 'control_read')
|
||||
Parameters:
|
||||
write: a socket that is used to send the data to
|
||||
read: a socket that is used to read the data from
|
||||
control_write: a second socket for communication with msgq
|
||||
control_read: a second socket for communication with msgq
|
||||
expect_arrive: if True, the read socket is read from, and the data
|
||||
that is read is expected to be the same as the data
|
||||
that has been sent to the write socket.
|
||||
expect_send_exception: if not None, this is the exception that is
|
||||
expected to be raised by msgq
|
||||
"""
|
||||
|
||||
# Some message and envelope data to send and check
|
||||
env = b'{"env": "foo"}'
|
||||
msg = b'{"msg": "bar"}'
|
||||
|
||||
msgq = MsgQ()
|
||||
# Don't need a listen_socket
|
||||
msgq.listen_socket = DummySocket
|
||||
msgq.setup_poller()
|
||||
msgq.register_socket(write)
|
||||
msgq.register_socket(control_write)
|
||||
# Queue the message for sending
|
||||
msgq.sendmsg(write, env, msg)
|
||||
|
||||
# Run it in a thread
|
||||
msgq_thread = MsgQThread(msgq)
|
||||
# If we're done, just kill it
|
||||
msgq_thread.start()
|
||||
|
||||
if expect_arrive:
|
||||
(recv_env, recv_msg) = msgq.read_packet(read.fileno(),
|
||||
read)
|
||||
self.assertEqual(env, recv_env)
|
||||
self.assertEqual(msg, recv_msg)
|
||||
|
||||
# Tell msgq to stop
|
||||
msg = msgq.preparemsg({"type" : "stop"})
|
||||
control_read.sendall(msg)
|
||||
|
||||
# Wait for thread to stop if it hasn't already.
|
||||
# Put in a (long) timeout; the thread *should* stop, but if it
|
||||
# does not, we don't want the test to hang forever
|
||||
msgq_thread.join(60)
|
||||
# Fail the test if it didn't stop
|
||||
self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
|
||||
|
||||
# Check the exception from the thread, if any
|
||||
# First, if we didn't expect it; reraise it (to make test fail and
|
||||
# show the stacktrace for debugging)
|
||||
if expect_send_exception is None:
|
||||
if msgq_thread.caught_exception is not None:
|
||||
raise msgq_thread.caught_exception
|
||||
else:
|
||||
# If we *did* expect it, fail it there was none
|
||||
self.assertIsNotNone(msgq_thread.caught_exception)
|
||||
|
||||
def do_send_with_send_error(self, raise_on_send, send_exception,
|
||||
expect_answer=True,
|
||||
expect_send_exception=None):
|
||||
"""
|
||||
Sets up two connected sockets, wraps the sender socket into a BadSocket
|
||||
class, then performs a do_send() test.
|
||||
Parameters:
|
||||
raise_on_send: the byte at which send_exception should be raised
|
||||
(see BadSocket)
|
||||
send_exception: the exception to raise (see BadSocket)
|
||||
expect_answer: whether the send is expected to complete (and hence
|
||||
the read socket should get the message)
|
||||
expect_send_exception: the exception msgq is expected to raise when
|
||||
send_exception is raised by BadSocket.
|
||||
"""
|
||||
(write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
(control_write, control_read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
badwrite = BadSocket(write, raise_on_send, send_exception)
|
||||
self.do_send(badwrite, read, control_write, control_read, expect_answer, expect_send_exception)
|
||||
write.close()
|
||||
read.close()
|
||||
control_write.close()
|
||||
control_read.close()
|
||||
|
||||
def test_send_raise_recoverable(self):
|
||||
"""
|
||||
Test whether msgq survices a recoverable socket errors when sending.
|
||||
Two tests are done: one where the error is raised on the 3rd octet,
|
||||
and one on the 23rd.
|
||||
"""
|
||||
sockerr = socket.error
|
||||
for err in [ errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR ]:
|
||||
sockerr.errno = err
|
||||
self.do_send_with_send_error(3, sockerr)
|
||||
self.do_send_with_send_error(23, sockerr)
|
||||
|
||||
def test_send_raise_nonrecoverable(self):
|
||||
"""
|
||||
Test whether msgq survives socket errors that are nonrecoverable
|
||||
(for said socket that is, i.e. EPIPE etc).
|
||||
Two tests are done: one where the error is raised on the 3rd octet,
|
||||
and one on the 23rd.
|
||||
"""
|
||||
sockerr = socket.error
|
||||
for err in [ errno.EPIPE, errno.ENOBUFS, errno.ECONNRESET ]:
|
||||
sockerr.errno = err
|
||||
self.do_send_with_send_error(3, sockerr, False)
|
||||
self.do_send_with_send_error(23, sockerr, False)
|
||||
|
||||
def otest_send_raise_crash(self):
|
||||
"""
|
||||
Test whether msgq does NOT survive on a general exception.
|
||||
Note, perhaps it should; but we'd have to first discuss and decide
|
||||
how it should recover (i.e. drop the socket and consider the client
|
||||
dead?
|
||||
It may be a coding problem in msgq itself, and we certainly don't
|
||||
want to ignore those.
|
||||
"""
|
||||
sockerr = Exception("just some general exception")
|
||||
self.do_send_with_send_error(3, sockerr, False, sockerr)
|
||||
self.do_send_with_send_error(23, sockerr, False, sockerr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@@ -61,7 +61,7 @@ IOAddress::toText() const {
|
||||
}
|
||||
|
||||
IOAddress
|
||||
IOAddress::from_bytes(short family, const uint8_t* data) {
|
||||
IOAddress::fromBytes(short family, const uint8_t* data) {
|
||||
if (data == NULL) {
|
||||
isc_throw(BadValue, "NULL pointer received.");
|
||||
} else
|
||||
|
@@ -111,7 +111,7 @@ public:
|
||||
///
|
||||
/// \return Created IOAddress object
|
||||
static IOAddress
|
||||
from_bytes(short family, const uint8_t* data);
|
||||
fromBytes(short family, const uint8_t* data);
|
||||
|
||||
/// \brief Compare addresses for equality
|
||||
///
|
||||
|
@@ -64,7 +64,7 @@ TEST(IOAddressTest, Family) {
|
||||
EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
|
||||
}
|
||||
|
||||
TEST(IOAddressTest, from_bytes) {
|
||||
TEST(IOAddressTest, fromBytes) {
|
||||
// 2001:db8:1::dead:beef
|
||||
uint8_t v6[] = {
|
||||
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
|
||||
@@ -74,12 +74,12 @@ TEST(IOAddressTest, from_bytes) {
|
||||
|
||||
IOAddress addr("::");
|
||||
EXPECT_NO_THROW({
|
||||
addr = IOAddress::from_bytes(AF_INET6, v6);
|
||||
addr = IOAddress::fromBytes(AF_INET6, v6);
|
||||
});
|
||||
EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
addr = IOAddress::from_bytes(AF_INET, v4);
|
||||
addr = IOAddress::fromBytes(AF_INET, v4);
|
||||
});
|
||||
EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
|
||||
}
|
||||
|
@@ -36,6 +36,8 @@ libb10_datasrc_la_SOURCES += database.h database.cc
|
||||
libb10_datasrc_la_SOURCES += factory.h factory.cc
|
||||
libb10_datasrc_la_SOURCES += client_list.h client_list.cc
|
||||
libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
|
||||
libb10_datasrc_la_SOURCES += master_loader_callbacks.h
|
||||
libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
|
||||
nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
|
||||
libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
|
||||
|
||||
|
@@ -315,6 +315,18 @@ An error was found in the zone data when it was being loaded from
|
||||
another data source. The zone was not loaded. The specific error is
|
||||
shown in the message, and should be addressed.
|
||||
|
||||
% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5
|
||||
There's an error in the given master file. The zone won't be loaded for
|
||||
this reason. Parsing might follow, so you might get further errors and
|
||||
warnings to fix everything at once. But in case the error is serious enough,
|
||||
the parser might just give up or get confused and generate false errors
|
||||
afterwards.
|
||||
|
||||
% DATASRC_MASTER_LOAD_WARN %1:%2: Zone '%3/%4' has a potential problem: %5
|
||||
There's something suspicious in the master file. This is a warning only.
|
||||
It may be a problem or it may be harmless, but it should be checked.
|
||||
This problem does not stop the zone from being loaded.
|
||||
|
||||
% DATASRC_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
|
||||
Debug information. An RRset is being added to the in-memory data source.
|
||||
|
||||
|
73
src/lib/datasrc/master_loader_callbacks.cc
Normal file
73
src/lib/datasrc/master_loader_callbacks.cc
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <datasrc/master_loader_callbacks.h>
|
||||
#include <datasrc/zone.h>
|
||||
#include <datasrc/logger.h>
|
||||
|
||||
#include <dns/name.h>
|
||||
#include <dns/rrclass.h>
|
||||
#include <dns/rrset.h>
|
||||
|
||||
#include <string>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
namespace isc {
|
||||
namespace datasrc {
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
logError(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
|
||||
bool* ok, const std::string& source, size_t line,
|
||||
const std::string& reason)
|
||||
{
|
||||
LOG_ERROR(logger, DATASRC_MASTER_LOAD_ERROR).arg(source).arg(line).
|
||||
arg(name).arg(rrclass).arg(reason);
|
||||
if (ok != NULL) {
|
||||
*ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
|
||||
const std::string& source, size_t line, const std::string& reason)
|
||||
{
|
||||
LOG_WARN(logger, DATASRC_MASTER_LOAD_WARN).arg(source).arg(line).
|
||||
arg(name).arg(rrclass).arg(reason);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isc::dns::MasterLoaderCallbacks
|
||||
createMasterLoaderCallbacks(const isc::dns::Name& name,
|
||||
const isc::dns::RRClass& rrclass, bool* ok)
|
||||
{
|
||||
return (isc::dns::MasterLoaderCallbacks(boost::bind(&logError, name,
|
||||
rrclass, ok, _1, _2,
|
||||
_3),
|
||||
boost::bind(&logWarning, name,
|
||||
rrclass, _1, _2, _3)));
|
||||
}
|
||||
|
||||
isc::dns::AddRRsetCallback
|
||||
createMasterLoaderAddCallback(ZoneUpdater& updater) {
|
||||
return (boost::bind(&ZoneUpdater::addRRset, &updater,
|
||||
// The callback provides a shared pointer, we
|
||||
// need the object. This bind unpacks the object.
|
||||
boost::bind(&isc::dns::RRsetPtr::operator*, _1)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
67
src/lib/datasrc/master_loader_callbacks.h
Normal file
67
src/lib/datasrc/master_loader_callbacks.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef DATASRC_MASTER_LOADER_CALLBACKS_H
|
||||
#define DATASRC_MASTER_LOADER_CALLBACKS_H
|
||||
|
||||
#include <dns/master_loader_callbacks.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dns {
|
||||
class Name;
|
||||
class RRClass;
|
||||
}
|
||||
namespace datasrc {
|
||||
|
||||
class ZoneUpdater;
|
||||
|
||||
/// \brief Create issue callbacks for MasterLoader
|
||||
///
|
||||
/// This will create set of callbacks for the MasterLoader that
|
||||
/// will be used to report any issues found in the zone data.
|
||||
///
|
||||
/// \param name Name of the zone. Used in logging.
|
||||
/// \param rrclass The class of the zone. Used in logging.
|
||||
/// \param ok If this is non-NULL and there are any errors during
|
||||
/// the loading, it is set to false. Otherwise, it is untouched.
|
||||
/// \return Set of callbacks to be passed to the master loader.
|
||||
/// \throw std::bad_alloc when allocation fails.
|
||||
isc::dns::MasterLoaderCallbacks
|
||||
createMasterLoaderCallbacks(const isc::dns::Name& name,
|
||||
const isc::dns::RRClass& rrclass, bool* ok);
|
||||
|
||||
/// \brief Create a callback for MasterLoader to add RRsets.
|
||||
///
|
||||
/// This creates a callback that can be used by the MasterLoader to add
|
||||
/// loaded RRsets into a zone updater.
|
||||
///
|
||||
/// The zone updater should be opened in the replace mode no changes should
|
||||
/// have been done to it yet (but it is not checked). It is not commited
|
||||
/// automatically and it is up to the caller to commit the changes (or not).
|
||||
/// It must not be destroyed for the whole time of loading.
|
||||
///
|
||||
/// The function is mostly straightforward packing of the updater.addRRset
|
||||
/// into a boost::function, it is defined explicitly due to small technical
|
||||
/// annoyences around boost::bind application, so it can be reused.
|
||||
///
|
||||
/// \param updater The zone updater to use.
|
||||
/// \return The callback to be passed to MasterLoader.
|
||||
/// \throw std::bad_alloc when allocation fails.
|
||||
isc::dns::AddRRsetCallback
|
||||
createMasterLoaderAddCallback(ZoneUpdater& updater);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@@ -27,6 +27,7 @@ libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
|
||||
libdatasrc_memory_la_SOURCES += zone_writer.h
|
||||
libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
|
||||
libdatasrc_memory_la_SOURCES += load_action.h
|
||||
libdatasrc_memory_la_SOURCES += util_internal.h
|
||||
|
||||
nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
|
||||
|
||||
|
@@ -122,8 +122,8 @@ RdataSet::create(util::MemorySegment& mem_sgmt, RdataEncoder& encoder,
|
||||
}
|
||||
|
||||
void
|
||||
RdataSet::destroy(util::MemorySegment& mem_sgmt, RRClass rrclass,
|
||||
RdataSet* rdataset)
|
||||
RdataSet::destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
|
||||
RRClass rrclass)
|
||||
{
|
||||
const size_t data_len =
|
||||
RdataReader(rrclass, rdataset->type,
|
||||
|
@@ -187,12 +187,12 @@ public:
|
||||
///
|
||||
/// \param mem_sgmt The \c MemorySegment that allocated memory for
|
||||
/// \c node.
|
||||
/// \param rrclass The RR class of the \c RdataSet to be destroyed.
|
||||
/// \param rdataset A non NULL pointer to a valid \c RdataSet object
|
||||
/// \param rrclass The RR class of the \c RdataSet to be destroyed.
|
||||
/// that was originally created by the \c create() method (the behavior
|
||||
/// is undefined if this condition isn't met).
|
||||
static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass,
|
||||
RdataSet* rdataset);
|
||||
static void destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
|
||||
dns::RRClass rrclass);
|
||||
|
||||
/// \brief Find \c RdataSet of given RR type from a list (const version).
|
||||
///
|
||||
@@ -205,6 +205,11 @@ public:
|
||||
/// if not found in the entire list, it returns NULL. The head pointer
|
||||
/// can be NULL, in which case this function will simply return NULL.
|
||||
///
|
||||
/// By default, this method ignores an RdataSet that only contains an
|
||||
/// RRSIG (i.e., missing the covered RdataSet); if the optional
|
||||
/// sigonly_ok parameter is explicitly set to true, it matches such
|
||||
/// RdataSet and returns it if found.
|
||||
///
|
||||
/// \note This function is defined as a (static) class method to
|
||||
/// clarify its an operation for \c RdataSet objects and to make the
|
||||
/// name shorter. But its implementation does not depend on private
|
||||
@@ -215,10 +220,14 @@ public:
|
||||
/// \param rdata_head A pointer to \c RdataSet from which the search
|
||||
/// starts. It can be NULL.
|
||||
/// \param type The RRType of \c RdataSet to find.
|
||||
/// \param sigonly_ok Whether it should find an RdataSet that only has
|
||||
/// RRSIG
|
||||
/// \return A pointer to the found \c RdataSet or NULL if none found.
|
||||
static const RdataSet*
|
||||
find(const RdataSet* rdataset_head, const dns::RRType& type) {
|
||||
return (find<const RdataSet>(rdataset_head, type));
|
||||
find(const RdataSet* rdataset_head, const dns::RRType& type,
|
||||
bool sigonly_ok = false)
|
||||
{
|
||||
return (find<const RdataSet>(rdataset_head, type, sigonly_ok));
|
||||
}
|
||||
|
||||
/// \brief Find \c RdataSet of given RR type from a list (non const
|
||||
@@ -227,8 +236,10 @@ public:
|
||||
/// This is similar to the const version, except it takes and returns non
|
||||
/// const pointers.
|
||||
static RdataSet*
|
||||
find(RdataSet* rdataset_head, const dns::RRType& type) {
|
||||
return (find<RdataSet>(rdataset_head, type));
|
||||
find(RdataSet* rdataset_head, const dns::RRType& type,
|
||||
bool sigonly_ok = false)
|
||||
{
|
||||
return (find<RdataSet>(rdataset_head, type, sigonly_ok));
|
||||
}
|
||||
|
||||
typedef boost::interprocess::offset_ptr<RdataSet> RdataSetPtr;
|
||||
@@ -347,12 +358,14 @@ private:
|
||||
// Shared by both mutable and immutable versions of find()
|
||||
template <typename RdataSetType>
|
||||
static RdataSetType*
|
||||
find(RdataSetType* rdataset_head, const dns::RRType& type) {
|
||||
find(RdataSetType* rdataset_head, const dns::RRType& type, bool sigonly_ok)
|
||||
{
|
||||
for (RdataSetType* rdataset = rdataset_head;
|
||||
rdataset != NULL;
|
||||
rdataset = rdataset->getNext()) // use getNext() for efficiency
|
||||
{
|
||||
if (rdataset->type == type) {
|
||||
if (rdataset->type == type &&
|
||||
(rdataset->getRdataCount() > 0 || sigonly_ok)) {
|
||||
return (rdataset);
|
||||
}
|
||||
}
|
||||
|
57
src/lib/datasrc/memory/util_internal.h
Normal file
57
src/lib/datasrc/memory/util_internal.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef DATASRC_MEMORY_UTIL_INTERNAL_H
|
||||
#define DATASRC_MEMORY_UTIL_INTERNAL_H 1
|
||||
|
||||
#include <dns/rdataclass.h>
|
||||
#include <dns/rrset.h>
|
||||
#include <dns/rrtype.h>
|
||||
|
||||
namespace isc {
|
||||
namespace datasrc {
|
||||
namespace memory {
|
||||
namespace detail {
|
||||
|
||||
/// \brief Return the covered RR type of an RRSIG RRset.
|
||||
///
|
||||
/// This is a commonly used helper to extract the type covered field of an
|
||||
/// RRSIG RRset and return it in the form of an RRType object.
|
||||
///
|
||||
/// Normally, an empty RRSIG shouldn't be passed to this function, whether
|
||||
/// it comes from a master file or another data source iterator, but it could
|
||||
/// still happen in some buggy situations. This function catches and rejects
|
||||
/// such cases.
|
||||
inline dns::RRType
|
||||
getCoveredType(const dns::ConstRRsetPtr& sig_rrset) {
|
||||
dns::RdataIteratorPtr it = sig_rrset->getRdataIterator();
|
||||
if (it->isLast()) {
|
||||
isc_throw(isc::Unexpected,
|
||||
"Empty RRset is passed in-memory loader, name: "
|
||||
<< sig_rrset->getName());
|
||||
}
|
||||
return (dynamic_cast<const dns::rdata::generic::RRSIG&>(it->getCurrent()).
|
||||
typeCovered());
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace memory
|
||||
} // namespace datasrc
|
||||
} // namespace isc
|
||||
|
||||
#endif // DATASRC_MEMORY_UTIL_INTERNAL_H
|
||||
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
@@ -49,7 +49,7 @@ rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
|
||||
rdataset = rdataset_next)
|
||||
{
|
||||
rdataset_next = rdataset->getNext();
|
||||
RdataSet::destroy(*mem_sgmt, rrclass, rdataset);
|
||||
RdataSet::destroy(*mem_sgmt, rdataset, rrclass);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,6 +16,7 @@
|
||||
#include <datasrc/memory/zone_data_updater.h>
|
||||
#include <datasrc/memory/logger.h>
|
||||
#include <datasrc/memory/segment_object_holder.h>
|
||||
#include <datasrc/memory/util_internal.h>
|
||||
|
||||
#include <dns/rdataclass.h>
|
||||
#include <dns/rrset.h>
|
||||
@@ -35,6 +36,7 @@ namespace datasrc {
|
||||
namespace memory {
|
||||
|
||||
using detail::SegmentObjectHolder;
|
||||
using detail::getCoveredType;
|
||||
|
||||
namespace { // unnamed namespace
|
||||
|
||||
@@ -75,8 +77,6 @@ private:
|
||||
typedef NodeRRsets::value_type NodeRRsetsVal;
|
||||
|
||||
// A helper to identify the covered type of an RRSIG.
|
||||
static isc::dns::RRType getCoveredType
|
||||
(const isc::dns::ConstRRsetPtr& sig_rrset);
|
||||
const isc::dns::Name& getCurrentName() const;
|
||||
|
||||
private:
|
||||
@@ -126,34 +126,17 @@ ZoneDataLoader::flushNodeRRsets() {
|
||||
updater_.add(val.second, sig_rrset);
|
||||
}
|
||||
|
||||
// Right now, we don't accept RRSIG without covered RRsets (this
|
||||
// should eventually allowed, but to do so we'll need to update the
|
||||
// finder).
|
||||
if (!node_rrsigsets_.empty()) {
|
||||
isc_throw(ZoneDataUpdater::AddError,
|
||||
"RRSIG is added without covered RRset for "
|
||||
<< getCurrentName());
|
||||
// Normally rrsigsets map should be empty at this point, but it's still
|
||||
// possible that an RRSIG that don't has covered RRset is added; they
|
||||
// still remain in the map. We add them to the zone separately.
|
||||
BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
|
||||
updater_.add(ConstRRsetPtr(), val.second);
|
||||
}
|
||||
|
||||
node_rrsets_.clear();
|
||||
node_rrsigsets_.clear();
|
||||
}
|
||||
|
||||
RRType
|
||||
ZoneDataLoader::getCoveredType(const ConstRRsetPtr& sig_rrset) {
|
||||
RdataIteratorPtr it = sig_rrset->getRdataIterator();
|
||||
// Empty RRSIG shouldn't be passed either via a master file or
|
||||
// another data source iterator, but it could still happen if the
|
||||
// iterator has a bug. We catch and reject such cases.
|
||||
if (it->isLast()) {
|
||||
isc_throw(isc::Unexpected,
|
||||
"Empty RRset is passed in-memory loader, name: "
|
||||
<< sig_rrset->getName());
|
||||
}
|
||||
return (dynamic_cast<const generic::RRSIG&>(it->getCurrent()).
|
||||
typeCovered());
|
||||
}
|
||||
|
||||
const Name&
|
||||
ZoneDataLoader::getCurrentName() const {
|
||||
if (!node_rrsets_.empty()) {
|
||||
|
@@ -12,12 +12,18 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <datasrc/memory/zone_data_updater.h>
|
||||
#include <datasrc/memory/logger.h>
|
||||
#include <datasrc/memory/util_internal.h>
|
||||
#include <datasrc/zone.h>
|
||||
|
||||
#include <dns/rdataclass.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
using namespace isc::dns;
|
||||
using namespace isc::dns::rdata;
|
||||
|
||||
@@ -25,6 +31,8 @@ namespace isc {
|
||||
namespace datasrc {
|
||||
namespace memory {
|
||||
|
||||
using detail::getCoveredType;
|
||||
|
||||
void
|
||||
ZoneDataUpdater::addWildcards(const Name& name) {
|
||||
Name wname(name);
|
||||
@@ -99,9 +107,7 @@ ZoneDataUpdater::contextCheck(const AbstractRRset& rrset,
|
||||
|
||||
void
|
||||
ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
|
||||
if (!rrset) {
|
||||
isc_throw(NullRRset, "The rrset provided is NULL");
|
||||
}
|
||||
assert(rrset);
|
||||
|
||||
if (rrset->getRdataCount() == 0) {
|
||||
isc_throw(AddError,
|
||||
@@ -241,31 +247,46 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
|
||||
}
|
||||
|
||||
void
|
||||
ZoneDataUpdater::addNSEC3(const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig)
|
||||
ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
|
||||
const ConstRRsetPtr rrsig)
|
||||
{
|
||||
setupNSEC3<generic::NSEC3>(rrset);
|
||||
if (rrset) {
|
||||
setupNSEC3<generic::NSEC3>(rrset);
|
||||
}
|
||||
|
||||
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
|
||||
if (nsec3_data == NULL) {
|
||||
// This is some tricky case: an RRSIG for NSEC3 is given without the
|
||||
// covered NSEC3, and we don't even know any NSEC3 related data.
|
||||
// This situation is not necessarily broken, but in our current
|
||||
// implementation it's very difficult to deal with. So we reject it;
|
||||
// hopefully this case shouldn't happen in practice, at least unless
|
||||
// zone is really broken.
|
||||
assert(!rrset);
|
||||
isc_throw(NotImplemented,
|
||||
"RRSIG for NSEC3 cannot be added - no known NSEC3 data");
|
||||
}
|
||||
|
||||
ZoneNode* node;
|
||||
nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node);
|
||||
nsec3_data->insertName(mem_sgmt_, name, &node);
|
||||
|
||||
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
|
||||
RdataSet* old_rdataset = node->setData(rdataset);
|
||||
if (old_rdataset != NULL) {
|
||||
RdataSet::destroy(mem_sgmt_, rrclass_, old_rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
|
||||
ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
|
||||
const ConstRRsetPtr rrset,
|
||||
const ConstRRsetPtr rrsig)
|
||||
{
|
||||
if (rrset->getType() == RRType::NSEC3()) {
|
||||
addNSEC3(rrset, rrsig);
|
||||
if (rrtype == RRType::NSEC3()) {
|
||||
addNSEC3(name, rrset, rrsig);
|
||||
} else {
|
||||
ZoneNode* node;
|
||||
zone_data_.insertName(mem_sgmt_, rrset->getName(), &node);
|
||||
zone_data_.insertName(mem_sgmt_, name, &node);
|
||||
|
||||
RdataSet* rdataset_head = node->getData();
|
||||
|
||||
@@ -273,13 +294,14 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
|
||||
// fails and the exception is thrown, it may break strong
|
||||
// exception guarantee. At the moment we prefer code simplicity
|
||||
// and don't bother to introduce complicated recovery code.
|
||||
contextCheck(*rrset, rdataset_head);
|
||||
if (rrset) { // this check is only for covered RRset, not RRSIG
|
||||
contextCheck(*rrset, rdataset_head);
|
||||
}
|
||||
|
||||
if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) {
|
||||
if (RdataSet::find(rdataset_head, rrtype, true) != NULL) {
|
||||
isc_throw(AddError,
|
||||
"RRset of the type already exists: "
|
||||
<< rrset->getName() << " (type: "
|
||||
<< rrset->getType() << ")");
|
||||
<< name << " (type: " << rrtype << ")");
|
||||
}
|
||||
|
||||
RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_,
|
||||
@@ -289,23 +311,25 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
|
||||
|
||||
// Ok, we just put it in.
|
||||
|
||||
// Convenient (and more efficient) shortcut to check RRsets at origin
|
||||
const bool is_origin = (node == zone_data_.getOriginNode());
|
||||
|
||||
// If this RRset creates a zone cut at this node, mark the node
|
||||
// indicating the need for callback in find().
|
||||
if (rrset->getType() == RRType::NS() &&
|
||||
rrset->getName() != zone_name_) {
|
||||
// indicating the need for callback in find(). Note that we do this
|
||||
// only when non RRSIG RRset of that type is added.
|
||||
if (rrset && rrtype == RRType::NS() && !is_origin) {
|
||||
node->setFlag(ZoneNode::FLAG_CALLBACK);
|
||||
// If it is DNAME, we have a callback as well here
|
||||
} else if (rrset->getType() == RRType::DNAME()) {
|
||||
} else if (rrset && rrtype == RRType::DNAME()) {
|
||||
node->setFlag(ZoneNode::FLAG_CALLBACK);
|
||||
}
|
||||
|
||||
// If we've added NSEC3PARAM at zone origin, set up NSEC3
|
||||
// specific data or check consistency with already set up
|
||||
// parameters.
|
||||
if (rrset->getType() == RRType::NSEC3PARAM() &&
|
||||
rrset->getName() == zone_name_) {
|
||||
if (rrset && rrtype == RRType::NSEC3PARAM() && is_origin) {
|
||||
setupNSEC3<generic::NSEC3PARAM>(rrset);
|
||||
} else if (rrset->getType() == RRType::NSEC()) {
|
||||
} else if (rrset && rrtype == RRType::NSEC() && is_origin) {
|
||||
// If it is NSEC signed zone, we mark the zone as signed
|
||||
// (conceptually "signed" is a broader notion but our
|
||||
// current zone finder implementation regards "signed" as
|
||||
@@ -319,27 +343,37 @@ void
|
||||
ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
|
||||
const ConstRRsetPtr& sig_rrset)
|
||||
{
|
||||
// Validate input. This will cause an exception to be thrown if the
|
||||
// input RRset is empty.
|
||||
validate(rrset);
|
||||
// Validate input.
|
||||
if (!rrset && !sig_rrset) {
|
||||
isc_throw(NullRRset,
|
||||
"ZoneDataUpdater::add is given 2 NULL pointers");
|
||||
}
|
||||
if (rrset) {
|
||||
validate(rrset);
|
||||
}
|
||||
if (sig_rrset) {
|
||||
validate(sig_rrset);
|
||||
}
|
||||
|
||||
const Name& name = rrset ? rrset->getName() : sig_rrset->getName();
|
||||
const RRType& rrtype = rrset ? rrset->getType() :
|
||||
getCoveredType(sig_rrset);
|
||||
|
||||
// OK, can add the RRset.
|
||||
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
|
||||
arg(rrset->getName()).arg(rrset->getType()).arg(zone_name_);
|
||||
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).arg(name).
|
||||
arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
|
||||
arg(zone_name_);
|
||||
|
||||
// Add wildcards possibly contained in the owner name to the domain
|
||||
// tree. This can only happen for the normal (non-NSEC3) tree.
|
||||
// Note: this can throw an exception, breaking strong exception
|
||||
// guarantee. (see also the note for the call to contextCheck()
|
||||
// above).
|
||||
if (rrset->getType() != RRType::NSEC3()) {
|
||||
addWildcards(rrset->getName());
|
||||
if (rrtype != RRType::NSEC3()) {
|
||||
addWildcards(name);
|
||||
}
|
||||
|
||||
addRdataSet(rrset, sig_rrset);
|
||||
addRdataSet(name, rrtype, rrset, sig_rrset);
|
||||
}
|
||||
|
||||
} // namespace memory
|
||||
|
@@ -110,10 +110,32 @@ public:
|
||||
/// populated with the record data and added to the ZoneData for the
|
||||
/// name in the RRset.
|
||||
///
|
||||
/// This method throws an \c NullRRset exception (see above) if
|
||||
/// \c rrset is empty. It throws \c AddError if any of a variety of
|
||||
/// validation checks fail for the \c rrset and its associated
|
||||
/// \c sig_rrset.
|
||||
/// At least one of \c rrset or \c sig_rrset must be non NULL.
|
||||
/// \c sig_rrset can be reasonably NULL when \c rrset is not signed in
|
||||
/// the zone; it's unusual that \c rrset is NULL, but is still possible
|
||||
/// if these RRsets are given separately to the loader, or if even the
|
||||
/// zone is half broken and really contains an RRSIG that doesn't have
|
||||
/// any covered RRset. This implementation supports these cases (but
|
||||
/// see the note below).
|
||||
///
|
||||
/// There is one tricky case: Due to a limitation of the current
|
||||
/// implementation, it cannot accept an RRSIG for NSEC3 without the covered
|
||||
/// NSEC3, unless at least one NSEC3 or NSEC3PARAM has been added.
|
||||
/// In this case an isc::NotImplemented exception will be thrown. It
|
||||
/// should be very rare in practice, and hopefully wouldn't be a real
|
||||
/// issue.
|
||||
///
|
||||
/// \note Due to limitations of the current implementation, if a
|
||||
/// (non RRSIG) RRset and its RRSIG are added separately in different
|
||||
/// calls to this method, the second attempt will be rejected due to
|
||||
/// an \c AddError exception. This will be loosened in Trac
|
||||
/// ticket #2441.
|
||||
///
|
||||
/// \throw NullRRset Both \c rrset and sig_rrset is NULL
|
||||
/// \throw AddError any of a variety of validation checks fail for the
|
||||
/// \c rrset and its associated \c sig_rrset.
|
||||
/// \throw NotImplemented RRSIG for NSEC3 cannot be added due to internal
|
||||
/// restriction.
|
||||
///
|
||||
/// \param rrset The RRset to be added.
|
||||
/// \param sig_rrset An associated RRSIG RRset for the \c rrset. It
|
||||
@@ -152,9 +174,12 @@ private:
|
||||
const isc::dns::NSEC3Hash* getNSEC3Hash();
|
||||
template <typename T>
|
||||
void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
|
||||
void addNSEC3(const isc::dns::ConstRRsetPtr rrset,
|
||||
void addNSEC3(const isc::dns::Name& name,
|
||||
const isc::dns::ConstRRsetPtr rrset,
|
||||
const isc::dns::ConstRRsetPtr rrsig);
|
||||
void addRdataSet(const isc::dns::ConstRRsetPtr rrset,
|
||||
void addRdataSet(const isc::dns::Name& name,
|
||||
const isc::dns::RRType& rrtype,
|
||||
const isc::dns::ConstRRsetPtr rrset,
|
||||
const isc::dns::ConstRRsetPtr rrsig);
|
||||
|
||||
util::MemorySegment& mem_sgmt_;
|
||||
|
@@ -216,6 +216,14 @@ createNSEC3RRset(const ZoneNode* node, const RRClass& rrclass) {
|
||||
assert(rdataset != NULL);
|
||||
assert(rdataset->type == RRType::NSEC3());
|
||||
|
||||
// Check for the rare case of RRSIG-only record; in theory it could exist
|
||||
// but we simply consider it broken for NSEC3.
|
||||
if (rdataset->getRdataCount() == 0) {
|
||||
uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
|
||||
isc_throw(DataSourceError, "Broken zone: RRSIG-only NSEC3 record at "
|
||||
<< node->getAbsoluteLabels(labels_buf) << "/" << rrclass);
|
||||
}
|
||||
|
||||
// Create the RRset. Note the DNSSEC flag: NSEC3 implies DNSSEC.
|
||||
return (createTreeNodeRRset(node, rdataset, rrclass,
|
||||
ZoneFinder::FIND_DNSSEC));
|
||||
@@ -627,7 +635,10 @@ private:
|
||||
// This can be a bit more optimized, but unless we have many
|
||||
// requested types the effect is probably marginal. For now we
|
||||
// keep it simple.
|
||||
if (std::find(type_beg, type_end, rdset->type) != type_end) {
|
||||
// Check for getRdataCount is necessary not to include RRSIG-only
|
||||
// records accidentally (should be rare, but possible).
|
||||
if (std::find(type_beg, type_end, rdset->type) != type_end &&
|
||||
rdset->getRdataCount() > 0) {
|
||||
result->push_back(createTreeNodeRRset(node, rdset, rrclass_,
|
||||
options, real_name));
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ run_unittests_SOURCES += zonetable_unittest.cc
|
||||
run_unittests_SOURCES += zone_finder_context_unittest.cc
|
||||
run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
|
||||
run_unittests_SOURCES += client_list_unittest.cc
|
||||
run_unittests_SOURCES += master_loader_callbacks_test.cc
|
||||
|
||||
# We need the actual module implementation in the tests (they are not part
|
||||
# of libdatasrc)
|
||||
@@ -93,6 +94,7 @@ endif
|
||||
|
||||
EXTRA_DIST = testdata/brokendb.sqlite3
|
||||
EXTRA_DIST += testdata/contexttest.zone
|
||||
EXTRA_DIST += testdata/contexttest-almost-obsolete.zone
|
||||
EXTRA_DIST += testdata/diffs.sqlite3
|
||||
EXTRA_DIST += testdata/duplicate_rrset.zone
|
||||
EXTRA_DIST += testdata/example2.com
|
||||
|
124
src/lib/datasrc/tests/master_loader_callbacks_test.cc
Normal file
124
src/lib/datasrc/tests/master_loader_callbacks_test.cc
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <datasrc/master_loader_callbacks.h>
|
||||
#include <datasrc/zone.h>
|
||||
|
||||
#include <dns/rrset.h>
|
||||
#include <dns/rrclass.h>
|
||||
#include <dns/rrttl.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
using namespace isc::datasrc;
|
||||
|
||||
namespace {
|
||||
|
||||
// An updater for the tests. Most of the virtual methods throw
|
||||
// NotImplemented, as they are not used in the tests.
|
||||
class MockUpdater : public ZoneUpdater {
|
||||
public:
|
||||
// We do the adding in this test. We currently only check these are
|
||||
// the correct ones, according to a predefined set in a list.
|
||||
virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
|
||||
ASSERT_FALSE(expected_rrsets_.empty());
|
||||
// In our tests, pointer equality is enough.
|
||||
EXPECT_EQ(expected_rrsets_.front().get(), &rrset);
|
||||
// And remove this RRset, as it has been used.
|
||||
expected_rrsets_.pop_front();
|
||||
}
|
||||
// The unused but required methods
|
||||
virtual ZoneFinder& getFinder() {
|
||||
isc_throw(isc::NotImplemented, "Not to be called in this test");
|
||||
}
|
||||
virtual void deleteRRset(const isc::dns::AbstractRRset&) {
|
||||
isc_throw(isc::NotImplemented, "Not to be called in this test");
|
||||
}
|
||||
virtual void commit() {
|
||||
isc_throw(isc::NotImplemented, "Not to be called in this test");
|
||||
}
|
||||
// The RRsets that are expected to appear through addRRset.
|
||||
std::list<isc::dns::RRsetPtr> expected_rrsets_;
|
||||
};
|
||||
|
||||
class MasterLoaderCallbackTest : public ::testing::Test {
|
||||
protected:
|
||||
MasterLoaderCallbackTest() :
|
||||
ok_(true),
|
||||
callbacks_(createMasterLoaderCallbacks(isc::dns::Name("example.org"),
|
||||
isc::dns::RRClass::IN(), &ok_))
|
||||
{}
|
||||
// Generate a new RRset, put it to the updater and return it.
|
||||
isc::dns::RRsetPtr generateRRset() {
|
||||
const isc::dns::RRsetPtr
|
||||
result(new isc::dns::RRset(isc::dns::Name("example.org"),
|
||||
isc::dns::RRClass::IN(),
|
||||
isc::dns::RRType::A(),
|
||||
isc::dns::RRTTL(3600)));
|
||||
updater_.expected_rrsets_.push_back(result);
|
||||
return (result);
|
||||
}
|
||||
// An updater to be passed to the context
|
||||
MockUpdater updater_;
|
||||
// Is the loading OK?
|
||||
bool ok_;
|
||||
// The tested context
|
||||
isc::dns::MasterLoaderCallbacks callbacks_;
|
||||
};
|
||||
|
||||
// Check it doesn't crash if we don't provide the OK
|
||||
TEST_F(MasterLoaderCallbackTest, noOkProvided) {
|
||||
createMasterLoaderCallbacks(isc::dns::Name("example.org"),
|
||||
isc::dns::RRClass::IN(), NULL).
|
||||
error("No source", 1, "No reason");
|
||||
}
|
||||
|
||||
// Check the callbacks can be called, don't crash and the error one switches
|
||||
// to non-OK mode. This, however, does not stop anybody from calling more
|
||||
// callbacks.
|
||||
TEST_F(MasterLoaderCallbackTest, callbacks) {
|
||||
EXPECT_NO_THROW(callbacks_.warning("No source", 1, "Just for fun"));
|
||||
// The warning does not hurt the OK mode.
|
||||
EXPECT_TRUE(ok_);
|
||||
// Now the error
|
||||
EXPECT_NO_THROW(callbacks_.error("No source", 2, "Some error"));
|
||||
// The OK is turned off once there's at least one error
|
||||
EXPECT_FALSE(ok_);
|
||||
|
||||
// Not being OK does not hurt that much, we can still call the callbacks
|
||||
EXPECT_NO_THROW(callbacks_.warning("No source", 3, "Just for fun"));
|
||||
// The OK is not reset back to true
|
||||
EXPECT_FALSE(ok_);
|
||||
EXPECT_NO_THROW(callbacks_.error("No source", 4, "Some error"));
|
||||
}
|
||||
|
||||
// Try adding some RRsets.
|
||||
TEST_F(MasterLoaderCallbackTest, addRRset) {
|
||||
isc::dns::AddRRsetCallback
|
||||
callback(createMasterLoaderAddCallback(updater_));
|
||||
// Put some of them in.
|
||||
EXPECT_NO_THROW(callback(generateRRset()));
|
||||
EXPECT_NO_THROW(callback(generateRRset()));
|
||||
// They all get pushed there right away, so there are none in the queue
|
||||
EXPECT_TRUE(updater_.expected_rrsets_.empty());
|
||||
}
|
||||
|
||||
}
|
@@ -32,6 +32,8 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
|
||||
run_unittests_SOURCES += memory_segment_test.h
|
||||
run_unittests_SOURCES += segment_object_holder_unittest.cc
|
||||
run_unittests_SOURCES += memory_client_unittest.cc
|
||||
run_unittests_SOURCES += zone_data_loader_unittest.cc
|
||||
run_unittests_SOURCES += zone_data_updater_unittest.cc
|
||||
run_unittests_SOURCES += zone_table_segment_test.h
|
||||
run_unittests_SOURCES += zone_table_segment_unittest.cc
|
||||
run_unittests_SOURCES += zone_writer_unittest.cc
|
||||
|
@@ -576,16 +576,6 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
|
||||
// Teardown checks for memory segment leaks
|
||||
}
|
||||
|
||||
TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
|
||||
// This causes the situation where an RRSIG is added without a covered
|
||||
// RRset. Such cases are currently rejected.
|
||||
EXPECT_THROW(client_->load(Name("example.org"),
|
||||
TEST_DATA_DIR
|
||||
"/example.org-rrsig-follows-nothing.zone"),
|
||||
ZoneDataUpdater::AddError);
|
||||
// Teardown checks for memory segment leaks
|
||||
}
|
||||
|
||||
TEST_F(MemoryClientTest, loadRRSIGs) {
|
||||
client_->load(Name("example.org"),
|
||||
TEST_DATA_DIR "/example.org-rrsigs.zone");
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include <dns/rrtype.h>
|
||||
#include <dns/rrttl.h>
|
||||
|
||||
#include <datasrc/memory/segment_object_holder.h>
|
||||
#include <datasrc/memory/rdata_serialization.h>
|
||||
#include <datasrc/memory/rdataset.h>
|
||||
|
||||
@@ -39,6 +40,7 @@ using namespace isc::dns;
|
||||
using namespace isc::dns::rdata;
|
||||
using namespace isc::datasrc::memory;
|
||||
using namespace isc::testutils;
|
||||
using isc::datasrc::memory::detail::SegmentObjectHolder;
|
||||
using boost::lexical_cast;
|
||||
|
||||
namespace {
|
||||
@@ -112,7 +114,7 @@ TEST_F(RdataSetTest, create) {
|
||||
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
|
||||
ConstRRsetPtr());
|
||||
checkRdataSet(*rdataset, true, false);
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
}
|
||||
|
||||
TEST_F(RdataSetTest, getNext) {
|
||||
@@ -131,7 +133,62 @@ TEST_F(RdataSetTest, getNext) {
|
||||
rdataset->next = rdataset;
|
||||
EXPECT_EQ(rdataset, static_cast<const RdataSet*>(rdataset)->getNext());
|
||||
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
}
|
||||
|
||||
TEST_F(RdataSetTest, find) {
|
||||
// Create some RdataSets and make a chain of them.
|
||||
SegmentObjectHolder<RdataSet, RRClass> holder1(
|
||||
mem_sgmt_,
|
||||
RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
|
||||
RRClass::IN());
|
||||
ConstRRsetPtr aaaa_rrset =
|
||||
textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
|
||||
SegmentObjectHolder<RdataSet, RRClass> holder2(
|
||||
mem_sgmt_,
|
||||
RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
|
||||
RRClass::IN());
|
||||
ConstRRsetPtr sigonly_rrset =
|
||||
textToRRset("www.example.com. 1076895760 IN RRSIG "
|
||||
"TXT 5 2 3600 20120814220826 20120715220826 "
|
||||
"1234 example.com. FAKE");
|
||||
SegmentObjectHolder<RdataSet, RRClass> holder3(
|
||||
mem_sgmt_,
|
||||
RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
|
||||
RRClass::IN());
|
||||
|
||||
RdataSet* rdataset_a = holder1.get();
|
||||
RdataSet* rdataset_aaaa = holder2.get();
|
||||
RdataSet* rdataset_sigonly = holder3.get();
|
||||
RdataSet* rdataset_null = NULL;
|
||||
rdataset_a->next = rdataset_aaaa;
|
||||
rdataset_aaaa->next = rdataset_sigonly;
|
||||
|
||||
// If a non-RRSIG part of rdataset exists for the given type, it will be
|
||||
// returned regardless of the value of sigonly_ok. If it's RRSIG-only
|
||||
// rdataset, it returns non NULL iff sigonly_ok is explicitly set to true.
|
||||
EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA()));
|
||||
EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), true));
|
||||
EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), false));
|
||||
|
||||
EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT()));
|
||||
EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a, RRType::TXT(),
|
||||
true));
|
||||
EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT(), false));
|
||||
|
||||
// Same tests for the const version of find().
|
||||
const RdataSet* rdataset_a_const = holder1.get();
|
||||
EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA()));
|
||||
EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
|
||||
true));
|
||||
EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
|
||||
false));
|
||||
|
||||
EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT()));
|
||||
EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a_const, RRType::TXT(),
|
||||
true));
|
||||
EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT(),
|
||||
false));
|
||||
}
|
||||
|
||||
// A helper function to create an RRset containing the given number of
|
||||
@@ -154,7 +211,7 @@ TEST_F(RdataSetTest, createManyRRs) {
|
||||
ConstRRsetPtr());
|
||||
EXPECT_EQ(8191, rdataset->getRdataCount());
|
||||
EXPECT_EQ(0, rdataset->getSigRdataCount());
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
|
||||
// Exceeding that will result in an exception.
|
||||
EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_,
|
||||
@@ -173,7 +230,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
|
||||
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
|
||||
rrsig_rrset_);
|
||||
checkRdataSet(*rdataset, true, true);
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
|
||||
// Unusual case: TTL doesn't match. This implementation accepts that,
|
||||
// using the TTL of the covered RRset.
|
||||
@@ -183,7 +240,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
|
||||
"20120715220826 1234 example.com. FAKE"));
|
||||
rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_badttl);
|
||||
checkRdataSet(*rdataset, true, true);
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
}
|
||||
|
||||
// A helper function to create an RRSIG RRset containing the given number of
|
||||
@@ -218,21 +275,21 @@ TEST_F(RdataSetTest, createManyRRSIGs) {
|
||||
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
|
||||
getRRSIGWithRdataCount(7));
|
||||
EXPECT_EQ(7, rdataset->getSigRdataCount());
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
|
||||
// 8 would cause overflow in the normal 3-bit field if there were no extra
|
||||
// count field.
|
||||
rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
|
||||
getRRSIGWithRdataCount(8));
|
||||
EXPECT_EQ(8, rdataset->getSigRdataCount());
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
|
||||
// Up to 2^16-1 RRSIGs are allowed (although that would be useless
|
||||
// in practice)
|
||||
rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
|
||||
getRRSIGWithRdataCount(65535));
|
||||
EXPECT_EQ(65535, rdataset->getSigRdataCount());
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
|
||||
// Exceeding this limit will result in an exception.
|
||||
EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
|
||||
@@ -250,7 +307,7 @@ TEST_F(RdataSetTest, createWithRRSIGOnly) {
|
||||
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
|
||||
rrsig_rrset_);
|
||||
checkRdataSet(*rdataset, false, true);
|
||||
RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
|
||||
RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
|
||||
}
|
||||
|
||||
TEST_F(RdataSetTest, badCeate) {
|
||||
|
65
src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
Normal file
65
src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <dns/name.h>
|
||||
#include <dns/rrclass.h>
|
||||
|
||||
#include <datasrc/memory/rdataset.h>
|
||||
#include <datasrc/memory/zone_data.h>
|
||||
#include <datasrc/memory/zone_data_updater.h>
|
||||
#include <datasrc/memory/zone_data_loader.h>
|
||||
|
||||
#include "memory_segment_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace isc::dns;
|
||||
using namespace isc::datasrc::memory;
|
||||
|
||||
namespace {
|
||||
|
||||
class ZoneDataLoaderTest : public ::testing::Test {
|
||||
protected:
|
||||
ZoneDataLoaderTest() : zclass_(RRClass::IN()), zone_data_(NULL) {}
|
||||
void TearDown() {
|
||||
if (zone_data_ != NULL) {
|
||||
ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
|
||||
}
|
||||
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
|
||||
}
|
||||
const RRClass zclass_;
|
||||
test::MemorySegmentTest mem_sgmt_;
|
||||
ZoneData* zone_data_;
|
||||
};
|
||||
|
||||
TEST_F(ZoneDataLoaderTest, loadRRSIGFollowsNothing) {
|
||||
// This causes the situation where an RRSIG is added without a covered
|
||||
// RRset. It will be accepted, and corresponding "sig-only" rdata will
|
||||
// be created.
|
||||
zone_data_ = loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
|
||||
TEST_DATA_DIR
|
||||
"/example.org-rrsig-follows-nothing.zone");
|
||||
ZoneNode* node = NULL;
|
||||
zone_data_->insertName(mem_sgmt_, Name("ns1.example.org"), &node);
|
||||
ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
|
||||
const RdataSet* rdset = node->getData();
|
||||
ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
|
||||
EXPECT_EQ(RRType::A(), rdset->type); // there should be only 1 data here
|
||||
EXPECT_EQ(0, rdset->getRdataCount()); // no RDATA
|
||||
EXPECT_EQ(1, rdset->getSigRdataCount()); // but 1 SIG
|
||||
|
||||
// Teardown checks for memory segment leaks
|
||||
}
|
||||
|
||||
}
|
208
src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
Normal file
208
src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <testutils/dnsmessage_test.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <dns/name.h>
|
||||
#include <dns/rrclass.h>
|
||||
#include <dns/rrset.h>
|
||||
|
||||
#include <datasrc/memory/rdataset.h>
|
||||
#include <datasrc/memory/zone_data.h>
|
||||
#include <datasrc/memory/zone_data_updater.h>
|
||||
|
||||
#include "memory_segment_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using isc::testutils::textToRRset;
|
||||
using namespace isc::dns;
|
||||
using namespace isc::datasrc::memory;
|
||||
|
||||
namespace {
|
||||
|
||||
class ZoneDataUpdaterTest : public ::testing::Test {
|
||||
protected:
|
||||
ZoneDataUpdaterTest() :
|
||||
zname_("example.org"), zclass_(RRClass::IN()),
|
||||
zone_data_(ZoneData::create(mem_sgmt_, zname_)),
|
||||
updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
|
||||
{}
|
||||
|
||||
~ZoneDataUpdaterTest() {
|
||||
if (zone_data_ != NULL) {
|
||||
ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
|
||||
}
|
||||
if (!mem_sgmt_.allMemoryDeallocated()) {
|
||||
ADD_FAILURE() << "Memory leak detected";
|
||||
}
|
||||
}
|
||||
|
||||
void clearZoneData() {
|
||||
assert(zone_data_ != NULL);
|
||||
ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
|
||||
zone_data_ = ZoneData::create(mem_sgmt_, zname_);
|
||||
updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
|
||||
*zone_data_));
|
||||
}
|
||||
|
||||
const Name zname_;
|
||||
const RRClass zclass_;
|
||||
test::MemorySegmentTest mem_sgmt_;
|
||||
ZoneData* zone_data_;
|
||||
boost::scoped_ptr<ZoneDataUpdater> updater_;
|
||||
};
|
||||
|
||||
TEST_F(ZoneDataUpdaterTest, bothNull) {
|
||||
// At least either covered RRset or RRSIG must be non NULL.
|
||||
EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
|
||||
ZoneDataUpdater::NullRRset);
|
||||
}
|
||||
|
||||
ZoneNode*
|
||||
getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
|
||||
ZoneData* zone_data)
|
||||
{
|
||||
ZoneNode* node = NULL;
|
||||
zone_data->insertName(mem_sgmt, name, &node);
|
||||
EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
|
||||
return (node);
|
||||
}
|
||||
|
||||
TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
|
||||
// RRSIG that doesn't have covered RRset can be added. The resulting
|
||||
// rdataset won't have "normal" RDATA but sig RDATA.
|
||||
updater_->add(ConstRRsetPtr(), textToRRset(
|
||||
"www.example.org. 3600 IN RRSIG A 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
|
||||
const RdataSet* rdset = node->getData();
|
||||
ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
|
||||
rdset = RdataSet::find(rdset, RRType::A(), true);
|
||||
ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
|
||||
EXPECT_EQ(0, rdset->getRdataCount());
|
||||
EXPECT_EQ(1, rdset->getSigRdataCount());
|
||||
|
||||
// The RRSIG covering A prohibits an actual A RRset from being added.
|
||||
// This should be loosened in future version, but we check the current
|
||||
// behavior.
|
||||
EXPECT_THROW(updater_->add(
|
||||
textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
|
||||
ConstRRsetPtr()), ZoneDataUpdater::AddError);
|
||||
|
||||
// The special "wildcarding" node mark should be added for the RRSIG-only
|
||||
// case, too.
|
||||
updater_->add(ConstRRsetPtr(), textToRRset(
|
||||
"*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
|
||||
EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
|
||||
|
||||
// Simply adding RRSIG covering (delegating NS) shouldn't enable callback
|
||||
// in search.
|
||||
updater_->add(ConstRRsetPtr(), textToRRset(
|
||||
"child.example.org. 3600 IN RRSIG NS 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
|
||||
EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
|
||||
|
||||
// Same for DNAME
|
||||
updater_->add(ConstRRsetPtr(), textToRRset(
|
||||
"dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
|
||||
EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
|
||||
|
||||
// Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
|
||||
// "NSEC3-signed".
|
||||
updater_->add(ConstRRsetPtr(), textToRRset(
|
||||
"example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
EXPECT_FALSE(zone_data_->isNSEC3Signed());
|
||||
|
||||
// And same for (RRSIG for) NSEC and "is signed".
|
||||
updater_->add(ConstRRsetPtr(), textToRRset(
|
||||
"example.org. 3600 IN RRSIG NSEC 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
EXPECT_FALSE(zone_data_->isSigned());
|
||||
}
|
||||
|
||||
// Commonly used checks for rrsigForNSEC3Only
|
||||
void
|
||||
checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
|
||||
ZoneData* zone_data)
|
||||
{
|
||||
ZoneNode* node = NULL;
|
||||
zone_data->getNSEC3Data()->insertName(mem_sgmt, name, &node);
|
||||
ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
|
||||
const RdataSet* rdset = node->getData();
|
||||
ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
|
||||
ASSERT_EQ(RRType::NSEC3(), rdset->type);
|
||||
EXPECT_EQ(0, rdset->getRdataCount());
|
||||
EXPECT_EQ(1, rdset->getSigRdataCount());
|
||||
}
|
||||
|
||||
TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
|
||||
// Adding only RRSIG covering NSEC3 is tricky. It should go to the
|
||||
// separate NSEC3 tree, but the separate space is only created when
|
||||
// NSEC3 or NSEC3PARAM is added. So, in many cases RRSIG-only is allowed,
|
||||
// but if no NSEC3 or NSEC3PARAM has been added it will be rejected.
|
||||
|
||||
// Below we use abnormal owner names and RDATA for NSEC3s for brevity,
|
||||
// but that doesn't matter for this test.
|
||||
|
||||
// Add NSEC3PARAM, then RRSIG-only, which is okay.
|
||||
updater_->add(textToRRset(
|
||||
"example.org. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD"),
|
||||
textToRRset(
|
||||
"example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
EXPECT_TRUE(zone_data_->isNSEC3Signed());
|
||||
updater_->add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
|
||||
|
||||
// Clear the current content of zone, then add NSEC3
|
||||
clearZoneData();
|
||||
updater_->add(textToRRset(
|
||||
"AABB.example.org. 3600 IN NSEC3 1 0 10 AA 00000000 A"),
|
||||
textToRRset(
|
||||
"AABB.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
updater_->add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE"));
|
||||
checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
|
||||
|
||||
// If we add only RRSIG without any NSEC3 related data beforehand,
|
||||
// it will be rejected; it's a limitation of the current implementation.
|
||||
clearZoneData();
|
||||
EXPECT_THROW(updater_->add(
|
||||
ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
|
||||
"20150420235959 20051021000000 1 example.org. FAKE")),
|
||||
isc::NotImplemented);
|
||||
}
|
||||
|
||||
}
|
@@ -1358,6 +1358,74 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
|
||||
DataSourceError);
|
||||
}
|
||||
|
||||
TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
|
||||
// Make the zone "NSEC signed"
|
||||
addToZoneData(rr_nsec_);
|
||||
const ZoneFinder::FindResultFlags expected_flags =
|
||||
ZoneFinder::RESULT_NSEC_SIGNED;
|
||||
|
||||
// Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
|
||||
// query for the TXT should result in NXRRSET.
|
||||
addToZoneData(rr_ns_a_);
|
||||
updater_.add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
|
||||
"20120715220826 1234 example.com. FAKE"));
|
||||
findTest(Name("ns.example.org"), RRType::TXT(),
|
||||
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
|
||||
|
||||
// Add RRSIG-only covering NSEC. This shouldn't be returned when NSEC is
|
||||
// requested, whether it's for NXRRSET or NXDOMAIN
|
||||
updater_.add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
|
||||
"20120814220826 20120715220826 1234 example.com. FAKE"));
|
||||
// The added RRSIG for NSEC could be used for NXRRSET but shouldn't
|
||||
findTest(Name("ns.example.org"), RRType::TXT(),
|
||||
ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
|
||||
expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
|
||||
// The added RRSIG for NSEC could be used for NXDOMAIN but shouldn't
|
||||
findTest(Name("nz.example.org"), RRType::A(),
|
||||
ZoneFinder::NXDOMAIN, true, rr_nsec_,
|
||||
expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
|
||||
|
||||
// RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
|
||||
updater_.add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
|
||||
"20120814220826 20120715220826 1234 example.com. FAKE"));
|
||||
findTest(Name("nocname.example.org"), RRType::A(),
|
||||
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
|
||||
|
||||
// RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
|
||||
// case explicitly.
|
||||
updater_.add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
|
||||
"20120814220826 20120715220826 1234 example.com. FAKE"));
|
||||
findTest(Name("nodelegation.example.org"), RRType::A(),
|
||||
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
|
||||
findTest(Name("www.nodelegation.example.org"), RRType::A(),
|
||||
ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
|
||||
|
||||
// Same for RRSIG-only for DNAME
|
||||
updater_.add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
"nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
|
||||
"20120814220826 20120715220826 1234 example.com. FAKE"));
|
||||
findTest(Name("www.nodname.example.org"), RRType::A(),
|
||||
ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
|
||||
// If we have a delegation NS at this node, it will be a bit trickier,
|
||||
// because the zonecut processing actually takes place at the node.
|
||||
// But the RRSIG-only for DNAME shouldn't confuse the process and the NS
|
||||
// should win.
|
||||
ConstRRsetPtr ns_rrset =
|
||||
textToRRset("nodname.example.org. 300 IN NS ns.nodname.example.org.");
|
||||
addToZoneData(ns_rrset);
|
||||
findTest(Name("www.nodname.example.org"), RRType::A(),
|
||||
ZoneFinder::DELEGATION, true, ns_rrset);
|
||||
}
|
||||
|
||||
/// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class
|
||||
class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest {
|
||||
public:
|
||||
@@ -1481,4 +1549,17 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3Walk) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
|
||||
// add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
|
||||
// should result in an exception.
|
||||
const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
|
||||
updater_.add(ConstRRsetPtr(),
|
||||
textToRRset(
|
||||
n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
|
||||
"20120814220826 20120715220826 1234 example.com. FAKE"));
|
||||
EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
|
||||
DataSourceError);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1219,7 +1219,7 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
|
||||
// purpose of this test, so it should just succeed.
|
||||
db_client = unittest::createSQLite3Client(
|
||||
class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
|
||||
TEST_DATA_DIR "/contexttest.zone");
|
||||
TEST_DATA_DIR "/contexttest-almost-obsolete.zone");
|
||||
zone_finder_.load(*db_client->getIterator(origin_));
|
||||
|
||||
// just checking a couple of RRs in the new version of zone.
|
||||
|
85
src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
vendored
Normal file
85
src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
;; test zone file used for ZoneFinderContext tests.
|
||||
;; RRSIGs are (obviouslly) faked ones for testing.
|
||||
|
||||
example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
|
||||
example.org. 3600 IN NS ns1.example.org.
|
||||
example.org. 3600 IN NS ns2.example.org.
|
||||
example.org. 3600 IN RRSIG NS 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
|
||||
example.org. 3600 IN MX 1 mx1.example.org.
|
||||
example.org. 3600 IN MX 2 mx2.example.org.
|
||||
example.org. 3600 IN MX 3 mx.a.example.org.
|
||||
|
||||
ns1.example.org. 3600 IN A 192.0.2.1
|
||||
ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
|
||||
ns1.example.org. 3600 IN AAAA 2001:db8::1
|
||||
ns1.example.org. 3600 IN RRSIG AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
|
||||
ns2.example.org. 3600 IN A 192.0.2.2
|
||||
ns2.example.org. 3600 IN TXT "text data"
|
||||
|
||||
mx1.example.org. 3600 IN A 192.0.2.10
|
||||
mx2.example.org. 3600 IN AAAA 2001:db8::10
|
||||
|
||||
;; delegation
|
||||
a.example.org. 3600 IN NS ns1.a.example.org.
|
||||
a.example.org. 3600 IN NS ns2.a.example.org.
|
||||
a.example.org. 3600 IN NS ns.example.com.
|
||||
|
||||
ns1.a.example.org. 3600 IN A 192.0.2.5
|
||||
ns2.a.example.org. 3600 IN A 192.0.2.6
|
||||
ns2.a.example.org. 3600 IN AAAA 2001:db8::6
|
||||
mx.a.example.org. 3600 IN A 192.0.2.7
|
||||
|
||||
;; delegation, one of its NS names is at zone cut.
|
||||
b.example.org. 3600 IN NS ns.b.example.org.
|
||||
b.example.org. 3600 IN NS b.example.org.
|
||||
b.example.org. 3600 IN AAAA 2001:db8::8
|
||||
|
||||
ns.b.example.org. 3600 IN A 192.0.2.9
|
||||
|
||||
;; The MX name is at a zone cut. shouldn't be included in the
|
||||
;; additional section.
|
||||
mxatcut.example.org. 3600 IN MX 1 b.example.org.
|
||||
|
||||
;; delegation, one of its NS names is under a DNAME delegation point;
|
||||
;; another is at that point; and yet another is under DNAME below a
|
||||
;; zone cut.
|
||||
c.example.org. 3600 IN NS ns.dname.example.org.
|
||||
c.example.org. 3600 IN NS dname.example.org.
|
||||
c.example.org. 3600 IN NS ns.deepdname.example.org.
|
||||
ns.dname.example.org. 3600 IN A 192.0.2.11
|
||||
dname.example.org. 3600 IN A 192.0.2.12
|
||||
ns.deepdname.example.org. 3600 IN AAAA 2001:db8::9
|
||||
|
||||
;; delegation, one of its NS name is at an empty non terminal.
|
||||
d.example.org. 3600 IN NS ns.empty.example.org.
|
||||
d.example.org. 3600 IN NS ns1.example.org.
|
||||
;; by adding these two we can create an empty RB node for
|
||||
;; ns.empty.example.org in the in-memory zone
|
||||
foo.ns.empty.example.org. 3600 IN A 192.0.2.13
|
||||
bar.ns.empty.example.org. 3600 IN A 192.0.2.14
|
||||
|
||||
;; delegation; the NS name matches a wildcard (and there's no exact
|
||||
;; match). One of the NS names matches an empty wildcard node, for
|
||||
;; which no additional record should be provided (or any other
|
||||
;; disruption should happen).
|
||||
e.example.org. 3600 IN NS ns.wild.example.org.
|
||||
e.example.org. 3600 IN NS ns.emptywild.example.org.
|
||||
e.example.org. 3600 IN NS ns2.example.org.
|
||||
*.wild.example.org. 3600 IN A 192.0.2.15
|
||||
a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2
|
||||
|
||||
;; additional for an answer RRset (MX) as a result of wildcard
|
||||
;; expansion
|
||||
*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
|
||||
|
||||
;; the owner name of additional for an answer RRset (MX) has DNAME
|
||||
dnamemx.example.org. 3600 IN MX 1 dname.example.org.
|
||||
|
||||
;; CNAME
|
||||
alias.example.org. 3600 IN CNAME cname.example.org.
|
||||
|
||||
;; DNAME
|
||||
dname.example.org. 3600 IN DNAME dname.example.com.
|
||||
|
||||
;; DNAME under a NS (strange one)
|
||||
deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.
|
@@ -68,6 +68,13 @@ e.example.org. 3600 IN NS ns2.example.org.
|
||||
*.wild.example.org. 3600 IN A 192.0.2.15
|
||||
a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2
|
||||
|
||||
;; One of the additional records actually only has RRSIG, which should
|
||||
;; be ignored.
|
||||
f.example.org. 3600 IN MX 5 mx1.f.example.org.
|
||||
f.example.org. 3600 IN MX 10 mx2.f.example.org.
|
||||
mx1.f.example.org. 3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
|
||||
mx2.f.example.org. 3600 IN A 192.0.2.16
|
||||
|
||||
;; additional for an answer RRset (MX) as a result of wildcard
|
||||
;; expansion
|
||||
*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
|
||||
|
@@ -418,4 +418,16 @@ TEST_P(ZoneFinderContextTest, getAdditionalForAny) {
|
||||
result_sets_.begin(), result_sets_.end());
|
||||
}
|
||||
|
||||
TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
|
||||
// This has two MX records, but type-A for one of them only has RRSIG.
|
||||
// It shouldn't be contained in the result.
|
||||
ZoneFinderContextPtr ctx = finder_->find(Name("f.example.org"),
|
||||
RRType::MX(),
|
||||
ZoneFinder::FIND_DNSSEC);
|
||||
EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
|
||||
ctx->getAdditional(REQUESTED_BOTH, result_sets_);
|
||||
rrsetsCheck("mx2.f.example.org. 3600 IN A 192.0.2.16\n",
|
||||
result_sets_.begin(), result_sets_.end());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -21,8 +21,9 @@ libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
|
||||
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
|
||||
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
|
||||
libb10_dhcp___la_SOURCES += option.cc option.h
|
||||
libb10_dhcp___la_SOURCES += option_data_types.h
|
||||
libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
|
||||
libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
|
||||
libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
|
||||
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
|
||||
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
|
||||
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
|
||||
|
@@ -1098,9 +1098,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
|
||||
|
||||
pkt->updateTimestamp();
|
||||
|
||||
pkt->setLocalAddr(IOAddress::from_bytes(AF_INET6,
|
||||
pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
|
||||
reinterpret_cast<const uint8_t*>(&to_addr)));
|
||||
pkt->setRemoteAddr(IOAddress::from_bytes(AF_INET6,
|
||||
pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
|
||||
reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
|
||||
pkt->setRemotePort(ntohs(from.sin6_port));
|
||||
pkt->setIndex(ifindex);
|
||||
|
@@ -302,7 +302,7 @@ void Netlink::ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info) {
|
||||
|
||||
memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]),
|
||||
ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN);
|
||||
IOAddress a = IOAddress::from_bytes(ifa->ifa_family, addr);
|
||||
IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr);
|
||||
iface.addAddress(a);
|
||||
|
||||
/// TODO: Read lifetimes of configured IPv6 addresses
|
||||
|
@@ -48,8 +48,12 @@ const OptionDefContainer&
|
||||
LibDHCP::getOptionDefs(Option::Universe u) {
|
||||
switch (u) {
|
||||
case Option::V4:
|
||||
initStdOptionDefs4();
|
||||
return (v4option_defs_);
|
||||
case Option::V6:
|
||||
if (v6option_defs_.size() == 0) {
|
||||
initStdOptionDefs6();
|
||||
}
|
||||
return (v6option_defs_);
|
||||
default:
|
||||
isc_throw(isc::BadValue, "invalid universe " << u << " specified");
|
||||
@@ -96,30 +100,43 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
|
||||
// @todo: consider throwing exception here.
|
||||
return (offset);
|
||||
}
|
||||
|
||||
// Get the list of stdandard option definitions.
|
||||
OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
|
||||
// Get the search index #1. It allows to search for option definitions
|
||||
// using option code.
|
||||
const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
|
||||
// Get all options with the particular option code. Note that option code
|
||||
// is non-unique within this container however at this point we expect
|
||||
// to get one option definition with the particular code. If more are
|
||||
// returned we report an error.
|
||||
const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
|
||||
// Get the number of returned option definitions for the option code.
|
||||
size_t num_defs = distance(range.first, range.second);
|
||||
OptionPtr opt;
|
||||
switch (opt_type) {
|
||||
case D6O_IA_NA:
|
||||
case D6O_IA_PD:
|
||||
opt = OptionPtr(new Option6IA(opt_type,
|
||||
buf.begin() + offset,
|
||||
buf.begin() + offset + opt_len));
|
||||
break;
|
||||
case D6O_IAADDR:
|
||||
opt = OptionPtr(new Option6IAAddr(opt_type,
|
||||
buf.begin() + offset,
|
||||
buf.begin() + offset + opt_len));
|
||||
break;
|
||||
case D6O_ORO:
|
||||
opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
|
||||
buf.begin() + offset,
|
||||
buf.begin() + offset + opt_len));
|
||||
break;
|
||||
default:
|
||||
opt = OptionPtr(new Option(Option::V6,
|
||||
opt_type,
|
||||
if (num_defs > 1) {
|
||||
// Multiple options of the same code are not supported right now!
|
||||
isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
|
||||
" for option type " << opt_type << " returned. Currently it is not"
|
||||
" supported to initialize multiple option definitions"
|
||||
" for the same option code. This will be supported once"
|
||||
" support for option spaces is implemented");
|
||||
} else if (num_defs == 0) {
|
||||
// @todo Don't crash if definition does not exist because only a few
|
||||
// option definitions are initialized right now. In the future
|
||||
// we will initialize definitions for all options and we will
|
||||
// remove this elseif. For now, return generic option.
|
||||
opt = OptionPtr(new Option(Option::V6, opt_type,
|
||||
buf.begin() + offset,
|
||||
buf.begin() + offset + opt_len));
|
||||
break;
|
||||
} else {
|
||||
// The option definition has been found. Use it to create
|
||||
// the option instance from the provided buffer chunk.
|
||||
const OptionDefinitionPtr& def = *(range.first);
|
||||
assert(def);
|
||||
opt = def->optionFactory(Option::V6, opt_type,
|
||||
buf.begin() + offset,
|
||||
buf.begin() + offset + opt_len);
|
||||
}
|
||||
// add option to options
|
||||
options.insert(std::make_pair(opt_type, opt));
|
||||
@@ -228,20 +245,6 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
LibDHCP::initStdOptionDefs(Option::Universe u) {
|
||||
switch (u) {
|
||||
case Option::V4:
|
||||
initStdOptionDefs4();
|
||||
break;
|
||||
case Option::V6:
|
||||
initStdOptionDefs6();
|
||||
break;
|
||||
default:
|
||||
isc_throw(isc::BadValue, "invalid universe " << u << " specified");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LibDHCP::initStdOptionDefs4() {
|
||||
isc_throw(isc::NotImplemented, "initStdOptionDefs4 is not implemented");
|
||||
@@ -254,19 +257,20 @@ LibDHCP::initStdOptionDefs6() {
|
||||
struct OptionParams {
|
||||
std::string name;
|
||||
uint16_t code;
|
||||
OptionDefinition::DataType type;
|
||||
OptionDataType type;
|
||||
bool array;
|
||||
};
|
||||
OptionParams params[] = {
|
||||
{ "CLIENTID", D6O_CLIENTID, OptionDefinition::BINARY_TYPE, false },
|
||||
{ "SERVERID", D6O_SERVERID, OptionDefinition::BINARY_TYPE, false },
|
||||
{ "IA_NA", D6O_IA_NA, OptionDefinition::RECORD_TYPE, false },
|
||||
{ "IAADDR", D6O_IAADDR, OptionDefinition::RECORD_TYPE, false },
|
||||
{ "ORO", D6O_ORO, OptionDefinition::UINT16_TYPE, true },
|
||||
{ "ELAPSED_TIME", D6O_ELAPSED_TIME, OptionDefinition::UINT16_TYPE, false },
|
||||
{ "STATUS_CODE", D6O_STATUS_CODE, OptionDefinition::RECORD_TYPE, false },
|
||||
{ "RAPID_COMMIT", D6O_RAPID_COMMIT, OptionDefinition::EMPTY_TYPE, false },
|
||||
{ "DNS_SERVERS", D6O_NAME_SERVERS, OptionDefinition::IPV6_ADDRESS_TYPE, true }
|
||||
{ "CLIENTID", D6O_CLIENTID, OPT_BINARY_TYPE, false },
|
||||
{ "SERVERID", D6O_SERVERID, OPT_BINARY_TYPE, false },
|
||||
{ "IA_NA", D6O_IA_NA, OPT_RECORD_TYPE, false },
|
||||
{ "IAADDR", D6O_IAADDR, OPT_RECORD_TYPE, false },
|
||||
{ "ORO", D6O_ORO, OPT_UINT16_TYPE, true },
|
||||
{ "ELAPSED_TIME", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
|
||||
{ "STATUS_CODE", D6O_STATUS_CODE, OPT_RECORD_TYPE, false },
|
||||
{ "RAPID_COMMIT", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
|
||||
{ "DNS_SERVERS", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
|
||||
{ "IA_PD", D6O_IA_PD, OPT_RECORD_TYPE, false }
|
||||
};
|
||||
const int params_size = sizeof(params) / sizeof(params[0]);
|
||||
|
||||
@@ -277,18 +281,19 @@ LibDHCP::initStdOptionDefs6() {
|
||||
params[i].array));
|
||||
switch(params[i].code) {
|
||||
case D6O_IA_NA:
|
||||
case D6O_IA_PD:
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
definition->addRecordField(OptionDefinition::UINT32_TYPE);
|
||||
definition->addRecordField(OPT_UINT32_TYPE);
|
||||
}
|
||||
break;
|
||||
case D6O_IAADDR:
|
||||
definition->addRecordField(OptionDefinition::IPV6_ADDRESS_TYPE);
|
||||
definition->addRecordField(OptionDefinition::UINT32_TYPE);
|
||||
definition->addRecordField(OptionDefinition::UINT32_TYPE);
|
||||
definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
|
||||
definition->addRecordField(OPT_UINT32_TYPE);
|
||||
definition->addRecordField(OPT_UINT32_TYPE);
|
||||
break;
|
||||
case D6O_STATUS_CODE:
|
||||
definition->addRecordField(OptionDefinition::UINT16_TYPE);
|
||||
definition->addRecordField(OptionDefinition::STRING_TYPE);
|
||||
definition->addRecordField(OPT_UINT16_TYPE);
|
||||
definition->addRecordField(OPT_STRING_TYPE);
|
||||
break;
|
||||
default:
|
||||
// The default case is intentionally left empty
|
||||
@@ -298,9 +303,12 @@ LibDHCP::initStdOptionDefs6() {
|
||||
try {
|
||||
definition->validate();
|
||||
} catch (const Exception& ex) {
|
||||
isc_throw(isc::Unexpected, "internal server error: invalid definition of standard"
|
||||
<< " DHCPv6 option (with code " << params[i].code << "): "
|
||||
<< ex.what());
|
||||
// This is unlikely event that validation fails and may
|
||||
// be only caused by programming error. To guarantee the
|
||||
// data consistency we clear all option definitions that
|
||||
// have been added so far and pass the exception forward.
|
||||
v6option_defs_.clear();
|
||||
throw;
|
||||
}
|
||||
v6option_defs_.push_back(definition);
|
||||
}
|
||||
|
@@ -33,7 +33,14 @@ public:
|
||||
|
||||
/// @brief Return collection of option definitions.
|
||||
///
|
||||
/// Method returns the collection of DHCP standard DHCP
|
||||
/// option definitions.
|
||||
/// @todo DHCPv4 option definitions are not implemented. For now
|
||||
/// this function will throw isc::NotImplemented in case of attempt
|
||||
/// to get option definitions for V4 universe.
|
||||
///
|
||||
/// @param u universe of the options (V4 or V6).
|
||||
///
|
||||
/// @return collection of option definitions.
|
||||
static const OptionDefContainer& getOptionDefs(Option::Universe u);
|
||||
|
||||
@@ -113,21 +120,6 @@ public:
|
||||
uint16_t type,
|
||||
Option::Factory * factory);
|
||||
|
||||
/// Initialize standard DHCP options (V4 or V6).
|
||||
///
|
||||
/// The method creates option definitions for all options
|
||||
/// (DHCPv4 or DHCPv6 depending on universe specified).
|
||||
/// Currently DHCPv4 option definitions initialization is not
|
||||
/// implemented thus this function will throw isc::NotImplemented
|
||||
/// if V4 universe is specified.
|
||||
///
|
||||
/// @param u universe
|
||||
/// @throw isc::Unexpected if internal error occured during option
|
||||
/// definitions creation.
|
||||
/// @throw std::bad_alloc if system went out of memory.
|
||||
/// @throw isc::NotImplemented when V4 universe specified.
|
||||
static void initStdOptionDefs(Option::Universe u);
|
||||
|
||||
private:
|
||||
|
||||
/// Initialize standard DHCPv4 option definitions.
|
||||
@@ -144,9 +136,9 @@ private:
|
||||
///
|
||||
/// The method creates option definitions for all DHCPv6 options.
|
||||
///
|
||||
/// @throw isc::Unexpected if internal error occured during option
|
||||
/// definitions creation.
|
||||
/// @throw std::bad_alloc if system went out of memory.
|
||||
/// @throw MalformedOptionDefinition if any of the definitions
|
||||
/// is incorect. This is a programming error.
|
||||
static void initStdOptionDefs6();
|
||||
|
||||
/// pointers to factories that produce DHCPv6 options
|
||||
|
@@ -62,14 +62,14 @@ Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
|
||||
void
|
||||
Option::check() {
|
||||
if ( (universe_ != V4) && (universe_ != V6) ) {
|
||||
isc_throw(BadValue, "Invalid universe type specified."
|
||||
isc_throw(BadValue, "Invalid universe type specified. "
|
||||
<< "Only V4 and V6 are allowed.");
|
||||
}
|
||||
|
||||
if (universe_ == V4) {
|
||||
|
||||
if (type_ > 255) {
|
||||
isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big."
|
||||
isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
|
||||
<< "For DHCPv4 allowed type range is 0..255");
|
||||
} else if (data_.size() > 255) {
|
||||
isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big.");
|
||||
@@ -87,20 +87,21 @@ void Option::pack(isc::util::OutputBuffer& buf) {
|
||||
switch (universe_) {
|
||||
case V6:
|
||||
return (pack6(buf));
|
||||
|
||||
case V4:
|
||||
return (pack4(buf));
|
||||
|
||||
default:
|
||||
isc_throw(BadValue, "Failed to pack " << type_ << " option. Do not "
|
||||
<< "use this method for options other than DHCPv6.");
|
||||
isc_throw(BadValue, "Failed to pack " << type_ << " option as the "
|
||||
<< "universe type is unknown.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Option::pack4(isc::util::OutputBuffer& buf) {
|
||||
switch (universe_) {
|
||||
case V4: {
|
||||
if (universe_ == V4) {
|
||||
if (len() > 255) {
|
||||
isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big."
|
||||
isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
|
||||
<< "At most 255 bytes are supported.");
|
||||
/// TODO Larger options can be stored as separate instances
|
||||
/// of DHCPv4 options. Clients MUST concatenate them.
|
||||
@@ -109,33 +110,46 @@ Option::pack4(isc::util::OutputBuffer& buf) {
|
||||
|
||||
buf.writeUint8(type_);
|
||||
buf.writeUint8(len() - getHeaderLen());
|
||||
if (!data_.empty()) {
|
||||
buf.writeData(&data_[0], data_.size());
|
||||
}
|
||||
|
||||
buf.writeData(&data_[0], data_.size());
|
||||
packOptions(buf);
|
||||
|
||||
LibDHCP::packOptions(buf, options_);
|
||||
return;
|
||||
} else {
|
||||
isc_throw(BadValue, "Invalid universe type " << universe_);
|
||||
}
|
||||
case V6:
|
||||
/// TODO: Do we need a sanity check for option size here?
|
||||
buf.writeUint16(type_);
|
||||
buf.writeUint16(len() - getHeaderLen());
|
||||
|
||||
LibDHCP::packOptions(buf, options_);
|
||||
return;
|
||||
default:
|
||||
isc_throw(OutOfRange, "Invalid universe type" << universe_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void Option::pack6(isc::util::OutputBuffer& buf) {
|
||||
buf.writeUint16(type_);
|
||||
buf.writeUint16(len() - getHeaderLen());
|
||||
if (universe_ == V6) {
|
||||
buf.writeUint16(type_);
|
||||
buf.writeUint16(len() - getHeaderLen());
|
||||
if (!data_.empty()) {
|
||||
buf.writeData(&data_[0], data_.size());
|
||||
}
|
||||
|
||||
if (! data_.empty()) {
|
||||
buf.writeData(&data_[0], data_.size());
|
||||
packOptions(buf);
|
||||
} else {
|
||||
isc_throw(BadValue, "Invalid universe type " << universe_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return LibDHCP::packOptions6(buf, options_);
|
||||
void
|
||||
Option::packOptions(isc::util::OutputBuffer& buf) {
|
||||
switch (universe_) {
|
||||
case V4:
|
||||
LibDHCP::packOptions(buf, options_);
|
||||
return;
|
||||
case V6:
|
||||
LibDHCP::packOptions6(buf, options_);
|
||||
return;
|
||||
default:
|
||||
isc_throw(isc::BadValue, "Invalid universe type " << universe_);
|
||||
}
|
||||
}
|
||||
|
||||
void Option::unpack(OptionBufferConstIter begin,
|
||||
@@ -143,6 +157,20 @@ void Option::unpack(OptionBufferConstIter begin,
|
||||
data_ = OptionBuffer(begin, end);
|
||||
}
|
||||
|
||||
void
|
||||
Option::unpackOptions(const OptionBuffer& buf) {
|
||||
switch (universe_) {
|
||||
case V4:
|
||||
LibDHCP::unpackOptions4(buf, options_);
|
||||
return;
|
||||
case V6:
|
||||
LibDHCP::unpackOptions6(buf, options_);
|
||||
return;
|
||||
default:
|
||||
isc_throw(isc::BadValue, "Invalid universe type " << universe_);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Option::len() {
|
||||
// Returns length of the complete option (data length + DHCPv4/DHCPv6
|
||||
// option header)
|
||||
|
@@ -119,7 +119,7 @@ public:
|
||||
/// This constructor takes vector<uint8_t>& which is used in cases
|
||||
/// when content of the option will be copied and stored within
|
||||
/// option object. V4 Options follow that approach already.
|
||||
/// TODO Migrate V6 options to that approach.
|
||||
/// @todo Migrate V6 options to that approach.
|
||||
///
|
||||
/// @param u specifies universe (V4 or V6)
|
||||
/// @param type option type (0-255 for V4 and 0-65535 for V6)
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
/// This contructor is similar to the previous one, but it does not take
|
||||
/// the whole vector<uint8_t>, but rather subset of it.
|
||||
///
|
||||
/// TODO: This can be templated to use different containers, not just
|
||||
/// @todo This can be templated to use different containers, not just
|
||||
/// vector. Prototype should look like this:
|
||||
/// template<typename InputIterator> Option(Universe u, uint16_t type,
|
||||
/// InputIterator first, InputIterator last);
|
||||
@@ -160,19 +160,24 @@ public:
|
||||
/// byte after stored option (that is useful for writing options one after
|
||||
/// another). Used in DHCPv6 options.
|
||||
///
|
||||
/// TODO: Migrate DHCPv6 code to pack(OutputBuffer& buf) version
|
||||
/// @todo Migrate DHCPv6 code to pack(OutputBuffer& buf) version
|
||||
///
|
||||
/// @param buf pointer to a buffer
|
||||
///
|
||||
/// @throw BadValue Universe of the option is neither V4 nor V6.
|
||||
virtual void pack(isc::util::OutputBuffer& buf);
|
||||
|
||||
/// @brief Writes option in a wire-format to a buffer.
|
||||
///
|
||||
/// Method will throw if option storing fails for some reason.
|
||||
///
|
||||
/// TODO Once old (DHCPv6) implementation is rewritten,
|
||||
/// @todo Once old (DHCPv6) implementation is rewritten,
|
||||
/// unify pack4() and pack6() and rename them to just pack().
|
||||
///
|
||||
/// @param buf output buffer (option will be stored there)
|
||||
///
|
||||
/// @throw OutOfRange Option type is greater than 255.
|
||||
/// @throw BadValue Universe is not V4.
|
||||
virtual void pack4(isc::util::OutputBuffer& buf);
|
||||
|
||||
/// @brief Parses received buffer.
|
||||
@@ -303,8 +308,39 @@ protected:
|
||||
/// defined suboptions. Version for building DHCPv4 options.
|
||||
///
|
||||
/// @param buf output buffer (built options will be stored here)
|
||||
///
|
||||
/// @throw BadValue Universe is not V6.
|
||||
virtual void pack6(isc::util::OutputBuffer& buf);
|
||||
|
||||
/// @brief Store sub options in a buffer.
|
||||
///
|
||||
/// This method stores all sub-options defined for a particular
|
||||
/// option in a on-wire format in output buffer provided.
|
||||
/// This function is called by pack function in this class or
|
||||
/// derived classes that override pack.
|
||||
///
|
||||
/// @param [out] buf output buffer.
|
||||
///
|
||||
/// @todo The set of exceptions thrown by this function depend on
|
||||
/// exceptions thrown by pack methods invoked on objects
|
||||
/// representing sub options. We should consider whether to aggregate
|
||||
/// those into one exception which can be documented here.
|
||||
void packOptions(isc::util::OutputBuffer& buf);
|
||||
|
||||
/// @brief Builds a collection of sub options from the buffer.
|
||||
///
|
||||
/// This method parses the provided buffer and builds a collection
|
||||
/// of objects representing sub options. This function may throw
|
||||
/// different exceptions when option assembly fails.
|
||||
///
|
||||
/// @param buf buffer to be parsed.
|
||||
///
|
||||
/// @todo The set of exceptions thrown by this function depend on
|
||||
/// exceptions thrown by unpack methods invoked on objects
|
||||
/// representing sub options. We should consider whether to aggregate
|
||||
/// those into one exception which can be documented here.
|
||||
void unpackOptions(const OptionBuffer& buf);
|
||||
|
||||
/// @brief A private method used for option correctness.
|
||||
///
|
||||
/// It is used in constructors. In there are any problems detected
|
||||
@@ -324,7 +360,7 @@ protected:
|
||||
/// collection for storing suboptions
|
||||
OptionCollection options_;
|
||||
|
||||
/// TODO: probably 2 different containers have to be used for v4 (unique
|
||||
/// @todo probably 2 different containers have to be used for v4 (unique
|
||||
/// options) and v6 (options with the same type can repeat)
|
||||
};
|
||||
|
||||
|
@@ -84,7 +84,7 @@ void Option6AddrLst::unpack(OptionBufferConstIter begin,
|
||||
<< " is not divisible by 16.");
|
||||
}
|
||||
while (begin != end) {
|
||||
addrs_.push_back(IOAddress::from_bytes(AF_INET6, &(*begin)));
|
||||
addrs_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin)));
|
||||
begin += V6ADDRESS_LEN;
|
||||
}
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ void Option6IA::pack(isc::util::OutputBuffer& buf) {
|
||||
buf.writeUint32(t1_);
|
||||
buf.writeUint32(t2_);
|
||||
|
||||
LibDHCP::packOptions6(buf, options_);
|
||||
packOptions(buf);
|
||||
}
|
||||
|
||||
void Option6IA::unpack(OptionBufferConstIter begin,
|
||||
@@ -62,7 +62,7 @@ void Option6IA::unpack(OptionBufferConstIter begin,
|
||||
t2_ = readUint32( &(*begin) );
|
||||
begin += sizeof(uint32_t);
|
||||
|
||||
LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
|
||||
unpackOptions(OptionBuffer(begin, end));
|
||||
}
|
||||
|
||||
std::string Option6IA::toText(int indent /* = 0*/) {
|
||||
|
@@ -59,7 +59,7 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) {
|
||||
buf.writeUint32(valid_);
|
||||
|
||||
// parse suboption (there shouldn't be any for IAADDR)
|
||||
LibDHCP::packOptions6(buf, options_);
|
||||
packOptions(buf);
|
||||
}
|
||||
|
||||
void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
|
||||
@@ -69,7 +69,7 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
|
||||
}
|
||||
|
||||
// 16 bytes: IPv6 address
|
||||
addr_ = IOAddress::from_bytes(AF_INET6, &(*begin));
|
||||
addr_ = IOAddress::fromBytes(AF_INET6, &(*begin));
|
||||
begin += V6ADDRESS_LEN;
|
||||
|
||||
preferred_ = readUint32( &(*begin) );
|
||||
@@ -77,7 +77,8 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
|
||||
|
||||
valid_ = readUint32( &(*begin) );
|
||||
begin += sizeof(uint32_t);
|
||||
LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
|
||||
|
||||
unpackOptions(OptionBuffer(begin, end));
|
||||
}
|
||||
|
||||
std::string Option6IAAddr::toText(int indent /* =0 */) {
|
||||
|
@@ -48,7 +48,7 @@ public:
|
||||
/// as template parameter is not a supported integer type.
|
||||
Option6Int(uint16_t type, T value)
|
||||
: Option(Option::V6, type), value_(value) {
|
||||
if (!OptionDataTypes<T>::valid) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
Option6Int(uint16_t type, OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end)
|
||||
: Option(Option::V6, type) {
|
||||
if (!OptionDataTypes<T>::valid) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
unpack(begin, end);
|
||||
@@ -89,9 +89,9 @@ public:
|
||||
// Depending on the data type length we use different utility functions
|
||||
// writeUint16 or writeUint32 which write the data in the network byte
|
||||
// order to the provided buffer. The same functions can be safely used
|
||||
// for either unsiged or signed integers so there is not need to create
|
||||
// for either unsigned or signed integers so there is not need to create
|
||||
// special cases for intX_t types.
|
||||
switch (OptionDataTypes<T>::len) {
|
||||
switch (OptionDataTypeTraits<T>::len) {
|
||||
case 1:
|
||||
buf.writeUint8(value_);
|
||||
break;
|
||||
@@ -104,7 +104,7 @@ public:
|
||||
default:
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
LibDHCP::packOptions6(buf, options_);
|
||||
packOptions(buf);
|
||||
}
|
||||
|
||||
/// @brief Parses received buffer
|
||||
@@ -128,9 +128,9 @@ public:
|
||||
// Depending on the data type length we use different utility functions
|
||||
// readUint16 or readUint32 which read the data laid in the network byte
|
||||
// order from the provided buffer. The same functions can be safely used
|
||||
// for either unsiged or signed integers so there is not need to create
|
||||
// for either unsigned or signed integers so there is not need to create
|
||||
// special cases for intX_t types.
|
||||
int data_size_len = OptionDataTypes<T>::len;
|
||||
int data_size_len = OptionDataTypeTraits<T>::len;
|
||||
switch (data_size_len) {
|
||||
case 1:
|
||||
value_ = *begin;
|
||||
@@ -145,11 +145,11 @@ public:
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
// Use local variable to set a new value for this iterator.
|
||||
// When using OptionDataTypes<T>::len directly some versions
|
||||
// When using OptionDataTypeTraits<T>::len directly some versions
|
||||
// of clang complain about unresolved reference to
|
||||
// OptionDataTypes structure during linking.
|
||||
// OptionDataTypeTraits structure during linking.
|
||||
begin += data_size_len;
|
||||
LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
|
||||
unpackOptions(OptionBuffer(begin, end));
|
||||
}
|
||||
|
||||
/// @brief Set option value.
|
||||
|
@@ -58,7 +58,7 @@ public:
|
||||
Option6IntArray(uint16_t type)
|
||||
: Option(Option::V6, type),
|
||||
values_(0) {
|
||||
if (!OptionDataTypes<T>::valid) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
/// as template parameter is not a supported integer type.
|
||||
Option6IntArray(uint16_t type, const OptionBuffer& buf)
|
||||
: Option(Option::V6, type) {
|
||||
if (!OptionDataTypes<T>::valid) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
unpack(buf.begin(), buf.end());
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
Option6IntArray(uint16_t type, OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end)
|
||||
: Option(Option::V6, type) {
|
||||
if (!OptionDataTypes<T>::valid) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
unpack(begin, end);
|
||||
@@ -118,9 +118,9 @@ public:
|
||||
// Depending on the data type length we use different utility functions
|
||||
// writeUint16 or writeUint32 which write the data in the network byte
|
||||
// order to the provided buffer. The same functions can be safely used
|
||||
// for either unsiged or signed integers so there is not need to create
|
||||
// for either unsigned or signed integers so there is not need to create
|
||||
// special cases for intX_t types.
|
||||
switch (OptionDataTypes<T>::len) {
|
||||
switch (OptionDataTypeTraits<T>::len) {
|
||||
case 1:
|
||||
buf.writeUint8(values_[i]);
|
||||
break;
|
||||
@@ -164,9 +164,9 @@ public:
|
||||
// Depending on the data type length we use different utility functions
|
||||
// readUint16 or readUint32 which read the data laid in the network byte
|
||||
// order from the provided buffer. The same functions can be safely used
|
||||
// for either unsiged or signed integers so there is not need to create
|
||||
// for either unsigned or signed integers so there is not need to create
|
||||
// special cases for intX_t types.
|
||||
int data_size_len = OptionDataTypes<T>::len;
|
||||
int data_size_len = OptionDataTypeTraits<T>::len;
|
||||
switch (data_size_len) {
|
||||
case 1:
|
||||
values_.push_back(*begin);
|
||||
@@ -181,9 +181,9 @@ public:
|
||||
isc_throw(dhcp::InvalidDataType, "non-integer type");
|
||||
}
|
||||
// Use local variable to set a new value for this iterator.
|
||||
// When using OptionDataTypes<T>::len directly some versions
|
||||
// When using OptionDataTypeTraits<T>::len directly some versions
|
||||
// of clang complain about unresolved reference to
|
||||
// OptionDataTypes structure during linking.
|
||||
// OptionDataTypeTraits structure during linking.
|
||||
begin += data_size_len;
|
||||
}
|
||||
// We do not unpack sub-options here because we have array-type option.
|
||||
|
370
src/lib/dhcp/option_custom.cc
Normal file
370
src/lib/dhcp/option_custom.cc
Normal file
@@ -0,0 +1,370 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <dhcp/libdhcp++.h>
|
||||
#include <dhcp/option_data_types.h>
|
||||
#include <dhcp/option_custom.h>
|
||||
#include <util/encode/hex.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
OptionCustom::OptionCustom(const OptionDefinition& def,
|
||||
Universe u,
|
||||
const OptionBuffer& data)
|
||||
: Option(u, def.getCode(), data.begin(), data.end()),
|
||||
definition_(def) {
|
||||
createBuffers();
|
||||
}
|
||||
|
||||
OptionCustom::OptionCustom(const OptionDefinition& def,
|
||||
Universe u,
|
||||
OptionBufferConstIter first,
|
||||
OptionBufferConstIter last)
|
||||
: Option(u, def.getCode(), first, last),
|
||||
definition_(def) {
|
||||
createBuffers();
|
||||
}
|
||||
|
||||
void
|
||||
OptionCustom::checkIndex(const uint32_t index) const {
|
||||
if (index >= buffers_.size()) {
|
||||
isc_throw(isc::OutOfRange, "specified data field index " << index
|
||||
<< " is out of rangex.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OptionCustom::createBuffers() {
|
||||
// Check that the option definition is correct as we are going
|
||||
// to use it to split the data_ buffer into set of sub buffers.
|
||||
definition_.validate();
|
||||
|
||||
std::vector<OptionBuffer> buffers;
|
||||
OptionBuffer::iterator data = data_.begin();
|
||||
|
||||
OptionDataType data_type = definition_.getType();
|
||||
if (data_type == OPT_RECORD_TYPE) {
|
||||
// An option comprises a record of data fields. We need to
|
||||
// get types of these data fields to allocate enough space
|
||||
// for each buffer.
|
||||
const OptionDefinition::RecordFieldsCollection& fields =
|
||||
definition_.getRecordFields();
|
||||
|
||||
// Go over all data fields within a record.
|
||||
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
|
||||
field != fields.end(); ++field) {
|
||||
// For fixed-size data type such as boolean, integer, even
|
||||
// IP address we can use the utility function to get the required
|
||||
// buffer size.
|
||||
int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
|
||||
|
||||
// For variable size types (such as string) the function above
|
||||
// will return 0 so we need to do a runtime check. Since variable
|
||||
// length data fields may be laid only at the end of an option we
|
||||
// consume the rest of this option. Note that validate() function
|
||||
// in OptionDefinition object should have checked whether the
|
||||
// data fields layout is correct (that the variable string fields
|
||||
// are laid at the end).
|
||||
if (data_size == 0) {
|
||||
data_size = std::distance(data, data_.end());
|
||||
if (data_size == 0) {
|
||||
// If we reached the end of buffer we assume that this option is
|
||||
// truncated because there is no remaining data to initialize
|
||||
// an option field.
|
||||
if (data_size == 0) {
|
||||
isc_throw(OutOfRange, "option buffer truncated");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Our data field requires that there is a certain chunk of
|
||||
// data left in the buffer. If not, option is truncated.
|
||||
if (std::distance(data, data_.end()) < data_size) {
|
||||
isc_throw(OutOfRange, "option buffer truncated");
|
||||
}
|
||||
}
|
||||
// Store the created buffer.
|
||||
buffers.push_back(OptionBuffer(data, data + data_size));
|
||||
// Proceed to the next data field.
|
||||
data += data_size;
|
||||
}
|
||||
} else if (data_type != OPT_EMPTY_TYPE) {
|
||||
// If data_type value is other than OPT_RECORD_TYPE, our option is
|
||||
// empty (have no data at all) or it comprises one or more
|
||||
// data fields of the same type. The type of those fields
|
||||
// is held in the data_type variable so let's use it to determine
|
||||
// a size of buffers.
|
||||
int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
|
||||
// The check below will fail if the input buffer is too short
|
||||
// for the data size being held by this option.
|
||||
// Note that data_size returned by getDataTypeLen may be zero
|
||||
// if variable length data is being held by the option but
|
||||
// this will not cause this check to throw exception.
|
||||
if (std::distance(data, data_.end()) < data_size) {
|
||||
isc_throw(OutOfRange, "option buffer truncated");
|
||||
}
|
||||
// For an array of values we are taking different path because
|
||||
// we have to handle multiple buffers.
|
||||
if (definition_.getArrayType()) {
|
||||
// We don't perform other checks for data types that can't be
|
||||
// used together with array indicator such as strings, empty field
|
||||
// etc. This is because OptionDefinition::validate function should
|
||||
// have checked this already. Thus data_size must be greater than
|
||||
// zero.
|
||||
assert(data_size > 0);
|
||||
// Get equal chunks of data and store as collection of buffers.
|
||||
// Truncate any remaining part which length is not divisible by
|
||||
// data_size. Note that it is ok to truncate the data if and only
|
||||
// if the data buffer is long enough to keep at least one value.
|
||||
// This has been checked above already.
|
||||
do {
|
||||
buffers.push_back(OptionBuffer(data, data + data_size));
|
||||
data += data_size;
|
||||
} while (std::distance(data, data_.end()) >= data_size);
|
||||
} else {
|
||||
// For non-arrays the data_size can be zero because
|
||||
// getDataTypeLen returns zero for variable size data types
|
||||
// such as strings. Simply take whole buffer.
|
||||
if (data_size == 0) {
|
||||
data_size = std::distance(data, data_.end());
|
||||
}
|
||||
if (data_size > 0) {
|
||||
buffers.push_back(OptionBuffer(data, data + data_size));
|
||||
} else {
|
||||
isc_throw(OutOfRange, "option buffer truncated");
|
||||
}
|
||||
}
|
||||
}
|
||||
// If everything went ok we can replace old buffer set with new ones.
|
||||
std::swap(buffers_, buffers);
|
||||
}
|
||||
|
||||
std::string
|
||||
OptionCustom::dataFieldToText(const OptionDataType data_type,
|
||||
const uint32_t index) const {
|
||||
std::ostringstream text;
|
||||
|
||||
// Get the value of the data field.
|
||||
switch (data_type) {
|
||||
case OPT_BINARY_TYPE:
|
||||
text << util::encode::encodeHex(readBinary(index));
|
||||
break;
|
||||
case OPT_BOOLEAN_TYPE:
|
||||
text << (readBoolean(index) ? "true" : "false");
|
||||
break;
|
||||
case OPT_INT8_TYPE:
|
||||
text << readInteger<int8_t>(index);
|
||||
break;
|
||||
case OPT_INT16_TYPE:
|
||||
text << readInteger<int16_t>(index);
|
||||
break;
|
||||
case OPT_INT32_TYPE:
|
||||
text << readInteger<int32_t>(index);
|
||||
break;
|
||||
case OPT_UINT8_TYPE:
|
||||
text << readInteger<uint8_t>(index);
|
||||
break;
|
||||
case OPT_UINT16_TYPE:
|
||||
text << readInteger<uint16_t>(index);
|
||||
break;
|
||||
case OPT_UINT32_TYPE:
|
||||
text << readInteger<uint32_t>(index);
|
||||
break;
|
||||
case OPT_IPV4_ADDRESS_TYPE:
|
||||
case OPT_IPV6_ADDRESS_TYPE:
|
||||
text << readAddress(index).toText();
|
||||
break;
|
||||
case OPT_STRING_TYPE:
|
||||
text << readString(index);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
// Append data field type in brackets.
|
||||
text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) ";
|
||||
|
||||
return (text.str());
|
||||
}
|
||||
|
||||
void
|
||||
OptionCustom::pack4(isc::util::OutputBuffer& buf) {
|
||||
if (len() > 255) {
|
||||
isc_throw(OutOfRange, "DHCPv4 Option " << type_
|
||||
<< " value is too high. At most 255 is supported.");
|
||||
}
|
||||
|
||||
buf.writeUint8(type_);
|
||||
buf.writeUint8(len() - getHeaderLen());
|
||||
|
||||
// Write data from buffers.
|
||||
for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
|
||||
it != buffers_.end(); ++it) {
|
||||
// In theory the createBuffers function should have taken
|
||||
// care that there are no empty buffers added to the
|
||||
// collection but it is almost always good to make sure.
|
||||
if (!it->empty()) {
|
||||
buf.writeData(&(*it)[0], it->size());
|
||||
}
|
||||
}
|
||||
|
||||
// Write suboptions.
|
||||
packOptions(buf);
|
||||
}
|
||||
|
||||
void
|
||||
OptionCustom::pack6(isc::util::OutputBuffer& buf) {
|
||||
buf.writeUint16(type_);
|
||||
buf.writeUint16(len() - getHeaderLen());
|
||||
|
||||
// Write data from buffers.
|
||||
for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
|
||||
it != buffers_.end(); ++it) {
|
||||
if (!it->empty()) {
|
||||
buf.writeData(&(*it)[0], it->size());
|
||||
}
|
||||
}
|
||||
|
||||
packOptions(buf);
|
||||
}
|
||||
|
||||
asiolink::IOAddress
|
||||
OptionCustom::readAddress(const uint32_t index) const {
|
||||
checkIndex(index);
|
||||
|
||||
// The address being read can be either IPv4 or IPv6. The decision
|
||||
// is made based on the buffer length. If it holds 4 bytes it is IPv4
|
||||
// address, if it holds 16 bytes it is IPv6.
|
||||
if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
|
||||
return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
|
||||
} else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
|
||||
return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
|
||||
} else {
|
||||
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
|
||||
<< " IP address. Invalid buffer length " << buffers_[index].size());
|
||||
}
|
||||
}
|
||||
|
||||
const OptionBuffer&
|
||||
OptionCustom::readBinary(const uint32_t index) const {
|
||||
checkIndex(index);
|
||||
return (buffers_[index]);
|
||||
}
|
||||
|
||||
bool
|
||||
OptionCustom::readBoolean(const uint32_t index) const {
|
||||
checkIndex(index);
|
||||
return (OptionDataTypeUtil::readBool(buffers_[index]));
|
||||
}
|
||||
|
||||
std::string
|
||||
OptionCustom::readString(const uint32_t index) const {
|
||||
checkIndex(index);
|
||||
return (OptionDataTypeUtil::readString(buffers_[index]));
|
||||
}
|
||||
|
||||
void
|
||||
OptionCustom::unpack(OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
data_ = OptionBuffer(begin, end);
|
||||
// Chop the buffer stored in data_ into set of sub buffers.
|
||||
createBuffers();
|
||||
}
|
||||
|
||||
uint16_t
|
||||
OptionCustom::len() {
|
||||
// The length of the option is a sum of option header ...
|
||||
int length = getHeaderLen();
|
||||
|
||||
// ... lengths of all buffers that hold option data ...
|
||||
for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
|
||||
buf != buffers_.end(); ++buf) {
|
||||
length += buf->size();
|
||||
}
|
||||
|
||||
// ... and lengths of all suboptions
|
||||
for (OptionCustom::OptionCollection::iterator it = options_.begin();
|
||||
it != options_.end();
|
||||
++it) {
|
||||
length += (*it).second->len();
|
||||
}
|
||||
|
||||
return (length);
|
||||
}
|
||||
|
||||
void OptionCustom::setData(const OptionBufferConstIter first,
|
||||
const OptionBufferConstIter last) {
|
||||
// We will copy entire option buffer, so we have to resize data_.
|
||||
data_.resize(std::distance(first, last));
|
||||
std::copy(first, last, data_.begin());
|
||||
|
||||
// Chop the data_ buffer into set of buffers that represent
|
||||
// option fields data.
|
||||
createBuffers();
|
||||
}
|
||||
|
||||
std::string OptionCustom::toText(int indent) {
|
||||
std::stringstream tmp;
|
||||
|
||||
for (int i = 0; i < indent; ++i)
|
||||
tmp << " ";
|
||||
|
||||
tmp << "type=" << type_ << ", len=" << len()-getHeaderLen()
|
||||
<< ", data fields:" << std::endl;
|
||||
|
||||
OptionDataType data_type = definition_.getType();
|
||||
if (data_type == OPT_RECORD_TYPE) {
|
||||
const OptionDefinition::RecordFieldsCollection& fields =
|
||||
definition_.getRecordFields();
|
||||
|
||||
// For record types we iterate over fields defined in
|
||||
// option definition and match the appropriate buffer
|
||||
// with them.
|
||||
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
|
||||
field != fields.end(); ++field) {
|
||||
for (int j = 0; j < indent + 2; ++j) {
|
||||
tmp << " ";
|
||||
}
|
||||
tmp << "#" << std::distance(fields.begin(), field) << " "
|
||||
<< dataFieldToText(*field, std::distance(fields.begin(),
|
||||
field))
|
||||
<< std::endl;
|
||||
}
|
||||
} else {
|
||||
// For non-record types we iterate over all buffers
|
||||
// and print the data type set globally for an option
|
||||
// definition. We take the same code path for arrays
|
||||
// and non-arrays as they only differ in such a way that
|
||||
// non-arrays have just single data field.
|
||||
for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
|
||||
for (int j = 0; j < indent + 2; ++j) {
|
||||
tmp << " ";
|
||||
}
|
||||
tmp << "#" << i << " "
|
||||
<< dataFieldToText(definition_.getType(), i)
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// print suboptions
|
||||
for (OptionCollection::const_iterator opt = options_.begin();
|
||||
opt != options_.end();
|
||||
++opt) {
|
||||
tmp << (*opt).second->toText(indent+2);
|
||||
}
|
||||
return tmp.str();
|
||||
}
|
||||
|
||||
} // end of isc::dhcp namespace
|
||||
} // end of isc namespace
|
235
src/lib/dhcp/option_custom.h
Normal file
235
src/lib/dhcp/option_custom.h
Normal file
@@ -0,0 +1,235 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef OPTION_CUSTOM_H
|
||||
#define OPTION_CUSTOM_H
|
||||
|
||||
#include <dhcp/option.h>
|
||||
#include <dhcp/option_definition.h>
|
||||
#include <util/io_utilities.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief Option with defined data fields represented as buffers that can
|
||||
/// be accessed using data field index.
|
||||
///
|
||||
/// This class represents an option which has defined structure: data fields
|
||||
/// of specific types and order. Those fields can be accessed using indexes,
|
||||
/// where index 0 represents first data field within an option. The last
|
||||
/// field can be accessed using index equal to 'number of fields' - 1.
|
||||
/// Internally, the option data is stored as a collection of OptionBuffer
|
||||
/// objects, each representing data for a particular data field. This data
|
||||
/// can be converted to the actual data type using methods implemented
|
||||
/// within this class. This class is used to represent those options that
|
||||
/// can't be represented by any other specialized class (this excludes the
|
||||
/// Option class which is generic and can be used to represent any option).
|
||||
class OptionCustom : public Option {
|
||||
public:
|
||||
|
||||
/// @brief Constructor, used for options to be sent.
|
||||
///
|
||||
/// This constructor creates an instance of an option from the whole
|
||||
/// supplied buffer. This constructor is mainly used to create an
|
||||
/// instances of options to be stored in outgoing DHCP packets.
|
||||
/// The buffer used to create the instance of an option can be
|
||||
/// created from the option data specified in server's configuration.
|
||||
///
|
||||
/// @param def option definition.
|
||||
/// @param u specifies universe (V4 or V6).
|
||||
/// @param data content of the option.
|
||||
///
|
||||
/// @throw OutOfRange if option buffer is truncated.
|
||||
///
|
||||
/// @todo list all exceptions thrown by ctor.
|
||||
OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data);
|
||||
|
||||
/// @brief Constructor, used for received options.
|
||||
///
|
||||
/// This constructor creates an instance an option from the portion
|
||||
/// of the buffer specified by iterators. This is mainly useful when
|
||||
/// parsing received packets. Such packets are represented by a single
|
||||
/// buffer holding option data and all sub options. Methods that are
|
||||
/// parsing a packet, supply relevant portions of the packet buffer
|
||||
/// to this constructor to create option instances out of it.
|
||||
///
|
||||
/// @param def option definition.
|
||||
/// @param u specifies universe (V4 or V6).
|
||||
/// @param first iterator to the first element that should be copied.
|
||||
/// @param last iterator to the next element after the last one
|
||||
/// to be copied.
|
||||
///
|
||||
/// @throw OutOfRange if option buffer is truncated.
|
||||
///
|
||||
/// @todo list all exceptions thrown by ctor.
|
||||
OptionCustom(const OptionDefinition& def, Universe u,
|
||||
OptionBufferConstIter first, OptionBufferConstIter last);
|
||||
|
||||
/// @brief Return a number of the data fields.
|
||||
///
|
||||
/// @return number of data fields held by the option.
|
||||
uint32_t getDataFieldsNum() const { return (buffers_.size()); }
|
||||
|
||||
/// @brief Read a buffer as IP address.
|
||||
///
|
||||
/// @param index buffer index.
|
||||
///
|
||||
/// @return IP address read from a buffer.
|
||||
/// @throw isc::OutOfRange if index is out of range.
|
||||
asiolink::IOAddress readAddress(const uint32_t index) const;
|
||||
|
||||
/// @brief Read a buffer as binary data.
|
||||
///
|
||||
/// @param index buffer index.
|
||||
///
|
||||
/// @throw isc::OutOfRange if index is out of range.
|
||||
/// @return read buffer holding binary data.
|
||||
const OptionBuffer& readBinary(const uint32_t index) const;
|
||||
|
||||
/// @brief Read a buffer as boolean value.
|
||||
///
|
||||
/// @param index buffer index.
|
||||
///
|
||||
/// @throw isc::OutOfRange if index is out of range.
|
||||
/// @return read boolean value.
|
||||
bool readBoolean(const uint32_t index) const;
|
||||
|
||||
/// @brief Read a buffer as integer value.
|
||||
///
|
||||
/// @param index buffer index.
|
||||
/// @tparam integer type of a value being returned.
|
||||
///
|
||||
/// @throw isc::OutOfRange if index is out of range.
|
||||
/// @return read integer value.
|
||||
template<typename T>
|
||||
T readInteger(const uint32_t index) const {
|
||||
checkIndex(index);
|
||||
|
||||
// Check that the requested return type is a supported integer.
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
|
||||
" by readInteger is not supported integer type");
|
||||
}
|
||||
|
||||
// Get the option definition type.
|
||||
OptionDataType data_type = definition_.getType();
|
||||
if (data_type == OPT_RECORD_TYPE) {
|
||||
const OptionDefinition::RecordFieldsCollection& record_fields =
|
||||
definition_.getRecordFields();
|
||||
// When we initialized buffers we have already checked that
|
||||
// the number of these buffers is equal to number of option
|
||||
// fields in the record so the condition below should be met.
|
||||
assert(index < record_fields.size());
|
||||
// Get the data type to be returned.
|
||||
data_type = record_fields[index];
|
||||
}
|
||||
|
||||
// Requested data type must match the data type in a record.
|
||||
if (OptionDataTypeTraits<T>::type != data_type) {
|
||||
isc_throw(isc::dhcp::InvalidDataType,
|
||||
"unable to read option field with index " << index
|
||||
<< " as integer value. The field's data type"
|
||||
<< data_type << " does not match the integer type"
|
||||
<< "returned by the readInteger function.");
|
||||
}
|
||||
// When we created the buffer we have checked that it has a
|
||||
// valid size so this condition here should be always fulfiled.
|
||||
assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
|
||||
// Read an integer value.
|
||||
return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
|
||||
}
|
||||
|
||||
/// @brief Read a buffer as string value.
|
||||
///
|
||||
/// @param index buffer index.
|
||||
///
|
||||
/// @return string value read from buffer.
|
||||
/// @throw isc::OutOfRange if index is out of range.
|
||||
std::string readString(const uint32_t index) const;
|
||||
|
||||
/// @brief Parses received buffer.
|
||||
///
|
||||
/// @param begin iterator to first byte of option data
|
||||
/// @param end iterator to end of option data (first byte after option end)
|
||||
virtual void unpack(OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end);
|
||||
|
||||
/// @brief Returns string representation of the option.
|
||||
///
|
||||
/// @param indent number of spaces before printed text.
|
||||
///
|
||||
/// @return string with text representation.
|
||||
virtual std::string toText(int indent = 0);
|
||||
|
||||
/// @brief Returns length of the complete option (data length +
|
||||
/// DHCPv4/DHCPv6 option header)
|
||||
///
|
||||
/// @return length of the option
|
||||
virtual uint16_t len();
|
||||
|
||||
/// @brief Sets content of this option from buffer.
|
||||
///
|
||||
/// Option will be resized to length of buffer.
|
||||
///
|
||||
/// @param first iterator pointing begining of buffer to copy.
|
||||
/// @param last iterator pointing to end of buffer to copy.
|
||||
void setData(const OptionBufferConstIter first,
|
||||
const OptionBufferConstIter last);
|
||||
|
||||
protected:
|
||||
|
||||
/// @brief Writes DHCPv4 option in a wire format to a buffer.
|
||||
///
|
||||
/// @param buf output buffer (option will be stored there).
|
||||
virtual void pack4(isc::util::OutputBuffer& buf);
|
||||
|
||||
/// @brief Writes DHCPv6 option in a wire format to a buffer.
|
||||
///
|
||||
/// @param buf output buffer (built options will be stored here)
|
||||
virtual void pack6(isc::util::OutputBuffer& buf);
|
||||
|
||||
private:
|
||||
|
||||
/// @brief Check if data field index is valid.
|
||||
///
|
||||
/// @param index Data field index to check.
|
||||
///
|
||||
/// @throw isc::OutOfRange if index is out of range.
|
||||
void checkIndex(const uint32_t index) const;
|
||||
|
||||
/// @brief Create collection of buffers representing data field values.
|
||||
void createBuffers();
|
||||
|
||||
/// @brief Return a text representation of a data field.
|
||||
///
|
||||
/// @param data_type data type of a field.
|
||||
/// @param index data field buffer index within a custom option.
|
||||
///
|
||||
/// @return text representation of a data field.
|
||||
std::string dataFieldToText(const OptionDataType data_type,
|
||||
const uint32_t index) const;
|
||||
|
||||
/// Option definition used to create an option.
|
||||
OptionDefinition definition_;
|
||||
|
||||
/// The collection of buffers holding data for option fields.
|
||||
/// The order of buffers corresponds to the order of option
|
||||
/// fields.
|
||||
std::vector<OptionBuffer> buffers_;
|
||||
};
|
||||
|
||||
} // namespace isc::dhcp
|
||||
} // namespace isc
|
||||
|
||||
#endif // OPTION_CUSTOM_H
|
227
src/lib/dhcp/option_data_types.cc
Normal file
227
src/lib/dhcp/option_data_types.cc
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <dhcp/option_data_types.h>
|
||||
#include <util/encode/hex.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
OptionDataTypeUtil::OptionDataTypeUtil() {
|
||||
data_types_["empty"] = OPT_EMPTY_TYPE;
|
||||
data_types_["binary"] = OPT_BINARY_TYPE;
|
||||
data_types_["boolean"] = OPT_BOOLEAN_TYPE;
|
||||
data_types_["int8"] = OPT_INT8_TYPE;
|
||||
data_types_["int16"] = OPT_INT16_TYPE;
|
||||
data_types_["int32"] = OPT_INT32_TYPE;
|
||||
data_types_["uint8"] = OPT_UINT8_TYPE;
|
||||
data_types_["uint16"] = OPT_UINT16_TYPE;
|
||||
data_types_["uint32"] = OPT_UINT32_TYPE;
|
||||
data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
|
||||
data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
|
||||
data_types_["string"] = OPT_STRING_TYPE;
|
||||
data_types_["fqdn"] = OPT_FQDN_TYPE;
|
||||
data_types_["record"] = OPT_RECORD_TYPE;
|
||||
|
||||
data_type_names_[OPT_EMPTY_TYPE] = "empty";
|
||||
data_type_names_[OPT_BINARY_TYPE] = "binary";
|
||||
data_type_names_[OPT_BOOLEAN_TYPE] = "boolean";
|
||||
data_type_names_[OPT_INT8_TYPE] = "int8";
|
||||
data_type_names_[OPT_INT16_TYPE] = "int16";
|
||||
data_type_names_[OPT_INT32_TYPE] = "int32";
|
||||
data_type_names_[OPT_UINT8_TYPE] = "uint8";
|
||||
data_type_names_[OPT_UINT16_TYPE] = "uint16";
|
||||
data_type_names_[OPT_UINT32_TYPE] = "uint32";
|
||||
data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
|
||||
data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
|
||||
data_type_names_[OPT_STRING_TYPE] = "string";
|
||||
data_type_names_[OPT_FQDN_TYPE] = "fqdn";
|
||||
data_type_names_[OPT_RECORD_TYPE] = "record";
|
||||
// The "unknown" data type is declared here so as
|
||||
// it can be returned by reference by a getDataTypeName
|
||||
// function it no other type is suitable. Other than that
|
||||
// this is unused.
|
||||
data_type_names_[OPT_UNKNOWN_TYPE] = "unknown";
|
||||
}
|
||||
|
||||
OptionDataType
|
||||
OptionDataTypeUtil::getDataType(const std::string& data_type) {
|
||||
return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type));
|
||||
}
|
||||
|
||||
OptionDataType
|
||||
OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const {
|
||||
std::map<std::string, OptionDataType>::const_iterator data_type_it =
|
||||
data_types_.find(data_type);
|
||||
if (data_type_it != data_types_.end()) {
|
||||
return (data_type_it->second);
|
||||
}
|
||||
return (OPT_UNKNOWN_TYPE);
|
||||
}
|
||||
|
||||
int
|
||||
OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
|
||||
switch (data_type) {
|
||||
case OPT_BOOLEAN_TYPE:
|
||||
case OPT_INT8_TYPE:
|
||||
case OPT_UINT8_TYPE:
|
||||
return (1);
|
||||
|
||||
case OPT_INT16_TYPE:
|
||||
case OPT_UINT16_TYPE:
|
||||
return (2);
|
||||
|
||||
case OPT_INT32_TYPE:
|
||||
case OPT_UINT32_TYPE:
|
||||
return (4);
|
||||
|
||||
case OPT_IPV4_ADDRESS_TYPE:
|
||||
return (asiolink::V4ADDRESS_LEN);
|
||||
|
||||
case OPT_IPV6_ADDRESS_TYPE:
|
||||
return (asiolink::V6ADDRESS_LEN);
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
const std::string&
|
||||
OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) {
|
||||
return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type));
|
||||
}
|
||||
|
||||
const std::string&
|
||||
OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const {
|
||||
std::map<OptionDataType, std::string>::const_iterator data_type_it =
|
||||
data_type_names_.find(data_type);
|
||||
if (data_type_it != data_type_names_.end()) {
|
||||
return (data_type_it->second);
|
||||
}
|
||||
return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second);
|
||||
}
|
||||
|
||||
OptionDataTypeUtil&
|
||||
OptionDataTypeUtil::instance() {
|
||||
static OptionDataTypeUtil instance;
|
||||
return (instance);
|
||||
}
|
||||
|
||||
asiolink::IOAddress
|
||||
OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
|
||||
const short family) {
|
||||
using namespace isc::asiolink;
|
||||
if (family == AF_INET) {
|
||||
if (buf.size() < V4ADDRESS_LEN) {
|
||||
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
|
||||
<< " IPv4 address. Invalid buffer size: " << buf.size());
|
||||
}
|
||||
return (IOAddress::fromBytes(AF_INET, &buf[0]));
|
||||
} else if (family == AF_INET6) {
|
||||
if (buf.size() < V6ADDRESS_LEN) {
|
||||
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
|
||||
<< " IPv6 address. Invalid buffer size: " << buf.size());
|
||||
}
|
||||
return (IOAddress::fromBytes(AF_INET6, &buf[0]));
|
||||
} else {
|
||||
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
|
||||
"IP address. Invalid family: " << family);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address,
|
||||
std::vector<uint8_t>& buf) {
|
||||
// @todo There is a ticket 2396 submitted, which adds the
|
||||
// functionality to return a buffer representation of
|
||||
// IOAddress. If so, this function can be simplified.
|
||||
if (address.getAddress().is_v4()) {
|
||||
asio::ip::address_v4::bytes_type addr_bytes =
|
||||
address.getAddress().to_v4().to_bytes();
|
||||
// Increase the buffer size by the size of IPv4 address.
|
||||
buf.resize(buf.size() + addr_bytes.size());
|
||||
std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
|
||||
buf.end());
|
||||
} else if (address.getAddress().is_v6()) {
|
||||
asio::ip::address_v6::bytes_type addr_bytes =
|
||||
address.getAddress().to_v6().to_bytes();
|
||||
// Incresase the buffer size by the size of IPv6 address.
|
||||
buf.resize(buf.size() + addr_bytes.size());
|
||||
std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
|
||||
buf.end());
|
||||
} else {
|
||||
isc_throw(BadDataTypeCast, "the address " << address.toText()
|
||||
<< " is neither valid IPv4 not IPv6 address.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OptionDataTypeUtil::writeBinary(const std::string& hex_str,
|
||||
std::vector<uint8_t>& buf) {
|
||||
// Binary value means that the value is encoded as a string
|
||||
// of hexadecimal digits. We need to decode this string
|
||||
// to the binary format here.
|
||||
OptionBuffer binary;
|
||||
try {
|
||||
util::encode::decodeHex(hex_str, binary);
|
||||
} catch (const Exception& ex) {
|
||||
isc_throw(BadDataTypeCast, "unable to cast " << hex_str
|
||||
<< " to binary data type: " << ex.what());
|
||||
}
|
||||
// Decode was successful so append decoded binary value
|
||||
// to the buffer.
|
||||
buf.insert(buf.end(), binary.begin(), binary.end());
|
||||
}
|
||||
|
||||
bool
|
||||
OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
|
||||
if (buf.size() < 1) {
|
||||
isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
|
||||
<< " value. Invalid buffer size " << buf.size());
|
||||
}
|
||||
if (buf[0] == 1) {
|
||||
return (true);
|
||||
} else if (buf[0] == 0) {
|
||||
return (false);
|
||||
}
|
||||
isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
|
||||
<< " value. Invalid value " << static_cast<int>(buf[0]));
|
||||
}
|
||||
|
||||
void
|
||||
OptionDataTypeUtil::writeBool(const bool value,
|
||||
std::vector<uint8_t>& buf) {
|
||||
buf.push_back(static_cast<uint8_t>(value ? 1 : 0));
|
||||
}
|
||||
|
||||
std::string
|
||||
OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
|
||||
std::string value;
|
||||
if (buf.size() > 0) {
|
||||
value.insert(value.end(), buf.begin(), buf.end());
|
||||
}
|
||||
return (value);
|
||||
}
|
||||
|
||||
void
|
||||
OptionDataTypeUtil::writeString(const std::string& value,
|
||||
std::vector<uint8_t>& buf) {
|
||||
if (value.size() > 0) {
|
||||
buf.insert(buf.end(), value.begin(), value.end());
|
||||
}
|
||||
}
|
||||
|
||||
} // end of isc::dhcp namespace
|
||||
} // end of isc namespace
|
@@ -15,7 +15,10 @@
|
||||
#ifndef OPTION_DATA_TYPES_H
|
||||
#define OPTION_DATA_TYPES_H
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/option.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <util/io_utilities.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -29,57 +32,356 @@ public:
|
||||
isc::Exception(file, line, what) { };
|
||||
};
|
||||
|
||||
/// @brief Trait class for integer data types supported in DHCP option definitions.
|
||||
/// @brief Exception to be thrown when cast to the data type was unsuccessful.
|
||||
class BadDataTypeCast : public Exception {
|
||||
public:
|
||||
BadDataTypeCast(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) { };
|
||||
};
|
||||
|
||||
|
||||
/// @brief Data types of DHCP option fields.
|
||||
///
|
||||
/// @warning The order of data types matters: OPT_UNKNOWN_TYPE
|
||||
/// must always be the last position. Also, OPT_RECORD_TYPE
|
||||
/// must be at last but one position. This is because some
|
||||
/// functions perform sanity checks on data type values using
|
||||
/// '>' operators, assuming that all values beyond the
|
||||
/// OPT_RECORD_TYPE are invalid.
|
||||
enum OptionDataType {
|
||||
OPT_EMPTY_TYPE,
|
||||
OPT_BINARY_TYPE,
|
||||
OPT_BOOLEAN_TYPE,
|
||||
OPT_INT8_TYPE,
|
||||
OPT_INT16_TYPE,
|
||||
OPT_INT32_TYPE,
|
||||
OPT_UINT8_TYPE,
|
||||
OPT_UINT16_TYPE,
|
||||
OPT_UINT32_TYPE,
|
||||
OPT_ANY_ADDRESS_TYPE,
|
||||
OPT_IPV4_ADDRESS_TYPE,
|
||||
OPT_IPV6_ADDRESS_TYPE,
|
||||
OPT_STRING_TYPE,
|
||||
OPT_FQDN_TYPE,
|
||||
OPT_RECORD_TYPE,
|
||||
OPT_UNKNOWN_TYPE
|
||||
};
|
||||
|
||||
/// @brief Trait class for data types supported in DHCP option definitions.
|
||||
///
|
||||
/// This is useful to check whether the type specified as template parameter
|
||||
/// is supported by classes like Option6Int, Option6IntArray and some template
|
||||
/// factory functions in OptionDefinition class.
|
||||
template<typename T>
|
||||
struct OptionDataTypes {
|
||||
struct OptionDataTypeTraits {
|
||||
static const bool valid = false;
|
||||
static const int len = 0;
|
||||
static const bool integer_type = false;
|
||||
static const OptionDataType type = OPT_UNKNOWN_TYPE;
|
||||
};
|
||||
|
||||
/// binary type is supported
|
||||
template<>
|
||||
struct OptionDataTypeTraits<OptionBuffer> {
|
||||
static const bool valid = true;
|
||||
static const int len = 0;
|
||||
static const bool integer_type = false;
|
||||
static const OptionDataType type = OPT_BINARY_TYPE;
|
||||
};
|
||||
|
||||
/// bool type is supported
|
||||
template<>
|
||||
struct OptionDataTypeTraits<bool> {
|
||||
static const bool valid = true;
|
||||
static const int len = sizeof(bool);
|
||||
static const bool integer_type = false;
|
||||
static const OptionDataType type = OPT_BOOLEAN_TYPE;
|
||||
};
|
||||
|
||||
/// int8_t type is supported.
|
||||
template<>
|
||||
struct OptionDataTypes<int8_t> {
|
||||
struct OptionDataTypeTraits<int8_t> {
|
||||
static const bool valid = true;
|
||||
static const int len = 1;
|
||||
static const bool integer_type = true;
|
||||
static const OptionDataType type = OPT_INT8_TYPE;
|
||||
};
|
||||
|
||||
/// int16_t type is supported.
|
||||
template<>
|
||||
struct OptionDataTypes<int16_t> {
|
||||
struct OptionDataTypeTraits<int16_t> {
|
||||
static const bool valid = true;
|
||||
static const int len = 2;
|
||||
static const bool integer_type = true;
|
||||
static const OptionDataType type = OPT_INT16_TYPE;
|
||||
};
|
||||
|
||||
/// int32_t type is supported.
|
||||
template<>
|
||||
struct OptionDataTypes<int32_t> {
|
||||
struct OptionDataTypeTraits<int32_t> {
|
||||
static const bool valid = true;
|
||||
static const int len = 4;
|
||||
static const bool integer_type = true;
|
||||
static const OptionDataType type = OPT_INT32_TYPE;
|
||||
};
|
||||
|
||||
/// uint8_t type is supported.
|
||||
template<>
|
||||
struct OptionDataTypes<uint8_t> {
|
||||
struct OptionDataTypeTraits<uint8_t> {
|
||||
static const bool valid = true;
|
||||
static const int len = 1;
|
||||
static const bool integer_type = true;
|
||||
static const OptionDataType type = OPT_UINT8_TYPE;
|
||||
};
|
||||
|
||||
/// uint16_t type is supported.
|
||||
template<>
|
||||
struct OptionDataTypes<uint16_t> {
|
||||
struct OptionDataTypeTraits<uint16_t> {
|
||||
static const bool valid = true;
|
||||
static const int len = 2;
|
||||
static const bool integer_type = true;
|
||||
static const OptionDataType type = OPT_UINT16_TYPE;
|
||||
};
|
||||
|
||||
/// uint32_t type is supported.
|
||||
template<>
|
||||
struct OptionDataTypes<uint32_t> {
|
||||
struct OptionDataTypeTraits<uint32_t> {
|
||||
static const bool valid = true;
|
||||
static const int len = 4;
|
||||
static const bool integer_type = true;
|
||||
static const OptionDataType type = OPT_UINT32_TYPE;
|
||||
};
|
||||
|
||||
/// IPv4 and IPv6 address type is supported
|
||||
template<>
|
||||
struct OptionDataTypeTraits<asiolink::IOAddress> {
|
||||
static const bool valid = true;
|
||||
// The len value is used to determine the size of the data
|
||||
// to be written to an option buffer. IOAddress object may
|
||||
// either represent an IPv4 or IPv6 addresses which have
|
||||
// different lengths. Thus we can't put fixed value here.
|
||||
// The length of a data to be written into an option buffer
|
||||
// have to be determined in the runtime for a particular
|
||||
// IOAddress object. Thus setting len to zero.
|
||||
static const int len = 0;
|
||||
static const bool integer_type = false;
|
||||
static const OptionDataType type = OPT_ANY_ADDRESS_TYPE;
|
||||
};
|
||||
|
||||
/// string type is supported
|
||||
template<>
|
||||
struct OptionDataTypeTraits<std::string> {
|
||||
static const bool valid = true;
|
||||
// The len value is used to determine the size of the data
|
||||
// to be written to an option buffer. For strings this
|
||||
// size is unknown until we actually deal with the particular
|
||||
// string to be written. Thus setting it to zero.
|
||||
static const int len = 0;
|
||||
static const bool integer_type = false;
|
||||
static const OptionDataType type = OPT_STRING_TYPE;
|
||||
};
|
||||
|
||||
/// @brief Utility class for option data types.
|
||||
///
|
||||
/// This class provides a set of utility functions to operate on
|
||||
/// supported DHCP option data types. It includes conversion
|
||||
/// between enumerator values representing data types and data
|
||||
/// type names. It also includes a set of functions that write
|
||||
/// data into option buffers and read data from option buffers.
|
||||
/// The data being written and read are converted from/to actual
|
||||
/// data types.
|
||||
/// @note This is a singleton class but it can be accessed via
|
||||
/// static methods only.
|
||||
class OptionDataTypeUtil {
|
||||
public:
|
||||
|
||||
/// @brief Return option data type from its name.
|
||||
///
|
||||
/// @param data_type data type name.
|
||||
/// @return option data type.
|
||||
static OptionDataType getDataType(const std::string& data_type);
|
||||
|
||||
/// @brief Return option data type name from the data type enumerator.
|
||||
///
|
||||
/// @param data_type option data type.
|
||||
/// @return option data type name.
|
||||
static const std::string& getDataTypeName(const OptionDataType data_type);
|
||||
|
||||
/// @brief Get data type buffer length.
|
||||
///
|
||||
/// This function returns the size of a particular data type.
|
||||
/// Values retured by this function correspond to the data type
|
||||
/// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and
|
||||
/// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate
|
||||
/// the fixed length of the data being written into the buffer,
|
||||
/// not the size of the particular data type. Thus for data types
|
||||
/// such as string, binary etc. for which the buffer length can't
|
||||
/// be determined this function returns 0.
|
||||
/// In addition, this function returns the data sizes for
|
||||
/// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer
|
||||
/// representations have fixed data lengths: 4 and 16 respectively.
|
||||
///
|
||||
/// @param data_type data type which size is to be returned.
|
||||
/// @return data type size or zero for variable length types.
|
||||
static int getDataTypeLen(const OptionDataType data_type);
|
||||
|
||||
/// @brief Read IPv4 or IPv6 addres from a buffer.
|
||||
///
|
||||
/// @param buf input buffer.
|
||||
/// @param family address family: AF_INET or AF_INET6.
|
||||
///
|
||||
/// @return address being read.
|
||||
static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
|
||||
const short family);
|
||||
|
||||
/// @brief Append IPv4 or IPv6 address to a buffer.
|
||||
///
|
||||
/// @param address IPv4 or IPv6 address.
|
||||
/// @param [out] buf output buffer.
|
||||
static void writeAddress(const asiolink::IOAddress& address,
|
||||
std::vector<uint8_t>& buf);
|
||||
|
||||
/// @brief Append hex-encoded binary values to a buffer.
|
||||
///
|
||||
/// @param hex_str string representing a binary value encoded
|
||||
/// with hexadecimal digits (without 0x prefix).
|
||||
/// @param [out] buf output buffer.
|
||||
static void writeBinary(const std::string& hex_str,
|
||||
std::vector<uint8_t>& buf);
|
||||
|
||||
/// @brief Read boolean value from a buffer.
|
||||
///
|
||||
/// @param buf input buffer.
|
||||
/// @return boolean value read from a buffer.
|
||||
static bool readBool(const std::vector<uint8_t>& buf);
|
||||
|
||||
/// @brief Append boolean value into a buffer.
|
||||
///
|
||||
/// The bool value is encoded in a buffer in such a way that
|
||||
/// "1" means "true" and "0" means "false".
|
||||
///
|
||||
/// @param value boolean value to be written.
|
||||
/// @param [out] buf output buffer.
|
||||
static void writeBool(const bool value, std::vector<uint8_t>& buf);
|
||||
|
||||
/// @brief Read integer value from a buffer.
|
||||
///
|
||||
/// @param buf input buffer.
|
||||
/// @tparam integer type of the returned value.
|
||||
/// @return integer value being read.
|
||||
template<typename T>
|
||||
static T readInt(const std::vector<uint8_t>& buf) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
|
||||
" by readInteger is unsupported integer type");
|
||||
}
|
||||
|
||||
assert(buf.size() == OptionDataTypeTraits<T>::len);
|
||||
T value;
|
||||
switch (OptionDataTypeTraits<T>::len) {
|
||||
case 1:
|
||||
value = *(buf.begin());
|
||||
break;
|
||||
case 2:
|
||||
// Calling readUint16 works either for unsigned
|
||||
// or signed types.
|
||||
value = isc::util::readUint16(&(*buf.begin()));
|
||||
break;
|
||||
case 4:
|
||||
// Calling readUint32 works either for unsigned
|
||||
// or signed types.
|
||||
value = isc::util::readUint32(&(*buf.begin()));
|
||||
break;
|
||||
default:
|
||||
// This should not happen because we made checks on data types
|
||||
// but it does not hurt to keep throw statement here.
|
||||
isc_throw(isc::dhcp::InvalidDataType,
|
||||
"invalid size of the data type to be read as integer.");
|
||||
}
|
||||
return (value);
|
||||
}
|
||||
|
||||
/// @brief Append integer or unsigned integer value to a buffer.
|
||||
///
|
||||
/// @param value an integer value to be written into a buffer.
|
||||
/// @param [out] buf output buffer.
|
||||
/// @tparam data type of the value.
|
||||
template<typename T>
|
||||
static void writeInt(const T value,
|
||||
std::vector<uint8_t>& buf) {
|
||||
if (!OptionDataTypeTraits<T>::integer_type) {
|
||||
isc_throw(InvalidDataType, "provided data type is not the supported.");
|
||||
}
|
||||
switch (OptionDataTypeTraits<T>::len) {
|
||||
case 1:
|
||||
buf.push_back(static_cast<uint8_t>(value));
|
||||
break;
|
||||
case 2:
|
||||
buf.resize(buf.size() + 2);
|
||||
isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2]);
|
||||
break;
|
||||
case 4:
|
||||
buf.resize(buf.size() + 4);
|
||||
isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4]);
|
||||
break;
|
||||
default:
|
||||
// The cases above cover whole range of possible data lengths because
|
||||
// we check at the beginning of this function that given data type is
|
||||
// a supported integer type which can be only 1,2 or 4 bytes long.
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Read string value from a buffer.
|
||||
///
|
||||
/// @param buf input buffer.
|
||||
///
|
||||
/// @return string value being read.
|
||||
static std::string readString(const std::vector<uint8_t>& buf);
|
||||
|
||||
/// @brief Write UTF8-encoded string into a buffer.
|
||||
///
|
||||
/// @param value string value to be written into a buffer.
|
||||
/// @param [out] buf output buffer.
|
||||
static void writeString(const std::string& value,
|
||||
std::vector<uint8_t>& buf);
|
||||
private:
|
||||
|
||||
/// The container holding mapping of data type names to
|
||||
/// data types enumerator.
|
||||
std::map<std::string, OptionDataType> data_types_;
|
||||
|
||||
/// The container holding mapping of data types to data
|
||||
/// type names.
|
||||
std::map<OptionDataType, std::string> data_type_names_;
|
||||
|
||||
/// @brief Private constructor.
|
||||
///
|
||||
/// This constructor is private because this class should
|
||||
/// be used as singleton (through static public functions).
|
||||
OptionDataTypeUtil();
|
||||
|
||||
/// @brief Return instance of OptionDataTypeUtil
|
||||
///
|
||||
/// This function is used by some of the public static functions
|
||||
/// to create an instance of OptionDataTypeUtil class.
|
||||
/// When instance is called it calls the class'es constructor
|
||||
/// and initializes some of the private data members.
|
||||
///
|
||||
/// @return instance of OptionDataTypeUtil singleton.
|
||||
static OptionDataTypeUtil& instance();
|
||||
|
||||
/// @brief Return option data type from its name.
|
||||
///
|
||||
/// @param data_type data type name.
|
||||
/// @return option data type.
|
||||
OptionDataType getDataTypeImpl(const std::string& data_type) const;
|
||||
|
||||
/// @brief Return option data type name from the data type enumerator.
|
||||
///
|
||||
/// @param data_type option data type.
|
||||
/// @return option data type name.
|
||||
const std::string& getDataTypeNameImpl(const OptionDataType data_type) const;
|
||||
};
|
||||
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include <dhcp/option6_int.h>
|
||||
#include <dhcp/option6_int_array.h>
|
||||
#include <dhcp/option_definition.h>
|
||||
#include <util/encode/hex.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::util;
|
||||
@@ -27,32 +28,6 @@ using namespace isc::util;
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
OptionDefinition::DataTypeUtil::DataTypeUtil() {
|
||||
data_types_["empty"] = EMPTY_TYPE;
|
||||
data_types_["binary"] = BINARY_TYPE;
|
||||
data_types_["boolean"] = BOOLEAN_TYPE;
|
||||
data_types_["int8"] = INT8_TYPE;
|
||||
data_types_["int16"] = INT16_TYPE;
|
||||
data_types_["int32"] = INT32_TYPE;
|
||||
data_types_["uint8"] = UINT8_TYPE;
|
||||
data_types_["uint16"] = UINT16_TYPE;
|
||||
data_types_["uint32"] = UINT32_TYPE;
|
||||
data_types_["ipv4-address"] = IPV4_ADDRESS_TYPE;
|
||||
data_types_["ipv6-address"] = IPV6_ADDRESS_TYPE;
|
||||
data_types_["string"] = STRING_TYPE;
|
||||
data_types_["fqdn"] = FQDN_TYPE;
|
||||
data_types_["record"] = RECORD_TYPE;
|
||||
}
|
||||
|
||||
OptionDefinition::DataType
|
||||
OptionDefinition::DataTypeUtil::getDataType(const std::string& data_type) {
|
||||
std::map<std::string, DataType>::const_iterator data_type_it =
|
||||
data_types_.find(data_type);
|
||||
if (data_type_it != data_types_.end()) {
|
||||
return (data_type_it->second);
|
||||
}
|
||||
return UNKNOWN_TYPE;
|
||||
}
|
||||
|
||||
OptionDefinition::OptionDefinition(const std::string& name,
|
||||
const uint16_t code,
|
||||
@@ -60,17 +35,17 @@ OptionDefinition::OptionDefinition(const std::string& name,
|
||||
const bool array_type /* = false */)
|
||||
: name_(name),
|
||||
code_(code),
|
||||
type_(UNKNOWN_TYPE),
|
||||
type_(OPT_UNKNOWN_TYPE),
|
||||
array_type_(array_type) {
|
||||
// Data type is held as enum value by this class.
|
||||
// Use the provided option type string to get the
|
||||
// corresponding enum value.
|
||||
type_ = DataTypeUtil::instance().getDataType(type);
|
||||
type_ = OptionDataTypeUtil::getDataType(type);
|
||||
}
|
||||
|
||||
OptionDefinition::OptionDefinition(const std::string& name,
|
||||
const uint16_t code,
|
||||
const DataType type,
|
||||
const OptionDataType type,
|
||||
const bool array_type /* = false */)
|
||||
: name_(name),
|
||||
code_(code),
|
||||
@@ -80,67 +55,115 @@ OptionDefinition::OptionDefinition(const std::string& name,
|
||||
|
||||
void
|
||||
OptionDefinition::addRecordField(const std::string& data_type_name) {
|
||||
DataType data_type = DataTypeUtil::instance().getDataType(data_type_name);
|
||||
OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name);
|
||||
addRecordField(data_type);
|
||||
}
|
||||
|
||||
void
|
||||
OptionDefinition::addRecordField(const DataType data_type) {
|
||||
if (type_ != RECORD_TYPE) {
|
||||
OptionDefinition::addRecordField(const OptionDataType data_type) {
|
||||
if (type_ != OPT_RECORD_TYPE) {
|
||||
isc_throw(isc::InvalidOperation, "'record' option type must be used"
|
||||
" to add data fields to the record");
|
||||
}
|
||||
if (data_type >= UNKNOWN_TYPE) {
|
||||
isc_throw(isc::BadValue, "attempted to add invalid data type to the record");
|
||||
if (data_type >= OPT_RECORD_TYPE ||
|
||||
data_type == OPT_ANY_ADDRESS_TYPE ||
|
||||
data_type == OPT_EMPTY_TYPE) {
|
||||
isc_throw(isc::BadValue, "attempted to add invalid data type to the record.");
|
||||
}
|
||||
record_fields_.push_back(data_type);
|
||||
}
|
||||
|
||||
Option::Factory*
|
||||
OptionDefinition::getFactory() const {
|
||||
OptionPtr
|
||||
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) const {
|
||||
validate();
|
||||
|
||||
try {
|
||||
if (type_ == OPT_BINARY_TYPE) {
|
||||
return (factoryGeneric(u, type, begin, end));
|
||||
|
||||
} else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
|
||||
return (factoryAddrList6(type, begin, end));
|
||||
|
||||
} else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
|
||||
return (factoryAddrList4(type, begin, end));
|
||||
|
||||
} else if (type_ == OPT_EMPTY_TYPE) {
|
||||
return (factoryEmpty(u, type));
|
||||
|
||||
} else if (u == Option::V6 &&
|
||||
code_ == D6O_IA_NA &&
|
||||
haveIA6Format()) {
|
||||
return (factoryIA6(type, begin, end));
|
||||
|
||||
} else if (u == Option::V6 &&
|
||||
code_ == D6O_IAADDR &&
|
||||
haveIAAddr6Format()) {
|
||||
return (factoryIAAddr6(type, begin, end));
|
||||
|
||||
} else if (type_ == OPT_UINT8_TYPE) {
|
||||
if (array_type_) {
|
||||
return (factoryGeneric(u, type, begin, end));
|
||||
} else {
|
||||
return (factoryInteger<uint8_t>(u, type, begin, end));
|
||||
}
|
||||
|
||||
} else if (type_ == OPT_UINT16_TYPE) {
|
||||
if (array_type_) {
|
||||
return (factoryIntegerArray<uint16_t>(type, begin, end));
|
||||
} else {
|
||||
return (factoryInteger<uint16_t>(u, type, begin, end));
|
||||
}
|
||||
|
||||
} else if (type_ == OPT_UINT32_TYPE) {
|
||||
if (array_type_) {
|
||||
return (factoryIntegerArray<uint32_t>(type, begin, end));
|
||||
} else {
|
||||
return (factoryInteger<uint32_t>(u, type, begin, end));
|
||||
}
|
||||
|
||||
}
|
||||
return (factoryGeneric(u, type, begin, end));
|
||||
|
||||
} catch (const Exception& ex) {
|
||||
isc_throw(InvalidOptionValue, ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf) const {
|
||||
return (optionFactory(u, type, buf.begin(), buf.end()));
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
|
||||
const std::vector<std::string>& values) const {
|
||||
validate();
|
||||
|
||||
// @todo This function must be extended to return more factory
|
||||
// functions that create instances of more specialized options.
|
||||
// This requires us to first implement those more specialized
|
||||
// options that will be derived from Option class.
|
||||
if (type_ == BINARY_TYPE) {
|
||||
return (factoryGeneric);
|
||||
} else if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
|
||||
return (factoryAddrList6);
|
||||
} else if (type_ == IPV4_ADDRESS_TYPE && array_type_) {
|
||||
return (factoryAddrList4);
|
||||
} else if (type_ == EMPTY_TYPE) {
|
||||
return (factoryEmpty);
|
||||
} else if (code_ == D6O_IA_NA && haveIA6Format()) {
|
||||
return (factoryIA6);
|
||||
} else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
|
||||
return (factoryIAAddr6);
|
||||
} else if (type_ == UINT8_TYPE) {
|
||||
if (array_type_) {
|
||||
return (factoryGeneric);
|
||||
} else {
|
||||
return (factoryInteger<uint8_t>);
|
||||
OptionBuffer buf;
|
||||
if (!array_type_ && type_ != OPT_RECORD_TYPE) {
|
||||
if (values.size() == 0) {
|
||||
isc_throw(InvalidOptionValue, "no option value specified");
|
||||
}
|
||||
} else if (type_ == UINT16_TYPE) {
|
||||
if (array_type_) {
|
||||
return (factoryIntegerArray<uint16_t>);
|
||||
} else {
|
||||
return (factoryInteger<uint16_t>);
|
||||
writeToBuffer(values[0], type_, buf);
|
||||
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
writeToBuffer(values[i], type_, buf);
|
||||
}
|
||||
} else if (type_ == UINT32_TYPE) {
|
||||
if (array_type_) {
|
||||
return (factoryIntegerArray<uint32_t>);
|
||||
} else {
|
||||
return (factoryInteger<uint32_t>);
|
||||
} else if (type_ == OPT_RECORD_TYPE) {
|
||||
const RecordFieldsCollection& records = getRecordFields();
|
||||
if (records.size() > values.size()) {
|
||||
isc_throw(InvalidOptionValue, "number of data fields for the option"
|
||||
<< " type " << type_ << " is greater than number of values"
|
||||
<< " provided.");
|
||||
}
|
||||
for (size_t i = 0; i < records.size(); ++i) {
|
||||
writeToBuffer(values[i], records[i], buf);
|
||||
}
|
||||
}
|
||||
// Factory generic returns instance of Option class. However, once we
|
||||
// implement CustomOption class we may want to return factory function
|
||||
// that will create instance of CustomOption rather than Option.
|
||||
// CustomOption will allow to access particular data fields within the
|
||||
// option rather than raw data buffer.
|
||||
return (factoryGeneric);
|
||||
return (optionFactory(u, type, buf.begin(), buf.end()));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -153,28 +176,78 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
|
||||
|
||||
void
|
||||
OptionDefinition::validate() const {
|
||||
// Option name must not be empty.
|
||||
std::ostringstream err_str;
|
||||
if (name_.empty()) {
|
||||
isc_throw(isc::BadValue, "option name must not be empty");
|
||||
// Option name must not be empty.
|
||||
err_str << "option name must not be empty.";
|
||||
} else if (name_.find(" ") != string::npos) {
|
||||
// Option name must not contain spaces.
|
||||
err_str << "option name must not contain spaces.";
|
||||
} else if (type_ >= OPT_UNKNOWN_TYPE) {
|
||||
// Option definition must be of a known type.
|
||||
err_str << "option type value " << type_ << " is out of range.";
|
||||
} else if (array_type_) {
|
||||
if (type_ == OPT_STRING_TYPE) {
|
||||
// Array of strings is not allowed because there is no way
|
||||
// to determine the size of a particular string and thus there
|
||||
// it no way to tell when other data fields begin.
|
||||
err_str << "array of strings is not a valid option definition.";
|
||||
} else if (type_ == OPT_BINARY_TYPE) {
|
||||
err_str << "array of binary values is not a valid option definition.";
|
||||
} else if (type_ == OPT_EMPTY_TYPE) {
|
||||
err_str << "array of empty value is not a valid option definition.";
|
||||
}
|
||||
} else if (type_ == OPT_RECORD_TYPE) {
|
||||
// At least two data fields should be added to the record. Otherwise
|
||||
// non-record option definition could be used.
|
||||
if (getRecordFields().size() < 2) {
|
||||
err_str << "invalid number of data fields: " << getRecordFields().size()
|
||||
<< " specified for the option of type 'record'. Expected at"
|
||||
<< " least 2 fields.";
|
||||
} else {
|
||||
// If the number of fields is valid we have to check if their order
|
||||
// is valid too. We check that string or binary data fields are not
|
||||
// laid before other fields. But we allow that they are laid at the end of
|
||||
// an option.
|
||||
const RecordFieldsCollection& fields = getRecordFields();
|
||||
for (RecordFieldsConstIter it = fields.begin();
|
||||
it != fields.end(); ++it) {
|
||||
if (*it == OPT_STRING_TYPE &&
|
||||
it < fields.end() - 1) {
|
||||
err_str << "string data field can't be laid before data fields"
|
||||
<< " of other types.";
|
||||
break;
|
||||
}
|
||||
if (*it == OPT_BINARY_TYPE &&
|
||||
it < fields.end() - 1) {
|
||||
err_str << "binary data field can't be laid before data fields"
|
||||
<< " of other types.";
|
||||
}
|
||||
/// Empty type is not allowed within a record.
|
||||
if (*it == OPT_EMPTY_TYPE) {
|
||||
err_str << "empty data type can't be stored as a field in an"
|
||||
<< " option record.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Option name must not contain spaces.
|
||||
if (name_.find(" ") != string::npos) {
|
||||
isc_throw(isc::BadValue, "option name must not contain spaces");
|
||||
}
|
||||
// Unsupported option types are not allowed.
|
||||
if (type_ >= UNKNOWN_TYPE) {
|
||||
isc_throw(isc::OutOfRange, "option type value " << type_
|
||||
<< " is out of range");
|
||||
|
||||
// Non-empty error string means that we have hit the error. We throw
|
||||
// exception and include error string.
|
||||
if (!err_str.str().empty()) {
|
||||
isc_throw(MalformedOptionDefinition, err_str.str());
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
OptionDefinition::haveIAx6Format(OptionDefinition::DataType first_type) const {
|
||||
return (haveType(RECORD_TYPE) &&
|
||||
OptionDefinition::haveIAx6Format(OptionDataType first_type) const {
|
||||
return (haveType(OPT_RECORD_TYPE) &&
|
||||
record_fields_.size() == 3 &&
|
||||
record_fields_[0] == first_type &&
|
||||
record_fields_[1] == UINT32_TYPE &&
|
||||
record_fields_[2] == UINT32_TYPE);
|
||||
record_fields_[1] == OPT_UINT32_TYPE &&
|
||||
record_fields_[2] == OPT_UINT32_TYPE);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -185,70 +258,184 @@ OptionDefinition::haveIA6Format() const {
|
||||
// arrays do not impose limitations on number of elements in
|
||||
// the array while this limitation is needed for IA_NA - need
|
||||
// exactly 3 elements.
|
||||
return (haveIAx6Format(UINT32_TYPE));
|
||||
return (haveIAx6Format(OPT_UINT32_TYPE));
|
||||
}
|
||||
|
||||
bool
|
||||
OptionDefinition::haveIAAddr6Format() const {
|
||||
return (haveIAx6Format(IPV6_ADDRESS_TYPE));
|
||||
return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryAddrList4(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf) {
|
||||
sanityCheckUniverse(u, Option::V4);
|
||||
boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, buf.begin(),
|
||||
buf.begin() + buf.size()));
|
||||
return (option);
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryAddrList6(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf) {
|
||||
sanityCheckUniverse(u, Option::V6);
|
||||
boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, buf.begin(),
|
||||
buf.begin() + buf.size()));
|
||||
return (option);
|
||||
}
|
||||
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
|
||||
if (buf.size() > 0) {
|
||||
isc_throw(isc::BadValue, "input option buffer must be empty"
|
||||
" when creating empty option instance");
|
||||
template<typename T>
|
||||
T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
|
||||
// Lexical cast in case of our data types make sense only
|
||||
// for uintX_t, intX_t and bool type.
|
||||
if (!OptionDataTypeTraits<T>::integer_type &&
|
||||
OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
|
||||
isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
|
||||
<< " non-boolean data type");
|
||||
}
|
||||
// We use the 64-bit value here because it has wider range than
|
||||
// any other type we use here and it allows to detect out of
|
||||
// bounds conditions e.g. negative value specified for uintX_t
|
||||
// data type. Obviously if the value exceeds the limits of int64
|
||||
// this function will not handle that properly.
|
||||
int64_t result = 0;
|
||||
try {
|
||||
result = boost::lexical_cast<int64_t>(value_str);
|
||||
} catch (const boost::bad_lexical_cast& ex) {
|
||||
// Prepare error message here.
|
||||
std::string data_type_str = "boolean";
|
||||
if (OptionDataTypeTraits<T>::integer_type) {
|
||||
data_type_str = "integer";
|
||||
}
|
||||
isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
|
||||
<< " data type for value " << value_str << ": " << ex.what());
|
||||
}
|
||||
// Perform range checks for integer values only (exclude bool values).
|
||||
if (OptionDataTypeTraits<T>::integer_type) {
|
||||
if (result > numeric_limits<T>::max() ||
|
||||
result < numeric_limits<T>::min()) {
|
||||
isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
|
||||
<< value_str << ". This value is expected to be in the range of "
|
||||
<< numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
|
||||
}
|
||||
}
|
||||
return (static_cast<T>(result));
|
||||
}
|
||||
|
||||
void
|
||||
OptionDefinition::writeToBuffer(const std::string& value,
|
||||
const OptionDataType type,
|
||||
OptionBuffer& buf) const {
|
||||
// We are going to write value given by value argument to the buffer.
|
||||
// The actual type of the value is given by second argument. Check
|
||||
// this argument to determine how to write this value to the buffer.
|
||||
switch (type) {
|
||||
case OPT_BINARY_TYPE:
|
||||
OptionDataTypeUtil::writeBinary(value, buf);
|
||||
return;
|
||||
case OPT_BOOLEAN_TYPE:
|
||||
// We encode the true value as 1 and false as 0 on 8 bits.
|
||||
// That way we actually waste 7 bits but it seems to be the
|
||||
// simpler way to encode boolean.
|
||||
// @todo Consider if any other encode methods can be used.
|
||||
OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
|
||||
return;
|
||||
case OPT_INT8_TYPE:
|
||||
OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
|
||||
buf);
|
||||
return;
|
||||
case OPT_INT16_TYPE:
|
||||
OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
|
||||
buf);
|
||||
return;
|
||||
case OPT_INT32_TYPE:
|
||||
OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
|
||||
buf);
|
||||
return;
|
||||
case OPT_UINT8_TYPE:
|
||||
OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
|
||||
buf);
|
||||
return;
|
||||
case OPT_UINT16_TYPE:
|
||||
OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
|
||||
buf);
|
||||
return;
|
||||
case OPT_UINT32_TYPE:
|
||||
OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
|
||||
buf);
|
||||
return;
|
||||
case OPT_IPV4_ADDRESS_TYPE:
|
||||
case OPT_IPV6_ADDRESS_TYPE:
|
||||
{
|
||||
asiolink::IOAddress address(value);
|
||||
if (address.getFamily() != AF_INET &&
|
||||
address.getFamily() != AF_INET6) {
|
||||
isc_throw(BadDataTypeCast, "provided address " << address.toText()
|
||||
<< " is not a valid "
|
||||
<< (address.getAddress().is_v4() ? "IPv4" : "IPv6")
|
||||
<< " address");
|
||||
}
|
||||
OptionDataTypeUtil::writeAddress(address, buf);
|
||||
return;
|
||||
}
|
||||
case OPT_STRING_TYPE:
|
||||
OptionDataTypeUtil::writeString(value, buf);
|
||||
return;
|
||||
case OPT_FQDN_TYPE:
|
||||
{
|
||||
// FQDN implementation is not terribly complicated but will require
|
||||
// creation of some additional logic (maybe object) that will parse
|
||||
// the fqdn into labels.
|
||||
isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
|
||||
" is not supported yet");
|
||||
return;
|
||||
}
|
||||
default:
|
||||
// We hit this point because invalid option data type has been specified
|
||||
// This may be the case because 'empty' or 'record' data type has been
|
||||
// specified. We don't throw exception here because it will be thrown
|
||||
// at the exit point from this function.
|
||||
;
|
||||
}
|
||||
isc_throw(isc::BadValue, "attempt to write invalid option data field type"
|
||||
" into the option buffer: " << type);
|
||||
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryAddrList4(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryAddrList6(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type) {
|
||||
OptionPtr option(new Option(u, type));
|
||||
return (option);
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
|
||||
OptionPtr option(new Option(u, type, buf));
|
||||
OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
OptionPtr option(new Option(u, type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryIA6(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
|
||||
sanityCheckUniverse(u, Option::V6);
|
||||
if (buf.size() != Option6IA::OPTION6_IA_LEN) {
|
||||
isc_throw(isc::OutOfRange, "input option buffer has invalid size, expeted "
|
||||
<< Option6IA::OPTION6_IA_LEN << " bytes");
|
||||
OptionDefinition::factoryIA6(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) {
|
||||
isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
|
||||
"at least " << Option6IA::OPTION6_IA_LEN << " bytes");
|
||||
}
|
||||
boost::shared_ptr<Option6IA> option(new Option6IA(type, buf.begin(),
|
||||
buf.begin() + buf.size()));
|
||||
boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
OptionPtr
|
||||
OptionDefinition::factoryIAAddr6(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
|
||||
sanityCheckUniverse(u, Option::V6);
|
||||
if (buf.size() != Option6IAAddr::OPTION6_IAADDR_LEN) {
|
||||
isc_throw(isc::OutOfRange, "input option buffer has invalid size, expeted "
|
||||
<< Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
|
||||
OptionDefinition::factoryIAAddr6(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) {
|
||||
isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
|
||||
" at least " << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
|
||||
}
|
||||
boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, buf.begin(),
|
||||
buf.begin() + buf.size()));
|
||||
boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,21 @@
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief Exception to be thrown when invalid option value has been
|
||||
/// specified for a particular option definition.
|
||||
class InvalidOptionValue : public Exception {
|
||||
public:
|
||||
InvalidOptionValue(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) { };
|
||||
};
|
||||
|
||||
/// @brief Exception to be thrown when option definition is invalid.
|
||||
class MalformedOptionDefinition : public Exception {
|
||||
public:
|
||||
MalformedOptionDefinition(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) { };
|
||||
};
|
||||
|
||||
/// @brief Forward declaration to OptionDefinition.
|
||||
class OptionDefinition;
|
||||
|
||||
@@ -82,7 +97,7 @@ class Option6IntArray;
|
||||
///
|
||||
/// Should the option comprise data fields of different types, the "record"
|
||||
/// option type is used. In such cases the data field types within the record
|
||||
/// are specified using \ref OptioDefinition::addRecordField.
|
||||
/// are specified using \ref OptionDefinition::addRecordField.
|
||||
///
|
||||
/// When the OptionDefinition object has been sucessfully created, it can be
|
||||
/// queried to return the appropriate option factory function for the specified
|
||||
@@ -111,73 +126,11 @@ class Option6IntArray;
|
||||
class OptionDefinition {
|
||||
public:
|
||||
|
||||
/// Data types of DHCP option fields.
|
||||
enum DataType {
|
||||
EMPTY_TYPE,
|
||||
BINARY_TYPE,
|
||||
BOOLEAN_TYPE,
|
||||
INT8_TYPE,
|
||||
INT16_TYPE,
|
||||
INT32_TYPE,
|
||||
UINT8_TYPE,
|
||||
UINT16_TYPE,
|
||||
UINT32_TYPE,
|
||||
IPV4_ADDRESS_TYPE,
|
||||
IPV6_ADDRESS_TYPE,
|
||||
STRING_TYPE,
|
||||
FQDN_TYPE,
|
||||
RECORD_TYPE,
|
||||
UNKNOWN_TYPE
|
||||
};
|
||||
|
||||
/// List of fields within the record.
|
||||
typedef std::vector<DataType> RecordFieldsCollection;
|
||||
typedef std::vector<OptionDataType> RecordFieldsCollection;
|
||||
/// Const iterator for record data fields.
|
||||
typedef std::vector<DataType>::const_iterator RecordFieldsConstIter;
|
||||
typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter;
|
||||
|
||||
private:
|
||||
|
||||
/// @brief Utility class for operations on DataTypes.
|
||||
///
|
||||
/// This class is implemented as the singleton because the list of
|
||||
/// supported data types need only be loaded only once into memory as it
|
||||
/// can persist for all option definitions.
|
||||
///
|
||||
/// @todo This class can be extended to return the string value
|
||||
/// representing the data type from the enum value.
|
||||
class DataTypeUtil {
|
||||
public:
|
||||
|
||||
/// @brief Return the sole instance of this class.
|
||||
///
|
||||
/// @return instance of this class.
|
||||
static DataTypeUtil& instance() {
|
||||
static DataTypeUtil instance;
|
||||
return (instance);
|
||||
}
|
||||
|
||||
/// @brief Convert type given as string value to option data type.
|
||||
///
|
||||
/// @param data_type_name data type string.
|
||||
///
|
||||
/// @return option data type.
|
||||
DataType getDataType(const std::string& data_type_name);
|
||||
|
||||
private:
|
||||
/// @brief Private constructor.
|
||||
///
|
||||
/// Constructor initializes the internal data structures, e.g.
|
||||
/// mapping between data type name and the corresponding enum.
|
||||
/// This constructor is private to ensure that exactly one
|
||||
/// instance of this class can be created using \ref instance
|
||||
/// function.
|
||||
DataTypeUtil();
|
||||
|
||||
/// Map of data types, maps name of the type to enum value.
|
||||
std::map<std::string, DataType> data_types_;
|
||||
};
|
||||
|
||||
public:
|
||||
/// @brief Constructor.
|
||||
///
|
||||
/// @param name option name.
|
||||
@@ -199,7 +152,7 @@ public:
|
||||
/// option fields are the array.
|
||||
OptionDefinition(const std::string& name,
|
||||
const uint16_t code,
|
||||
const DataType type,
|
||||
const OptionDataType type,
|
||||
const bool array_type = false);
|
||||
|
||||
/// @brief Adds data field to the record.
|
||||
@@ -216,7 +169,7 @@ public:
|
||||
///
|
||||
/// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE.
|
||||
/// @throw isc::BadValue if specified invalid data type.
|
||||
void addRecordField(const DataType data_type);
|
||||
void addRecordField(const OptionDataType data_type);
|
||||
|
||||
/// @brief Return array type indicator.
|
||||
///
|
||||
@@ -231,13 +184,6 @@ public:
|
||||
/// @return option code.
|
||||
uint16_t getCode() const { return (code_); }
|
||||
|
||||
/// @brief Return factory function for the given definition.
|
||||
///
|
||||
/// @throw isc::OutOfRange if \ref validate returns it.
|
||||
/// @throw isc::BadValue if \ref validate returns it.
|
||||
/// @return pointer to a factory function.
|
||||
Option::Factory* getFactory() const;
|
||||
|
||||
/// @brief Return option name.
|
||||
///
|
||||
/// @return option name.
|
||||
@@ -251,13 +197,11 @@ public:
|
||||
/// @brief Return option data type.
|
||||
///
|
||||
/// @return option data type.
|
||||
DataType getType() const { return (type_); };
|
||||
OptionDataType getType() const { return (type_); };
|
||||
|
||||
/// @brief Check if the option definition is valid.
|
||||
///
|
||||
/// @throw isc::OutOfRange if invalid option type was specified.
|
||||
/// @throw isc::BadValue if invalid option name was specified,
|
||||
/// e.g. empty or containing spaces.
|
||||
/// @throw MalformedOptionDefinition option definition is invalid.
|
||||
void validate() const;
|
||||
|
||||
/// @brief Check if specified format is IA_NA option format.
|
||||
@@ -270,101 +214,163 @@ public:
|
||||
/// @return true if specified format is IAADDR option format.
|
||||
bool haveIAAddr6Format() const;
|
||||
|
||||
/// @brief Factory to create option with address list.
|
||||
/// @brief Option factory.
|
||||
///
|
||||
/// @param u universe (must be V4).
|
||||
/// This function creates an instance of DHCP option using
|
||||
/// provided chunk of buffer. This function may be used to
|
||||
/// create option which is to be sent in the outgoing packet.
|
||||
///
|
||||
/// @param u option universe (V4 or V6).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer with a list of IPv4 addresses.
|
||||
/// @param begin beginning of the option buffer.
|
||||
/// @param end end of the option buffer.
|
||||
///
|
||||
/// @throw isc::OutOfRange if length of the provided option buffer
|
||||
/// is not multiple of IPV4 address length.
|
||||
static OptionPtr factoryAddrList4(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf);
|
||||
/// @return instance of the DHCP option.
|
||||
/// @throw MalformedOptionDefinition if option definition is invalid.
|
||||
/// @throw InvalidOptionValue if data for the option is invalid.
|
||||
OptionPtr optionFactory(Option::Universe u, uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) const;
|
||||
|
||||
/// @brief Option factory.
|
||||
///
|
||||
/// This function creates an instance of DHCP option using
|
||||
/// whole provided buffer. This function may be used to
|
||||
/// create option which is to be sent in the outgoing packet.
|
||||
///
|
||||
/// @param u option universe (V4 or V6).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer.
|
||||
///
|
||||
/// @return instance of the DHCP option.
|
||||
/// @throw MalformedOptionDefinition if option definition is invalid.
|
||||
/// @throw InvalidOptionValue if data for the option is invalid.
|
||||
OptionPtr optionFactory(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf) const;
|
||||
|
||||
/// @brief Option factory.
|
||||
///
|
||||
/// This function creates an instance of DHCP option using the vector
|
||||
/// of strings which carry data values for option data fields.
|
||||
/// The order of values in the vector corresponds to the order of data
|
||||
/// fields in the option. The supplied string values are cast to
|
||||
/// their actual data types which are determined based on the
|
||||
/// option definition. If cast fails due to type mismatch, an exception
|
||||
/// is thrown. This factory function can be used to create option
|
||||
/// instance when user specified option value in the <b>comma separated
|
||||
/// values</b> format in the configuration database. Provided string
|
||||
/// must be tokenized into the vector of string values and this vector
|
||||
/// can be supplied to this function.
|
||||
///
|
||||
/// @param u option universe (V4 or V6).
|
||||
/// @param type option type.
|
||||
/// @param values a vector of values to be used to set data for an option.
|
||||
///
|
||||
/// @return instance of the DHCP option.
|
||||
/// @throw MalformedOptionDefinition if option definition is invalid.
|
||||
/// @throw InvalidOptionValue if data for the option is invalid.
|
||||
OptionPtr optionFactory(Option::Universe u, uint16_t type,
|
||||
const std::vector<std::string>& values) const;
|
||||
|
||||
/// @brief Factory to create option with address list.
|
||||
///
|
||||
/// @param u universe (must be V6).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer with a list of IPv6 addresses.
|
||||
/// @param begin iterator pointing to the beginning of the buffer
|
||||
/// with a list of IPv4 addresses.
|
||||
/// @param end iterator pointing to the end of the buffer with
|
||||
/// a list of IPv4 addresses.
|
||||
///
|
||||
/// @throw isc::OutOfRange if length of the provided option buffer
|
||||
/// is not multiple of IPV4 address length.
|
||||
static OptionPtr factoryAddrList4(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end);
|
||||
|
||||
/// @brief Factory to create option with address list.
|
||||
///
|
||||
/// @param type option type.
|
||||
/// @param begin iterator pointing to the beginning of the buffer
|
||||
/// with a list of IPv6 addresses.
|
||||
/// @param end iterator pointing to the end of the buffer with
|
||||
/// a list of IPv6 addresses.
|
||||
///
|
||||
/// @throw isc::OutOfaRange if length of provided option buffer
|
||||
/// is not multiple of IPV6 address length.
|
||||
static OptionPtr factoryAddrList6(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf);
|
||||
static OptionPtr factoryAddrList6(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end);
|
||||
|
||||
/// @brief Empty option factory.
|
||||
///
|
||||
/// @param u universe (V6 or V4).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer (must be empty).
|
||||
static OptionPtr factoryEmpty(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf);
|
||||
static OptionPtr factoryEmpty(Option::Universe u, uint16_t type);
|
||||
|
||||
/// @brief Factory to create generic option.
|
||||
///
|
||||
/// @param u universe (V6 or V4).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer.
|
||||
/// @param begin iterator pointing to the beginning of the buffer.
|
||||
/// @param end iterator pointing to the end of the buffer.
|
||||
static OptionPtr factoryGeneric(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf);
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end);
|
||||
|
||||
/// @brief Factory for IA-type of option.
|
||||
///
|
||||
/// @param u universe (must be V6).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer.
|
||||
/// @param begin iterator pointing to the beginning of the buffer.
|
||||
/// @param end iterator pointing to the end of the buffer.
|
||||
///
|
||||
/// @throw isc::OutOfRange if provided option buffer is too short or
|
||||
/// too long. Expected size is 12 bytes.
|
||||
/// @throw isc::BadValue if specified universe value is not V6.
|
||||
static OptionPtr factoryIA6(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf);
|
||||
static OptionPtr factoryIA6(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end);
|
||||
|
||||
/// @brief Factory for IAADDR-type of option.
|
||||
///
|
||||
/// @param u universe (must be V6).
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer.
|
||||
/// @param begin iterator pointing to the beginning of the buffer.
|
||||
/// @param end iterator pointing to the end of the buffer.
|
||||
///
|
||||
/// @throw isc::OutOfRange if provided option buffer is too short or
|
||||
/// too long. Expected size is 24 bytes.
|
||||
/// @throw isc::BadValue if specified universe value is not V6.
|
||||
static OptionPtr factoryIAAddr6(Option::Universe u, uint16_t type,
|
||||
const OptionBuffer& buf);
|
||||
static OptionPtr factoryIAAddr6(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end);
|
||||
|
||||
/// @brief Factory function to create option with integer value.
|
||||
///
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer.
|
||||
/// @param begin iterator pointing to the beginning of the buffer.
|
||||
/// @param end iterator pointing to the end of the buffer.
|
||||
/// @tparam T type of the data field (must be one of the uintX_t or intX_t).
|
||||
///
|
||||
/// @throw isc::OutOfRange if provided option buffer length is invalid.
|
||||
template<typename T>
|
||||
static OptionPtr factoryInteger(Option::Universe, uint16_t type, const OptionBuffer& buf) {
|
||||
if (buf.size() > sizeof(T)) {
|
||||
isc_throw(isc::OutOfRange, "provided option buffer is too large, expected: "
|
||||
<< sizeof(T) << " bytes");
|
||||
}
|
||||
OptionPtr option(new Option6Int<T>(type, buf.begin(), buf.end()));
|
||||
static OptionPtr factoryInteger(Option::Universe, uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
OptionPtr option(new Option6Int<T>(type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
/// @brief Factory function to create option with array of integer values.
|
||||
///
|
||||
/// @param type option type.
|
||||
/// @param buf option buffer.
|
||||
/// @param begin iterator pointing to the beginning of the buffer.
|
||||
/// @param end iterator pointing to the end of the buffer.
|
||||
/// @tparam T type of the data field (must be one of the uintX_t or intX_t).
|
||||
///
|
||||
/// @throw isc::OutOfRange if provided option buffer length is invalid.
|
||||
template<typename T>
|
||||
static OptionPtr factoryIntegerArray(Option::Universe, uint16_t type, const OptionBuffer& buf) {
|
||||
if (buf.size() == 0) {
|
||||
isc_throw(isc::OutOfRange, "option buffer length must be greater than zero");
|
||||
} else if (buf.size() % OptionDataTypes<T>::len != 0) {
|
||||
isc_throw(isc::OutOfRange, "option buffer length must be multiple of "
|
||||
<< OptionDataTypes<T>::len << " bytes");
|
||||
}
|
||||
OptionPtr option(new Option6IntArray<T>(type, buf.begin(), buf.end()));
|
||||
static OptionPtr factoryIntegerArray(uint16_t type,
|
||||
OptionBufferConstIter begin,
|
||||
OptionBufferConstIter end) {
|
||||
OptionPtr option(new Option6IntArray<T>(type, begin, end));
|
||||
return (option);
|
||||
}
|
||||
|
||||
@@ -379,15 +385,49 @@ private:
|
||||
/// @param first_type type of the first data field.
|
||||
///
|
||||
/// @return true if actual option format matches expected format.
|
||||
bool haveIAx6Format(const OptionDefinition::DataType first_type) const;
|
||||
bool haveIAx6Format(const OptionDataType first_type) const;
|
||||
|
||||
/// @brief Check if specified type matches option definition type.
|
||||
///
|
||||
/// @return true if specified type matches option definition type.
|
||||
inline bool haveType(const DataType type) const {
|
||||
inline bool haveType(const OptionDataType type) const {
|
||||
return (type == type_);
|
||||
}
|
||||
|
||||
/// @brief Perform lexical cast of the value and validate its range.
|
||||
///
|
||||
/// This function performs lexical cast of a string value to integer
|
||||
/// or boolean value and checks if the resulting value is within a
|
||||
/// range of a target type. Note that range checks are not performed
|
||||
/// on boolean values. The target type should be one of the supported
|
||||
/// integer types or bool.
|
||||
///
|
||||
/// @param value_str input value given as string.
|
||||
/// @tparam T target type for lexical cast.
|
||||
///
|
||||
/// @return cast value.
|
||||
/// @throw BadDataTypeCast if cast was not successful.
|
||||
template<typename T>
|
||||
T lexicalCastWithRangeCheck(const std::string& value_str) const;
|
||||
|
||||
/// @brief Write the string value into the provided buffer.
|
||||
///
|
||||
/// This method writes the given value to the specified buffer.
|
||||
/// The provided string value may represent data of different types.
|
||||
/// The actual data type is specified with the second argument.
|
||||
/// Based on a value of this argument, this function will first
|
||||
/// try to cast the string value to the particular data type and
|
||||
/// if it is successful it will store the data in the buffer
|
||||
/// in a binary format.
|
||||
///
|
||||
/// @param value string representation of the value to be written.
|
||||
/// @param type the actual data type to be stored.
|
||||
/// @param [in, out] buf buffer where the value is to be stored.
|
||||
///
|
||||
/// @throw BadDataTypeCast if data write was unsuccessful.
|
||||
void writeToBuffer(const std::string& value, const OptionDataType type,
|
||||
OptionBuffer& buf) const;
|
||||
|
||||
/// @brief Sanity check universe value.
|
||||
///
|
||||
/// @param expected_universe expected universe value.
|
||||
@@ -395,14 +435,14 @@ private:
|
||||
///
|
||||
/// @throw isc::BadValue if expected universe and actual universe don't match.
|
||||
static inline void sanityCheckUniverse(const Option::Universe expected_universe,
|
||||
const Option::Universe actual_universe);
|
||||
const Option::Universe actual_universe);
|
||||
|
||||
/// Option name.
|
||||
std::string name_;
|
||||
/// Option code.
|
||||
uint16_t code_;
|
||||
/// Option data type.
|
||||
DataType type_;
|
||||
OptionDataType type_;
|
||||
/// Indicates wheter option is a single value or array.
|
||||
bool array_type_;
|
||||
/// Collection of data fields within the record.
|
||||
@@ -421,7 +461,7 @@ private:
|
||||
/// Note that this container can hold multiple options with the
|
||||
/// same code. For this reason, the latter index can be used to
|
||||
/// obtain a range of options for a particular option code.
|
||||
///
|
||||
///
|
||||
/// @todo: need an index to search options using option space name
|
||||
/// once option spaces are implemented.
|
||||
typedef boost::multi_index_container<
|
||||
|
@@ -36,6 +36,7 @@ libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option6_int_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_definition_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_custom_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt4_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt6_unittest.cc
|
||||
|
@@ -39,8 +39,7 @@ using namespace isc::util;
|
||||
namespace {
|
||||
class LibDhcpTest : public ::testing::Test {
|
||||
public:
|
||||
LibDhcpTest() {
|
||||
}
|
||||
LibDhcpTest() { }
|
||||
|
||||
/// @brief Generic factory function to create any option.
|
||||
///
|
||||
@@ -64,14 +63,13 @@ public:
|
||||
/// @param bug buffer to be used to create option instance.
|
||||
/// @param expected_type type of the option created by the
|
||||
/// factory function returned by the option definition.
|
||||
static void testInitOptionDefs6(const uint16_t code,
|
||||
static void testStdOptionDefs6(const uint16_t code,
|
||||
const OptionBuffer& buf,
|
||||
const std::type_info& expected_type) {
|
||||
// Initialize stdandard options definitions. They are held
|
||||
// in the static container throughout the program.
|
||||
LibDHCP::initStdOptionDefs(Option::V6);
|
||||
// Get all option definitions, we will use them to extract
|
||||
// the definition for a particular option code.
|
||||
// We don't have to initialize option deinitions here because they
|
||||
// are initialized in the class'es constructor.
|
||||
OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
|
||||
// Get the container index #1. This one allows for searching
|
||||
// option definitions using option code.
|
||||
@@ -90,14 +88,9 @@ public:
|
||||
ASSERT_TRUE(def);
|
||||
// Check that option definition is valid.
|
||||
ASSERT_NO_THROW(def->validate());
|
||||
// Get the factory function for the particular option
|
||||
// definition. We will use this factory function to
|
||||
// create option instance.
|
||||
Option::Factory* factory = NULL;
|
||||
ASSERT_NO_THROW(factory = def->getFactory());
|
||||
OptionPtr option;
|
||||
// Create the option.
|
||||
ASSERT_NO_THROW(option = factory(Option::V6, code, buf));
|
||||
ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
|
||||
// Make sure it is not NULL.
|
||||
ASSERT_TRUE(option);
|
||||
// And the actual object type is the one that we expect.
|
||||
@@ -108,14 +101,14 @@ public:
|
||||
};
|
||||
|
||||
static const uint8_t packed[] = {
|
||||
0, 12, 0, 5, 100, 101, 102, 103, 104, // opt1 (9 bytes)
|
||||
0, 13, 0, 3, 105, 106, 107, // opt2 (7 bytes)
|
||||
0, 14, 0, 2, 108, 109, // opt3 (6 bytes)
|
||||
1, 0, 0, 4, 110, 111, 112, 113, // opt4 (8 bytes)
|
||||
1, 1, 0, 1, 114 // opt5 (5 bytes)
|
||||
0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes)
|
||||
0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes)
|
||||
0, 14, 0, 0, // RAPID_COMMIT (0 bytes)
|
||||
0, 6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes)
|
||||
0, 8, 0, 2, 112, 113 // ELAPSED_TIME (6 bytes)
|
||||
};
|
||||
|
||||
TEST(LibDhcpTest, optionFactory) {
|
||||
TEST_F(LibDhcpTest, optionFactory) {
|
||||
OptionBuffer buf;
|
||||
// Factory functions for specific options must be registered before
|
||||
// they can be used to create options instances. Otherwise exception
|
||||
@@ -187,7 +180,7 @@ TEST(LibDhcpTest, optionFactory) {
|
||||
opt_clientid->getData().begin()));
|
||||
}
|
||||
|
||||
TEST(LibDhcpTest, packOptions6) {
|
||||
TEST_F(LibDhcpTest, packOptions6) {
|
||||
OptionBuffer buf(512);
|
||||
isc::dhcp::Option::OptionCollection opts; // list of options
|
||||
|
||||
@@ -196,11 +189,11 @@ TEST(LibDhcpTest, packOptions6) {
|
||||
buf[i]=i+100;
|
||||
}
|
||||
|
||||
OptionPtr opt1(new Option(Option::V6, 12, buf.begin() + 0, buf.begin() + 5));
|
||||
OptionPtr opt2(new Option(Option::V6, 13, buf.begin() + 5, buf.begin() + 8));
|
||||
OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 10));
|
||||
OptionPtr opt4(new Option(Option::V6,256, buf.begin() + 10,buf.begin() + 14));
|
||||
OptionPtr opt5(new Option(Option::V6,257, buf.begin() + 14,buf.begin() + 15));
|
||||
OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5));
|
||||
OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8));
|
||||
OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8));
|
||||
OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
|
||||
OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
|
||||
|
||||
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
|
||||
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
|
||||
@@ -211,11 +204,11 @@ TEST(LibDhcpTest, packOptions6) {
|
||||
OutputBuffer assembled(512);
|
||||
|
||||
EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
|
||||
EXPECT_EQ(35, assembled.getLength()); // options should take 35 bytes
|
||||
EXPECT_EQ(0, memcmp(assembled.getData(), packed, 35) );
|
||||
EXPECT_EQ(sizeof(packed), assembled.getLength());
|
||||
EXPECT_EQ(0, memcmp(assembled.getData(), packed, sizeof(packed)));
|
||||
}
|
||||
|
||||
TEST(LibDhcpTest, unpackOptions6) {
|
||||
TEST_F(LibDhcpTest, unpackOptions6) {
|
||||
|
||||
// just couple of random options
|
||||
// Option is used as a simple option implementation
|
||||
@@ -224,55 +217,85 @@ TEST(LibDhcpTest, unpackOptions6) {
|
||||
isc::dhcp::Option::OptionCollection options; // list of options
|
||||
|
||||
OptionBuffer buf(512);
|
||||
memcpy(&buf[0], packed, 35);
|
||||
memcpy(&buf[0], packed, sizeof(packed));
|
||||
|
||||
EXPECT_NO_THROW ({
|
||||
LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin()+35), options);
|
||||
LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(packed)),
|
||||
options);
|
||||
});
|
||||
|
||||
EXPECT_EQ(options.size(), 5); // there should be 5 options
|
||||
|
||||
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
|
||||
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(1);
|
||||
ASSERT_FALSE(x == options.end()); // option 1 should exist
|
||||
EXPECT_EQ(12, x->second->getType()); // this should be option 12
|
||||
EXPECT_EQ(1, x->second->getType()); // this should be option 1
|
||||
ASSERT_EQ(9, x->second->len()); // it should be of length 9
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+4, 5)); // data len=5
|
||||
ASSERT_EQ(5, x->second->getData().size());
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed + 4, 5)); // data len=5
|
||||
|
||||
x = options.find(13);
|
||||
ASSERT_FALSE(x == options.end()); // option 13 should exist
|
||||
EXPECT_EQ(13, x->second->getType()); // this should be option 13
|
||||
x = options.find(2);
|
||||
ASSERT_FALSE(x == options.end()); // option 2 should exist
|
||||
EXPECT_EQ(2, x->second->getType()); // this should be option 2
|
||||
ASSERT_EQ(7, x->second->len()); // it should be of length 7
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+13, 3)); // data len=3
|
||||
ASSERT_EQ(3, x->second->getData().size());
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed + 13, 3)); // data len=3
|
||||
|
||||
x = options.find(14);
|
||||
ASSERT_FALSE(x == options.end()); // option 3 should exist
|
||||
ASSERT_FALSE(x == options.end()); // option 14 should exist
|
||||
EXPECT_EQ(14, x->second->getType()); // this should be option 14
|
||||
ASSERT_EQ(6, x->second->len()); // it should be of length 6
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+20, 2)); // data len=2
|
||||
ASSERT_EQ(4, x->second->len()); // it should be of length 4
|
||||
EXPECT_EQ(0, x->second->getData().size()); // data len = 0
|
||||
|
||||
x = options.find(256);
|
||||
ASSERT_FALSE(x == options.end()); // option 256 should exist
|
||||
EXPECT_EQ(256, x->second->getType()); // this should be option 256
|
||||
ASSERT_EQ(8, x->second->len()); // it should be of length 7
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+26, 4)); // data len=4
|
||||
x = options.find(6);
|
||||
ASSERT_FALSE(x == options.end()); // option 6 should exist
|
||||
EXPECT_EQ(6, x->second->getType()); // this should be option 6
|
||||
ASSERT_EQ(8, x->second->len()); // it should be of length 8
|
||||
// Option with code 6 is the OPTION_ORO. This option is
|
||||
// represented by the Option6IntArray<uint16_t> class which
|
||||
// comprises the set of uint16_t values. We need to cast the
|
||||
// returned pointer to this type to get values stored in it.
|
||||
boost::shared_ptr<Option6IntArray<uint16_t> > opt_oro =
|
||||
boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(x->second);
|
||||
// This value will be NULL if cast was unsuccessful. This is the case
|
||||
// when returned option has different type than expected.
|
||||
ASSERT_TRUE(opt_oro);
|
||||
// Get set of uint16_t values.
|
||||
std::vector<uint16_t> opts = opt_oro->getValues();
|
||||
// Prepare the refrence data.
|
||||
std::vector<uint16_t> expected_opts;
|
||||
expected_opts.push_back(0x6C6D); // equivalent to: 108, 109
|
||||
expected_opts.push_back(0x6E6F); // equivalent to 110, 111
|
||||
ASSERT_EQ(expected_opts.size(), opts.size());
|
||||
// Validated if option has been unpacked correctly.
|
||||
EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(),
|
||||
opts.begin()));
|
||||
|
||||
x = options.find(257);
|
||||
ASSERT_FALSE(x == options.end()); // option 257 should exist
|
||||
EXPECT_EQ(257, x->second->getType()); // this should be option 257
|
||||
ASSERT_EQ(5, x->second->len()); // it should be of length 5
|
||||
EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+34, 1)); // data len=1
|
||||
x = options.find(8);
|
||||
ASSERT_FALSE(x == options.end()); // option 8 should exist
|
||||
EXPECT_EQ(8, x->second->getType()); // this should be option 8
|
||||
ASSERT_EQ(6, x->second->len()); // it should be of length 9
|
||||
// Option with code 8 is OPTION_ELAPSED_TIME. This option is
|
||||
// represented by Option6Int<uint16_t> value that holds single
|
||||
// uint16_t value.
|
||||
boost::shared_ptr<Option6Int<uint16_t> > opt_elapsed_time =
|
||||
boost::dynamic_pointer_cast<Option6Int<uint16_t> >(x->second);
|
||||
// This value will be NULL if cast was unsuccessful. This is the case
|
||||
// when returned option has different type than expected.
|
||||
ASSERT_TRUE(opt_elapsed_time);
|
||||
// Returned value should be equivalent to two byte values: 112, 113
|
||||
EXPECT_EQ(0x7071, opt_elapsed_time->getValue());
|
||||
|
||||
x = options.find(0);
|
||||
EXPECT_TRUE(x == options.end()); // option 0 not found
|
||||
|
||||
x = options.find(1); // 1 is htons(256) on little endians. Worth checking
|
||||
x = options.find(256); // 256 is htons(1) on little endians. Worth checking
|
||||
EXPECT_TRUE(x == options.end()); // option 1 not found
|
||||
|
||||
x = options.find(2);
|
||||
x = options.find(7);
|
||||
EXPECT_TRUE(x == options.end()); // option 2 not found
|
||||
|
||||
x = options.find(32000);
|
||||
EXPECT_TRUE(x == options.end()); // option 32000 not found
|
||||
EXPECT_TRUE(x == options.end()); // option 32000 not found */
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +307,7 @@ static uint8_t v4Opts[] = {
|
||||
128, 3, 40, 41, 42
|
||||
};
|
||||
|
||||
TEST(LibDhcpTest, packOptions4) {
|
||||
TEST_F(LibDhcpTest, packOptions4) {
|
||||
|
||||
vector<uint8_t> payload[5];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
@@ -316,7 +339,7 @@ TEST(LibDhcpTest, packOptions4) {
|
||||
|
||||
}
|
||||
|
||||
TEST(LibDhcpTest, unpackOptions4) {
|
||||
TEST_F(LibDhcpTest, unpackOptions4) {
|
||||
|
||||
vector<uint8_t> packed(v4Opts, v4Opts + sizeof(v4Opts));
|
||||
isc::dhcp::Option::OptionCollection options; // list of options
|
||||
@@ -375,24 +398,24 @@ TEST(LibDhcpTest, unpackOptions4) {
|
||||
// @todo Only limited number of option definitions are now created
|
||||
// This test have to be extended once all option definitions are
|
||||
// created.
|
||||
TEST(LibDhcpTest, initStdOptionDefs) {
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
|
||||
TEST_F(LibDhcpTest, stdOptionDefs6) {
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
|
||||
typeid(Option));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
|
||||
typeid(Option));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
|
||||
typeid(Option6IA));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
|
||||
typeid(Option6IAAddr));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
|
||||
typeid(Option6IntArray<uint16_t>));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
|
||||
typeid(Option6Int<uint16_t>));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
|
||||
typeid(Option));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
|
||||
typeid(Option));
|
||||
LibDhcpTest::testInitOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
|
||||
LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
|
||||
typeid(Option6AddrLst));
|
||||
}
|
||||
|
||||
|
@@ -206,7 +206,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
|
||||
|
||||
Option6IA* ia = 0;
|
||||
EXPECT_NO_THROW({
|
||||
ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
|
||||
ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
|
||||
});
|
||||
ASSERT_TRUE(ia);
|
||||
|
||||
|
906
src/lib/dhcp/tests/option_custom_unittest.cc
Normal file
906
src/lib/dhcp/tests/option_custom_unittest.cc
Normal file
@@ -0,0 +1,906 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/option_custom.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief OptionCustomTest test class.
|
||||
class OptionCustomTest : public ::testing::Test {
|
||||
public:
|
||||
/// @brief Constructor.
|
||||
OptionCustomTest() { }
|
||||
|
||||
/// @brief Write IP address into a buffer.
|
||||
///
|
||||
/// @param address address to be written.
|
||||
/// @param [out] buf output buffer.
|
||||
void writeAddress(const asiolink::IOAddress& address,
|
||||
std::vector<uint8_t>& buf) {
|
||||
short family = address.getFamily();
|
||||
if (family == AF_INET) {
|
||||
asio::ip::address_v4::bytes_type buf_addr =
|
||||
address.getAddress().to_v4().to_bytes();
|
||||
buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
|
||||
} else if (family == AF_INET6) {
|
||||
asio::ip::address_v6::bytes_type buf_addr =
|
||||
address.getAddress().to_v6().to_bytes();
|
||||
buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Write integer (signed or unsigned) into a buffer.
|
||||
///
|
||||
/// @param value integer value.
|
||||
/// @param [out] buf output buffer.
|
||||
/// @tparam integer type.
|
||||
template<typename T>
|
||||
void writeInt(T value, std::vector<uint8_t>& buf) {
|
||||
for (int i = 0; i < sizeof(T); ++i) {
|
||||
buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Write a string into a buffer.
|
||||
///
|
||||
/// @param value string to be written into a buffer.
|
||||
/// @param buf output buffer.
|
||||
void writeString(const std::string& value,
|
||||
std::vector<uint8_t>& buf) {
|
||||
buf.resize(buf.size() + value.size());
|
||||
std::copy_backward(value.c_str(), value.c_str() + value.size(),
|
||||
buf.end());
|
||||
}
|
||||
};
|
||||
|
||||
// The purpose of this test is to check that parameters passed to
|
||||
// a custom option's constructor are used to initialize class
|
||||
// members.
|
||||
TEST_F(OptionCustomTest, constructor) {
|
||||
// Create option definition for a DHCPv6 option.
|
||||
OptionDefinition opt_def1("OPTION_FOO", 1000, "boolean", true);
|
||||
|
||||
// Initialize some dummy buffer that holds single boolean value.
|
||||
OptionBuffer buf;
|
||||
buf.push_back(1);
|
||||
|
||||
// Create DHCPv6 option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def1, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// Check if constructor initialized the universe and type correctly.
|
||||
EXPECT_EQ(Option::V6, option->getUniverse());
|
||||
EXPECT_EQ(1000, option->getType());
|
||||
|
||||
// Do another round of testing for DHCPv4 option.
|
||||
OptionDefinition opt_def2("OPTION_FOO", 232, "boolean");
|
||||
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
EXPECT_EQ(Option::V4, option->getUniverse());
|
||||
EXPECT_EQ(232, option->getType());
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that 'empty' option definition can
|
||||
// be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, emptyData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 232, "empty");
|
||||
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// Option is 'empty' so no data fields are expected.
|
||||
EXPECT_EQ(0, option->getDataFieldsNum());
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// a binary value can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, binaryData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 231, "binary");
|
||||
|
||||
// Create a buffer holding some binary data. This data will be
|
||||
// used as reference when we read back the data from a created
|
||||
// option.
|
||||
OptionBuffer buf_in(14);
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
buf_in[i] = i;
|
||||
}
|
||||
// Use scoped pointer because it allows to declare the option
|
||||
// in the function scope and initialize it under ASSERT.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
// Custom option may throw exception if the provided buffer is
|
||||
// malformed.
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf_in));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
// The custom option should hold just one buffer that can be
|
||||
// accessed using index 0.
|
||||
OptionBuffer buf_out;
|
||||
ASSERT_NO_THROW(buf_out = option->readBinary(0));
|
||||
|
||||
// Read buffer must match exactly with the buffer used to
|
||||
// create option instance.
|
||||
ASSERT_EQ(buf_in.size(), buf_out.size());
|
||||
EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
|
||||
|
||||
// Check that option with "no data" is rejected.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that an option definition comprising
|
||||
// a single boolean value can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, booleanData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
|
||||
|
||||
OptionBuffer buf;
|
||||
// Push back the value that represents 'false'.
|
||||
buf.push_back(0);
|
||||
// Push back the 'true' value. Note that this value should
|
||||
// be ignored by custom option because it holds single boolean
|
||||
// value (according to option definition).
|
||||
buf.push_back(1);
|
||||
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
// Initialize the value to true because we want to make sure
|
||||
// that it is modified to 'false' by readBoolean below.
|
||||
bool value = true;
|
||||
|
||||
// Read the boolean value from only one available buffer indexed
|
||||
// with 0. It is expected to be 'false'.
|
||||
ASSERT_NO_THROW(value = option->readBoolean(0));
|
||||
EXPECT_FALSE(value);
|
||||
|
||||
// Check that the option with "no data" is rejected.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// 16-bit signed integer value can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, int16Data) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "int16");
|
||||
|
||||
OptionBuffer buf;
|
||||
// Store signed integer value in the input buffer.
|
||||
writeInt<int16_t>(-234, buf);
|
||||
|
||||
// Create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
// Initialize value to 0 explicitely to make sure that is
|
||||
// modified by readInteger function to expected -234.
|
||||
int16_t value = 0;
|
||||
ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
|
||||
EXPECT_EQ(-234, value);
|
||||
|
||||
// Check that the option is not created when a buffer is
|
||||
// too short (1 byte instead of 2 bytes).
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// 32-bit signed integer value can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, int32Data) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "int32");
|
||||
|
||||
OptionBuffer buf;
|
||||
writeInt<int32_t>(-234, buf);
|
||||
writeInt<int32_t>(100, buf);
|
||||
|
||||
// Create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
// Initialize value to 0 explicitely to make sure that is
|
||||
// modified by readInteger function to expected -234.
|
||||
int32_t value = 0;
|
||||
ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
|
||||
EXPECT_EQ(-234, value);
|
||||
|
||||
// Check that the option is not created when a buffer is
|
||||
// too short (3 bytes instead of 4 bytes).
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// single IPv4 addres can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, ipv4AddressData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address");
|
||||
|
||||
// Create input buffer.
|
||||
OptionBuffer buf;
|
||||
writeAddress(IOAddress("192.168.100.50"), buf);
|
||||
|
||||
// Create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
IOAddress address("127.0.0.1");
|
||||
// Read IPv4 address from using index 0.
|
||||
ASSERT_NO_THROW(address = option->readAddress(0));
|
||||
|
||||
EXPECT_EQ("192.168.100.50", address.toText());
|
||||
|
||||
// Check that option is not created if the provided buffer is
|
||||
// too short (use 3 bytes instead of 4).
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// single IPv6 addres can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, ipv6AddressData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
|
||||
|
||||
// Initialize input buffer.
|
||||
OptionBuffer buf;
|
||||
writeAddress(IOAddress("2001:db8:1::100"), buf);
|
||||
|
||||
// Create custom option using input buffer.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
// Custom option should comprise exactly one buffer that represents
|
||||
// IPv6 address.
|
||||
IOAddress address("::1");
|
||||
// Read an address from buffer #0.
|
||||
ASSERT_NO_THROW(address = option->readAddress(0));
|
||||
|
||||
EXPECT_EQ("2001:db8:1::100", address.toText());
|
||||
|
||||
// Check that option is not created if the provided buffer is
|
||||
// too short (use 15 bytes instead of 16).
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
|
||||
buf.begin() + 15)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// string value can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, stringData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "string");
|
||||
|
||||
// Create an input buffer holding some string value.
|
||||
OptionBuffer buf;
|
||||
writeString("hello world!", buf);
|
||||
|
||||
// Create custom option using input buffer.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have just one data field.
|
||||
ASSERT_EQ(1, option->getDataFieldsNum());
|
||||
|
||||
// Custom option should now comprise single string value that
|
||||
// can be accessed using index 0.
|
||||
std::string value;
|
||||
ASSERT_NO_THROW(value = option->readString(0));
|
||||
|
||||
EXPECT_EQ("hello world!", value);
|
||||
|
||||
// Check that option will not be created if empty buffer is provided.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// an array of boolean values can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, booleanDataArray) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
|
||||
|
||||
// Create a buffer with 5 values that represent array of
|
||||
// booleans.
|
||||
OptionBuffer buf(5);
|
||||
buf[0] = 1; // true
|
||||
buf[1] = 0; // false
|
||||
buf[2] = 0; // false
|
||||
buf[3] = 1; // true
|
||||
buf[4] = 1; // true
|
||||
|
||||
// Use the input buffer to create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 5 data fields.
|
||||
ASSERT_EQ(5, option->getDataFieldsNum());
|
||||
|
||||
// Read values from custom option using indexes 0..4 and
|
||||
// check that they are valid.
|
||||
bool value0 = false;
|
||||
ASSERT_NO_THROW(value0 = option->readBoolean(0));
|
||||
EXPECT_TRUE(value0);
|
||||
|
||||
bool value1 = true;
|
||||
ASSERT_NO_THROW(value1 = option->readBoolean(1));
|
||||
EXPECT_FALSE(value1);
|
||||
|
||||
bool value2 = true;
|
||||
ASSERT_NO_THROW(value2 = option->readBoolean(2));
|
||||
EXPECT_FALSE(value2);
|
||||
|
||||
bool value3 = false;
|
||||
ASSERT_NO_THROW(value3 = option->readBoolean(3));
|
||||
EXPECT_TRUE(value3);
|
||||
|
||||
bool value4 = false;
|
||||
ASSERT_NO_THROW(value4 = option->readBoolean(4));
|
||||
EXPECT_TRUE(value4);
|
||||
|
||||
// Check that empty buffer can't be used to create option holding
|
||||
// array of boolean values.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// an array of 32-bit signed integer values can be used to create an instance
|
||||
// of custom option.
|
||||
TEST_F(OptionCustomTest, uint32DataArray) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "uint32", true);
|
||||
|
||||
// Create an input buffer that holds 4 uint32 values that
|
||||
// represent an array.
|
||||
std::vector<uint32_t> values;
|
||||
values.push_back(71234);
|
||||
values.push_back(12234);
|
||||
values.push_back(54362);
|
||||
values.push_back(1234);
|
||||
|
||||
// Store these values in a buffer.
|
||||
OptionBuffer buf;
|
||||
for (int i = 0; i < values.size(); ++i) {
|
||||
writeInt<uint32_t>(values[i], buf);
|
||||
}
|
||||
// Create custom option using the input buffer.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
// Note that we just use a part of the whole buffer here: 13 bytes. We want to
|
||||
// check that buffer length which is non-divisible by 4 (size of uint32_t) is
|
||||
// accepted and only 3 (instead of 4) elements will be stored in a custom option.
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 3 data fields.
|
||||
ASSERT_EQ(3, option->getDataFieldsNum());
|
||||
|
||||
// Expect only 3 values.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
uint32_t value = 0;
|
||||
ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i));
|
||||
EXPECT_EQ(values[i], value);
|
||||
}
|
||||
|
||||
// Check that too short buffer can't be used to create the option.
|
||||
// Using buffer having length of 3 bytes. The length of 4 bytes is
|
||||
// a minimal length to create the option with single uint32_t value.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
|
||||
buf.begin() + 3)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// an array of IPv4 addresses can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, ipv4AddressDataArray) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
|
||||
|
||||
// Initialize reference data.
|
||||
std::vector<IOAddress> addresses;
|
||||
addresses.push_back(IOAddress("192.168.0.1"));
|
||||
addresses.push_back(IOAddress("127.0.0.1"));
|
||||
addresses.push_back(IOAddress("10.10.1.2"));
|
||||
|
||||
// Store the collection of IPv4 addresses into the buffer.
|
||||
OptionBuffer buf;
|
||||
for (int i = 0; i < addresses.size(); ++i) {
|
||||
writeAddress(addresses[i], buf);
|
||||
}
|
||||
|
||||
// Use the input buffer to create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 3 data fields.
|
||||
ASSERT_EQ(3, option->getDataFieldsNum());
|
||||
|
||||
// We expect 3 IPv4 addresses being stored in the option.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
IOAddress address("10.10.10.10");
|
||||
ASSERT_NO_THROW(address = option->readAddress(i));
|
||||
EXPECT_EQ(addresses[i].toText(), address.toText());
|
||||
}
|
||||
|
||||
// Check that it is ok if buffer length is not a multiple of IPv4
|
||||
// address length. Resize it by two bytes.
|
||||
buf.resize(buf.size() + 2);
|
||||
EXPECT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf));
|
||||
);
|
||||
|
||||
// Check that option is not created when the provided buffer
|
||||
// is too short. At least a buffer length of 4 bytes is needed.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
|
||||
buf.begin() + 2)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// an array of IPv6 addresses can be used to create an instance of custom option.
|
||||
TEST_F(OptionCustomTest, ipv6AddressDataArray) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
|
||||
|
||||
// Initialize reference data.
|
||||
std::vector<IOAddress> addresses;
|
||||
addresses.push_back(IOAddress("2001:db8:1::3"));
|
||||
addresses.push_back(IOAddress("::1"));
|
||||
addresses.push_back(IOAddress("fe80::3"));
|
||||
|
||||
// Store the collection of IPv6 addresses into the buffer.
|
||||
OptionBuffer buf;
|
||||
for (int i = 0; i < addresses.size(); ++i) {
|
||||
writeAddress(addresses[i], buf);
|
||||
}
|
||||
|
||||
// Use the input buffer to create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 3 data fields.
|
||||
ASSERT_EQ(3, option->getDataFieldsNum());
|
||||
|
||||
// We expect 3 IPv6 addresses being stored in the option.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
IOAddress address("fe80::4");
|
||||
ASSERT_NO_THROW(address = option->readAddress(i));
|
||||
EXPECT_EQ(addresses[i].toText(), address.toText());
|
||||
}
|
||||
|
||||
// Check that it is ok if buffer length is not a multiple of IPv6
|
||||
// address length. Resize it by two bytes.
|
||||
buf.resize(buf.size() + 2);
|
||||
EXPECT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
|
||||
// Check that option is not created when the provided buffer
|
||||
// is too short. At least a buffer length of 16 bytes is needed.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
|
||||
buf.begin() + 15)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the option definition comprising
|
||||
// a record of various data fields can be used to create an instance of
|
||||
// custom option.
|
||||
TEST_F(OptionCustomTest, recordData) {
|
||||
// Create the definition of an option which comprises
|
||||
// a record of fields of different types.
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("string"));
|
||||
|
||||
OptionBuffer buf;
|
||||
// Initialize field 0.
|
||||
writeInt<uint16_t>(8712, buf);
|
||||
// Initialize field 1 to 'true'
|
||||
buf.push_back(static_cast<unsigned short>(1));
|
||||
// Initialize field 2 to IPv4 address.
|
||||
writeAddress(IOAddress("192.168.0.1"), buf);
|
||||
// Initialize field 3 to IPv6 address.
|
||||
writeAddress(IOAddress("2001:db8:1::1"), buf);
|
||||
// Initialize field 4 to string value.
|
||||
writeString("ABCD", buf);
|
||||
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 5 data fields.
|
||||
ASSERT_EQ(5, option->getDataFieldsNum());
|
||||
|
||||
// Verify value in the field 0.
|
||||
uint16_t value0 = 0;
|
||||
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
|
||||
EXPECT_EQ(8712, value0);
|
||||
|
||||
// Verify value in the field 1.
|
||||
bool value1 = false;
|
||||
ASSERT_NO_THROW(value1 = option->readBoolean(1));
|
||||
EXPECT_TRUE(value1);
|
||||
|
||||
// Verify value in the field 2.
|
||||
IOAddress value2("127.0.0.1");
|
||||
ASSERT_NO_THROW(value2 = option->readAddress(2));
|
||||
EXPECT_EQ("192.168.0.1", value2.toText());
|
||||
|
||||
// Verify value in the field 3.
|
||||
IOAddress value3("::1");
|
||||
ASSERT_NO_THROW(value3 = option->readAddress(3));
|
||||
EXPECT_EQ("2001:db8:1::1", value3.toText());
|
||||
|
||||
// Verify value in the field 4.
|
||||
std::string value4;
|
||||
ASSERT_NO_THROW(value4 = option->readString(4));
|
||||
EXPECT_EQ("ABCD", value4);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that truncated buffer
|
||||
// can't be used to create an option being a record of value of
|
||||
// different types.
|
||||
TEST_F(OptionCustomTest, recordDataTruncated) {
|
||||
// Create the definition of an option which comprises
|
||||
// a record of fields of different types.
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("string"));
|
||||
|
||||
OptionBuffer buf;
|
||||
// Initialize field 0.
|
||||
writeInt<uint16_t>(8712, buf);
|
||||
// Initialize field 1 to IPv6 address.
|
||||
writeAddress(IOAddress("2001:db8:1::1"), buf);
|
||||
// Initialize field 2 to string value.
|
||||
writeString("ABCD", buf);
|
||||
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
|
||||
// Constructor should not throw exception here because the length of the
|
||||
// buffer meets the minimum length. The first 19 bytes hold data for
|
||||
// all option fields: uint16, IPv4 address and first letter of string.
|
||||
// Note that string will be truncated but this is acceptable because
|
||||
// constructor have no way to determine the length of the original string.
|
||||
EXPECT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19));
|
||||
);
|
||||
|
||||
// Reduce the buffer length by one byte should cause the constructor
|
||||
// to fail. This is because 18 bytes can only hold first two data fields:
|
||||
// 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
|
||||
// 3 data fields for this option but the length of the data is insufficient
|
||||
// to initialize 3 data field.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
|
||||
// Try to further reduce the length of the buffer to make it insufficient
|
||||
// to even initialize the second data field.
|
||||
EXPECT_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that pack function for
|
||||
// DHCPv4 custom option works correctly.
|
||||
TEST_F(OptionCustomTest, pack4) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 234, "record");
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint8"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
|
||||
OptionBuffer buf;
|
||||
writeInt<uint8_t>(1, buf);
|
||||
writeInt<uint16_t>(1000, buf);
|
||||
writeInt<uint32_t>(100000, buf);
|
||||
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
util::OutputBuffer buf_out(7);
|
||||
ASSERT_NO_THROW(option->pack(buf_out));
|
||||
ASSERT_EQ(9, buf_out.getLength());
|
||||
|
||||
// The original buffer holds the option data but it lacks a header.
|
||||
// We append data length and option code so as it can be directly
|
||||
// compared with the output buffer that holds whole option.
|
||||
buf.insert(buf.begin(), 7);
|
||||
buf.insert(buf.begin(), 234);
|
||||
|
||||
// Validate the buffer.
|
||||
EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that pack function for
|
||||
// DHCPv6 custom option works correctly.
|
||||
TEST_F(OptionCustomTest, pack6) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("string"));
|
||||
|
||||
OptionBuffer buf;
|
||||
buf.push_back(1);
|
||||
writeInt<uint16_t>(1000, buf);
|
||||
writeString("hello world", buf);
|
||||
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
util::OutputBuffer buf_out(buf.size() + option->getHeaderLen());
|
||||
ASSERT_NO_THROW(option->pack(buf_out));
|
||||
ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength());
|
||||
|
||||
// The original buffer holds the option data but it lacks a header.
|
||||
// We append data length and option code so as it can be directly
|
||||
// compared with the output buffer that holds whole option.
|
||||
OptionBuffer tmp;
|
||||
writeInt<uint16_t>(1000, tmp);
|
||||
writeInt<uint16_t>(buf.size(), tmp);
|
||||
buf.insert(buf.begin(), tmp.begin(), tmp.end());
|
||||
|
||||
// Validate the buffer.
|
||||
EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that unpack function works
|
||||
// correctly for a custom option.
|
||||
TEST_F(OptionCustomTest, unpack) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
|
||||
|
||||
// Initialize reference data.
|
||||
std::vector<IOAddress> addresses;
|
||||
addresses.push_back(IOAddress("192.168.0.1"));
|
||||
addresses.push_back(IOAddress("127.0.0.1"));
|
||||
addresses.push_back(IOAddress("10.10.1.2"));
|
||||
|
||||
// Store the collection of IPv4 addresses into the buffer.
|
||||
OptionBuffer buf;
|
||||
for (int i = 0; i < addresses.size(); ++i) {
|
||||
writeAddress(addresses[i], buf);
|
||||
}
|
||||
|
||||
// Use the input buffer to create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 3 data fields.
|
||||
ASSERT_EQ(3, option->getDataFieldsNum());
|
||||
|
||||
// We expect 3 IPv4 addresses being stored in the option.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
IOAddress address("10.10.10.10");
|
||||
ASSERT_NO_THROW(address = option->readAddress(i));
|
||||
EXPECT_EQ(addresses[i].toText(), address.toText());
|
||||
}
|
||||
|
||||
// Remove all addresses we had added. We are going to replace
|
||||
// them with a new set of addresses.
|
||||
addresses.clear();
|
||||
|
||||
// Add new addresses.
|
||||
addresses.push_back(IOAddress("10.1.2.3"));
|
||||
addresses.push_back(IOAddress("85.26.43.234"));
|
||||
|
||||
// Clear the buffer as we need to store new addresses in it.
|
||||
buf.clear();
|
||||
for (int i = 0; i < addresses.size(); ++i) {
|
||||
writeAddress(addresses[i], buf);
|
||||
}
|
||||
|
||||
// Perform 'unpack'.
|
||||
ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end()));
|
||||
|
||||
// Now we should have only 2 data fields.
|
||||
ASSERT_EQ(2, option->getDataFieldsNum());
|
||||
|
||||
// Verify that the addresses have been overwritten.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
IOAddress address("10.10.10.10");
|
||||
ASSERT_NO_THROW(address = option->readAddress(i));
|
||||
EXPECT_EQ(addresses[i].toText(), address.toText());
|
||||
}
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that new data can be set for
|
||||
// a custom option.
|
||||
TEST_F(OptionCustomTest, setData) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
|
||||
|
||||
// Initialize reference data.
|
||||
std::vector<IOAddress> addresses;
|
||||
addresses.push_back(IOAddress("2001:db8:1::3"));
|
||||
addresses.push_back(IOAddress("::1"));
|
||||
addresses.push_back(IOAddress("fe80::3"));
|
||||
|
||||
// Store the collection of IPv6 addresses into the buffer.
|
||||
OptionBuffer buf;
|
||||
for (int i = 0; i < addresses.size(); ++i) {
|
||||
writeAddress(addresses[i], buf);
|
||||
}
|
||||
|
||||
// Use the input buffer to create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We should have 3 data fields.
|
||||
ASSERT_EQ(3, option->getDataFieldsNum());
|
||||
|
||||
// We expect 3 IPv6 addresses being stored in the option.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
IOAddress address("fe80::4");
|
||||
ASSERT_NO_THROW(address = option->readAddress(i));
|
||||
EXPECT_EQ(addresses[i].toText(), address.toText());
|
||||
}
|
||||
|
||||
// Clear addresses we had previously added.
|
||||
addresses.clear();
|
||||
|
||||
// Store new addresses.
|
||||
addresses.push_back(IOAddress("::1"));
|
||||
addresses.push_back(IOAddress("fe80::10"));
|
||||
|
||||
// Clear the buffer as we need to store new addresses in it.
|
||||
buf.clear();
|
||||
for (int i = 0; i < addresses.size(); ++i) {
|
||||
writeAddress(addresses[i], buf);
|
||||
}
|
||||
|
||||
// Replace the option data.
|
||||
ASSERT_NO_THROW(option->setData(buf.begin(), buf.end()));
|
||||
|
||||
// Now we should have only 2 data fields.
|
||||
ASSERT_EQ(2, option->getDataFieldsNum());
|
||||
|
||||
// Check that it has been replaced.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
IOAddress address("10.10.10.10");
|
||||
ASSERT_NO_THROW(address = option->readAddress(i));
|
||||
EXPECT_EQ(addresses[i].toText(), address.toText());
|
||||
}
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that an invalid index
|
||||
// value can't be used to access option data fields.
|
||||
TEST_F(OptionCustomTest, invalidIndex) {
|
||||
OptionDefinition opt_def("OPTION_FOO", 999, "uint32", true);
|
||||
|
||||
OptionBuffer buf;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
writeInt<uint32_t>(i, buf);
|
||||
}
|
||||
|
||||
// Use the input buffer to create custom option.
|
||||
boost::scoped_ptr<OptionCustom> option;
|
||||
ASSERT_NO_THROW(
|
||||
option.reset(new OptionCustom(opt_def, Option::V6, buf));
|
||||
);
|
||||
ASSERT_TRUE(option);
|
||||
|
||||
// We expect that there are 10 uint32_t values stored in
|
||||
// the option. The 10th element is accessed by index eq 9.
|
||||
// Check that 9 is accepted.
|
||||
EXPECT_NO_THROW(option->readInteger<uint32_t>(9));
|
||||
|
||||
// Check that index value beyond 9 is not accepted.
|
||||
EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange);
|
||||
EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // anonymous namespace
|
@@ -47,32 +47,35 @@ public:
|
||||
OptionDefinitionTest() { }
|
||||
};
|
||||
|
||||
// The purpose of this test is to verify that OptionDefinition
|
||||
// constructor initializes its members correctly.
|
||||
TEST_F(OptionDefinitionTest, constructor) {
|
||||
// Specify the option data type as string. This should get converted
|
||||
// to enum value returned by getType().
|
||||
OptionDefinition opt_def1("OPTION_CLIENTID", 1, "string");
|
||||
EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName());
|
||||
|
||||
EXPECT_EQ(1, opt_def1.getCode());
|
||||
EXPECT_EQ(OptionDefinition::STRING_TYPE, opt_def1.getType());
|
||||
EXPECT_EQ(OPT_STRING_TYPE, opt_def1.getType());
|
||||
EXPECT_FALSE(opt_def1.getArrayType());
|
||||
EXPECT_NO_THROW(opt_def1.validate());
|
||||
|
||||
// Specify the option data type as an enum value.
|
||||
OptionDefinition opt_def2("OPTION_RAPID_COMMIT", 14,
|
||||
OptionDefinition::EMPTY_TYPE);
|
||||
OPT_EMPTY_TYPE);
|
||||
EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName());
|
||||
EXPECT_EQ(14, opt_def2.getCode());
|
||||
EXPECT_EQ(OptionDefinition::EMPTY_TYPE, opt_def2.getType());
|
||||
EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType());
|
||||
EXPECT_FALSE(opt_def2.getArrayType());
|
||||
EXPECT_NO_THROW(opt_def1.validate());
|
||||
|
||||
// Check if it is possible to set that option is an array.
|
||||
OptionDefinition opt_def3("OPTION_NIS_SERVERS", 27,
|
||||
OptionDefinition::IPV6_ADDRESS_TYPE,
|
||||
OPT_IPV6_ADDRESS_TYPE,
|
||||
true);
|
||||
EXPECT_EQ("OPTION_NIS_SERVERS", opt_def3.getName());
|
||||
EXPECT_EQ(27, opt_def3.getCode());
|
||||
EXPECT_EQ(OptionDefinition::IPV6_ADDRESS_TYPE, opt_def3.getType());
|
||||
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def3.getType());
|
||||
EXPECT_TRUE(opt_def3.getArrayType());
|
||||
EXPECT_NO_THROW(opt_def3.validate());
|
||||
|
||||
@@ -81,23 +84,27 @@ TEST_F(OptionDefinitionTest, constructor) {
|
||||
// it has been created.
|
||||
EXPECT_NO_THROW(
|
||||
OptionDefinition opt_def4("OPTION_SERVERID",
|
||||
OptionDefinition::UNKNOWN_TYPE + 10,
|
||||
OptionDefinition::STRING_TYPE);
|
||||
OPT_UNKNOWN_TYPE + 10,
|
||||
OPT_STRING_TYPE);
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that various data fields
|
||||
// can be specified for an option definition when this definition
|
||||
// is marked as 'record' and that fields can't be added if option
|
||||
// definition is not marked as 'record'.
|
||||
TEST_F(OptionDefinitionTest, addRecordField) {
|
||||
// We can only add fields to record if the option type has been
|
||||
// specified as 'record'. We try all other types but 'record'
|
||||
// here and expect exception to be thrown.
|
||||
for (int i = 0; i < OptionDefinition::UNKNOWN_TYPE; ++i) {
|
||||
for (int i = 0; i < OPT_UNKNOWN_TYPE; ++i) {
|
||||
// Do not try for 'record' type because this is the only
|
||||
// type for which adding record will succeed.
|
||||
if (i == OptionDefinition::RECORD_TYPE) {
|
||||
if (i == OPT_RECORD_TYPE) {
|
||||
continue;
|
||||
}
|
||||
OptionDefinition opt_def("OPTION_IAADDR", 5,
|
||||
static_cast<OptionDefinition::DataType>(i));
|
||||
static_cast<OptionDataType>(i));
|
||||
EXPECT_THROW(opt_def.addRecordField("uint8"), isc::InvalidOperation);
|
||||
}
|
||||
|
||||
@@ -106,54 +113,88 @@ TEST_F(OptionDefinitionTest, addRecordField) {
|
||||
EXPECT_NO_THROW(opt_def.addRecordField("ipv6-address"));
|
||||
EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
// It should not matter if we specify field type by its name or using enum.
|
||||
EXPECT_NO_THROW(opt_def.addRecordField(OptionDefinition::UINT32_TYPE));
|
||||
EXPECT_NO_THROW(opt_def.addRecordField(OPT_UINT32_TYPE));
|
||||
|
||||
// Check what we have actually added.
|
||||
OptionDefinition::RecordFieldsCollection fields = opt_def.getRecordFields();
|
||||
ASSERT_EQ(3, fields.size());
|
||||
EXPECT_EQ(OptionDefinition::IPV6_ADDRESS_TYPE, fields[0]);
|
||||
EXPECT_EQ(OptionDefinition::UINT32_TYPE, fields[1]);
|
||||
EXPECT_EQ(OptionDefinition::UINT32_TYPE, fields[2]);
|
||||
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, fields[0]);
|
||||
EXPECT_EQ(OPT_UINT32_TYPE, fields[1]);
|
||||
EXPECT_EQ(OPT_UINT32_TYPE, fields[2]);
|
||||
|
||||
// Let's try some more negative scenarios: use invalid data types.
|
||||
EXPECT_THROW(opt_def.addRecordField("unknown_type_xyz"), isc::BadValue);
|
||||
OptionDefinition::DataType invalid_type =
|
||||
static_cast<OptionDefinition::DataType>(OptionDefinition::UNKNOWN_TYPE + 10);
|
||||
OptionDataType invalid_type =
|
||||
static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + 10);
|
||||
EXPECT_THROW(opt_def.addRecordField(invalid_type), isc::BadValue);
|
||||
|
||||
// It is bad if we use 'record' option type but don't specify
|
||||
// at least two fields.
|
||||
OptionDefinition opt_def2("OPTION_EMPTY_RECORD", 100, "record");
|
||||
EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
|
||||
opt_def2.addRecordField("uint8");
|
||||
EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
|
||||
opt_def2.addRecordField("uint32");
|
||||
EXPECT_NO_THROW(opt_def2.validate());
|
||||
}
|
||||
|
||||
// The purpose of this test is to check that validate() function
|
||||
// reports errors for invalid option definitions.
|
||||
TEST_F(OptionDefinitionTest, validate) {
|
||||
// Not supported option type string is not allowed.
|
||||
OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, "non-existent-type");
|
||||
EXPECT_THROW(opt_def1.validate(), isc::OutOfRange);
|
||||
EXPECT_THROW(opt_def1.validate(), MalformedOptionDefinition);
|
||||
|
||||
// Not supported option type enum value is not allowed.
|
||||
OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, OptionDefinition::UNKNOWN_TYPE);
|
||||
EXPECT_THROW(opt_def2.validate(), isc::OutOfRange);
|
||||
OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, OPT_UNKNOWN_TYPE);
|
||||
EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
|
||||
|
||||
OptionDefinition opt_def3("OPTION_CLIENTID", D6O_CLIENTID,
|
||||
static_cast<OptionDefinition::DataType>(OptionDefinition::UNKNOWN_TYPE
|
||||
static_cast<OptionDataType>(OPT_UNKNOWN_TYPE
|
||||
+ 2));
|
||||
EXPECT_THROW(opt_def3.validate(), isc::OutOfRange);
|
||||
|
||||
EXPECT_THROW(opt_def3.validate(), MalformedOptionDefinition);
|
||||
|
||||
// Empty option name is not allowed.
|
||||
OptionDefinition opt_def4("", D6O_CLIENTID, "string");
|
||||
EXPECT_THROW(opt_def4.validate(), isc::BadValue);
|
||||
EXPECT_THROW(opt_def4.validate(), MalformedOptionDefinition);
|
||||
|
||||
// Option name must not contain spaces.
|
||||
OptionDefinition opt_def5(" OPTION_CLIENTID", D6O_CLIENTID, "string");
|
||||
EXPECT_THROW(opt_def5.validate(), isc::BadValue);
|
||||
EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
|
||||
|
||||
OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
|
||||
EXPECT_THROW(opt_def6.validate(), isc::BadValue);
|
||||
// Option name must not contain spaces.
|
||||
OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string", true);
|
||||
EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
|
||||
|
||||
// Having array of strings does not make sense because there is no way
|
||||
// to determine string's length.
|
||||
OptionDefinition opt_def7("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
|
||||
EXPECT_THROW(opt_def7.validate(), MalformedOptionDefinition);
|
||||
|
||||
// It does not make sense to have string field within the record before
|
||||
// other fields because there is no way to determine the length of this
|
||||
// string and thus there is no way to determine where the other field
|
||||
// begins.
|
||||
OptionDefinition opt_def8("OPTION_STATUS_CODE", D6O_STATUS_CODE,
|
||||
"record");
|
||||
opt_def8.addRecordField("string");
|
||||
opt_def8.addRecordField("uint16");
|
||||
EXPECT_THROW(opt_def8.validate(), MalformedOptionDefinition);
|
||||
|
||||
// ... but it is ok if the string value is the last one.
|
||||
OptionDefinition opt_def9("OPTION_STATUS_CODE", D6O_STATUS_CODE,
|
||||
"record");
|
||||
opt_def9.addRecordField("uint8");
|
||||
opt_def9.addRecordField("string");
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryAddrList6) {
|
||||
|
||||
// The purpose of this test is to verify that option definition
|
||||
// that comprises array of IPv6 addresses will return an instance
|
||||
// of option with a list of IPv6 addresses.
|
||||
TEST_F(OptionDefinitionTest, ipv6AddressArray) {
|
||||
OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
|
||||
"ipv6-address", true);
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
// Create a list of some V6 addresses.
|
||||
std::vector<asiolink::IOAddress> addrs;
|
||||
@@ -176,7 +217,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
|
||||
// the provided buffer.
|
||||
OptionPtr option_v6;
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_NIS_SERVERS, buf);
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst));
|
||||
boost::shared_ptr<Option6AddrLst> option_cast_v6 =
|
||||
@@ -195,17 +236,64 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
|
||||
buf.insert(buf.end(), 1, 1);
|
||||
// It should throw exception then.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V6, D6O_NIS_SERVERS, buf),
|
||||
isc::OutOfRange
|
||||
opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf),
|
||||
InvalidOptionValue
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryAddrList4) {
|
||||
// The purpose of this test is to verify that option definition
|
||||
// that comprises array of IPv6 addresses will return an instance
|
||||
// of option with a list of IPv6 addresses. Array of IPv6 addresses
|
||||
// is specified as a vector of strings (each string represents single
|
||||
// IPv6 address).
|
||||
TEST_F(OptionDefinitionTest, ipv6AddressArrayTokenized) {
|
||||
OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
|
||||
"ipv6-address", true);
|
||||
|
||||
// Create a vector of some V6 addresses.
|
||||
std::vector<asiolink::IOAddress> addrs;
|
||||
addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329"));
|
||||
addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319"));
|
||||
addrs.push_back(asiolink::IOAddress("::1"));
|
||||
addrs.push_back(asiolink::IOAddress("::2"));
|
||||
|
||||
// Create a vector of strings representing addresses given above.
|
||||
std::vector<std::string> addrs_str;
|
||||
for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
|
||||
it != addrs.end(); ++it) {
|
||||
addrs_str.push_back(it->toText());
|
||||
}
|
||||
|
||||
// Create DHCPv6 option using the list of IPv6 addresses given in the
|
||||
// string form.
|
||||
OptionPtr option_v6;
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS,
|
||||
addrs_str);
|
||||
);
|
||||
// Non-null pointer option is supposed to be returned and it
|
||||
// should have Option6AddrLst type.
|
||||
ASSERT_TRUE(option_v6);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst));
|
||||
// Cast to the actual option type to get IPv6 addresses from it.
|
||||
boost::shared_ptr<Option6AddrLst> option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6AddrLst>(option_v6);
|
||||
// Check that cast was successful.
|
||||
ASSERT_TRUE(option_cast_v6);
|
||||
// Get the list of parsed addresses from the option object.
|
||||
std::vector<asiolink::IOAddress> addrs_returned =
|
||||
option_cast_v6->getAddresses();
|
||||
// Returned addresses must match the addresses that have been used to create
|
||||
// the option instance.
|
||||
EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that option definition
|
||||
// that comprises array of IPv4 addresses will return an instance
|
||||
// of option with a list of IPv4 addresses.
|
||||
TEST_F(OptionDefinitionTest, ipv4AddressArray) {
|
||||
OptionDefinition opt_def("OPTION_NAME_SERVERS", D6O_NIS_SERVERS,
|
||||
"ipv4-address", true);
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
// Create a list of some V6 addresses.
|
||||
std::vector<asiolink::IOAddress> addrs;
|
||||
@@ -228,7 +316,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
|
||||
// the provided buffer.
|
||||
OptionPtr option_v4;
|
||||
ASSERT_NO_THROW(
|
||||
option_v4 = factory(Option::V4, DHO_NAME_SERVERS, buf)
|
||||
option_v4 = opt_def.optionFactory(Option::V4, DHO_NAME_SERVERS, buf)
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst));
|
||||
// Get the list of parsed addresses from the option object.
|
||||
@@ -245,19 +333,66 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
|
||||
// fulfilled anymore.
|
||||
buf.insert(buf.end(), 1, 1);
|
||||
// It should throw exception then.
|
||||
EXPECT_THROW(factory(Option::V4, DHO_NIS_SERVERS, buf), isc::OutOfRange);
|
||||
EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, buf),
|
||||
InvalidOptionValue);
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryEmpty) {
|
||||
// The purpose of this test is to verify that option definition
|
||||
// that comprises array of IPv4 addresses will return an instance
|
||||
// of option with a list of IPv4 addresses. The array of IPv4 addresses
|
||||
// is specified as a vector of strings (each string represents single
|
||||
// IPv4 address).
|
||||
TEST_F(OptionDefinitionTest, ipv4AddressArrayTokenized) {
|
||||
OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS,
|
||||
"ipv4-address", true);
|
||||
|
||||
// Create a vector of some V6 addresses.
|
||||
std::vector<asiolink::IOAddress> addrs;
|
||||
addrs.push_back(asiolink::IOAddress("192.168.0.1"));
|
||||
addrs.push_back(asiolink::IOAddress("172.16.1.1"));
|
||||
addrs.push_back(asiolink::IOAddress("127.0.0.1"));
|
||||
addrs.push_back(asiolink::IOAddress("213.41.23.12"));
|
||||
|
||||
// Create a vector of strings representing addresses given above.
|
||||
std::vector<std::string> addrs_str;
|
||||
for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
|
||||
it != addrs.end(); ++it) {
|
||||
addrs_str.push_back(it->toText());
|
||||
}
|
||||
|
||||
// Create DHCPv4 option using the list of IPv4 addresses given in the
|
||||
// string form.
|
||||
OptionPtr option_v4;
|
||||
ASSERT_NO_THROW(
|
||||
option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS,
|
||||
addrs_str);
|
||||
);
|
||||
// Non-null pointer option is supposed to be returned and it
|
||||
// should have Option6AddrLst type.
|
||||
ASSERT_TRUE(option_v4);
|
||||
ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst));
|
||||
// Cast to the actual option type to get IPv4 addresses from it.
|
||||
boost::shared_ptr<Option4AddrLst> option_cast_v4 =
|
||||
boost::static_pointer_cast<Option4AddrLst>(option_v4);
|
||||
// Check that cast was successful.
|
||||
ASSERT_TRUE(option_cast_v4);
|
||||
// Get the list of parsed addresses from the option object.
|
||||
std::vector<asiolink::IOAddress> addrs_returned =
|
||||
option_cast_v4->getAddresses();
|
||||
// Returned addresses must match the addresses that have been used to create
|
||||
// the option instance.
|
||||
EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
|
||||
}
|
||||
|
||||
// The purpose of thie test is to verify that option definition for
|
||||
// 'empty' option can be created and that it returns 'empty' option.
|
||||
TEST_F(OptionDefinitionTest, empty) {
|
||||
OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, "empty");
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
// Create option instance and provide empty buffer as expected.
|
||||
OptionPtr option_v6;
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
|
||||
// Expect 'empty' DHCPv6 option.
|
||||
@@ -266,31 +401,23 @@ TEST_F(OptionDefinitionTest, factoryEmpty) {
|
||||
EXPECT_EQ(0, option_v6->getData().size());
|
||||
|
||||
// Repeat the same test scenario for DHCPv4 option.
|
||||
EXPECT_THROW(factory(Option::V4, 214, OptionBuffer(2)),isc::BadValue);
|
||||
|
||||
OptionPtr option_v4;
|
||||
ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, OptionBuffer()));
|
||||
ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, OptionBuffer()));
|
||||
// Expect 'empty' DHCPv4 option.
|
||||
EXPECT_EQ(Option::V4, option_v4->getUniverse());
|
||||
EXPECT_EQ(2, option_v4->getHeaderLen());
|
||||
EXPECT_EQ(0, option_v4->getData().size());
|
||||
|
||||
// This factory produces empty option (consisting of option type
|
||||
// and length). Attempt to provide some data in the buffer should
|
||||
// result in exception.
|
||||
EXPECT_THROW(factory(Option::V6, D6O_RAPID_COMMIT,OptionBuffer(2)),isc::BadValue);
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryBinary) {
|
||||
// The purpose of this test is to verify that definition can be
|
||||
// creates for the option that holds binary data.
|
||||
TEST_F(OptionDefinitionTest, binary) {
|
||||
// Binary option is the one that is represented by the generic
|
||||
// Option class. In fact all options can be represented by this
|
||||
// class but for some of them it is just natural. The SERVERID
|
||||
// option consists of the option code, length and binary data so
|
||||
// this one was picked for this test.
|
||||
OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, "binary");
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
// Prepare some dummy data (serverid): 0, 1, 2 etc.
|
||||
OptionBuffer buf(14);
|
||||
@@ -302,7 +429,7 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
|
||||
// object of the type Option should be returned.
|
||||
OptionPtr option_v6;
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_SERVERID, buf);
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_SERVERID, buf);
|
||||
);
|
||||
// Expect base option type returned.
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
|
||||
@@ -320,7 +447,7 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
|
||||
|
||||
// Repeat the same test scenario for DHCPv4 option.
|
||||
OptionPtr option_v4;
|
||||
ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, buf));
|
||||
ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, buf));
|
||||
// Expect 'empty' DHCPv4 option.
|
||||
EXPECT_EQ(Option::V4, option_v4->getUniverse());
|
||||
EXPECT_EQ(2, option_v4->getHeaderLen());
|
||||
@@ -331,19 +458,19 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
|
||||
buf.begin()));
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryIA6) {
|
||||
// The purpose of this test is to verify that definition can be created
|
||||
// for option that comprises record of data. In this particular test
|
||||
// the IA_NA option is used. This option comprises three uint32 fields.
|
||||
TEST_F(OptionDefinitionTest, recordIA6) {
|
||||
// This option consists of IAID, T1 and T2 fields (each 4 bytes long).
|
||||
const int option6_ia_len = 12;
|
||||
|
||||
// Get the factory function pointer.
|
||||
OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, "record", true);
|
||||
OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, "record", false);
|
||||
// Each data field is uint32.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
}
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
// Check the positive scenario.
|
||||
OptionBuffer buf(12);
|
||||
@@ -351,7 +478,7 @@ TEST_F(OptionDefinitionTest, factoryIA6) {
|
||||
buf[i] = i;
|
||||
}
|
||||
OptionPtr option_v6;
|
||||
ASSERT_NO_THROW(option_v6 = factory(Option::V6, D6O_IA_NA, buf));
|
||||
ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IA_NA, buf));
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IA));
|
||||
boost::shared_ptr<Option6IA> option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6IA>(option_v6);
|
||||
@@ -359,25 +486,18 @@ TEST_F(OptionDefinitionTest, factoryIA6) {
|
||||
EXPECT_EQ(0x04050607, option_cast_v6->getT1());
|
||||
EXPECT_EQ(0x08090A0B, option_cast_v6->getT2());
|
||||
|
||||
// This should work for DHCPv6 only, try passing invalid universe value.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V4, D6O_IA_NA, OptionBuffer(option6_ia_len)),
|
||||
isc::BadValue
|
||||
);
|
||||
// The length of the buffer must be 12 bytes.
|
||||
// The length of the buffer must be at least 12 bytes.
|
||||
// Check too short buffer.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
|
||||
isc::OutOfRange
|
||||
opt_def.optionFactory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
|
||||
InvalidOptionValue
|
||||
);
|
||||
// Check too long buffer.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len + 1)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryIAAddr6) {
|
||||
// The purpose of this test is to verify that definition can be created
|
||||
// for option that comprises record of data. In this particular test
|
||||
// the IAADDR option is used.
|
||||
TEST_F(OptionDefinitionTest, recordIAAddr6) {
|
||||
// This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
|
||||
// valid-lifetime fields (each 4 bytes long).
|
||||
const int option6_iaaddr_len = 24;
|
||||
@@ -386,9 +506,6 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
// Check the positive scenario.
|
||||
OptionPtr option_v6;
|
||||
@@ -403,7 +520,7 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
|
||||
for (int i = 0; i < option6_iaaddr_len - asiolink::V6ADDRESS_LEN; ++i) {
|
||||
buf.push_back(i);
|
||||
}
|
||||
ASSERT_NO_THROW(option_v6 = factory(Option::V6, D6O_IAADDR, buf));
|
||||
ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, buf));
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr));
|
||||
boost::shared_ptr<Option6IAAddr> option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6IAAddr>(option_v6);
|
||||
@@ -411,44 +528,54 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
|
||||
EXPECT_EQ(0x00010203, option_cast_v6->getPreferred());
|
||||
EXPECT_EQ(0x04050607, option_cast_v6->getValid());
|
||||
|
||||
// This should work for DHCPv6 only, try passing invalid universe value.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V4, D6O_IAADDR, OptionBuffer(option6_iaaddr_len)),
|
||||
isc::BadValue
|
||||
);
|
||||
// The length of the buffer must be 12 bytes.
|
||||
// The length of the buffer must be at least 12 bytes.
|
||||
// Check too short buffer.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
|
||||
isc::OutOfRange
|
||||
opt_def.optionFactory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
|
||||
InvalidOptionValue
|
||||
);
|
||||
// Check too long buffer.
|
||||
EXPECT_THROW(
|
||||
factory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len + 1)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryIntegerInvalidType) {
|
||||
// The template function factoryInteger<> accepts integer values only
|
||||
// as template typename. Here we try passing different type and
|
||||
// see if it rejects it.
|
||||
EXPECT_THROW(
|
||||
OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, OptionBuffer(1)),
|
||||
isc::dhcp::InvalidDataType
|
||||
);
|
||||
// The purpose of this test is to verify that definition can be created
|
||||
// for option that comprises record of data. In this particular test
|
||||
// the IAADDR option is used. The data for the option is speicifed as
|
||||
// a vector of strings. Each string carries the data for the corresponding
|
||||
// data field.
|
||||
TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) {
|
||||
// This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
|
||||
// valid-lifetime fields (each 4 bytes long).
|
||||
OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, "record");
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
|
||||
|
||||
// Check the positive scenario.
|
||||
std::vector<std::string> data_field_values;
|
||||
data_field_values.push_back("2001:0db8::ff00:0042:8329");
|
||||
data_field_values.push_back("1234");
|
||||
data_field_values.push_back("5678");
|
||||
|
||||
OptionPtr option_v6;
|
||||
ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR,
|
||||
data_field_values));
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr));
|
||||
boost::shared_ptr<Option6IAAddr> option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6IAAddr>(option_v6);
|
||||
EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText());
|
||||
EXPECT_EQ(1234, option_cast_v6->getPreferred());
|
||||
EXPECT_EQ(5678, option_cast_v6->getValid());
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryUint8) {
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises single uint8 value can be created and that this definition
|
||||
// can be used to create an option with single uint8 value.
|
||||
TEST_F(OptionDefinitionTest, uint8) {
|
||||
OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8");
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
OptionPtr option_v6;
|
||||
// Try to use correct buffer length = 1 byte.
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint8_t>));
|
||||
// Validate the value.
|
||||
@@ -456,26 +583,42 @@ TEST_F(OptionDefinitionTest, factoryUint8) {
|
||||
boost::static_pointer_cast<Option6Int<uint8_t> >(option_v6);
|
||||
EXPECT_EQ(1, option_cast_v6->getValue());
|
||||
|
||||
// Try to provide too large buffer. Expect exception.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer(3)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
|
||||
// Try to provide zero-length buffer. Expect exception.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
|
||||
InvalidOptionValue
|
||||
);
|
||||
|
||||
// @todo Add more cases for DHCPv4
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryUint16) {
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises single uint8 value can be created and that this definition
|
||||
// can be used to create an option with single uint8 value.
|
||||
TEST_F(OptionDefinitionTest, uint8Tokenized) {
|
||||
OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8");
|
||||
|
||||
OptionPtr option_v6;
|
||||
std::vector<std::string> values;
|
||||
values.push_back("123");
|
||||
values.push_back("456");
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint8_t>));
|
||||
// Validate the value.
|
||||
boost::shared_ptr<Option6Int<uint8_t> > option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6Int<uint8_t> >(option_v6);
|
||||
EXPECT_EQ(123, option_cast_v6->getValue());
|
||||
|
||||
// @todo Add more cases for DHCPv4
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises single uint16 value can be created and that this definition
|
||||
// can be used to create an option with single uint16 value.
|
||||
TEST_F(OptionDefinitionTest, uint16) {
|
||||
OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16");
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
OptionPtr option_v6;
|
||||
// Try to use correct buffer length = 2 bytes.
|
||||
@@ -483,7 +626,7 @@ TEST_F(OptionDefinitionTest, factoryUint16) {
|
||||
buf.push_back(1);
|
||||
buf.push_back(2);
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, buf);
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, buf);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint16_t>));
|
||||
// Validate the value.
|
||||
@@ -491,25 +634,44 @@ TEST_F(OptionDefinitionTest, factoryUint16) {
|
||||
boost::static_pointer_cast<Option6Int<uint16_t> >(option_v6);
|
||||
EXPECT_EQ(0x0102, option_cast_v6->getValue());
|
||||
|
||||
// Try to provide too large buffer. Expect exception.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(3)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
// Try to provide zero-length buffer. Expect exception.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
|
||||
InvalidOptionValue
|
||||
);
|
||||
|
||||
// @todo Add more cases for DHCPv4
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryUint32) {
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises single uint16 value can be created and that this definition
|
||||
// can be used to create an option with single uint16 value.
|
||||
TEST_F(OptionDefinitionTest, uint16Tokenized) {
|
||||
OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16");
|
||||
|
||||
OptionPtr option_v6;
|
||||
|
||||
std::vector<std::string> values;
|
||||
values.push_back("1234");
|
||||
values.push_back("5678");
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint16_t>));
|
||||
// Validate the value.
|
||||
boost::shared_ptr<Option6Int<uint16_t> > option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6Int<uint16_t> >(option_v6);
|
||||
EXPECT_EQ(1234, option_cast_v6->getValue());
|
||||
|
||||
// @todo Add more cases for DHCPv4
|
||||
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises single uint32 value can be created and that this definition
|
||||
// can be used to create an option with single uint32 value.
|
||||
TEST_F(OptionDefinitionTest, uint32) {
|
||||
OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32");
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
OptionPtr option_v6;
|
||||
OptionBuffer buf;
|
||||
@@ -518,7 +680,7 @@ TEST_F(OptionDefinitionTest, factoryUint32) {
|
||||
buf.push_back(3);
|
||||
buf.push_back(4);
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_CLT_TIME, buf);
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, buf);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint32_t>));
|
||||
// Validate the value.
|
||||
@@ -526,27 +688,44 @@ TEST_F(OptionDefinitionTest, factoryUint32) {
|
||||
boost::static_pointer_cast<Option6Int<uint32_t> >(option_v6);
|
||||
EXPECT_EQ(0x01020304, option_cast_v6->getValue());
|
||||
|
||||
// Try to provide too large buffer. Expect exception.
|
||||
// Try to provide too short buffer. Expect exception.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_CLT_TIME, OptionBuffer(5)),
|
||||
isc::OutOfRange
|
||||
);
|
||||
// Try to provide zero-length buffer. Expect exception.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
|
||||
InvalidOptionValue
|
||||
);
|
||||
|
||||
// @todo Add more cases for DHCPv4
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryUint16Array) {
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises single uint32 value can be created and that this definition
|
||||
// can be used to create an option with single uint32 value.
|
||||
TEST_F(OptionDefinitionTest, uint32Tokenized) {
|
||||
OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32");
|
||||
|
||||
OptionPtr option_v6;
|
||||
std::vector<std::string> values;
|
||||
values.push_back("123456");
|
||||
values.push_back("789");
|
||||
ASSERT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint32_t>));
|
||||
// Validate the value.
|
||||
boost::shared_ptr<Option6Int<uint32_t> > option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6Int<uint32_t> >(option_v6);
|
||||
EXPECT_EQ(123456, option_cast_v6->getValue());
|
||||
|
||||
// @todo Add more cases for DHCPv4
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises array of uint16 values can be created and that this definition
|
||||
// can be used to create option with an array of uint16 values.
|
||||
TEST_F(OptionDefinitionTest, uint16Array) {
|
||||
// Let's define some dummy option.
|
||||
const uint16_t opt_code = 79;
|
||||
OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true);
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
OptionPtr option_v6;
|
||||
// Positive scenario, initiate the buffer with length being
|
||||
@@ -558,7 +737,7 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) {
|
||||
}
|
||||
// Constructor should succeed because buffer has correct size.
|
||||
EXPECT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, opt_code, buf);
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint16_t>));
|
||||
boost::shared_ptr<Option6IntArray<uint16_t> > option_cast_v6 =
|
||||
@@ -576,24 +755,50 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) {
|
||||
// Provided buffer size must be greater than zero. Check if we
|
||||
// get exception if we provide zero-length buffer.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, opt_code, OptionBuffer()),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
|
||||
InvalidOptionValue
|
||||
);
|
||||
// Buffer length must be multiple of data type size.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, opt_code, OptionBuffer(5)),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
|
||||
InvalidOptionValue
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(OptionDefinitionTest, factoryUint32Array) {
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises array of uint16 values can be created and that this definition
|
||||
// can be used to create option with an array of uint16 values.
|
||||
TEST_F(OptionDefinitionTest, uint16ArrayTokenized) {
|
||||
// Let's define some dummy option.
|
||||
const uint16_t opt_code = 79;
|
||||
OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true);
|
||||
|
||||
OptionPtr option_v6;
|
||||
std::vector<std::string> str_values;
|
||||
str_values.push_back("12345");
|
||||
str_values.push_back("5679");
|
||||
str_values.push_back("12");
|
||||
EXPECT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint16_t>));
|
||||
boost::shared_ptr<Option6IntArray<uint16_t> > option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6IntArray<uint16_t> >(option_v6);
|
||||
// Get the values from the initiated options and validate.
|
||||
std::vector<uint16_t> values = option_cast_v6->getValues();
|
||||
EXPECT_EQ(12345, values[0]);
|
||||
EXPECT_EQ(5679, values[1]);
|
||||
EXPECT_EQ(12, values[2]);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises array of uint32 values can be created and that this definition
|
||||
// can be used to create option with an array of uint32 values.
|
||||
TEST_F(OptionDefinitionTest, uint32Array) {
|
||||
// Let's define some dummy option.
|
||||
const uint16_t opt_code = 80;
|
||||
|
||||
OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true);
|
||||
Option::Factory* factory(NULL);
|
||||
EXPECT_NO_THROW(factory = opt_def.getFactory());
|
||||
ASSERT_TRUE(factory != NULL);
|
||||
|
||||
OptionPtr option_v6;
|
||||
// Positive scenario, initiate the buffer with length being
|
||||
@@ -605,7 +810,7 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) {
|
||||
}
|
||||
// Constructor should succeed because buffer has correct size.
|
||||
EXPECT_NO_THROW(
|
||||
option_v6 = factory(Option::V6, opt_code, buf);
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint32_t>));
|
||||
boost::shared_ptr<Option6IntArray<uint32_t> > option_cast_v6 =
|
||||
@@ -623,16 +828,85 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) {
|
||||
// Provided buffer size must be greater than zero. Check if we
|
||||
// get exception if we provide zero-length buffer.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, opt_code, OptionBuffer()),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
|
||||
InvalidOptionValue
|
||||
);
|
||||
// Buffer length must be multiple of data type size.
|
||||
EXPECT_THROW(
|
||||
option_v6 = factory(Option::V6, opt_code, OptionBuffer(5)),
|
||||
isc::OutOfRange
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
|
||||
InvalidOptionValue
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that definition for option that
|
||||
// comprises array of uint32 values can be created and that this definition
|
||||
// can be used to create option with an array of uint32 values.
|
||||
TEST_F(OptionDefinitionTest, uint32ArrayTokenized) {
|
||||
// Let's define some dummy option.
|
||||
const uint16_t opt_code = 80;
|
||||
|
||||
OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true);
|
||||
|
||||
OptionPtr option_v6;
|
||||
std::vector<std::string> str_values;
|
||||
str_values.push_back("123456");
|
||||
str_values.push_back("7");
|
||||
str_values.push_back("256");
|
||||
str_values.push_back("1111");
|
||||
|
||||
EXPECT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
|
||||
);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint32_t>));
|
||||
boost::shared_ptr<Option6IntArray<uint32_t> > option_cast_v6 =
|
||||
boost::static_pointer_cast<Option6IntArray<uint32_t> >(option_v6);
|
||||
// Get the values from the initiated options and validate.
|
||||
std::vector<uint32_t> values = option_cast_v6->getValues();
|
||||
EXPECT_EQ(123456, values[0]);
|
||||
EXPECT_EQ(7, values[1]);
|
||||
EXPECT_EQ(256, values[2]);
|
||||
EXPECT_EQ(1111, values[3]);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that the definition can be created
|
||||
// for the option that comprises string value in the UTF8 format.
|
||||
TEST_F(OptionDefinitionTest, utf8StringTokenized) {
|
||||
// Let's create some dummy option.
|
||||
const uint16_t opt_code = 80;
|
||||
OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "string");
|
||||
|
||||
std::vector<std::string> values;
|
||||
values.push_back("Hello World");
|
||||
values.push_back("this string should not be included in the option");
|
||||
OptionPtr option_v6;
|
||||
EXPECT_NO_THROW(
|
||||
option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
|
||||
);
|
||||
ASSERT_TRUE(option_v6);
|
||||
ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
|
||||
std::vector<uint8_t> data = option_v6->getData();
|
||||
std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
|
||||
+ values[0].length());
|
||||
EXPECT_TRUE(std::equal(ref_data.begin(), ref_data.end(), data.begin()));
|
||||
}
|
||||
|
||||
// The purpose of this test is to check that non-integer data type can't
|
||||
// be used for factoryInteger function.
|
||||
TEST_F(OptionDefinitionTest, integerInvalidType) {
|
||||
// The template function factoryInteger<> accepts integer values only
|
||||
// as template typename. Here we try passing different type and
|
||||
// see if it rejects it.
|
||||
OptionBuffer buf(1);
|
||||
EXPECT_THROW(
|
||||
OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE,
|
||||
buf.begin(), buf.end()),
|
||||
isc::dhcp::InvalidDataType
|
||||
);
|
||||
}
|
||||
|
||||
// The purpose of this test is to verify that helper methods
|
||||
// haveIA6Format and haveIAAddr6Format can be used to determine
|
||||
// IA_NA and IAADDR option formats.
|
||||
TEST_F(OptionDefinitionTest, recognizeFormat) {
|
||||
// IA_NA option format.
|
||||
OptionDefinition opt_def1("OPTION_IA_NA", D6O_IA_NA, "record");
|
||||
|
@@ -79,7 +79,7 @@ isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& pref
|
||||
}
|
||||
|
||||
// Finally, let's wrap this into nice and easy IOAddress object.
|
||||
return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
|
||||
return (isc::asiolink::IOAddress::fromBytes(AF_INET6, packed));
|
||||
}
|
||||
|
||||
/// @brief calculates the first IPv4 address in a IPv4 prefix
|
||||
@@ -159,7 +159,7 @@ isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefi
|
||||
}
|
||||
|
||||
// Finally, let's wrap this into nice and easy IOAddress object.
|
||||
return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
|
||||
return (isc::asiolink::IOAddress::fromBytes(AF_INET6, packed));
|
||||
}
|
||||
|
||||
}; // end of anonymous namespace
|
||||
|
@@ -51,7 +51,7 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
|
||||
}
|
||||
}
|
||||
|
||||
return (IOAddress::from_bytes(addr.getFamily(), packed));
|
||||
return (IOAddress::fromBytes(addr.getFamily(), packed));
|
||||
}
|
||||
|
||||
|
||||
|
1
src/lib/dhcpsrv/tests/.gitignore
vendored
Normal file
1
src/lib/dhcpsrv/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/libdhcpsrv_unittests
|
@@ -119,6 +119,7 @@ libb10_dns___la_SOURCES += tsigerror.h tsigerror.cc
|
||||
libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
|
||||
libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
|
||||
libb10_dns___la_SOURCES += character_string.h character_string.cc
|
||||
libb10_dns___la_SOURCES += master_loader_callbacks.h
|
||||
libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
|
||||
libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
|
||||
libb10_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc
|
||||
|
@@ -19,7 +19,9 @@
|
||||
#include <dns/master_lexer_state.h>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -29,20 +31,33 @@ namespace dns {
|
||||
|
||||
namespace {
|
||||
typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
|
||||
}
|
||||
} // end unnamed namespace
|
||||
using namespace master_lexer_internal;
|
||||
|
||||
|
||||
struct MasterLexer::MasterLexerImpl {
|
||||
MasterLexerImpl() : source_(NULL), token_(Token::NOT_STARTED),
|
||||
paren_count_(0), last_was_eol_(false)
|
||||
{}
|
||||
paren_count_(0), last_was_eol_(false),
|
||||
has_previous_(false),
|
||||
previous_paren_count_(0),
|
||||
previous_was_eol_(false)
|
||||
{
|
||||
separators_.set('\r');
|
||||
separators_.set('\n');
|
||||
separators_.set(' ');
|
||||
separators_.set('\t');
|
||||
separators_.set('(');
|
||||
separators_.set(')');
|
||||
esc_separators_.set('\r');
|
||||
esc_separators_.set('\n');
|
||||
}
|
||||
|
||||
// A helper method to skip possible comments toward the end of EOL or EOF.
|
||||
// commonly used by state classes. It returns the corresponding "end-of"
|
||||
// character in case it's a comment; otherwise it simply returns the
|
||||
// current character.
|
||||
int skipComment(int c) {
|
||||
if (c == ';') {
|
||||
int skipComment(int c, bool escaped = false) {
|
||||
if (c == ';' && !escaped) {
|
||||
while (true) {
|
||||
c = source_->getChar();
|
||||
if (c == '\n' || c == InputSource::END_OF_STREAM) {
|
||||
@@ -53,14 +68,39 @@ struct MasterLexer::MasterLexerImpl {
|
||||
return (c);
|
||||
}
|
||||
|
||||
bool isTokenEnd(int c, bool escaped) {
|
||||
// Special case of EOF (end of stream); this is not in the bitmaps
|
||||
if (c == InputSource::END_OF_STREAM) {
|
||||
return (true);
|
||||
}
|
||||
// In this implementation we only ensure the behavior for unsigned
|
||||
// range of characters, so we restrict the range of the values up to
|
||||
// 0x7f = 127
|
||||
return (escaped ? esc_separators_.test(c & 0x7f) :
|
||||
separators_.test(c & 0x7f));
|
||||
}
|
||||
|
||||
std::vector<InputSourcePtr> sources_;
|
||||
InputSource* source_; // current source (NULL if sources_ is empty)
|
||||
Token token_; // currently recognized token (set by a state)
|
||||
std::vector<char> data_; // placeholder for string data
|
||||
|
||||
// These are used in states, and defined here only as a placeholder.
|
||||
// The main lexer class does not need these members.
|
||||
size_t paren_count_; // nest count of the parentheses
|
||||
bool last_was_eol_; // whether the lexer just passed an end-of-line
|
||||
|
||||
// Bitmaps that gives whether a given (positive) character should be
|
||||
// considered a separator of a string/number token. The esc_ version
|
||||
// is a subset of the other, excluding characters that can be ignored
|
||||
// if escaped by a backslash. See isTokenEnd() for the bitmap size.
|
||||
std::bitset<128> separators_;
|
||||
std::bitset<128> esc_separators_;
|
||||
|
||||
// These are to allow restoring state before previous token.
|
||||
bool has_previous_;
|
||||
size_t previous_paren_count_;
|
||||
bool previous_was_eol_;
|
||||
};
|
||||
|
||||
MasterLexer::MasterLexer() : impl_(new MasterLexerImpl) {
|
||||
@@ -86,6 +126,7 @@ MasterLexer::pushSource(const char* filename, std::string* error) {
|
||||
}
|
||||
|
||||
impl_->source_ = impl_->sources_.back().get();
|
||||
impl_->has_previous_ = false;
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -93,6 +134,7 @@ void
|
||||
MasterLexer::pushSource(std::istream& input) {
|
||||
impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
|
||||
impl_->source_ = impl_->sources_.back().get();
|
||||
impl_->has_previous_ = false;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -104,6 +146,7 @@ MasterLexer::popSource() {
|
||||
impl_->sources_.pop_back();
|
||||
impl_->source_ = impl_->sources_.empty() ? NULL :
|
||||
impl_->sources_.back().get();
|
||||
impl_->has_previous_ = false;
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -122,15 +165,57 @@ MasterLexer::getSourceLine() const {
|
||||
return (impl_->sources_.back()->getCurrentLine());
|
||||
}
|
||||
|
||||
const MasterLexer::Token&
|
||||
MasterLexer::getNextToken(Options options) {
|
||||
// If the source is not available
|
||||
if (impl_->source_ == NULL) {
|
||||
isc_throw(isc::InvalidOperation, "No source to read tokens from");
|
||||
}
|
||||
// Store the current state so we can restore it in ungetToken
|
||||
impl_->previous_paren_count_ = impl_->paren_count_;
|
||||
impl_->previous_was_eol_ = impl_->last_was_eol_;
|
||||
impl_->source_->mark();
|
||||
impl_->has_previous_ = true;
|
||||
// Reset the token now. This is to check a token was actually produced.
|
||||
// This is debugging aid.
|
||||
impl_->token_ = Token(Token::NO_TOKEN_PRODUCED);
|
||||
// And get the token
|
||||
|
||||
// This actually handles EOF internally too.
|
||||
const State* state = State::start(*this, options);
|
||||
if (state != NULL) {
|
||||
state->handle(*this);
|
||||
}
|
||||
// Make sure a token was produced. Since this Can Not Happen, we assert
|
||||
// here instead of throwing.
|
||||
assert(impl_->token_.getType() != Token::ERROR ||
|
||||
impl_->token_.getErrorCode() != Token::NO_TOKEN_PRODUCED);
|
||||
return (impl_->token_);
|
||||
}
|
||||
|
||||
void
|
||||
MasterLexer::ungetToken() {
|
||||
if (impl_->has_previous_) {
|
||||
impl_->has_previous_ = false;
|
||||
impl_->source_->ungetAll();
|
||||
impl_->last_was_eol_ = impl_->previous_was_eol_;
|
||||
impl_->paren_count_ = impl_->previous_paren_count_;
|
||||
} else {
|
||||
isc_throw(isc::InvalidOperation, "No token to unget ready");
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
const char* const error_text[] = {
|
||||
"lexer not started", // NOT_STARTED
|
||||
"unbalanced parentheses", // UNBALANCED_PAREN
|
||||
"unexpected end of input", // UNEXPECTED_END
|
||||
"unbalanced quotes" // UNBALANCED_QUOTES
|
||||
"unbalanced quotes", // UNBALANCED_QUOTES
|
||||
"no token produced", // NO_TOKEN_PRODUCED
|
||||
"number out of range" // NUMBER_OUT_OF_RANGE
|
||||
};
|
||||
const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
|
||||
}
|
||||
} // end unnamed namespace
|
||||
|
||||
std::string
|
||||
MasterLexer::Token::getErrorText() const {
|
||||
@@ -171,7 +256,7 @@ class CRLF : public State {
|
||||
public:
|
||||
CRLF() {}
|
||||
virtual ~CRLF() {} // see the base class for the destructor
|
||||
virtual const State* handle(MasterLexer& lexer) const {
|
||||
virtual void handle(MasterLexer& lexer) const {
|
||||
// We've just seen '\r'. If this is part of a sequence of '\r\n',
|
||||
// we combine them as a single END-OF-LINE. Otherwise we treat the
|
||||
// single '\r' as an EOL and continue tokeniziation from the character
|
||||
@@ -188,18 +273,28 @@ public:
|
||||
}
|
||||
getLexerImpl(lexer)->token_ = Token(Token::END_OF_LINE);
|
||||
getLexerImpl(lexer)->last_was_eol_ = true;
|
||||
return (NULL);
|
||||
}
|
||||
};
|
||||
|
||||
// Currently this is provided mostly as a place holder
|
||||
class String : public State {
|
||||
public:
|
||||
String() {}
|
||||
virtual ~String() {} // see the base class for the destructor
|
||||
virtual const State* handle(MasterLexer& /*lexer*/) const {
|
||||
return (NULL);
|
||||
}
|
||||
virtual void handle(MasterLexer& lexer) const;
|
||||
};
|
||||
|
||||
class QString : public State {
|
||||
public:
|
||||
QString() {}
|
||||
virtual ~QString() {} // see the base class for the destructor
|
||||
virtual void handle(MasterLexer& lexer) const;
|
||||
};
|
||||
|
||||
class Number : public State {
|
||||
public:
|
||||
Number() {}
|
||||
virtual ~Number() {}
|
||||
virtual void handle(MasterLexer& lexer) const;
|
||||
};
|
||||
|
||||
// We use a common instance of a each state in a singleton-like way to save
|
||||
@@ -209,7 +304,9 @@ public:
|
||||
// this file.
|
||||
const CRLF CRLF_STATE;
|
||||
const String STRING_STATE;
|
||||
}
|
||||
const QString QSTRING_STATE;
|
||||
const Number NUMBER_STATE;
|
||||
} // end unnamed namespace
|
||||
|
||||
const State&
|
||||
State::getInstance(ID state_id) {
|
||||
@@ -218,6 +315,10 @@ State::getInstance(ID state_id) {
|
||||
return (CRLF_STATE);
|
||||
case String:
|
||||
return (STRING_STATE);
|
||||
case QString:
|
||||
return (QSTRING_STATE);
|
||||
case Number:
|
||||
return (NUMBER_STATE);
|
||||
}
|
||||
|
||||
// This is a bug of the caller, and this method is only expected to be
|
||||
@@ -233,6 +334,9 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
|
||||
MasterLexer::MasterLexerImpl& lexerimpl = *lexer.impl_;
|
||||
size_t& paren_count = lexerimpl.paren_count_;
|
||||
|
||||
// Note: the if-else in the loop is getting complicated. When we complete
|
||||
// #2374, revisit the organization to see if we need a fundamental
|
||||
// refactoring.
|
||||
while (true) {
|
||||
const int c = lexerimpl.skipComment(lexerimpl.source_->getChar());
|
||||
if (c == InputSource::END_OF_STREAM) {
|
||||
@@ -262,6 +366,9 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
|
||||
if (paren_count == 0) { // check if we are in () (see above)
|
||||
return (&CRLF_STATE);
|
||||
}
|
||||
} else if (c == '"' && (options & MasterLexer::QSTRING) != 0) {
|
||||
lexerimpl.last_was_eol_ = false;
|
||||
return (&QSTRING_STATE);
|
||||
} else if (c == '(') {
|
||||
lexerimpl.last_was_eol_ = false;
|
||||
++paren_count;
|
||||
@@ -272,8 +379,14 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
|
||||
return (NULL);
|
||||
}
|
||||
--paren_count;
|
||||
} else if ((options & MasterLexer::NUMBER) != 0 &&isdigit(c)) {
|
||||
lexerimpl.last_was_eol_ = false;
|
||||
// this character will be handled in the number state
|
||||
lexerimpl.source_->ungetChar();
|
||||
return (&NUMBER_STATE);
|
||||
} else {
|
||||
// Note: in #2373 we should probably ungetChar().
|
||||
// this character will be handled in the string state
|
||||
lexerimpl.source_->ungetChar();
|
||||
lexerimpl.last_was_eol_ = false;
|
||||
return (&STRING_STATE);
|
||||
}
|
||||
@@ -281,6 +394,102 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
String::handle(MasterLexer& lexer) const {
|
||||
std::vector<char>& data = getLexerImpl(lexer)->data_;
|
||||
data.clear();
|
||||
|
||||
bool escaped = false;
|
||||
while (true) {
|
||||
const int c = getLexerImpl(lexer)->skipComment(
|
||||
getLexerImpl(lexer)->source_->getChar(), escaped);
|
||||
|
||||
if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
|
||||
getLexerImpl(lexer)->source_->ungetChar();
|
||||
getLexerImpl(lexer)->token_ =
|
||||
MasterLexer::Token(&data.at(0), data.size());
|
||||
return;
|
||||
}
|
||||
escaped = (c == '\\' && !escaped);
|
||||
data.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QString::handle(MasterLexer& lexer) const {
|
||||
MasterLexer::Token& token = getLexerImpl(lexer)->token_;
|
||||
std::vector<char>& data = getLexerImpl(lexer)->data_;
|
||||
data.clear();
|
||||
|
||||
bool escaped = false;
|
||||
while (true) {
|
||||
const int c = getLexerImpl(lexer)->source_->getChar();
|
||||
if (c == InputSource::END_OF_STREAM) {
|
||||
token = Token(Token::UNEXPECTED_END);
|
||||
return;
|
||||
} else if (c == '"') {
|
||||
if (escaped) {
|
||||
// found escaped '"'. overwrite the preceding backslash.
|
||||
assert(!data.empty());
|
||||
escaped = false;
|
||||
data.back() = '"';
|
||||
} else {
|
||||
token = MasterLexer::Token(&data.at(0), data.size(), true);
|
||||
return;
|
||||
}
|
||||
} else if (c == '\n' && !escaped) {
|
||||
getLexerImpl(lexer)->source_->ungetChar();
|
||||
token = Token(Token::UNBALANCED_QUOTES);
|
||||
return;
|
||||
} else {
|
||||
escaped = (c == '\\' && !escaped);
|
||||
data.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Number::handle(MasterLexer& lexer) const {
|
||||
MasterLexer::Token& token = getLexerImpl(lexer)->token_;
|
||||
|
||||
// It may yet turn out to be a string, so we first
|
||||
// collect all the data
|
||||
bool digits_only = true;
|
||||
std::vector<char>& data = getLexerImpl(lexer)->data_;
|
||||
data.clear();
|
||||
bool escaped = false;
|
||||
|
||||
while (true) {
|
||||
const int c = getLexerImpl(lexer)->skipComment(
|
||||
getLexerImpl(lexer)->source_->getChar(), escaped);
|
||||
if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
|
||||
getLexerImpl(lexer)->source_->ungetChar();
|
||||
if (digits_only) {
|
||||
// Close the string for lexical_cast
|
||||
data.push_back('\0');
|
||||
try {
|
||||
const uint32_t number32 =
|
||||
boost::lexical_cast<uint32_t, const char*>(&data[0]);
|
||||
token = MasterLexer::Token(number32);
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
// Since we already know we have only digits,
|
||||
// range should be the only possible problem.
|
||||
token = Token(Token::NUMBER_OUT_OF_RANGE);
|
||||
}
|
||||
} else {
|
||||
token = MasterLexer::Token(&data.at(0),
|
||||
data.size());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isdigit(c)) {
|
||||
digits_only = false;
|
||||
}
|
||||
escaped = (c == '\\' && !escaped);
|
||||
data.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace master_lexer_internal
|
||||
|
||||
} // end of namespace dns
|
||||
|
@@ -69,6 +69,14 @@ class State;
|
||||
class MasterLexer {
|
||||
friend class master_lexer_internal::State;
|
||||
public:
|
||||
/// \brief Exception thrown when we fail to read from the input
|
||||
/// stream or file.
|
||||
struct ReadError : public Unexpected {
|
||||
ReadError(const char* file, size_t line, const char* what) :
|
||||
Unexpected(file, line, what)
|
||||
{}
|
||||
};
|
||||
|
||||
class Token; // we define it separately for better readability
|
||||
|
||||
/// \brief Options for getNextToken.
|
||||
@@ -178,6 +186,52 @@ public:
|
||||
/// \return The current line number of the source (see the description)
|
||||
size_t getSourceLine() const;
|
||||
|
||||
/// \brief Parse and return another token from the input.
|
||||
///
|
||||
/// It reads a bit of the last opened source and produces another token
|
||||
/// found in it.
|
||||
///
|
||||
/// This method does not provide the strong exception guarantee. Generally,
|
||||
/// if it throws, the object should not be used any more and should be
|
||||
/// discarded. It was decided all the exceptions thrown from here are
|
||||
/// serious enough that aborting the loading process is the only reasonable
|
||||
/// recovery anyway, so the strong exception guarantee is not needed.
|
||||
///
|
||||
/// \param options The options can be used to modify the tokenization.
|
||||
/// The method can be made reporting things which are usually ignored
|
||||
/// by this parameter. Multiple options can be passed at once by
|
||||
/// bitwise or (eg. option1 | option 2). See description of available
|
||||
/// options.
|
||||
/// \return Next token found in the input. Note that the token refers to
|
||||
/// some internal data in the lexer. It is valid only until
|
||||
/// getNextToken or ungetToken is called. Also, the token becomes
|
||||
/// invalid when the lexer is destroyed.
|
||||
/// \throw isc::InvalidOperation in case the source is not available. This
|
||||
/// may mean the pushSource() has not been called yet, or that the
|
||||
/// current source has been read past the end.
|
||||
/// \throw ReadError in case there's problem reading from the underlying
|
||||
/// source (eg. I/O error in the file on the disk).
|
||||
/// \throw std::bad_alloc in case allocation of some internal resources
|
||||
/// or the token fail.
|
||||
const Token& getNextToken(Options options = NONE);
|
||||
|
||||
/// \brief Return the last token back to the lexer.
|
||||
///
|
||||
/// The method undoes the lasts call to getNextToken(). If you call the
|
||||
/// getNextToken() again with the same options, it'll return the same
|
||||
/// token. If the options are different, it may return a different token,
|
||||
/// but it acts as if the previous getNextToken() was never called.
|
||||
///
|
||||
/// It is possible to return only one token back in time (you can't call
|
||||
/// ungetToken() twice in a row without calling getNextToken() in between
|
||||
/// successfully).
|
||||
///
|
||||
/// It does not work after change of source (by pushSource or popSource).
|
||||
///
|
||||
/// \throw isc::InvalidOperation If called second time in a row or if
|
||||
/// getNextToken() was not called since the last change of the source.
|
||||
void ungetToken();
|
||||
|
||||
private:
|
||||
struct MasterLexerImpl;
|
||||
MasterLexerImpl* impl_;
|
||||
@@ -216,10 +270,10 @@ public:
|
||||
/// as an unsigned 32-bit integer. If we see the need for larger integers
|
||||
/// or negative numbers, we can then extend the token types.
|
||||
enum Type {
|
||||
END_OF_LINE, ///< End of line detected (if asked for detecting it)
|
||||
END_OF_FILE, ///< End of file detected (if asked for detecting it)
|
||||
END_OF_LINE, ///< End of line detected
|
||||
END_OF_FILE, ///< End of file detected
|
||||
INITIAL_WS, ///< White spaces at the beginning of a line after an
|
||||
///< end of line
|
||||
///< end of line (if asked for detecting it)
|
||||
NOVALUE_TYPE_MAX = INITIAL_WS, ///< Max integer corresponding to
|
||||
/// no-value (type only) types.
|
||||
/// Mainly for internal use.
|
||||
@@ -234,8 +288,11 @@ public:
|
||||
NOT_STARTED, ///< The lexer is just initialized and has no token
|
||||
UNBALANCED_PAREN, ///< Unbalanced parentheses detected
|
||||
UNEXPECTED_END, ///< The lexer reaches the end of line or file
|
||||
/// unexpectedly
|
||||
/// unexpectedly
|
||||
UNBALANCED_QUOTES, ///< Unbalanced quotations detected
|
||||
NO_TOKEN_PRODUCED, ///< No token was produced. This means programmer
|
||||
/// error and should never get out of the lexer.
|
||||
NUMBER_OUT_OF_RANGE, ///< Number was out of range
|
||||
MAX_ERROR_CODE ///< Max integer corresponding to valid error codes.
|
||||
/// (excluding this one). Mainly for internal use.
|
||||
};
|
||||
@@ -340,12 +397,34 @@ public:
|
||||
/// string object.
|
||||
/// \return A std::string object corresponding to the string token value.
|
||||
std::string getString() const {
|
||||
std::string ret;
|
||||
getString(ret);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/// \brief Fill in a string with the value of a string-variant token.
|
||||
///
|
||||
/// This is similar to the other version of \c getString(), but
|
||||
/// the caller is supposed to pass a placeholder string object.
|
||||
/// This will be more efficient if the caller uses the same
|
||||
/// \c MasterLexer repeatedly and needs to get string token in the
|
||||
/// form of a string object many times as this version could reuse
|
||||
/// the existing internal storage of the passed string.
|
||||
///
|
||||
/// Any existing content of the passed string will be removed.
|
||||
///
|
||||
/// \throw InvalidOperation Called on a non string-variant types of token.
|
||||
/// \throw std::bad_alloc Resource allocation failure in constructing the
|
||||
/// string object.
|
||||
///
|
||||
/// \param ret A string object to be filled with the token string.
|
||||
void getString(std::string& ret) const {
|
||||
if (type_ != STRING && type_ != QSTRING) {
|
||||
isc_throw(InvalidOperation,
|
||||
"Token::getString() for non string-variant type");
|
||||
}
|
||||
return (std::string(val_.str_region_.beg,
|
||||
val_.str_region_.beg + val_.str_region_.len));
|
||||
ret.assign(val_.str_region_.beg,
|
||||
val_.str_region_.beg + val_.str_region_.len);
|
||||
}
|
||||
|
||||
/// \brief Return the value of a string-variant token as a string object.
|
||||
|
@@ -13,6 +13,7 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <dns/master_lexer_inputsource.h>
|
||||
#include <dns/master_lexer.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
@@ -94,7 +95,7 @@ InputSource::getChar() {
|
||||
// This has to come after the .eof() check as some
|
||||
// implementations seem to check the eofbit also in .fail().
|
||||
if (input_.fail()) {
|
||||
isc_throw(ReadError,
|
||||
isc_throw(MasterLexer::ReadError,
|
||||
"Error reading from the input stream: " << getName());
|
||||
}
|
||||
buffer_.push_back(c);
|
||||
|
@@ -56,14 +56,6 @@ public:
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Exception thrown when we fail to read from the input
|
||||
/// stream or file.
|
||||
struct ReadError : public Unexpected {
|
||||
ReadError(const char* file, size_t line, const char* what) :
|
||||
Unexpected(file, line, what)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Exception thrown when we fail to open the input file.
|
||||
struct OpenError : public Unexpected {
|
||||
OpenError(const char* file, size_t line, const char* what) :
|
||||
@@ -124,8 +116,8 @@ public:
|
||||
/// \brief Returns a single character from the input source. If end
|
||||
/// of file is reached, \c END_OF_STREAM is returned.
|
||||
///
|
||||
/// \throws ReadError when reading from the input stream or file
|
||||
/// fails.
|
||||
/// \throws MasterLexer::ReadError when reading from the input stream or
|
||||
/// file fails.
|
||||
int getChar();
|
||||
|
||||
/// \brief Skips backward a single character in the input
|
||||
|
@@ -17,6 +17,8 @@
|
||||
|
||||
#include <dns/master_lexer.h>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
|
||||
namespace isc {
|
||||
namespace dns {
|
||||
|
||||
@@ -67,7 +69,7 @@ public:
|
||||
/// tokenization session. The lexer passes a reference to itself
|
||||
/// and options given in \c getNextToken().
|
||||
///
|
||||
/// \throw InputSource::ReadError Unexpected I/O error
|
||||
/// \throw MasterLexer::ReadError Unexpected I/O error
|
||||
/// \throw std::bad_alloc Internal resource allocation failure
|
||||
///
|
||||
/// \param lexer The lexer object that holds the main context.
|
||||
@@ -80,16 +82,16 @@ public:
|
||||
/// \brief Handle the process of one specific state.
|
||||
///
|
||||
/// This method is expected to be called on the object returned by
|
||||
/// start(), and keep called on the returned object until NULL is
|
||||
/// returned. The call chain will form the complete state transition.
|
||||
/// start(). In the usual state transition design pattern, it would
|
||||
/// return the next state. But as we noticed, we never have another
|
||||
/// state, so we simplify it by not returning anything instead of
|
||||
/// returning NULL every time.
|
||||
///
|
||||
/// \throw InputSource::ReadError Unexpected I/O error
|
||||
/// \throw MasterLexer::ReadError Unexpected I/O error
|
||||
/// \throw std::bad_alloc Internal resource allocation failure
|
||||
///
|
||||
/// \param lexer The lexer object that holds the main context.
|
||||
/// \return A pointer to the next state object or NULL if the transition
|
||||
/// is completed.
|
||||
virtual const State* handle(MasterLexer& lexer) const = 0;
|
||||
virtual void handle(MasterLexer& lexer) const = 0;
|
||||
|
||||
/// \brief Types of states.
|
||||
///
|
||||
@@ -98,7 +100,9 @@ public:
|
||||
/// a way to get an instance of a specific state.
|
||||
enum ID {
|
||||
CRLF, ///< Just seen a carriage-return character
|
||||
String ///< Handling a string token
|
||||
String, ///< Handling a string token
|
||||
QString, ///< Handling a quoted string token
|
||||
Number ///< Handling a number
|
||||
};
|
||||
|
||||
/// \brief Returns a \c State instance of the given state.
|
||||
|
122
src/lib/dns/master_loader_callbacks.h
Normal file
122
src/lib/dns/master_loader_callbacks.h
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef MASTER_LOADER_CALLBACKS_H
|
||||
#define MASTER_LOADER_CALLBACKS_H
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <string>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace isc {
|
||||
namespace dns {
|
||||
|
||||
class AbstractRRset;
|
||||
typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
|
||||
|
||||
/// \brief Type of callback to add a RRset.
|
||||
///
|
||||
/// This type of callback is used by the loader to report another loaded
|
||||
/// RRset. The RRset is no longer preserved by the loader and is fully
|
||||
/// owned by the callback.
|
||||
///
|
||||
/// \param RRset The rrset to add. It does not contain the accompanying
|
||||
/// RRSIG (if the zone is signed), they are reported with separate
|
||||
/// calls to the callback.
|
||||
typedef boost::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
|
||||
|
||||
/// \brief Set of issue callbacks for a loader.
|
||||
///
|
||||
/// This holds a set of callbacks by which a loader (such as MasterLoader)
|
||||
/// can report loaded RRsets, errors and other unusual conditions.
|
||||
///
|
||||
/// All the callbacks must be set.
|
||||
class MasterLoaderCallbacks {
|
||||
public:
|
||||
/// \brief Type of one callback to report problems.
|
||||
///
|
||||
/// This is the type of one callback used to report an unusual
|
||||
/// condition or error.
|
||||
///
|
||||
/// \param source_name The name of the source where the problem happened.
|
||||
/// This is usually a file name.
|
||||
/// \param source_line Position of the problem, counted in lines from the
|
||||
/// beginning of the source.
|
||||
/// \param reason Human readable description of what happened.
|
||||
typedef boost::function<void(const std::string& source_name,
|
||||
size_t source_line,
|
||||
const std::string& reason)> IssueCallback;
|
||||
|
||||
/// \brief Constructor
|
||||
///
|
||||
/// Initializes the callbacks.
|
||||
///
|
||||
/// \param error The error callback to use.
|
||||
/// \param warning The warning callback to use.
|
||||
/// \throw isc::InvalidParameter if any of the callbacks is empty.
|
||||
MasterLoaderCallbacks(const IssueCallback& error,
|
||||
const IssueCallback& warning) :
|
||||
error_(error),
|
||||
warning_(warning)
|
||||
{
|
||||
if (error_.empty() || warning_.empty()) {
|
||||
isc_throw(isc::InvalidParameter,
|
||||
"Empty function passed as callback");
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Call callback for serious errors
|
||||
///
|
||||
/// This is called whenever there's a serious problem which makes the data
|
||||
/// being loaded unusable. Further processing may or may not happen after
|
||||
/// this (for example to detect further errors), but the data should not
|
||||
/// be used.
|
||||
///
|
||||
/// It calls whatever was passed to the error parameter to the constructor.
|
||||
///
|
||||
/// If the caller of the loader wants to abort, it is possible to throw
|
||||
/// from the callback, which aborts the load.
|
||||
void error(const std::string& source_name, size_t source_line,
|
||||
const std::string& reason)
|
||||
{
|
||||
error_(source_name, source_line, reason);
|
||||
}
|
||||
|
||||
/// \brief Call callback for potential problems
|
||||
///
|
||||
/// This is called whenever a minor problem is discovered. This might mean
|
||||
/// the data is completely OK, it just looks suspicious.
|
||||
///
|
||||
/// It calls whatever was passed to the warn parameter to the constructor.
|
||||
///
|
||||
/// The loading will continue after the callback. If the caller wants to
|
||||
/// abort (which is probably not a very good idea, since warnings
|
||||
/// may be false positives), it is possible to throw from inside the
|
||||
/// callback.
|
||||
void warning(const std::string& source_name, size_t source_line,
|
||||
const std::string& reason)
|
||||
{
|
||||
warning_(source_name, source_line, reason);
|
||||
}
|
||||
|
||||
private:
|
||||
IssueCallback error_, warning_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LOADER_CALLBACKS_H
|
@@ -71,6 +71,7 @@ run_unittests_SOURCES += tsigerror_unittest.cc
|
||||
run_unittests_SOURCES += tsigkey_unittest.cc
|
||||
run_unittests_SOURCES += tsigrecord_unittest.cc
|
||||
run_unittests_SOURCES += character_string_unittest.cc
|
||||
run_unittests_SOURCES += master_loader_callbacks_test.cc
|
||||
run_unittests_SOURCES += run_unittests.cc
|
||||
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||
# We shouldn't need to include BOTAN_LIBS here, but there
|
||||
|
@@ -32,6 +32,8 @@ protected:
|
||||
s_null(NULL),
|
||||
s_crlf(State::getInstance(State::CRLF)),
|
||||
s_string(State::getInstance(State::String)),
|
||||
s_qstring(State::getInstance(State::QString)),
|
||||
s_number(State::getInstance(State::Number)),
|
||||
options(MasterLexer::NONE),
|
||||
orig_options(options)
|
||||
{}
|
||||
@@ -42,6 +44,8 @@ protected:
|
||||
const State* const s_null;
|
||||
const State& s_crlf;
|
||||
const State& s_string;
|
||||
const State& s_qstring;
|
||||
const State& s_number;
|
||||
std::stringstream ss;
|
||||
MasterLexer::Options options, orig_options;
|
||||
};
|
||||
@@ -111,8 +115,7 @@ TEST_F(MasterLexerStateTest, parentheses) {
|
||||
EXPECT_EQ(1, s_crlf.getParenCount(lexer)); // check post condition
|
||||
EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
|
||||
|
||||
// skip 'a' (note: until #2373 it's actually skipped as part of the '('
|
||||
// handling)
|
||||
// skip 'a'
|
||||
s_string.handle(lexer);
|
||||
|
||||
// Then handle ')'. '\n' before ')' isn't recognized because
|
||||
@@ -226,7 +229,7 @@ TEST_F(MasterLexerStateTest, crlf) {
|
||||
|
||||
// 1. A sequence of \r, \n is recognized as a single 'end-of-line'
|
||||
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
|
||||
EXPECT_EQ(s_null, s_crlf.handle(lexer)); // recognize '\n'
|
||||
s_crlf.handle(lexer); // recognize '\n'
|
||||
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
|
||||
EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
|
||||
|
||||
@@ -234,23 +237,361 @@ TEST_F(MasterLexerStateTest, crlf) {
|
||||
// 'end-of-line'. then there will be "initial WS"
|
||||
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
|
||||
// see ' ', "unget" it
|
||||
EXPECT_EQ(s_null, s_crlf.handle(lexer));
|
||||
s_crlf.handle(lexer);
|
||||
EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize ' '
|
||||
EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
|
||||
|
||||
// 3. comment between \r and \n
|
||||
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
|
||||
// skip comments, recognize '\n'
|
||||
EXPECT_EQ(s_null, s_crlf.handle(lexer));
|
||||
s_crlf.handle(lexer);
|
||||
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // skip 'a'
|
||||
|
||||
// 4. \r then EOF
|
||||
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
|
||||
// see EOF, then "unget" it
|
||||
EXPECT_EQ(s_null, s_crlf.handle(lexer));
|
||||
s_crlf.handle(lexer);
|
||||
EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize EOF
|
||||
EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
|
||||
}
|
||||
|
||||
// Commonly used check for string related test cases, checking if the given
|
||||
// token has expected values.
|
||||
void
|
||||
stringTokenCheck(const std::string& expected, const MasterLexer::Token& token,
|
||||
bool quoted = false)
|
||||
{
|
||||
EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType());
|
||||
EXPECT_EQ(expected, token.getString());
|
||||
const std::string actual(token.getStringRegion().beg,
|
||||
token.getStringRegion().beg +
|
||||
token.getStringRegion().len);
|
||||
EXPECT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
TEST_F(MasterLexerStateTest, string) {
|
||||
// Check with simple strings followed by separate characters
|
||||
ss << "followed-by-EOL\n";
|
||||
ss << "followed-by-CR\r";
|
||||
ss << "followed-by-space ";
|
||||
ss << "followed-by-tab\t";
|
||||
ss << "followed-by-comment;this is comment and ignored\n";
|
||||
ss << "followed-by-paren(closing)";
|
||||
ss << "followed-by-EOF";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see \n
|
||||
EXPECT_FALSE(s_string.wasLastEOL(lexer));
|
||||
stringTokenCheck("followed-by-EOL", s_string.getToken(lexer));
|
||||
EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see \r
|
||||
stringTokenCheck("followed-by-CR", s_string.getToken(lexer));
|
||||
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // handle \r...
|
||||
s_crlf.handle(lexer); // ...and skip it
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' '
|
||||
stringTokenCheck("followed-by-space", s_string.getToken(lexer));
|
||||
|
||||
// skip ' ', then recognize the next string
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see \t
|
||||
stringTokenCheck("followed-by-tab", s_string.getToken(lexer));
|
||||
|
||||
// skip \t, then recognize the next string
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see comment
|
||||
stringTokenCheck("followed-by-comment", s_string.getToken(lexer));
|
||||
EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see '('
|
||||
stringTokenCheck("followed-by-paren", s_string.getToken(lexer));
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options)); // str in ()
|
||||
s_string.handle(lexer); // recognize the str, see ')'
|
||||
stringTokenCheck("closing", s_string.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see EOF
|
||||
stringTokenCheck("followed-by-EOF", s_string.getToken(lexer));
|
||||
}
|
||||
|
||||
TEST_F(MasterLexerStateTest, stringEscape) {
|
||||
// some of the separate characters should be considered part of the
|
||||
// string if escaped.
|
||||
ss << "escaped\\ space ";
|
||||
ss << "escaped\\\ttab ";
|
||||
ss << "escaped\\(paren ";
|
||||
ss << "escaped\\)close ";
|
||||
ss << "escaped\\;comment ";
|
||||
ss << "escaped\\\\ backslash "; // second '\' shouldn't escape ' '
|
||||
lexer.pushSource(ss);
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("escaped\\ space", s_string.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("escaped\\\ttab", s_string.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("escaped\\(paren", s_string.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("escaped\\)close", s_string.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("escaped\\;comment", s_string.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' in mid
|
||||
stringTokenCheck("escaped\\\\", s_string.getToken(lexer));
|
||||
|
||||
// Confirm the word that follows the escaped '\' is correctly recognized.
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("backslash", s_string.getToken(lexer));
|
||||
}
|
||||
|
||||
TEST_F(MasterLexerStateTest, quotedString) {
|
||||
ss << "\"ignore-quotes\"\n";
|
||||
ss << "\"quoted string\" "; // space is part of the qstring
|
||||
// also check other separator characters. note that \r doesn't cause
|
||||
// UNBALANCED_QUOTES. Not sure if it's intentional, but that's how the
|
||||
// BIND 9 version works, so we follow it (it should be too minor to matter
|
||||
// in practice anyway)
|
||||
ss << "\"quoted()\t\rstring\" ";
|
||||
ss << "\"escape\\ in quote\" ";
|
||||
ss << "\"escaped\\\"\" ";
|
||||
ss << "\"escaped backslash\\\\\" ";
|
||||
ss << "\"no;comment\"";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// by default, '"' doesn't have any special meaning and part of string
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer); // recognize str, see \n
|
||||
stringTokenCheck("\"ignore-quotes\"", s_string.getToken(lexer));
|
||||
EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
|
||||
EXPECT_TRUE(s_string.wasLastEOL(lexer));
|
||||
|
||||
// If QSTRING is specified in option, '"' is regarded as a beginning of
|
||||
// a quoted string.
|
||||
const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
EXPECT_FALSE(s_string.wasLastEOL(lexer)); // EOL is canceled due to '"'
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("quoted string", s_string.getToken(lexer), true);
|
||||
|
||||
// Also checks other separator characters within a qstring
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("quoted()\t\rstring", s_string.getToken(lexer), true);
|
||||
|
||||
// escape character mostly doesn't have any effect in the qstring
|
||||
// processing
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("escape\\ in quote", s_string.getToken(lexer), true);
|
||||
|
||||
// The only exception is the quotation mark itself. Note that the escape
|
||||
// only works on the quotation mark immediately after it.
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("escaped\"", s_string.getToken(lexer), true);
|
||||
|
||||
// quoted '\' then '"'. Unlike the previous case '"' shouldn't be
|
||||
// escaped.
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("escaped backslash\\\\", s_string.getToken(lexer), true);
|
||||
|
||||
// ';' has no meaning in a quoted string (not indicating a comment)
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("no;comment", s_string.getToken(lexer), true);
|
||||
}
|
||||
|
||||
TEST_F(MasterLexerStateTest, brokenQuotedString) {
|
||||
ss << "\"unbalanced-quote\n";
|
||||
ss << "\"quoted\\\n\" ";
|
||||
ss << "\"unclosed quote and EOF";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// EOL is encountered without closing the quote
|
||||
const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
|
||||
EXPECT_EQ(Token::UNBALANCED_QUOTES,
|
||||
s_qstring.getToken(lexer).getErrorCode());
|
||||
// We can resume after the error from the '\n'
|
||||
EXPECT_EQ(s_null, State::start(lexer, options));
|
||||
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
|
||||
|
||||
// \n is okay in a quoted string if escaped
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
stringTokenCheck("quoted\\\n", s_string.getToken(lexer), true);
|
||||
|
||||
// EOF is encountered without closing the quote
|
||||
EXPECT_EQ(&s_qstring, State::start(lexer, options));
|
||||
s_qstring.handle(lexer);
|
||||
ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
|
||||
EXPECT_EQ(Token::UNEXPECTED_END, s_qstring.getToken(lexer).getErrorCode());
|
||||
// If we continue we'll simply see the EOF
|
||||
EXPECT_EQ(s_null, State::start(lexer, options));
|
||||
EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
|
||||
}
|
||||
|
||||
TEST_F(MasterLexerStateTest, basicNumbers) {
|
||||
ss << "0 ";
|
||||
ss << "1 ";
|
||||
ss << "12345 ";
|
||||
ss << "4294967295 "; // 2^32-1
|
||||
ss << "4294967296 "; // Out of range
|
||||
ss << "340282366920938463463374607431768211456 ";
|
||||
// Very much out of range (2^128)
|
||||
ss << "005 "; // Leading zeroes are ignored
|
||||
ss << "42;asdf\n"; // Number with comment
|
||||
ss << "37"; // Simple number again, here to make
|
||||
// sure none of the above messed up
|
||||
// the tokenizer
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// Ask the lexer to recognize numbers as well
|
||||
const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(0, s_number.getToken(lexer).getNumber());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(1, s_number.getToken(lexer).getNumber());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(12345, s_number.getToken(lexer).getNumber());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(4294967295u, s_number.getToken(lexer).getNumber());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
|
||||
s_number.getToken(lexer).getErrorCode());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
|
||||
s_number.getToken(lexer).getErrorCode());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(5, s_number.getToken(lexer).getNumber());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(42, s_number.getToken(lexer).getNumber());
|
||||
|
||||
EXPECT_EQ(s_null, State::start(lexer, options));
|
||||
EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
|
||||
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
EXPECT_EQ(37, s_number.getToken(lexer).getNumber());
|
||||
|
||||
// If we continue we'll simply see the EOF
|
||||
EXPECT_EQ(s_null, State::start(lexer, options));
|
||||
EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
|
||||
}
|
||||
|
||||
// Test tokens that look like (or start out as) numbers,
|
||||
// but turn out to be strings. Tests include escaped characters.
|
||||
TEST_F(MasterLexerStateTest, stringNumbers) {
|
||||
ss << "123 "; // Should be read as a string if the
|
||||
// NUMBER option is not given
|
||||
ss << "-1 "; // Negative numbers are interpreted
|
||||
// as strings (unsigned integers only)
|
||||
ss << "123abc456 "; // 'Numbers' containing non-digits should
|
||||
// be interpreted as strings
|
||||
ss << "123\\456 "; // Numbers containing escaped digits are
|
||||
// interpreted as strings
|
||||
ss << "3scaped\\ space ";
|
||||
ss << "3scaped\\\ttab ";
|
||||
ss << "3scaped\\(paren ";
|
||||
ss << "3scaped\\)close ";
|
||||
ss << "3scaped\\;comment ";
|
||||
ss << "3scaped\\\\ 8ackslash "; // second '\' shouldn't escape ' '
|
||||
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// Note that common_options does not include MasterLexer::NUMBER,
|
||||
// so the token should be recognized as a string
|
||||
EXPECT_EQ(&s_string, State::start(lexer, common_options));
|
||||
s_string.handle(lexer);
|
||||
stringTokenCheck("123", s_string.getToken(lexer), false);
|
||||
|
||||
// Ask the lexer to recognize numbers as well
|
||||
const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
|
||||
|
||||
EXPECT_EQ(&s_string, State::start(lexer, options));
|
||||
s_string.handle(lexer);
|
||||
stringTokenCheck("-1", s_string.getToken(lexer), false);
|
||||
|
||||
// Starts out as a number, but ends up being a string
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
stringTokenCheck("123abc456", s_number.getToken(lexer), false);
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer);
|
||||
stringTokenCheck("123\\456", s_number.getToken(lexer), false);
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("3scaped\\ space", s_number.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("3scaped\\\ttab", s_number.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("3scaped\\(paren", s_number.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("3scaped\\)close", s_number.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("3scaped\\;comment", s_number.getToken(lexer));
|
||||
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' in mid
|
||||
stringTokenCheck("3scaped\\\\", s_number.getToken(lexer));
|
||||
|
||||
// Confirm the word that follows the escaped '\' is correctly recognized.
|
||||
EXPECT_EQ(&s_number, State::start(lexer, options));
|
||||
s_number.handle(lexer); // recognize str, see ' ' at end
|
||||
stringTokenCheck("8ackslash", s_number.getToken(lexer));
|
||||
|
||||
// If we continue we'll simply see the EOF
|
||||
EXPECT_EQ(s_null, State::start(lexer, options));
|
||||
EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
|
@@ -48,6 +48,9 @@ TEST_F(MasterLexerTokenTest, strings) {
|
||||
// basic construction and getter checks
|
||||
EXPECT_EQ(MasterLexer::Token::STRING, token_str.getType());
|
||||
EXPECT_EQ(std::string("string token"), token_str.getString());
|
||||
std::string strval = "dummy"; // this should be replaced
|
||||
token_str.getString(strval);
|
||||
EXPECT_EQ(std::string("string token"), strval);
|
||||
const MasterLexer::Token::StringRegion str_region =
|
||||
token_str.getStringRegion();
|
||||
EXPECT_EQ(TEST_STRING, str_region.beg);
|
||||
@@ -60,6 +63,8 @@ TEST_F(MasterLexerTokenTest, strings) {
|
||||
expected_str.push_back('\0');
|
||||
EXPECT_EQ(expected_str,
|
||||
MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString());
|
||||
MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString(strval);
|
||||
EXPECT_EQ(expected_str, strval);
|
||||
|
||||
// Construct type of qstring
|
||||
EXPECT_EQ(MasterLexer::Token::QSTRING,
|
||||
@@ -72,7 +77,9 @@ TEST_F(MasterLexerTokenTest, strings) {
|
||||
|
||||
// getString/StringRegion() aren't allowed for non string(-variant) types
|
||||
EXPECT_THROW(token_eof.getString(), isc::InvalidOperation);
|
||||
EXPECT_THROW(token_eof.getString(strval), isc::InvalidOperation);
|
||||
EXPECT_THROW(token_num.getString(), isc::InvalidOperation);
|
||||
EXPECT_THROW(token_num.getString(strval), isc::InvalidOperation);
|
||||
EXPECT_THROW(token_eof.getStringRegion(), isc::InvalidOperation);
|
||||
EXPECT_THROW(token_num.getStringRegion(), isc::InvalidOperation);
|
||||
}
|
||||
@@ -135,15 +142,21 @@ TEST_F(MasterLexerTokenTest, errors) {
|
||||
EXPECT_EQ("unbalanced quotes",
|
||||
MasterLexer::Token(MasterLexer::Token::UNBALANCED_QUOTES).
|
||||
getErrorText());
|
||||
EXPECT_EQ("no token produced",
|
||||
MasterLexer::Token(MasterLexer::Token::NO_TOKEN_PRODUCED).
|
||||
getErrorText());
|
||||
EXPECT_EQ("number out of range",
|
||||
MasterLexer::Token(MasterLexer::Token::NUMBER_OUT_OF_RANGE).
|
||||
getErrorText());
|
||||
|
||||
// getErrorCode/Text() isn't allowed for non number types
|
||||
EXPECT_THROW(token_num.getErrorCode(), isc::InvalidOperation);
|
||||
EXPECT_THROW(token_num.getErrorText(), isc::InvalidOperation);
|
||||
|
||||
// Only the pre-defined error code is accepted. Hardcoding '4' (max code
|
||||
// Only the pre-defined error code is accepted. Hardcoding '6' (max code
|
||||
// + 1) is intentional; it'd be actually better if we notice it when we
|
||||
// update the enum list (which shouldn't happen too often).
|
||||
EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(4)),
|
||||
EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(6)),
|
||||
isc::InvalidParameter);
|
||||
|
||||
// Check the coexistence of "from number" and "from error-code"
|
||||
|
@@ -15,10 +15,14 @@
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <dns/master_lexer.h>
|
||||
#include <dns/master_lexer_state.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@@ -27,6 +31,8 @@ using namespace isc::dns;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using boost::lexical_cast;
|
||||
using boost::scoped_ptr;
|
||||
using master_lexer_internal::State;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -124,4 +130,158 @@ TEST_F(MasterLexerTest, invalidPop) {
|
||||
EXPECT_THROW(lexer.popSource(), isc::InvalidOperation);
|
||||
}
|
||||
|
||||
// Test it is not possible to get token when no source is available.
|
||||
TEST_F(MasterLexerTest, noSource) {
|
||||
EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
|
||||
}
|
||||
|
||||
// Test getting some tokens
|
||||
TEST_F(MasterLexerTest, getNextToken) {
|
||||
ss << "\n \n\"STRING\"\n";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// First, the newline should get out.
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// Then the whitespace, if we specify the option.
|
||||
EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
|
||||
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
|
||||
// The newline
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// The (quoted) string
|
||||
EXPECT_EQ(MasterLexer::Token::QSTRING,
|
||||
lexer.getNextToken(MasterLexer::QSTRING).getType());
|
||||
|
||||
// And the end of line and file
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
|
||||
}
|
||||
|
||||
// Test we correctly find end of file.
|
||||
TEST_F(MasterLexerTest, eof) {
|
||||
// Let the ss empty.
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// The first one is found to be EOF
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
|
||||
// And it stays on EOF for any following attempts
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
|
||||
// And we can step back one token, but that is the EOF too.
|
||||
lexer.ungetToken();
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
|
||||
}
|
||||
|
||||
// Check we properly return error when there's an opened parentheses and no
|
||||
// closing one
|
||||
TEST_F(MasterLexerTest, getUnbalancedParen) {
|
||||
ss << "(\"string\"";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// The string gets out first
|
||||
EXPECT_EQ(MasterLexer::Token::STRING, lexer.getNextToken().getType());
|
||||
// Then an unbalanced parenthesis
|
||||
EXPECT_EQ(MasterLexer::Token::UNBALANCED_PAREN,
|
||||
lexer.getNextToken().getErrorCode());
|
||||
// And then EOF
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
|
||||
}
|
||||
|
||||
// Check we properly return error when there's an opened quoted string and no
|
||||
// closing one
|
||||
TEST_F(MasterLexerTest, getUnbalancedString) {
|
||||
ss << "\"string";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// Then an unbalanced qstring (reported as an unexpected end)
|
||||
EXPECT_EQ(MasterLexer::Token::UNEXPECTED_END,
|
||||
lexer.getNextToken(MasterLexer::QSTRING).getErrorCode());
|
||||
// And then EOF
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
|
||||
}
|
||||
|
||||
// Test ungetting tokens works
|
||||
TEST_F(MasterLexerTest, ungetToken) {
|
||||
ss << "\n (\"string\"\n) more";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
// Try getting the newline
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// Return it and get again
|
||||
lexer.ungetToken();
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// Get the string and return it back
|
||||
EXPECT_EQ(MasterLexer::Token::QSTRING,
|
||||
lexer.getNextToken(MasterLexer::QSTRING).getType());
|
||||
lexer.ungetToken();
|
||||
// But if we change the options, it honors them
|
||||
EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
|
||||
lexer.getNextToken(MasterLexer::QSTRING |
|
||||
MasterLexer::INITIAL_WS).getType());
|
||||
// Get to the "more" string
|
||||
EXPECT_EQ(MasterLexer::Token::QSTRING,
|
||||
lexer.getNextToken(MasterLexer::QSTRING).getType());
|
||||
EXPECT_EQ(MasterLexer::Token::STRING,
|
||||
lexer.getNextToken(MasterLexer::QSTRING).getType());
|
||||
// Return it back. It should get inside the parentheses.
|
||||
// Upon next attempt to get it again, the newline inside the parentheses
|
||||
// should be still ignored.
|
||||
lexer.ungetToken();
|
||||
EXPECT_EQ(MasterLexer::Token::STRING,
|
||||
lexer.getNextToken(MasterLexer::QSTRING).getType());
|
||||
}
|
||||
|
||||
// Check ungetting token without overriding the start method. We also
|
||||
// check it works well with changing options between the calls.
|
||||
TEST_F(MasterLexerTest, ungetRealOptions) {
|
||||
ss << "\n \n";
|
||||
lexer.pushSource(ss);
|
||||
// Skip the first newline
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
|
||||
// If we call it the usual way, it skips up to the newline and returns
|
||||
// it
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
|
||||
// Now we return it. If we call it again, but with different options,
|
||||
// we get the initial whitespace.
|
||||
lexer.ungetToken();
|
||||
EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
|
||||
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
|
||||
}
|
||||
|
||||
// Test only one token can be ungotten
|
||||
TEST_F(MasterLexerTest, ungetTwice) {
|
||||
ss << "\n";
|
||||
lexer.pushSource(ss);
|
||||
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// Unget the token. It can be done once
|
||||
lexer.ungetToken();
|
||||
// But not twice
|
||||
EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
|
||||
}
|
||||
|
||||
// Test we can't unget a token before we get one
|
||||
TEST_F(MasterLexerTest, ungetBeforeGet) {
|
||||
lexer.pushSource(ss); // Just to eliminate the missing source problem
|
||||
EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
|
||||
}
|
||||
|
||||
// Test we can't unget a token after a source switch, even when we got
|
||||
// something before.
|
||||
TEST_F(MasterLexerTest, ungetAfterSwitch) {
|
||||
ss << "\n\n";
|
||||
lexer.pushSource(ss);
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// Switch the source
|
||||
std::stringstream ss2;
|
||||
ss2 << "\n\n";
|
||||
lexer.pushSource(ss2);
|
||||
EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
|
||||
// We can get from the new source
|
||||
EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
|
||||
// And when we drop the current source, we can't unget again
|
||||
lexer.popSource();
|
||||
EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
|
||||
}
|
||||
|
||||
}
|
||||
|
84
src/lib/dns/tests/master_loader_callbacks_test.cc
Normal file
84
src/lib/dns/tests/master_loader_callbacks_test.cc
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <dns/master_loader_callbacks.h>
|
||||
#include <dns/rrset.h>
|
||||
#include <dns/name.h>
|
||||
#include <dns/rrttl.h>
|
||||
#include <dns/rrclass.h>
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
using std::string;
|
||||
using namespace isc::dns;
|
||||
|
||||
class MasterLoaderCallbacksTest : public ::testing::Test {
|
||||
protected:
|
||||
MasterLoaderCallbacksTest() :
|
||||
last_was_error_(false), // Not needed, but then cppcheck complains
|
||||
issue_called_(false),
|
||||
rrset_(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
|
||||
RRTTL(3600))),
|
||||
error_(boost::bind(&MasterLoaderCallbacksTest::checkCallback, this,
|
||||
true, _1, _2, _3)),
|
||||
warning_(boost::bind(&MasterLoaderCallbacksTest::checkCallback, this,
|
||||
false, _1, _2, _3)),
|
||||
callbacks_(error_, warning_)
|
||||
{}
|
||||
|
||||
void checkCallback(bool error, const string& source, size_t line,
|
||||
const string& reason)
|
||||
{
|
||||
issue_called_ = true;
|
||||
last_was_error_ = error;
|
||||
EXPECT_EQ("source", source);
|
||||
EXPECT_EQ(1, line);
|
||||
EXPECT_EQ("reason", reason);
|
||||
}
|
||||
bool last_was_error_;
|
||||
bool issue_called_;
|
||||
const RRsetPtr rrset_;
|
||||
const MasterLoaderCallbacks::IssueCallback error_, warning_;
|
||||
MasterLoaderCallbacks callbacks_;
|
||||
};
|
||||
|
||||
// Check the constructor rejects empty callbacks, but accepts non-empty ones
|
||||
TEST_F(MasterLoaderCallbacksTest, constructor) {
|
||||
EXPECT_THROW(MasterLoaderCallbacks(MasterLoaderCallbacks::IssueCallback(),
|
||||
warning_), isc::InvalidParameter);
|
||||
EXPECT_THROW(MasterLoaderCallbacks(error_,
|
||||
MasterLoaderCallbacks::IssueCallback()),
|
||||
isc::InvalidParameter);
|
||||
EXPECT_NO_THROW(MasterLoaderCallbacks(error_, warning_));
|
||||
}
|
||||
|
||||
// Call the issue callbacks
|
||||
TEST_F(MasterLoaderCallbacksTest, issueCall) {
|
||||
callbacks_.error("source", 1, "reason");
|
||||
EXPECT_TRUE(last_was_error_);
|
||||
EXPECT_TRUE(issue_called_);
|
||||
|
||||
issue_called_ = false;
|
||||
|
||||
callbacks_.warning("source", 1, "reason");
|
||||
EXPECT_FALSE(last_was_error_);
|
||||
EXPECT_TRUE(issue_called_);
|
||||
}
|
||||
|
||||
}
|
@@ -203,7 +203,7 @@ void LoggerManagerImpl::setConsoleAppenderLayout(
|
||||
log4cplus::SharedAppenderPtr& appender)
|
||||
{
|
||||
// Create the pattern we want for the output - local time.
|
||||
string pattern = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c] %m\n";
|
||||
string pattern = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n";
|
||||
|
||||
// Finally the text of the message
|
||||
auto_ptr<log4cplus::Layout> layout(new log4cplus::PatternLayout(pattern));
|
||||
|
@@ -127,3 +127,10 @@ check-local:
|
||||
$(SHELL) $(abs_builddir)/local_file_test.sh
|
||||
$(SHELL) $(abs_builddir)/logger_lock_test.sh
|
||||
$(SHELL) $(abs_builddir)/severity_test.sh
|
||||
|
||||
noinst_SCRIPTS = console_test.sh
|
||||
noinst_SCRIPTS += destination_test.sh
|
||||
noinst_SCRIPTS += init_logger_test.sh
|
||||
noinst_SCRIPTS += local_file_test.sh
|
||||
noinst_SCRIPTS += logger_lock_test.sh
|
||||
noinst_SCRIPTS += severity_test.sh
|
||||
|
@@ -20,6 +20,8 @@ echo $testname
|
||||
|
||||
failcount=0
|
||||
tempfile=@abs_builddir@/destination_test_tempfile_$$
|
||||
destfile1_tmp=@abs_builddir@/destination_test_destfile_1_tmp_$$
|
||||
destfile2_tmp=@abs_builddir@/destination_test_destfile_2_tmp_$$
|
||||
destfile1=@abs_builddir@/destination_test_destfile_1_$$
|
||||
destfile2=@abs_builddir@/destination_test_destfile_2_$$
|
||||
|
||||
@@ -40,7 +42,11 @@ FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
|
||||
ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
|
||||
.
|
||||
rm -f $destfile1 $destfile2
|
||||
./logger_example -s error -f $destfile1 -f $destfile2
|
||||
./logger_example -s error -f $destfile1_tmp -f $destfile2_tmp
|
||||
|
||||
# strip the pids
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
|
||||
|
||||
echo -n " - destination 1:"
|
||||
cut -d' ' -f3- $destfile1 | diff $tempfile -
|
||||
@@ -50,9 +56,16 @@ echo -n " - destination 2:"
|
||||
cut -d' ' -f3- $destfile2 | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
# Tidy up.
|
||||
rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
|
||||
|
||||
echo "2. Two loggers, different destinations and severities"
|
||||
rm -f $destfile1 $destfile2
|
||||
./logger_example -l example -s info -f $destfile1 -l alpha -s warn -f $destfile2
|
||||
./logger_example -l example -s info -f $destfile1_tmp -l alpha -s warn -f $destfile2_tmp
|
||||
|
||||
# strip the pids
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
|
||||
|
||||
# All output for example and example.beta should have gone to destfile1.
|
||||
# Output for example.alpha should have done to destfile2.
|
||||
@@ -86,6 +99,6 @@ else
|
||||
fi
|
||||
|
||||
# Tidy up.
|
||||
rm -f $tempfile $destfile1 $destfile2
|
||||
rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
|
||||
|
||||
exit $failcount
|
||||
|
@@ -21,6 +21,7 @@ echo $testname
|
||||
|
||||
failcount=0
|
||||
tempfile=@abs_builddir@/init_logger_test_tempfile_$$
|
||||
destfile_tmp=@abs_builddir@/init_logger_test_destfile_tmp_$$
|
||||
destfile=@abs_builddir@/init_logger_test_destfile_$$
|
||||
|
||||
passfail() {
|
||||
@@ -45,6 +46,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
|
||||
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
|
||||
.
|
||||
B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=DEBUG B10_LOGGER_DBGLEVEL=99 ./init_logger_test | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
@@ -58,6 +60,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
|
||||
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
|
||||
.
|
||||
B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=DEBUG B10_LOGGER_DBGLEVEL=50 ./init_logger_test | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
@@ -68,6 +71,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
|
||||
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
|
||||
.
|
||||
B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=WARN ./init_logger_test | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
@@ -77,20 +81,23 @@ echo -n " - stdout: "
|
||||
cat > $tempfile << .
|
||||
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
|
||||
.
|
||||
rm -f $destfile
|
||||
B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile
|
||||
rm -f $destfile_tmp $destfile
|
||||
B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile_tmp
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
|
||||
cut -d' ' -f3- $destfile | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
echo -n " - stderr: "
|
||||
rm -f $destfile
|
||||
B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile
|
||||
rm -f $destfile_tmp $destfile
|
||||
B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile_tmp
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
|
||||
cut -d' ' -f3- $destfile | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
echo -n " - file: "
|
||||
rm -f $destfile
|
||||
B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=$destfile ./init_logger_test
|
||||
rm -f $destfile_tmp $destfile
|
||||
B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=$destfile_tmp ./init_logger_test
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
|
||||
cut -d' ' -f3- $destfile | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
@@ -105,6 +112,6 @@ else
|
||||
fi
|
||||
|
||||
# Tidy up.
|
||||
rm -f $tempfile $destfile
|
||||
rm -f $tempfile $destfile_tmp $destfile
|
||||
|
||||
exit $failcount
|
||||
|
@@ -51,7 +51,9 @@ FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
|
||||
ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
|
||||
WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
|
||||
.
|
||||
./logger_example -c stdout -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
|
||||
./logger_example -c stdout -s warn $localmes | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
echo -n "2. Report error if unable to read local message file:"
|
||||
@@ -66,7 +68,9 @@ ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_erro
|
||||
WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
|
||||
.
|
||||
rm -f $localmes
|
||||
./logger_example -c stdout -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
|
||||
./logger_example -c stdout -s warn $localmes | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
if [ $failcount -eq 0 ]; then
|
||||
|
@@ -36,7 +36,8 @@ INFO [bind10.log] LOG_LOCK_TEST_MESSAGE this is a test message.
|
||||
LOGGER_LOCK_TEST: UNLOCK
|
||||
.
|
||||
rm -f $destfile
|
||||
B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test > $destfile
|
||||
B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' > $destfile
|
||||
cut -d' ' -f3- $destfile | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
|
@@ -43,7 +43,9 @@ ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_erro
|
||||
WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
|
||||
INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
|
||||
.
|
||||
./logger_example -c stdout | cut -d' ' -f3- | diff $tempfile -
|
||||
./logger_example -c stdout | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
echo -n "2. Severity filter:"
|
||||
@@ -53,7 +55,9 @@ ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
|
||||
FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
|
||||
ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
|
||||
.
|
||||
./logger_example -c stdout -s error | cut -d' ' -f3- | diff $tempfile -
|
||||
./logger_example -c stdout -s error | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
echo -n "3. Debug level:"
|
||||
@@ -72,7 +76,9 @@ WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
|
||||
INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
|
||||
DEBUG [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta/25
|
||||
.
|
||||
./logger_example -c stdout -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
|
||||
./logger_example -c stdout -s debug -d 25 | \
|
||||
sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
|
||||
cut -d' ' -f3- | diff $tempfile -
|
||||
passfail $?
|
||||
|
||||
if [ $failcount -eq 0 ]; then
|
||||
|
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
"$1" 2>&1 | cut -d\ -f3- | diff - "$2" 1>&2
|
||||
"$1" 2>&1 | sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | cut -d\ -f3- | diff - "$2" 1>&2
|
||||
|
@@ -896,7 +896,7 @@ TEST_F(TestControlTest, Packet6) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestControlTest, Packet4Exchange) {
|
||||
TEST_F(TestControlTest, DISABLED_Packet4Exchange) {
|
||||
// Get the local loopback interface to open socket on
|
||||
// it and test packets exchanges. We don't want to fail
|
||||
// the test if interface is not available.
|
||||
@@ -939,7 +939,7 @@ TEST_F(TestControlTest, Packet4Exchange) {
|
||||
EXPECT_EQ(12, iterations_performed);
|
||||
}
|
||||
|
||||
TEST_F(TestControlTest, Packet6Exchange) {
|
||||
TEST_F(TestControlTest, DISABLED_Packet6Exchange) {
|
||||
// Get the local loopback interface to open socket on
|
||||
// it and test packets exchanges. We don't want to fail
|
||||
// the test if interface is not available.
|
||||
|
Reference in New Issue
Block a user