]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3074] add more UTs
authorPiotrek Zadroga <piotrek@isc.org>
Tue, 17 Oct 2023 10:48:59 +0000 (12:48 +0200)
committerPiotrek Zadroga <piotrek@isc.org>
Tue, 9 Jan 2024 10:38:08 +0000 (11:38 +0100)
src/lib/dhcp/option_classless_static_route.cc
src/lib/dhcp/option_classless_static_route.h
src/lib/dhcp/tests/option_classless_static_route_unittest.cc

index 4a6f640cdcf78bcaf02b2af52d6b3db3ca492046..816583ec6371251308b2640e166f144919d00e66 100644 (file)
@@ -6,9 +6,9 @@
 
 #include <config.h>
 
+#include <asiolink/io_error.h>
 #include <util/strutil.h>
 
-#include <asiolink/io_error.h>
 #include <option_classless_static_route.h>
 
 using namespace isc::asiolink;
@@ -88,10 +88,8 @@ OptionClasslessStaticRoute::toText(int indent) const {
            << "len=" << (len() - getHeaderLen());
     int i = 0;
     for (const auto& route : static_routes_) {
-        stream << ", Route " << ++i
-               << " (subnet " << get<0>(route).toText() << "/"
-               << static_cast<int>(get<1>(route)) << ", router IP "
-               << get<2>(route) << ")";
+        stream << ", Route " << ++i << " (subnet " << get<0>(route).toText() << "/"
+               << static_cast<int>(get<1>(route)) << ", router IP " << get<2>(route) << ")";
     }
 
     return (stream.str());
@@ -186,8 +184,8 @@ OptionClasslessStaticRoute::parseWireData(OptionBufferConstIter begin, OptionBuf
 
         // once we know haw many significant octets there are, check for truncated data again
         if (distance(begin, end) < (significant_octets + V4ADDRESS_LEN)) {
-            isc_throw(OutOfRange, "DHCPv4 OptionClasslessStaticRoute "
-                                      << type_ << " is truncated.");
+            isc_throw(OutOfRange,
+                      "DHCPv4 OptionClasslessStaticRoute " << type_ << " is truncated.");
         }
 
         // following octets are significant octets of the subnet nr
@@ -201,18 +199,18 @@ OptionClasslessStaticRoute::parseWireData(OptionBufferConstIter begin, OptionBuf
         case 1:
             subnet_octets = *begin;
             subnet_nr = IOAddress(subnet_octets << 24);
-            break ;
+            break;
         case 2:
             subnet_octets = readUint16(&(*begin), distance(begin, end));
             subnet_nr = IOAddress(subnet_octets << 16);
-            break ;
+            break;
         case 3:
             // we are reading one octet too much in this case,
             // but since we did check for truncated data before,
             // we are safe do so and mask 4th octet with zeros
             subnet_octets = readUint32(&(*begin), distance(begin, end));
             subnet_nr = IOAddress(subnet_octets & 0xFFFFFF00);
-            break ;
+            break;
         case 4:
             subnet_octets = readUint32(&(*begin), distance(begin, end));
             subnet_nr = IOAddress(subnet_octets);
@@ -238,9 +236,10 @@ OptionClasslessStaticRoute::parseConfigData(const std::string& config_txt) {
         std::vector<std::string> parts = str::tokens(str::trim(route_str), std::string("-"));
         if (parts.size() != 2) {
             isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
-                                    << type_ << " has invalid value, route definition must"
-                                                " have format as in example: 10.229.0.128/25"
-                                                " - 10.229.0.1");
+                                    << type_
+                                    << " has invalid value, option definition must"
+                                       " have comma separated routes formatted as in "
+                                       "example: 10.229.0.128/25 - 10.229.0.1");
         }
 
         std::string txt_prefix = str::trim(parts[0]);
@@ -276,7 +275,7 @@ OptionClasslessStaticRoute::parseConfigData(const std::string& config_txt) {
             if (len > 32) {
                 isc_throw(BadValue, "");
             }
-        } catch (...)  {
+        } catch (...) {
             isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute "
                                     << type_ << " has invalid value, provided prefix len "
                                     << txt_prefix_len << " is not valid.");
index 5ebcfcadfdaf8053ca0b63912969d2413b61247d..f0b2438ffaacdfe1d4a52571183538b73f8f5c66 100644 (file)
@@ -21,7 +21,9 @@ typedef std::tuple<asiolink::IOAddress, uint8_t, asiolink::IOAddress> StaticRout
 class OptionClasslessStaticRoute : public Option {
 public:
     /// @brief Empty Constructor
-    OptionClasslessStaticRoute() : Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0) {}
+    OptionClasslessStaticRoute()
+        : Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0) {
+    }
 
     /// @brief Constructor of the %Option from on-wire data.
     ///
@@ -103,7 +105,8 @@ private:
     /// @return number of significant octets
     static uint8_t calcSignificantOctets(const uint8_t& mask_width);
 
-    /// @brief Calculates length in octets of all encoded static routes and stores it in @c data_len_
+    /// @brief Calculates length in octets of all encoded static routes and stores it in @c
+    /// data_len_
     ///
     /// Calculation is done according to static routes encoding rules in RFC3442.
     /// This should be called whenever @c static_routes_ is changed.
index ef0d615e12960f9ed41c34bfcb5f650aaafc9c77..c81fd8979d6a9fd05b97f2135f6c66ff58476c8f 100644 (file)
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <dhcp/option_classless_static_route.h>
+#include <util/strutil.h>
 
 #include <gtest/gtest.h>
 
@@ -47,16 +48,238 @@ TEST(OptionClasslessStaticRouteTest, emptyCtorAddOneRoute) {
               option->toText());
 }
 
