Browse Source

[1186] Added suboptions support in Option class, tests implemented.

Tomek Mrugalski 13 years ago
parent
commit
2f39435c98

+ 41 - 18
src/lib/dhcp/option.cc

@@ -27,7 +27,7 @@ using namespace std;
 using namespace isc::dhcp;
 
 Option::Option(Universe u, unsigned short type)
-    :universe_(u), type_(type) {
+    :universe_(u), type_(type), data_len_(0) {
 
 
 }
@@ -82,14 +82,20 @@ Option::pack6(boost::shared_array<char> buf,
         isc_throw(OutOfRange, "Failed to pack v6 option=" <<
                   type_ << ",len=" << len() << ": too small buffer.");
     }
+
+    int length = len() - getHeaderLen();
+
     char * ptr = &buf[offset];
     *(uint16_t*)ptr = htons(type_);
     ptr += 2;
-    *(uint16_t*)ptr = htons(data_len_);
+    *(uint16_t*)ptr = htons(length);
     ptr += 2;
-    memcpy(ptr, &data_[offset_], data_len_);
+    if (data_len_)
+        memcpy(ptr, &data_[offset_], data_len_);
 
-    return offset + len();
+    offset += 4 + data_len_; // end of this option
+
+    return LibDHCP::packOptions6(buf, buf_len, offset, optionLst_);
 }
 
 unsigned int
@@ -126,8 +132,9 @@ Option::unpack6(boost::shared_array<char> buf,
 
     if (buf_len < offset+parse_len) {
         isc_throw(OutOfRange, "Failed to unpack DHCPv6 option len="
-                  << parse_len << " offset=" << offset << " from buffer (length="
-                  << buf_len << "): too small buffer.");
+                  << parse_len << " offset=" << offset
+                  << " from buffer (length=" << buf_len
+                  << "): too small buffer.");
     }
 
     data_ = buf;
