Browse Source

[2512] Add a CharStringData type (with no length byte prefix)

Mukund Sivaraman 11 years ago
parent
commit
cb1407f6e8

+ 78 - 0
src/lib/dns/rdata/generic/detail/char_string.cc

@@ -93,6 +93,33 @@ stringToCharString(const MasterToken::StringRegion& str_region,
     result[0] = result.size() - 1;
 }
 
+void
+stringToCharStringData(const MasterToken::StringRegion& str_region,
+                       CharStringData& result)
+{
+    bool escape = false;
+    const char* s = str_region.beg;
+    const char* const s_end = str_region.beg + str_region.len;
+
+    for (size_t n = str_region.len; n != 0; --n, ++s) {
+        int c = (*s & 0xff);
+        if (escape && std::isdigit(c) != 0) {
+            c = decimalToNumber(s, s_end);
+            assert(n >= 3);
+            n -= 2;
+            s += 2;
+        } else if (!escape && c == '\\') {
+            escape = true;
+            continue;
+        }
+        escape = false;
+        result.push_back(c);
+    }
+    if (escape) {               // terminated by non-escaped '\'
+        isc_throw(InvalidRdataText, "character-string ends with '\\'");
+    }
+}
+
 std::string
 charStringToString(const CharString& char_string) {
     std::string s;
@@ -116,6 +143,29 @@ charStringToString(const CharString& char_string) {
     return (s);
 }
 
+std::string
+charStringDataToString(const CharStringData& char_string) {
+    std::string s;
+    for (CharString::const_iterator it = char_string.begin();
+         it != char_string.end(); ++it) {
+        const uint8_t ch = *it;
+        if ((ch < 0x20) || (ch >= 0x7f)) {
+            // convert to escaped \xxx (decimal) format
+            s.push_back('\\');
+            s.push_back('0' + ((ch / 100) % 10));
+            s.push_back('0' + ((ch / 10) % 10));
+            s.push_back('0' + (ch % 10));
+            continue;
+        }
+        if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+            s.push_back('\\');
+        }
+        s.push_back(ch);
+    }
+
+    return (s);
+}
+
 int compareCharStrings(const detail::CharString& self,
                        const detail::CharString& other) {
     if (self.size() == 0 && other.size() == 0) {
@@ -144,6 +194,34 @@ int compareCharStrings(const detail::CharString& self,
     }
 }
 
+int compareCharStringDatas(const detail::CharStringData& self,
+                           const detail::CharStringData& other) {
+    if (self.size() == 0 && other.size() == 0) {
+        return (0);
+    }
+    if (self.size() == 0) {
+        return (-1);
+    }
+    if (other.size() == 0) {
+        return (1);
+    }
+    const size_t self_len = self.size();
+    const size_t other_len = other.size();
+    const size_t cmp_len = std::min(self_len, other_len);
+    const int cmp = std::memcmp(&self[0], &other[0], cmp_len);
+    if (cmp < 0) {
+        return (-1);
+    } else if (cmp > 0) {
+        return (1);
+    } else if (self_len < other_len) {
+        return (-1);
+    } else if (self_len > other_len) {
+        return (1);
+    } else {
+        return (0);
+    }
+}
+
 size_t
 bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
                    CharString& target) {

+ 37 - 0
src/lib/dns/rdata/generic/detail/char_string.h

@@ -34,6 +34,9 @@ namespace detail {
 /// be the bare char basis.
 typedef std::vector<uint8_t> CharString;
 
+/// \brief Type for DNS character string without the length prefix.
+typedef std::vector<uint8_t> CharStringData;
+
 /// \brief Convert a DNS character-string into corresponding binary data.
 ///
 /// This helper function takes a string object that is expected to be a
@@ -53,6 +56,20 @@ typedef std::vector<uint8_t> CharString;
 void stringToCharString(const MasterToken::StringRegion& str_region,
                         CharString& result);
 
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This method functions similar to \c stringToCharString() except it
+/// does not include the 1-octet length prefix in the \c result, and the
+/// result is not limited to MAX_CHARSTRING_LEN octets.
+///
+/// \throw InvalidRdataText Upon syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored.  Expected to be empty, but it's not checked.
+void stringToCharStringData(const MasterToken::StringRegion& str_region,
+                            CharStringData& result);
+
 /// \brief Convert a CharString into a textual DNS character-string.
 ///
 /// This method converts a binary 8-bit representation of a DNS
@@ -67,6 +84,15 @@ void stringToCharString(const MasterToken::StringRegion& str_region,
 /// \return A string representation of \c char_string.
 std::string charStringToString(const CharString& char_string);
 
+/// \brief Convert a CharStringData into a textual DNS character-string.
+///
+/// Reverse of \c stringToCharStringData(). See \c stringToCharString()
+/// vs. \c stringToCharStringData().
+///
+/// \param char_string The \c CharStringData to convert.
+/// \return A string representation of \c char_string.
+std::string charStringDataToString(const CharStringData& char_string);
+
 /// \brief Compare two CharString objects
 ///
 /// \param self The CharString field to compare
@@ -77,6 +103,17 @@ std::string charStringToString(const CharString& char_string);
 ///          0 if \c self and \c other are equal
 int compareCharStrings(const CharString& self, const CharString& other);
 
+/// \brief Compare two CharStringData objects
+///
+/// \param self The CharStringData field to compare
+/// \param other The CharStringData field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+///          1 if \c self would be sorted after \c other
+///          0 if \c self and \c other are equal
+int compareCharStringDatas(const CharStringData& self,
+                           const CharStringData& other);
+
 /// \brief Convert a buffer containing a character-string to CharString
 ///
 /// This method reads one character-string from the given buffer (in wire

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

@@ -39,6 +39,7 @@ run_unittests_SOURCES += rcode_unittest.cc
 run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdatafields_unittest.cc
 run_unittests_SOURCES += rdata_char_string_unittest.cc
+run_unittests_SOURCES += rdata_char_string_data_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_like_unittest.cc

+ 187 - 0
src/lib/dns/tests/rdata_char_string_data_unittest.cc

@@ -0,0 +1,187 @@
+// Copyright (C) 2014  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 <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharStringData;
+using isc::dns::rdata::generic::detail::stringToCharStringData;
+using isc::dns::rdata::generic::detail::charStringDataToString;
+using isc::dns::rdata::generic::detail::compareCharStringDatas;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringDataTest : public ::testing::Test {
+protected:
+    CharStringDataTest() :
+        // char-string representation for test data using two types of escape
+        // ('r' = 114)
+        test_str("Test\\ St\\114ing")
+    {
+        str_region.beg = &test_str[0];
+        str_region.len = test_str.size();
+    }
+    CharStringData chstr;           // place holder
+    const std::string test_str;
+    MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+    MasterToken::StringRegion region;
+    region.beg = &str[0]; // note std ensures this works even if str is empty
+    region.len = str.size();
+    return (region);
+}
+
+TEST_F(CharStringDataTest, normalConversion) {
+    uint8_t tmp[3];             // placeholder for expected sequence
+
+    stringToCharStringData(str_region, chstr);
+    matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+    // Empty string
+    chstr.clear();
+    stringToCharStringData(createStringRegion(""), chstr);
+    EXPECT_TRUE(chstr.empty());
+
+    // Possible largest char string
+    chstr.clear();
+    std::string long_str(255, 'x');
+    stringToCharStringData(createStringRegion(long_str), chstr);
+    std::vector<uint8_t> expected;
+    expected.insert(expected.end(), long_str.begin(), long_str.end());
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Escaped '\'
+    chstr.clear();
+    tmp[0] = '\\';
+    stringToCharStringData(createStringRegion("\\\\"), chstr);
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Boundary values for \DDD
+    chstr.clear();
+    tmp[0] = 0;
+    stringToCharStringData(createStringRegion("\\000"), chstr);
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    chstr.clear();
+    stringToCharStringData(createStringRegion("\\255"), chstr);
+    tmp[0] = 255;
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Another digit follows DDD; it shouldn't cause confusion
+    chstr.clear();
+    stringToCharStringData(createStringRegion("\\2550"), chstr);
+    tmp[1] = '0';
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringDataTest, badConversion) {
+    // input string ending with (non escaped) '\'
+    chstr.clear();
+    EXPECT_THROW(stringToCharStringData(createStringRegion("foo\\"), chstr),
+                 InvalidRdataText);
+}
+
+TEST_F(CharStringDataTest, badDDD) {
+    // Check various type of bad form of \DDD
+
+    // Not a number
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\1a2"), chstr),
+                 InvalidRdataText);
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\12a"), chstr),
+                 InvalidRdataText);
+
+    // Not in the range of uint8_t
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\256"), chstr),
+                 InvalidRdataText);
+
+    // Short buffer
+    EXPECT_THROW(stringToCharStringData(createStringRegion("\\42"), chstr),
+                 InvalidRdataText);
+}
+
+const struct TestData {
+    const char *data;
+    const char *expected;
+} conversion_data[] = {
+    {"Test\"Test", "Test\\\"Test"},
+    {"Test;Test", "Test\\;Test"},
+    {"Test\\Test", "Test\\\\Test"},
+    {"Test\x1fTest", "Test\\031Test"},
+    {"Test ~ Test", "Test ~ Test"},
+    {"Test\x7fTest", "Test\\127Test"},
+    {NULL, NULL}
+};
+
+TEST_F(CharStringDataTest, charStringDataToString) {
+    for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+        uint8_t idata[32];
+        size_t length = std::strlen(cur->data);
+        assert(sizeof(idata) >= length);
+        std::memcpy(idata, cur->data, length);
+        const CharStringData test_data(idata, idata + length);
+        EXPECT_EQ(cur->expected, charStringDataToString(test_data));
+    }
+}
+
+TEST_F(CharStringDataTest, compareCharStringData) {
+    CharStringData charstr;
+    CharStringData charstr2;
+    CharStringData charstr_small1;
+    CharStringData charstr_small2;
+    CharStringData charstr_large1;
+    CharStringData charstr_large2;
+    CharStringData charstr_empty;
+
+    stringToCharStringData(createStringRegion("test string"), charstr);
+    stringToCharStringData(createStringRegion("test string"), charstr2);
+    stringToCharStringData(createStringRegion("test strin"), charstr_small1);
+    stringToCharStringData(createStringRegion("test strina"), charstr_small2);
+    stringToCharStringData(createStringRegion("test stringa"), charstr_large1);
+    stringToCharStringData(createStringRegion("test strinz"), charstr_large2);
+
+    EXPECT_EQ(0, compareCharStringDatas(charstr, charstr2));
+    EXPECT_EQ(0, compareCharStringDatas(charstr2, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small1));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small2));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large1));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large2));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_small1, charstr));
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_small2, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr_large1, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr_large2, charstr));
+
+    EXPECT_EQ(-1, compareCharStringDatas(charstr_empty, charstr));
+    EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_empty));
+    EXPECT_EQ(0, compareCharStringDatas(charstr_empty, charstr_empty));
+}
+
+} // unnamed namespace