-// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
std::string value;
if (!buf.empty()) {
- value.insert(value.end(), buf.begin(), buf.end());
+ // Per RFC 2132, section 2 we need to drop trailing NULLs
+ auto begin = buf.begin();
+ auto end = buf.end();
+ for ( ; end != begin && *(end - 1) == 0x0; --end);
+
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::OutOfRange, "string value carried by the option "
+ << " contained only NULLs");
+ }
+
+ value.insert(value.end(), begin, end);
}
+
return (value);
}
-// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
/// @brief Read string value from a buffer.
///
+ /// To be compliant with RFC 2132, Sec. 2, trailing NULLs are trimmed.
/// @param buf input buffer.
///
/// @return string value being read.
+ /// @throw isc::dhcp::OutOfRange is the payload contains only NULLs.
static std::string readString(const std::vector<uint8_t>& buf);
/// @brief Write UTF8-encoded string into a buffer.
<< getType() << "' must not be empty");
}
- setData(value.begin(), value.end());
+ // Trim off any trailing NULLs.
+ auto begin = value.begin();
+ auto end = value.end();
+ for ( ; end != begin && *(end - 1) == 0x0; --end);
+
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::OutOfRange, "string value carried by the option '"
+ << getType() << "' contained only NULLs");
+ }
+
+ // Now set the value.
+ setData(begin, end);
}
void
OptionString::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
+ // Trim off trailing null(s)
+ for ( ; end != begin && *(end - 1) == 0x0; --end);
+
if (std::distance(begin, end) == 0) {
isc_throw(isc::OutOfRange, "failed to parse an option '"
<< getType() << "' holding string value"
- << " - empty value is not accepted");
+ << "' was empty or contained only NULLs");
}
+
+ // Now set the data.
setData(begin, end);
}
///
/// This class represents an option carrying a single string value.
/// Currently this class imposes that the minimal length of the carried
-/// string is 1.
+/// string is 1. Per RFC 2132, Sec 2 trailing NULLs are trimmed during
+/// either construction or unpacking.
///
/// @todo In the future this class may be extended with some more string
/// content checks and encoding methods if required.
/// This constructor creates an instance of option which carries a
/// string value specified as constructor's parameter. This constructor
/// is most often used to create an instance of an option which will
- /// be sent in the outgoing packet.
+ /// be sent in the outgoing packet. Trailing NULLs will be trimmed.
///
/// @param u universe (V4 or V6).
/// @param type option code.
///
/// This constructor creates an instance of the option from the provided
/// chunk of buffer. This buffer may hold the data received on the wire.
+ /// Trailing NULLs will be trimmed.
///
/// @param u universe (V4 or V6).
/// @param type option code.
/// @brief Sets the string value to be held by the option.
///
+ /// Trailing NULLs will be trimmed.
+ ///
/// @param value string value to be set.
///
/// @throw isc::OutOfRange if a string value to be set is empty.
/// it does not decode the option code and length, so the iterators must
/// point to the beginning and end of the option payload respectively.
/// The size of the decoded payload must be at least 1 byte.
+ /// Trailing NULLs will be trimmed.
///
/// @param begin the iterator pointing to the option payload.
/// @param end the iterator pointing to the end of the option payload.
-// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// The purpose of this test is to verify that the string
// can be read from a buffer correctly.
TEST_F(OptionDataTypesTest, readString) {
+
// Prepare a buffer with some string in it.
std::vector<uint8_t> buf;
writeString("hello world", buf);
);
// Check that it is valid.
EXPECT_EQ("hello world", value);
+
+ // Only nulls should throw.
+ OptionBuffer buffer = { 0, 0 };
+ ASSERT_THROW(OptionDataTypeUtil::readString(buffer), isc::OutOfRange);
+
+ // One trailing null should trim off.
+ buffer = {'o', 'n', 'e', 0 };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(3, value.length());
+ EXPECT_EQ(value, std::string("one"));
+
+ // More than one trailing null should trim off.
+ buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(5, value.length());
+ EXPECT_EQ(value, std::string("three"));
+
+ // Embedded null should be left in place.
+ buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(6, value.length());
+ EXPECT_EQ(value, (std::string{"em\0bed", 6}));
+
+ // Leading null should be left in place.
+ buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(8, value.length());
+ EXPECT_EQ(value, (std::string{"\0leading", 8}));
}
// The purpose of this test is to verify that a string can be
-// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// Check that an attempt to use empty string in the constructor
// will result in an exception.
EXPECT_THROW(OptionString(Option::V6, 123, ""), isc::OutOfRange);
+
+ // Check that an attempt to use string containgin only nulls
+ // in the constructor will result in an exception.
+ std::string nulls{"\0\0",2};
+ EXPECT_THROW(OptionString(Option::V6, 123, nulls), isc::OutOfRange);
}
// This test verifies that the constructor which creates an option instance
isc::OutOfRange
);
+ // NULLs should result in an exception.
+ std::vector<uint8_t>nulls = { 0, 0, 0 };
+ EXPECT_THROW(
+ OptionString(Option::V4, 234, nulls.begin(), nulls.begin()),
+ isc::OutOfRange
+ );
+
// Declare option as a scoped pointer here so as its scope is
// function wide. The initialization (constructor invocation)
// is pushed to the ASSERT_NO_THROW macro below, as it may
// And create a string from it.
std::string test_string(data.begin(), data.end());
// This string should be equal to the string used to create
- // option's instance.
+ // option's instance.
EXPECT_TRUE(option_value == test_string);
}
EXPECT_EQ("type=00512, len=00016: \"is a filler text\" (string)", optv6.toText());
}
+// This test checks proper handling of trailing and embedded NULLs in
+// data use to create or option value.
+TEST_F(OptionStringTest, setValueNullsHandling) {
+ OptionString optv4(Option::V4, 123, "123");
+
+ // Only nulls should throw.
+ ASSERT_THROW(optv4.setValue(std::string{"\0\0", 2}), isc::OutOfRange);
+
+ // One trailing null should trim off.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"one\0", 4}));
+ EXPECT_EQ(3, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("one"));
+
+ // More than one trailing null should trim off.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"three\0\0\0", 8}));
+ EXPECT_EQ(5, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("three"));
+
+ // Embedded null should be left in place.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"em\0bed", 6}));
+ EXPECT_EQ(6, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6}));
+
+ // Leading null should be left in place.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"\0leading", 8}));
+ EXPECT_EQ(8, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8}));
+}
+
+// This test checks proper handling of trailing and embedded NULLs in
+// data use to create or option value.
+TEST_F(OptionStringTest, unpackNullsHandling) {
+ OptionString optv4(Option::V4, 123, "123");
+
+ // Only nulls should throw.
+ OptionBuffer buffer = { 0, 0 };
+ ASSERT_THROW(optv4.unpack(buffer.begin(), buffer.end()), isc::OutOfRange);
+
+ // One trailing null should trim off.
+ buffer = {'o', 'n', 'e', 0 };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(3, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("one"));
+
+ // More than one trailing null should trim off.
+ buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(5, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("three"));
+
+ // Embedded null should be left in place.
+ buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(6, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6}));
+
+ // Leading null should be left in place.
+ buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(8, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8}));
+}
+
} // anonymous namespace
-// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2019 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
boost::shared_ptr<Option> x = pkt->getOption(12);
ASSERT_TRUE(x); // option 1 should exist
OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
+
ASSERT_TRUE(option12);
EXPECT_EQ(12, option12->getType()); // this should be option 12
ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
EXPECT_EQ(5, option12->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&option12->getValue()[0], opt_data_ptr + 2, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], opt_data_ptr + 2, 2)); // data len=3
opt_data_ptr += x->len();
x = pkt->getOption(14);
EXPECT_EQ(14, option14->getType()); // this should be option 14
ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
EXPECT_EQ(5, option14->len()); // total option length 5
+
EXPECT_EQ(0, memcmp(&option14->getValue()[0], opt_data_ptr + 2, 3)); // data len=3
opt_data_ptr += x->len();
ASSERT_FALSE(x);
}
+// Verifies that we handle text options that contain trailing
+// and embedded NULLs correctly. Per RFC 2132, Sec 2 we should
+// be stripping trailing NULLs. We've agreed to permit
+// embedded NULLs (for now).
+TEST_F(Pkt4Test, nullTerminatedOptions) {
+ // Construct the onwire packet.
+ vector<uint8_t> base_msg = generateTestPacket2();
+ base_msg.push_back(0x63); // magic cookie
+ base_msg.push_back(0x82);
+ base_msg.push_back(0x53);
+ base_msg.push_back(0x63);
+
+ base_msg.push_back(0x35); // message-type
+ base_msg.push_back(0x1);
+ base_msg.push_back(0x1);
+
+ int base_size = base_msg.size();
+
+ // We'll create four text options, with various combinations of NULLs.
+ vector<uint8_t> hostname = { DHO_HOST_NAME, 5, 't', 'w', 'o', 0, 0 };
+ vector<uint8_t> merit_dump = { DHO_MERIT_DUMP, 4, 'o', 'n', 'e', 0 };
+ vector<uint8_t> root_path = { DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e' };
+ vector<uint8_t> domain_name = { DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd' };
+
+ // Add the options to the onwire packet.
+ vector<uint8_t> test_msg = base_msg;
+ test_msg.insert(test_msg.end(), hostname.begin(), hostname.end());
+ test_msg.insert(test_msg.end(), root_path.begin(), root_path.end());
+ test_msg.insert(test_msg.end(), merit_dump.begin(), merit_dump.end());
+ test_msg.insert(test_msg.end(), domain_name.begin(), domain_name.end());
+ test_msg.push_back(DHO_END);
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&test_msg[0], test_msg.size()));
+
+ // Unpack the onwire packet.
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+
+ OptionPtr opt;
+ OptionStringPtr opstr;
+
+ // Now let's verify that each text option is as expected.
+ ASSERT_TRUE(opt = pkt->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("two", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_MERIT_DUMP));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("one", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_ROOT_PATH));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(4, opstr->getValue().length());
+ EXPECT_EQ("none", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_DOMAIN_NAME));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(6, opstr->getValue().length());
+ std::string embed{"em\0bed", 6};
+ EXPECT_EQ(embed, opstr->getValue());
+
+
+ // Next we pack the packet, to make sure trailing NULLs have
+ // been eliminated, embedded NULLs are intact.
+ EXPECT_NO_THROW(
+ pkt->pack()
+ );
+
+ // Create a vector of our expectd packed option data.
+ vector<uint8_t> packed_opts =
+ {
+ DHO_HOST_NAME, 3, 't', 'w', 'o',
+ DHO_MERIT_DUMP, 3, 'o', 'n', 'e',
+ DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd',
+ DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e',
+ };
+
+ const uint8_t* packed = static_cast<const uint8_t*>(pkt->getBuffer().getData());
+ int packed_len = pkt->getBuffer().getLength();
+
+ // Packed message options should be 3 bytes smaller than original onwire data.
+ int dif = packed_len - test_msg.size();
+ ASSERT_EQ(-3, dif);
+
+ // Make sure the packed content is as expected.
+ EXPECT_EQ(0, memcmp(&packed[base_size], &packed_opts[0], packed_opts.size()));
+}
+
} // end of anonymous namespace