@@ -139,16 +146,15 @@ Option::unpack6(boost::shared_array<char> buf,
 }
 
 unsigned short Option::len() {
-    switch (universe_) {
-    case V4:
-        return data_len_ + 2; // DHCPv4 option header length: 2 bytes
-    case V6:
-        return data_len_ + 4; // DHCPv6 option header length: 4 bytes
-    default:
-        isc_throw(BadValue, "Unknown universe defined for Option " << type_);
+    int length = getHeaderLen() + data_len_;
+
+    for (Option::Option6Lst::iterator it = optionLst_.begin();
+         it != optionLst_.end();
+         ++it) {
+        length += (*it).second->len();
     }
 
-    return 0; // should not happen
+    return (length);
 }
 
 bool Option::valid() {
@@ -165,19 +171,25 @@ bool Option::valid() {
 
 void
 isc::dhcp::Option::addOption(boost::shared_ptr<isc::dhcp::Option> opt) {
-    optionLst_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
+    optionLst_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(),
+                                                            opt));
 
 }
 
-std::string Option::toText() {
+std::string Option::toText(int indent /* =0 */ ) {
     std::stringstream tmp;
-    tmp << type_ << "(len=" << data_len_ << "):";
+
+    for (int i=0; i<indent; i++)
+        tmp << " ";
+
+    tmp << "type=" << type_ << ", len=" << data_len_ << ":";
 
     for (unsigned int i=0; i<data_len_; i++) {
         if (i) {
             tmp << ":";
         }
-        tmp << setfill('0') << setw(2) << hex << (unsigned short)(unsigned char)data_[offset_+i];
+        tmp << setfill('0') << setw(2) << hex
+            << (unsigned short)(unsigned char)data_[offset_+i];
     }
     return tmp.str();
 }
@@ -196,6 +208,17 @@ Option::getData() {
     }
 }
 
+unsigned short
+Option::getHeaderLen() {
+    switch (universe_) {
+    case V4:
+        return 2; // header length for v4
+    case V6:
+        return 4; // header length for v6
+    }
+    return 0; // should not happen
+}
+
 
 Option::~Option() {
 

+ 15 - 3
src/lib/dhcp/option.h

@@ -80,10 +80,12 @@ public:
     ///
     /// Returns string representation of the option.
     ///
+    /// @param indent number of spaces before printing text
+    ///
     /// @return string with text representation.
     ///
     virtual std::string
-    toText();
+    toText(int indent = 0);
 
     ///
     /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
@@ -101,11 +103,21 @@ public:
     virtual unsigned short
     len();
 
-    // returns if option is valid (e.g. option may be truncated)
+    /// @brief Returns length of header (2 for v4, 4 for v6)
+    ///
+    /// @return length of option header
+    ///
+    virtual unsigned short
+    getHeaderLen();
+
+    /// returns if option is valid (e.g. option may be truncated)
     virtual bool
     valid();
 
-    // returns pointer to actual data
+    /// Returns pointer to actual data.
+    ///
+    /// @return pointer to actual data (or NULL if there is no data)
+    ///
     virtual char*
     getData();
 

+ 5 - 1
src/lib/dhcp/option6_ia.cc

@@ -89,8 +89,12 @@ Option6IA::unpack(boost::shared_array<char> buf,
     return (offset);
 }
 
-std::string Option6IA::toText() {
+std::string Option6IA::toText(int indent /* = 0*/) {
     stringstream tmp;
+
+    for (int i=0; i<indent; i++)
+        tmp << " ";
+
     switch (type_) {
     case D6O_IA_NA:
         tmp << "IA_NA";

+ 1 - 1
src/lib/dhcp/option6_ia.h

@@ -51,7 +51,7 @@ public:
            unsigned int offset, 
            unsigned int parse_len);
 
-    virtual std::string toText();
+    virtual std::string toText(int indent = 0);
 
     void setT1(unsigned int t1) { t1_=t1; }
     void setT2(unsigned int t2) { t2_=t2; }

+ 4 - 1
src/lib/dhcp/option6_iaaddr.cc

@@ -99,8 +99,11 @@ Option6IAAddr::unpack(boost::shared_array<char> buf,
     return offset;
 }
 
-std::string Option6IAAddr::toText() {
+std::string Option6IAAddr::toText(int indent /* =0 */) {
     stringstream tmp;
+    for (int i=0; i<indent; i++)
+        tmp << " ";
+
     tmp << "addr: " << addr_.toText() << ", preferred-lft=" << preferred_ 
         << ", valid-lft=" << valid_ << endl;
 

+ 1 - 1
src/lib/dhcp/option6_iaaddr.h

@@ -55,7 +55,7 @@ public:
            unsigned int offset, 
            unsigned int parse_len);
 
-    virtual std::string toText();
+    virtual std::string toText(int indent = 0);
 
     void setAddress(isc::asiolink::IOAddress addr) { addr_ = addr; }
     void setPreferred(unsigned int pref) { preferred_=pref; }

+ 8 - 8
src/lib/dhcp/tests/libdhcp_unittest.cc

@@ -87,8 +87,8 @@ TEST_F(LibDhcpTest, unpackOptions6) {
         1,  1, 0, 1, 114 // opt5 (5 bytes)
     };
     // Option is used as a simple option implementation
-    // More advanced classes are used in tests dedicated for
-    // specific options.
+    // More advanced uses are validated in tests dedicated for
+    // specific derived classes.
 
     isc::dhcp::Option::Option6Lst options; // list of options
 
@@ -109,36 +109,36 @@ TEST_F(LibDhcpTest, unpackOptions6) {
     ASSERT_NE(x, options.end()); // option 1 should exist
     EXPECT_EQ(12, x->second->getType());  // this should be option 12
     ASSERT_EQ(9, x->second->len()); // it should be of length 9
-    EXPECT_EQ(0, memcmp(x->second->getData(), packed+4, 5)); // data len = 5
+    EXPECT_EQ(0, memcmp(x->second->getData(), packed+4, 5)); // data len=5
 
     x = options.find(13);
     ASSERT_NE(x, options.end()); // option 13 should exist
     EXPECT_EQ(13, x->second->getType());  // this should be option 13
     ASSERT_EQ(7, x->second->len()); // it should be of length 7
-    EXPECT_EQ(0, memcmp(x->second->getData(), packed+13, 3)); // data length = 3
+    EXPECT_EQ(0, memcmp(x->second->getData(), packed+13, 3)); // data len=3
 
     x = options.find(14);
     ASSERT_NE(x, options.end()); // option 3 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(), packed+20, 2)); // data length = 2
+    EXPECT_EQ(0, memcmp(x->second->getData(), packed+20, 2)); // data len=2
 
     x = options.find(256);
     ASSERT_NE(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(), packed+26, 4)); // data length = 4
+    EXPECT_EQ(0, memcmp(x->second->getData(), packed+26, 4)); // data len=4
 
     x = options.find(257);
     ASSERT_NE(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(), packed+34, 1)); // data length = 2
+    EXPECT_EQ(0, memcmp(x->second->getData(), packed+34, 1)); // data len=1
 
     x = options.find(0);
     EXPECT_EQ(x, options.end()); // option 0 not found
 
-    x = options.find(1); // 1 is htons(256). Worth checking
+    x = options.find(1); // 1 is htons(256) on little endians. Worth checking
     EXPECT_EQ(x, options.end()); // option 1 not found
 
     x = options.find(2);

+ 142 - 6
src/lib/dhcp/tests/option_unittest.cc

@@ -18,6 +18,7 @@
 
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
 
 #include "dhcp/dhcp6.h"
 #include "dhcp/option.h"
@@ -33,7 +34,63 @@ public:
     }
 };
 
-TEST_F(OptionTest, basic) {
+// v4 is not really implemented yet. A simple test will do for now
+TEST_F(OptionTest, basic4) {
+
+    Option* opt = new Option(Option::V4, 17);
+
+    EXPECT_EQ(17, opt->getType());
+    EXPECT_EQ(NULL, opt->getData());
+    EXPECT_EQ(2, opt->len()); // just v4 header
+
+    delete opt;
+}
+
+// tests simple constructor
+TEST_F(OptionTest, basic6) {
+
+    Option* opt = new Option(Option::V6, 1);
+
+    EXPECT_EQ(1, opt->getType());
+    EXPECT_EQ(NULL, opt->getData());
+    EXPECT_EQ(4, opt->len()); // just v6 header
+
+    delete opt;
+}
+
+// tests contructor used in pkt reception
+// option contains actual data
+TEST_F(OptionTest, data1) {
+    boost::shared_array<char> buf(new char[32]);
+    for (int i=0; i<32; i++)
+        buf[i] = 100+i;
+    Option* opt = new Option(Option::V6, 333, //type
+                             buf,
+                             3, // offset
+                             7); // 7 bytes of data
+    EXPECT_EQ(333, opt->getType());
+    ASSERT_EQ(&buf[3], opt->getData());
+    ASSERT_EQ(11, opt->len());
+    EXPECT_EQ(0, memcmp(&buf[3], opt->getData(), 7) );
+
+    int offset = opt->pack(buf, 32, 20);
+    EXPECT_EQ(31, offset);
+
+    EXPECT_EQ(buf[20], 333/256); // type
+    EXPECT_EQ(buf[21], 333%256);
+
+    EXPECT_EQ(buf[22], 0); // len
+    EXPECT_EQ(buf[23], 7);
+
+    // payload
+    EXPECT_EQ(0, memcmp(&buf[3], &buf[24], 7) );
+
+    delete opt;
+}
+
+// another text that tests the same thing, just
+// with different input parameters
+TEST_F(OptionTest, data2) {
 
     boost::shared_array<char> simple_buf(new char[128]);
     for (int i=0; i<128; i++)
@@ -70,12 +127,91 @@ TEST_F(OptionTest, basic) {
     // if option content is correct
     EXPECT_EQ(0, memcmp(&simple_buf[0], &simple_buf[14],4));
 
-    for (int i=0; i<20; i++) {
-        std::cout << i << ":" << (unsigned short) (unsigned char)simple_buf[i] << " ";
-    }
-    std::cout << std::endl;
-
     delete opt;
 }
 
+// check that an option can contain 2 suboptions:
+// opt1
+//  +----opt2
+//  |
+//  +----opt3
+//
+TEST_F(OptionTest, suboptions1) {
+    boost::shared_array<char> buf(new char[128]);
+    for (int i=0; i<128; i++)
+        buf[i] = 100+i;
+    Option* opt1 = new Option(Option::V6, 65535, //type
+                              buf,
+                              0, // offset
+                              3); // 3 bytes of data
+    boost::shared_ptr<Option> opt2(new Option(Option::V6, 13));
+    boost::shared_ptr<Option> opt3(new Option(Option::V6, 7,
+                                              buf,
+                                              3, // offset
+                                              5)); // 5 bytes of data
+    opt1->addOption(opt2);
+    opt1->addOption(opt3);
+    // opt2 len = 4 (just header)
+    // opt3 len = 9 4(header)+5(data)
+    // opt1 len = 7 + suboptions() = 7 + 4 + 9 = 20
+
+    EXPECT_EQ(4, opt2->len());
+    EXPECT_EQ(9, opt3->len());
+    EXPECT_EQ(20, opt1->len());
+
+    char expected[] = {
+        0xff, 0xff, 0, 16, 100, 101, 102,
+        0, 7, 0, 5, 103, 104, 105, 106, 107,
+        0, 13, 0, 0 // no data at all
+    };
+
+    int offset = opt1->pack(buf, 128, 20);
+    EXPECT_EQ(40, offset);
+
+    // payload
+    EXPECT_EQ(0, memcmp(&buf[20], expected, 20) );
+
+    delete opt1;
+}
+
+// check that an option can contain 2 suboptions:
+// opt1
+//  +----opt2
+//        |
+//        +----opt3
+//
+TEST_F(OptionTest, suboptions2) {
+    boost::shared_array<char> buf(new char[128]);
+    for (int i=0; i<128; i++)
+        buf[i] = 100+i;
+    Option* opt1 = new Option(Option::V6, 65535, //type
+                              buf,
+                              0, // offset
+                              3); // 3 bytes of data
+    boost::shared_ptr<Option> opt2(new Option(Option::V6, 13));
+    boost::shared_ptr<Option> opt3(new Option(Option::V6, 7,
+                                              buf,
+                                              3, // offset
+                                              5)); // 5 bytes of data
+    opt1->addOption(opt2);
+    opt2->addOption(opt3);
+    // opt3 len = 9 4(header)+5(data)
+    // opt2 len = 4 (just header) + len(opt3)
+    // opt1 len = 7 + len(opt2)
+
+    char expected[] = {
+        0xff, 0xff, 0, 16, 100, 101, 102,
+        0, 13, 0, 9,
+        0, 7, 0, 5, 103, 104, 105, 106, 107,
+    };
+
+    int offset = opt1->pack(buf, 128, 20);
+    EXPECT_EQ(40, offset);
+
+    // payload
+    EXPECT_EQ(0, memcmp(&buf[20], expected, 20) );
+
+    delete opt1;
+}
+
 }