Commit a6865a76 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac2382'

with fixing Conflicts for
	src/lib/dns/gen-rdatacode.py.in
	src/lib/dns/rdata.cc
	src/lib/dns/rrparamregistry-placeholder.cc
	src/lib/dns/tests/rdata_hinfo_unittest.cc
	src/lib/dns/tests/rdata_txt_like_unittest.cc
parents e6647238 816eacf4
......@@ -32,7 +32,7 @@ import sys
#
# Example:
# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
new_rdata_factory_users = []
new_rdata_factory_users = [('aaaa', 'in')]
re_typecode = re.compile('([\da-z]+)_(\d+)')
classcode2txt = {}
......@@ -126,6 +126,9 @@ class AbstractMessageRenderer;\n\n'''
explicit ''' + type_utxt + '''(const std::string& type_str);
''' + type_utxt + '''(isc::util::InputBuffer& buffer, size_t rdata_len);
''' + type_utxt + '''(const ''' + type_utxt + '''& other);
''' + type_utxt + '''(
MasterLexer& lexer, const Name* name,
MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
virtual std::string toText() const;
virtual void toWire(isc::util::OutputBuffer& buffer) const;
virtual void toWire(AbstractMessageRenderer& renderer) const;
......@@ -213,17 +216,33 @@ def generate_rdatadef(file, basemtime):
rdata_deffile.write(class_definitions)
rdata_deffile.close()
def generate_rdatahdr(file, declarations, basemtime):
def generate_rdatahdr(file, heading, declarations, basemtime):
if not need_generate(file, basemtime):
print('skip generating ' + file);
return
heading += '''
#ifndef DNS_RDATACLASS_H
#define DNS_RDATACLASS_H 1
#include <dns/master_loader.h>
namespace isc {
namespace dns {
class Name;
class MasterLexer;
class MasterLoaderCallbacks;
}
}
'''
declarations += '''
#endif // DNS_RDATACLASS_H
// Local Variables:
// mode: c++
// End:
'''
rdata_header = open(file, 'w')
rdata_header.write(heading_txt)
rdata_header.write(heading)
rdata_header.write(declarations)
rdata_header.close()
......@@ -320,8 +339,8 @@ if __name__ == "__main__":
try:
import_definitions(classcode2txt, typecode2txt, typeandclass)
generate_rdatadef('@builddir@/rdataclass.cc', rdatadef_mtime)
generate_rdatahdr('@builddir@/rdataclass.h', rdata_declarations,
rdatahdr_mtime)
generate_rdatahdr('@builddir@/rdataclass.h', heading_txt,
rdata_declarations, rdatahdr_mtime)
generate_typeclasscode('rrtype', rdatahdr_mtime, typecode2txt, 'Type')
generate_typeclasscode('rrclass', classdir_mtime,
classcode2txt, 'Class')
......
......@@ -458,8 +458,11 @@ String::handle(MasterLexer& lexer) const {
if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
getLexerImpl(lexer)->source_->ungetChar();
// make sure it nul-terminated as a c-str (excluded from token
// data).
data.push_back('\0');
getLexerImpl(lexer)->token_ =
MasterToken(&data.at(0), data.size());
MasterToken(&data.at(0), data.size() - 1);
return;
}
escaped = (c == '\\' && !escaped);
......@@ -486,7 +489,10 @@ QString::handle(MasterLexer& lexer) const {
escaped = false;
data.back() = '"';
} else {
token = MasterToken(&data.at(0), data.size(), true);
// make sure it nul-terminated as a c-str (excluded from token
// data). This also simplifies the case of an empty string.
data.push_back('\0');
token = MasterToken(&data.at(0), data.size() - 1, true);
return;
}
} else if (c == '\n' && !escaped) {
......@@ -516,9 +522,10 @@ Number::handle(MasterLexer& lexer) const {
getLexerImpl(lexer)->source_->getChar(), escaped);
if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
getLexerImpl(lexer)->source_->ungetChar();
// We need to close the string whether it's digits-only (for
// lexical_cast) or not (see String::handle()).
data.push_back('\0');
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]);
......@@ -529,7 +536,7 @@ Number::handle(MasterLexer& lexer) const {
token = MasterToken(MasterToken::NUMBER_OUT_OF_RANGE);
}
} else {
token = MasterToken(&data.at(0), data.size());
token = MasterToken(&data.at(0), data.size() - 1);
}
return;
}
......
......@@ -90,6 +90,13 @@ public:
/// the region. On the other hand, it is not ensured that the string
/// is nul-terminated. So the usual string manipulation API may not work
/// as expected.
///
/// The `MasterLexer` implementation ensures that there are at least
/// len + 1 bytes of valid memory region starting from beg, and that
/// beg[len] is \0. This means the application can use the bytes as a
/// validly nul-terminated C string if there is no intermediate nul
/// character. Note also that due to this property beg is always non
/// NULL; for an empty string len will be set to 0 and beg[0] is \0.
struct StringRegion {
const char* beg; ///< The start address of the string
size_t len; ///< The length of the string in bytes
......
......@@ -12,6 +12,20 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/master_lexer.h>
#include <dns/rdata.h>
#include <dns/rrparamregistry.h>
#include <dns/rrtype.h>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <algorithm>
#include <cctype>
#include <string>
......@@ -24,16 +38,6 @@
#include <stdint.h>
#include <string.h>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rrparamregistry.h>
#include <dns/rrtype.h>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
......@@ -81,23 +85,92 @@ createRdata(const RRType& rrtype, const RRClass& rrclass, const Rdata& source)
source));
}
namespace {
void
fromtextError(bool& error_issued, const MasterLexer& lexer,
MasterLoaderCallbacks& callbacks,
const MasterToken* token, const char* reason)
{
// Don't be too noisy if there are many issues for single RDATA
if (error_issued) {
return;
}
error_issued = true;
if (token == NULL) {
callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
"createRdata from text failed: " + string(reason));
return;
}
switch (token->getType()) {
case MasterToken::STRING:
case MasterToken::QSTRING:
callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
"createRdata from text failed near '" +
token->getString() + "': " + string(reason));
break;
case MasterToken::ERROR:
callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
"createRdata from text failed: " +
token->getErrorText());
break;
default:
// This case shouldn't happen based on how we use MasterLexer in
// createRdata(), so we could assert() that here. But since it
// depends on detailed behavior of other classes, we treat the case
// in a bit less harsh way.
isc_throw(Unexpected, "bug: createRdata() saw unexpected token type");
}
}
}
RdataPtr
createRdata(const RRType& rrtype, const RRClass& rrclass,
MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
MasterLoaderCallbacks& callbacks)
{
RdataPtr ret;
RdataPtr rdata;
bool error_issued = false;
try {
ret = RRParamRegistry::getRegistry().createRdata(rrtype, rrclass,
lexer, origin,
options, callbacks);
} catch (...) {
// ret is NULL here.
rdata = RRParamRegistry::getRegistry().createRdata(
rrtype, rrclass, lexer, origin, options, callbacks);
} catch (const MasterLexer::LexerError& error) {
fromtextError(error_issued, lexer, callbacks, &error.token_, "");
} catch (const Exception& ex) {
// Catching all isc::Exception is too broad, but right now we don't
// have better granularity. When we complete #2518 we can make this
// finer.
fromtextError(error_issued, lexer, callbacks, NULL, ex.what());
}
// Other exceptions mean a serious implementation bug or fatal system
// error; it doesn't make sense to catch and try to recover from them
// here. Just propagate.
// Consume to end of line / file.
// Call callback via fromtextError once if there was an error.
do {
const MasterToken& token = lexer.getNextToken();
switch (token.getType()) {
case MasterToken::END_OF_LINE:
return (rdata);
case MasterToken::END_OF_FILE:
callbacks.warning(lexer.getSourceName(), lexer.getSourceLine(),
"file does not end with newline");
return (rdata);
default:
rdata.reset(); // we'll return NULL
fromtextError(error_issued, lexer, callbacks, &token,
"extra input text");
// Continue until we see EOL or EOF
}
} while (true);
return (ret);
// We shouldn't reach here
assert(false);
return (RdataPtr()); // add explicit return to silence some compilers
}
int
......@@ -214,6 +287,7 @@ Generic::Generic(MasterLexer& lexer, const Name*,
const MasterToken& token = lexer.getNextToken();
if ((token.getType() == MasterToken::END_OF_FILE) ||
(token.getType() == MasterToken::END_OF_LINE)) {
lexer.ungetToken(); // let the upper layer handle the end-of token
break;
}
......
......@@ -485,8 +485,47 @@ RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
const Rdata& source);
/// \brief Create RDATA of a given pair of RR type and class from the
/// \brief Create RDATA of a given pair of RR type and class using the
/// master lexer.
///
/// This is a more generic form of factory from textual RDATA, and is mainly
/// intended to be used internally by the master file parser (\c MasterLoader)
/// of this library.
///
/// The \c lexer is expected to be at the beginning of textual RDATA of the
/// specified type and class. This function (and its underlying Rdata
/// implementations) extracts necessary tokens from the lexer and constructs
/// the RDATA from them.
///
/// Due to the intended usage of this version, this function handles error
/// cases quite differently from other versions. It internally catches
/// most of syntax and semantics errors of the input (reported as exceptions),
/// calls the corresponding callback specified by the \c callbacks parameters,
/// and returns a NULL smart pointer. If the caller rather wants to get
/// an exception in these cases, it can pass a callback that internally
/// throws on error. Some critical exceptions such as \c std::bad_alloc are
/// still propagated to the upper layer as it doesn't make sense to try
/// recovery from such a situation within this function.
///
/// Whether or not the creation succeeds, this function updates the lexer
/// until it reaches either the end of line or file, starting from the end of
/// the RDATA text (or the point of failure if the parsing fails in the
/// middle of it). The caller can therefore assume it's ready for reading
/// the next data (which is normally a subsequent RR in the zone file) on
/// return, whether or not this function succeeds.
///
/// \param rrtype An \c RRType object specifying the type/class pair.
/// \param rrclass An \c RRClass object specifying the type/class pair.
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
/// \param origin If non NULL, specifies the origin of any domain name fields
/// of the RDATA that are non absolute.
/// \param options Master loader options controlling how to deal with errors
/// or non critical issues in the parsed RDATA.
/// \param callbacks Callback to be called when an error or non critical issue
/// is found.
/// \return An \c RdataPtr object pointing to the created
/// \c Rdata object. Will be NULL if parsing fails.
RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
......
......@@ -12,6 +12,15 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/master_lexer.h>
#include <dns/master_loader.h>
#include <stdint.h>
#include <string.h>
......@@ -20,14 +29,6 @@
#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
#include <sys/socket.h> // for AF_INET/AF_INET6
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
using namespace std;
using namespace isc::util;
......@@ -42,6 +43,16 @@ AAAA::AAAA(const std::string& addrstr) {
}
}
AAAA::AAAA(MasterLexer& lexer, const Name*,
MasterLoader::Options, MasterLoaderCallbacks&)
{
const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
if (inet_pton(AF_INET6, token.getStringRegion().beg, &addr_) != 1) {
isc_throw(InvalidRdataText, "Failed to convert '"
<< token.getString() << "' to IN/AAAA RDATA");
}
}
AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) {
if (rdata_len != sizeof(addr_)) {
isc_throw(DNSMessageFORMERR,
......
......@@ -34,6 +34,11 @@ using namespace isc::util;
// If you added member functions specific to this derived class, you'll need
// to implement them here, of course.
MyType::MyType(MasterLexer& lexer, const Name* origin,
MasterLoader::Options options, MasterLoaderCallbacks& callbacks)
{
}
MyType::MyType(const string& type_str) {
}
......
......@@ -54,6 +54,7 @@ AbstractRdataFactory::create(MasterLexer& lexer, const Name*,
const MasterToken& token = lexer.getNextToken();
if ((token.getType() == MasterToken::END_OF_FILE) ||
(token.getType() == MasterToken::END_OF_LINE)) {
lexer.ungetToken(); // let the upper layer handle the end-of token
break;
}
......
......@@ -119,10 +119,22 @@ public:
/// \return An \c RdataPtr object pointing to the created \c Rdata object.
virtual RdataPtr create(const rdata::Rdata& source) const = 0;
/// \brief Create RDATA from MasterLexer
virtual RdataPtr create(MasterLexer& lexer, const Name*,
MasterLoader::Options,
MasterLoaderCallbacks&) const;
/// \brief Create RDATA using MasterLexer.
///
/// This version of the method defines the entry point of factory
/// of a specific RR type and class for \c RRParamRegistry::createRdata()
/// that uses \c MasterLexer. See its description for the expected
/// behavior and meaning of the parameters.
///
/// \note Right now this is not defined as a pure virtual method and
/// provides the default implementation. This is an intermediate
/// workaround until we implement the underlying constructor for all
/// supported \c Rdata classes; once it's completed the workaround
/// default implementation should be removed and this method should become
/// pure virtual.
virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
MasterLoaderCallbacks& callbacks) const;
//@}
};
......@@ -504,9 +516,20 @@ public:
rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
const rdata::Rdata& source);
/// \brief Create RDATA from MasterLexer
/// \brief Create RDATA using MasterLexer
///
/// This method is expected to be used as the underlying implementation
/// of the same signature of \c rdata::createRdata(). One main
/// difference is that this method is only responsible for constructing
/// the Rdata; it doesn't update the lexer to reach the end of line or
/// file or doesn't care about whether there's an extra (garbage) token
/// after the textual RDATA representation. Another difference is that
/// this method can throw on error and never returns a NULL pointer.
///
/// For other details and parameters, see the description of
/// \c rdata::createRdata().
rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
MasterLexer& lexer, const Name* name,
MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
MasterLoaderCallbacks& callbacks);
//@}
......
......@@ -269,6 +269,10 @@ stringTokenCheck(const std::string& expected, const MasterToken& token,
token.getStringRegion().beg +
token.getStringRegion().len);
EXPECT_EQ(expected, actual);
// There should be "hidden" nul-terminator after the string data.
ASSERT_NE(static_cast<const char*>(NULL), token.getStringRegion().beg);
EXPECT_EQ(0, *(token.getStringRegion().beg + token.getStringRegion().len));
}
TEST_F(MasterLexerStateTest, string) {
......@@ -365,6 +369,7 @@ TEST_F(MasterLexerStateTest, stringEscape) {
TEST_F(MasterLexerStateTest, quotedString) {
ss << "\"ignore-quotes\"\n";
ss << "\"quoted string\" "; // space is part of the qstring
ss << "\"\" "; // empty quoted string
// 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
......@@ -391,6 +396,11 @@ TEST_F(MasterLexerStateTest, quotedString) {
s_qstring.handle(lexer);
stringTokenCheck("quoted string", s_string.getToken(lexer), true);
// Empty string is okay as qstring
EXPECT_EQ(&s_qstring, State::start(lexer, options));
s_qstring.handle(lexer);
stringTokenCheck("", 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);
......
......@@ -29,6 +29,7 @@
#include <dns/tests/rdata_unittest.h>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
using isc::UnitTestUtil;
using namespace std;
......@@ -82,6 +83,138 @@ createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
} // end of namespace isc::dns::rdata::test
// A mock class to check parameters passed via loader callbacks. Its callback
// records the passed parameters, allowing the test to check them later via
// the check() method.
class CreateRdataCallback {
public:
enum CallbackType { NONE, ERROR, WARN };
CreateRdataCallback() : type_(NONE), line_(0) {}
void callback(CallbackType type, const string& source, size_t line,
const string& reason_txt) {
type_ = type;
source_ = source;
line_ = line;
reason_txt_ = reason_txt;
}
void clear() {
type_ = NONE;
source_.clear();
line_ = 0;
reason_txt_.clear();
}
// Return if callback is called since the previous call to clear().
bool isCalled() const { return (type_ != NONE); }
void check(const string& expected_srcname, size_t expected_line,
CallbackType expected_type, const string& expected_reason)
const
{
EXPECT_EQ(expected_srcname, source_);
EXPECT_EQ(expected_line, line_);
EXPECT_EQ(expected_type, type_);
EXPECT_EQ(expected_reason, reason_txt_);
}
private:
CallbackType type_;
string source_;
size_t line_;
string reason_txt_;
};
// Test class/type-independent behavior of createRdata().
TEST_F(RdataTest, createRdataWithLexer) {
const in::AAAA aaaa_rdata("2001:db8::1");
stringstream ss;
const string src_name = "stream-" + boost::lexical_cast<string>(&ss);
ss << aaaa_rdata.toText() << "\n"; // valid case
ss << aaaa_rdata.toText() << "; comment, should be ignored\n";
ss << aaaa_rdata.toText() << " extra-token\n"; // extra token
ss << aaaa_rdata.toText() << " extra token\n"; // 2 extra tokens
ss << ")\n"; // causing lexer error in parsing the RDATA text
ss << "192.0.2.1\n"; // semantics error: IPv4 address is given for AAAA
ss << aaaa_rdata.toText(); // valid, but end with EOF, not EOL
lexer.pushSource(ss);
CreateRdataCallback callback;
MasterLoaderCallbacks callbacks(
boost::bind(&CreateRdataCallback::callback, &callback,
CreateRdataCallback::ERROR, _1, _2, _3),
boost::bind(&CreateRdataCallback::callback, &callback,
CreateRdataCallback::WARN, _1, _2, _3));
size_t line = 0;
// Valid case.
++line;
ConstRdataPtr rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer,
NULL, MasterLoader::MANY_ERRORS,
callbacks);
EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
EXPECT_FALSE(callback.isCalled());
// Similar to the previous case, but RDATA is followed by a comment.
// It should cause any confusion.
++line;
callback.clear();
rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
MasterLoader::MANY_ERRORS, callbacks);
EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
EXPECT_FALSE(callback.isCalled());
// Broken RDATA text: extra token. createRdata() returns NULL, error
// callback is called.
++line;
callback.clear();
EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
MasterLoader::MANY_ERRORS, callbacks));
callback.check(src_name, line, CreateRdataCallback::ERROR,
"createRdata from text failed near 'extra-token': "
"extra input text");
// Similar to the previous case, but only the first extra token triggers
// callback.
++line;
callback.clear();
EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
MasterLoader::MANY_ERRORS, callbacks));
callback.check(src_name, line, CreateRdataCallback::ERROR,
"createRdata from text failed near 'extra': "
"extra input text");
// Lexer error will happen, corresponding error callback will be triggered.
++line;
callback.clear();
EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
MasterLoader::MANY_ERRORS, callbacks));
callback.check(src_name, line, CreateRdataCallback::ERROR,
"createRdata from text failed: unbalanced parentheses");
// Semantics level error will happen, corresponding error callback will be
// triggered.
++line;
callback.clear();
EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
MasterLoader::MANY_ERRORS, callbacks));
callback.check(src_name, line, CreateRdataCallback::ERROR,
"createRdata from text failed: Failed to convert "
"'192.0.2.1' to IN/AAAA RDATA");
// Input is valid and parse will succeed, but with a warning that the
// file is not ended with a newline.
++line;
callback.clear();
rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
MasterLoader::MANY_ERRORS, callbacks);
EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
callback.check(src_name, line, CreateRdataCallback::WARN,
"file does not end with newline");
}
}
}
}
......