+// This test verifies adding more routes to OptionClasslessStaticRoute class.
+TEST(OptionClasslessStaticRouteTest, emptyCtorAddMoreRoutes) {
+    // Create option instance. Check that constructor doesn't throw.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute()));
+    ASSERT_TRUE(option);
+
+    // Add 3 static routes.
+    StaticRouteTuple route1 = std::make_tuple(IOAddress("0.0.0.0"), 0, IOAddress("10.198.122.1"));
+    option->addRoute(route1);
+    StaticRouteTuple route2 = std::make_tuple(IOAddress("1.2.3.4"), 32, IOAddress("10.20.30.40"));
+    option->addRoute(route2);
+    StaticRouteTuple route3 = std::make_tuple(IOAddress("5.6.0.0"), 16, IOAddress("50.60.70.80"));
+    option->addRoute(route3);
+
+    // Expected len: 2 (option code + option len headers) + 5 (1 dest descriptor + 4 router addr)
+    // + 9 (5 d.d. + 4 r.a.) + 7 (3 d.d. + 4 r.a.).
+    EXPECT_EQ(23, option->len());
+
+    // Verify toText() is working fine.
+    EXPECT_EQ("type=121(CLASSLESS_STATIC_ROUTE), len=21, "
+              "Route 1 (subnet 0.0.0.0/0, router IP 10.198.122.1), "
+              "Route 2 (subnet 1.2.3.4/32, router IP 10.20.30.40), "
+              "Route 3 (subnet 5.6.0.0/16, router IP 50.60.70.80)",
+              option->toText());
+}
+
 // This test verifies constructor of the OptionClasslessStaticRoute class from config data.
 // Only one static route is defined.
