option_custom.cc 24 KB

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