Commit 1e61d7db authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3316'

parents ff7fa9a2 7e723875
......@@ -4431,15 +4431,16 @@ Dhcp4/subnet4 [] list (default)
extensions will be using hooks extensions.
</para>
</note>
<para>In certain cases it is useful to differentiate between different types
of clients and treat them differently. The process of doing classification
is conducted in two steps. The first step is to assess incoming packet and
assign it to zero or more classes. This classification is currently simple,
but is expected to grow in capability soon. Currently the server checks whether
incoming packet has vendor class identifier option (60). If it has, content
of that option is interpreted as a class. For example, modern cable modems
will send this option with value &quot;docsis3.0&quot; and as a result the
packet will belong to class &quot;docsis3.0&quot;.
<para>In certain cases it is useful to differentiate between different
types of clients and treat them differently. The process of doing
classification is conducted in two steps. The first step is to assess
incoming packet and assign it to zero or more classes. This classification
is currently simple, but is expected to grow in capability soon. Currently
the server checks whether incoming packet has vendor class identifier
option (60). If it has, content of that option is prepended with
&quot;VENDOR_CLASS_&quot; then is interpreted as a class. For example,
modern cable modems will send this option with value &quot;docsis3.0&quot;
and as a result the packet will belong to class &quot;VENDOR_CLASS_docsis3.0&quot;.
</para>
<para>It is envisaged that the client classification will be used for changing
......@@ -4450,12 +4451,12 @@ Dhcp4/subnet4 [] list (default)
subnet selection.</para>
<para>
For clients that belong to the docsis3.0 class, the siaddr field is set to
the value of next-server (if specified in a subnet). If there is
boot-file-name option specified, its value is also set in the file field
in the DHCPv4 packet. For eRouter1.0 class, the siaddr is always set to
0.0.0.0. That capability is expected to be moved to external hook
library that will be dedicated to cable modems.
For clients that belong to the VENDOR_CLASS_docsis3.0 class, the siaddr
field is set to the value of next-server (if specified in a subnet). If
there is boot-file-name option specified, its value is also set in the
file field in the DHCPv4 packet. For eRouter1.0 class, the siaddr is
always set to 0.0.0.0. That capability is expected to be moved to
external hook library that will be dedicated to cable modems.
</para>
<para>
......@@ -4483,13 +4484,13 @@ Dhcp4/subnet4 [] list (default)
the 192.0.2.0/24 prefix. The Administrator of that network has decided
that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
managed by the Dhcp4 server. Only clients belonging to client class
docsis3.0 are allowed to use this subnet. Such a configuration can be
achieved in the following way:
VENDOR_CLASS_docsis3.0 are allowed to use this subnet. Such a
configuration can be achieved in the following way:
<screen>
&gt; <userinput>config add Dhcp4/subnet4</userinput>
&gt; <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
&gt; <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
&gt; <userinput>config set Dhcp4/subnet4[0]/client-class "docsis3.0"</userinput>
&gt; <userinput>config set Dhcp4/subnet4[0]/client-class "VENDOR_CLASS_docsis3.0"</userinput>
&gt; <userinput>config commit</userinput></screen>
</para>
......@@ -5558,9 +5559,10 @@ should include options from the isc option space:
assign it to zero or more classes. This classification is currently simple,
but is expected to grow in capability soon. Currently the server checks whether
incoming packet has vendor class option (16). If it has, content
of that option is interpreted as a class. For example, modern cable modems
will send this option with value &quot;docsis3.0&quot; and as a result the
packet will belong to class &quot;docsis3.0&quot;.
of that option is prepended with &quot;VENDOR_CLASS_&quot; interpreted as a
class. For example, modern cable modems will send this option with value
&quot;docsis3.0&quot; and as a result the packet will belong to class
&quot;VENDOR_CLASS_docsis3.0&quot;.
</para>
<para>It is envisaged that the client classification will be used for changing
......
......@@ -80,6 +80,8 @@ Dhcp4Hooks Hooks;
namespace isc {
namespace dhcp {
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
const bool direct_response_desired)
: shutdown_(true), alloc_engine_(), port_(port),
......@@ -1809,15 +1811,15 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
// quals subscriber-id option that was inserted by the relay (CMTS).
// This kind of logic will appear here soon.
if (vendor_class->getValue().find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
pkt->addClass(DOCSIS3_CLASS_MODEM);
classes += string(DOCSIS3_CLASS_MODEM) + " ";
pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM);
classes += string(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM) + " ";
} else
if (vendor_class->getValue().find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
pkt->addClass(DOCSIS3_CLASS_EROUTER);
classes += string(DOCSIS3_CLASS_EROUTER) + " ";
pkt->addClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER);
classes += string(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_EROUTER) + " ";
} else {
classes += vendor_class->getValue();
pkt->addClass(vendor_class->getValue());
classes += VENDOR_CLASS_PREFIX + vendor_class->getValue();
pkt->addClass(VENDOR_CLASS_PREFIX + vendor_class->getValue());
}
if (!classes.empty()) {
......@@ -1833,7 +1835,7 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
return (true);
}
if (query->inClass(DOCSIS3_CLASS_MODEM)) {
if (query->inClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM)) {
// Set next-server. This is TFTP server address. Cable modems will
// download their configuration from that server.
......
......@@ -430,6 +430,14 @@ protected:
/// @param [out] answer A response message to be sent to a client.
void processClientName(const Pkt4Ptr& query, Pkt4Ptr& answer);
/// @brief this is a prefix added to the contend of vendor-class option
///
/// If incoming packet has a vendor class option, its content is
/// prepended with this prefix and then interpreted as a class.
/// For example, a packet that sends vendor class with value of "FOO"
/// will cause the packet to be assigned to class VENDOR_CLASS_FOO.
static const std::string VENDOR_CLASS_PREFIX;
private:
/// @brief Process Client FQDN %Option sent by a client.
///
......
......@@ -3294,8 +3294,8 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
srv.classifyPacket(dis1);
EXPECT_TRUE(dis1->inClass("docsis3.0"));
EXPECT_FALSE(dis1->inClass("eRouter1.0"));
EXPECT_TRUE(dis1->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
EXPECT_FALSE(dis1->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
// Let's create a relayed DISCOVER. This particular relayed DISCOVER has
// vendor-class set to eRouter1.0
......@@ -3305,8 +3305,8 @@ TEST_F(Dhcpv4SrvTest, clientClassification) {
srv.classifyPacket(dis2);
EXPECT_TRUE(dis2->inClass("eRouter1.0"));
EXPECT_FALSE(dis2->inClass("docsis3.0"));
EXPECT_TRUE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
EXPECT_FALSE(dis2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
}
// Checks if the client-class field is indeed used for subnet selection.
......
......@@ -199,6 +199,7 @@ public:
using Dhcpv4Srv::accept;
using Dhcpv4Srv::acceptMessageType;
using Dhcpv4Srv::selectSubnet;
using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
};
class Dhcpv4SrvTest : public ::testing::Test {
......
......@@ -28,6 +28,7 @@
#include <dhcp/option6_iaprefix.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
......@@ -54,6 +55,7 @@
#include <time.h>
#include <iomanip>
#include <fstream>
#include <sstream>
using namespace isc;
using namespace isc::asiolink;
......@@ -98,6 +100,8 @@ Dhcp6Hooks Hooks;
namespace isc {
namespace dhcp {
const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
namespace {
// The following constants describe server's behavior with respect to the
......@@ -2440,36 +2444,30 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
}
void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
OptionVendorClassPtr vclass = boost::dynamic_pointer_cast<
OptionVendorClass>(pkt->getOption(D6O_VENDOR_CLASS));
boost::shared_ptr<OptionCustom> vclass =
boost::dynamic_pointer_cast<OptionCustom>(pkt->getOption(D6O_VENDOR_CLASS));
if (!vclass) {
if (!vclass || vclass->getTuplesNum() == 0) {
return;
}
string classes = "";
std::ostringstream classes;
if (vclass->hasTuple(DOCSIS3_CLASS_MODEM)) {
classes << VENDOR_CLASS_PREFIX << DOCSIS3_CLASS_MODEM;
} else if (vclass->hasTuple(DOCSIS3_CLASS_EROUTER)) {
classes << VENDOR_CLASS_PREFIX << DOCSIS3_CLASS_EROUTER;
} else {
classes << vclass->getTuple(0).getText();
// DOCSIS specific section
if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
.find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
pkt->addClass(DOCSIS3_CLASS_MODEM);
classes += string(DOCSIS3_CLASS_MODEM) + " ";
} else
if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
.find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
pkt->addClass(DOCSIS3_CLASS_EROUTER);
classes += string(DOCSIS3_CLASS_EROUTER) + " ";
}else
{
// Otherwise use the string as is
classes += vclass->readString(VENDOR_CLASS_STRING_INDEX);
pkt->addClass(vclass->readString(VENDOR_CLASS_STRING_INDEX));
}
if (!classes.empty()) {
// If there is no class identified, leave.
if (!classes.str().empty()) {
pkt->addClass(classes.str());
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
.arg(classes);
.arg(classes.str());
}
}
......
......@@ -536,6 +536,15 @@ protected:
/// @param pkt packet to be classified
void classifyPacket(const Pkt6Ptr& pkt);
/// @brief this is a prefix added to the contend of vendor-class option
///
/// If incoming packet has a vendor class option, its content is
/// prepended with this prefix and then interpreted as a class.
/// For example, a packet that sends vendor class with value of "FOO"
/// will cause the packet to be assigned to class VENDOR_CLASS_FOO.
static const std::string VENDOR_CLASS_PREFIX;
private:
/// @brief Implements the error handler for socket open failure.
......
......@@ -1725,7 +1725,7 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
srv.classifyPacket(sol1);
// It should belong to docsis3.0 class. It should not belong to eRouter1.0
EXPECT_TRUE(sol1->inClass("docsis3.0"));
EXPECT_TRUE(sol1->inClass("VENDOR_CLASS_docsis3.0"));
EXPECT_FALSE(sol1->inClass("eRouter1.0"));
// Let's get a relayed SOLICIT. This particular relayed SOLICIT has
......@@ -1736,8 +1736,8 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
srv.classifyPacket(sol2);
EXPECT_TRUE(sol2->inClass("eRouter1.0"));
EXPECT_FALSE(sol2->inClass("docsis3.0"));
EXPECT_TRUE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
}
// Checks if the client-class field is indeed used for subnet selection.
......@@ -1806,6 +1806,31 @@ TEST_F(Dhcpv6SrvTest, clientClassify2) {
EXPECT_TRUE(srv.selectSubnet(sol));
}
// This test checks that the server will handle a Solicit with the Vendor Class
// having a length of 4 (enterprise-id only).
TEST_F(Dhcpv6SrvTest, cableLabsShortVendorClass) {
NakedDhcpv6Srv srv(0);
// Create a simple Solicit with the 4-byte long vendor class option.
Pkt6Ptr sol = captureCableLabsShortVendorClass();
// Simulate that we have received that traffic
srv.fakeReceive(sol);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive6(), it will read all packets from the list set by
// fakeReceive()
srv.run();
// Get Advertise...
ASSERT_FALSE(srv.fake_sent_.empty());
Pkt6Ptr adv = srv.fake_sent_.front();
ASSERT_TRUE(adv);
// This is sent back to client, so port is 546
EXPECT_EQ(DHCP6_CLIENT_PORT, adv->getRemotePort());
}
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.
......
......@@ -110,6 +110,7 @@ public:
using Dhcpv6Srv::writeServerID;
using Dhcpv6Srv::unpackOptions;
using Dhcpv6Srv::name_change_reqs_;
using Dhcpv6Srv::VENDOR_CLASS_PREFIX;
/// @brief packets we pretend to receive
///
......@@ -508,6 +509,7 @@ public:
isc::dhcp::Pkt6Ptr captureRelayedSolicit();
isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass();
/// @brief Auxiliary method that sets Pkt6 fields
///
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-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
......@@ -301,5 +301,29 @@ DHCPv6
return (pkt);
}
Pkt6Ptr isc::test::Dhcpv6SrvTest::captureCableLabsShortVendorClass() {
// This is a simple non-relayed Solicit:
// - client-identifier
// - IA_NA
// - Vendor Class (4 bytes)
// - enterprise-id 4491
// - Vendor-specific Information
// - enterprise-id 4491
std::string hex_string =
"01671cb90001000e0001000152ea903a08002758f1e80003000c00004bd10000000000"
"000000001000040000118b0011000a0000118b000100020020";
std::vector<uint8_t> bin;
// Decode the hex string and store it in bin (which happens
// to be OptionBuffer format)
isc::util::encode::decodeHex(hex_string, bin);
Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
captureSetDefaultFields(pkt);
return (pkt);
}
}; // end of isc::test namespace
}; // end of isc namespace
......@@ -23,6 +23,7 @@ libb10_dhcp___la_SOURCES += iface_mgr_error_handler.h
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
libb10_dhcp___la_SOURCES += opaque_data_tuple.cc opaque_data_tuple.h
libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
......@@ -31,6 +32,7 @@ libb10_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
libb10_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
libb10_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
libb10_dhcp___la_SOURCES += option.cc option.h
......
// 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 <dhcp/opaque_data_tuple.h>
namespace isc {
namespace dhcp {
OpaqueDataTuple::OpaqueDataTuple(LengthFieldType length_field_type)
: length_field_type_(length_field_type) {
}
void
OpaqueDataTuple::append(const std::string& text) {
// Don't do anything if text is empty.
if (!text.empty()) {
append(&text[0], text.size());
}
}
void
OpaqueDataTuple::assign(const std::string& text) {
// If empty string is being assigned, reset the buffer.
if (text.empty()) {
clear();
} else {
assign(&text[0], text.size());
}
}
void
OpaqueDataTuple::clear() {
data_.clear();
}
bool
OpaqueDataTuple::equals(const std::string& other) const {
return (getText() == other);
}
std::string
OpaqueDataTuple::getText() const {
// Convert the data carried in the buffer to a string.
return (std::string(data_.begin(), data_.end()));
}
void
OpaqueDataTuple::pack(isc::util::OutputBuffer& buf) const {
if (getLength() == 0) {
isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
" opaque data field, because the field appears to be empty");
} else if ((1 << (getDataFieldSize() * 8)) <= getLength()) {
isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
" opaque data field, because current data length "
<< getLength() << " exceeds the maximum size for the length"
<< " field size " << getDataFieldSize());
}
if (getDataFieldSize() == 1) {
buf.writeUint8(static_cast<uint8_t>(getLength()));
} else {
buf.writeUint16(getLength());
}
buf.writeData(&getData()[0], getLength());
}
int
OpaqueDataTuple::getDataFieldSize() const {
return (length_field_type_ == LENGTH_1_BYTE ? 1 : 2);
}
OpaqueDataTuple&
OpaqueDataTuple::operator=(const std::string& other) {
// Replace existing data with the new data converted from a string.
assign(&other[0], other.length());
return (*this);
}
bool
OpaqueDataTuple::operator==(const std::string& other) const {
return (equals(other));
}
bool
OpaqueDataTuple::operator!=(const std::string& other) {
return (!equals(other));
}
std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple) {
os << tuple.getText();
return (os);
}
std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple) {
// We will replace the current data with new data.
tuple.clear();
char buf[256];
// Read chunks of data as long as we haven't reached the end of the stream.
while (!is.eof()) {
is.read(buf, sizeof(buf));
// Append the chunk of data we have just read. It is fine if the
// gcount is 0, because append() function will check that.
tuple.append(buf, is.gcount());
}
// Clear eof flag, so as the stream can be read again.
is.clear();
return (is);
}
}
}
// 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.
#ifndef OPAQUE_DATA_TUPLE_H
#define OPAQUE_DATA_TUPLE_H
#include <util/buffer.h>
#include <util/io_utilities.h>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
namespace isc {
namespace dhcp {
/// @brief Exception to be thrown when the operation on @c OpaqueDataTuple
/// object results in an error.
class OpaqueDataTupleError : public Exception {
public:
OpaqueDataTupleError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Represents a single instance of the opaque data preceded by length.
///
/// Some of the DHCP options, such as Vendor Class option (16) in DHCPv6 or
/// V-I Vendor Class option (124) in DHCPv4 may carry multiple pairs of
/// opaque-data preceded by its length. Such pairs are called tuples. This class
/// represents a single instance of the tuple in the DHCPv4 or DHCPv6 option.
///
/// Although, the primary purpose of this class is to represent data tuples in
/// Vendor Class options, there may be other options defined in the future that
/// may have similar structure and this class can be used to represent the data
/// tuples in these new options too.
///
/// This class exposes a set of convenience methods to assign and retrieve the
/// opaque data from the tuple. It also implements a method to render the tuple
/// data into a wire format, as well as a method to create an instance of the
/// tuple from the wire format.
class OpaqueDataTuple {
public:
/// @brief Size of the length field in the tuple.
///
/// In the wire format, the tuple consists of the two fields: one holding
/// a length of the opaque data size, second holding opaque data. The first
/// field's size may be equal to 1 or 2 bytes. Usually, the tuples carried
/// in the DHCPv6 options have 2 byte long length fields, the tuples carried
/// in DHCPv4 options have 1 byte long length fields.
enum LengthFieldType {
LENGTH_1_BYTE,
LENGTH_2_BYTES
};
/// @brief Defines a type of the data buffer used to hold the opaque data.
typedef std::vector<uint8_t> Buffer;
/// @brief Default constructor.
///
/// @param length_field_type Indicates a length of the field which holds
/// the size of the tuple.
OpaqueDataTuple(LengthFieldType length_field_type);
/// @brief Constructor
///
/// Creates a tuple from on-wire data. It calls @c OpaqueDataTuple::unpack
/// internally.
///
/// @param length_field_type Indicates the length of the field holding the
/// opaque data size.
/// @param begin Iterator pointing to the beginning of the buffer holding
/// wire data.
/// @param end Iterator pointing to the end of the buffer holding wire data.
/// @tparam InputIterator Type of the iterators passed to this function.
/// @throw It may throw an exception if the @c unpack throws.
template<typename InputIterator>
OpaqueDataTuple(LengthFieldType length_field_type, InputIterator begin,
InputIterator end)
: length_field_type_(length_field_type) {
unpack(begin, end);
}
/// @brief Appends data to the tuple.
///
/// This function appends the data of the specified length to the tuple.
/// If the specified buffer length is greater than the size of the buffer,
/// the behavior of this function is undefined.
///
/// @param data Iterator pointing to the beginning of the buffer being
/// appended. The source buffer may be an STL object or an array of
/// characters. In the latter case, the pointer to the beginning of this
/// array should be passed.
/// @param len Length of the source buffer.
/// @tparam InputIterator Type of the iterator pointing to the beginning of
/// the source buffer.
template<typename InputIterator>
void append(InputIterator data, const size_t len) {
data_.insert(data_.end(), data, data + len);
}
/// @brief Appends string to the tuple.