-TEST(OptionClasslessStaticRouteTest, bufferCtorWithOneRoute) {
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorWithOneRoute) {
+    // Prepare data to decode - one route with mask width = 8.
+    const std::string config = "'10.0.0.0/8 - 10.198.122.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
+    ASSERT_TRUE(option);
+
+    // Expected len: 2 (option code + option len headers) + 6 (2 dest descriptor + 4 router addr).
+    EXPECT_EQ(8, option->len());
+
+    // Verify toText() is working fine.
+    EXPECT_EQ("type=121(CLASSLESS_STATIC_ROUTE), len=6, Route 1 (subnet 10.0.0.0/8,"
+              " router IP 10.198.122.1)",
+              option->toText());
+}
+
+// This test verifies constructor of the OptionClasslessStaticRoute class from config data.
+// 3 static routes are defined.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorWithMoreRoutes) {
+    // Prepare data to decode - 3 static routes
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128/25-10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
+    ASSERT_TRUE(option);
+
+    // Expected len: 2 (option code + option len headers) + 5 (1 dest descriptor + 4 router addr)
+    // + 9 (5 dest descriptor + 4 router addr) + 8 (4 dest descriptor + 4 router addr).
+    EXPECT_EQ(24, option->len());
+
+    // Verify toText() is working fine.
+    EXPECT_EQ("type=121(CLASSLESS_STATIC_ROUTE), len=22, "
+              "Route 1 (subnet 0.0.0.0/0, router IP 10.17.0.1), "
+              "Route 2 (subnet 10.229.0.128/25, router IP 10.229.0.1), "
+              "Route 3 (subnet 10.27.129.0/24, router IP 10.27.129.1)",
+              option->toText());
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorMissingDash) {
+    // Prepare data to decode - second route has missing dash separator
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128/25 10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorMissingPrefixLen) {
+    // Prepare data to decode - second route has missing "/prefix len"
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128 - 10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDestIpV6Given) {
+    // Prepare data to decode - second route has IPv6 prefix
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,3001::5/64 - 10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDestInvalidAddr) {
+    // Prepare data to decode - second route has invalid IP address
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.a/32 - 10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorInvalidPrefixLen) {
+    // Prepare data to decode - second route has invalid prefix len
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/a - 10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorPrefixLenTooBig) {
+    // Prepare data to decode - second route has prefix len too big for IPv4
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/64 - 10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorRouterInvalidAddr) {
+    // Prepare data to decode - second route has invalid router IP address
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/31 - 10.229.0.a, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorRouterIpV6Given) {
+    // Prepare data to decode - second route has IPv6 router address
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/31 - 3001::5, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer has wrong format.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorCommaMissing) {
+    // Prepare data to decode - comma separators are missing
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1 1.2.3.4/31 - 1.2.3.4 "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from config data throws an exception
+// when data in the buffer is truncated.
+TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDataTruncated) {
+    // Prepare data to decode - truncated data
+    const std::string config = "'0.0.'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Create option instance. Check that constructor throws OutOfRange during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::OutOfRange);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies constructor of the OptionClasslessStaticRoute class from
+// on wire binary hex data.
+// Only one static route is defined.
+TEST(OptionClasslessStaticRouteTest, wireDatabufferCtorWithOneRoute) {
     // Prepare data to decode - one route with mask width = 8.
     const uint8_t buf_data[] = {
-        10,  0,   0,   0,  // subnet nr
-        255, 0,   0,   0,  // mask
-        10,  198, 122, 1   // router IP
+        8,               // mask width
+        10,              // significant subnet octet for 10.0.0.0/8
+        10, 198, 122, 1  // router IP address
     };
-
     OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
 
     // Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
@@ -73,22 +296,21 @@ TEST(OptionClasslessStaticRouteTest, bufferCtorWithOneRoute) {
               option->toText());
 }
 
-// This test verifies constructor of the OptionClasslessStaticRoute class from config data.
+// This test verifies constructor of the OptionClasslessStaticRoute class from
+// on wire binary hex data.
 // 3 static routes are defined.
-TEST(OptionClasslessStaticRouteTest, bufferCtorWithMoreRoutes) {
+TEST(OptionClasslessStaticRouteTest, wireDatabufferCtorWithMoreRoutes) {
     // Prepare data to decode - 3 static routes
     const uint8_t buf_data[] = {
-        0,   0,   0,   0,    // subnet nr
-        0,   0,   0,   0,    // mask
-        10,  17,  0,   1,    // router IP
-        10,  229, 0,   128,  // subnet nr
-        255, 255, 255, 128,  // mask
-        10,  229, 0,   1,    // router IP
-        10,  27,  129, 0,    // subnet nr
-        255, 255, 255, 0,    // mask
-        10,  27,  129, 1     // router IP
+        0,                  // mask width
+        10, 17,  0,   1,    // router IP address
+        25,                 // mask width
+        10, 229, 0,   128,  // significant subnet octets for 10.229.0.128/25
+        10, 229, 0,   1,    // router IP address
+        24,                 // mask width
+        10, 27,  129,       // significant subnet octets for 10.27.129.0/24
+        10, 27,  129, 1     // router IP address
     };
-
     OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
 
     // Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
@@ -108,4 +330,154 @@ TEST(OptionClasslessStaticRouteTest, bufferCtorWithMoreRoutes) {
               option->toText());
 }
 
-}
\ No newline at end of file
+// This test verifies that constructor from on wire binary hex data throws an exception
+// when data in the buffer is truncated.
+TEST(OptionClasslessStaticRouteTest, wireDatabufferCtorDataTruncated) {
+    // Prepare data to decode - truncated data
+    const uint8_t buf_data[] = {
+        8,   // mask width
+        10,  // significant subnet octet for 10.0.0.0/8
+        10,  // router IP address truncated
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    // Create option instance. Check that constructor throws OutOfRange during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::OutOfRange);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from on wire binary hex data throws an exception
+// when data of the second static route in the buffer is truncated.
+TEST(OptionClasslessStaticRouteTest, wireDatabufferCtorSecondRouteDataTruncated) {
+    // Prepare data to decode - truncated data
+    const uint8_t buf_data[] = {
+        0,             // mask width
+        10, 17, 0, 1,  // router IP address
+        25,            // mask width
+        10, 229        // significant subnet octets truncated
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    // Create option instance. Check that constructor throws OutOfRange during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::OutOfRange);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies that constructor from on wire binary hex data throws an exception
+// when data of the second static route in the buffer contains bad subnet width.
+TEST(OptionClasslessStaticRouteTest, wireDatabufferCtorSecondRouteBadSubnetWidth) {
+    // Prepare data to decode - truncated data
+    const uint8_t buf_data[] = {
+        0,                 // mask width
+        10,  17,  0, 1,    // router IP address
+        125,               // bad mask width
+        10,  229, 0, 128,  // significant subnet octets for 10.229.0.128/25
+        10,  229, 0, 1     // router IP address
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    // Create option instance. Check that constructor throws BadValue during Unpack.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
+                 isc::BadValue);
+    ASSERT_FALSE(option);
+}
+
+// This test verifies toText() method
+TEST(OptionClasslessStaticRouteTest, toText) {
+    // Prepare data to decode - 3 static routes
+    const uint8_t buf_data[] = {
+        0,                  // mask width
+        10, 17,  0,   1,    // router IP address
+        25,                 // mask width
+        10, 229, 0,   128,  // significant subnet octets for 10.229.0.128/25
+        10, 229, 0,   1,    // router IP address
+        24,                 // mask width
+        10, 27,  129,       // significant subnet octets for 10.27.129.0/24
+        10, 27,  129, 1     // router IP address
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    // Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
+    ASSERT_TRUE(option);
+
+    const uint8_t indent = 4;
+
+    // Verify toText() is working fine.
+    EXPECT_EQ("    type=121(CLASSLESS_STATIC_ROUTE), len=22, "  // 4 spaces of indentation
+              "Route 1 (subnet 0.0.0.0/0, router IP 10.17.0.1), "
+              "Route 2 (subnet 10.229.0.128/25, router IP 10.229.0.1), "
+              "Route 3 (subnet 10.27.129.0/24, router IP 10.27.129.1)",
+              option->toText(indent));
+}
+
+// This test verifies pack() method
+TEST(OptionClasslessStaticRouteTest, pack) {
+    // Prepare data to decode - 3 static routes
+    const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128/25-10.229.0.1, "
+                               "10.27.129.0/24 - 10.27.129.1'";
+    OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
+
+    // Prepare expected packed data
+    const uint8_t ref_data[] = {
+        DHO_CLASSLESS_STATIC_ROUTE, // option code
+        22,                         // data len
+        0,                          // mask width
+        10, 17,  0,   1,            // router IP address
+        25,                         // mask width
+        10, 229, 0,   128,          // significant subnet octets for 10.229.0.128/25
+        10, 229, 0,   1,            // router IP address
+        24,                         // mask width
+        10, 27,  129,               // significant subnet octets for 10.27.129.0/24
+        10, 27,  129, 1             // router IP address
+    };
+
+    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer output_buf(10);
+    ASSERT_NO_THROW(option->pack(output_buf));
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, output_buf.getLength());
+    EXPECT_EQ(0, memcmp(ref_data, output_buf.getData(), output_buf.getLength()));
+}
+
+// This test verifies that pack() method throws an exception when option len to be packed is
+// bigger than 255 octets.
+TEST(OptionClasslessStaticRouteTest, packThrows) {
+    // Create option instance. Check that constructor doesn't throw.
+    OptionClasslessStaticRoutePtr option;
+    EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute()));
+    ASSERT_TRUE(option);
+
+    // Create 9 octets long static route.
+    StaticRouteTuple route = std::make_tuple(IOAddress("1.2.3.4"), 32, IOAddress("10.198.122.1"));
+    // Add 50 routes
+    uint8_t i = 0;
+    while (i < 50) {
+        option->addRoute(route);
+        ++i;
+    }
+
+    // Expected len: 2 (headers) + 50x9 = 452
+    EXPECT_EQ(452, option->len());
+
+    // Check that pack() method throws an exception for option len > 255
+    isc::util::OutputBuffer output_buf(10);
+    ASSERT_THROW(option->pack(output_buf), isc::OutOfRange);
+}
+
+}  // namespace
\ No newline at end of file