option_custom.cc 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. // Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <dhcp/libdhcp++.h>
  15. #include <dhcp/option_data_types.h>
  16. #include <dhcp/option_custom.h>
  17. #include <util/encode/hex.h>
  18. namespace isc {
  19. namespace dhcp {
  20. OptionCustom::OptionCustom(const OptionDefinition& def,
  21. Universe u)
  22. : Option(u, def.getCode(), OptionBuffer()),
  23. definition_(def) {
  24. createBuffers();
  25. }
  26. OptionCustom::OptionCustom(const OptionDefinition& def,
  27. Universe u,
  28. const OptionBuffer& data)
  29. : Option(u, def.getCode(), data.begin(), data.end()),
  30. definition_(def) {
  31. // It is possible that no data is provided if an option
  32. // is being created on a server side. In such case a bunch
  33. // of buffers with default values is first created and then
  34. // the values are replaced using writeXXX functions. Thus
  35. // we need to detect that no data has been specified and
  36. // take a different code path.
  37. if (!data_.empty()) {
  38. createBuffers(data_);
  39. } else {
  40. createBuffers();
  41. }
  42. }
  43. OptionCustom::OptionCustom(const OptionDefinition& def,
  44. Universe u,
  45. OptionBufferConstIter first,
  46. OptionBufferConstIter last)
  47. : Option(u, def.getCode(), first, last),
  48. definition_(def) {
  49. createBuffers(data_);
  50. }
  51. void
  52. OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
  53. checkArrayType();
  54. if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
  55. (address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
  56. isc_throw(BadDataTypeCast, "invalid address specified "
  57. << address.toText() << ". Expected a valid IPv"
  58. << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ?
  59. "4" : "6") << " address.");
  60. }
  61. OptionBuffer buf;
  62. OptionDataTypeUtil::writeAddress(address, buf);
  63. buffers_.push_back(buf);
  64. }
  65. void
  66. OptionCustom::addArrayDataField(const bool value) {
  67. checkArrayType();
  68. OptionBuffer buf;
  69. OptionDataTypeUtil::writeBool(value, buf);
  70. buffers_.push_back(buf);
  71. }
  72. void
  73. OptionCustom::checkIndex(const uint32_t index) const {
  74. if (index >= buffers_.size()) {
  75. isc_throw(isc::OutOfRange, "specified data field index " << index
  76. << " is out of range.");
  77. }
  78. }
  79. template<typename T>
  80. void
  81. OptionCustom::checkDataType(const uint32_t index) const {
  82. // Check that the requested return type is a supported integer.
  83. if (!OptionDataTypeTraits<T>::integer_type) {
  84. isc_throw(isc::dhcp::InvalidDataType, "specified data type"
  85. " is not a supported integer type.");
  86. }
  87. // Get the option definition type.
  88. OptionDataType data_type = definition_.getType();
  89. if (data_type == OPT_RECORD_TYPE) {
  90. const OptionDefinition::RecordFieldsCollection& record_fields =
  91. definition_.getRecordFields();
  92. // When we initialized buffers we have already checked that
  93. // the number of these buffers is equal to number of option
  94. // fields in the record so the condition below should be met.
  95. assert(index < record_fields.size());
  96. // Get the data type to be returned.
  97. data_type = record_fields[index];
  98. }
  99. if (OptionDataTypeTraits<T>::type != data_type) {
  100. isc_throw(isc::dhcp::InvalidDataType,
  101. "specified data type " << data_type << " does not"
  102. " match the data type in an option definition for field"
  103. " index " << index);
  104. }
  105. }
  106. void
  107. OptionCustom::createBuffers() {
  108. definition_.validate();
  109. std::vector<OptionBuffer> buffers;
  110. OptionDataType data_type = definition_.getType();
  111. // This function is called when an empty data buffer has been
  112. // passed to the constructor. In such cases values for particular
  113. // data fields will be set using modifier functions but for now
  114. // we need to initialize a set of buffers that are specified
  115. // for an option by its definition. Since there is no data yet,
  116. // we are going to fill these buffers with default values.
  117. if (data_type == OPT_RECORD_TYPE) {
  118. // For record types we need to iterate over all data fields
  119. // specified in option definition and create corresponding
  120. // buffers for each of them.
  121. const OptionDefinition::RecordFieldsCollection fields =
  122. definition_.getRecordFields();
  123. for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
  124. field != fields.end(); ++field) {
  125. OptionBuffer buf;
  126. // For data types that have a fixed size we can use the
  127. // utility function to get the buffer's size.
  128. size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
  129. // For variable data sizes the utility function returns zero.
  130. // It is ok for string values because the default string
  131. // is 'empty'. However for FQDN the empty value is not valid
  132. // so we initialize it to '.'.
  133. if (data_size == 0 &&
  134. *field == OPT_FQDN_TYPE) {
  135. OptionDataTypeUtil::writeFqdn(".", buf);
  136. } else {
  137. // At this point we can resize the buffer. Note that
  138. // for string values we are setting the empty buffer
  139. // here.
  140. buf.resize(data_size);
  141. }
  142. // We have the buffer with default value prepared so we
  143. // add it to the set of buffers.
  144. buffers.push_back(buf);
  145. }
  146. } else if (!definition_.getArrayType() &&
  147. data_type != OPT_EMPTY_TYPE) {
  148. // For either 'empty' options we don't have to create any buffers
  149. // for obvious reason. For arrays we also don't create any buffers
  150. // yet because the set of fields that belong to the array is open
  151. // ended so we can't allocate required buffers until we know how
  152. // many of them are needed.
  153. // For non-arrays we have a single value being held by the option
  154. // so we have to allocate exactly one buffer.
  155. OptionBuffer buf;
  156. size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
  157. if (data_size == 0 &&
  158. data_type == OPT_FQDN_TYPE) {
  159. OptionDataTypeUtil::writeFqdn(".", buf);
  160. } else {
  161. // Note that if our option holds a string value then
  162. // we are making empty buffer here.
  163. buf.resize(data_size);
  164. }
  165. // Add a buffer that we have created and leave.
  166. buffers.push_back(buf);
  167. }
  168. // The 'swap' is used here because we want to make sure that we
  169. // don't touch buffers_ until we successfully allocate all
  170. // buffers to be stored there.
  171. std::swap(buffers, buffers_);
  172. }
  173. void
  174. OptionCustom::createBuffers(const OptionBuffer& data_buf) {
  175. // Check that the option definition is correct as we are going
  176. // to use it to split the data_ buffer into set of sub buffers.
  177. definition_.validate();
  178. std::vector<OptionBuffer> buffers;
  179. OptionBuffer::const_iterator data = data_buf.begin();
  180. OptionDataType data_type = definition_.getType();
  181. if (data_type == OPT_RECORD_TYPE) {
  182. // An option comprises a record of data fields. We need to
  183. // get types of these data fields to allocate enough space
  184. // for each buffer.
  185. const OptionDefinition::RecordFieldsCollection& fields =
  186. definition_.getRecordFields();
  187. // Go over all data fields within a record.
  188. for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
  189. field != fields.end(); ++field) {
  190. // For fixed-size data type such as boolean, integer, even
  191. // IP address we can use the utility function to get the required
  192. // buffer size.
  193. size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
  194. // For variable size types (e.g. string) the function above will
  195. // return 0 so we need to do a runtime check of the length.
  196. if (data_size == 0) {
  197. // FQDN is a special data type as it stores variable length data
  198. // but the data length is encoded in the buffer. The easiest way
  199. // to obtain the length of the data is to read the FQDN. The
  200. // utility function will return the size of the buffer on success.
  201. if (*field == OPT_FQDN_TYPE) {
  202. std::string fqdn =
  203. OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
  204. // The size of the buffer holding an FQDN is always
  205. // 1 byte larger than the size of the string
  206. // representation of this FQDN.
  207. data_size = fqdn.size() + 1;
  208. } else {
  209. // In other case we are dealing with string or binary value
  210. // which size can't be determined. Thus we consume the
  211. // remaining part of the buffer for it. Note that variable
  212. // size data can be laid at the end of the option only and
  213. // that the validate() function in OptionDefinition object
  214. // should have checked wheter it is a case for this option.
  215. data_size = std::distance(data, data_buf.end());
  216. }
  217. if (data_size == 0) {
  218. // If we reached the end of buffer we assume that this option is
  219. // truncated because there is no remaining data to initialize
  220. // an option field.
  221. if (data_size == 0) {
  222. isc_throw(OutOfRange, "option buffer truncated");
  223. }
  224. }
  225. } else {
  226. // Our data field requires that there is a certain chunk of
  227. // data left in the buffer. If not, option is truncated.
  228. if (std::distance(data, data_buf.end()) < data_size) {
  229. isc_throw(OutOfRange, "option buffer truncated");
  230. }
  231. }
  232. // Store the created buffer.
  233. buffers.push_back(OptionBuffer(data, data + data_size));
  234. // Proceed to the next data field.
  235. data += data_size;
  236. }
  237. } else if (data_type != OPT_EMPTY_TYPE) {
  238. // If data_type value is other than OPT_RECORD_TYPE, our option is
  239. // empty (have no data at all) or it comprises one or more
  240. // data fields of the same type. The type of those fields
  241. // is held in the data_type variable so let's use it to determine
  242. // a size of buffers.
  243. size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
  244. // The check below will fail if the input buffer is too short
  245. // for the data size being held by this option.
  246. // Note that data_size returned by getDataTypeLen may be zero
  247. // if variable length data is being held by the option but
  248. // this will not cause this check to throw exception.
  249. if (std::distance(data, data_buf.end()) < data_size) {
  250. isc_throw(OutOfRange, "option buffer truncated");
  251. }
  252. // For an array of values we are taking different path because
  253. // we have to handle multiple buffers.
  254. if (definition_.getArrayType()) {
  255. while (data != data_buf.end()) {
  256. // FQDN is a special case because it is of a variable length.
  257. // The actual length for a particular FQDN is encoded within
  258. // a buffer so we have to actually read the FQDN from a buffer
  259. // to get it.
  260. if (data_type == OPT_FQDN_TYPE) {
  261. std::string fqdn =
  262. OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
  263. // The size of the buffer holding an FQDN is always
  264. // 1 byte larger than the size of the string
  265. // representation of this FQDN.
  266. data_size = fqdn.size() + 1;
  267. }
  268. // We don't perform other checks for data types that can't be
  269. // used together with array indicator such as strings, empty field
  270. // etc. This is because OptionDefinition::validate function should
  271. // have checked this already. Thus data_size must be greater than
  272. // zero.
  273. assert(data_size > 0);
  274. // Get chunks of data and store as a collection of buffers.
  275. // Truncate any remaining part which length is not divisible by
  276. // data_size. Note that it is ok to truncate the data if and only
  277. // if the data buffer is long enough to keep at least one value.
  278. // This has been checked above already.
  279. if (std::distance(data, data_buf.end()) < data_size) {
  280. break;
  281. }
  282. buffers.push_back(OptionBuffer(data, data + data_size));
  283. data += data_size;
  284. }
  285. } else {
  286. // For non-arrays the data_size can be zero because
  287. // getDataTypeLen returns zero for variable size data types
  288. // such as strings. Simply take whole buffer.
  289. if (data_size == 0) {
  290. // For FQDN we get the size by actually reading the FQDN.
  291. if (data_type == OPT_FQDN_TYPE) {
  292. std::string fqdn =
  293. OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
  294. // The size of the buffer holding an FQDN is always
  295. // 1 bytes larger than the size of the string
  296. // representation of this FQDN.
  297. data_size = fqdn.size() + 1;
  298. } else {
  299. data_size = std::distance(data, data_buf.end());
  300. }
  301. }
  302. if (data_size > 0) {
  303. buffers.push_back(OptionBuffer(data, data + data_size));
  304. } else {
  305. isc_throw(OutOfRange, "option buffer truncated");
  306. }
  307. }
  308. }
  309. // If everything went ok we can replace old buffer set with new ones.
  310. std::swap(buffers_, buffers);
  311. }
  312. std::string
  313. OptionCustom::dataFieldToText(const OptionDataType data_type,
  314. const uint32_t index) const {
  315. std::ostringstream text;
  316. // Get the value of the data field.
  317. switch (data_type) {
  318. case OPT_BINARY_TYPE:
  319. text << util::encode::encodeHex(readBinary(index));
  320. break;
  321. case OPT_BOOLEAN_TYPE:
  322. text << (readBoolean(index) ? "true" : "false");
  323. break;
  324. case OPT_INT8_TYPE:
  325. text << readInteger<int8_t>(index);
  326. break;
  327. case OPT_INT16_TYPE:
  328. text << readInteger<int16_t>(index);
  329. break;
  330. case OPT_INT32_TYPE:
  331. text << readInteger<int32_t>(index);
  332. break;
  333. case OPT_UINT8_TYPE:
  334. text << readInteger<uint8_t>(index);
  335. break;
  336. case OPT_UINT16_TYPE:
  337. text << readInteger<uint16_t>(index);
  338. break;
  339. case OPT_UINT32_TYPE:
  340. text << readInteger<uint32_t>(index);
  341. break;
  342. case OPT_IPV4_ADDRESS_TYPE:
  343. case OPT_IPV6_ADDRESS_TYPE:
  344. text << readAddress(index).toText();
  345. break;
  346. case OPT_FQDN_TYPE:
  347. text << readFqdn(index);
  348. break;
  349. case OPT_STRING_TYPE:
  350. text << readString(index);
  351. break;
  352. default:
  353. ;
  354. }
  355. // Append data field type in brackets.
  356. text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) ";
  357. return (text.str());
  358. }
  359. void
  360. OptionCustom::pack4(isc::util::OutputBuffer& buf) {
  361. if (len() > 255) {
  362. isc_throw(OutOfRange, "DHCPv4 Option " << type_
  363. << " value is too high. At most 255 is supported.");
  364. }
  365. buf.writeUint8(type_);
  366. buf.writeUint8(len() - getHeaderLen());
  367. // Write data from buffers.
  368. for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
  369. it != buffers_.end(); ++it) {
  370. // In theory the createBuffers function should have taken
  371. // care that there are no empty buffers added to the
  372. // collection but it is almost always good to make sure.
  373. if (!it->empty()) {
  374. buf.writeData(&(*it)[0], it->size());
  375. }
  376. }
  377. // Write suboptions.
  378. packOptions(buf);
  379. }
  380. void
  381. OptionCustom::pack6(isc::util::OutputBuffer& buf) {
  382. buf.writeUint16(type_);
  383. buf.writeUint16(len() - getHeaderLen());
  384. // Write data from buffers.
  385. for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
  386. it != buffers_.end(); ++it) {
  387. if (!it->empty()) {
  388. buf.writeData(&(*it)[0], it->size());
  389. }
  390. }
  391. packOptions(buf);
  392. }
  393. asiolink::IOAddress
  394. OptionCustom::readAddress(const uint32_t index) const {
  395. checkIndex(index);
  396. // The address being read can be either IPv4 or IPv6. The decision
  397. // is made based on the buffer length. If it holds 4 bytes it is IPv4
  398. // address, if it holds 16 bytes it is IPv6.
  399. if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
  400. return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
  401. } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
  402. return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
  403. } else {
  404. isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
  405. << " IP address. Invalid buffer length "
  406. << buffers_[index].size() << ".");
  407. }
  408. }
  409. void
  410. OptionCustom::writeAddress(const asiolink::IOAddress& address,
  411. const uint32_t index) {
  412. using namespace isc::asiolink;
  413. checkIndex(index);
  414. if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
  415. (address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) {
  416. isc_throw(BadDataTypeCast, "invalid address specified "
  417. << address.toText() << ". Expected a valid IPv"
  418. << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
  419. << " address.");
  420. }
  421. OptionBuffer buf;
  422. OptionDataTypeUtil::writeAddress(address, buf);
  423. std::swap(buf, buffers_[index]);
  424. }
  425. const OptionBuffer&
  426. OptionCustom::readBinary(const uint32_t index) const {
  427. checkIndex(index);
  428. return (buffers_[index]);
  429. }
  430. void
  431. OptionCustom::writeBinary(const OptionBuffer& buf,
  432. const uint32_t index) {
  433. checkIndex(index);
  434. buffers_[index] = buf;
  435. }
  436. bool
  437. OptionCustom::readBoolean(const uint32_t index) const {
  438. checkIndex(index);
  439. return (OptionDataTypeUtil::readBool(buffers_[index]));
  440. }
  441. void
  442. OptionCustom::writeBoolean(const bool value, const uint32_t index) {
  443. checkIndex(index);
  444. buffers_[index].clear();
  445. OptionDataTypeUtil::writeBool(value, buffers_[index]);
  446. }
  447. std::string
  448. OptionCustom::readFqdn(const uint32_t index) const {
  449. checkIndex(index);
  450. return (OptionDataTypeUtil::readFqdn(buffers_[index]));
  451. }
  452. void
  453. OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
  454. checkIndex(index);
  455. // Create a temporay buffer where the FQDN will be written.
  456. OptionBuffer buf;
  457. // Try to write to the temporary buffer rather than to the
  458. // buffers_ member directly guarantees that we don't modify
  459. // (clear) buffers_ until we are sure that the provided FQDN
  460. // is valid.
  461. OptionDataTypeUtil::writeFqdn(fqdn, buf);
  462. // If we got to this point it means that the FQDN is valid.
  463. // We can move the contents of the teporary buffer to the
  464. // target buffer.
  465. std::swap(buffers_[index], buf);
  466. }
  467. std::string
  468. OptionCustom::readString(const uint32_t index) const {
  469. checkIndex(index);
  470. return (OptionDataTypeUtil::readString(buffers_[index]));
  471. }
  472. void
  473. OptionCustom::writeString(const std::string& text, const uint32_t index) {
  474. checkIndex(index);
  475. // Let's clear a buffer as we want to replace the value of the
  476. // whole buffer. If we fail to clear the buffer the data will
  477. // be appended.
  478. buffers_[index].clear();
  479. // If the text value is empty we can leave because the buffer
  480. // is already empty.
  481. if (!text.empty()) {
  482. OptionDataTypeUtil::writeString(text, buffers_[index]);
  483. }
  484. }
  485. void
  486. OptionCustom::unpack(OptionBufferConstIter begin,
  487. OptionBufferConstIter end) {
  488. data_ = OptionBuffer(begin, end);
  489. // Chop the buffer stored in data_ into set of sub buffers.
  490. createBuffers(data_);
  491. }
  492. uint16_t
  493. OptionCustom::len() {
  494. // The length of the option is a sum of option header ...
  495. int length = getHeaderLen();
  496. // ... lengths of all buffers that hold option data ...
  497. for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
  498. buf != buffers_.end(); ++buf) {
  499. length += buf->size();
  500. }
  501. // ... and lengths of all suboptions
  502. for (OptionCustom::OptionCollection::iterator it = options_.begin();
  503. it != options_.end();
  504. ++it) {
  505. length += (*it).second->len();
  506. }
  507. return (length);
  508. }
  509. void OptionCustom::setData(const OptionBufferConstIter first,
  510. const OptionBufferConstIter last) {
  511. // We will copy entire option buffer, so we have to resize data_.
  512. data_.resize(std::distance(first, last));
  513. std::copy(first, last, data_.begin());
  514. // Chop the data_ buffer into set of buffers that represent
  515. // option fields data.
  516. createBuffers(data_);
  517. }
  518. std::string OptionCustom::toText(int indent) {
  519. std::stringstream tmp;
  520. for (int i = 0; i < indent; ++i)
  521. tmp << " ";
  522. tmp << "type=" << type_ << ", len=" << len()-getHeaderLen()
  523. << ", data fields:" << std::endl;
  524. OptionDataType data_type = definition_.getType();
  525. if (data_type == OPT_RECORD_TYPE) {
  526. const OptionDefinition::RecordFieldsCollection& fields =
  527. definition_.getRecordFields();
  528. // For record types we iterate over fields defined in
  529. // option definition and match the appropriate buffer
  530. // with them.
  531. for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
  532. field != fields.end(); ++field) {
  533. for (int j = 0; j < indent + 2; ++j) {
  534. tmp << " ";
  535. }
  536. tmp << "#" << std::distance(fields.begin(), field) << " "
  537. << dataFieldToText(*field, std::distance(fields.begin(),
  538. field))
  539. << std::endl;
  540. }
  541. } else {
  542. // For non-record types we iterate over all buffers
  543. // and print the data type set globally for an option
  544. // definition. We take the same code path for arrays
  545. // and non-arrays as they only differ in such a way that
  546. // non-arrays have just single data field.
  547. for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
  548. for (int j = 0; j < indent + 2; ++j) {
  549. tmp << " ";
  550. }
  551. tmp << "#" << i << " "
  552. << dataFieldToText(definition_.getType(), i)
  553. << std::endl;
  554. }
  555. }
  556. // print suboptions
  557. for (OptionCollection::const_iterator opt = options_.begin();
  558. opt != options_.end();
  559. ++opt) {
  560. tmp << (*opt).second->toText(indent+2);
  561. }
  562. return tmp.str();
  563. }
  564. } // end of isc::dhcp namespace
  565. } // end of isc namespace