BIND 10 trac2378, updated. 52f964ebdc8687ee237a651849bea7c5d3301199 [2378] Fixes of ZoneLoader and tests

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Dec 4 14:33:11 UTC 2012


The branch, trac2378 has been updated
  discards  6f90157ffab047e614c9574529018cc8ca230d1b (commit)
  discards  3c212ec16d0b74d9532510c12f4f8f8611667111 (commit)
  discards  6f58803e1bd507586a10ba00c86cfb179124cfd7 (commit)
  discards  fb1789ccdcb4740550af52b388dc46b0fed81319 (commit)
  discards  0c89bc5d564a9961538d4efa47a3194f239cf681 (commit)
  discards  abd1274d3bc04463752ff163bf156f8d21cf376e (commit)
  discards  33faa4833595ec772f8b5cfd0db01059bac078f1 (commit)
  discards  70592542b7219bcf086259b4cab8f2a934f3e2b7 (commit)
  discards  80a9ba70da40fe59384c8da4ae3dd283a9816ce6 (commit)
  discards  4e430b6801b4c484c75595087d6e5e825664dcc2 (commit)
  discards  cf85e4d51ac0817f95f969066cc395677a28902c (commit)
  discards  bc546548e6794935d843edd90374608d9c813795 (commit)
  discards  abe51e180be58ec3eb815cf0d467982bd496bfaa (commit)
  discards  3bb5cfa36de0b8df04fccfb672023c6f4a8df71b (commit)
  discards  b7d282cb7b4b6b61b5daa317eece5155533acda8 (commit)
  discards  73529a601bf9206ff7dc8931df384b578984221d (commit)
  discards  b5f8390492053825950e7ef31733f80b1548d315 (commit)
  discards  f6f141e854a5956f97c8dbd2aca9771d36280709 (commit)
       via  52f964ebdc8687ee237a651849bea7c5d3301199 (commit)
       via  bde988a3678e7f3b68a585d0a98c43be646076d3 (commit)
       via  b35b0f3aa8042ac3602b7d505861bdf100d74701 (commit)
       via  d6c2f27796f92922ea50ce1efcacfb5ba5c68064 (commit)
       via  0c9d9a17f920a7d49ba7fefec3bd33e40e33fb87 (commit)
       via  dbbce202bf0636ca9884b2369f0c75929b8a7342 (commit)
       via  3992021b27bd11edb83a8727fef9329e942cd37a (commit)
       via  ddb5bb5d8c0df437720ac09cea2c57fbad13a2ab (commit)
       via  0bee5dbc95e6e3d6af6aaff07e11c6244b6b47e3 (commit)
       via  aa101d2d0690864729fdd9ce4507e0a8af17f756 (commit)
       via  821a81bd0c21eb33ed2095468915c18e88bc5f5a (commit)
       via  5a737ce6d4f4d5f464d2004216fcf9c8bbfda8af (commit)
       via  3c1bfee29255f423f4c17894e985d5e1f5d53071 (commit)
       via  d194e75bfce2855cdd24ab25057f06ae388153dc (commit)
       via  3da9d6198bdedd9317b290f70c9bbe613894037b (commit)
       via  549e44164f1e4203bf1acb8d6c137f1de5a48f5a (commit)
       via  fd7f3a84f391957a09996ab869d079b6fe53a9fd (commit)
       via  5ec0945ea8abd8ce3aa59aa851421269d9f807d4 (commit)
       via  11ed5b5ca1fb48c7754c1d85b65fa00b00630d78 (commit)
       via  ad9807ad75f8b76413de4ab0478cff9611a2817a (commit)
       via  0f6b84e5523df12f5017ef7615bd0bb25359cc5f (commit)
       via  42b0cb7bc7423e9f78199ea2f60a449dd98a2843 (commit)
       via  2894c45545298103feef9dd0a5f06405d88d67ea (commit)
       via  24c515276e5a15558824fa12b8fefc9b80c315d3 (commit)
       via  cbffb4ac5bf27888553a102ac6ee2a1fbf07642b (commit)
       via  b548f4ed6cc20a5d8bb802aed646af007c0653b8 (commit)
       via  c90033f1d91fd82c7bafbe645de95281c46ff46f (commit)
       via  7e5b7f352db7ef0778b85dbf73be66210b739b46 (commit)
       via  92226343e053027d4abc7a9f2406016d6adfc112 (commit)
       via  57fda3f1db7ebbc966ae1ab980409a923196ba5e (commit)
       via  8545674c14771700ce0aa611f3fa734082d6e444 (commit)
       via  3814969790ec02dceaee88372a977a52bc210199 (commit)
       via  e4ecc70f8d016e19fb3274a8d2e802cb22b76231 (commit)
       via  3a81d2d941bb7f8be0219cc2ec4176c894634237 (commit)
       via  425e18f7045e7300230bb2876d69a1ec9837a6bd (commit)
       via  5bb4db417ecd1b94170fa87d11d91d604616c1bb (commit)
       via  5d239b6aed71f987330add0bbcfc7baed670e804 (commit)
       via  9440d65d7e71ec30e0d268f5f9f5b1980d288b98 (commit)
       via  dfd0dd105b4fc8054e3b9cfd1bc5547302f54578 (commit)
       via  e441d6b05a022f3c0a7140ae43fe42d5fd12cce3 (commit)
       via  0ad163bb379066067551f1f3623afe43cf5bd327 (commit)
       via  f7f3060b97886bea4fbc4408a5c432e81096e3a9 (commit)
       via  b9f1eefe7e7040db4841e75a38b6a9cd1511eb59 (commit)
       via  03dc2386b14131c0eb35408952e3ddc8ca017831 (commit)
       via  3ae13a89a2f238602d5dd7e3dc12da963de21ec8 (commit)
       via  f73f27474fe73abacded88f4dad74867331cb402 (commit)
       via  ca8fc9f4147a8f332280fde6cfad99b963ebc40a (commit)
       via  b48ecd894a0ee52c132e041f686938bdb230d743 (commit)
       via  638a2d7fbb2a0548e002d57f662f51621175a3a4 (commit)
       via  cb52403a8ad934f8dc32c6404b5c97b65057d63d (commit)
       via  a9a6007e022bdc61bcec8d01b51562f9f33f8fd3 (commit)
       via  3e964a48bd208e8a7dc04ef1ff8de5cf1da5a514 (commit)
       via  8e32eb96aad5d079931b5c7afac55a02f332f3cf (commit)
       via  6226330df851a092d9df353081fe1d9c10743d36 (commit)
       via  c73280d2ebe15512cd4f09ddbb28c2f5030cd86f (commit)
       via  1c4683ddcc108f12b355c5e7b5476671fa6c8a33 (commit)
       via  25f65134a9058c888a4181ba413ec1756e6b1d45 (commit)
       via  5c8cd4010e950d6d0aa68586252c5a3039e5a248 (commit)
       via  3563065b6cc893ab8436973c41eb388dbbecc07e (commit)
       via  41754f406f6edb8d77bd84b34c52c4a2487143a8 (commit)
       via  9a5e89e0e6aab0e50005cf9f9d7bf577eedd1bf3 (commit)
       via  5dacdd2da8710b91b85fd73d025d38e69317d6dc (commit)
       via  6744c100953f6def5500bcb4bfc330b9ffba0f5f (commit)
       via  5e0e2e53c5c5876ae540a0ab26934c0e7242a673 (commit)
       via  2b8518d98293f746baab407d7d6744bde367ced3 (commit)
       via  022d3888199e59b7a4bc256eaaf188a64db61cdf (commit)
       via  d4b9c33282199c912e6f795f6bbfddde12510795 (commit)
       via  d27729277c9e4c9bb4cf121700d67181d6c0aadc (commit)
       via  09cd4b9b32415144a2542171558b56226322bd7e (commit)
       via  597ed99f869f9d170c93467e8abcc2b03959f75f (commit)
       via  ae3a1e5d17d5c8ab8c9dcf20cf60a68744f5cca0 (commit)
       via  d52186367e1f8b49dcb5a3b4825841a95251412f (commit)
       via  f70d131fd20085a472de32f86299a77647f18fd5 (commit)
       via  b991213a389213fe42ef0c87bde56e462c025229 (commit)
       via  c6c7cdee4088d6be3728ffaf07a25b920b0e8b5c (commit)
       via  ebea1d79bcd55536f89e250ce1be13a8369171f7 (commit)
       via  610720a5bef22ba7193c9ad6e3cbe318eebdbf14 (commit)
       via  3b408810eb76ca8e360da549b0e271d15d02b0ce (commit)
       via  d8748ae0321eca340142c98f708cdccc2d62bda7 (commit)
       via  9f6b45ee210a253dca608848a58c824ff5e0d234 (commit)
       via  fa4d217e1e5977170eda350f3101616d0182a21b (commit)
       via  b89f0e0e69cedd0018a5c558bac1db6df944ba3c (commit)
       via  a5683591b24d1eaf659b3820707f4de08bb59e40 (commit)
       via  aac7b173828dde1f9bfb1807828132b15c58674a (commit)
       via  28d885b457dda970d9aecc5de018ec1120143a10 (commit)
       via  11e658493d1672d39194580ffb1dde98101608a9 (commit)
       via  d3c5e8d90a97ce403e2e0e079094d20b43fc8030 (commit)
       via  0854840b717e6f79ba5648571dd0c71306af73e5 (commit)
       via  c8194121792a6f19615f76de611cfba3af927a07 (commit)
       via  1c36cb6a3edd1029e115862fbede8a81a6a96cf3 (commit)
       via  eb79abb35f62e96268eb687c100c1f1129d15537 (commit)
       via  0c36cd61ca034017db27d2f03d7a791c4717813a (commit)
       via  41b26fc471f6e15525a7fe5d522529eea45c73db (commit)
       via  7024914e4253ce983f0dc28f623956ea6c82241e (commit)
       via  2c10fa4964550d7402ee0fc93401f8ad39f29e87 (commit)
       via  c3e696cb4ba09b3cd53842d684f98262cf0cee7a (commit)
       via  d85d48eee9608db9f4c912e5cfe3ae1f2f726c05 (commit)
       via  ee7dbbde6311ec0e9519169871aa182dd7cc1440 (commit)
       via  e8dd780ac88735b31bf3e7df6dd9f4a2b972da94 (commit)
       via  43b0ea4216ce8578e630be460fa5eadacb712c40 (commit)
       via  e206bfd604fd82c5747bc85e3f1e8d6de8b14908 (commit)
       via  31b4c8a77f74ff4f417171af1ceb428c134572a3 (commit)
       via  0c58aace22046e4e58360df79299150b745994a4 (commit)
       via  69950d299b32dea11452b629fedff973108bc263 (commit)
       via  863da09e4d4ef50693860dd95948ae5171ba5116 (commit)
       via  8b10f38aeb02dba5b454c94e8788de450a5955be (commit)
       via  bb38dbf06510c2a9dfa60b68e56836404d62f694 (commit)
       via  c42c6b938afa4e77d8b0545f1b2dbd335c9d3890 (commit)
       via  019d55a3b50fffc8694f982ccc457dc1c61a04a2 (commit)
       via  ff2167388ff8a86dfdae23fbd147602b102b0319 (commit)
       via  01c67a64dc682abada0cd52505e2cf17a5029494 (commit)
       via  2dd98a113773481b77ca0600c1d1028e2b5fdd72 (commit)
       via  7cbe475737d43aaf4757b5df76d9bea75b0bf5a1 (commit)
       via  e4670244659f82b8fabdcaddce43d229a79f34f8 (commit)
       via  47e1dfee342b4b1fc269109d130bea5eeeff743f (commit)
       via  81231871c46b9dd9a06db6bd239688bd60270dec (commit)
       via  deaf3af3f2b12e34dee77b31ff28748cbaef3778 (commit)
       via  af81d772421d616ff4d219a0feb371a9e2ec3edf (commit)
       via  276a08d3371eef268c0e0b738d0c8583c3d8641d (commit)
       via  8e6846495d72600013021d331c8138943f1d4c14 (commit)
       via  0a5970240b9e8ae08e4a06b67a0678a19b5def45 (commit)
       via  c12d1b3b6e90a0918e498282b2374ac294258cdf (commit)
       via  a6c3266fe1acdfa5393f1cd2aa2a502615800943 (commit)
       via  c02e37894d858a0c3f70deb90ffba14c0ae58b54 (commit)
       via  6921ae3d95c08a6f00f38dae2fb7f14622f49440 (commit)
       via  331d377c8fc5f5a1876cbe52ca714b2a002bb103 (commit)
       via  e980e67482b6b82fab84cd4d2b0aa15f07c23f79 (commit)
       via  0bfb997069b6574b985291a0a50f02487c3614dd (commit)
       via  1b6614825a75916836c64bbf7a52d7913b189910 (commit)
       via  1051eb47c63f0260729de5213cc4f95827edbd3e (commit)
       via  79968a268be53f0804141068ddbb433dc5ff1bbd (commit)
       via  f47316a1d2b49791b785cf7b8b5e11a13a7a0fbe (commit)
       via  8f0e9452fd3cc94ef44352839e91e2a4a1d91c55 (commit)
       via  bfc050ed6c9daf2537b5d7aad8fb6f4c6327e6e5 (commit)
       via  7c73dd5e84c12f286970ae22c31aa4513a495646 (commit)
       via  f3da5ed9ef5a25ba65096620b1e7a7e21e04a2c2 (commit)
       via  f957fa651d7cb9b7e8364a58a7c438c367abb659 (commit)
       via  08d0d10dd79cfa9b4f5b4d42bbfef15846d77d68 (commit)
       via  69e9734996d5d1edf533929598093cba0432e406 (commit)
       via  4263ba5f49dad8f1d282de20b9dc7644953472b7 (commit)
       via  f31f6dd3b5abf5e25c3416ac963d8de8db43175f (commit)
       via  65962eeed622a6fc92b2e47ac881fbe7b54d3bf2 (commit)
       via  553dfb31fc1ef98ae43e9d2929ba748c4ea14ca4 (commit)
       via  0ce073c9aab78d0136e7c2ba48731741b886ab4b (commit)
       via  ebe0d8a602ba6157bf519f1f79759a5bfcf1a4a1 (commit)
       via  8018033c5e0e31be028a3c08ec33234d794aa708 (commit)
       via  5378e305cb5c51dd3245c8844fb76a99e0f7999d (commit)
       via  d9c0f5f2d9294d8eb026bf92fd8fda103e9fc747 (commit)
       via  2241bdcfffcc27aea35b79926eb2769fd479f1e9 (commit)
       via  2066e02cfc6950bf8cf8e40a9a678ad74563bd36 (commit)
       via  35491a7585e0e7ed13ea9012ff03415ffb85103a (commit)
       via  9397bd5dbf2b82a62f2006775ea92bb3b05b755d (commit)
       via  7eb7d5efa51485ec96434c08249856cc8cf7c8c9 (commit)
       via  38f650c71af54bc238221b540dd2fa302a862885 (commit)
       via  f7e29b88367c65f57c6a84383fd0ebc520dc26d6 (commit)
       via  798e61add913f36ebf6967d62e456ec6c6e1b757 (commit)
       via  cb20cd84c91a2de735a0e31653cf8704efd5e159 (commit)
       via  56b5c356dd0e122da66a0ee94c33368ff1f86386 (commit)
       via  d805ae680fc4fc5920c5bc50e2ae0817379df1cf (commit)
       via  371ad74bd7276f2c5e6c9917f154ebd333a835e0 (commit)
       via  bedd068396a44ac7fed5a7d4c3f3a8a94861c327 (commit)
       via  4fb2c320d91021da1a7fa3c3f9f2f3a2906ee24f (commit)
       via  002527091f067467ab0d85837d12b89295e961d1 (commit)
       via  1558c3b17a0c31adb4aa62ad2f9d1cff65056bbd (commit)
       via  89a677920d39e4da45f49811f6bb61e3e46fcd49 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (6f90157ffab047e614c9574529018cc8ca230d1b)
            \
             N -- N -- N (52f964ebdc8687ee237a651849bea7c5d3301199)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 52f964ebdc8687ee237a651849bea7c5d3301199
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Dec 4 13:57:50 2012 +0100

    [2378] Fixes of ZoneLoader and tests
    
    After the rebase on top of working 2377. The most significant - don't
    access updater if it doesn't exist.

commit bde988a3678e7f3b68a585d0a98c43be646076d3
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Thu Nov 29 15:26:42 2012 +0100

    [2378] Implement the master file loading

commit b35b0f3aa8042ac3602b7d505861bdf100d74701
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Thu Nov 29 15:25:49 2012 +0100

    [2378] It would throw at load(), not constructor
    
    There's no way to know the master file would be broken in the
    constructor. So update the documentation to say it'd throw from the
    load() and loadIncremental().

commit d6c2f27796f92922ea50ce1efcacfb5ba5c68064
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Thu Nov 29 15:21:40 2012 +0100

    [2378] Detect class mismatch with copy mode

commit 0c9d9a17f920a7d49ba7fefec3bd33e40e33fb87
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 14:36:47 2012 +0100

    [2378] Tests for the loading from file
    
    They are similar to the ones for copy mode. Should they be unified in
    some way?

commit dbbce202bf0636ca9884b2369f0c75929b8a7342
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 14:14:10 2012 +0100

    [2378] Consider masterfile errors in ZoneLoader docs

commit 3992021b27bd11edb83a8727fef9329e942cd37a
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 14:03:44 2012 +0100

    [2378] Test throwing when missing zone

commit ddb5bb5d8c0df437720ac09cea2c57fbad13a2ab
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 13:35:29 2012 +0100

    [2378] Test loading signed zone

commit 0bee5dbc95e6e3d6af6aaff07e11c6244b6b47e3
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 13:34:41 2012 +0100

    [2378] Fix InMemory iterator
    
    Not directly related, but it breaks tests of the ZoneLoader. The NSEC3
    namespace and all RRSIGs were missing from the returned data.

commit aa101d2d0690864729fdd9ce4507e0a8af17f756
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 11:34:56 2012 +0100

    [2378] Basic test for incremental loading

commit 821a81bd0c21eb33ed2095468915c18e88bc5f5a
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 11:28:23 2012 +0100

    [2378] Check attempt to load past the end

commit 5a737ce6d4f4d5f464d2004216fcf9c8bbfda8af
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 11:22:21 2012 +0100

    [2378] The copy mode of ZoneLoader
    
    At least its basic version. Error handling must still be tested and done.

commit 3c1bfee29255f423f4c17894e985d5e1f5d53071
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 10:39:09 2012 +0100

    [2378] Run doxygen -u
    
    To update the Doxyfile. It no longer complains about obsolete options.

commit d194e75bfce2855cdd24ab25057f06ae388153dc
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Nov 27 10:33:37 2012 +0100

    [2378] Some Doxyfile tweaks

commit 3da9d6198bdedd9317b290f70c9bbe613894037b
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Nov 26 18:54:44 2012 +0100

    [2378] Very basic test for copy between data sources
    
    This is test for the ZoneLoader and its variant for testing copying
    between two data sources. It includes some testing setups and mocks to
    be reused in following tests.
    
    Doesn't compile, undefined references to the tested methods.

commit 549e44164f1e4203bf1acb8d6c137f1de5a48f5a
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Nov 26 16:56:42 2012 +0100

    [2378] Interface of the ZoneLoader
    
    Including documentation.

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                          |   30 +
 configure.ac                                       |    1 -
 doc/guide/bind10-guide.xml                         |   19 +-
 src/bin/cmdctl/.gitignore                          |    2 +
 src/bin/cmdctl/Makefile.am                         |   24 +-
 src/bin/cmdctl/b10-certgen.cc                      |  429 +++++++++
 src/bin/cmdctl/b10-certgen.xml                     |  214 +++++
 src/bin/cmdctl/cmdctl-keyfile.pem                  |   15 -
 src/bin/cmdctl/tests/Makefile.am                   |    9 +-
 src/bin/cmdctl/tests/b10-certgen_test.py           |  254 ++++++
 .../testdata/expired-certfile.pem}                 |    0
 .../testdata/mangled-certfile.pem}                 |    8 +-
 src/bin/cmdctl/tests/testdata/noca-certfile.pem    |   19 +
 src/bin/msgq/msgq.py.in                            |   80 +-
 src/bin/msgq/tests/msgq_test.py                    |  217 ++++-
 src/lib/datasrc/master_loader_callbacks.cc         |   20 +-
 src/lib/datasrc/master_loader_callbacks.h          |    2 +-
 src/lib/datasrc/memory/Makefile.am                 |    1 +
 src/lib/datasrc/memory/rdataset.cc                 |    4 +-
 src/lib/datasrc/memory/rdataset.h                  |   31 +-
 src/lib/datasrc/memory/util_internal.h             |   57 ++
 src/lib/datasrc/memory/zone_data.cc                |    2 +-
 src/lib/datasrc/memory/zone_data_loader.cc         |   31 +-
 src/lib/datasrc/memory/zone_data_updater.cc        |   94 +-
 src/lib/datasrc/memory/zone_data_updater.h         |   37 +-
 src/lib/datasrc/memory/zone_finder.cc              |   13 +-
 src/lib/datasrc/tests/Makefile.am                  |    1 +
 .../datasrc/tests/master_loader_callbacks_test.cc  |   23 +-
 src/lib/datasrc/tests/memory/Makefile.am           |    2 +
 .../datasrc/tests/memory/memory_client_unittest.cc |   10 -
 src/lib/datasrc/tests/memory/rdataset_unittest.cc  |   75 +-
 .../tests/memory/zone_data_loader_unittest.cc      |   65 ++
 .../tests/memory/zone_data_updater_unittest.cc     |  208 +++++
 .../datasrc/tests/memory/zone_finder_unittest.cc   |   81 ++
 src/lib/datasrc/tests/memory_datasrc_unittest.cc   |    2 +-
 ...ttest.zone => contexttest-almost-obsolete.zone} |    2 +-
 src/lib/datasrc/tests/testdata/contexttest.zone    |    7 +
 .../datasrc/tests/zone_finder_context_unittest.cc  |   12 +
 src/lib/datasrc/tests/zone_loader_unittest.cc      |    8 +-
 src/lib/datasrc/zone_loader.cc                     |   23 +-
 src/lib/datasrc/zone_loader.h                      |    2 +-
 src/lib/dhcp/Makefile.am                           |    3 +-
 src/lib/dhcp/option.cc                             |   32 +-
 src/lib/dhcp/option.h                              |   29 +
 src/lib/dhcp/option6_ia.cc                         |    4 +-
 src/lib/dhcp/option6_iaaddr.cc                     |    5 +-
 src/lib/dhcp/option6_int.h                         |    8 +-
 src/lib/dhcp/option6_int_array.h                   |    4 +-
 src/lib/dhcp/option_custom.cc                      |  370 ++++++++
 src/lib/dhcp/option_custom.h                       |  235 +++++
 src/lib/dhcp/option_data_types.cc                  |  227 +++++
 src/lib/dhcp/option_data_types.h                   |  215 ++++-
 src/lib/dhcp/option_definition.cc                  |  379 ++++----
 src/lib/dhcp/option_definition.h                   |  111 +--
 src/lib/dhcp/tests/Makefile.am                     |    1 +
 src/lib/dhcp/tests/option_custom_unittest.cc       |  906 ++++++++++++++++++++
 src/lib/dhcp/tests/option_definition_unittest.cc   |   55 --
 src/lib/dns/Makefile.am                            |    1 +
 src/lib/dns/gen-rdatacode.py.in                    |   41 +-
 src/lib/dns/master_lexer.cc                        |  131 ++-
 src/lib/dns/master_lexer.h                         |  542 +++++++-----
 src/lib/dns/master_lexer_state.h                   |   10 +-
 src/lib/dns/master_loader.cc                       |  150 +++-
 src/lib/dns/master_loader.h                        |   72 +-
 src/lib/dns/master_loader_callbacks.h              |   18 +-
 src/lib/dns/rdata.cc                               |  118 ++-
 src/lib/dns/rdata.h                                |   67 +-
 src/lib/dns/rdata/in_1/aaaa_28.cc                  |   27 +-
 src/lib/dns/rdata/template.cc                      |    5 +
 src/lib/dns/rrparamregistry-placeholder.cc         |   71 +-
 src/lib/dns/rrparamregistry.h                      |   44 +-
 src/lib/dns/tests/master_lexer_state_unittest.cc   |   87 +-
 src/lib/dns/tests/master_lexer_token_unittest.cc   |   91 +-
 src/lib/dns/tests/master_lexer_unittest.cc         |  194 ++++-
 src/lib/dns/tests/master_loader_unittest.cc        |  217 ++++-
 src/lib/dns/tests/rdata_afsdb_unittest.cc          |   10 +
 src/lib/dns/tests/rdata_cname_unittest.cc          |    6 +
 src/lib/dns/tests/rdata_dhcid_unittest.cc          |   10 +
 src/lib/dns/tests/rdata_dname_unittest.cc          |    6 +
 src/lib/dns/tests/rdata_dnskey_unittest.cc         |   11 +
 src/lib/dns/tests/rdata_ds_like_unittest.cc        |   10 +
 src/lib/dns/tests/rdata_hinfo_unittest.cc          |   11 +
 src/lib/dns/tests/rdata_in_a_unittest.cc           |    5 +
 src/lib/dns/tests/rdata_in_aaaa_unittest.cc        |    6 +
 src/lib/dns/tests/rdata_minfo_unittest.cc          |    6 +
 src/lib/dns/tests/rdata_mx_unittest.cc             |   10 +
 src/lib/dns/tests/rdata_naptr_unittest.cc          |   13 +
 src/lib/dns/tests/rdata_ns_unittest.cc             |   10 +
 src/lib/dns/tests/rdata_nsec3_unittest.cc          |   12 +
 .../dns/tests/rdata_nsec3param_like_unittest.cc    |   11 +
 src/lib/dns/tests/rdata_nsec3param_unittest.cc     |    7 +
 src/lib/dns/tests/rdata_nsec_unittest.cc           |   11 +
 src/lib/dns/tests/rdata_opt_unittest.cc            |    7 +
 src/lib/dns/tests/rdata_ptr_unittest.cc            |    6 +
 src/lib/dns/tests/rdata_rp_unittest.cc             |   11 +
 src/lib/dns/tests/rdata_rrsig_unittest.cc          |   61 +-
 src/lib/dns/tests/rdata_soa_unittest.cc            |    7 +
 src/lib/dns/tests/rdata_srv_unittest.cc            |   11 +
 src/lib/dns/tests/rdata_sshfp_unittest.cc          |    6 +
 src/lib/dns/tests/rdata_tsig_unittest.cc           |   10 +
 src/lib/dns/tests/rdata_txt_like_unittest.cc       |   12 +
 src/lib/dns/tests/rdata_unittest.cc                |  143 +++
 src/lib/dns/tests/rdata_unittest.h                 |   13 +-
 src/lib/dns/tests/rrparamregistry_unittest.cc      |   40 +
 src/lib/dns/tests/testdata/Makefile.am             |    1 +
 src/lib/dns/tests/testdata/example.org             |   14 +
 106 files changed, 6075 insertions(+), 1026 deletions(-)
 create mode 100644 src/bin/cmdctl/b10-certgen.cc
 create mode 100644 src/bin/cmdctl/b10-certgen.xml
 delete mode 100644 src/bin/cmdctl/cmdctl-keyfile.pem
 create mode 100644 src/bin/cmdctl/tests/b10-certgen_test.py
 copy src/bin/cmdctl/{cmdctl-certfile.pem => tests/testdata/expired-certfile.pem} (100%)
 rename src/bin/cmdctl/{cmdctl-certfile.pem => tests/testdata/mangled-certfile.pem} (80%)
 create mode 100644 src/bin/cmdctl/tests/testdata/noca-certfile.pem
 create mode 100644 src/lib/datasrc/memory/util_internal.h
 create mode 100644 src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
 create mode 100644 src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
 copy src/lib/datasrc/tests/testdata/{contexttest.zone => contexttest-almost-obsolete.zone} (99%)
 create mode 100644 src/lib/dhcp/option_custom.cc
 create mode 100644 src/lib/dhcp/option_custom.h
 create mode 100644 src/lib/dhcp/option_data_types.cc
 create mode 100644 src/lib/dhcp/tests/option_custom_unittest.cc
 create mode 100644 src/lib/dns/tests/testdata/example.org

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index b412ce4..4b3a4c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+515.	[bug]		jinmei
+	The in-memory data source now accepts an RRSIG provided without
+	a covered RRset in loading.  A subsequent query for its owner name
+	of the covered type would generally result in NXRRSET; if the
+	covered RRset is of type NSEC3, the corresponding NSEC3 processing
+	would result in SERVFAIL.
+	(Trac #2420, git 6744c100953f6def5500bcb4bfc330b9ffba0f5f)
+
+514.	[bug]		jelte
+	b10-msgq now handles socket errors more gracefully when sending data
+	to clients. It no longer exits with 'broken pipe' errors, and is
+	also better at resending data on temporary error codes from send().
+	(Trac #2398, git 9f6b45ee210a253dca608848a58c824ff5e0d234)
+
+513.	[func]		marcin
+	Implemented the OptionCustom class for DHCPv4 and DHCPv6.
+	This class represents an option which has a defined
+	structure: a set of data fields of specific types and order.
+	It is used to represent those options that can't be
+	represented by any other specialized class.
+	(Trac #2312, git 28d885b457dda970d9aecc5de018ec1120143a10)
+
+512.	[func]		jelte
+	Added a new tool b10-certgen, to check and update the self-signed
+	SSL certificate used by b10-cmdctl. The original certificate
+	provided has been removed, and a fresh one is generated upon first
+	build. See the b10-certgen manpage for information on how to update
+	existing installed certificates.
+	(Trac #1044, git 510773dd9057ccf6caa8241e74a7a0b34ca971ab)
+
 511.	[bug]		stephen
 	Fixed a race condition in the DHCP tests whereby the test program
 	spawned a subprocess and attempted to read (without waiting) from
diff --git a/configure.ac b/configure.ac
index b0ca18a..636f8aa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -705,7 +705,6 @@ fi
 AC_SUBST(BOTAN_LDFLAGS)
 AC_SUBST(BOTAN_LIBS)
 AC_SUBST(BOTAN_INCLUDES)
-
 # Even though chances are high we already performed a real compilation check
 # in the search for the right (pkg)config data, we try again here, to
 # be sure.
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index eec5bde..9d8f932 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -784,7 +784,24 @@ as a dependency earlier -->
         <note>
           <para>The install step may require superuser privileges.</para>
         </note>
-
+        <para>
+	  If required, run <command>ldconfig</command> as root with
+	  <filename>/usr/local/lib</filename> (or with ${prefix}/lib if
+	  configured with --prefix) in
+	  <filename>/etc/ld.so.conf</filename> (or the relevant linker
+	  cache configuration file for your OS):
+	  <screen>$ <userinput>ldconfig</userinput></screen>
+        </para>
+        <note>
+          <para>
+	    If you do not run <command>ldconfig</command> where it is
+	    required, you may see errors like the following:
+            <screen>
+	      program: error while loading shared libraries: libb10-something.so.1:
+	      cannot open shared object file: No such file or directory
+	    </screen>
+	  </para>
+        </note>
       </section>
 
   <!-- TODO: tests -->
diff --git a/src/bin/cmdctl/.gitignore b/src/bin/cmdctl/.gitignore
index 01e3ef0..dd6bbc2 100644
--- a/src/bin/cmdctl/.gitignore
+++ b/src/bin/cmdctl/.gitignore
@@ -4,3 +4,5 @@
 /cmdctl.spec.pre
 /run_b10-cmdctl.sh
 /b10-cmdctl.8
+/cmdctl-keyfile.pem
+/cmdctl-certfile.pem
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 3b88f4b..bfc13af 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -4,6 +4,8 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 pkglibexec_SCRIPTS = b10-cmdctl
 
+bin_PROGRAMS = b10-certgen
+
 nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 pylogmessagedir = $(pyexecdir)/isc/log_messages/
 
@@ -25,15 +27,18 @@ CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
 
-man_MANS = b10-cmdctl.8
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST += $(man_MANS) b10-cmdctl.xml cmdctl_messages.mes
+man_MANS = b10-cmdctl.8 b10-certgen.1
+DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
+EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
 
 if GENERATE_DOCS
 
 b10-cmdctl.8: b10-cmdctl.xml
 	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-cmdctl.xml
 
+b10-certgen.1: b10-certgen.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-certgen.xml
+
 else
 
 $(man_MANS):
@@ -54,12 +59,23 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 	$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
 	chmod a+x $@
 
+b10_certgen_SOURCES = b10-certgen.cc
+b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
+b10_certgen_LDFLAGS = $(BOTAN_LIBS)
+
+# Generate the initial certificates immediately
+cmdctl-certfile.pem: b10-certgen
+	./b10-certgen -q -w
+
+cmdctl-keyfile.pem: b10-certgen
+	./b10-certgen -q -w
+
 if INSTALL_CONFIGURATIONS
 
 # Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
 # because these file will contain sensitive information.
 install-data-local:
-	$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@   
+	$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
 	for f in $(CMDCTL_CONFIGURATIONS) ; do	\
 	  if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then	\
 	    ${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ;	\
diff --git a/src/bin/cmdctl/b10-certgen.cc b/src/bin/cmdctl/b10-certgen.cc
new file mode 100644
index 0000000..579ae60
--- /dev/null
+++ b/src/bin/cmdctl/b10-certgen.cc
@@ -0,0 +1,429 @@
+// Copyright (C) 2012  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 <botan/botan.h>
+#include <botan/x509self.h>
+#include <botan/x509stor.h>
+#include <botan/rsa.h>
+#include <botan/dsa.h>
+#include <botan/data_src.h>
+using namespace Botan;
+
+#include <iostream>
+#include <fstream>
+#include <memory>
+#include <getopt.h>
+
+// For cleaner 'does not exist or is not readable' output than
+// botan provides
+#include <unistd.h>
+#include <errno.h>
+
+// This is a simple tool that creates a self-signed PEM certificate
+// for use with BIND 10. It creates a simple certificate for initial
+// setup. Currently, all values are hardcoded defaults. For future
+// versions, we may want to add more options for administrators.
+
+// It will create a PEM file containing a certificate with the following
+// values:
+// common name: localhost
+// organization: BIND10
+// country code: US
+
+// Additional error return codes; these are specifically
+// chosen to be distinct from validation error codes as
+// provided by Botan. Their main use is to distinguish
+// error cases in the unit tests.
+const int DECODING_ERROR = 100;
+const int BAD_OPTIONS = 101;
+const int READ_ERROR = 102;
+const int WRITE_ERROR = 103;
+const int UNKNOWN_ERROR = 104;
+const int NO_SUCH_FILE = 105;
+const int FILE_PERMISSION_ERROR = 106;
+
+void
+usage() {
+    std::cout << "Usage: b10-certgen [OPTION]..." << std::endl;
+    std::cout << "Validate, create, or update a self-signed certificate for "
+                 "use with b10-cmdctl" << std::endl;
+    std::cout << "" << std::endl;
+    std::cout << "Options:" << std::endl;
+    std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate"
+              << std::endl;
+    std::cout << "-f, --force\t\t\toverwrite existing certficate even if it"
+              << std::endl <<"\t\t\t\tis valid" << std::endl;
+    std::cout << "-h, --help\t\t\tshow this help" << std::endl;
+    std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key"
+              << std::endl;
+    std::cout << "-w, --write\t\t\tcreate a new certificate if the given file"
+              << std::endl << "\t\t\t\tdoes not exist, or if is is not valid"
+              << std::endl;
+    std::cout << "-q, --quiet\t\t\tprint no output when creating or validating"
+              << std::endl;
+}
+
+/// \brief Returns true if the given file exists
+///
+/// \param filename The file to check
+/// \return true if file exists
+bool
+fileExists(const std::string& filename) {
+    return (access(filename.c_str(), F_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is readable
+///
+/// \param filename The file to check
+/// \return true if file exists and is readable
+bool
+fileIsReadable(const std::string& filename) {
+    return (access(filename.c_str(), R_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is writable
+///
+/// \param filename The file to check
+/// \return true if file exists and is writable
+bool
+fileIsWritable(const std::string& filename) {
+    return (access(filename.c_str(), W_OK) == 0);
+}
+
+/// \brief Helper function for readable error output;
+///
+/// Returns string representation of X509 result code
+/// This does not appear to be provided by Botan itself
+///
+/// \param code An \c X509_Code instance
+/// \return A human-readable c string
+const char*
+X509CodeToString(const X509_Code& code) {
+    // note that this list provides more than we would
+    // need in this context, it is just the enum from
+    // the source code of Botan.
+    switch (code) {
+    case VERIFIED:
+        return ("verified");
+    case UNKNOWN_X509_ERROR:
+        return ("unknown x509 error");
+    case CANNOT_ESTABLISH_TRUST:
+        return ("cannot establish trust");
+    case CERT_CHAIN_TOO_LONG:
+        return ("certificate chain too long");
+    case SIGNATURE_ERROR:
+        return ("signature error");
+    case POLICY_ERROR:
+        return ("policy error");
+    case INVALID_USAGE:
+        return ("invalid usage");
+    case CERT_FORMAT_ERROR:
+        return ("certificate format error");
+    case CERT_ISSUER_NOT_FOUND:
+        return ("certificate issuer not found");
+    case CERT_NOT_YET_VALID:
+        return ("certificate not yet valid");
+    case CERT_HAS_EXPIRED:
+        return ("certificate has expired");
+    case CERT_IS_REVOKED:
+        return ("certificate has been revoked");
+    case CRL_FORMAT_ERROR:
+        return ("crl format error");
+    case CRL_NOT_YET_VALID:
+        return ("crl not yet valid");
+    case CRL_HAS_EXPIRED:
+        return ("crl has expired");
+    case CA_CERT_CANNOT_SIGN:
+        return ("CA cert cannot sign");
+    case CA_CERT_NOT_FOR_CERT_ISSUER:
+        return ("CA certificate not for certificate issuer");
+    case CA_CERT_NOT_FOR_CRL_ISSUER:
+        return ("CA certificate not for crl issuer");
+    default:
+        return ("Unknown X509 code");
+    }
+}
+
+class CertificateTool {
+public:
+    CertificateTool(bool quiet) : quiet_(quiet) {}
+
+    int
+    createKeyAndCertificate(const std::string& key_file_name,
+                            const std::string& cert_file_name) {
+        try {
+            AutoSeeded_RNG rng;
+
+            // Create and store a private key
+            print("Creating key file " + key_file_name);
+            RSA_PrivateKey key(rng, 2048);
+            std::ofstream key_file(key_file_name.c_str());
+            if (!key_file.good()) {
+                print(std::string("Error writing to ") + key_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            key_file << PKCS8::PEM_encode(key, rng, "");
+            if (!key_file.good()) {
+                print(std::string("Error writing to ") + key_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            key_file.close();
+
+            // Certificate options, currently hardcoded.
+            // For a future version we may want to make these
+            // settable.
+            X509_Cert_Options opts;
+            opts.common_name = "localhost";
+            opts.organization = "UNKNOWN";
+            opts.country = "XX";
+
+            opts.CA_key();
+
+            print("Creating certificate file " + cert_file_name);
+
+            // The exact call changed aftert 1.8, adding the
+            // hash function option
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+            X509_Certificate cert =
+            X509::create_self_signed_cert(opts, key, "SHA-256", rng);
+#else
+            X509_Certificate cert =
+            X509::create_self_signed_cert(opts, key, rng);
+#endif
+
+            std::ofstream cert_file(cert_file_name.c_str());
+            if (!cert_file.good()) {
+                print(std::string("Error writing to ") + cert_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            cert_file << cert.PEM_encode();
+            if (!cert_file.good()) {
+                print(std::string("Error writing to ") + cert_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            cert_file.close();
+        } catch(std::exception& e) {
+            std::cout << "Error creating key or certificate: " << e.what()
+                      << std::endl;
+            return (UNKNOWN_ERROR);
+        }
+        return (0);
+    }
+
+    int
+    validateCertificate(const std::string& certfile) {
+        // Since we are dealing with a self-signed certificate here, we
+        // also use the certificate to check itself; i.e. we add it
+        // as a trusted certificate, then validate the certficate itself.
+        //const X509_Certificate cert(certfile);
+        try {
+            X509_Store store;
+            DataSource_Stream in(certfile);
+            store.add_trusted_certs(in);
+
+            const X509_Code result = store.validate_cert(certfile);
+
+            if (result == VERIFIED) {
+                print(certfile + " is valid");
+            } else {
+                print(certfile + " failed to verify: " +
+                      X509CodeToString(result));
+            }
+            return (result);
+        } catch (const Botan::Decoding_Error& bde) {
+            print(certfile + " failed to verify: " + bde.what());
+            return (DECODING_ERROR);
+        } catch (const Botan::Stream_IO_Error& bsie) {
+            print(certfile + " not read: " + bsie.what());
+            return (READ_ERROR);
+        }
+    }
+
+    /// \brief Runs the tool
+    ///
+    /// \param create_cert  Create certificate if true, validate if false.
+    ///                     Does nothing if certificate exists and is valid.
+    /// \param force_create Create new certificate even if it is valid.
+    /// \param certfile     Certificate file to read to or write from.
+    /// \param keyfile      Key file to write if certificate is created.
+    ///                     Ignored if create_cert is false
+    /// \return zero on success, non-zero on failure
+    int
+    run(bool create_cert, bool force_create, const std::string& certfile,
+        const std::string& keyfile)
+    {
+        if (create_cert) {
+            // Unless force is given, only create it if the current
+            // one is not OK
+
+            // First do some basic permission checks; both files
+            // should either not exist, or be both readable
+            // and writable
+            // The checks are done one by one so all errors can
+            // be enumerated in one go
+            if (fileExists(certfile)) {
+                if (!fileIsReadable(certfile)) {
+                    print(certfile + " not readable: " + std::strerror(errno));
+                    create_cert = false;
+                }
+                if (!fileIsWritable(certfile)) {
+                    print(certfile + " not writable: " + std::strerror(errno));
+                    create_cert = false;
+                }
+            }
+            // The key file really only needs write permissions (for
+            // b10-certgen that is)
+            if (fileExists(keyfile)) {
+                if (!fileIsWritable(keyfile)) {
+                    print(keyfile + " not writable: " + std::strerror(errno));
+                    create_cert = false;
+                }
+            }
+            if (!create_cert) {
+                print("Not creating new certificate, "
+                      "check file permissions");
+                return (FILE_PERMISSION_ERROR);
+            }
+
+            // If we reach this, we know that if they exist, we can both
+            // read and write them, so now it's up to content checking
+            // and/or force_create
+
+            if (force_create || !fileExists(certfile) ||
+                validateCertificate(certfile) != VERIFIED) {
+                return (createKeyAndCertificate(keyfile, certfile));
+            } else {
+                print("Not creating a new certificate (use -f to force)");
+            }
+        } else {
+            if (!fileExists(certfile)) {
+                print(certfile + ": " + std::strerror(errno));
+                return (NO_SUCH_FILE);
+            }
+            if (!fileIsReadable(certfile)) {
+                print(certfile + " not readable: " + std::strerror(errno));
+                return (FILE_PERMISSION_ERROR);
+            }
+            int result = validateCertificate(certfile);
+            if (result != 0) {
+                print("Running with -w would overwrite the certificate");
+            }
+            return (result);
+        }
+        return (0);
+    }
+private:
+    /// Prints the message to stdout unless quiet_ is true
+    void print(const std::string& msg) {
+        if (!quiet_) {
+            std::cout << msg << std::endl;
+        }
+    }
+
+    bool quiet_;
+};
+
+int
+main(int argc, char* argv[])
+{
+    Botan::LibraryInitializer init;
+
+    // create or check certificate
+    bool create_cert = false;
+    // force creation even if not necessary
+    bool force_create = false;
+    // don't print any output
+    bool quiet = false;
+
+    // default certificate file
+    std::string certfile("cmdctl-certfile.pem");
+    // default key file
+    std::string keyfile("cmdctl-keyfile.pem");
+
+    // whether or not the above values have been
+    // overridden (used in command line checking)
+    bool certfile_default = true;
+    bool keyfile_default = true;
+
+    // It would appear some environments insist on
+    // char* here (Sunstudio on Solaris), so we const_cast
+    // them to get rid of compiler warnings.
+    const struct option long_options[] = {
+        { const_cast<char*>("certfile"), required_argument, NULL, 'c' },
+        { const_cast<char*>("force"), no_argument, NULL, 'f' },
+        { const_cast<char*>("help"), no_argument, NULL, 'h' },
+        { const_cast<char*>("keyfile"), required_argument, NULL, 'k' },
+        { const_cast<char*>("write"), no_argument, NULL, 'w' },
+        { const_cast<char*>("quiet"), no_argument, NULL, 'q' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt, option_index;
+    while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options,
+                              &option_index)) != -1) {
+        switch (opt) {
+            case 'c':
+                certfile = optarg;
+                certfile_default = false;
+                break;
+            case 'f':
+                force_create = true;
+                break;
+            case 'h':
+                usage();
+                return (0);
+                break;
+            case 'k':
+                keyfile = optarg;
+                keyfile_default = false;
+                break;
+            case 'w':
+                create_cert = true;
+                break;
+            case 'q':
+                quiet = true;
+                break;
+            default:
+                // A message will have already been output about the error.
+                return (BAD_OPTIONS);
+        }
+    }
+
+    if (optind < argc) {
+        std::cout << "Error: extraneous arguments" << std::endl << std::endl;
+        usage();
+        return (BAD_OPTIONS);
+    }
+
+    // Some sanity checks on option combinations
+    if (create_cert && (certfile_default ^ keyfile_default)) {
+        std::cout << "Error: keyfile and certfile must both be specified "
+                     "if one of them is when calling b10-certgen in write "
+                     "mode." << std::endl;
+        return (BAD_OPTIONS);
+    }
+    if (!create_cert && !keyfile_default) {
+        std::cout << "Error: keyfile is not used when not in write mode"
+                  << std::endl;
+        return (BAD_OPTIONS);
+    }
+
+    // Initialize the tool and perform the appropriate action(s)
+    CertificateTool tool(quiet);
+    return (tool.run(create_cert, force_create, certfile, keyfile));
+}
diff --git a/src/bin/cmdctl/b10-certgen.xml b/src/bin/cmdctl/b10-certgen.xml
new file mode 100644
index 0000000..1e3c8e3
--- /dev/null
+++ b/src/bin/cmdctl/b10-certgen.xml
@@ -0,0 +1,214 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2012  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.
+-->
+
+<refentry>
+
+  <refentryinfo>
+    <date>November 15, 2012</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-certgen</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-certgen</refname>
+    <refpurpose>X509 Certificate generation tool for use with b10-cmdctl</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2012</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-certgen</command>
+        <group choice="opt">
+          <arg choice="[OPTION]..."><option>-</option></arg>
+        </group>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>The <command>b10-certgen</command> tool validates, creates, or
+      updates a self-signed X509 certificate for use in b10-cmdctl.
+    </para>
+
+    <para>
+      The connection between <command>bindctl</command> and
+      <command>b10-cmdctl</command> is done over HTTPS, and therefore
+      <command>b10-cmdctl</command> needs a certificate. Since these
+      certificates have expiry dates, they also need to be regenerated at
+      some point.
+
+      There are many tools to do so, but for ease of use, <command>
+      b10-certgen</command> can create a simple self-signed certificate.
+
+      By default, it will not create anything, but it will merely check an
+      existing certificate (if not specified, cmdctl-certfile.pem, in the
+      current working directory). And print whether it is valid, and
+      whether it would update if the option '-w' is given.
+
+      With that option, the certificate could then be replaced by a newly
+      created one. If the certificate is still valid, it would still not
+      be overwritten (however, if it is found to be invalid, for example
+      because it has expired, it would create a new one).
+
+      A new certificate is always created if the certificate file does
+      not exist, or if creation is forced (with the -f option).
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>ARGUMENTS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>
+          <option>-c <replaceable>file</replaceable></option>,
+          <option>--certfile=<replaceable>file</replaceable></option>
+        </term>
+        <listitem>
+          <para>
+            File to read the certificate from, or write the certificate to.
+            If <option>-w</option> and <option>-c</option> are used,
+            <option>-k</option> is mandatory as well.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-f</option>,
+          <option>--force</option>
+        </term>
+        <listitem>
+          <para>
+            Force updating of certificate when <option>-w</option> is used,
+            even if the existing certificate is still valid.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-h</option>,
+          <option>--help</option>
+        </term>
+        <listitem>
+          <para>
+            Print the command line arguments and exit.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-k <replaceable>file</replaceable></option>,
+          <option>--keyfile=<replaceable>file</replaceable></option>
+        </term>
+        <listitem>
+          <para>
+            File to write the private key to. This option is only valid when <option>-w</option> is used, and if this option is used, <option>-c</option> is mandatory as well.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-w</option>,
+          <option>--write</option>
+        </term>
+        <listitem>
+          <para>
+            Check the given certificate file. If it does not exist, a new
+            private key and certificate are created. If it does exist, the
+            certificate is validated. If it is not valid (for instance
+            because it has expired), it is overwritten with a newly created
+            certificate. If it is valid, nothing happens (use
+            <option>-f</option> to force an update in that case).
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-q</option>,
+          <option>--quiet</option>
+        </term>
+        <listitem>
+          <para>
+            Don't print informational messages (only command-line errors are
+            printed). Useful in scripts when only the return code is needed.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-certgen</command> tool was first implemented
+      in November 2012 for the ISC BIND 10 project.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>EXAMPLE</title>
+    <para>
+      To update an expired certificate in BIND 10 that has been installed to
+      /usr/local:
+      <screen>
+$> cd /usr/local/etc/bind10-devel/
+
+$> b10-certgen
+cmdctl-certfile.pem failed to verify: certificate has expired
+Running with -w would overwrite the certificate
+
+$> b10-certgen --write
+cmdctl-certfile.pem failed to verify: certificate has expired
+Creating key file cmdctl-keyfile.pem
+Creating certificate file cmdctl-certfile.pem
+
+$> b10-certgen --write
+cmdctl-certfile.pem is valid
+Not creating a new certificate (use -f to force)
+      </screen>
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/cmdctl/cmdctl-certfile.pem b/src/bin/cmdctl/cmdctl-certfile.pem
deleted file mode 100644
index 384a222..0000000
--- a/src/bin/cmdctl/cmdctl-certfile.pem
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
-VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
-A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
-MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
-NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
-ZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
-CxMFY25uaWMxEzARBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
-YW5nbGlrdW5AY25uaWMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
-JbEkYoy9SEsU9t/mfxlaiCqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
-UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
-O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
-BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
-tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
-amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
-BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
-Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
-9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
-jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
-EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
------END CERTIFICATE-----
diff --git a/src/bin/cmdctl/cmdctl-keyfile.pem b/src/bin/cmdctl/cmdctl-keyfile.pem
deleted file mode 100644
index 8fff2dc..0000000
--- a/src/bin/cmdctl/cmdctl-keyfile.pem
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDpICWxJGKMvUhLFPbf5n8ZWogqjYcQqqoHqHVRHYjyiey6FZdt
-ZkY2s1gYh0G0NXtimlIgic+vEcFe7vdmyKntW7DYDaqAj0KrED7RKAj8324jNbSJ
-HtLP4evvJep3vsoNtTvNuceQJ46vukxyxgg3DuC9kVqPuD8CZ1Rq4ATyiwIDAQAB
-AoGBAOJlOtV+DUq6Y2Ou91VXRiU8GzKgAQP5iWgoe84Ljbxkn4XThBxVD2j94Fbp
-u7AjpDCMx6cbzpoo9w6XqaGizAmAehIfTE3eFYs74N/FM09Wg2OSDyxMY0jgyECU
-A4ukjlPwcGDbmgbmlY3i+FVHp+zCgtZEsMC1IAosMac1BoX5AkEA/lrXWaVtH8bo
-mut3GBaXvubZMdaUr0BUd5a9q+tt4dQcKG1kFqgCNKhNhBIcpiMVcz+jGmOuopNA
-8dnUGqv3FQJBAOqiJ54ZvOTWNDpJIe02wIXRxRmc1xhHFCqYP23KxBVrAcTYB19J
-lesov/hEbnGLCbKS/naZJ1zrTImUPNRLqx8CQCzDtA7U7GWhTiKluioFH+O7IRKC
-X1yQh80cPHlbT9VkzSfYSLssCmdWD35k6aHbntTPqFbmoD+AhveJjKi9BxkCQDwX
-1c+/RcrSNcQr0N2hZUOgyztZGRnlsnuKTMyA3yGhK23P6mt0PEpjQG+Ej0jTVGOB
-FF0pspQwy4R9C+tPif8CQH36NNlXBfVNmT7kDtyLmaE6pID0vY9duX56BJbU1R0x
-SQ8/LcfJagk6gvp08OyYCPA+WZ7u/bas9R/nMTCLivc=
------END RSA PRIVATE KEY-----
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index b5b65f6..6d8f282 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -1,6 +1,9 @@
 PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = cmdctl_test.py
+PYTESTS = cmdctl_test.py b10-certgen_test.py
 EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/expired-certfile.pem
+EXTRA_DIST += testdata/mangled-certfile.pem
+EXTRA_DIST += testdata/noca-certfile.pem
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
@@ -9,10 +12,12 @@ if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
+CLEANFILES = test-keyfile.pem test-certfile.pem
+
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 if ENABLE_PYTHON_COVERAGE
-	touch $(abs_top_srcdir)/.coverage 
+	touch $(abs_top_srcdir)/.coverage
 	rm -f .coverage
 	${LN_S} $(abs_top_srcdir)/.coverage .coverage
 endif
diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py
new file mode 100644
index 0000000..d54efa3
--- /dev/null
+++ b/src/bin/cmdctl/tests/b10-certgen_test.py
@@ -0,0 +1,254 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+# Note: the main code is in C++, but what we are mostly testing is
+# options and behaviour (output/file creation, etc), which is easier
+# to test in python.
+
+import unittest
+import os
+from subprocess import call
+import subprocess
+import ssl
+import stat
+
+def run(command):
+    """
+    Small helper function that returns a tuple of (rcode, stdout, stderr) after
+    running the given command (an array of command and arguments, as passed on
+    to subprocess).
+    """
+    subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    (stdout, stderr) = subp.communicate()
+    return (subp.returncode, stdout, stderr)
+
+class FileDeleterContext:
+    """
+    Simple Context Manager that deletes a given set of files when the context
+    is left.
+    """
+    def __init__(self, files):
+        self.files = files
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, type, value, traceback):
+        for f in self.files:
+            if os.path.exists(f):
+                os.unlink(f)
+
+class FilePermissionContext:
+    """
+    Simple Context Manager that temporarily modifies file permissions for
+    a given file
+    """
+    def __init__(self, f, unset_flags = [], set_flags = []):
+        """
+        Initialize file permission context.
+        See the stat module for possible flags to set or unset.
+        The flags are changed when the context is entered (i.e.
+        you can create the context first without any change)
+        The flags are changed back when the context is left.
+
+        Parameters:
+        f: string, file to change permissions for
+        unset_flags: list of flags to unset
+        set_flags: list of flags to set
+        """
+        self.file = f
+        self.orig_mode = os.stat(f).st_mode
+        new_mode = self.orig_mode
+        for flag in unset_flags:
+            new_mode = new_mode & ~flag
+        for flag in set_flags:
+            new_mode = new_mode | flag
+        self.new_mode = new_mode
+
+    def __enter__(self):
+        os.chmod(self.file, self.new_mode)
+
+    def __exit__(self, type, value, traceback):
+        os.chmod(self.file, self.orig_mode)
+
+def read_file_data(filename):
+    """
+    Simple text file reader that returns its contents as an array
+    """
+    with open(filename) as f:
+        return f.readlines()
+
+class TestCertGenTool(unittest.TestCase):
+    TOOL = '../b10-certgen'
+
+    def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
+        """
+        Runs the given command, and checks return code, and outputs (if provided).
+        Arguments:
+        expected_returncode, return code of the command
+        expected_stdout, (multiline) string that is checked agains stdout.
+                         May be None, in which case the check is skipped.
+        expected_stderr, (multiline) string that is checked agains stderr.
+                         May be None, in which case the check is skipped.
+        """
+        (returncode, stdout, stderr) = run(command)
+        self.assertEqual(expected_returncode, returncode, " ".join(command))
+        if expected_stdout is not None:
+            self.assertEqual(expected_stdout, stdout.decode())
+        if expected_stderr is not None:
+            self.assertEqual(expected_stderr, stderr.decode())
+
+    def validate_certificate(self, expected_result, certfile):
+        """
+        Validate a certificate, using the quiet option of the tool; it runs
+        the check option (-c) for the given base name of the certificate (-f
+        <certfile>), and compares the return code to the given
+        expected_result value
+        """
+        self.run_check(expected_result, '', '',
+                       [self.TOOL, '-q', '-c', certfile])
+        # Same with long options
+        self.run_check(expected_result, '', '',
+                       [self.TOOL, '--quiet', '--certfile', certfile])
+
+
+    def test_basic_creation(self):
+        """
+        Tests whether basic creation with no arguments (except output
+        file name) successfully creates a key and certificate
+        """
+        keyfile = 'test-keyfile.pem'
+        certfile = 'test-certfile.pem'
+        command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+        self.creation_helper(command, certfile, keyfile)
+        # Do same with long options
+        command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ]
+        self.creation_helper(command, certfile, keyfile)
+
+    def creation_helper(self, command, certfile, keyfile):
+        """
+        Helper method for test_basic_creation.
+        Performs the actual checks
+        """
+        with FileDeleterContext([keyfile, certfile]):
+            self.assertFalse(os.path.exists(keyfile))
+            self.assertFalse(os.path.exists(certfile))
+            self.run_check(0, '', '', command)
+            self.assertTrue(os.path.exists(keyfile))
+            self.assertTrue(os.path.exists(certfile))
+
+            # Validate the certificate that was just created
+            self.validate_certificate(0, certfile)
+
+            # When run with the same options, it should *not* create it again,
+            # as the current certificate should still be valid
+            certdata = read_file_data(certfile)
+            keydata = read_file_data(keyfile)
+
+            self.run_check(0, '', '', command)
+
+            self.assertEqual(certdata, read_file_data(certfile))
+            self.assertEqual(keydata, read_file_data(keyfile))
+
+            # but if we add -f, it should force a new creation
+            command.append('-f')
+            self.run_check(0, '', '', command)
+            self.assertNotEqual(certdata, read_file_data(certfile))
+            self.assertNotEqual(keydata, read_file_data(keyfile))
+
+    def test_check_bad_certificates(self):
+        """
+        Tests a few pre-created certificates with the -c option
+        """
+        if ('CMDCTL_SRC_PATH' in os.environ):
+            path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
+        else:
+            path = "testdata/"
+        self.validate_certificate(10, path + 'expired-certfile.pem')
+        self.validate_certificate(100, path + 'mangled-certfile.pem')
+        self.validate_certificate(17, path + 'noca-certfile.pem')
+
+    def test_bad_options(self):
+        """
+        Tests some combinations of commands that should fail.
+        """
+        # specify -c but not -k
+        self.run_check(101,
+                       'Error: keyfile and certfile must both be specified '
+                       'if one of them is when calling b10-certgen in write '
+                       'mode.\n',
+                       '', [self.TOOL, '-w', '-c', 'foo'])
+        self.run_check(101,
+                       'Error: keyfile and certfile must both be specified '
+                       'if one of them is when calling b10-certgen in write '
+                       'mode.\n',
+                       '', [self.TOOL, '-w', '-k', 'foo'])
+        self.run_check(101,
+                       'Error: keyfile is not used when not in write mode\n',
+                       '', [self.TOOL, '-k', 'foo'])
+        # Extraneous argument
+        self.run_check(101, None, None, [self.TOOL, 'foo'])
+        # No such file
+        self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
+
+    def test_permissions(self):
+        """
+        Test some combinations of correct and bad permissions.
+        """
+        keyfile = 'mod-keyfile.pem'
+        certfile = 'mod-certfile.pem'
+        command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+        # Delete them at the end
+        with FileDeleterContext([keyfile, certfile]):
+            # Create the two files first
+            self.run_check(0, '', '', command)
+            self.validate_certificate(0, certfile)
+
+            # Make the key file unwritable
+            with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]):
+                self.run_check(106, '', '', command)
+                # Should have no effect on validation
+                self.validate_certificate(0, certfile)
+
+            # Make the cert file unwritable
+            with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]):
+                self.run_check(106, '', '', command)
+                # Should have no effect on validation
+                self.validate_certificate(0, certfile)
+
+            # Make the key file unreadable (this should not matter)
+            with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]):
+                self.run_check(0, '', '', command)
+
+                # unreadable key file should also not have any effect on
+                # validation
+                self.validate_certificate(0, certfile)
+
+            # Make the cert file unreadable (this should matter)
+            with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]):
+                self.run_check(106, '', '', command)
+
+                # Unreadable cert file should also fail validation
+                self.validate_certificate(106, certfile)
+
+        # Not directly a permission problem, but trying to check or create
+        # in a nonexistent directory returns different error codes
+        self.validate_certificate(105, 'fakedir/cert')
+        self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c',
+                                      'fakedir/cert', '-k', 'fakedir/key' ])
+
+if __name__== '__main__':
+    unittest.main()
+
diff --git a/src/bin/cmdctl/tests/testdata/expired-certfile.pem b/src/bin/cmdctl/tests/testdata/expired-certfile.pem
new file mode 100644
index 0000000..384a222
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/expired-certfile.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxEzARBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25uaWMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxlaiCqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----
diff --git a/src/bin/cmdctl/tests/testdata/mangled-certfile.pem b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem
new file mode 100644
index 0000000..7c47fda
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMraWDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxeZaRBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25UAwMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxLAICqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----
diff --git a/src/bin/cmdctl/tests/testdata/noca-certfile.pem b/src/bin/cmdctl/tests/testdata/noca-certfile.pem
new file mode 100644
index 0000000..5636869
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/noca-certfile.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe6gAwIBAgIRALUIj3nnW5uDE/+fglPvUDwwDQYJKoZIhvcNAQELBQAw
+HjELMAkGA1UEBhMCVVMxDzANBgNVBAMTBkJJTkQxMDAeFw0xMjExMTQxMjQ5MjVa
+Fw0xMzExMTQxMjQ5MjVaMB4xCzAJBgNVBAYTAlVTMQ8wDQYDVQQDEwZCSU5EMTAw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkIOPfs3Aw9kNDu1JqA2w3
+84/n9oUgAwAlHVmuJv7ZDw1MDaIKHjsh3DW09z+nv67GVksI7pFtAw5O4mnTDxpa
+JT0NKzhvYGfe8VdV/hWDogTIdk1QBJNZ2/id8z0h8z5001sARXPf+4mHBJslenH3
+YtZs22BG5RBLULtZ/2Nr7JkdfLlc6D5PCoDG22r1OiFkYVdCWfLDjisVIbSYPBtY
+BlKAIrvbmOtWcaGM+vQAhl0T5N8WRCKhaQH0DEmzQNckkYd7rSECo57KYiuvOdzp
+d+3bWTgGGy2ff0o3LZypv0O5s0TDC2H6hYtN4bUbcChUJbFu9b5sVZaOEVZtUsyD
+AgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQW
+BBSqGzsEDNs9E7gBL5pD6XVAwUo4DTANBgkqhkiG9w0BAQsFAAOCAQEAMTNB8NCU
+dnLFZ0jNpvecbECkX/OWGlBYU4/CsoNiibwp4CtUYS2A4NFVjWAyuzLSHhRQi0vJ
+CCWLpKL4VTkaDN5Oft42iUhvEXMnriJqpfXHnjCiBwFFSPl5WKfMIaRNK+tF4zbB
+F+FGNEEmYG3t/ni82orDLq4oy+7CoQwzZNzj5yoV6q7O9kLR9OMPNwJrc27A4erB
+7VMRZslSrNA4uA6YhMZl8iEvO1H801ct0zTxawrCihPOZOCSLew35xjztO7d3YH8
+YavOu5kzeu7AgZ2n75H/qU47ZgBjbonn9Osvrct+RIwZuWTB2bDML8JhNaZCq0aA
+TDBC0QWqIYypLg==
+-----END CERTIFICATE-----
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index bab193e..bd13a1c 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -127,6 +127,7 @@ class MsgQ:
         self.subs = SubscriptionManager()
         self.lnames = {}
         self.sendbuffs = {}
+        self.running = False
 
     def setup_poller(self):
         """Set up the poll thing.  Internal function."""
@@ -315,6 +316,8 @@ class MsgQ:
         elif cmd == 'ping':
             # Command for testing purposes
             self.process_command_ping(sock, routing, data)
+        elif cmd == 'stop':
+            self.stop()
         else:
             sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
 
@@ -336,14 +339,34 @@ class MsgQ:
         self.send_prepared_msg(sock, self.preparemsg(env, msg))
 
     def __send_data(self, sock, data):
+        """
+        Send a piece of data to the given socket.
+        Parameters:
+        sock: The socket to send to
+        data: The list of bytes to send
+        Returns:
+        An integer or None. If an integer (which can be 0), it signals
+        the number of bytes sent. If None, the socket appears to have
+        been closed on the other end, and it has been killed on this
+        side too.
+        """
         try:
             # We set the socket nonblocking, MSG_DONTWAIT doesn't exist
             # on some OSes
             sock.setblocking(0)
             return sock.send(data)
         except socket.error as e:
-            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+            if e.errno in [ errno.EAGAIN,
+                            errno.EWOULDBLOCK,
+                            errno.EINTR ]:
                 return 0
+            elif e.errno in [ errno.EPIPE,
+                              errno.ECONNRESET,
+                              errno.ENOBUFS ]:
+                print("[b10-msgq] " + errno.errorcode[e.errno] +
+                      " on send, dropping message and closing connection")
+                self.kill_socket(sock.fileno(), sock)
+                return None
             else:
                 raise e
         finally:
@@ -356,20 +379,12 @@ class MsgQ:
         if fileno in self.sendbuffs:
             amount_sent = 0
         else:
-            try:
-                amount_sent = self.__send_data(sock, msg)
-            except socket.error as sockerr:
-                # in the case the other side seems gone, kill the socket
-                # and drop the send action
-                if sockerr.errno == errno.EPIPE:
-                    print("[b10-msgq] SIGPIPE on send, dropping message " +
-                          "and closing connection")
-                    self.kill_socket(fileno, sock)
-                    return
-                else:
-                    raise
+            amount_sent = self.__send_data(sock, msg)
+            if amount_sent is None:
+                # Socket has been killed, drop the send
+                return
 
-        # Still something to send
+        # Still something to send, add it to outgoing queue
         if amount_sent < len(msg):
             now = time.clock()
             # Append it to buffer (but check the data go away)
@@ -394,17 +409,18 @@ class MsgQ:
         (_, msg) = self.sendbuffs[fileno]
         sock = self.sockets[fileno]
         amount_sent = self.__send_data(sock, msg)
-        # Keep the rest
-        msg = msg[amount_sent:]
-        if len(msg) == 0:
-            # If there's no more, stop requesting for write availability
-            if self.poller:
-                self.poller.register(fileno, select.POLLIN)
+        if amount_sent is not None:
+            # Keep the rest
+            msg = msg[amount_sent:]
+            if len(msg) == 0:
+                # If there's no more, stop requesting for write availability
+                if self.poller:
+                    self.poller.register(fileno, select.POLLIN)
+                else:
+                    self.delete_kqueue_socket(sock, True)
+                del self.sendbuffs[fileno]
             else:
-                self.delete_kqueue_socket(sock, True)
-            del self.sendbuffs[fileno]
-        else:
-            self.sendbuffs[fileno] = (time.clock(), msg)
+                self.sendbuffs[fileno] = (time.clock(), msg)
 
     def newlname(self):
         """Generate a unique connection identifier for this socket.
@@ -458,6 +474,7 @@ class MsgQ:
 
     def run(self):
         """Process messages.  Forever.  Mostly."""
+        self.running = True
 
         if self.poller:
             self.run_poller()
@@ -465,8 +482,10 @@ class MsgQ:
             self.run_kqueue()
 
     def run_poller(self):
-        while True:
+        while self.running:
             try:
+                # Poll with a timeout so that every once in a while,
+                # the loop checks for self.running.
                 events = self.poller.poll()
             except select.error as err:
                 if err.args[0] == errno.EINTR:
@@ -480,11 +499,15 @@ class MsgQ:
                 else:
                     if event & select.POLLOUT:
                         self.__process_write(fd)
-                    if event & select.POLLIN:
+                    elif event & select.POLLIN:
                         self.process_socket(fd)
+                    else:
+                        print("[b10-msgq] Error: Unknown even in run_poller()")
 
     def run_kqueue(self):
-        while True:
+        while self.running:
+            # Check with a timeout so that every once in a while,
+            # the loop checks for self.running.
             events = self.kqueue.control(None, 10)
             if not events:
                 raise RuntimeError('serve: kqueue returned no events')
@@ -502,6 +525,9 @@ class MsgQ:
                         self.kill_socket(event.ident,
                                          self.sockets[event.ident])
 
+    def stop(self):
+        self.running = False
+
     def shutdown(self):
         """Stop the MsgQ master."""
         if self.verbose:
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 6dc7d1c..4060190 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -6,7 +6,10 @@ import socket
 import signal
 import sys
 import time
+import errno
+import threading
 import isc.cc
+import collections
 
 #
 # Currently only the subscription part and some sending is implemented...
@@ -112,6 +115,85 @@ class TestSubscriptionManager(unittest.TestCase):
         msgq = MsgQ("/does/not/exist")
         self.assertRaises(socket.error, msgq.setup)
 
+class DummySocket:
+    """
+    Dummy socket class.
+    This one does nothing at all, but some calls are used.
+    It is mainly intended to override the listen socket for msgq, which
+    we do not need in these tests.
+    """
+    def fileno():
+        return -1
+
+    def close():
+        pass
+
+class BadSocket:
+    """
+    Special socket wrapper class. Once given a socket in its constructor,
+    it completely behaves like that socket, except that its send() call
+    will only actually send one byte per call, and optionally raise a given
+    exception at a given time.
+    """
+    def __init__(self, real_socket, raise_on_send=0, send_exception=None):
+        """
+        Parameters:
+        real_socket: The actual socket to wrap
+        raise_on_send: integer. If higher than 0, and send_exception is
+                       not None, send_exception will be raised on the
+                       'raise_on_send'th call to send().
+        send_exception: if not None, this exception will be raised
+                        (if raise_on_send is not 0)
+        """
+        self.socket = real_socket
+        self.send_count = 0
+        self.raise_on_send = raise_on_send
+        self.send_exception = send_exception
+
+    # completely wrap all calls and member access
+    # (except explicitely overridden ones)
+    def __getattr__(self, name, *args):
+        attr = getattr(self.socket, name)
+        if isinstance(attr, collections.Callable):
+            def callable_attr(*args):
+                return attr.__call__(*args)
+            return callable_attr
+        else:
+            return attr
+
+    def send(self, data):
+        self.send_count += 1
+        if self.send_exception is not None and\
+           self.send_count == self.raise_on_send:
+            raise self.send_exception
+
+        if len(data) > 0:
+            return self.socket.send(data[:1])
+        else:
+            return 0
+
+class MsgQThread(threading.Thread):
+    """
+    Very simple thread class that runs msgq.run() when started,
+    and stores the exception that msgq.run() raises, if any.
+    """
+    def __init__(self, msgq):
+        threading.Thread.__init__(self)
+        self.msgq_ = msgq
+        self.caught_exception = None
+        self.lock = threading.Lock()
+
+    def run(self):
+        try:
+            self.msgq_.run()
+        except Exception as exc:
+            # Store the exception to make the test fail if necessary
+            self.caught_exception = exc
+
+    def stop(self):
+        self.msgq_.stop()
+
+
 class SendNonblock(unittest.TestCase):
     """
     Tests that the whole thing will not get blocked if someone does not read.
@@ -191,9 +273,6 @@ class SendNonblock(unittest.TestCase):
         msgq = MsgQ()
         # msgq.run needs to compare with the listen_socket, so we provide
         # a replacement
-        class DummySocket:
-            def fileno():
-                return -1
         msgq.listen_socket = DummySocket
         (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
         def run():
@@ -245,5 +324,137 @@ class SendNonblock(unittest.TestCase):
             data = data + data
         self.send_many(data)
 
+    def do_send(self, write, read, control_write, control_read,
+                expect_arrive=True, expect_send_exception=None):
+        """
+        Makes a msgq object that is talking to itself,
+        run it in a separate thread so we can use and
+        test run().
+        It is given two sets of connected sockets; write/read, and
+        control_write/control_read. The former may be throwing errors
+        and mangle data to test msgq. The second is mainly used to
+        send msgq the stop command.
+        (Note that the terms 'read' and 'write' are from the msgq
+        point of view, so the test itself writes to 'control_read')
+        Parameters:
+        write: a socket that is used to send the data to
+        read: a socket that is used to read the data from
+        control_write: a second socket for communication with msgq
+        control_read: a second socket for communication with msgq
+        expect_arrive: if True, the read socket is read from, and the data
+                       that is read is expected to be the same as the data
+                       that has been sent to the write socket.
+        expect_send_exception: if not None, this is the exception that is
+                               expected to be raised by msgq
+        """
+
+        # Some message and envelope data to send and check
+        env = b'{"env": "foo"}'
+        msg = b'{"msg": "bar"}'
+
+        msgq = MsgQ()
+        # Don't need a listen_socket
+        msgq.listen_socket = DummySocket
+        msgq.setup_poller()
+        msgq.register_socket(write)
+        msgq.register_socket(control_write)
+        # Queue the message for sending
+        msgq.sendmsg(write, env, msg)
+
+        # Run it in a thread
+        msgq_thread = MsgQThread(msgq)
+        # If we're done, just kill it
+        msgq_thread.start()
+
+        if expect_arrive:
+            (recv_env, recv_msg) = msgq.read_packet(read.fileno(),
+                read)
+            self.assertEqual(env, recv_env)
+            self.assertEqual(msg, recv_msg)
+
+        # Tell msgq to stop
+        msg = msgq.preparemsg({"type" : "stop"})
+        control_read.sendall(msg)
+
+        # Wait for thread to stop if it hasn't already.
+        # Put in a (long) timeout; the thread *should* stop, but if it
+        # does not, we don't want the test to hang forever
+        msgq_thread.join(60)
+        # Fail the test if it didn't stop
+        self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
+
+        # Check the exception from the thread, if any
+        # First, if we didn't expect it; reraise it (to make test fail and
+        # show the stacktrace for debugging)
+        if expect_send_exception is None:
+            if msgq_thread.caught_exception is not None:
+                raise msgq_thread.caught_exception
+        else:
+            # If we *did* expect it, fail it there was none
+            self.assertIsNotNone(msgq_thread.caught_exception)
+
+    def do_send_with_send_error(self, raise_on_send, send_exception,
+                                expect_answer=True,
+                                expect_send_exception=None):
+        """
+        Sets up two connected sockets, wraps the sender socket into a BadSocket
+        class, then performs a do_send() test.
+        Parameters:
+        raise_on_send: the byte at which send_exception should be raised
+                       (see BadSocket)
+        send_exception: the exception to raise (see BadSocket)
+        expect_answer: whether the send is expected to complete (and hence
+                       the read socket should get the message)
+        expect_send_exception: the exception msgq is expected to raise when
+                               send_exception is raised by BadSocket.
+        """
+        (write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        (control_write, control_read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        badwrite = BadSocket(write, raise_on_send, send_exception)
+        self.do_send(badwrite, read, control_write, control_read, expect_answer, expect_send_exception)
+        write.close()
+        read.close()
+        control_write.close()
+        control_read.close()
+
+    def test_send_raise_recoverable(self):
+        """
+        Test whether msgq survices a recoverable socket errors when sending.
+        Two tests are done: one where the error is raised on the 3rd octet,
+                            and one on the 23rd.
+        """
+        sockerr = socket.error
+        for err in [ errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR ]:
+            sockerr.errno = err
+            self.do_send_with_send_error(3, sockerr)
+            self.do_send_with_send_error(23, sockerr)
+
+    def test_send_raise_nonrecoverable(self):
+        """
+        Test whether msgq survives socket errors that are nonrecoverable
+        (for said socket that is, i.e. EPIPE etc).
+        Two tests are done: one where the error is raised on the 3rd octet,
+                            and one on the 23rd.
+        """
+        sockerr = socket.error
+        for err in [ errno.EPIPE, errno.ENOBUFS, errno.ECONNRESET ]:
+            sockerr.errno = err
+            self.do_send_with_send_error(3, sockerr, False)
+            self.do_send_with_send_error(23, sockerr, False)
+
+    def otest_send_raise_crash(self):
+        """
+        Test whether msgq does NOT survive on a general exception.
+        Note, perhaps it should; but we'd have to first discuss and decide
+        how it should recover (i.e. drop the socket and consider the client
+        dead?
+        It may be a coding problem in msgq itself, and we certainly don't
+        want to ignore those.
+        """
+        sockerr = Exception("just some general exception")
+        self.do_send_with_send_error(3, sockerr, False, sockerr)
+        self.do_send_with_send_error(23, sockerr, False, sockerr)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/src/lib/datasrc/master_loader_callbacks.cc b/src/lib/datasrc/master_loader_callbacks.cc
index 54e4d0e..04b8940 100644
--- a/src/lib/datasrc/master_loader_callbacks.cc
+++ b/src/lib/datasrc/master_loader_callbacks.cc
@@ -48,6 +48,19 @@ logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
         arg(name).arg(rrclass).arg(reason);
 }
 
+void
+addRR(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+      const isc::dns::RRType& type, const isc::dns::RRTTL& ttl,
+      const isc::dns::rdata::RdataPtr& data, ZoneUpdater* updater)
+{
+    // We get description of one RR. The updater takes RRset, so we
+    // wrap it up and push there. It should collate the RRsets of the
+    // same name and type together, since the addRRset should "merge".
+    isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
+    rrset.addRdata(data);
+    updater->addRRset(rrset);
+}
+
 }
 
 isc::dns::MasterLoaderCallbacks
@@ -61,12 +74,9 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
                                                         rrclass, _1, _2, _3)));
 }
 
-isc::dns::AddRRsetCallback
+isc::dns::AddRRCallback
 createMasterLoaderAddCallback(ZoneUpdater& updater) {
-    return (boost::bind(&ZoneUpdater::addRRset, &updater,
-                        // The callback provides a shared pointer, we
-                        // need the object. This bind unpacks the object.
-                        boost::bind(&isc::dns::RRsetPtr::operator*, _1)));
+    return (boost::bind(addRR, _1, _2, _3, _4, _5, &updater));
 }
 
 }
diff --git a/src/lib/datasrc/master_loader_callbacks.h b/src/lib/datasrc/master_loader_callbacks.h
index c258303..ae827c9 100644
--- a/src/lib/datasrc/master_loader_callbacks.h
+++ b/src/lib/datasrc/master_loader_callbacks.h
@@ -58,7 +58,7 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
 /// \param updater The zone updater to use.
 /// \return The callback to be passed to MasterLoader.
 /// \throw std::bad_alloc when allocation fails.
-isc::dns::AddRRsetCallback
+isc::dns::AddRRCallback
 createMasterLoaderAddCallback(ZoneUpdater& updater);
 
 }
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index 7b82269..72b3273 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -27,6 +27,7 @@ libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
 libdatasrc_memory_la_SOURCES += zone_writer.h
 libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
 libdatasrc_memory_la_SOURCES += load_action.h
+libdatasrc_memory_la_SOURCES += util_internal.h
 
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 
diff --git a/src/lib/datasrc/memory/rdataset.cc b/src/lib/datasrc/memory/rdataset.cc
index aae64f3..e7a070f 100644
--- a/src/lib/datasrc/memory/rdataset.cc
+++ b/src/lib/datasrc/memory/rdataset.cc
@@ -122,8 +122,8 @@ RdataSet::create(util::MemorySegment& mem_sgmt, RdataEncoder& encoder,
 }
 
 void
-RdataSet::destroy(util::MemorySegment& mem_sgmt, RRClass rrclass,
-                  RdataSet* rdataset)
+RdataSet::destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+                  RRClass rrclass)
 {
     const size_t data_len =
         RdataReader(rrclass, rdataset->type,
diff --git a/src/lib/datasrc/memory/rdataset.h b/src/lib/datasrc/memory/rdataset.h
index b0b3b48..ffa5075 100644
--- a/src/lib/datasrc/memory/rdataset.h
+++ b/src/lib/datasrc/memory/rdataset.h
@@ -187,12 +187,12 @@ public:
     ///
     /// \param mem_sgmt The \c MemorySegment that allocated memory for
     /// \c node.
-    /// \param rrclass The RR class of the \c RdataSet to be destroyed.
     /// \param rdataset A non NULL pointer to a valid \c RdataSet object
+    /// \param rrclass The RR class of the \c RdataSet to be destroyed.
     /// that was originally created by the \c create() method (the behavior
     /// is undefined if this condition isn't met).
-    static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass,
-                        RdataSet* rdataset);
+    static void destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+                        dns::RRClass rrclass);
 
     /// \brief Find \c RdataSet of given RR type from a list (const version).
     ///
@@ -205,6 +205,11 @@ public:
     /// if not found in the entire list, it returns NULL.  The head pointer
     /// can be NULL, in which case this function will simply return NULL.
     ///
+    /// By default, this method ignores an RdataSet that only contains an
+    /// RRSIG (i.e., missing the covered RdataSet); if the optional
+    /// sigonly_ok parameter is explicitly set to true, it matches such
+    /// RdataSet and returns it if found.
+    ///
     /// \note This function is defined as a (static) class method to
     /// clarify its an operation for \c RdataSet objects and to make the
     /// name shorter.  But its implementation does not depend on private
@@ -215,10 +220,14 @@ public:
     /// \param rdata_head A pointer to \c RdataSet from which the search
     /// starts.  It can be NULL.
     /// \param type The RRType of \c RdataSet to find.
+    /// \param sigonly_ok Whether it should find an RdataSet that only has
+    /// RRSIG
     /// \return A pointer to the found \c RdataSet or NULL if none found.
     static const RdataSet*
-    find(const RdataSet* rdataset_head, const dns::RRType& type) {
-        return (find<const RdataSet>(rdataset_head, type));
+    find(const RdataSet* rdataset_head, const dns::RRType& type,
+         bool sigonly_ok = false)
+    {
+        return (find<const RdataSet>(rdataset_head, type, sigonly_ok));
     }
 
     /// \brief Find \c RdataSet of given RR type from a list (non const
@@ -227,8 +236,10 @@ public:
     /// This is similar to the const version, except it takes and returns non
     /// const pointers.
     static RdataSet*
-    find(RdataSet* rdataset_head, const dns::RRType& type) {
-        return (find<RdataSet>(rdataset_head, type));
+    find(RdataSet* rdataset_head, const dns::RRType& type,
+         bool sigonly_ok = false)
+    {
+        return (find<RdataSet>(rdataset_head, type, sigonly_ok));
     }
 
     typedef boost::interprocess::offset_ptr<RdataSet> RdataSetPtr;
@@ -347,12 +358,14 @@ private:
     // Shared by both mutable and immutable versions of find()
     template <typename RdataSetType>
     static RdataSetType*
-    find(RdataSetType* rdataset_head, const dns::RRType& type) {
+    find(RdataSetType* rdataset_head, const dns::RRType& type, bool sigonly_ok)
+    {
         for (RdataSetType* rdataset = rdataset_head;
              rdataset != NULL;
              rdataset = rdataset->getNext()) // use getNext() for efficiency
         {
-            if (rdataset->type == type) {
+            if (rdataset->type == type &&
+                (rdataset->getRdataCount() > 0 || sigonly_ok)) {
                 return (rdataset);
             }
         }
diff --git a/src/lib/datasrc/memory/util_internal.h b/src/lib/datasrc/memory/util_internal.h
new file mode 100644
index 0000000..05aaa29
--- /dev/null
+++ b/src/lib/datasrc/memory/util_internal.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2012  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 DATASRC_MEMORY_UTIL_INTERNAL_H
+#define DATASRC_MEMORY_UTIL_INTERNAL_H 1
+
+#include <dns/rdataclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+/// \brief Return the covered RR type of an RRSIG RRset.
+///
+/// This is a commonly used helper to extract the type covered field of an
+/// RRSIG RRset and return it in the form of an RRType object.
+///
+/// Normally, an empty RRSIG shouldn't be passed to this function, whether
+/// it comes from a master file or another data source iterator, but it could
+/// still happen in some buggy situations.  This function catches and rejects
+/// such cases.
+inline dns::RRType
+getCoveredType(const dns::ConstRRsetPtr& sig_rrset) {
+    dns::RdataIteratorPtr it = sig_rrset->getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(isc::Unexpected,
+                  "Empty RRset is passed in-memory loader, name: "
+                  << sig_rrset->getName());
+    }
+    return (dynamic_cast<const dns::rdata::generic::RRSIG&>(it->getCurrent()).
+            typeCovered());
+}
+
+} // namespace detail
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_UTIL_INTERNAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index e2cbdef..1bf9c9c 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -49,7 +49,7 @@ rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
          rdataset = rdataset_next)
     {
         rdataset_next = rdataset->getNext();
-        RdataSet::destroy(*mem_sgmt, rrclass, rdataset);
+        RdataSet::destroy(*mem_sgmt, rdataset, rrclass);
     }
 }
 
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index d0a43a3..051acc3 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -16,6 +16,7 @@
 #include <datasrc/memory/zone_data_updater.h>
 #include <datasrc/memory/logger.h>
 #include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/util_internal.h>
 
 #include <dns/rdataclass.h>
 #include <dns/rrset.h>
@@ -35,6 +36,7 @@ namespace datasrc {
 namespace memory {
 
 using detail::SegmentObjectHolder;
+using detail::getCoveredType;
 
 namespace { // unnamed namespace
 
@@ -75,8 +77,6 @@ private:
     typedef NodeRRsets::value_type NodeRRsetsVal;
 
     // A helper to identify the covered type of an RRSIG.
-    static isc::dns::RRType getCoveredType
-        (const isc::dns::ConstRRsetPtr& sig_rrset);
     const isc::dns::Name& getCurrentName() const;
 
 private:
@@ -126,34 +126,17 @@ ZoneDataLoader::flushNodeRRsets() {
         updater_.add(val.second, sig_rrset);
     }
 
-    // Right now, we don't accept RRSIG without covered RRsets (this
-    // should eventually allowed, but to do so we'll need to update the
-    // finder).
-    if (!node_rrsigsets_.empty()) {
-        isc_throw(ZoneDataUpdater::AddError,
-                  "RRSIG is added without covered RRset for "
-                  << getCurrentName());
+    // Normally rrsigsets map should be empty at this point, but it's still
+    // possible that an RRSIG that don't has covered RRset is added; they
+    // still remain in the map.  We add them to the zone separately.
+    BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
+        updater_.add(ConstRRsetPtr(), val.second);
     }
 
     node_rrsets_.clear();
     node_rrsigsets_.clear();
 }
 
-RRType
-ZoneDataLoader::getCoveredType(const ConstRRsetPtr& sig_rrset) {
-    RdataIteratorPtr it = sig_rrset->getRdataIterator();
-    // Empty RRSIG shouldn't be passed either via a master file or
-    // another data source iterator, but it could still happen if the
-    // iterator has a bug.  We catch and reject such cases.
-    if (it->isLast()) {
-        isc_throw(isc::Unexpected,
-                  "Empty RRset is passed in-memory loader, name: "
-                  << sig_rrset->getName());
-    }
-    return (dynamic_cast<const generic::RRSIG&>(it->getCurrent()).
-            typeCovered());
-}
-
 const Name&
 ZoneDataLoader::getCurrentName() const {
     if (!node_rrsets_.empty()) {
diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 3df8c66..7605644 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -12,12 +12,18 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+
 #include <datasrc/memory/zone_data_updater.h>
 #include <datasrc/memory/logger.h>
+#include <datasrc/memory/util_internal.h>
 #include <datasrc/zone.h>
 
 #include <dns/rdataclass.h>
 
+#include <cassert>
+#include <string>
+
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
@@ -25,6 +31,8 @@ namespace isc {
 namespace datasrc {
 namespace memory {
 
+using detail::getCoveredType;
+
 void
 ZoneDataUpdater::addWildcards(const Name& name) {
     Name wname(name);
@@ -99,9 +107,7 @@ ZoneDataUpdater::contextCheck(const AbstractRRset& rrset,
 
 void
 ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
-    if (!rrset) {
-        isc_throw(NullRRset, "The rrset provided is NULL");
-    }
+    assert(rrset);
 
     if (rrset->getRdataCount() == 0) {
         isc_throw(AddError,
@@ -241,31 +247,46 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
 }
 
 void
-ZoneDataUpdater::addNSEC3(const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig)
+ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
+                          const ConstRRsetPtr rrsig)
 {
-    setupNSEC3<generic::NSEC3>(rrset);
+    if (rrset) {
+        setupNSEC3<generic::NSEC3>(rrset);
+    }
 
     NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+    if (nsec3_data == NULL) {
+        // This is some tricky case: an RRSIG for NSEC3 is given without the
+        // covered NSEC3, and we don't even know any NSEC3 related data.
+        // This situation is not necessarily broken, but in our current
+        // implementation it's very difficult to deal with.  So we reject it;
+        // hopefully this case shouldn't happen in practice, at least unless
+        // zone is really broken.
+        assert(!rrset);
+        isc_throw(NotImplemented,
+                  "RRSIG for NSEC3 cannot be added - no known NSEC3 data");
+    }
 
     ZoneNode* node;
-    nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node);
+    nsec3_data->insertName(mem_sgmt_, name, &node);
 
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
     RdataSet* old_rdataset = node->setData(rdataset);
     if (old_rdataset != NULL) {
-        RdataSet::destroy(mem_sgmt_, rrclass_, old_rdataset);
+        RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
     }
 }
 
 void
-ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
+ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
+                             const ConstRRsetPtr rrset,
                              const ConstRRsetPtr rrsig)
 {
-    if (rrset->getType() == RRType::NSEC3()) {
-        addNSEC3(rrset, rrsig);
+    if (rrtype == RRType::NSEC3()) {
+        addNSEC3(name, rrset, rrsig);
     } else {
         ZoneNode* node;
-        zone_data_.insertName(mem_sgmt_, rrset->getName(), &node);
+        zone_data_.insertName(mem_sgmt_, name, &node);
 
         RdataSet* rdataset_head = node->getData();
 
@@ -273,13 +294,14 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
         // fails and the exception is thrown, it may break strong
         // exception guarantee.  At the moment we prefer code simplicity
         // and don't bother to introduce complicated recovery code.
-        contextCheck(*rrset, rdataset_head);
+        if (rrset) { // this check is only for covered RRset, not RRSIG
+            contextCheck(*rrset, rdataset_head);
+        }
 
-        if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) {
+        if (RdataSet::find(rdataset_head, rrtype, true) != NULL) {
             isc_throw(AddError,
                       "RRset of the type already exists: "
-                      << rrset->getName() << " (type: "
-                      << rrset->getType() << ")");
+                      << name << " (type: " << rrtype << ")");
         }
 
         RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_,
@@ -289,23 +311,25 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
 
         // Ok, we just put it in.
 
+        // Convenient (and more efficient) shortcut to check RRsets at origin
+        const bool is_origin = (node == zone_data_.getOriginNode());
+
         // If this RRset creates a zone cut at this node, mark the node
-        // indicating the need for callback in find().
-        if (rrset->getType() == RRType::NS() &&
-            rrset->getName() != zone_name_) {
+        // indicating the need for callback in find().  Note that we do this
+        // only when non RRSIG RRset of that type is added.
+        if (rrset && rrtype == RRType::NS() && !is_origin) {
             node->setFlag(ZoneNode::FLAG_CALLBACK);
             // If it is DNAME, we have a callback as well here
-        } else if (rrset->getType() == RRType::DNAME()) {
+        } else if (rrset && rrtype == RRType::DNAME()) {
             node->setFlag(ZoneNode::FLAG_CALLBACK);
         }
 
         // If we've added NSEC3PARAM at zone origin, set up NSEC3
         // specific data or check consistency with already set up
         // parameters.
-        if (rrset->getType() == RRType::NSEC3PARAM() &&
-            rrset->getName() == zone_name_) {
+        if (rrset && rrtype == RRType::NSEC3PARAM() && is_origin) {
             setupNSEC3<generic::NSEC3PARAM>(rrset);
-        } else if (rrset->getType() == RRType::NSEC()) {
+        } else if (rrset && rrtype == RRType::NSEC() && is_origin) {
             // If it is NSEC signed zone, we mark the zone as signed
             // (conceptually "signed" is a broader notion but our
             // current zone finder implementation regards "signed" as
@@ -319,27 +343,37 @@ void
 ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
                      const ConstRRsetPtr& sig_rrset)
 {
-    // Validate input.  This will cause an exception to be thrown if the
-    // input RRset is empty.
-    validate(rrset);
+    // Validate input.
+    if (!rrset && !sig_rrset) {
+        isc_throw(NullRRset,
+                  "ZoneDataUpdater::add is given 2 NULL pointers");
+    }
+    if (rrset) {
+        validate(rrset);
+    }
     if (sig_rrset) {
         validate(sig_rrset);
     }
 
+    const Name& name = rrset ? rrset->getName() : sig_rrset->getName();
+    const RRType& rrtype = rrset ? rrset->getType() :
+        getCoveredType(sig_rrset);
+
     // OK, can add the RRset.
-    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
-        arg(rrset->getName()).arg(rrset->getType()).arg(zone_name_);
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).arg(name).
+        arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
+        arg(zone_name_);
 
     // Add wildcards possibly contained in the owner name to the domain
     // tree.  This can only happen for the normal (non-NSEC3) tree.
     // Note: this can throw an exception, breaking strong exception
     // guarantee.  (see also the note for the call to contextCheck()
     // above).
-    if (rrset->getType() != RRType::NSEC3()) {
-        addWildcards(rrset->getName());
+    if (rrtype != RRType::NSEC3()) {
+        addWildcards(name);
     }
 
-    addRdataSet(rrset, sig_rrset);
+    addRdataSet(name, rrtype, rrset, sig_rrset);
 }
 
 } // namespace memory
diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h
index fa9a6af..9d669a0 100644
--- a/src/lib/datasrc/memory/zone_data_updater.h
+++ b/src/lib/datasrc/memory/zone_data_updater.h
@@ -110,10 +110,32 @@ public:
     /// populated with the record data and added to the ZoneData for the
     /// name in the RRset.
     ///
-    /// This method throws an \c NullRRset exception (see above) if
-    /// \c rrset is empty. It throws \c AddError if any of a variety of
-    /// validation checks fail for the \c rrset and its associated
-    /// \c sig_rrset.
+    /// At least one of \c rrset or \c sig_rrset must be non NULL.
+    /// \c sig_rrset can be reasonably NULL when \c rrset is not signed in
+    /// the zone; it's unusual that \c rrset is NULL, but is still possible
+    /// if these RRsets are given separately to the loader, or if even the
+    /// zone is half broken and really contains an RRSIG that doesn't have
+    /// any covered RRset.  This implementation supports these cases (but
+    /// see the note below).
+    ///
+    /// There is one tricky case: Due to a limitation of the current
+    /// implementation, it cannot accept an RRSIG for NSEC3 without the covered
+    /// NSEC3, unless at least one NSEC3 or NSEC3PARAM has been added.
+    /// In this case an isc::NotImplemented exception will be thrown.  It
+    /// should be very rare in practice, and hopefully wouldn't be a real
+    /// issue.
+    ///
+    /// \note Due to limitations of the current implementation, if a
+    /// (non RRSIG) RRset and its RRSIG are added separately in different
+    /// calls to this method, the second attempt will be rejected due to
+    /// an \c AddError exception.  This will be loosened in Trac
+    /// ticket #2441.
+    ///
+    /// \throw NullRRset Both \c rrset and sig_rrset is NULL
+    /// \throw AddError any of a variety of validation checks fail for the
+    /// \c rrset and its associated \c sig_rrset.
+    /// \throw NotImplemented RRSIG for NSEC3 cannot be added due to internal
+    /// restriction.
     ///
     /// \param rrset The RRset to be added.
     /// \param sig_rrset An associated RRSIG RRset for the \c rrset. It
@@ -152,9 +174,12 @@ private:
     const isc::dns::NSEC3Hash* getNSEC3Hash();
     template <typename T>
     void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
-    void addNSEC3(const isc::dns::ConstRRsetPtr rrset,
+    void addNSEC3(const isc::dns::Name& name,
+                  const isc::dns::ConstRRsetPtr rrset,
                   const isc::dns::ConstRRsetPtr rrsig);
-    void addRdataSet(const isc::dns::ConstRRsetPtr rrset,
+    void addRdataSet(const isc::dns::Name& name,
+                     const isc::dns::RRType& rrtype,
+                     const isc::dns::ConstRRsetPtr rrset,
                      const isc::dns::ConstRRsetPtr rrsig);
 
     util::MemorySegment& mem_sgmt_;
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index 11188a0..4240c21 100644
--- a/src/lib/datasrc/memory/zone_finder.cc
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -216,6 +216,14 @@ createNSEC3RRset(const ZoneNode* node, const RRClass& rrclass) {
      assert(rdataset != NULL);
      assert(rdataset->type == RRType::NSEC3());
 
+     // Check for the rare case of RRSIG-only record; in theory it could exist
+     // but we simply consider it broken for NSEC3.
+     if (rdataset->getRdataCount() == 0) {
+         uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+         isc_throw(DataSourceError, "Broken zone: RRSIG-only NSEC3 record at "
+                   << node->getAbsoluteLabels(labels_buf) << "/" << rrclass);
+     }
+
     // Create the RRset.  Note the DNSSEC flag: NSEC3 implies DNSSEC.
     return (createTreeNodeRRset(node, rdataset, rrclass,
                                 ZoneFinder::FIND_DNSSEC));
@@ -627,7 +635,10 @@ private:
             // This can be a bit more optimized, but unless we have many
             // requested types the effect is probably marginal.  For now we
             // keep it simple.
-            if (std::find(type_beg, type_end, rdset->type) != type_end) {
+            // Check for getRdataCount is necessary not to include RRSIG-only
+            // records accidentally (should be rare, but possible).
+            if (std::find(type_beg, type_end, rdset->type) != type_end &&
+                rdset->getRdataCount() > 0) {
                 result->push_back(createTreeNodeRRset(node, rdset, rrclass_,
                                                       options, real_name));
             }
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 7ba8960..61858bd 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -95,6 +95,7 @@ endif
 
 EXTRA_DIST =  testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/contexttest.zone
+EXTRA_DIST += testdata/contexttest-almost-obsolete.zone
 EXTRA_DIST += testdata/diffs.sqlite3
 EXTRA_DIST += testdata/duplicate_rrset.zone
 EXTRA_DIST += testdata/example2.com
diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
index f814288..f555961 100644
--- a/src/lib/datasrc/tests/master_loader_callbacks_test.cc
+++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
@@ -18,6 +18,7 @@
 #include <dns/rrset.h>
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
+#include <dns/rdata.h>
 
 #include <exceptions/exceptions.h>
 
@@ -40,8 +41,8 @@ public:
     // the correct ones, according to a predefined set in a list.
     virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
         ASSERT_FALSE(expected_rrsets_.empty());
-        // In our tests, pointer equality is enough.
-        EXPECT_EQ(expected_rrsets_.front().get(), &rrset);
+
+        EXPECT_EQ(expected_rrsets_.front().get()->toText(), rrset.toText());
         // And remove this RRset, as it has been used.
         expected_rrsets_.pop_front();
     }
@@ -67,14 +68,22 @@ protected:
                                                isc::dns::RRClass::IN(), &ok_))
     {}
     // Generate a new RRset, put it to the updater and return it.
-    isc::dns::RRsetPtr generateRRset() {
+    void generateRRset(isc::dns::AddRRCallback callback) {
         const isc::dns::RRsetPtr
             result(new isc::dns::RRset(isc::dns::Name("example.org"),
                                        isc::dns::RRClass::IN(),
                                        isc::dns::RRType::A(),
                                        isc::dns::RRTTL(3600)));
+        const isc::dns::rdata::RdataPtr
+            data(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
+                                              isc::dns::RRClass::IN(),
+                                              "192.0.2.1"));
+
+        result->addRdata(data);
         updater_.expected_rrsets_.push_back(result);
-        return (result);
+
+        callback(result->getName(), result->getClass(), result->getType(),
+                 result->getTTL(), data);
     }
     // An updater to be passed to the context
     MockUpdater updater_;
@@ -112,11 +121,11 @@ TEST_F(MasterLoaderCallbackTest, callbacks) {
 
 // Try adding some RRsets.
 TEST_F(MasterLoaderCallbackTest, addRRset) {
-    isc::dns::AddRRsetCallback
+    isc::dns::AddRRCallback
         callback(createMasterLoaderAddCallback(updater_));
     // Put some of them in.
-    EXPECT_NO_THROW(callback(generateRRset()));
-    EXPECT_NO_THROW(callback(generateRRset()));
+    EXPECT_NO_THROW(generateRRset(callback));
+    EXPECT_NO_THROW(generateRRset(callback));
     // They all get pushed there right away, so there are none in the queue
     EXPECT_TRUE(updater_.expected_rrsets_.empty());
 }
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index 67e63b9..a5d73b4 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -32,6 +32,8 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
 run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += memory_client_unittest.cc
+run_unittests_SOURCES += zone_data_loader_unittest.cc
+run_unittests_SOURCES += zone_data_updater_unittest.cc
 run_unittests_SOURCES += zone_table_segment_test.h
 run_unittests_SOURCES += zone_table_segment_unittest.cc
 run_unittests_SOURCES += zone_writer_unittest.cc
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 5bc359c..0a03645 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -576,16 +576,6 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
     // Teardown checks for memory segment leaks
 }
 
-TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
-    // This causes the situation where an RRSIG is added without a covered
-    // RRset.  Such cases are currently rejected.
-    EXPECT_THROW(client_->load(Name("example.org"),
-                               TEST_DATA_DIR
-                               "/example.org-rrsig-follows-nothing.zone"),
-                 ZoneDataUpdater::AddError);
-    // Teardown checks for memory segment leaks
-}
-
 TEST_F(MemoryClientTest, loadRRSIGs) {
     client_->load(Name("example.org"),
                   TEST_DATA_DIR "/example.org-rrsigs.zone");
diff --git a/src/lib/datasrc/tests/memory/rdataset_unittest.cc b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
index 897e53c..f599999 100644
--- a/src/lib/datasrc/tests/memory/rdataset_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
@@ -24,6 +24,7 @@
 #include <dns/rrtype.h>
 #include <dns/rrttl.h>
 
+#include <datasrc/memory/segment_object_holder.h>
 #include <datasrc/memory/rdata_serialization.h>
 #include <datasrc/memory/rdataset.h>
 
@@ -39,6 +40,7 @@ using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::datasrc::memory;
 using namespace isc::testutils;
+using isc::datasrc::memory::detail::SegmentObjectHolder;
 using boost::lexical_cast;
 
 namespace {
@@ -112,7 +114,7 @@ TEST_F(RdataSetTest, create) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           ConstRRsetPtr());
     checkRdataSet(*rdataset, true, false);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 TEST_F(RdataSetTest, getNext) {
@@ -131,7 +133,62 @@ TEST_F(RdataSetTest, getNext) {
     rdataset->next = rdataset;
     EXPECT_EQ(rdataset, static_cast<const RdataSet*>(rdataset)->getNext());
 
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
+}
+
+TEST_F(RdataSetTest, find) {
+    // Create some RdataSets and make a chain of them.
+    SegmentObjectHolder<RdataSet, RRClass> holder1(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
+        RRClass::IN());
+    ConstRRsetPtr aaaa_rrset =
+        textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
+    SegmentObjectHolder<RdataSet, RRClass> holder2(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
+        RRClass::IN());
+    ConstRRsetPtr sigonly_rrset =
+        textToRRset("www.example.com. 1076895760 IN RRSIG "
+                    "TXT 5 2 3600 20120814220826 20120715220826 "
+                    "1234 example.com. FAKE");
+    SegmentObjectHolder<RdataSet, RRClass> holder3(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
+        RRClass::IN());
+
+    RdataSet* rdataset_a = holder1.get();
+    RdataSet* rdataset_aaaa = holder2.get();
+    RdataSet* rdataset_sigonly = holder3.get();
+    RdataSet* rdataset_null = NULL;
+    rdataset_a->next = rdataset_aaaa;
+    rdataset_aaaa->next = rdataset_sigonly;
+
+    // If a non-RRSIG part of rdataset exists for the given type, it will be
+    // returned regardless of the value of sigonly_ok.  If it's RRSIG-only
+    // rdataset, it returns non NULL iff sigonly_ok is explicitly set to true.
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA()));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), true));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), false));
+
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT()));
+    EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a, RRType::TXT(),
+                                               true));
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT(), false));
+
+    // Same tests for the const version of find().
+    const RdataSet* rdataset_a_const = holder1.get();
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA()));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+                                            true));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+                                            false));
+
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT()));
+    EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a_const, RRType::TXT(),
+                                               true));
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT(),
+                                            false));
 }
 
 // A helper function to create an RRset containing the given number of
@@ -154,7 +211,7 @@ TEST_F(RdataSetTest, createManyRRs) {
                                           ConstRRsetPtr());
     EXPECT_EQ(8191, rdataset->getRdataCount());
     EXPECT_EQ(0, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Exceeding that will result in an exception.
     EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_,
@@ -173,7 +230,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           rrsig_rrset_);
     checkRdataSet(*rdataset, true, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Unusual case: TTL doesn't match.  This implementation accepts that,
     // using the TTL of the covered RRset.
@@ -183,7 +240,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
                                    "20120715220826 1234 example.com. FAKE"));
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_badttl);
     checkRdataSet(*rdataset, true, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 // A helper function to create an RRSIG RRset containing the given number of
@@ -218,21 +275,21 @@ TEST_F(RdataSetTest, createManyRRSIGs) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           getRRSIGWithRdataCount(7));
     EXPECT_EQ(7, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // 8 would cause overflow in the normal 3-bit field if there were no extra
     // count field.
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                 getRRSIGWithRdataCount(8));
     EXPECT_EQ(8, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Up to 2^16-1 RRSIGs are allowed (although that would be useless
     // in practice)
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                 getRRSIGWithRdataCount(65535));
     EXPECT_EQ(65535, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Exceeding this limit will result in an exception.
     EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
@@ -250,7 +307,7 @@ TEST_F(RdataSetTest, createWithRRSIGOnly) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
                                           rrsig_rrset_);
     checkRdataSet(*rdataset, false, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 TEST_F(RdataSetTest, badCeate) {
diff --git a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
new file mode 100644
index 0000000..c005bf1
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2012  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 <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataLoaderTest : public ::testing::Test {
+protected:
+    ZoneDataLoaderTest() : zclass_(RRClass::IN()), zone_data_(NULL) {}
+    void TearDown() {
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        }
+        EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
+    }
+    const RRClass zclass_;
+    test::MemorySegmentTest mem_sgmt_;
+    ZoneData* zone_data_;
+};
+
+TEST_F(ZoneDataLoaderTest, loadRRSIGFollowsNothing) {
+    // This causes the situation where an RRSIG is added without a covered
+    // RRset.  It will be accepted, and corresponding "sig-only" rdata will
+    // be created.
+    zone_data_ = loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+                              TEST_DATA_DIR
+                              "/example.org-rrsig-follows-nothing.zone");
+    ZoneNode* node = NULL;
+    zone_data_->insertName(mem_sgmt_, Name("ns1.example.org"), &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    EXPECT_EQ(RRType::A(), rdset->type); // there should be only 1 data here
+    EXPECT_EQ(0, rdset->getRdataCount()); // no RDATA
+    EXPECT_EQ(1, rdset->getSigRdataCount()); // but 1 SIG
+
+    // Teardown checks for memory segment leaks
+}
+
+}
diff --git a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
new file mode 100644
index 0000000..63c69c8
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
@@ -0,0 +1,208 @@
+// Copyright (C) 2012  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 <testutils/dnsmessage_test.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cassert>
+
+using isc::testutils::textToRRset;
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataUpdaterTest : public ::testing::Test {
+protected:
+    ZoneDataUpdaterTest() :
+        zname_("example.org"), zclass_(RRClass::IN()),
+        zone_data_(ZoneData::create(mem_sgmt_, zname_)),
+        updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
+    {}
+
+    ~ZoneDataUpdaterTest() {
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        }
+        if (!mem_sgmt_.allMemoryDeallocated()) {
+            ADD_FAILURE() << "Memory leak detected";
+        }
+    }
+
+    void clearZoneData() {
+        assert(zone_data_ != NULL);
+        ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        zone_data_ = ZoneData::create(mem_sgmt_, zname_);
+        updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
+                                           *zone_data_));
+    }
+
+    const Name zname_;
+    const RRClass zclass_;
+    test::MemorySegmentTest mem_sgmt_;
+    ZoneData* zone_data_;
+    boost::scoped_ptr<ZoneDataUpdater> updater_;
+};
+
+TEST_F(ZoneDataUpdaterTest, bothNull) {
+    // At least either covered RRset or RRSIG must be non NULL.
+    EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
+                 ZoneDataUpdater::NullRRset);
+}
+
+ZoneNode*
+getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
+        ZoneData* zone_data)
+{
+    ZoneNode* node = NULL;
+    zone_data->insertName(mem_sgmt, name, &node);
+    EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+    return (node);
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
+    // RRSIG that doesn't have covered RRset can be added.  The resulting
+    // rdataset won't have "normal" RDATA but sig RDATA.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "www.example.org. 3600 IN RRSIG A 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    rdset = RdataSet::find(rdset, RRType::A(), true);
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    EXPECT_EQ(0, rdset->getRdataCount());
+    EXPECT_EQ(1, rdset->getSigRdataCount());
+
+    // The RRSIG covering A prohibits an actual A RRset from being added.
+    // This should be loosened in future version, but we check the current
+    // behavior.
+    EXPECT_THROW(updater_->add(
+                     textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
+                     ConstRRsetPtr()), ZoneDataUpdater::AddError);
+
+    // The special "wildcarding" node mark should be added for the RRSIG-only
+    // case, too.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
+    EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
+
+    // Simply adding RRSIG covering (delegating NS) shouldn't enable callback
+    // in search.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "child.example.org. 3600 IN RRSIG NS 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
+    EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+    // Same for DNAME
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
+    EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+    // Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
+    // "NSEC3-signed".
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_FALSE(zone_data_->isNSEC3Signed());
+
+    // And same for (RRSIG for) NSEC and "is signed".
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_FALSE(zone_data_->isSigned());
+}
+
+// Commonly used checks for rrsigForNSEC3Only
+void
+checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
+                ZoneData* zone_data)
+{
+    ZoneNode* node = NULL;
+    zone_data->getNSEC3Data()->insertName(mem_sgmt, name, &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    ASSERT_EQ(RRType::NSEC3(), rdset->type);
+    EXPECT_EQ(0, rdset->getRdataCount());
+    EXPECT_EQ(1, rdset->getSigRdataCount());
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
+    // Adding only RRSIG covering NSEC3 is tricky.  It should go to the
+    // separate NSEC3 tree, but the separate space is only created when
+    // NSEC3 or NSEC3PARAM is added.  So, in many cases RRSIG-only is allowed,
+    // but if no NSEC3 or NSEC3PARAM has been added it will be rejected.
+
+    // Below we use abnormal owner names and RDATA for NSEC3s for brevity,
+    // but that doesn't matter for this test.
+
+    // Add NSEC3PARAM, then RRSIG-only, which is okay.
+    updater_->add(textToRRset(
+                      "example.org. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD"),
+                  textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_TRUE(zone_data_->isNSEC3Signed());
+    updater_->add(ConstRRsetPtr(),
+                  textToRRset(
+                      "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+    // Clear the current content of zone, then add NSEC3
+    clearZoneData();
+    updater_->add(textToRRset(
+                      "AABB.example.org. 3600 IN NSEC3 1 0 10 AA 00000000 A"),
+                  textToRRset(
+                      "AABB.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    updater_->add(ConstRRsetPtr(),
+                  textToRRset(
+                      "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+    // If we add only RRSIG without any NSEC3 related data beforehand,
+    // it will be rejected; it's a limitation of the current implementation.
+    clearZoneData();
+    EXPECT_THROW(updater_->add(
+                     ConstRRsetPtr(),
+                     textToRRset(
+                         "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                         "20150420235959 20051021000000 1 example.org. FAKE")),
+                 isc::NotImplemented);
+}
+
+}
diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index 4cd08c0..f112119 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -1358,6 +1358,74 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
                  DataSourceError);
 }
 
+TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
+    // Make the zone "NSEC signed"
+    addToZoneData(rr_nsec_);
+    const ZoneFinder::FindResultFlags expected_flags =
+        ZoneFinder::RESULT_NSEC_SIGNED;
+
+    // Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
+    // query for the TXT should result in NXRRSET.
+    addToZoneData(rr_ns_a_);
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
+                     "20120715220826 1234 example.com. FAKE"));
+    findTest(Name("ns.example.org"), RRType::TXT(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+    // Add RRSIG-only covering NSEC.  This shouldn't be returned when NSEC is
+    // requested, whether it's for NXRRSET or NXDOMAIN
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    // The added RRSIG for NSEC could be used for NXRRSET but shouldn't
+    findTest(Name("ns.example.org"), RRType::TXT(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+             expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+    // The added RRSIG for NSEC could be used for NXDOMAIN but shouldn't
+    findTest(Name("nz.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, rr_nsec_,
+             expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+
+    // RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("nocname.example.org"), RRType::A(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+    // RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
+    // case explicitly.
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("nodelegation.example.org"), RRType::A(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+    findTest(Name("www.nodelegation.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+
+    // Same for RRSIG-only for DNAME
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("www.nodname.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+    // If we have a delegation NS at this node, it will be a bit trickier,
+    // because the zonecut processing actually takes place at the node.
+    // But the RRSIG-only for DNAME shouldn't confuse the process and the NS
+    // should win.
+    ConstRRsetPtr ns_rrset =
+        textToRRset("nodname.example.org. 300 IN NS ns.nodname.example.org.");
+    addToZoneData(ns_rrset);
+    findTest(Name("www.nodname.example.org"), RRType::A(),
+             ZoneFinder::DELEGATION, true, ns_rrset);
+}
+
 /// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest {
 public:
@@ -1481,4 +1549,17 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3Walk) {
         }
     }
 }
+
+TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
+    // add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
+    // should result in an exception.
+    const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
+                 DataSourceError);
+}
+
 }
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index 5223d83..85be310 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -1219,7 +1219,7 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
     // purpose of this test, so it should just succeed.
     db_client = unittest::createSQLite3Client(
         class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
-        TEST_DATA_DIR "/contexttest.zone");
+        TEST_DATA_DIR "/contexttest-almost-obsolete.zone");
     zone_finder_.load(*db_client->getIterator(origin_));
 
     // just checking a couple of RRs in the new version of zone.
diff --git a/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone b/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
new file mode 100644
index 0000000..f65f8c1
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
@@ -0,0 +1,85 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+example.org.			      3600 IN NS	ns1.example.org.
+example.org.			      3600 IN NS	ns2.example.org.
+example.org.			      3600 IN RRSIG	NS 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+example.org.			      3600 IN MX	1 mx1.example.org.
+example.org.			      3600 IN MX	2 mx2.example.org.
+example.org.			      3600 IN MX	3 mx.a.example.org.
+
+ns1.example.org.		      3600 IN A		192.0.2.1
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org.		      3600 IN AAAA	2001:db8::1
+ns1.example.org.		      3600 IN RRSIG	AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+ns2.example.org.		      3600 IN A		192.0.2.2
+ns2.example.org.		      3600 IN TXT	"text data"
+
+mx1.example.org.		      3600 IN A		192.0.2.10
+mx2.example.org.		      3600 IN AAAA	2001:db8::10
+
+;; delegation
+a.example.org.			      3600 IN NS	ns1.a.example.org.
+a.example.org.			      3600 IN NS	ns2.a.example.org.
+a.example.org.			      3600 IN NS	ns.example.com.
+
+ns1.a.example.org.		      3600 IN A		192.0.2.5
+ns2.a.example.org.		      3600 IN A		192.0.2.6
+ns2.a.example.org.		      3600 IN AAAA	2001:db8::6
+mx.a.example.org.		      3600 IN A		192.0.2.7
+
+;; delegation, one of its NS names is at zone cut.
+b.example.org.			      3600 IN NS	ns.b.example.org.
+b.example.org.			      3600 IN NS	b.example.org.
+b.example.org.			      3600 IN AAAA	2001:db8::8
+
+ns.b.example.org.		      3600 IN A		192.0.2.9
+
+;; The MX name is at a zone cut.  shouldn't be included in the
+;; additional section.
+mxatcut.example.org.		      3600 IN MX	1 b.example.org.
+
+;; delegation, one of its NS names is under a DNAME delegation point;
+;; another is at that point; and yet another is under DNAME below a
+;; zone cut.
+c.example.org. 	      	      3600 IN NS	ns.dname.example.org.
+c.example.org. 	      	      3600 IN NS	dname.example.org.
+c.example.org.      	      3600 IN NS	ns.deepdname.example.org.
+ns.dname.example.org.		      3600 IN A		192.0.2.11
+dname.example.org.		      3600 IN A		192.0.2.12
+ns.deepdname.example.org.	      3600 IN AAAA	2001:db8::9
+
+;; delegation, one of its NS name is at an empty non terminal.
+d.example.org. 	      	      3600 IN NS	ns.empty.example.org.
+d.example.org. 	      	      3600 IN NS	ns1.example.org.
+;; by adding these two we can create an empty RB node for
+;; ns.empty.example.org in the in-memory zone
+foo.ns.empty.example.org.     3600 IN A		192.0.2.13
+bar.ns.empty.example.org.     3600 IN A		192.0.2.14
+
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match).  One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
+e.example.org. 	      	      3600 IN NS	ns.wild.example.org.
+e.example.org. 	      	      3600 IN NS	ns.emptywild.example.org.
+e.example.org. 	      	      3600 IN NS	ns2.example.org.
+*.wild.example.org.	      3600 IN A		192.0.2.15
+a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
+
+;; additional for an answer RRset (MX) as a result of wildcard
+;; expansion
+*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
+
+;; the owner name of additional for an answer RRset (MX) has DNAME
+dnamemx.example.org. 3600 IN MX 1 dname.example.org.
+
+;; CNAME
+alias.example.org. 3600 IN CNAME cname.example.org.
+
+;; DNAME
+dname.example.org. 3600 IN DNAME dname.example.com.
+
+;; DNAME under a NS (strange one)
+deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.
diff --git a/src/lib/datasrc/tests/testdata/contexttest.zone b/src/lib/datasrc/tests/testdata/contexttest.zone
index 0c1393c..ae028c4 100644
--- a/src/lib/datasrc/tests/testdata/contexttest.zone
+++ b/src/lib/datasrc/tests/testdata/contexttest.zone
@@ -68,6 +68,13 @@ e.example.org. 	      	      3600 IN NS	ns2.example.org.
 *.wild.example.org.	      3600 IN A		192.0.2.15
 a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
 
+;; One of the additional records actually only has RRSIG, which should
+;; be ignored.
+f.example.org. 	      	      3600 IN MX 5 mx1.f.example.org.
+f.example.org. 	      	      3600 IN MX 10 mx2.f.example.org.
+mx1.f.example.org.     	      3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+mx2.f.example.org.     	      3600 IN A 192.0.2.16
+
 ;; additional for an answer RRset (MX) as a result of wildcard
 ;; expansion
 *.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 1a4cae2..6844712 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -418,4 +418,16 @@ TEST_P(ZoneFinderContextTest, getAdditionalForAny) {
                 result_sets_.begin(), result_sets_.end());
 }
 
+TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
+    // This has two MX records, but type-A for one of them only has RRSIG.
+    // It shouldn't be contained in the result.
+    ZoneFinderContextPtr ctx = finder_->find(Name("f.example.org"),
+                                             RRType::MX(),
+                                             ZoneFinder::FIND_DNSSEC);
+    EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+    ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+    rrsetsCheck("mx2.f.example.org. 3600 IN A 192.0.2.16\n",
+                result_sets_.begin(), result_sets_.end());
+}
+
 }
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index ccc5b9d..c71c1e3 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -288,7 +288,7 @@ TEST_F(ZoneLoaderTest, classMismatch) {
 // Load an unsigned zone, all at once
 TEST_F(ZoneLoaderTest, loadUnsigned) {
     ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
-                      TEST_DATA_DIR "root.zone");
+                      TEST_DATA_DIR "/root.zone");
     // It gets the updater directly in the constructor
     ASSERT_EQ(1, destination_client_.provided_updaters_.size());
     EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
@@ -377,8 +377,10 @@ TEST_F(ZoneLoaderTest, loadSigned) {
 
 // Test it throws when there's no such file
 TEST_F(ZoneLoaderTest, loadNoSuchFile) {
-    EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
-                            "This file does not exist"), MasterFileError);
+    ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+                      "This file does not exist");
+    EXPECT_THROW(loader.load(), MasterFileError);
+    EXPECT_FALSE(destination_client_.commit_called_);
 }
 
 // And it also throws when there's a syntax error in the master file
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index b77f6b8..1d06740 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -59,18 +59,23 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
 ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
                        const char* filename) :
     updater_(destination.getUpdater(zone_name, true, false)),
-    loader_(new MasterLoader(filename, zone_name,
-                             // TODO: Maybe we should have getClass() on the
-                             // data source?
-                             updater_->getFinder().getClass(),
-                             createMasterLoaderCallbacks(zone_name,
-                                             updater_->getFinder().getClass(),
-                                             &loaded_ok_),
-                             createMasterLoaderAddCallback(*updater_))),
     complete_(false),
     loaded_ok_(true)
 {
-
+    if (updater_ == ZoneUpdaterPtr()) {
+        isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
+                  "destination data source, can't fill it with data");
+    } else {
+        loader_.reset(new
+                      MasterLoader(filename, zone_name,
+                                   // TODO: Maybe we should have getClass()
+                                   // on the data source?
+                                   updater_->getFinder().getClass(),
+                                   createMasterLoaderCallbacks(zone_name,
+                                       updater_->getFinder().getClass(),
+                                       &loaded_ok_),
+                                   createMasterLoaderAddCallback(*updater_)));
+    }
 }
 
 namespace {
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
index 65be733..5f5ddfd 100644
--- a/src/lib/datasrc/zone_loader.h
+++ b/src/lib/datasrc/zone_loader.h
@@ -140,7 +140,7 @@ private:
     /// \brief The destination zone updater
     const ZoneUpdaterPtr updater_;
     /// \brief The master loader (for the loader mode)
-    const boost::scoped_ptr<isc::dns::MasterLoader> loader_;
+    boost::scoped_ptr<isc::dns::MasterLoader> loader_;
     /// \brief Indicator if loading was completed
     bool complete_;
     /// \brief Was the loading successful?
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 1c74446..e9333e2 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -21,8 +21,9 @@ 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 += option.cc option.h
-libb10_dhcp___la_SOURCES += option_data_types.h
+libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
 libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
+libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index b8ed6c2..4638025 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -114,7 +114,7 @@ Option::pack4(isc::util::OutputBuffer& buf) {
             buf.writeData(&data_[0], data_.size());
         }
 
-        LibDHCP::packOptions(buf, options_);
+        packOptions(buf);
 
     } else {
         isc_throw(BadValue, "Invalid universe type " << universe_);
@@ -131,18 +131,46 @@ void Option::pack6(isc::util::OutputBuffer& buf) {
             buf.writeData(&data_[0], data_.size());
         }
 
-        LibDHCP::packOptions6(buf, options_);
+        packOptions(buf);
     } else {
         isc_throw(BadValue, "Invalid universe type " << universe_);
     }
     return;
 }
 
+void
+Option::packOptions(isc::util::OutputBuffer& buf) {
+    switch (universe_) {
+    case V4:
+        LibDHCP::packOptions(buf, options_);
+        return;
+    case V6:
+        LibDHCP::packOptions6(buf, options_);
+        return;
+    default:
+        isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+    }
+}
+
 void Option::unpack(OptionBufferConstIter begin,
                     OptionBufferConstIter end) {
     data_ = OptionBuffer(begin, end);
 }
 
+void
+Option::unpackOptions(const OptionBuffer& buf) {
+    switch (universe_) {
+    case V4:
+        LibDHCP::unpackOptions4(buf, options_);
+        return;
+    case V6:
+        LibDHCP::unpackOptions6(buf, options_);
+        return;
+    default:
+        isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+    }
+}
+
 uint16_t Option::len() {
     // Returns length of the complete option (data length + DHCPv4/DHCPv6
     // option header)
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index d6d5f2d..a6b0622 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -312,6 +312,35 @@ protected:
     /// @throw BadValue Universe is not V6.
     virtual void pack6(isc::util::OutputBuffer& buf);
 
+    /// @brief Store sub options in a buffer.
+    ///
+    /// This method stores all sub-options defined for a particular
+    /// option in a on-wire format in output buffer provided.
+    /// This function is called by pack function in this class or
+    /// derived classes that override pack.
+    ///
+    /// @param [out] buf output buffer.
+    ///
+    /// @todo The set of exceptions thrown by this function depend on
+    /// exceptions thrown by pack methods invoked on objects
+    /// representing sub options. We should consider whether to aggregate
+    /// those into one exception which can be documented here.
+    void packOptions(isc::util::OutputBuffer& buf);
+
+    /// @brief Builds a collection of sub options from the buffer.
+    ///
+    /// This method parses the provided buffer and builds a collection
+    /// of objects representing sub options. This function may throw
+    /// different exceptions when option assembly fails.
+    ///
+    /// @param buf buffer to be parsed.
+    ///
+    /// @todo The set of exceptions thrown by this function depend on
+    /// exceptions thrown by unpack methods invoked on objects
+    /// representing sub options. We should consider whether to aggregate
+    /// those into one exception which can be documented here.
+    void unpackOptions(const OptionBuffer& buf);
+
     /// @brief A private method used for option correctness.
     ///
     /// It is used in constructors. In there are any problems detected
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
index 9e4e01e..64c2936 100644
--- a/src/lib/dhcp/option6_ia.cc
+++ b/src/lib/dhcp/option6_ia.cc
@@ -44,7 +44,7 @@ void Option6IA::pack(isc::util::OutputBuffer& buf) {
     buf.writeUint32(t1_);
     buf.writeUint32(t2_);
 
-    LibDHCP::packOptions6(buf, options_);
+    packOptions(buf);
 }
 
 void Option6IA::unpack(OptionBufferConstIter begin,
@@ -62,7 +62,7 @@ void Option6IA::unpack(OptionBufferConstIter begin,
     t2_ = readUint32( &(*begin) );
     begin += sizeof(uint32_t);
 
-    LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+    unpackOptions(OptionBuffer(begin, end));
 }
 
 std::string Option6IA::toText(int indent /* = 0*/) {
diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc
index b503a91..8db5047 100644
--- a/src/lib/dhcp/option6_iaaddr.cc
+++ b/src/lib/dhcp/option6_iaaddr.cc
@@ -59,7 +59,7 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) {
     buf.writeUint32(valid_);
 
     // parse suboption (there shouldn't be any for IAADDR)
-    LibDHCP::packOptions6(buf, options_);
+    packOptions(buf);
 }
 
 void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
@@ -77,7 +77,8 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
 
     valid_ = readUint32( &(*begin) );
     begin += sizeof(uint32_t);
-    LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+
+    unpackOptions(OptionBuffer(begin, end));
 }
 
 std::string Option6IAAddr::toText(int indent /* =0 */) {
diff --git a/src/lib/dhcp/option6_int.h b/src/lib/dhcp/option6_int.h
index 9b116e0..d61509b 100644
--- a/src/lib/dhcp/option6_int.h
+++ b/src/lib/dhcp/option6_int.h
@@ -89,7 +89,7 @@ public:
         // Depending on the data type length we use different utility functions
         // writeUint16 or writeUint32 which write the data in the network byte
         // order to the provided buffer. The same functions can be safely used
-        // for either unsiged or signed integers so there is not need to create
+        // for either unsigned or signed integers so there is not need to create
         // special cases for intX_t types.
         switch (OptionDataTypeTraits<T>::len) {
         case 1:
@@ -104,7 +104,7 @@ public:
         default:
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
-        LibDHCP::packOptions6(buf, options_);
+        packOptions(buf);
     }
 
     /// @brief Parses received buffer
@@ -128,7 +128,7 @@ public:
         // Depending on the data type length we use different utility functions
         // readUint16 or readUint32 which read the data laid in the network byte
         // order from the provided buffer. The same functions can be safely used
-        // for either unsiged or signed integers so there is not need to create
+        // for either unsigned or signed integers so there is not need to create
         // special cases for intX_t types.
         int data_size_len = OptionDataTypeTraits<T>::len;
         switch (data_size_len) {
@@ -149,7 +149,7 @@ public:
         // of clang complain about unresolved reference to
         // OptionDataTypeTraits structure during linking.
         begin += data_size_len;
-        LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+        unpackOptions(OptionBuffer(begin, end));
     }
 
     /// @brief Set option value.
diff --git a/src/lib/dhcp/option6_int_array.h b/src/lib/dhcp/option6_int_array.h
index c37c107..3b64213 100644
--- a/src/lib/dhcp/option6_int_array.h
+++ b/src/lib/dhcp/option6_int_array.h
@@ -118,7 +118,7 @@ public:
             // Depending on the data type length we use different utility functions
             // writeUint16 or writeUint32 which write the data in the network byte
             // order to the provided buffer. The same functions can be safely used
-            // for either unsiged or signed integers so there is not need to create
+            // for either unsigned or signed integers so there is not need to create
             // special cases for intX_t types.
             switch (OptionDataTypeTraits<T>::len) {
             case 1:
@@ -164,7 +164,7 @@ public:
             // Depending on the data type length we use different utility functions
             // readUint16 or readUint32 which read the data laid in the network byte
             // order from the provided buffer. The same functions can be safely used
-            // for either unsiged or signed integers so there is not need to create
+            // for either unsigned or signed integers so there is not need to create
             // special cases for intX_t types.
             int data_size_len = OptionDataTypeTraits<T>::len;
             switch (data_size_len) {
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
new file mode 100644
index 0000000..8b3ef11
--- /dev/null
+++ b/src/lib/dhcp/option_custom.cc
@@ -0,0 +1,370 @@
+// Copyright (C) 2012 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/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_custom.h>
+#include <util/encode/hex.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+                             Universe u,
+                             const OptionBuffer& data)
+    : Option(u, def.getCode(), data.begin(), data.end()),
+      definition_(def) {
+    createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+                             Universe u,
+                             OptionBufferConstIter first,
+                             OptionBufferConstIter last)
+    : Option(u, def.getCode(), first, last),
+      definition_(def) {
+    createBuffers();
+}
+
+void
+OptionCustom::checkIndex(const uint32_t index) const {
+    if (index >= buffers_.size()) {
+        isc_throw(isc::OutOfRange, "specified data field index " << index
+                  << " is out of rangex.");
+    }
+}
+
+void
+OptionCustom::createBuffers() {
+    // Check that the option definition is correct as we are going
+    // to use it to split the data_ buffer into set of sub buffers.
+    definition_.validate();
+
+    std::vector<OptionBuffer> buffers;
+    OptionBuffer::iterator data = data_.begin();
+
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        // An option comprises a record of data fields. We need to
+        // get types of these data fields to allocate enough space
+        // for each buffer.
+        const OptionDefinition::RecordFieldsCollection& fields =
+            definition_.getRecordFields();
+
+        // Go over all data fields within a record.
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            // For fixed-size data type such as boolean, integer, even
+            // IP address we can use the utility function to get the required
+            // buffer size.
+            int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            // For variable size types (such as string) the function above
+            // will return 0 so we need to do a runtime check. Since variable
+            // length data fields may be laid only at the end of an option we
+            // consume the rest of this option. Note that validate() function
+            // in OptionDefinition object should have checked whether the
+            // data fields layout is correct (that the variable string fields
+            // are laid at the end).
+            if (data_size == 0) {
+                data_size = std::distance(data, data_.end());
+                if (data_size == 0) {
+                    // If we reached the end of buffer we assume that this option is
+                    // truncated because there is no remaining data to initialize
+                    // an option field.
+                    if (data_size == 0) {
+                        isc_throw(OutOfRange, "option buffer truncated");
+                    }
+                }
+            } else {
+                // Our data field requires that there is a certain chunk of
+                // data left in the buffer. If not, option is truncated.
+                if (std::distance(data, data_.end()) < data_size) {
+                    isc_throw(OutOfRange, "option buffer truncated");
+                }
+            }
+            // Store the created buffer.
+            buffers.push_back(OptionBuffer(data, data + data_size));
+            // Proceed to the next data field.
+            data += data_size;
+        }
+    } else if (data_type != OPT_EMPTY_TYPE) {
+        // If data_type value is other than OPT_RECORD_TYPE, our option is
+        // empty (have no data at all) or it comprises one or more
+        // data fields of the same type. The type of those fields
+        // is held in the data_type variable so let's use it to determine
+        // a size of buffers.
+        int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+        // The check below will fail if the input buffer is too short
+        // for the data size being held by this option.
+        // Note that data_size returned by getDataTypeLen may be zero
+        // if variable length data is being held by the option but
+        // this will not cause this check to throw exception.
+        if (std::distance(data, data_.end()) < data_size) {
+            isc_throw(OutOfRange, "option buffer truncated");
+        }
+        // For an array of values we are taking different path because
+        // we have to handle multiple buffers.
+        if (definition_.getArrayType()) {
+            // We don't perform other checks for data types that can't be
+            // used together with array indicator such as strings, empty field
+            // etc. This is because OptionDefinition::validate function should
+            // have checked this already. Thus data_size must be greater than
+            // zero.
+            assert(data_size > 0);
+            // Get equal chunks of data and store as collection of buffers.
+            // Truncate any remaining part which length is not divisible by
+            // data_size. Note that it is ok to truncate the data if and only
+            // if the data buffer is long enough to keep at least one value.
+            // This has been checked above already.
+            do {
+                buffers.push_back(OptionBuffer(data, data + data_size));
+                data += data_size;
+            } while (std::distance(data, data_.end()) >= data_size);
+        } else {
+            // For non-arrays the data_size can be zero because
+            // getDataTypeLen returns zero for variable size data types
+            // such as strings. Simply take whole buffer.
+            if (data_size == 0) {
+                data_size = std::distance(data, data_.end());
+            }
+            if (data_size > 0) {
+                buffers.push_back(OptionBuffer(data, data + data_size));
+            } else {
+                isc_throw(OutOfRange, "option buffer truncated");
+            }
+        }
+    }
+    // If everything went ok we can replace old buffer set with new ones.
+    std::swap(buffers_, buffers);
+}
+
+std::string
+OptionCustom::dataFieldToText(const OptionDataType data_type,
+                              const uint32_t index) const {
+    std::ostringstream text;
+
+    // Get the value of the data field.
+    switch (data_type) {
+    case OPT_BINARY_TYPE:
+        text << util::encode::encodeHex(readBinary(index));
+        break;
+    case OPT_BOOLEAN_TYPE:
+        text << (readBoolean(index) ? "true" : "false");
+        break;
+    case OPT_INT8_TYPE:
+        text << readInteger<int8_t>(index);
+        break;
+    case OPT_INT16_TYPE:
+        text << readInteger<int16_t>(index);
+        break;
+    case OPT_INT32_TYPE:
+        text << readInteger<int32_t>(index);
+        break;
+    case OPT_UINT8_TYPE:
+        text << readInteger<uint8_t>(index);
+        break;
+    case OPT_UINT16_TYPE:
+        text << readInteger<uint16_t>(index);
+        break;
+    case OPT_UINT32_TYPE:
+        text << readInteger<uint32_t>(index);
+        break;
+    case OPT_IPV4_ADDRESS_TYPE:
+    case OPT_IPV6_ADDRESS_TYPE:
+        text << readAddress(index).toText();
+        break;
+    case OPT_STRING_TYPE:
+        text << readString(index);
+        break;
+    default:
+        ;
+    }
+
+    // Append data field type in brackets.
+    text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) ";
+
+    return (text.str());
+}
+
+void
+OptionCustom::pack4(isc::util::OutputBuffer& buf) {
+    if (len() > 255) {
+        isc_throw(OutOfRange, "DHCPv4 Option " << type_
+                  << " value is too high. At most 255 is supported.");
+    }
+
+    buf.writeUint8(type_);
+    buf.writeUint8(len() - getHeaderLen());
+
+    // Write data from buffers.
+    for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+         it != buffers_.end(); ++it) {
+        // In theory the createBuffers function should have taken
+        // care that there are no empty buffers added to the
+        // collection but it is almost always good to make sure.
+        if (!it->empty()) {
+            buf.writeData(&(*it)[0], it->size());
+        }
+    }
+
+    // Write suboptions.
+    packOptions(buf);
+}
+
+void
+OptionCustom::pack6(isc::util::OutputBuffer& buf) {
+    buf.writeUint16(type_);
+    buf.writeUint16(len() - getHeaderLen());
+
+    // Write data from buffers.
+    for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+         it != buffers_.end(); ++it) {
+        if (!it->empty()) {
+            buf.writeData(&(*it)[0], it->size());
+        }
+    }
+
+    packOptions(buf);
+}
+
+asiolink::IOAddress
+OptionCustom::readAddress(const uint32_t index) const {
+    checkIndex(index);
+
+    // The address being read can be either IPv4 or IPv6. The decision
+    // is made based on the buffer length. If it holds 4 bytes it is IPv4
+    // address, if it holds 16 bytes it is IPv6.
+    if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
+        return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
+    } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
+        return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
+    } else {
+        isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                  << " IP address. Invalid buffer length " << buffers_[index].size());
+    }
+}
+
+const OptionBuffer&
+OptionCustom::readBinary(const uint32_t index) const {
+    checkIndex(index);
+    return (buffers_[index]);
+}
+
+bool
+OptionCustom::readBoolean(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readBool(buffers_[index]));
+}
+
+std::string
+OptionCustom::readString(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readString(buffers_[index]));
+}
+
+void
+OptionCustom::unpack(OptionBufferConstIter begin,
+                     OptionBufferConstIter end) {
+    data_ = OptionBuffer(begin, end);
+    // Chop the buffer stored in data_ into set of sub buffers.
+    createBuffers();
+}
+
+uint16_t
+OptionCustom::len() {
+    // The length of the option is a sum of option header ...
+    int length = getHeaderLen();
+
+    // ... lengths of all buffers that hold option data ...
+    for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
+         buf != buffers_.end(); ++buf) {
+        length += buf->size();
+    }
+
+    // ... and lengths of all suboptions
+    for (OptionCustom::OptionCollection::iterator it = options_.begin();
+         it != options_.end();
+         ++it) {
+        length += (*it).second->len();
+    }
+
+    return (length);
+}
+
+void OptionCustom::setData(const OptionBufferConstIter first,
+                     const OptionBufferConstIter last) {
+    // We will copy entire option buffer, so we have to resize data_.
+    data_.resize(std::distance(first, last));
+    std::copy(first, last, data_.begin());
+
+    // Chop the data_ buffer into set of buffers that represent
+    // option fields data.
+    createBuffers();
+}
+
+std::string OptionCustom::toText(int indent) {
+    std::stringstream tmp;
+
+    for (int i = 0; i < indent; ++i)
+        tmp << " ";
+
+    tmp << "type=" << type_ << ", len=" << len()-getHeaderLen()
+        << ", data fields:" << std::endl;
+
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        const OptionDefinition::RecordFieldsCollection& fields =
+            definition_.getRecordFields();
+
+        // For record types we iterate over fields defined in
+        // option definition and match the appropriate buffer
+        // with them.
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            for (int j = 0; j < indent + 2; ++j) {
+                tmp << " ";
+            }
+            tmp << "#" << std::distance(fields.begin(), field) << " "
+                << dataFieldToText(*field, std::distance(fields.begin(),
+                                                         field))
+                << std::endl;
+        }
+    } else {
+        // For non-record types we iterate over all buffers
+        // and print the data type set globally for an option
+        // definition. We take the same code path for arrays
+        // and non-arrays as they only differ in such a way that
+        // non-arrays have just single data field.
+        for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
+            for (int j = 0; j < indent + 2; ++j) {
+                tmp << " ";
+            }
+            tmp << "#" << i << " "
+                << dataFieldToText(definition_.getType(), i)
+                << std::endl;
+        }
+    }
+
+    // print suboptions
+    for (OptionCollection::const_iterator opt = options_.begin();
+         opt != options_.end();
+         ++opt) {
+        tmp << (*opt).second->toText(indent+2);
+    }
+    return tmp.str();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
new file mode 100644
index 0000000..ad248c5
--- /dev/null
+++ b/src/lib/dhcp/option_custom.h
@@ -0,0 +1,235 @@
+// Copyright (C) 2012 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 OPTION_CUSTOM_H
+#define OPTION_CUSTOM_H
+
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <util/io_utilities.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Option with defined data fields represented as buffers that can
+/// be accessed using data field index.
+///
+/// This class represents an option which has defined structure: data fields
+/// of specific types and order. Those fields can be accessed using indexes,
+/// where index 0 represents first data field within an option. The last
+/// field can be accessed using index equal to 'number of fields' - 1.
+/// Internally, the option data is stored as a collection of OptionBuffer
+/// objects, each representing data for a particular data field. This data
+/// can be converted to the actual data type using methods implemented
+/// within this class. This class is used to represent those options that
+/// can't be represented by any other specialized class (this excludes the
+/// Option class which is generic and can be used to represent any option).
+class OptionCustom : public Option {
+public:
+
+    /// @brief Constructor, used for options to be sent.
+    ///
+    /// This constructor creates an instance of an option from the whole
+    /// supplied buffer. This constructor is mainly used to create an
+    /// instances of options to be stored in outgoing DHCP packets.
+    /// The buffer used to create the instance of an option can be
+    /// created from the option data specified in server's configuration.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6).
+    /// @param data content of the option.
+    ///
+    /// @throw OutOfRange if option buffer is truncated.
+    ///
+    /// @todo list all exceptions thrown by ctor.
+    OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data);
+
+    /// @brief Constructor, used for received options.
+    ///
+    /// This constructor creates an instance an option from the portion
+    /// of the buffer specified by iterators. This is mainly useful when
+    /// parsing received packets. Such packets are represented by a single
+    /// buffer holding option data and all sub options. Methods that are
+    /// parsing a packet, supply relevant portions of the packet buffer
+    /// to this constructor to create option instances out of it.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6).
+    /// @param first iterator to the first element that should be copied.
+    /// @param last iterator to the next element after the last one
+    /// to be copied.
+    ///
+    /// @throw OutOfRange if option buffer is truncated.
+    ///
+    /// @todo list all exceptions thrown by ctor.
+    OptionCustom(const OptionDefinition& def, Universe u,
+                 OptionBufferConstIter first, OptionBufferConstIter last);
+
+    /// @brief Return a number of the data fields.
+    ///
+    /// @return number of data fields held by the option.
+    uint32_t getDataFieldsNum() const { return (buffers_.size()); }
+
+    /// @brief Read a buffer as IP address.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @return IP address read from a buffer.
+    /// @throw isc::OutOfRange if index is out of range.
+    asiolink::IOAddress readAddress(const uint32_t index) const;
+
+    /// @brief Read a buffer as binary data.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @return read buffer holding binary data.
+    const OptionBuffer& readBinary(const uint32_t index) const;
+
+    /// @brief Read a buffer as boolean value.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @return read boolean value.
+    bool readBoolean(const uint32_t index) const;
+
+    /// @brief Read a buffer as integer value.
+    ///
+    /// @param index buffer index.
+    /// @tparam integer type of a value being returned.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @return read integer value.
+    template<typename T>
+    T readInteger(const uint32_t index) const {
+        checkIndex(index);
+
+        // Check that the requested return type is a supported integer.
+        if (!OptionDataTypeTraits<T>::integer_type) {
+            isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
+                      " by readInteger is not supported integer type");
+        }
+
+        // Get the option definition type.
+        OptionDataType data_type = definition_.getType();
+        if (data_type == OPT_RECORD_TYPE) {
+            const OptionDefinition::RecordFieldsCollection& record_fields =
+                definition_.getRecordFields();
+            // When we initialized buffers we have already checked that
+            // the number of these buffers is equal to number of option
+            // fields in the record so the condition below should be met.
+            assert(index < record_fields.size());
+            // Get the data type to be returned.
+            data_type = record_fields[index];
+        }
+
+        // Requested data type must match the data type in a record.
+        if (OptionDataTypeTraits<T>::type != data_type) {
+            isc_throw(isc::dhcp::InvalidDataType,
+                      "unable to read option field with index " << index
+                      << " as integer value. The field's data type"
+                      << data_type << " does not match the integer type"
+                      << "returned by the readInteger function.");
+        }
+        // When we created the buffer we have checked that it has a
+        // valid size so this condition here should be always fulfiled.
+        assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
+        // Read an integer value.
+        return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
+    }
+
+    /// @brief Read a buffer as string value.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @return string value read from buffer.
+    /// @throw isc::OutOfRange if index is out of range.
+    std::string readString(const uint32_t index) const;
+
+    /// @brief Parses received buffer.
+    ///
+    /// @param begin iterator to first byte of option data
+    /// @param end iterator to end of option data (first byte after option end)
+    virtual void unpack(OptionBufferConstIter begin,
+                        OptionBufferConstIter end);
+
+    /// @brief Returns string representation of the option.
+    ///
+    /// @param indent number of spaces before printed text.
+    ///
+    /// @return string with text representation.
+    virtual std::string toText(int indent = 0);
+
+    /// @brief Returns length of the complete option (data length +
+    ///        DHCPv4/DHCPv6 option header)
+    ///
+    /// @return length of the option
+    virtual uint16_t len();
+
+    /// @brief Sets content of this option from buffer.
+    ///
+    /// Option will be resized to length of buffer.
+    ///
+    /// @param first iterator pointing begining of buffer to copy.
+    /// @param last iterator pointing to end of buffer to copy.
+    void setData(const OptionBufferConstIter first,
+                 const OptionBufferConstIter last);
+
+protected:
+
+    /// @brief Writes DHCPv4 option in a wire format to a buffer.
+    ///
+    /// @param buf output buffer (option will be stored there).
+    virtual void pack4(isc::util::OutputBuffer& buf);
+
+    /// @brief Writes DHCPv6 option in a wire format to a buffer.
+    ///
+    /// @param buf output buffer (built options will be stored here)
+    virtual void pack6(isc::util::OutputBuffer& buf);
+
+private:
+
+    /// @brief Check if data field index is valid.
+    ///
+    /// @param index Data field index to check.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void checkIndex(const uint32_t index) const;
+
+    /// @brief Create collection of buffers representing data field values.
+    void createBuffers();
+
+    /// @brief Return a text representation of a data field.
+    ///
+    /// @param data_type data type of a field.
+    /// @param index data field buffer index within a custom option.
+    ///
+    /// @return text representation of a data field.
+    std::string dataFieldToText(const OptionDataType data_type,
+                                const uint32_t index) const;
+
+    /// Option definition used to create an option.
+    OptionDefinition definition_;
+
+    /// The collection of buffers holding data for option fields.
+    /// The order of buffers corresponds to the order of option
+    /// fields.
+    std::vector<OptionBuffer> buffers_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_CUSTOM_H
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
new file mode 100644
index 0000000..46aa663
--- /dev/null
+++ b/src/lib/dhcp/option_data_types.cc
@@ -0,0 +1,227 @@
+// Copyright (C) 2012 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/option_data_types.h>
+#include <util/encode/hex.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionDataTypeUtil::OptionDataTypeUtil() {
+    data_types_["empty"] = OPT_EMPTY_TYPE;
+    data_types_["binary"] = OPT_BINARY_TYPE;
+    data_types_["boolean"] = OPT_BOOLEAN_TYPE;
+    data_types_["int8"] = OPT_INT8_TYPE;
+    data_types_["int16"] = OPT_INT16_TYPE;
+    data_types_["int32"] = OPT_INT32_TYPE;
+    data_types_["uint8"] = OPT_UINT8_TYPE;
+    data_types_["uint16"] = OPT_UINT16_TYPE;
+    data_types_["uint32"] = OPT_UINT32_TYPE;
+    data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
+    data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+    data_types_["string"] = OPT_STRING_TYPE;
+    data_types_["fqdn"] = OPT_FQDN_TYPE;
+    data_types_["record"] = OPT_RECORD_TYPE;
+
+    data_type_names_[OPT_EMPTY_TYPE] = "empty";
+    data_type_names_[OPT_BINARY_TYPE] = "binary";
+    data_type_names_[OPT_BOOLEAN_TYPE] = "boolean";
+    data_type_names_[OPT_INT8_TYPE] = "int8";
+    data_type_names_[OPT_INT16_TYPE] = "int16";
+    data_type_names_[OPT_INT32_TYPE] = "int32";
+    data_type_names_[OPT_UINT8_TYPE] = "uint8";
+    data_type_names_[OPT_UINT16_TYPE] = "uint16";
+    data_type_names_[OPT_UINT32_TYPE] = "uint32";
+    data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
+    data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+    data_type_names_[OPT_STRING_TYPE] = "string";
+    data_type_names_[OPT_FQDN_TYPE] = "fqdn";
+    data_type_names_[OPT_RECORD_TYPE] = "record";
+    // The "unknown" data type is declared here so as
+    // it can be returned by reference by a getDataTypeName
+    // function it no other type is suitable. Other than that
+    // this is unused.
+    data_type_names_[OPT_UNKNOWN_TYPE] = "unknown";
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataType(const std::string& data_type) {
+    return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type));
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const {
+    std::map<std::string, OptionDataType>::const_iterator data_type_it =
+        data_types_.find(data_type);
+    if (data_type_it != data_types_.end()) {
+        return (data_type_it->second);
+    }
+    return (OPT_UNKNOWN_TYPE);
+}
+
+int
+OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
+    switch (data_type) {
+    case OPT_BOOLEAN_TYPE:
+    case OPT_INT8_TYPE:
+    case OPT_UINT8_TYPE:
+        return (1);
+
+    case OPT_INT16_TYPE:
+    case OPT_UINT16_TYPE:
+        return (2);
+
+    case OPT_INT32_TYPE:
+    case OPT_UINT32_TYPE:
+        return (4);
+
+    case OPT_IPV4_ADDRESS_TYPE:
+        return (asiolink::V4ADDRESS_LEN);
+
+    case OPT_IPV6_ADDRESS_TYPE:
+        return (asiolink::V6ADDRESS_LEN);
+
+    default:
+        ;
+    }
+    return (0);
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) {
+    return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type));
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const {
+    std::map<OptionDataType, std::string>::const_iterator data_type_it =
+        data_type_names_.find(data_type);
+    if (data_type_it != data_type_names_.end()) {
+        return (data_type_it->second);
+    }
+    return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second);
+}
+
+OptionDataTypeUtil&
+OptionDataTypeUtil::instance() {
+    static OptionDataTypeUtil instance;
+    return (instance);
+}
+
+asiolink::IOAddress
+OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
+                                const short family) {
+    using namespace isc::asiolink;
+    if (family == AF_INET) {
+        if (buf.size() < V4ADDRESS_LEN) {
+            isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                      << " IPv4 address. Invalid buffer size: " << buf.size());
+        }
+        return (IOAddress::fromBytes(AF_INET, &buf[0]));
+    } else if (family == AF_INET6) {
+        if (buf.size() < V6ADDRESS_LEN) {
+            isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                      << " IPv6 address. Invalid buffer size: " << buf.size());
+        }
+        return (IOAddress::fromBytes(AF_INET6, &buf[0]));
+    } else {
+        isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                  "IP address. Invalid family: " << family);
+    }
+}
+
+void
+OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address,
+                                 std::vector<uint8_t>& buf) {
+    // @todo There is a ticket 2396 submitted, which adds the
+    // functionality to return a buffer representation of
+    // IOAddress. If so, this function can be simplified.
+    if (address.getAddress().is_v4()) {
+        asio::ip::address_v4::bytes_type addr_bytes =
+            address.getAddress().to_v4().to_bytes();
+        // Increase the buffer size by the size of IPv4 address.
+        buf.resize(buf.size() + addr_bytes.size());
+        std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
+                           buf.end());
+    } else if (address.getAddress().is_v6()) {
+        asio::ip::address_v6::bytes_type addr_bytes =
+            address.getAddress().to_v6().to_bytes();
+        // Incresase the buffer size by the size of IPv6 address.
+        buf.resize(buf.size() + addr_bytes.size());
+        std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
+                           buf.end());
+    } else {
+        isc_throw(BadDataTypeCast, "the address " << address.toText()
+                  << " is neither valid IPv4 not IPv6 address.");
+    }
+}
+
+void
+OptionDataTypeUtil::writeBinary(const std::string& hex_str,
+                                std::vector<uint8_t>& buf) {
+    // Binary value means that the value is encoded as a string
+    // of hexadecimal digits. We need to decode this string
+    // to the binary format here.
+    OptionBuffer binary;
+    try {
+        util::encode::decodeHex(hex_str, binary);
+    } catch (const Exception& ex) {
+        isc_throw(BadDataTypeCast, "unable to cast " << hex_str
+                  << " to binary data type: " << ex.what());
+    }
+    // Decode was successful so append decoded binary value
+    // to the buffer.
+    buf.insert(buf.end(), binary.begin(), binary.end());
+}
+
+bool
+OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
+    if (buf.size() < 1) {
+        isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+                  << " value. Invalid buffer size " << buf.size());
+    }
+    if (buf[0] == 1) {
+        return (true);
+    } else if (buf[0] == 0) {
+        return (false);
+    }
+    isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+              << " value. Invalid value " << static_cast<int>(buf[0]));
+}
+
+void
+OptionDataTypeUtil::writeBool(const bool value,
+                              std::vector<uint8_t>& buf) {
+    buf.push_back(static_cast<uint8_t>(value ? 1 : 0));
+}
+
+std::string
+OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
+    std::string value;
+    if (buf.size() > 0) {
+        value.insert(value.end(), buf.begin(), buf.end());
+    }
+    return (value);
+}
+
+void
+OptionDataTypeUtil::writeString(const std::string& value,
+                                std::vector<uint8_t>& buf) {
+    if (value.size() > 0) {
+        buf.insert(buf.end(), value.begin(), value.end());
+    }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index 99b4220..afc6d93 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -16,7 +16,9 @@
 #define OPTION_DATA_TYPES_H
 
 #include <asiolink/io_address.h>
+#include <dhcp/option.h>
 #include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
 
 #include <stdint.h>
 
@@ -39,6 +41,13 @@ public:
 
 
 /// @brief Data types of DHCP option fields.
+///
+/// @warning The order of data types matters: OPT_UNKNOWN_TYPE
+/// must always be the last position. Also, OPT_RECORD_TYPE
+/// must be at last but one position. This is because some
+/// functions perform sanity checks on data type values using
+/// '>' operators, assuming that all values beyond the
+/// OPT_RECORD_TYPE are invalid.
 enum OptionDataType {
     OPT_EMPTY_TYPE,
     OPT_BINARY_TYPE,
@@ -75,7 +84,7 @@ struct OptionDataTypeTraits {
 template<>
 struct OptionDataTypeTraits<OptionBuffer> {
     static const bool valid = true;
-    static const int len = sizeof(OptionBuffer);
+    static const int len = 0;
     static const bool integer_type = false;
     static const OptionDataType type = OPT_BINARY_TYPE;
 };
@@ -172,6 +181,210 @@ struct OptionDataTypeTraits<std::string> {
     static const OptionDataType type = OPT_STRING_TYPE;
 };
 
+/// @brief Utility class for option data types.
+///
+/// This class provides a set of utility functions to operate on
+/// supported DHCP option data types. It includes conversion
+/// between enumerator values representing data types and data
+/// type names. It also includes a set of functions that write
+/// data into option buffers and read data from option buffers.
+/// The data being written and read are converted from/to actual
+/// data types.
+/// @note This is a singleton class but it can be accessed via
+/// static methods only.
+class OptionDataTypeUtil {
+public:
+
+    /// @brief Return option data type from its name.
+    ///
+    /// @param data_type data type name.
+    /// @return option data type.
+    static OptionDataType getDataType(const std::string& data_type);
+
+    /// @brief Return option data type name from the data type enumerator.
+    ///
+    /// @param data_type option data type.
+    /// @return option data type name.
+    static const std::string& getDataTypeName(const OptionDataType data_type);
+
+    /// @brief Get data type buffer length.
+    ///
+    /// This function returns the size of a particular data type.
+    /// Values retured by this function correspond to the data type
+    /// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and
+    /// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate
+    /// the fixed length of the data being written into the buffer,
+    /// not the size of the particular data type. Thus for data types
+    /// such as string, binary etc. for which the buffer length can't
+    /// be determined this function returns 0.
+    /// In addition, this function returns the data sizes for
+    /// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer
+    /// representations have fixed data lengths: 4 and 16 respectively.
+    ///
+    /// @param data_type data type which size is to be returned.
+    /// @return data type size or zero for variable length types.
+    static int getDataTypeLen(const OptionDataType data_type);
+
+    /// @brief Read IPv4 or IPv6 addres from a buffer.
+    ///
+    /// @param buf input buffer.
+    /// @param family address family: AF_INET or AF_INET6.
+    /// 
+    /// @return address being read.
+    static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
+                                           const short family);
+
+    /// @brief Append IPv4 or IPv6 address to a buffer.
+    ///
+    /// @param address IPv4 or IPv6 address.
+    /// @param [out] buf output buffer.
+    static void writeAddress(const asiolink::IOAddress& address,
+                             std::vector<uint8_t>& buf);
+
+    /// @brief Append hex-encoded binary values to a buffer.
+    ///
+    /// @param hex_str string representing a binary value encoded
+    /// with hexadecimal digits (without 0x prefix).
+    /// @param [out] buf output buffer.
+    static void writeBinary(const std::string& hex_str,
+                            std::vector<uint8_t>& buf);
+
+    /// @brief Read boolean value from a buffer.
+    ///
+    /// @param buf input buffer.
+    /// @return boolean value read from a buffer.
+    static bool readBool(const std::vector<uint8_t>& buf);
+
+    /// @brief Append boolean value into a buffer.
+    ///
+    /// The bool value is encoded in a buffer in such a way that
+    /// "1" means "true" and "0" means "false".
+    ///
+    /// @param value boolean value to be written.
+    /// @param [out] buf output buffer.
+    static void writeBool(const bool value, std::vector<uint8_t>& buf);
+
+    /// @brief Read integer value from a buffer.
+    ///
+    /// @param buf input buffer.
+    /// @tparam integer type of the returned value.
+    /// @return integer value being read.
+    template<typename T>
+    static T readInt(const std::vector<uint8_t>& buf) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
+            isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
+                      " by readInteger is unsupported integer type");
+        }
+
+        assert(buf.size() == OptionDataTypeTraits<T>::len);
+        T value;
+        switch (OptionDataTypeTraits<T>::len) {
+        case 1:
+            value = *(buf.begin());
+            break;
+        case 2:
+            // Calling readUint16 works either for unsigned
+            // or signed types.
+            value = isc::util::readUint16(&(*buf.begin()));
+            break;
+        case 4:
+            // Calling readUint32 works either for unsigned
+            // or signed types.
+            value = isc::util::readUint32(&(*buf.begin()));
+            break;
+        default:
+            // This should not happen because we made checks on data types
+            // but it does not hurt to keep throw statement here.
+            isc_throw(isc::dhcp::InvalidDataType,
+                      "invalid size of the data type to be read as integer.");
+        }
+        return (value);
+    }
+
+    /// @brief Append integer or unsigned integer value to a buffer.
+    ///
+    /// @param value an integer value to be written into a buffer.
+    /// @param [out] buf output buffer.
+    /// @tparam data type of the value.
+    template<typename T>
+    static void writeInt(const T value,
+                         std::vector<uint8_t>& buf) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
+            isc_throw(InvalidDataType, "provided data type is not the supported.");
+        }
+        switch (OptionDataTypeTraits<T>::len) {
+        case 1:
+            buf.push_back(static_cast<uint8_t>(value));
+            break;
+        case 2:
+            buf.resize(buf.size() + 2);
+            isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2]);
+            break;
+        case 4:
+            buf.resize(buf.size() + 4);
+            isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4]);
+            break;
+        default:
+            // The cases above cover whole range of possible data lengths because
+            // we check at the beginning of this function that given data type is
+            // a supported integer type which can be only 1,2 or 4 bytes long.
+            ;
+        }
+    }
+
+    /// @brief Read string value from a buffer.
+    ///
+    /// @param buf input buffer.
+    ///
+    /// @return string value being read.
+    static std::string readString(const std::vector<uint8_t>& buf);
+
+    /// @brief Write UTF8-encoded string into a buffer.
+    ///
+    /// @param value string value to be written into a buffer.
+    /// @param [out] buf output buffer.
+    static void writeString(const std::string& value,
+                            std::vector<uint8_t>& buf);
+private:
+
+    /// The container holding mapping of data type names to
+    /// data types enumerator.
+    std::map<std::string, OptionDataType> data_types_;
+
+    /// The container holding mapping of data types to data
+    /// type names.
+    std::map<OptionDataType, std::string> data_type_names_;
+
+    /// @brief Private constructor.
+    ///
+    /// This constructor is private because this class should
+    /// be used as singleton (through static public functions).
+    OptionDataTypeUtil();
+
+    /// @brief Return instance of OptionDataTypeUtil
+    ///
+    /// This function is used by some of the public static functions
+    /// to create an instance of OptionDataTypeUtil class.
+    /// When instance is called it calls the class'es constructor
+    /// and initializes some of the private data members.
+    ///
+    /// @return instance of OptionDataTypeUtil singleton.
+    static OptionDataTypeUtil& instance();
+
+    /// @brief Return option data type from its name.
+    ///
+    /// @param data_type data type name.
+    /// @return option data type.
+    OptionDataType getDataTypeImpl(const std::string& data_type) const;
+
+    /// @brief Return option data type name from the data type enumerator.
+    ///
+    /// @param data_type option data type.
+    /// @return option data type name.
+    const std::string& getDataTypeNameImpl(const OptionDataType data_type) const;
+};
+
+
 } // isc::dhcp namespace
 } // isc namespace
 
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index 62a81c2..58d0c4b 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -28,214 +28,6 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-OptionDefinition::DataTypeUtil::DataTypeUtil() {
-    data_types_["empty"] = OPT_EMPTY_TYPE;
-    data_types_["binary"] = OPT_BINARY_TYPE;
-    data_types_["boolean"] = OPT_BOOLEAN_TYPE;
-    data_types_["int8"] = OPT_INT8_TYPE;
-    data_types_["int16"] = OPT_INT16_TYPE;
-    data_types_["int32"] = OPT_INT32_TYPE;
-    data_types_["uint8"] = OPT_UINT8_TYPE;
-    data_types_["uint16"] = OPT_UINT16_TYPE;
-    data_types_["uint32"] = OPT_UINT32_TYPE;
-    data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
-    data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
-    data_types_["string"] = OPT_STRING_TYPE;
-    data_types_["fqdn"] = OPT_FQDN_TYPE;
-    data_types_["record"] = OPT_RECORD_TYPE;
-}
-
-OptionDataType
-OptionDefinition::DataTypeUtil::getOptionDataType(const std::string& data_type) {
-    std::map<std::string, OptionDataType>::const_iterator data_type_it =
-        data_types_.find(data_type);
-    if (data_type_it != data_types_.end()) {
-        return (data_type_it->second);
-    }
-    return (OPT_UNKNOWN_TYPE);
-}
-
-template<typename T>
-T OptionDefinition::DataTypeUtil::lexicalCastWithRangeCheck(const std::string& value_str) const {
-    // Lexical cast in case of our data types make sense only
-    // for uintX_t, intX_t and bool type.
-    if (!OptionDataTypeTraits<T>::integer_type &&
-        OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
-        isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
-                  << " non-boolean data type");
-    }
-    // We use the 64-bit value here because it has wider range than
-    // any other type we use here and it allows to detect out of
-    // bounds conditions e.g. negative value specified for uintX_t
-    // data type. Obviously if the value exceeds the limits of int64
-    // this function will not handle that properly.
-    int64_t result = 0;
-    try {
-        result = boost::lexical_cast<int64_t>(value_str);
-    } catch (const boost::bad_lexical_cast& ex) {
-        // Prepare error message here.
-        std::string data_type_str = "boolean";
-        if (OptionDataTypeTraits<T>::integer_type) {
-            data_type_str = "integer";
-        }
-        isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
-                  << " data type for value " << value_str << ": " << ex.what());
-    }
-    // Perform range checks for integer values only (exclude bool values).
-    if (OptionDataTypeTraits<T>::integer_type) {
-        if (result > numeric_limits<T>::max() ||
-            result < numeric_limits<T>::min()) {
-            isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
-                      << value_str << ". This value is expected to be in the range of "
-                      << numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
-        }
-    }
-    return (static_cast<T>(result));
-}
-
-void
-OptionDefinition::DataTypeUtil::writeToBuffer(const std::string& value,
-                                              const OptionDataType type,
-                                              OptionBuffer& buf) {
-    // We are going to write value given by value argument to the buffer.
-    // The actual type of the value is given by second argument. Check
-    // this argument to determine how to write this value to the buffer.
-    switch (type) {
-    case OPT_BINARY_TYPE:
-        {
-            // Binary value means that the value is encoded as a string
-            // of hexadecimal deigits. We need to decode this string
-            // to the binary format here.
-            OptionBuffer binary;
-            try {
-                util::encode::decodeHex(value, binary);
-            } catch (const Exception& ex) {
-                isc_throw(BadDataTypeCast, "unable to cast " << value
-                          << " to binary data type: " << ex.what());
-            }
-            // Decode was successful so append decoded binary value
-            // to the buffer.
-            buf.insert(buf.end(), binary.begin(), binary.end());
-            return;
-        }
-    case OPT_BOOLEAN_TYPE:
-        {
-            // We encode the true value as 1 and false as 0 on 8 bits.
-            // That way we actually waist 7 bits but it seems to be the
-            // simpler way to encode boolean.
-            // @todo Consider if any other encode methods can be used.
-            bool bool_value = lexicalCastWithRangeCheck<bool>(value);
-            if (bool_value) {
-                buf.push_back(static_cast<uint8_t>(1));
-            } else {
-                buf.push_back(static_cast<uint8_t>(0));
-            }
-            return;
-        }
-    case OPT_INT8_TYPE:
-        {
-            // Buffer holds the uin8_t values so we need to cast the signed
-            // value to unsigned but the bits values remain untouched.
-            buf.push_back(static_cast<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value)));
-            return;
-        }
-    case OPT_INT16_TYPE:
-        {
-            // Write the int16 value as uint16 value is ok because the bit values
-            // remain untouched.
-            int16_t int_value = lexicalCastWithRangeCheck<int16_t>(value);
-            buf.resize(buf.size() + 2);
-            writeUint16(static_cast<uint16_t>(int_value), &buf[buf.size() - 2]);
-            return;
-        }
-    case OPT_INT32_TYPE:
-        {
-            int32_t int_value = lexicalCastWithRangeCheck<int32_t>(value);
-            buf.resize(buf.size() + 4);
-            writeUint32(static_cast<uint32_t>(int_value), &buf[buf.size() - 4]);
-            return;
-        }
-    case OPT_UINT8_TYPE:
-        {
-            buf.push_back(lexicalCastWithRangeCheck<uint8_t>(value));
-            return;
-        }
-    case OPT_UINT16_TYPE:
-        {
-            uint16_t uint_value = lexicalCastWithRangeCheck<uint16_t>(value);
-            buf.resize(buf.size() + 2);
-            writeUint16(uint_value, &buf[buf.size() - 2]);
-            return;
-        }
-    case OPT_UINT32_TYPE:
-        {
-            uint32_t uint_value = lexicalCastWithRangeCheck<uint32_t>(value);
-            buf.resize(buf.size() + 4);
-            writeUint32(uint_value, &buf[buf.size() - 4]);
-            return;
-        }
-    case OPT_IPV4_ADDRESS_TYPE:
-        {
-            // The easiest way to get the binary form of IPv4 address is
-            // to create IOAddress object from string and use its accessors
-            // to retrieve the binary form.
-            asiolink::IOAddress address(value);
-            if (!address.getAddress().is_v4()) {
-                isc_throw(BadDataTypeCast, "provided address " << address.toText()
-                          << " is not a valid IPV4 address");
-            }
-            asio::ip::address_v4::bytes_type addr_bytes =
-                address.getAddress().to_v4().to_bytes();
-            // Increase the buffer size by the size of IPv4 address.
-            buf.resize(buf.size() + addr_bytes.size());
-            std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
-                               buf.end());
-            return;
-        }
-    case OPT_IPV6_ADDRESS_TYPE:
-        {
-            asiolink::IOAddress address(value);
-            if (!address.getAddress().is_v6()) {
-                isc_throw(BadDataTypeCast, "provided address " << address.toText()
-                          << " is not a valid IPV6 address");
-            }
-            asio::ip::address_v6::bytes_type addr_bytes =
-                address.getAddress().to_v6().to_bytes();
-            // Incresase the buffer size by the size of IPv6 address.
-            buf.resize(buf.size() + addr_bytes.size());
-            std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
-                               buf.end());
-            return;
-        }
-    case OPT_STRING_TYPE:
-        if (value.size() > 0) {
-            // Increase the size of the storage by the size of the string.
-            buf.resize(buf.size() + value.size());
-            // Assuming that the string is already UTF8 encoded.
-            std::copy_backward(value.c_str(), value.c_str() + value.size(),
-                               buf.end());
-            return;
-        }
-    case OPT_FQDN_TYPE:
-        {
-            // FQDN implementation is not terribly complicated but will require
-            // creation of some additional logic (maybe object) that will parse
-            // the fqdn into labels.
-            isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
-                      " is not supported yet");
-            return;
-        }
-    default:
-        // We hit this point because invalid option data type has been specified
-        // This may be the case because 'empty' or 'record' data type has been
-        // specified. We don't throw exception here because it will be thrown
-        // at the exit point from this function.
-        ;
-    }
-    isc_throw(isc::BadValue, "attempt to write invalid option data field type"
-              " into the option buffer: " << type);
-
-}
 
 OptionDefinition::OptionDefinition(const std::string& name,
                                  const uint16_t code,
@@ -248,7 +40,7 @@ OptionDefinition::OptionDefinition(const std::string& name,
     // Data type is held as enum value by this class.
     // Use the provided option type string to get the
     // corresponding enum value.
-    type_ = DataTypeUtil::instance().getOptionDataType(type);
+    type_ = OptionDataTypeUtil::getDataType(type);
 }
 
 OptionDefinition::OptionDefinition(const std::string& name,
@@ -263,7 +55,7 @@ OptionDefinition::OptionDefinition(const std::string& name,
 
 void
 OptionDefinition::addRecordField(const std::string& data_type_name) {
-    OptionDataType data_type = DataTypeUtil::instance().getOptionDataType(data_type_name);
+    OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name);
     addRecordField(data_type);
 }
 
@@ -273,8 +65,10 @@ OptionDefinition::addRecordField(const OptionDataType data_type) {
         isc_throw(isc::InvalidOperation, "'record' option type must be used"
                   " to add data fields to the record");
     }
-    if (data_type >= OPT_UNKNOWN_TYPE) {
-        isc_throw(isc::BadValue, "attempted to add invalid data type to the record");
+    if (data_type >= OPT_RECORD_TYPE ||
+        data_type == OPT_ANY_ADDRESS_TYPE ||
+        data_type == OPT_EMPTY_TYPE) {
+        isc_throw(isc::BadValue, "attempted to add invalid data type to the record.");
     }
     record_fields_.push_back(data_type);
 }
@@ -353,10 +147,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
         if (values.size() == 0) {
             isc_throw(InvalidOptionValue, "no option value specified");
         }
-        DataTypeUtil::instance().writeToBuffer(values[0], type_, buf);
+        writeToBuffer(values[0], type_, buf);
     } else if (array_type_ && type_ != OPT_RECORD_TYPE) {
         for (size_t i = 0; i < values.size(); ++i) {
-            DataTypeUtil::instance().writeToBuffer(values[i], type_, buf);
+            writeToBuffer(values[i], type_, buf);
         }
     } else if (type_ == OPT_RECORD_TYPE) {
         const RecordFieldsCollection& records = getRecordFields();
@@ -366,7 +160,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                       << " provided.");
         }
         for (size_t i = 0; i < records.size(); ++i) {
-            DataTypeUtil::instance().writeToBuffer(values[i], records[i], buf);
+            writeToBuffer(values[i], records[i], buf);
         }
     }
     return (optionFactory(u, type, buf.begin(), buf.end()));
@@ -385,18 +179,24 @@ OptionDefinition::validate() const {
     std::ostringstream err_str;
     if (name_.empty()) {
         // Option name must not be empty.
-        err_str << "option name must not be empty";
+        err_str << "option name must not be empty.";
     } else if (name_.find(" ") != string::npos) {
         // Option name must not contain spaces.
-        err_str << "option name must not contain spaces";
+        err_str << "option name must not contain spaces.";
     } else if (type_ >= OPT_UNKNOWN_TYPE) {
         // Option definition must be of a known type.
-        err_str << "option type value " << type_ << " is out of range";
-    } else if (type_ == OPT_STRING_TYPE && array_type_) {
-        // Array of strings is not allowed because there is no way
-        // to determine the size of a particular string and thus there
-        // it no way to tell when other data fields begin.
-        err_str << "array of strings is not a valid option definition";
+        err_str << "option type value " << type_ << " is out of range.";
+    } else if (array_type_) {
+        if (type_ == OPT_STRING_TYPE) {
+            // Array of strings is not allowed because there is no way
+            // to determine the size of a particular string and thus there
+            // it no way to tell when other data fields begin.
+            err_str << "array of strings is not a valid option definition.";
+        } else if (type_ == OPT_BINARY_TYPE) {
+            err_str << "array of binary values is not a valid option definition.";
+        } else if (type_ == OPT_EMPTY_TYPE) {
+            err_str << "array of empty value is not a valid option definition.";
+        }
     } else if (type_ == OPT_RECORD_TYPE) {
         // At least two data fields should be added to the record. Otherwise
         // non-record option definition could be used.
@@ -406,8 +206,8 @@ OptionDefinition::validate() const {
                     << " least 2 fields.";
         } else {
             // If the number of fields is valid we have to check if their order
-            // is valid too. We check that string data fields are not laid before
-            // other fields. But we allow that they are laid at the end of
+            // is valid too. We check that string or binary data fields are not
+            // laid before other fields. But we allow that they are laid at the end of
             // an option.
             const RecordFieldsCollection& fields = getRecordFields();
             for (RecordFieldsConstIter it = fields.begin();
@@ -418,6 +218,17 @@ OptionDefinition::validate() const {
                             << " of other types.";
                     break;
                 }
+                if (*it == OPT_BINARY_TYPE &&
+                    it < fields.end() - 1) {
+                    err_str << "binary data field can't be laid before data fields"
+                            << " of other types.";
+                }
+                /// Empty type is not allowed within a record.
+                if (*it == OPT_EMPTY_TYPE) {
+                    err_str << "empty data type can't be stored as a field in an"
+                            << " option record.";
+                    break;
+                }
             }
         }
 
@@ -455,6 +266,124 @@ OptionDefinition::haveIAAddr6Format() const {
     return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
 }
 
+template<typename T>
+T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
+    // Lexical cast in case of our data types make sense only
+    // for uintX_t, intX_t and bool type.
+    if (!OptionDataTypeTraits<T>::integer_type &&
+        OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
+        isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
+                  << " non-boolean data type");
+    }
+    // We use the 64-bit value here because it has wider range than
+    // any other type we use here and it allows to detect out of
+    // bounds conditions e.g. negative value specified for uintX_t
+    // data type. Obviously if the value exceeds the limits of int64
+    // this function will not handle that properly.
+    int64_t result = 0;
+    try {
+        result = boost::lexical_cast<int64_t>(value_str);
+    } catch (const boost::bad_lexical_cast& ex) {
+        // Prepare error message here.
+        std::string data_type_str = "boolean";
+        if (OptionDataTypeTraits<T>::integer_type) {
+            data_type_str = "integer";
+        }
+        isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
+                  << " data type for value " << value_str << ": " << ex.what());
+    }
+    // Perform range checks for integer values only (exclude bool values).
+    if (OptionDataTypeTraits<T>::integer_type) {
+        if (result > numeric_limits<T>::max() ||
+            result < numeric_limits<T>::min()) {
+            isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
+                      << value_str << ". This value is expected to be in the range of "
+                      << numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
+        }
+    }
+    return (static_cast<T>(result));
+}
+
+void
+OptionDefinition::writeToBuffer(const std::string& value,
+                                const OptionDataType type,
+                                OptionBuffer& buf) const {
+    // We are going to write value given by value argument to the buffer.
+    // The actual type of the value is given by second argument. Check
+    // this argument to determine how to write this value to the buffer.
+    switch (type) {
+    case OPT_BINARY_TYPE:
+        OptionDataTypeUtil::writeBinary(value, buf);
+        return;
+    case OPT_BOOLEAN_TYPE:
+        // We encode the true value as 1 and false as 0 on 8 bits.
+        // That way we actually waste 7 bits but it seems to be the
+        // simpler way to encode boolean.
+        // @todo Consider if any other encode methods can be used.
+        OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
+        return;
+    case OPT_INT8_TYPE:
+        OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
+                                              buf);
+        return;
+    case OPT_INT16_TYPE:
+        OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
+                                               buf);
+        return;
+    case OPT_INT32_TYPE:
+        OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
+                                               buf);
+        return;
+    case OPT_UINT8_TYPE:
+        OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
+                                              buf);
+        return;
+    case OPT_UINT16_TYPE:
+        OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
+                                               buf);
+        return;
+    case OPT_UINT32_TYPE:
+        OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
+                                               buf);
+        return;
+    case OPT_IPV4_ADDRESS_TYPE:
+    case OPT_IPV6_ADDRESS_TYPE:
+        {
+            asiolink::IOAddress address(value);
+            if (address.getFamily() != AF_INET &&
+                address.getFamily() != AF_INET6) {
+                isc_throw(BadDataTypeCast, "provided address " << address.toText()
+                          << " is not a valid "
+                          << (address.getAddress().is_v4() ? "IPv4" : "IPv6")
+                          << " address");
+            }
+            OptionDataTypeUtil::writeAddress(address, buf);
+            return;
+        }
+    case OPT_STRING_TYPE:
+        OptionDataTypeUtil::writeString(value, buf);
+        return;
+    case OPT_FQDN_TYPE:
+        {
+            // FQDN implementation is not terribly complicated but will require
+            // creation of some additional logic (maybe object) that will parse
+            // the fqdn into labels.
+            isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
+                      " is not supported yet");
+            return;
+        }
+    default:
+        // We hit this point because invalid option data type has been specified
+        // This may be the case because 'empty' or 'record' data type has been
+        // specified. We don't throw exception here because it will be thrown
+        // at the exit point from this function.
+        ;
+    }
+    isc_throw(isc::BadValue, "attempt to write invalid option data field type"
+              " into the option buffer: " << type);
+
+}
+
 OptionPtr
 OptionDefinition::factoryAddrList4(uint16_t type,
                                   OptionBufferConstIter begin,
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 18f79d2..3d48ef2 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -131,83 +131,6 @@ public:
     /// Const iterator for record data fields.
     typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter;
 
-private:
-
-    /// @brief Utility class for operations on OptionDataTypes.
-    ///
-    /// This class is implemented as the singleton because the list of
-    /// supported data types need only be loaded only once into memory as it
-    /// can persist for all option definitions.
-    ///
-    /// @todo This class can be extended to return the string value
-    /// representing the data type from the enum value.
-    class DataTypeUtil {
-    public:
-
-        /// @brief Return the sole instance of this class.
-        ///
-        /// @return instance of this class.
-        static DataTypeUtil& instance() {
-            static DataTypeUtil instance;
-            return (instance);
-        }
-
-        /// @brief Convert type given as string value to option data type.
-        ///
-        /// @param data_type_name data type string.
-        ///
-        /// @return option data type.
-        OptionDataType getOptionDataType(const std::string& data_type_name);
-
-        /// @brief Perform lexical cast of the value and validate its range.
-        ///
-        /// This function performs lexical cast of a string value to integer
-        /// or boolean value and checks if the resulting value is within a
-        /// range of a target type. Note that range checks are not performed
-        /// on boolean values. The target type should be one of the supported
-        /// integer types or bool.
-        ///
-        /// @param value_str input value given as string.
-        /// @tparam T target type for lexical cast.
-        ///
-        /// @return cast value.
-        /// @throw BadDataTypeCast if cast was not successful.
-        template<typename T>
-        T lexicalCastWithRangeCheck(const std::string& value_str) const;
-
-        /// @brief Write the string value into the provided buffer.
-        ///
-        /// This method writes the given value to the specified buffer.
-        /// The provided string value may represent data of different types.
-        /// The actual data type is specified with the second argument.
-        /// Based on a value of this argument, this function will first
-        /// try to cast the string value to the particular data type and
-        /// if it is successful it will store the data in the buffer
-        /// in a binary format.
-        ///
-        /// @param value string representation of the value to be written.
-        /// @param type the actual data type to be stored.
-        /// @param [in, out] buf buffer where the value is to be stored.
-        ///
-        /// @throw BadDataTypeCast if data write was unsuccessful.
-        void writeToBuffer(const std::string& value, const OptionDataType type,
-                           OptionBuffer& buf);
-
-    private:
-        /// @brief Private constructor.
-        ///
-        /// Constructor initializes the internal data structures, e.g.
-        /// mapping between data type name and the corresponding enum.
-        /// This constructor is private to ensure that exactly one
-        /// instance of this class can be created using \ref instance
-        /// function.
-        DataTypeUtil();
-
-        /// Map of data types, maps name of the type to enum value.
-        std::map<std::string, OptionDataType> data_types_;
-    };
-
-public:
     /// @brief Constructor.
     ///
     /// @param name option name.
@@ -471,6 +394,40 @@ private:
         return (type == type_);
     }
 
+    /// @brief Perform lexical cast of the value and validate its range.
+    ///
+    /// This function performs lexical cast of a string value to integer
+    /// or boolean value and checks if the resulting value is within a
+    /// range of a target type. Note that range checks are not performed
+    /// on boolean values. The target type should be one of the supported
+    /// integer types or bool.
+    ///
+    /// @param value_str input value given as string.
+    /// @tparam T target type for lexical cast.
+    ///
+    /// @return cast value.
+    /// @throw BadDataTypeCast if cast was not successful.
+    template<typename T>
+    T lexicalCastWithRangeCheck(const std::string& value_str) const;
+
+    /// @brief Write the string value into the provided buffer.
+    ///
+    /// This method writes the given value to the specified buffer.
+    /// The provided string value may represent data of different types.
+    /// The actual data type is specified with the second argument.
+    /// Based on a value of this argument, this function will first
+    /// try to cast the string value to the particular data type and
+    /// if it is successful it will store the data in the buffer
+    /// in a binary format.
+    ///
+    /// @param value string representation of the value to be written.
+    /// @param type the actual data type to be stored.
+    /// @param [in, out] buf buffer where the value is to be stored.
+    ///
+    /// @throw BadDataTypeCast if data write was unsuccessful.
+    void writeToBuffer(const std::string& value, const OptionDataType type,
+                       OptionBuffer& buf) const;
+
     /// @brief Sanity check universe value.
     ///
     /// @param expected_universe expected universe value.
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 945f822..e66d700 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -36,6 +36,7 @@ libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
+libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
new file mode 100644
index 0000000..34969ee
--- /dev/null
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -0,0 +1,906 @@
+// Copyright (C) 2012 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 <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option_custom.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    OptionCustomTest() { }
+
+    /// @brief Write IP address into a buffer.
+    ///
+    /// @param address address to be written.
+    /// @param [out] buf output buffer.
+    void writeAddress(const asiolink::IOAddress& address,
+                      std::vector<uint8_t>& buf) {
+        short family = address.getFamily();
+        if (family == AF_INET) {
+            asio::ip::address_v4::bytes_type buf_addr =
+                address.getAddress().to_v4().to_bytes();
+            buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+        } else if (family == AF_INET6) {
+            asio::ip::address_v6::bytes_type buf_addr =
+                address.getAddress().to_v6().to_bytes();
+            buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+        }
+    }
+
+    /// @brief Write integer (signed or unsigned) into a buffer.
+    ///
+    /// @param value integer value.
+    /// @param [out] buf output buffer.
+    /// @tparam integer type.
+    template<typename T>
+    void writeInt(T value, std::vector<uint8_t>& buf) {
+        for (int i = 0; i < sizeof(T); ++i) {
+            buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+        }
+    }
+
+    /// @brief Write a string into a buffer.
+    ///
+    /// @param value string to be written into a buffer.
+    /// @param buf output buffer.
+    void writeString(const std::string& value,
+                     std::vector<uint8_t>& buf) {
+        buf.resize(buf.size() + value.size());
+        std::copy_backward(value.c_str(), value.c_str() + value.size(),
+                           buf.end());
+    }
+};
+
+// The purpose of this test is to check that parameters passed to
+// a custom option's constructor are used to initialize class
+// members.
+TEST_F(OptionCustomTest, constructor) {
+    // Create option definition for a DHCPv6 option.
+    OptionDefinition opt_def1("OPTION_FOO", 1000, "boolean", true);
+
+    // Initialize some dummy buffer that holds single boolean value.
+    OptionBuffer buf;
+    buf.push_back(1);
+
+    // Create DHCPv6 option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def1, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // Check if constructor initialized the universe and type correctly.
+    EXPECT_EQ(Option::V6, option->getUniverse());
+    EXPECT_EQ(1000, option->getType());
+
+    // Do another round of testing for DHCPv4 option.
+    OptionDefinition opt_def2("OPTION_FOO", 232, "boolean");
+
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(Option::V4, option->getUniverse());
+    EXPECT_EQ(232, option->getType());
+}
+
+// The purpose of this test is to verify that 'empty' option definition can
+// be used to create an instance of custom option.
+TEST_F(OptionCustomTest, emptyData) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "empty");
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
+    );
+    ASSERT_TRUE(option);
+
+    // Option is 'empty' so no data fields are expected.
+    EXPECT_EQ(0, option->getDataFieldsNum());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a binary value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, binaryData) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "binary");
+
+    // Create a buffer holding some binary data. This data will be
+    // used as reference when we read back the data from a created
+    // option.
+    OptionBuffer buf_in(14);
+    for (int i = 0; i < 14; ++i) {
+        buf_in[i] = i;
+    }
+    // Use scoped pointer because it allows to declare the option
+    // in the function scope and initialize it under ASSERT.
+    boost::scoped_ptr<OptionCustom> option;
+    // Custom option may throw exception if the provided buffer is
+    // malformed.
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf_in));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // The custom option should hold just one buffer that can be
+    // accessed using index 0.
+    OptionBuffer buf_out;
+    ASSERT_NO_THROW(buf_out = option->readBinary(0));
+
+    // Read buffer must match exactly with the buffer used to
+    // create option instance.
+    ASSERT_EQ(buf_in.size(), buf_out.size());
+    EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+    // Check that option with "no data" is rejected.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that an option definition comprising
+// a single boolean value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+    OptionBuffer buf;
+    // Push back the value that represents 'false'.
+    buf.push_back(0);
+    // Push back the 'true' value. Note that this value should
+    // be ignored by custom option because it holds single boolean
+    // value (according to option definition).
+    buf.push_back(1);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Initialize the value to true because we want to make sure
+    // that it is modified to 'false' by readBoolean below.
+    bool value = true;
+
+    // Read the boolean value from only one available buffer indexed
+    // with 0. It is expected to be 'false'.
+    ASSERT_NO_THROW(value = option->readBoolean(0));
+    EXPECT_FALSE(value);
+
+    // Check that the option with "no data" is rejected.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 16-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int16Data) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "int16");
+
+    OptionBuffer buf;
+    // Store signed integer value in the input buffer.
+    writeInt<int16_t>(-234, buf);
+
+    // Create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Initialize value to 0 explicitely to make sure that is
+    // modified by readInteger function to expected -234.
+    int16_t value = 0;
+    ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
+    EXPECT_EQ(-234, value);
+
+    // Check that the option is not created when a buffer is
+    // too short (1 byte instead of 2 bytes).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 32-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int32Data) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "int32");
+
+    OptionBuffer buf;
+    writeInt<int32_t>(-234, buf);
+    writeInt<int32_t>(100, buf);
+
+    // Create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Initialize value to 0 explicitely to make sure that is
+    // modified by readInteger function to expected -234.
+    int32_t value = 0;
+    ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
+    EXPECT_EQ(-234, value);
+
+    // Check that the option is not created when a buffer is
+    // too short (3 bytes instead of 4 bytes).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv4 addres can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address");
+
+    // Create input buffer.
+    OptionBuffer buf;
+    writeAddress(IOAddress("192.168.100.50"), buf);
+
+    // Create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    IOAddress address("127.0.0.1");
+    // Read IPv4 address from using index 0.
+    ASSERT_NO_THROW(address = option->readAddress(0));
+
+    EXPECT_EQ("192.168.100.50", address.toText());
+
+    // Check that option is not created if the provided buffer is
+    // too short (use 3 bytes instead of 4).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv6 addres can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+    // Initialize input buffer.
+    OptionBuffer buf;
+    writeAddress(IOAddress("2001:db8:1::100"), buf);
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Custom option should comprise exactly one buffer that represents
+    // IPv6 address.
+    IOAddress address("::1");
+    // Read an address from buffer #0.
+    ASSERT_NO_THROW(address = option->readAddress(0));
+
+    EXPECT_EQ("2001:db8:1::100", address.toText());
+
+    // Check that option is not created if the provided buffer is
+    // too short (use 15 bytes instead of 16).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+                                      buf.begin() + 15)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// string value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, stringData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+    // Create an input buffer holding some string value.
+    OptionBuffer buf;
+    writeString("hello world!", buf);
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Custom option should now comprise single string value that
+    // can be accessed using index 0.
+    std::string value;
+    ASSERT_NO_THROW(value = option->readString(0));
+
+    EXPECT_EQ("hello world!", value);
+
+    // Check that option will not be created if empty buffer is provided.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of boolean values can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+    // Create a buffer with 5 values that represent array of
+    // booleans.
+    OptionBuffer buf(5);
+    buf[0] = 1; // true
+    buf[1] = 0; // false
+    buf[2] = 0; // false
+    buf[3] = 1; // true
+    buf[4] = 1; // true
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 5 data fields.
+    ASSERT_EQ(5, option->getDataFieldsNum());
+
+    // Read values from custom option using indexes 0..4 and
+    // check that they are valid.
+    bool value0 = false;
+    ASSERT_NO_THROW(value0 = option->readBoolean(0));
+    EXPECT_TRUE(value0);
+
+    bool value1 = true;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_FALSE(value1);
+
+    bool value2 = true;
+    ASSERT_NO_THROW(value2 = option->readBoolean(2));
+    EXPECT_FALSE(value2);
+
+    bool value3 = false;
+    ASSERT_NO_THROW(value3 = option->readBoolean(3));
+    EXPECT_TRUE(value3);
+
+    bool value4 = false;
+    ASSERT_NO_THROW(value4 = option->readBoolean(4));
+    EXPECT_TRUE(value4);
+
+    // Check that empty buffer can't be used to create option holding
+    // array of boolean values.
+    EXPECT_THROW(
+         option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+         isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of 32-bit signed integer values can be used to create an instance
+// of custom option.
+TEST_F(OptionCustomTest, uint32DataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint32", true);
+
+    // Create an input buffer that holds 4 uint32 values that
+    // represent an array.
+    std::vector<uint32_t> values;
+    values.push_back(71234);
+    values.push_back(12234);
+    values.push_back(54362);
+    values.push_back(1234);
+
+    // Store these values in a buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < values.size(); ++i) {
+        writeInt<uint32_t>(values[i], buf);
+    }
+    // Create custom option using the input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        // Note that we just use a part of the whole buffer here: 13 bytes. We want to
+        // check that buffer length which is non-divisible by 4 (size of uint32_t) is
+        // accepted and only 3 (instead of 4) elements will be stored in a custom option.
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Expect only 3 values.
+    for (int i = 0; i < 3; ++i) {
+        uint32_t value = 0;
+        ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i));
+        EXPECT_EQ(values[i], value);
+    }
+
+    // Check that too short buffer can't be used to create the option.
+    // Using buffer having length of 3 bytes. The length of 4 bytes is
+    // a minimal length to create the option with single uint32_t value.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+                                      buf.begin() + 3)),
+        isc::OutOfRange
+    );
+
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv4 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("192.168.0.1"));
+    addresses.push_back(IOAddress("127.0.0.1"));
+    addresses.push_back(IOAddress("10.10.1.2"));
+
+    // Store the collection of IPv4 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv4 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Check that it is ok if buffer length is not a multiple of IPv4
+    // address length. Resize it by two bytes.
+    buf.resize(buf.size() + 2);
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+
+    // Check that option is not created when the provided buffer
+    // is too short. At least a buffer length of 4 bytes is needed.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+                                      buf.begin() + 2)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("2001:db8:1::3"));
+    addresses.push_back(IOAddress("::1"));
+    addresses.push_back(IOAddress("fe80::3"));
+
+    // Store the collection of IPv6 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv6 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("fe80::4");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Check that it is ok if buffer length is not a multiple of IPv6
+    // address length. Resize it by two bytes.
+    buf.resize(buf.size() + 2);
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+
+    // Check that option is not created when the provided buffer
+    // is too short. At least a buffer length of 16 bytes is needed.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+                                      buf.begin() + 15)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields can be used to create an instance of
+// custom option.
+TEST_F(OptionCustomTest, recordData) {
+    // Create the definition of an option which comprises
+    // a record of fields of different types.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    OptionBuffer buf;
+    // Initialize field 0.
+    writeInt<uint16_t>(8712, buf);
+    // Initialize field 1 to 'true'
+    buf.push_back(static_cast<unsigned short>(1));
+    // Initialize field 2 to IPv4 address.
+    writeAddress(IOAddress("192.168.0.1"), buf);
+    // Initialize field 3 to IPv6 address.
+    writeAddress(IOAddress("2001:db8:1::1"), buf);
+    // Initialize field 4 to string value.
+    writeString("ABCD", buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+         option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 5 data fields.
+    ASSERT_EQ(5, option->getDataFieldsNum());
+
+    // Verify value in the field 0.
+    uint16_t value0 = 0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(8712, value0);
+
+    // Verify value in the field 1.
+    bool value1 = false;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_TRUE(value1);
+
+    // Verify value in the field 2.
+    IOAddress value2("127.0.0.1");
+    ASSERT_NO_THROW(value2 = option->readAddress(2));
+    EXPECT_EQ("192.168.0.1", value2.toText());
+
+    // Verify value in the field 3.
+    IOAddress value3("::1");
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("2001:db8:1::1", value3.toText());
+
+    // Verify value in the field 4.
+    std::string value4;
+    ASSERT_NO_THROW(value4 = option->readString(4));
+    EXPECT_EQ("ABCD", value4);
+}
+
+// The purpose of this test is to verify that truncated buffer
+// can't be used to create an option being a record of value of
+// different types.
+TEST_F(OptionCustomTest, recordDataTruncated) {
+    // Create the definition of an option which comprises
+    // a record of fields of different types.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    OptionBuffer buf;
+    // Initialize field 0.
+    writeInt<uint16_t>(8712, buf);
+    // Initialize field 1 to IPv6 address.
+    writeAddress(IOAddress("2001:db8:1::1"), buf);
+    // Initialize field 2 to string value.
+    writeString("ABCD", buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+
+    // Constructor should not throw exception here because the length of the
+    // buffer meets the minimum length. The first 19 bytes hold data for
+    // all option fields: uint16, IPv4 address and first letter of string.
+    // Note that string will be truncated but this is acceptable because
+    // constructor have no way to determine the length of the original string.
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19));
+    );
+
+    // Reduce the buffer length by one byte should cause the constructor
+    // to fail. This is because 18 bytes can only hold first two data fields:
+    // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
+    // 3 data fields for this option but the length of the data is insufficient
+    // to initialize 3 data field.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
+        isc::OutOfRange
+    );
+
+    // Try to further reduce the length of the buffer to make it insufficient
+    // to even initialize the second data field.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv4 custom option works correctly.
+TEST_F(OptionCustomTest, pack4) {
+    OptionDefinition opt_def("OPTION_FOO", 234, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("uint8"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+    OptionBuffer buf;
+    writeInt<uint8_t>(1, buf);
+    writeInt<uint16_t>(1000, buf);
+    writeInt<uint32_t>(100000, buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+    ASSERT_TRUE(option);
+
+    util::OutputBuffer buf_out(7);
+    ASSERT_NO_THROW(option->pack(buf_out));
+    ASSERT_EQ(9, buf_out.getLength());
+
+    // The original buffer holds the option data but it lacks a header.
+    // We append data length and option code so as it can be directly
+    // compared with the output buffer that holds whole option.
+    buf.insert(buf.begin(), 7);
+    buf.insert(buf.begin(), 234);
+
+    // Validate the buffer.
+    EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv6 custom option works correctly.
+TEST_F(OptionCustomTest, pack6) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    OptionBuffer buf;
+    buf.push_back(1);
+    writeInt<uint16_t>(1000, buf);
+    writeString("hello world", buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    util::OutputBuffer buf_out(buf.size() + option->getHeaderLen());
+    ASSERT_NO_THROW(option->pack(buf_out));
+    ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength());
+
+    // The original buffer holds the option data but it lacks a header.
+    // We append data length and option code so as it can be directly
+    // compared with the output buffer that holds whole option.
+    OptionBuffer tmp;
+    writeInt<uint16_t>(1000, tmp);
+    writeInt<uint16_t>(buf.size(), tmp);
+    buf.insert(buf.begin(), tmp.begin(), tmp.end());
+
+    // Validate the buffer.
+    EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option.
+TEST_F(OptionCustomTest, unpack) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("192.168.0.1"));
+    addresses.push_back(IOAddress("127.0.0.1"));
+    addresses.push_back(IOAddress("10.10.1.2"));
+
+    // Store the collection of IPv4 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv4 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Remove all addresses we had added. We are going to replace
+    // them with a new set of addresses.
+    addresses.clear();
+
+    // Add new addresses.
+    addresses.push_back(IOAddress("10.1.2.3"));
+    addresses.push_back(IOAddress("85.26.43.234"));
+
+    // Clear the buffer as we need to store new addresses in it.
+    buf.clear();
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Perform 'unpack'.
+    ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end()));
+
+    // Now we should have only 2 data fields.
+    ASSERT_EQ(2, option->getDataFieldsNum());
+
+    // Verify that the addresses have been overwritten.
+    for (int i = 0; i < 2; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+}
+
+// The purpose of this test is to verify that new data can be set for
+// a custom option.
+TEST_F(OptionCustomTest, setData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("2001:db8:1::3"));
+    addresses.push_back(IOAddress("::1"));
+    addresses.push_back(IOAddress("fe80::3"));
+
+    // Store the collection of IPv6 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv6 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("fe80::4");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Clear addresses we had previously added.
+    addresses.clear();
+
+    // Store new addresses.
+    addresses.push_back(IOAddress("::1"));
+    addresses.push_back(IOAddress("fe80::10"));
+
+    // Clear the buffer as we need to store new addresses in it.
+    buf.clear();
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Replace the option data.
+    ASSERT_NO_THROW(option->setData(buf.begin(), buf.end()));
+
+    // Now we should have only 2 data fields.
+    ASSERT_EQ(2, option->getDataFieldsNum());
+
+    // Check that it has been replaced.
+    for (int i = 0; i < 2; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+}
+
+// The purpose of this test is to verify that an invalid index
+// value can't be used to access option data fields.
+TEST_F(OptionCustomTest, invalidIndex) {
+    OptionDefinition opt_def("OPTION_FOO", 999, "uint32", true);
+
+    OptionBuffer buf;
+    for (int i = 0; i < 10; ++i) {
+        writeInt<uint32_t>(i, buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We expect that there are 10 uint32_t values stored in
+    // the option. The 10th element is accessed by index eq 9.
+    // Check that 9 is accepted.
+    EXPECT_NO_THROW(option->readInteger<uint32_t>(9));
+
+    // Check that index value beyond 9 is not accepted.
+    EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange);
+    EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
+}
+
+
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 9fa39c4..20c87d6 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -458,56 +458,6 @@ TEST_F(OptionDefinitionTest, binary) {
                            buf.begin()));
 }
 
-// The purpose of this test is to verify that definition can be
-// creates for the option that holds binary data and that the
-// binary data can be specified in 'comma separated values'
-// format.
-TEST_F(OptionDefinitionTest, binaryTokenized) {
-    OptionDefinition opt_def("OPTION_FOO", 1000, "binary", true);
-
-    // Prepare some dummy data (serverid): 0, 1, 2 etc.
-    OptionBuffer buf(16);
-    for (int i = 0; i < buf.size(); ++i) {
-        buf[i] = i;
-    }
-    std::vector<std::string> hex_data;
-    hex_data.push_back("00010203");
-    hex_data.push_back("04050607");
-    hex_data.push_back("08090A0B0C0D0E0F");
-
-    // Create option instance with the factory function.
-    // If the OptionDefinition code works properly than
-    // object of the type Option should be returned.
-    OptionPtr option_v6;
-    ASSERT_NO_THROW(
-        option_v6 = opt_def.optionFactory(Option::V6, 1000, hex_data);
-    );
-    // Expect base option type returned.
-    ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
-    // Sanity check on universe, length and size. These are
-    // the basic parameters identifying any option.
-    EXPECT_EQ(Option::V6, option_v6->getUniverse());
-    EXPECT_EQ(4, option_v6->getHeaderLen());
-    ASSERT_EQ(buf.size(), option_v6->getData().size());
-
-    // Get data from the option and compare against reference buffer.
-    // They are expected to match.
-    EXPECT_TRUE(std::equal(option_v6->getData().begin(),
-                           option_v6->getData().end(),
-                           buf.begin()));
-
-    // Repeat the same test scenario for DHCPv4 option.
-    OptionPtr option_v4;
-    ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, hex_data));
-    EXPECT_EQ(Option::V4, option_v4->getUniverse());
-    EXPECT_EQ(2, option_v4->getHeaderLen());
-    ASSERT_EQ(buf.size(), option_v4->getData().size());
-
-    EXPECT_TRUE(std::equal(option_v6->getData().begin(),
-                           option_v6->getData().end(),
-                           buf.begin()));
-}
-
 // The purpose of this test is to verify that definition can be created
 // for option that comprises record of data. In this particular test
 // the IA_NA option is used. This option comprises three uint32 fields.
@@ -652,11 +602,6 @@ TEST_F(OptionDefinitionTest, uint8Tokenized) {
     std::vector<std::string> values;
     values.push_back("123");
     values.push_back("456");
-    try {
-        option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values);
-    } catch (std::exception& ex) {
-        std::cout << ex.what() << std::endl;
-    }
     ASSERT_NO_THROW(
         option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values);
     );
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 8d7bcfd..70c65e7 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -121,6 +121,7 @@ libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
 libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
 libb10_dns___la_SOURCES += character_string.h character_string.cc
 libb10_dns___la_SOURCES += master_loader_callbacks.h
+libb10_dns___la_SOURCES += master_loader.h
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index e51dfc5..6a9ff8a 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -28,6 +28,7 @@ re_typecode = re.compile('([\da-z]+)_(\d+)')
 classcode2txt = {}
 typecode2txt = {}
 typeandclass = []
+new_rdatafactory_users = ['aaaa']
 generic_code = 65536            # something larger than any code value
 rdata_declarations = ''
 class_definitions = ''
@@ -116,6 +117,9 @@ class AbstractMessageRenderer;\n\n'''
     explicit ''' + type_utxt + '''(const std::string& type_str);
     ''' + type_utxt + '''(isc::util::InputBuffer& buffer, size_t rdata_len);
     ''' + type_utxt + '''(const ''' + type_utxt + '''& other);
+    ''' + type_utxt + '''(
+        MasterLexer& lexer, const Name* name,
+        MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
     virtual std::string toText() const;
     virtual void toWire(isc::util::OutputBuffer& buffer) const;
     virtual void toWire(AbstractMessageRenderer& renderer) const;
@@ -203,17 +207,33 @@ def generate_rdatadef(file, basemtime):
     rdata_deffile.write(class_definitions)
     rdata_deffile.close()
 
-def generate_rdatahdr(file, declarations, basemtime):
+def generate_rdatahdr(file, heading, declarations, basemtime):
     if not need_generate(file, basemtime):
         print('skip generating ' + file);
         return
+    heading += '''
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H 1
+
+#include <dns/master_loader.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class MasterLexer;
+class MasterLoaderCallbacks;
+}
+}
+'''
     declarations += '''
+#endif // DNS_RDATACLASS_H
+
 // Local Variables:
 // mode: c++
 // End:
 '''
     rdata_header = open(file, 'w')
-    rdata_header.write(heading_txt)
+    rdata_header.write(heading)
     rdata_header.write(declarations)
     rdata_header.close()
 
@@ -271,15 +291,24 @@ def generate_rrparam(fileprefix, basemtime):
         class_utxt = class_tuple[1].upper()
         indent = ' ' * 8
         typeandclassparams += indent
+
+        # By default, we use OldRdataFactory (see bug #2497). If you
+        # want to pick RdataFactory for a particular type, add it to
+        # new_rdatafactory_users.
+        if type_txt in new_rdatafactory_users:
+            rdf_class = 'RdataFactory'
+        else:
+            rdf_class = 'OldRdataFactory'
+
         if class_tuple[1] != 'generic':
             typeandclassparams += 'add("' + type_utxt + '", '
             typeandclassparams += str(type_code) + ', "' + class_utxt
             typeandclassparams += '", ' + str(class_code)
-            typeandclassparams += ', RdataFactoryPtr(new RdataFactory<'
+            typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
             typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
         else:
             typeandclassparams += 'add("' + type_utxt + '", ' + str(type_code)
-            typeandclassparams += ', RdataFactoryPtr(new RdataFactory<'
+            typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
             typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
 
     rrparam_temp = open(placeholder, 'r')
@@ -296,8 +325,8 @@ if __name__ == "__main__":
     try:
         import_definitions(classcode2txt, typecode2txt, typeandclass)
         generate_rdatadef('@builddir@/rdataclass.cc', rdatadef_mtime)
-        generate_rdatahdr('@builddir@/rdataclass.h', rdata_declarations,
-                          rdatahdr_mtime)
+        generate_rdatahdr('@builddir@/rdataclass.h', heading_txt,
+                          rdata_declarations, rdatahdr_mtime)
         generate_typeclasscode('rrtype', rdatahdr_mtime, typecode2txt, 'Type')
         generate_typeclasscode('rrclass', classdir_mtime,
                                classcode2txt, 'Class')
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index 8364f80..4593b48 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -19,6 +19,7 @@
 #include <dns/master_lexer_state.h>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include <bitset>
 #include <cassert>
@@ -35,7 +36,7 @@ using namespace master_lexer_internal;
 
 
 struct MasterLexer::MasterLexerImpl {
-    MasterLexerImpl() : source_(NULL), token_(Token::NOT_STARTED),
+    MasterLexerImpl() : source_(NULL), token_(MasterToken::NOT_STARTED),
                         paren_count_(0), last_was_eol_(false),
                         has_previous_(false),
                         previous_paren_count_(0),
@@ -81,7 +82,7 @@ struct MasterLexer::MasterLexerImpl {
 
     std::vector<InputSourcePtr> sources_;
     InputSource* source_;       // current source (NULL if sources_ is empty)
-    Token token_;               // currently recognized token (set by a state)
+    MasterToken token_;         // currently recognized token (set by a state)
     std::vector<char> data_;    // placeholder for string data
 
     // These are used in states, and defined here only as a placeholder.
@@ -164,9 +165,8 @@ MasterLexer::getSourceLine() const {
     return (impl_->sources_.back()->getCurrentLine());
 }
 
-const MasterLexer::Token&
+const MasterToken&
 MasterLexer::getNextToken(Options options) {
-    // If the source is not available
     if (impl_->source_ == NULL) {
         isc_throw(isc::InvalidOperation, "No source to read tokens from");
     }
@@ -177,7 +177,7 @@ MasterLexer::getNextToken(Options options) {
     impl_->has_previous_ = true;
     // Reset the token now. This is to check a token was actually produced.
     // This is debugging aid.
-    impl_->token_ = Token(Token::NO_TOKEN_PRODUCED);
+    impl_->token_ = MasterToken(MasterToken::NO_TOKEN_PRODUCED);
     // And get the token
 
     // This actually handles EOF internally too.
@@ -187,8 +187,62 @@ MasterLexer::getNextToken(Options options) {
     }
     // Make sure a token was produced. Since this Can Not Happen, we assert
     // here instead of throwing.
-    assert(impl_->token_.getType() != Token::ERROR ||
-           impl_->token_.getErrorCode() != Token::NO_TOKEN_PRODUCED);
+    assert(impl_->token_.getType() != MasterToken::ERROR ||
+           impl_->token_.getErrorCode() != MasterToken::NO_TOKEN_PRODUCED);
+    return (impl_->token_);
+}
+
+namespace {
+inline MasterLexer::Options
+optionsForTokenType(MasterToken::Type expect) {
+    switch (expect) {
+    case MasterToken::STRING:
+        return (MasterLexer::NONE);
+    case MasterToken::QSTRING:
+        return (MasterLexer::QSTRING);
+    case MasterToken::NUMBER:
+        return (MasterLexer::NUMBER);
+    default:
+        isc_throw(InvalidParameter,
+                  "expected type for getNextToken not supported: " << expect);
+    }
+}
+}
+
+const MasterToken&
+MasterLexer::getNextToken(MasterToken::Type expect, bool eol_ok) {
+    // Get the next token, specifying an appropriate option corresponding to
+    // the expected type.  The result should be set in impl_->token_.
+    getNextToken(optionsForTokenType(expect));
+
+    if (impl_->token_.getType() == MasterToken::ERROR) {
+        if (impl_->token_.getErrorCode() == MasterToken::NUMBER_OUT_OF_RANGE) {
+            ungetToken();
+        }
+        throw LexerError(__FILE__, __LINE__, impl_->token_);
+    }
+
+    const bool is_eol_like =
+        (impl_->token_.getType() == MasterToken::END_OF_LINE ||
+         impl_->token_.getType() == MasterToken::END_OF_FILE);
+    if (eol_ok && is_eol_like) {
+        return (impl_->token_);
+    }
+    if (impl_->token_.getType() == MasterToken::STRING &&
+        expect == MasterToken::QSTRING) {
+        return (impl_->token_);
+    }
+    if (impl_->token_.getType() != expect) {
+        ungetToken();
+        if (is_eol_like) {
+            throw LexerError(__FILE__, __LINE__,
+                             MasterToken(MasterToken::UNEXPECTED_END));
+        }
+        assert(expect == MasterToken::NUMBER);
+        throw LexerError(__FILE__, __LINE__,
+                         MasterToken(MasterToken::BAD_NUMBER));
+    }
+
     return (impl_->token_);
 }
 
@@ -210,16 +264,18 @@ const char* const error_text[] = {
     "unbalanced parentheses",   // UNBALANCED_PAREN
     "unexpected end of input",  // UNEXPECTED_END
     "unbalanced quotes",        // UNBALANCED_QUOTES
-    "no token produced"         // NO_TOKEN_PRODUCED
+    "no token produced",        // NO_TOKEN_PRODUCED
+    "number out of range",      // NUMBER_OUT_OF_RANGE
+    "not a valid number"        // BAD_NUMBER
 };
 const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
 } // end unnamed namespace
 
 std::string
-MasterLexer::Token::getErrorText() const {
+MasterToken::getErrorText() const {
     if (type_ != ERROR) {
         isc_throw(InvalidOperation,
-                  "Token::getErrorText() for non error type");
+                  "MasterToken::getErrorText() for non error type");
     }
 
     // The class integrity ensures the following:
@@ -232,14 +288,12 @@ namespace master_lexer_internal {
 // Note that these need to be defined here so that they can refer to
 // the details of MasterLexerImpl.
 
-typedef MasterLexer::Token Token; // convenience shortcut
-
 bool
 State::wasLastEOL(const MasterLexer& lexer) const {
     return (lexer.impl_->last_was_eol_);
 }
 
-const MasterLexer::Token&
+const MasterToken&
 State::getToken(const MasterLexer& lexer) const {
     return (lexer.impl_->token_);
 }
@@ -269,7 +323,7 @@ public:
         if (c != '\n') {
             getLexerImpl(lexer)->source_->ungetChar();
         }
-        getLexerImpl(lexer)->token_ = Token(Token::END_OF_LINE);
+        getLexerImpl(lexer)->token_ = MasterToken(MasterToken::END_OF_LINE);
         getLexerImpl(lexer)->last_was_eol_ = true;
     }
 };
@@ -340,24 +394,24 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
         if (c == InputSource::END_OF_STREAM) {
             lexerimpl.last_was_eol_ = false;
             if (paren_count != 0) {
-                lexerimpl.token_ = Token(Token::UNBALANCED_PAREN);
+                lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN);
                 paren_count = 0; // reset to 0; this helps in lenient mode.
                 return (NULL);
             }
-            lexerimpl.token_ = Token(Token::END_OF_FILE);
+            lexerimpl.token_ = MasterToken(MasterToken::END_OF_FILE);
             return (NULL);
         } else if (c == ' ' || c == '\t') {
             // If requested and we are not in (), recognize the initial space.
             if (lexerimpl.last_was_eol_ && paren_count == 0 &&
                 (options & MasterLexer::INITIAL_WS) != 0) {
                 lexerimpl.last_was_eol_ = false;
-                lexerimpl.token_ = Token(Token::INITIAL_WS);
+                lexerimpl.token_ = MasterToken(MasterToken::INITIAL_WS);
                 return (NULL);
             }
         } else if (c == '\n') {
             lexerimpl.last_was_eol_ = true;
             if (paren_count == 0) { // we don't recognize EOL if we are in ()
-                lexerimpl.token_ = Token(Token::END_OF_LINE);
+                lexerimpl.token_ = MasterToken(MasterToken::END_OF_LINE);
                 return (NULL);
             }
         } else if (c == '\r') {
@@ -373,11 +427,11 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
         } else if (c == ')') {
             lexerimpl.last_was_eol_ = false;
             if (paren_count == 0) {
-                lexerimpl.token_ = Token(Token::UNBALANCED_PAREN);
+                lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN);
                 return (NULL);
             }
             --paren_count;
-        } else if (isdigit(c)) {
+        } else if ((options & MasterLexer::NUMBER) != 0 &&isdigit(c)) {
             lexerimpl.last_was_eol_ = false;
             // this character will be handled in the number state
             lexerimpl.source_->ungetChar();
@@ -404,8 +458,11 @@ String::handle(MasterLexer& lexer) const {
 
         if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
             getLexerImpl(lexer)->source_->ungetChar();
+            // make sure it nul-terminated as a c-str (excluded from token
+            // data).
+            data.push_back('\0');
             getLexerImpl(lexer)->token_ =
-                MasterLexer::Token(&data.at(0), data.size());
+                MasterToken(&data.at(0), data.size() - 1);
             return;
         }
         escaped = (c == '\\' && !escaped);
@@ -415,7 +472,7 @@ String::handle(MasterLexer& lexer) const {
 
 void
 QString::handle(MasterLexer& lexer) const {
-    MasterLexer::Token& token = getLexerImpl(lexer)->token_;
+    MasterToken& token = getLexerImpl(lexer)->token_;
     std::vector<char>& data = getLexerImpl(lexer)->data_;
     data.clear();
 
@@ -423,7 +480,7 @@ QString::handle(MasterLexer& lexer) const {
     while (true) {
         const int c = getLexerImpl(lexer)->source_->getChar();
         if (c == InputSource::END_OF_STREAM) {
-            token = Token(Token::UNEXPECTED_END);
+            token = MasterToken(MasterToken::UNEXPECTED_END);
             return;
         } else if (c == '"') {
             if (escaped) {
@@ -432,12 +489,15 @@ QString::handle(MasterLexer& lexer) const {
                 escaped = false;
                 data.back() = '"';
             } else {
-                token = MasterLexer::Token(&data.at(0), data.size(), true);
+                // make sure it nul-terminated as a c-str (excluded from token
+                // data).  This also simplifies the case of an empty string.
+                data.push_back('\0');
+                token = MasterToken(&data.at(0), data.size() - 1, true);
                 return;
             }
         } else if (c == '\n' && !escaped) {
             getLexerImpl(lexer)->source_->ungetChar();
-            token = Token(Token::UNBALANCED_QUOTES);
+            token = MasterToken(MasterToken::UNBALANCED_QUOTES);
             return;
         } else {
             escaped = (c == '\\' && !escaped);
@@ -448,9 +508,7 @@ QString::handle(MasterLexer& lexer) const {
 
 void
 Number::handle(MasterLexer& lexer) const {
-    MasterLexer::Token& token = getLexerImpl(lexer)->token_;
-    // Do we want to support octal and/or hex here?
-    const int base = 10;
+    MasterToken& token = getLexerImpl(lexer)->token_;
 
     // It may yet turn out to be a string, so we first
     // collect all the data
@@ -465,13 +523,20 @@ Number::handle(MasterLexer& lexer) const {
         if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
             getLexerImpl(lexer)->source_->ungetChar();
             if (digits_only) {
-                // Close the string for strtoul
+                // Close the string for lexical_cast
                 data.push_back('\0');
-                token = MasterLexer::Token(strtoul(&data.at(0),
-                                                   NULL, base));
+                try {
+                    const uint32_t number32 =
+                        boost::lexical_cast<uint32_t, const char*>(&data[0]);
+                    token = MasterToken(number32);
+                } catch (const boost::bad_lexical_cast&) {
+                    // Since we already know we have only digits,
+                    // range should be the only possible problem.
+                    token = MasterToken(MasterToken::NUMBER_OUT_OF_RANGE);
+                }
             } else {
-                token = MasterLexer::Token(&data.at(0),
-                                           data.size());
+                data.push_back('\0'); // see String::handle()
+                token = MasterToken(&data.at(0), data.size() - 1);
             }
             return;
         }
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 431e0ce..35586fe 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -28,225 +28,6 @@ namespace master_lexer_internal {
 class State;
 }
 
-/// \brief Tokenizer for parsing DNS master files.
-///
-/// The \c MasterLexer class provides tokenize interfaces for parsing DNS
-/// master files.  It understands some special rules of master files as
-/// defined in RFC 1035, such as comments, character escaping, and multi-line
-/// data, and provides the user application with the actual data in a
-/// more convenient form such as a std::string object.
-///
-/// In order to support the $INCLUDE notation, this class is designed to be
-/// able to operate on multiple files or input streams in the nested way.
-/// The \c pushSource() and \c popSource() methods correspond to the push
-/// and pop operations.
-///
-/// While this class is public, it is less likely to be used by normal
-/// applications; it's mainly expected to be used within this library,
-/// specifically by the \c MasterLoader class and \c Rdata implementation
-/// classes.
-///
-/// \note The error handling policy of this class is slightly different from
-/// that of other classes of this library.  We generally throw an exception
-/// for an invalid input, whether it's more likely to be a program error or
-/// a "user error", which means an invalid input that comes from outside of
-/// the library.  But, this class returns an error code for some certain
-/// types of user errors instead of throwing an exception.  Such cases include
-/// a syntax error identified by the lexer or a misspelled file name that
-/// causes a system error at the time of open.  This is based on the assumption
-/// that the main user of this class is a parser of master files, where
-/// we want to give an option to ignore some non fatal errors and continue
-/// the parsing.  This will be useful if it just performs overall error
-/// checks on a master file.  When the (immediate) caller needs to do explicit
-/// error handling, exceptions are not that a useful tool for error reporting
-/// because we cannot separate the normal and error cases anyway, which would
-/// be one major advantage when we use exceptions.  And, exceptions are
-/// generally more expensive, either when it happens or just by being able
-/// to handle with \c try and \c catch (depending on the underlying
-/// implementation of the exception handling).  For these reasons, some of
-/// this class does not throw for an error that would be reported as an
-/// exception in other classes.
-class MasterLexer {
-    friend class master_lexer_internal::State;
-public:
-    /// \brief Exception thrown when we fail to read from the input
-    /// stream or file.
-    struct ReadError : public Unexpected {
-        ReadError(const char* file, size_t line, const char* what) :
-            Unexpected(file, line, what)
-        {}
-    };
-
-    class Token;       // we define it separately for better readability
-
-    /// \brief Options for getNextToken.
-    ///
-    /// A compound option, indicating multiple options are set, can be
-    /// specified using the logical OR operator (operator|()).
-    enum Options {
-        NONE = 0,               ///< No option
-        INITIAL_WS = 1, ///< recognize begin-of-line spaces after an
-                        ///< end-of-line
-        QSTRING = 2,    ///< recognize quoted string
-        NUMBER = 4   ///< recognize numeric text as integer
-    };
-
-    /// \brief The constructor.
-    ///
-    /// \throw std::bad_alloc Internal resource allocation fails (rare case).
-    MasterLexer();
-
-    /// \brief The destructor.
-    ///
-    /// It internally closes any remaining input sources.
-    ~MasterLexer();
-
-    /// \brief Open a file and make it the current input source of MasterLexer.
-    ///
-    /// The opened file can be explicitly closed by the \c popSource() method;
-    /// if \c popSource() is not called within the lifetime of the
-    /// \c MasterLexer, it will be closed in the destructor.
-    ///
-    /// In the case possible system errors in opening the file (most likely
-    /// because of specifying a non-existent or unreadable file), it returns
-    /// false, and if the optional \c error parameter is non NULL, it will be
-    /// set to a description of the error (any existing content of the string
-    /// will be discarded).  If opening the file succeeds, the given
-    /// \c error parameter will be intact.
-    ///
-    /// Note that this method has two styles of error reporting: one by
-    /// returning \c false (and setting \c error optionally) and the other
-    /// by throwing an exception.  See the note for the class description
-    /// about the distinction.
-    ///
-    /// \throw InvalidParameter filename is NULL
-    /// \param filename A non NULL string specifying a master file
-    /// \param error If non null, a placeholder to set error description in
-    /// case of failure.
-    ///
-    /// \return true if pushing the file succeeds; false otherwise.
-    bool pushSource(const char* filename, std::string* error = NULL);
-
-    /// \brief Make the given stream the current input source of MasterLexer.
-    ///
-    /// The caller still holds the ownership of the passed stream; it's the
-    /// caller's responsibility to keep it valid as long as it's used in
-    /// \c MasterLexer or to release any resource for the stream after that.
-    /// The caller can explicitly tell \c MasterLexer to stop using the
-    /// stream by calling the \c popSource() method.
-    ///
-    /// \param input An input stream object that produces textual
-    /// representation of DNS RRs.
-    void pushSource(std::istream& input);
-
-    /// \brief Stop using the most recently opened input source (file or
-    /// stream).
-    ///
-    /// If it's a file, the previously opened file will be closed internally.
-    /// If it's a stream, \c MasterLexer will simply stop using
-    /// the stream; the caller can assume it will be never used in
-    /// \c MasterLexer thereafter.
-    ///
-    /// This method must not be called when there is no source pushed for
-    /// \c MasterLexer.  This method is otherwise exception free.
-    ///
-    /// \throw isc::InvalidOperation Called with no pushed source.
-    void popSource();
-
-    /// \brief Return the name of the current input source name.
-    ///
-    /// If it's a file, it will be the C string given at the corresponding
-    /// \c pushSource() call, that is, its filename.  If it's a stream, it will
-    /// be formatted as \c "stream-%p" where \c %p is hex representation
-    /// of the address of the stream object.
-    ///
-    /// If there is no opened source at the time of the call, this method
-    /// returns an empty string.
-    ///
-    /// \throw std::bad_alloc Resource allocation failed for string
-    /// construction (rare case)
-    ///
-    /// \return A string representation of the current source (see the
-    /// description)
-    std::string getSourceName() const;
-
-    /// \brief Return the input source line number.
-    ///
-    /// If there is an opened source, the return value will be a non-0
-    /// integer indicating the line number of the current source where
-    /// the \c MasterLexer is currently working.  The expected usage of
-    /// this value is to print a helpful error message when parsing fails
-    /// by specifically identifying the position of the error.
-    ///
-    /// If there is no opened source at the time of the call, this method
-    /// returns 0.
-    ///
-    /// \throw None
-    ///
-    /// \return The current line number of the source (see the description)
-    size_t getSourceLine() const;
-
-    /// \brief Parse and return another token from the input.
-    ///
-    /// It reads a bit of the last opened source and produces another token
-    /// found in it.
-    ///
-    /// This method does not provide the strong exception guarantee. Generally,
-    /// if it throws, the object should not be used any more and should be
-    /// discarded. It was decided all the exceptions thrown from here are
-    /// serious enough that aborting the loading process is the only reasonable
-    /// recovery anyway, so the strong exception guarantee is not needed.
-    ///
-    /// \param options The options can be used to modify the tokenization.
-    ///     The method can be made reporting things which are usually ignored
-    ///     by this parameter. Multiple options can be passed at once by
-    ///     bitwise or (eg. option1 | option 2). See description of available
-    ///     options.
-    /// \return Next token found in the input. Note that the token refers to
-    ///     some internal data in the lexer. It is valid only until
-    ///     getNextToken or ungetToken is called. Also, the token becomes
-    ///     invalid when the lexer is destroyed.
-    /// \throw isc::InvalidOperation in case the source is not available. This
-    ///     may mean the pushSource() has not been called yet, or that the
-    ///     current source has been read past the end.
-    /// \throw ReadError in case there's problem reading from the underlying
-    ///     source (eg. I/O error in the file on the disk).
-    /// \throw std::bad_alloc in case allocation of some internal resources
-    ///     or the token fail.
-    const Token& getNextToken(Options options = NONE);
-
-    /// \brief Return the last token back to the lexer.
-    ///
-    /// The method undoes the lasts call to getNextToken(). If you call the
-    /// getNextToken() again with the same options, it'll return the same
-    /// token. If the options are different, it may return a different token,
-    /// but it acts as if the previous getNextToken() was never called.
-    ///
-    /// It is possible to return only one token back in time (you can't call
-    /// ungetToken() twice in a row without calling getNextToken() in between
-    /// successfully).
-    ///
-    /// It does not work after change of source (by pushSource or popSource).
-    ///
-    /// \throw isc::InvalidOperation If called second time in a row or if
-    ///     getNextToken() was not called since the last change of the source.
-    void ungetToken();
-
-private:
-    struct MasterLexerImpl;
-    MasterLexerImpl* impl_;
-};
-
-/// \brief Operator to combine \c MasterLexer options
-///
-/// This is a trivial shortcut so that compound options can be specified
-/// in an intuitive way.
-inline MasterLexer::Options
-operator|(MasterLexer::Options o1, MasterLexer::Options o2) {
-    return (static_cast<MasterLexer::Options>(
-                static_cast<unsigned>(o1) | static_cast<unsigned>(o2)));
-}
-
 /// \brief Tokens for \c MasterLexer
 ///
 /// This is a simple value-class encapsulating a type of a lexer token and
@@ -261,7 +42,7 @@ operator|(MasterLexer::Options o1, MasterLexer::Options o2) {
 /// (using the default version of copy constructor and assignment operator),
 /// but it's mainly for internal implementation convenience.  Applications will
 /// simply refer to Token object as a reference via the \c MasterLexer class.
-class MasterLexer::Token {
+class MasterToken {
 public:
     /// \brief Enumeration for token types
     ///
@@ -292,6 +73,8 @@ public:
         UNBALANCED_QUOTES,      ///< Unbalanced quotations detected
         NO_TOKEN_PRODUCED, ///< No token was produced. This means programmer
                            /// error and should never get out of the lexer.
+        NUMBER_OUT_OF_RANGE, ///< Number was out of range
+        BAD_NUMBER,    ///< Number is expected but not recognized
         MAX_ERROR_CODE ///< Max integer corresponding to valid error codes.
                        /// (excluding this one). Mainly for internal use.
     };
@@ -307,6 +90,13 @@ public:
     /// the region.  On the other hand, it is not ensured that the string
     /// is nul-terminated.  So the usual string manipulation API may not work
     /// as expected.
+    ///
+    /// The `MasterLexer` implementation ensures that there are at least
+    /// len + 1 bytes of valid memory region starting from beg, and that
+    /// beg[len] is \0.  This means the application can use the bytes as a
+    /// validly nul-terminated C string if there is no intermediate nul
+    /// character.  Note also that due to this property beg is always non
+    /// NULL; for an empty string len will be set to 0 and beg[0] is \0.
     struct StringRegion {
         const char* beg;        ///< The start address of the string
         size_t len;             ///< The length of the string in bytes
@@ -317,7 +107,7 @@ public:
     /// \throw InvalidParameter A value type token is specified.
     /// \param type The type of the token.  It must indicate a non-value
     /// type (not larger than \c NOVALUE_TYPE_MAX).
-    explicit Token(Type type) : type_(type) {
+    explicit MasterToken(Type type) : type_(type) {
         if (type > NOVALUE_TYPE_MAX) {
             isc_throw(InvalidParameter, "Token per-type constructor "
                       "called with invalid type: " << type);
@@ -339,7 +129,7 @@ public:
     /// \param str_beg The start address of the string
     /// \param str_len The size of the string in bytes
     /// \param quoted true if it's a quoted string; false otherwise.
-    Token(const char* str_beg, size_t str_len, bool quoted = false) :
+    MasterToken(const char* str_beg, size_t str_len, bool quoted = false) :
         type_(quoted ? QSTRING : STRING)
     {
         val_.str_region_.beg = str_beg;
@@ -350,7 +140,7 @@ public:
     ///
     /// \brief number An unsigned 32-bit integer corresponding to the token
     /// value.
-    explicit Token(uint32_t number) : type_(NUMBER) {
+    explicit MasterToken(uint32_t number) : type_(NUMBER) {
         val_.number_ = number;
     }
 
@@ -358,7 +148,7 @@ public:
     ///
     /// \throw InvalidParameter Invalid error code value is specified.
     /// \brief error_code A pre-defined constant of \c ErrorCode.
-    explicit Token(ErrorCode error_code) : type_(ERROR) {
+    explicit MasterToken(ErrorCode error_code) : type_(ERROR) {
         if (!(error_code < MAX_ERROR_CODE)) {
             isc_throw(InvalidParameter, "Invalid master lexer error code: "
                       << error_code);
@@ -475,6 +265,310 @@ private:
     } val_;
 };
 
+/// \brief Tokenizer for parsing DNS master files.
+///
+/// The \c MasterLexer class provides tokenize interfaces for parsing DNS
+/// master files.  It understands some special rules of master files as
+/// defined in RFC 1035, such as comments, character escaping, and multi-line
+/// data, and provides the user application with the actual data in a
+/// more convenient form such as a std::string object.
+///
+/// In order to support the $INCLUDE notation, this class is designed to be
+/// able to operate on multiple files or input streams in the nested way.
+/// The \c pushSource() and \c popSource() methods correspond to the push
+/// and pop operations.
+///
+/// While this class is public, it is less likely to be used by normal
+/// applications; it's mainly expected to be used within this library,
+/// specifically by the \c MasterLoader class and \c Rdata implementation
+/// classes.
+///
+/// \note The error handling policy of this class is slightly different from
+/// that of other classes of this library.  We generally throw an exception
+/// for an invalid input, whether it's more likely to be a program error or
+/// a "user error", which means an invalid input that comes from outside of
+/// the library.  But, this class returns an error code for some certain
+/// types of user errors instead of throwing an exception.  Such cases include
+/// a syntax error identified by the lexer or a misspelled file name that
+/// causes a system error at the time of open.  This is based on the assumption
+/// that the main user of this class is a parser of master files, where
+/// we want to give an option to ignore some non fatal errors and continue
+/// the parsing.  This will be useful if it just performs overall error
+/// checks on a master file.  When the (immediate) caller needs to do explicit
+/// error handling, exceptions are not that a useful tool for error reporting
+/// because we cannot separate the normal and error cases anyway, which would
+/// be one major advantage when we use exceptions.  And, exceptions are
+/// generally more expensive, either when it happens or just by being able
+/// to handle with \c try and \c catch (depending on the underlying
+/// implementation of the exception handling).  For these reasons, some of
+/// this class does not throw for an error that would be reported as an
+/// exception in other classes.
+class MasterLexer {
+    friend class master_lexer_internal::State;
+public:
+    /// \brief Exception thrown when we fail to read from the input
+    /// stream or file.
+    class ReadError : public Unexpected {
+    public:
+        ReadError(const char* file, size_t line, const char* what) :
+            Unexpected(file, line, what)
+        {}
+    };
+
+    /// \brief Exception thrown from a wrapper version of
+    /// \c MasterLexer::getNextToken() for non fatal errors.
+    ///
+    /// See the method description for more details.
+    ///
+    /// The \c token_ member variable (read-only) is set to a \c MasterToken
+    /// object of type ERROR indicating the reason for the error.
+    class LexerError : public Exception {
+    public:
+        LexerError(const char* file, size_t line, MasterToken error_token) :
+            Exception(file, line, error_token.getErrorText().c_str()),
+            token_(error_token)
+        {}
+        const MasterToken token_;
+    };
+
+    /// \brief Options for getNextToken.
+    ///
+    /// A compound option, indicating multiple options are set, can be
+    /// specified using the logical OR operator (operator|()).
+    enum Options {
+        NONE = 0,               ///< No option
+        INITIAL_WS = 1, ///< recognize begin-of-line spaces after an
+                        ///< end-of-line
+        QSTRING = 2,    ///< recognize quoted string
+        NUMBER = 4   ///< recognize numeric text as integer
+    };
+
+    /// \brief The constructor.
+    ///
+    /// \throw std::bad_alloc Internal resource allocation fails (rare case).
+    MasterLexer();
+
+    /// \brief The destructor.
+    ///
+    /// It internally closes any remaining input sources.
+    ~MasterLexer();
+
+    /// \brief Open a file and make it the current input source of MasterLexer.
+    ///
+    /// The opened file can be explicitly closed by the \c popSource() method;
+    /// if \c popSource() is not called within the lifetime of the
+    /// \c MasterLexer, it will be closed in the destructor.
+    ///
+    /// In the case possible system errors in opening the file (most likely
+    /// because of specifying a non-existent or unreadable file), it returns
+    /// false, and if the optional \c error parameter is non NULL, it will be
+    /// set to a description of the error (any existing content of the string
+    /// will be discarded).  If opening the file succeeds, the given
+    /// \c error parameter will be intact.
+    ///
+    /// Note that this method has two styles of error reporting: one by
+    /// returning \c false (and setting \c error optionally) and the other
+    /// by throwing an exception.  See the note for the class description
+    /// about the distinction.
+    ///
+    /// \throw InvalidParameter filename is NULL
+    /// \param filename A non NULL string specifying a master file
+    /// \param error If non null, a placeholder to set error description in
+    /// case of failure.
+    ///
+    /// \return true if pushing the file succeeds; false otherwise.
+    bool pushSource(const char* filename, std::string* error = NULL);
+
+    /// \brief Make the given stream the current input source of MasterLexer.
+    ///
+    /// The caller still holds the ownership of the passed stream; it's the
+    /// caller's responsibility to keep it valid as long as it's used in
+    /// \c MasterLexer or to release any resource for the stream after that.
+    /// The caller can explicitly tell \c MasterLexer to stop using the
+    /// stream by calling the \c popSource() method.
+    ///
+    /// \param input An input stream object that produces textual
+    /// representation of DNS RRs.
+    void pushSource(std::istream& input);
+
+    /// \brief Stop using the most recently opened input source (file or
+    /// stream).
+    ///
+    /// If it's a file, the previously opened file will be closed internally.
+    /// If it's a stream, \c MasterLexer will simply stop using
+    /// the stream; the caller can assume it will be never used in
+    /// \c MasterLexer thereafter.
+    ///
+    /// This method must not be called when there is no source pushed for
+    /// \c MasterLexer.  This method is otherwise exception free.
+    ///
+    /// \throw isc::InvalidOperation Called with no pushed source.
+    void popSource();
+
+    /// \brief Return the name of the current input source name.
+    ///
+    /// If it's a file, it will be the C string given at the corresponding
+    /// \c pushSource() call, that is, its filename.  If it's a stream, it will
+    /// be formatted as \c "stream-%p" where \c %p is hex representation
+    /// of the address of the stream object.
+    ///
+    /// If there is no opened source at the time of the call, this method
+    /// returns an empty string.
+    ///
+    /// \throw std::bad_alloc Resource allocation failed for string
+    /// construction (rare case)
+    ///
+    /// \return A string representation of the current source (see the
+    /// description)
+    std::string getSourceName() const;
+
+    /// \brief Return the input source line number.
+    ///
+    /// If there is an opened source, the return value will be a non-0
+    /// integer indicating the line number of the current source where
+    /// the \c MasterLexer is currently working.  The expected usage of
+    /// this value is to print a helpful error message when parsing fails
+    /// by specifically identifying the position of the error.
+    ///
+    /// If there is no opened source at the time of the call, this method
+    /// returns 0.
+    ///
+    /// \throw None
+    ///
+    /// \return The current line number of the source (see the description)
+    size_t getSourceLine() const;
+
+    /// \brief Parse and return another token from the input.
+    ///
+    /// It reads a bit of the last opened source and produces another token
+    /// found in it.
+    ///
+    /// This method does not provide the strong exception guarantee. Generally,
+    /// if it throws, the object should not be used any more and should be
+    /// discarded. It was decided all the exceptions thrown from here are
+    /// serious enough that aborting the loading process is the only reasonable
+    /// recovery anyway, so the strong exception guarantee is not needed.
+    ///
+    /// \param options The options can be used to modify the tokenization.
+    ///     The method can be made reporting things which are usually ignored
+    ///     by this parameter. Multiple options can be passed at once by
+    ///     bitwise or (eg. option1 | option 2). See description of available
+    ///     options.
+    /// \return Next token found in the input. Note that the token refers to
+    ///     some internal data in the lexer. It is valid only until
+    ///     getNextToken or ungetToken is called. Also, the token becomes
+    ///     invalid when the lexer is destroyed.
+    /// \throw isc::InvalidOperation in case the source is not available. This
+    ///     may mean the pushSource() has not been called yet, or that the
+    ///     current source has been read past the end.
+    /// \throw ReadError in case there's problem reading from the underlying
+    ///     source (eg. I/O error in the file on the disk).
+    /// \throw std::bad_alloc in case allocation of some internal resources
+    ///     or the token fail.
+    const MasterToken& getNextToken(Options options = NONE);
+
+    /// \brief Parse the input for the expected type of token.
+    ///
+    /// This method is a wrapper of the other version, customized for the case
+    /// where a particular type of token is expected as the next one.
+    /// More specifically, it's intended to be used to get tokens for RDATA
+    /// fields.  Since most RDATA types of fixed format, the token type is
+    /// often predictable and the method interface can be simplified.
+    ///
+    /// This method basically works as follows: it gets the type of the
+    /// expected token, calls the other version of \c getNextToken(Options),
+    /// and returns the token if it's of the expected type (due to the usage
+    /// assumption this should be normally the case).  There are some non
+    /// trivial details though:
+    ///
+    /// - If the expected type is MasterToken::QSTRING, both quoted and
+    ///   unquoted strings are recognized and returned.
+    /// - If the optional \c eol_ok parameter is \c true (very rare case),
+    ///   MasterToken::END_OF_LINE and MasterToken::END_OF_FILE are recognized
+    ///   and returned if they are found instead of the expected type of
+    ///   token.
+    /// - If the next token is not of the expected type (including the case
+    ///   a number is expected but it's out of range), ungetToken() is
+    ///   internally called so the caller can re-read that token.
+    /// - If other types or errors (such as unbalanced parentheses) are
+    ///   detected, the erroneous part isn't "ungotten"; the caller can
+    ///   continue parsing after that part.
+    ///
+    /// In some very rare cases where the RDATA has an optional trailing field,
+    /// the \c eol_ok parameter would be set to \c true.  This way the caller
+    /// can handle both cases (the field does or does not exist) by a single
+    /// call to this method.  In all other cases \c eol_ok should be set to
+    /// \c false, and that is the default and can be omitted.
+    ///
+    /// Unlike the other version of \c getNextToken(Options), this method
+    /// throws an exception of type \c LexerError for non fatal errors such as
+    /// broken syntax or encountering an unexpected type of token.  This way
+    /// the caller can write RDATA parser code without bothering to handle
+    /// errors for each field.  For example, pseudo parser code for MX RDATA
+    /// would look like this:
+    /// \code
+    ///    const uint32_t pref =
+    ///        lexer.getNextToken(MasterToken::NUMBER).getNumber();
+    ///    // check if pref is the uint16_t range; no other check is needed.
+    ///    const Name mx(lexer.getNextToken(MasterToken::STRING).getString());
+    /// \endcode
+    ///
+    /// In the case where \c LexerError exception is thrown, it's expected
+    /// to be handled comprehensively for the parser of the RDATA or at a
+    /// higher layer.  The \c token_ member variable of the corresponding
+    /// \c LexerError exception object stores a token of type
+    /// \c MasterToken::ERROR that indicates the reason for the error.
+    ///
+    /// Due to the specific intended usage of this method, only a subset
+    /// of \c MasterToken::Type values are acceptable for the \c expect
+    /// parameter: \c MasterToken::STRING, \c MasterToken::QSTRING, and
+    /// \c MasterToken::NUMBER.  Specifying other values will result in
+    /// an \c InvalidParameter exception.
+    ///
+    /// \throw InvalidParameter The expected token type is not allowed for
+    /// this method.
+    /// \throw LexerError The lexer finds non fatal error or it finds an
+    /// \throw other Anything the other version of getNextToken() can throw.
+    ///
+    /// \param expect Expected type of token.  Must be either STRING, QSTRING,
+    /// or NUMBER.
+    /// \param eol_ok \c true iff END_OF_LINE or END_OF_FILE is acceptable.
+    /// \return The expected type of token.
+    const MasterToken& getNextToken(MasterToken::Type expect,
+                                    bool eol_ok = false);
+
+    /// \brief Return the last token back to the lexer.
+    ///
+    /// The method undoes the lasts call to getNextToken(). If you call the
+    /// getNextToken() again with the same options, it'll return the same
+    /// token. If the options are different, it may return a different token,
+    /// but it acts as if the previous getNextToken() was never called.
+    ///
+    /// It is possible to return only one token back in time (you can't call
+    /// ungetToken() twice in a row without calling getNextToken() in between
+    /// successfully).
+    ///
+    /// It does not work after change of source (by pushSource or popSource).
+    ///
+    /// \throw isc::InvalidOperation If called second time in a row or if
+    ///     getNextToken() was not called since the last change of the source.
+    void ungetToken();
+
+private:
+    struct MasterLexerImpl;
+    MasterLexerImpl* impl_;
+};
+
+/// \brief Operator to combine \c MasterLexer options
+///
+/// This is a trivial shortcut so that compound options can be specified
+/// in an intuitive way.
+inline MasterLexer::Options
+operator|(MasterLexer::Options o1, MasterLexer::Options o2) {
+    return (static_cast<MasterLexer::Options>(
+                static_cast<unsigned>(o1) | static_cast<unsigned>(o2)));
+}
+
 } // namespace dns
 } // namespace isc
 #endif  // MASTER_LEXER_H
diff --git a/src/lib/dns/master_lexer_state.h b/src/lib/dns/master_lexer_state.h
index 2a64c9d..f296c1c 100644
--- a/src/lib/dns/master_lexer_state.h
+++ b/src/lib/dns/master_lexer_state.h
@@ -43,10 +43,10 @@ namespace master_lexer_internal {
 /// state, so it makes more sense to separate the interface for the transition
 /// from the initial state.
 ///
-/// When an object of a specific state class completes the session, it
-/// normally sets the identified token in the lexer, and returns NULL;
-/// if more transition is necessary, it returns a pointer to the next state
-/// object.
+/// If the whole lexer transition is completed within start(), it sets the
+/// identified token and returns NULL; otherwise it returns a pointer to
+/// an object of a specific state class that completes the session
+/// on the call of handle().
 ///
 /// As is usual in the state design pattern, the \c State class is made
 /// a friend class of \c MasterLexer and can refer to its internal details.
@@ -119,7 +119,7 @@ public:
     /// purposes.
     ///@{
     bool wasLastEOL(const MasterLexer& lexer) const;
-    const MasterLexer::Token& getToken(const MasterLexer& lexer) const;
+    const MasterToken& getToken(const MasterLexer& lexer) const;
     size_t getParenCount(const MasterLexer& lexer) const;
     ///@}
 
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 9f3b7ea..60a97d6 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -13,6 +13,14 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dns/master_loader.h>
+#include <dns/master_lexer.h>
+#include <dns/name.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+using std::string;
 
 namespace isc {
 namespace dns {
@@ -23,45 +31,155 @@ public:
                      const Name& zone_origin,
                      const RRClass& zone_class,
                      const MasterLoaderCallbacks& callbacks,
-                     const AddRRsetCallback& add_callback,
+                     const AddRRCallback& add_callback,
                      MasterLoader::Options options) :
         lexer_(),
         zone_origin_(zone_origin),
         zone_class_(zone_class),
         callbacks_(callbacks),
         add_callback_(add_callback),
-        options_(options)
-    {
-        lexer_.pushSource(master_file);
+        options_(options),
+        master_file_(master_file),
+        initialized_(false),
+        ok_(true),
+        complete_(false)
+    {}
+
+    void pushSource(const std::string& filename) {
+        std::string error;
+        if (!lexer_.pushSource(filename.c_str(), &error)) {
+            ok_ = false;
+            callbacks_.error("", 0, error);
+        }
+    }
+
+    // Get a string token. Handle it as error if it is not string.
+    const string getString() {
+        return (lexer_.getNextToken(MasterToken::QSTRING).getString());
     }
 
     bool loadIncremental(size_t count_limit) {
+        if (complete_) {
+            isc_throw(isc::InvalidOperation,
+                      "Trying to load when already loaded");
+        }
+        if (!initialized_) {
+            pushSource(master_file_);
+            initialized_ = true;
+        }
         size_t count = 0;
-        bool done = false;
-        do {
-            // Code goes here
-        } while (!done && (count_limit != 0 && ++count < count_limit));
-        // add remaining rrset that was being built (TODO)
-        return (false);
+        while (ok_ && count < count_limit) {
+            try {
+                // Skip all EOLNs (empty lines) and finish on EOF
+                bool empty = true;
+                do {
+                    const MasterToken& empty_token(lexer_.getNextToken());
+                    if (empty_token.getType() == MasterToken::END_OF_FILE) {
+                        // TODO: Check if this is the last source, possibly pop
+                        return (true);
+                    }
+                    empty = empty_token.getType() == MasterToken::END_OF_LINE;
+                } while (empty);
+                // Return the last token, as it was not empty
+                lexer_.ungetToken();
+
+                const MasterToken::StringRegion
+                    name_string(lexer_.getNextToken(MasterToken::QSTRING).
+                                getStringRegion());
+                // TODO $ handling
+                const Name name(name_string.beg, name_string.len,
+                                &zone_origin_);
+                // TODO: Some more flexibility. We don't allow omitting
+                // anything yet
+
+                // The parameters
+                const RRTTL ttl(getString());
+                const RRClass rrclass(getString());
+                const RRType rrtype(getString());
+
+                // TODO: Some more validation?
+                if (rrclass != zone_class_) {
+                    // It doesn't really matter much what type of exception
+                    // we throw, we catch it just below.
+                    isc_throw(isc::BadValue, "Class mismatch: " << rrclass <<
+                              "vs. " << zone_class_);
+                }
+
+                const rdata::RdataPtr data(rdata::createRdata(rrtype, rrclass,
+                                                              lexer_,
+                                                              &zone_origin_,
+                                                              options_,
+                                                              callbacks_));
+                // In case we get NULL, it means there was error creating
+                // the Rdata. The errors should have been reported by
+                // callbacks_ already. We need to decide if we want to continue
+                // or not.
+                if (data != rdata::RdataPtr()) {
+                    add_callback_(name, rrclass, rrtype, ttl, data);
+
+                    // Good, we loaded another one
+                    ++count;
+                } else if (!(options_ & MANY_ERRORS)) {
+                    return (true);
+                }
+            } catch (const isc::Exception& e) {
+                // TODO: Do we want to list expected exceptions here instead?
+                callbacks_.error(lexer_.getSourceName(),
+                                 lexer_.getSourceLine(),
+                                 e.what());
+                if (options_ & MANY_ERRORS) {
+                    // We want to continue. Try to read until the end of line
+                    bool end = false;
+                    do {
+                        const MasterToken& token(lexer_.getNextToken());
+                        switch (token.getType()) {
+                            case MasterToken::END_OF_FILE:
+                                // TODO: Try pop in case this is not the only
+                                // source
+                                return (true);
+                            case MasterToken::END_OF_LINE:
+                                end = true;
+                                break;
+                            default:
+                                // Do nothing. This is just to make compiler
+                                // happy
+                                break;
+                        }
+                    } while (!end);
+                } else {
+                    // We abort on first error. We are therefore done.
+                    return (true);
+                }
+            }
+        }
+        // When there was a fatal error and ok is false, we say we are done.
+        return (!ok_);
     }
 
 private:
     MasterLexer lexer_;
-    const Name& zone_origin_;
+    const Name zone_origin_;
     const RRClass zone_class_;
     MasterLoaderCallbacks callbacks_;
-    AddRRsetCallback add_callback_;
+    AddRRCallback add_callback_;
     MasterLoader::Options options_;
-    RRsetPtr current_rrset_;
+    const std::string master_file_;
+    bool initialized_;
+    bool ok_;
+public:
+    bool complete_;
 };
 
 MasterLoader::MasterLoader(const char* master_file,
                            const Name& zone_origin,
                            const RRClass& zone_class,
                            const MasterLoaderCallbacks& callbacks,
-                           const AddRRsetCallback& add_callback,
+                           const AddRRCallback& add_callback,
                            Options options)
 {
+    if (add_callback.empty()) {
+        isc_throw(isc::InvalidParameter, "Empty add RR callback");
+    }
     impl_ = new MasterLoaderImpl(master_file, zone_origin,
                                  zone_class, callbacks, add_callback, options);
 }
@@ -72,7 +190,9 @@ MasterLoader::~MasterLoader() {
 
 bool
 MasterLoader::loadIncremental(size_t count_limit) {
-    return (impl_->loadIncremental(count_limit));
+    bool result = impl_->loadIncremental(count_limit);
+    impl_->complete_ = result;
+    return (result);
 }
 
 } // end namespace dns
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
index a150087..3079bf8 100644
--- a/src/lib/dns/master_loader.h
+++ b/src/lib/dns/master_loader.h
@@ -15,29 +15,85 @@
 #ifndef MASTER_LOADER_H
 #define MASTER_LOADER_H
 
-#include <dns/name.h>
-#include <dns/rrclass.h>
-#include <dns/master_lexer.h>
 #include <dns/master_loader_callbacks.h>
 
+#include <boost/noncopyable.hpp>
+
 namespace isc {
 namespace dns {
-class MasterLoader {
+
+class Name;
+class RRClass;
+
+/// \brief A class able to load DNS master files
+///
+/// This class is able to produce a stream of RRs from a master file.
+/// It is able to load all of the master file at once, or by blocks
+/// incrementally.
+///
+/// It reports the loaded RRs and encountered errors by callbacks.
+class MasterLoader : boost::noncopyable {
 public:
+    /// \brief Options how the parsing should work.
     enum Options {
-        DEFAULT = 0,
-        MANY_ERRORS        ///< Lenient mode
+        DEFAULT = 0,       ///< Nothing special.
+        MANY_ERRORS = 1    ///< Lenient mode.
     };
+    /// \brief Constructor
+    ///
+    /// This creates a master loader and provides it with all
+    /// relevant information.
+    ///
+    /// Except for the exceptions listed below, the constructor doesn't
+    /// throw. Most errors (like non-existent master file) are reported
+    /// by the callbacks during load() or loadIncremental().
+    ///
+    /// \param master_file Path to the file to load.
+    /// \param zone_origin The origin of zone to be expected inside
+    ///     the master file. Currently unused, but it is expected to
+    ///     be used for some validation.
+    /// \param zone_class The class of zone to be expected inside the
+    ///     master file.
+    /// \param callbacks The callbacks by which it should report problems.
+    /// \param add_callback The callback which would be called with each
+    ///     loaded RR.
+    /// \param options Options for the parsing, which is bitwise-or of
+    ///     the Options values or DEFAULT. If the MANY_ERRORS option is
+    ///     included, the parser tries to continue past errors. If it
+    ///     is not included, it stops at first encountered error.
+    /// \throw std::bad_alloc when there's not enough memory.
+    /// \throw isc::InvalidParameter if add_callback is empty.
     MasterLoader(const char* master_file,
                  const Name& zone_origin,
                  const RRClass& zone_class,
                  const MasterLoaderCallbacks& callbacks,
-                 const AddRRsetCallback& add_callback,
+                 const AddRRCallback& add_callback,
                  Options options = DEFAULT);
+    /// \brief Destructor
     ~MasterLoader();
 
+    /// \brief Load some RRs
+    ///
+    /// This method loads at most count_limit RRs and reports them. In case
+    /// an error (either fatal or without MANY_ERRORS) or end of file is
+    /// encountered, they may be less.
+    ///
+    /// \param count_limit Upper limit on the number of RRs loaded.
+    /// \return In case it stops because of the count limit, it returns false.
+    ///     It returns true if the loading is done.
+    /// \throw isc::InvalidOperation when called after loading was done
+    ///     already.
     bool loadIncremental(size_t count_limit);
-    //void load();
+    /// \brief Load everything
+    ///
+    /// This simply calls loadIncremental until the loading is done.
+    /// \throw isc::InvalidOperation when called after loading was done
+    ///     already.
+    void load() {
+        while (!loadIncremental(1000)) { // 1000 = arbitrary largish number
+            // Body intentionally left blank
+        }
+    }
 
 private:
     class MasterLoaderImpl;
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
index 2da6e47..44ddbfa 100644
--- a/src/lib/dns/master_loader_callbacks.h
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -23,9 +23,14 @@
 
 namespace isc {
 namespace dns {
-
-class AbstractRRset;
-typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
+class Name;
+class RRClass;
+class RRType;
+class RRTTL;
+namespace rdata {
+class Rdata;
+typedef boost::shared_ptr<Rdata> RdataPtr;
+}
 
 /// \brief Type of callback to add a RRset.
 ///
@@ -36,7 +41,10 @@ typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
 /// \param RRset The rrset to add. It does not contain the accompanying
 ///     RRSIG (if the zone is signed), they are reported with separate
 ///     calls to the callback.
-typedef boost::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
+typedef boost::function<void(const Name& name, const RRClass& rrclass,
+                             const RRType& rrtype, const RRTTL& rrttl,
+                             const rdata::RdataPtr& rdata)>
+    AddRRCallback;
 
 /// \brief Set of issue callbacks for a loader.
 ///
@@ -119,4 +127,4 @@ private:
 }
 }
 
-#endif // LOADER_CALLBACKS_H
+#endif // MASTER_LOADER_CALLBACKS_H
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index c7eaa13..9e96378 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -30,6 +30,7 @@
 #include <util/buffer.h>
 #include <dns/name.h>
 #include <dns/messagerenderer.h>
+#include <dns/master_lexer.h>
 #include <dns/rdata.h>
 #include <dns/rrparamregistry.h>
 #include <dns/rrtype.h>
@@ -65,7 +66,7 @@ createRdata(const RRType& rrtype, const RRClass& rrclass,
     RdataPtr rdata =
         RRParamRegistry::getRegistry().createRdata(rrtype, rrclass, buffer,
                                                    len);
-                                                   
+
     if (buffer.getPosition() - old_pos != len) {
         isc_throw(InvalidRdataLength, "RDLENGTH mismatch: " <<
                   buffer.getPosition() - old_pos << " != " << len);
@@ -81,6 +82,90 @@ createRdata(const RRType& rrtype, const RRClass& rrclass, const Rdata& source)
                                                        source));
 }
 
+namespace {
+void
+fromtextError(bool& error_issued, const MasterLexer& lexer,
+              MasterLoaderCallbacks& callbacks,
+              const MasterToken* token, const char* reason)
+{
+    // Don't be too noisy if there are many issues for single RDATA
+    if (error_issued) {
+        return;
+    }
+    error_issued = true;
+
+    if (token == NULL) {
+        callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+                        "createRdata from text failed: " + string(reason));
+        return;
+    }
+
+    switch (token->getType()) {
+    case MasterToken::STRING:
+    case MasterToken::QSTRING:
+        callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+                        "createRdata from text failed near '" +
+                        token->getString() + "': " + string(reason));
+        break;
+    case MasterToken::ERROR:
+        callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+                        "createRdata from text failed: " +
+                        token->getErrorText());
+        break;
+    default:
+        assert(false);
+    }
+}
+}
+
+RdataPtr
+createRdata(const RRType& rrtype, const RRClass& rrclass,
+            MasterLexer& lexer, const Name* origin,
+            MasterLoader::Options options,
+            MasterLoaderCallbacks& callbacks)
+{
+    RdataPtr rdata;
+
+    bool error_issued = false;
+    try {
+        rdata = RRParamRegistry::getRegistry().createRdata(
+            rrtype, rrclass, lexer, origin, options, callbacks);
+    } catch (const MasterLexer::LexerError& error) {
+        fromtextError(error_issued, lexer, callbacks, &error.token_, "");
+    } catch (const Exception& ex) {
+        // Catching all isc::Exception is too broad, but right now we don't
+        // have better granularity.  When we complete #2518 we can make this
+        // finer.
+        fromtextError(error_issued, lexer, callbacks, NULL, ex.what());
+    }
+    // Other exceptions mean a serious implementation bug or fatal system
+    // error; it doesn't make sense to catch and try to recover from them
+    // here.  Just propagate.
+
+    // Consume to end of line / file.
+    // Call callback via fromtextError once if there was an error.
+    do {
+        const MasterToken& token = lexer.getNextToken();
+        switch (token.getType()) {
+        case MasterToken::END_OF_LINE:
+            return (rdata);
+        case MasterToken::END_OF_FILE:
+            callbacks.warning(lexer.getSourceName(), lexer.getSourceLine(),
+                              "file does not end with newline");
+            return (rdata);
+        default:
+            rdata.reset();      // we'll return NULL
+            fromtextError(error_issued, lexer, callbacks, &token,
+                          "extra input text");
+            // Continue until we see EOL or EOF
+        }
+    } while (true);
+
+    // We shouldn't reach here
+    assert(false);
+    return (RdataPtr()); // add explicit return to silence some compilers
+}
+
 int
 compareNames(const Name& n1, const Name& n2) {
     size_t len1 = n1.getLength();
@@ -119,7 +204,8 @@ Generic::Generic(isc::util::InputBuffer& buffer, size_t rdata_len) {
     impl_ = new GenericImpl(data);
 }
 
-Generic::Generic(const std::string& rdata_string) {
+void
+Generic::constructHelper(const std::string& rdata_string) {
     istringstream iss(rdata_string);
     string unknown_mark;
     iss >> unknown_mark;
@@ -180,6 +266,34 @@ Generic::Generic(const std::string& rdata_string) {
     impl_ = new GenericImpl(data);
 }
 
+Generic::Generic(const std::string& rdata_string) {
+    constructHelper(rdata_string);
+}
+
+Generic::Generic(MasterLexer& lexer, const Name*,
+                 MasterLoader::Options,
+                 MasterLoaderCallbacks&)
+{
+    std::string s;
+
+    while (true) {
+        const MasterToken& token = lexer.getNextToken();
+        if ((token.getType() == MasterToken::END_OF_FILE) ||
+            (token.getType() == MasterToken::END_OF_LINE)) {
+            lexer.ungetToken(); // let the upper layer handle the end-of token
+            break;
+        }
+
+        if (!s.empty()) {
+            s += " ";
+        }
+
+        s += token.getString();
+    }
+
+    constructHelper(s);
+}
+
 Generic::~Generic() {
     delete impl_;
 }
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index f77ea6e..186ace2 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -15,11 +15,15 @@
 #ifndef RDATA_H
 #define RDATA_H 1
 
-#include <stdint.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+
+#include <exceptions/exceptions.h>
 
 #include <boost/shared_ptr.hpp>
 
-#include <exceptions/exceptions.h>
+#include <stdint.h>
 
 namespace isc {
 namespace util {
@@ -279,6 +283,11 @@ public:
     /// \param rdata_len The length in buffer of the \c Rdata.  In bytes.
     Generic(isc::util::InputBuffer& buffer, size_t rdata_len);
 
+    /// \brief Constructor from master lexer.
+    ///
+    Generic(MasterLexer& lexer, const Name* name,
+            MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
+
     ///
     /// \brief The destructor.
     virtual ~Generic();
@@ -367,7 +376,10 @@ public:
     /// \return > 0 if \c this would be sorted after \c other.
     virtual int compare(const Rdata& other) const;
     //@}
+
 private:
+    void constructHelper(const std::string& rdata_string);
+
     GenericImpl* impl_;
 };
 
@@ -472,6 +484,53 @@ RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
 /// \c Rdata object.
 RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                      const Rdata& source);
+
+/// \brief Create RDATA of a given pair of RR type and class using the
+/// master lexer.
+///
+/// This is a more generic form of factory from textual RDATA, and is mainly
+/// intended to be used internally by the master file parser (\c MasterLoader)
+/// of this library.
+///
+/// The \c lexer is expected to be at the beginning of textual RDATA of the
+/// specified type and class.  This function (and its underlying Rdata
+/// implementations) extracts necessary tokens from the lexer and constructs
+/// the RDATA from them.
+///
+/// Due to the intended usage of this version, this function handles error
+/// cases quite differently from other versions.  It internally catches
+/// most of syntax and semantics errors of the input (reported as exceptions),
+/// calls the corresponding callback specified by the \c callbacks parameters,
+/// and returns a NULL smart pointer.  If the caller rather wants to get
+/// an exception in these cases, it can use pass a callback that internally
+/// throws on error.  Some critical exceptions such as \c std::bad_alloc are
+/// still propagated to the upper layer as it doesn't make sense to try
+/// recovery from such a situation within this function.
+///
+/// Whether or not the creation succeeds, this function updates the lexer
+/// until it reaches either the end of line or file, starting from the end of
+/// the RDATA text (or the point of failure if the parsing fails in the
+/// middle of it).  The caller can therefore assume it's ready for reading
+/// the next data (which is normally a subsequent RR in the zone file) on
+/// return, whether or not this function succeeds.
+///
+/// \param rrtype An \c RRType object specifying the type/class pair.
+/// \param rrclass An \c RRClass object specifying the type/class pair.
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of any domain name fields
+/// of the RDATA that are non absolute.
+/// \param options Master loader options controlling how to deal with errors
+/// or non critical issues in the parsed RDATA.
+/// \param callbacks Callback to be called when an error or non critical issue
+/// is found.
+/// \return An \c RdataPtr object pointing to the created
+/// \c Rdata object.  Will be NULL if parsing fails.
+RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+                     MasterLexer& lexer, const Name* origin,
+                     MasterLoader::Options options,
+                     MasterLoaderCallbacks& callbacks);
+
 //@}
 
 ///
@@ -511,6 +570,6 @@ int compareNames(const Name& n1, const Name& n2);
 }
 #endif  // RDATA_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.cc b/src/lib/dns/rdata/in_1/aaaa_28.cc
index ce49a04..0466f1a 100644
--- a/src/lib/dns/rdata/in_1/aaaa_28.cc
+++ b/src/lib/dns/rdata/in_1/aaaa_28.cc
@@ -12,6 +12,15 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+
 #include <stdint.h>
 #include <string.h>
 
@@ -20,14 +29,6 @@
 #include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
 #include <sys/socket.h> // for AF_INET/AF_INET6
 
-#include <exceptions/exceptions.h>
-
-#include <util/buffer.h>
-#include <dns/exceptions.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-
 using namespace std;
 using namespace isc::util;
 
@@ -42,6 +43,16 @@ AAAA::AAAA(const std::string& addrstr) {
     }
 }
 
+AAAA::AAAA(MasterLexer& lexer, const Name*,
+           MasterLoader::Options, MasterLoaderCallbacks&)
+{
+    const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+    if (inet_pton(AF_INET6, token.getStringRegion().beg, &addr_) != 1) {
+        isc_throw(InvalidRdataText, "Failed to convert '"
+                  << token.getString() << "' to IN/AAAA RDATA");
+    }
+}
+
 AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) {
     if (rdata_len != sizeof(addr_)) {
         isc_throw(DNSMessageFORMERR,
diff --git a/src/lib/dns/rdata/template.cc b/src/lib/dns/rdata/template.cc
index ee1097e..6486e6a 100644
--- a/src/lib/dns/rdata/template.cc
+++ b/src/lib/dns/rdata/template.cc
@@ -34,6 +34,11 @@ using namespace isc::util;
 // If you added member functions specific to this derived class, you'll need
 // to implement them here, of course.
 
+MyType::MyType(MasterLexer& lexer, const Name* origin,
+               MasterLoader::Options options, MasterLoaderCallbacks& callbacks)
+{
+}
+
 MyType::MyType(const string& type_str) {
 }
 
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index f7f3a1a..2208d3f 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -37,10 +37,40 @@ using namespace std;
 using namespace boost;
 
 using namespace isc::util;
-using namespace isc::dns::rdata; 
+using namespace isc::dns::rdata;
 
 namespace isc {
 namespace dns {
+
+namespace rdata {
+
+RdataPtr
+AbstractRdataFactory::create(MasterLexer& lexer, const Name*,
+                             MasterLoader::Options,
+                             MasterLoaderCallbacks&) const
+{
+    std::string s;
+
+    while (true) {
+        const MasterToken& token = lexer.getNextToken();
+        if ((token.getType() == MasterToken::END_OF_FILE) ||
+            (token.getType() == MasterToken::END_OF_LINE)) {
+            lexer.ungetToken(); // let the upper layer handle the end-of token
+            break;
+        }
+
+        if (!s.empty()) {
+            s += " ";
+        }
+
+        s += token.getString();
+    }
+
+    return (create(s));
+}
+
+} // end of namespace isc::dns::rdata
+
 namespace {
 ///
 /// The following function and class are a helper to define case-insensitive
@@ -161,8 +191,10 @@ typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap;
 typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap;
 
 template <typename T>
-class RdataFactory : public AbstractRdataFactory {
+class OldRdataFactory : public AbstractRdataFactory {
 public:
+    using AbstractRdataFactory::create;
+
     virtual RdataPtr create(const string& rdata_str) const
     {
         return (RdataPtr(new T(rdata_str)));
@@ -179,6 +211,18 @@ public:
     }
 };
 
+template <typename T>
+class RdataFactory : public OldRdataFactory<T> {
+public:
+    using OldRdataFactory<T>::create;
+
+    virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+                            MasterLoader::Options options,
+                            MasterLoaderCallbacks& callbacks) const {
+        return (RdataPtr(new T(lexer, origin, options, callbacks)));
+    }
+};
+
 ///
 /// \brief The \c RRParamRegistryImpl class is the actual implementation of
 /// \c RRParamRegistry.
@@ -305,7 +349,7 @@ namespace {
 /// This could be simplified using strncasecmp(), but unfortunately it's not
 /// included in <cstring>.  To be as much as portable within the C++ standard
 /// we take the "in house" approach here.
-/// 
+///
 bool CICharEqual(char c1, char c2) {
     return (tolower(static_cast<unsigned char>(c1)) ==
             tolower(static_cast<unsigned char>(c2)));
@@ -528,5 +572,26 @@ RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
     return (RdataPtr(new rdata::generic::Generic(
                          dynamic_cast<const generic::Generic&>(source))));
 }
+
+RdataPtr
+RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
+                             MasterLexer& lexer, const Name* name,
+                             MasterLoader::Options options,
+                             MasterLoaderCallbacks& callbacks)
+{
+    RdataFactoryMap::const_iterator found =
+        impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass));
+    if (found != impl_->rdata_factories.end()) {
+        return (found->second->create(lexer, name, options, callbacks));
+    }
+
+    GenericRdataFactoryMap::const_iterator genfound =
+        impl_->genericrdata_factories.find(rrtype);
+    if (genfound != impl_->genericrdata_factories.end()) {
+        return (genfound->second->create(lexer, name, options, callbacks));
+    }
+
+    return (RdataPtr(new generic::Generic(lexer, name, options, callbacks)));
+}
 }
 }
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index b1ca225..56ae981 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -82,7 +82,7 @@ public:
     /// \name Factory methods for polymorphic creation.
     ///
     //@{
-    ///
+
     /// \brief Create RDATA from a string.
     ///
     /// This method creates from a string an \c Rdata object of specific class
@@ -91,7 +91,7 @@ public:
     /// \param rdata_str A string of textual representation of the \c Rdata.
     /// \return An \c RdataPtr object pointing to the created \c Rdata object.
     virtual RdataPtr create(const std::string& rdata_str) const = 0;
-    ///
+
     /// \brief Create RDATA from wire-format data.
     ///
     /// This method creates from wire-format binary data an \c Rdata object
@@ -103,7 +103,7 @@ public:
     /// \param rdata_len The length in buffer of the \c Rdata.  In bytes.
     /// \return An \c RdataPtr object pointing to the created \c Rdata object.
     virtual RdataPtr create(isc::util::InputBuffer& buffer, size_t rdata_len) const = 0;
-    ///
+
     /// \brief Create RDATA from another \c Rdata object of the same type.
     ///
     /// This method creates an \c Rdata object of specific class corresponding
@@ -118,6 +118,23 @@ public:
     /// be copied to the created \c Rdata object.
     /// \return An \c RdataPtr object pointing to the created \c Rdata object.
     virtual RdataPtr create(const rdata::Rdata& source) const = 0;
+
+    /// \brief Create RDATA using MasterLexer.
+    ///
+    /// This version of the method defines the entry point of factory
+    /// of a specific RR type and class for \c RRParamRegistry::createRdata()
+    /// that uses \c MasterLexer.  See its description for the expected
+    /// behavior and meaning of the parameters.
+    ///
+    /// \note Right now this is not defined as a pure virtual method and
+    /// provides the default implementation.  This is an intermediate
+    /// workaround until we implement the underlying constructor for all
+    /// supported \c Rdata classes; once it's completed the workaround
+    /// default implementation should be removed and this method should become
+    /// pure virtual.
+    virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+                            MasterLoader::Options options,
+                            MasterLoaderCallbacks& callbacks) const;
     //@}
 };
 
@@ -498,6 +515,23 @@ public:
     /// \c rdata::Rdata object.
     rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                                 const rdata::Rdata& source);
+
+    /// \brief Create RDATA using MasterLexer
+    ///
+    /// This method is expected to be used as the underlying implementation
+    /// of the same signature of \c rdata::createRdata().  One main
+    /// difference is that this method is only responsible for constructing
+    /// the Rdata; it doesn't update the lexer to reach the end of line or
+    /// file or doesn't care about whether there's an extra (garbage) token
+    /// after the textual RDATA representation.  Another difference is that
+    /// this method can throw on error and never returns a NULL pointer.
+    ///
+    /// For other details and parameters, see the description of
+    /// \c rdata::createRdata().
+    rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+                                MasterLexer& lexer, const Name* origin,
+                                MasterLoader::Options options,
+                                MasterLoaderCallbacks& callbacks);
     //@}
 
 private:
@@ -508,6 +542,6 @@ private:
 }
 #endif  // RRPARAMREGISTRY_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:
diff --git a/src/lib/dns/tests/master_lexer_state_unittest.cc b/src/lib/dns/tests/master_lexer_state_unittest.cc
index 5b9cf54..846c4c2 100644
--- a/src/lib/dns/tests/master_lexer_state_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_state_unittest.cc
@@ -24,7 +24,7 @@ using namespace isc::dns;
 using namespace master_lexer_internal;
 
 namespace {
-typedef MasterLexer::Token Token; // shortcut
+typedef MasterToken Token; // shortcut
 
 class MasterLexerStateTest : public ::testing::Test {
 protected:
@@ -260,7 +260,7 @@ TEST_F(MasterLexerStateTest, crlf) {
 // Commonly used check for string related test cases, checking if the given
 // token has expected values.
 void
-stringTokenCheck(const std::string& expected, const MasterLexer::Token& token,
+stringTokenCheck(const std::string& expected, const MasterToken& token,
                  bool quoted = false)
 {
     EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType());
@@ -269,6 +269,10 @@ stringTokenCheck(const std::string& expected, const MasterLexer::Token& token,
                              token.getStringRegion().beg +
                              token.getStringRegion().len);
     EXPECT_EQ(expected, actual);
+
+    // There should be "hidden" nul-terminator after the string data.
+    ASSERT_NE(static_cast<const char*>(NULL), token.getStringRegion().beg);
+    EXPECT_EQ(0, *(token.getStringRegion().beg + token.getStringRegion().len));
 }
 
 TEST_F(MasterLexerStateTest, string) {
@@ -365,6 +369,7 @@ TEST_F(MasterLexerStateTest, stringEscape) {
 TEST_F(MasterLexerStateTest, quotedString) {
     ss << "\"ignore-quotes\"\n";
     ss << "\"quoted string\" "; // space is part of the qstring
+    ss << "\"\" "; // empty quoted string
     // also check other separator characters. note that \r doesn't cause
     // UNBALANCED_QUOTES.  Not sure if it's intentional, but that's how the
     // BIND 9 version works, so we follow it (it should be too minor to matter
@@ -391,6 +396,11 @@ TEST_F(MasterLexerStateTest, quotedString) {
     s_qstring.handle(lexer);
     stringTokenCheck("quoted string", s_string.getToken(lexer), true);
 
+    // Empty string is okay as qstring
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("", s_string.getToken(lexer), true);
+
     // Also checks other separator characters within a qstring
     EXPECT_EQ(&s_qstring, State::start(lexer, options));
     s_qstring.handle(lexer);
@@ -457,12 +467,9 @@ TEST_F(MasterLexerStateTest, basicNumbers) {
     ss << "1 ";
     ss << "12345 ";
     ss << "4294967295 "; // 2^32-1
-    ss << "4294967296 "; // 2^32 (this overflows to 0, we
-                         // can consider failing on it, but
-                         // this is what bind9 does as well)
-    ss << "4294967297 "; // 2^32+1 (this overflows to 1, see
-                         // above)
-    ss << "1000000000000000000 "; // overflows to 2808348672
+    ss << "4294967296 "; // Out of range
+    ss << "340282366920938463463374607431768211456 ";
+                         // Very much out of range (2^128)
     ss << "005 ";        // Leading zeroes are ignored
     ss << "42;asdf\n";   // Number with comment
     ss << "37";          // Simple number again, here to make
@@ -470,47 +477,48 @@ TEST_F(MasterLexerStateTest, basicNumbers) {
                          // the tokenizer
     lexer.pushSource(ss);
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    // Ask the lexer to recognize numbers as well
+    const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     EXPECT_EQ(0, s_number.getToken(lexer).getNumber());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     EXPECT_EQ(1, s_number.getToken(lexer).getNumber());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     EXPECT_EQ(12345, s_number.getToken(lexer).getNumber());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
-    s_number.handle(lexer);
-    EXPECT_EQ(4294967295, s_number.getToken(lexer).getNumber());
-
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
-    EXPECT_EQ(0, s_number.getToken(lexer).getNumber());
+    EXPECT_EQ(4294967295u, s_number.getToken(lexer).getNumber());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
-    EXPECT_EQ(1, s_number.getToken(lexer).getNumber());
+    EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+              s_number.getToken(lexer).getErrorCode());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
-    EXPECT_EQ(2808348672, s_number.getToken(lexer).getNumber());
+    EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+              s_number.getToken(lexer).getErrorCode());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     EXPECT_EQ(5, s_number.getToken(lexer).getNumber());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     EXPECT_EQ(42, s_number.getToken(lexer).getNumber());
 
-    EXPECT_EQ(s_null, State::start(lexer, common_options));
+    EXPECT_EQ(s_null, State::start(lexer, options));
     EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
     EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     EXPECT_EQ(37, s_number.getToken(lexer).getNumber());
 
@@ -522,6 +530,8 @@ TEST_F(MasterLexerStateTest, basicNumbers) {
 // Test tokens that look like (or start out as) numbers,
 // but turn out to be strings. Tests include escaped characters.
 TEST_F(MasterLexerStateTest, stringNumbers) {
+    ss << "123 ";        // Should be read as a string if the
+                         // NUMBER option is not given
     ss << "-1 ";         // Negative numbers are interpreted
                          // as strings (unsigned integers only)
     ss << "123abc456 ";  // 'Numbers' containing non-digits should
@@ -537,45 +547,54 @@ TEST_F(MasterLexerStateTest, stringNumbers) {
 
     lexer.pushSource(ss);
 
+    // Note that common_options does not include MasterLexer::NUMBER,
+    // so the token should be recognized as a string
     EXPECT_EQ(&s_string, State::start(lexer, common_options));
     s_string.handle(lexer);
+    stringTokenCheck("123", s_string.getToken(lexer), false);
+
+    // Ask the lexer to recognize numbers as well
+    const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
+
+    EXPECT_EQ(&s_string, State::start(lexer, options));
+    s_string.handle(lexer);
     stringTokenCheck("-1", s_string.getToken(lexer), false);
 
     // Starts out as a number, but ends up being a string
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     stringTokenCheck("123abc456", s_number.getToken(lexer), false);
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer);
     stringTokenCheck("123\\456", s_number.getToken(lexer), false);
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' at end
     stringTokenCheck("3scaped\\ space", s_number.getToken(lexer));
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' at end
     stringTokenCheck("3scaped\\\ttab", s_number.getToken(lexer));
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' at end
     stringTokenCheck("3scaped\\(paren", s_number.getToken(lexer));
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' at end
     stringTokenCheck("3scaped\\)close", s_number.getToken(lexer));
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' at end
     stringTokenCheck("3scaped\\;comment", s_number.getToken(lexer));
 
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' in mid
     stringTokenCheck("3scaped\\\\", s_number.getToken(lexer));
 
     // Confirm the word that follows the escaped '\' is correctly recognized.
-    EXPECT_EQ(&s_number, State::start(lexer, common_options));
+    EXPECT_EQ(&s_number, State::start(lexer, options));
     s_number.handle(lexer); // recognize str, see ' ' at end
     stringTokenCheck("8ackslash", s_number.getToken(lexer));
 
diff --git a/src/lib/dns/tests/master_lexer_token_unittest.cc b/src/lib/dns/tests/master_lexer_token_unittest.cc
index 5f887f9..89a4f9c 100644
--- a/src/lib/dns/tests/master_lexer_token_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_token_unittest.cc
@@ -31,27 +31,27 @@ const size_t TEST_STRING_LEN = sizeof(TEST_STRING) - 1;
 class MasterLexerTokenTest : public ::testing::Test {
 protected:
     MasterLexerTokenTest() :
-        token_eof(MasterLexer::Token::END_OF_FILE),
+        token_eof(MasterToken::END_OF_FILE),
         token_str(TEST_STRING, TEST_STRING_LEN),
         token_num(42),
-        token_err(MasterLexer::Token::UNEXPECTED_END)
+        token_err(MasterToken::UNEXPECTED_END)
     {}
 
-    const MasterLexer::Token token_eof; // an example of non-value type token
-    const MasterLexer::Token token_str;
-    const MasterLexer::Token token_num;
-    const MasterLexer::Token token_err;
+    const MasterToken token_eof; // an example of non-value type token
+    const MasterToken token_str;
+    const MasterToken token_num;
+    const MasterToken token_err;
 };
 
 
 TEST_F(MasterLexerTokenTest, strings) {
     // basic construction and getter checks
-    EXPECT_EQ(MasterLexer::Token::STRING, token_str.getType());
+    EXPECT_EQ(MasterToken::STRING, token_str.getType());
     EXPECT_EQ(std::string("string token"), token_str.getString());
     std::string strval = "dummy"; // this should be replaced
     token_str.getString(strval);
     EXPECT_EQ(std::string("string token"), strval);
-    const MasterLexer::Token::StringRegion str_region =
+    const MasterToken::StringRegion str_region =
         token_str.getStringRegion();
     EXPECT_EQ(TEST_STRING, str_region.beg);
     EXPECT_EQ(TEST_STRING_LEN, str_region.len);
@@ -62,17 +62,17 @@ TEST_F(MasterLexerTokenTest, strings) {
     std::string expected_str("string token");
     expected_str.push_back('\0');
     EXPECT_EQ(expected_str,
-              MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString());
-    MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString(strval);
+              MasterToken(TEST_STRING, TEST_STRING_LEN + 1).getString());
+    MasterToken(TEST_STRING, TEST_STRING_LEN + 1).getString(strval);
     EXPECT_EQ(expected_str, strval);
 
     // Construct type of qstring
-    EXPECT_EQ(MasterLexer::Token::QSTRING,
-              MasterLexer::Token(TEST_STRING, sizeof(TEST_STRING), true).
+    EXPECT_EQ(MasterToken::QSTRING,
+              MasterToken(TEST_STRING, sizeof(TEST_STRING), true).
               getType());
     // if we explicitly set 'quoted' to false, it should be normal string
-    EXPECT_EQ(MasterLexer::Token::STRING,
-              MasterLexer::Token(TEST_STRING, sizeof(TEST_STRING), false).
+    EXPECT_EQ(MasterToken::STRING,
+              MasterToken(TEST_STRING, sizeof(TEST_STRING), false).
               getType());
 
     // getString/StringRegion() aren't allowed for non string(-variant) types
@@ -86,23 +86,23 @@ TEST_F(MasterLexerTokenTest, strings) {
 
 TEST_F(MasterLexerTokenTest, numbers) {
     EXPECT_EQ(42, token_num.getNumber());
-    EXPECT_EQ(MasterLexer::Token::NUMBER, token_num.getType());
+    EXPECT_EQ(MasterToken::NUMBER, token_num.getType());
 
     // It's copyable and assignable.
-    MasterLexer::Token token(token_num);
+    MasterToken token(token_num);
     EXPECT_EQ(42, token.getNumber());
-    EXPECT_EQ(MasterLexer::Token::NUMBER, token.getType());
+    EXPECT_EQ(MasterToken::NUMBER, token.getType());
 
     token = token_num;
     EXPECT_EQ(42, token.getNumber());
-    EXPECT_EQ(MasterLexer::Token::NUMBER, token.getType());
+    EXPECT_EQ(MasterToken::NUMBER, token.getType());
 
     // it's okay to replace it with a different type of token
     token = token_eof;
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, token.getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, token.getType());
 
     // Possible max value
-    token = MasterLexer::Token(0xffffffff);
+    token = MasterToken(0xffffffff);
     EXPECT_EQ(4294967295u, token.getNumber());
 
     // getNumber() isn't allowed for non number types
@@ -112,55 +112,52 @@ TEST_F(MasterLexerTokenTest, numbers) {
 
 TEST_F(MasterLexerTokenTest, novalues) {
     // Just checking we can construct them and getType() returns correct value.
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, token_eof.getType());
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE,
-              MasterLexer::Token(MasterLexer::Token::END_OF_LINE).getType());
-    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
-              MasterLexer::Token(MasterLexer::Token::INITIAL_WS).getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, token_eof.getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE,
+              MasterToken(MasterToken::END_OF_LINE).getType());
+    EXPECT_EQ(MasterToken::INITIAL_WS,
+              MasterToken(MasterToken::INITIAL_WS).getType());
 
     // Special types of tokens cannot have value-based types
-    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::STRING),
-                 isc::InvalidParameter);
-    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::QSTRING),
-                 isc::InvalidParameter);
-    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::NUMBER),
-                 isc::InvalidParameter);
-    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::ERROR),
-                 isc::InvalidParameter);
+    EXPECT_THROW(MasterToken t(MasterToken::STRING), isc::InvalidParameter);
+    EXPECT_THROW(MasterToken t(MasterToken::QSTRING), isc::InvalidParameter);
+    EXPECT_THROW(MasterToken t(MasterToken::NUMBER), isc::InvalidParameter);
+    EXPECT_THROW(MasterToken t(MasterToken::ERROR), isc::InvalidParameter);
 }
 
 TEST_F(MasterLexerTokenTest, errors) {
-    EXPECT_EQ(MasterLexer::Token::ERROR, token_err.getType());
-    EXPECT_EQ(MasterLexer::Token::UNEXPECTED_END, token_err.getErrorCode());
+    EXPECT_EQ(MasterToken::ERROR, token_err.getType());
+    EXPECT_EQ(MasterToken::UNEXPECTED_END, token_err.getErrorCode());
     EXPECT_EQ("unexpected end of input", token_err.getErrorText());
-    EXPECT_EQ("lexer not started",
-              MasterLexer::Token(MasterLexer::Token::NOT_STARTED).
+    EXPECT_EQ("lexer not started", MasterToken(MasterToken::NOT_STARTED).
               getErrorText());
     EXPECT_EQ("unbalanced parentheses",
-              MasterLexer::Token(MasterLexer::Token::UNBALANCED_PAREN).
+              MasterToken(MasterToken::UNBALANCED_PAREN).
+              getErrorText());
+    EXPECT_EQ("unbalanced quotes", MasterToken(MasterToken::UNBALANCED_QUOTES).
               getErrorText());
-    EXPECT_EQ("unbalanced quotes",
-              MasterLexer::Token(MasterLexer::Token::UNBALANCED_QUOTES).
+    EXPECT_EQ("no token produced", MasterToken(MasterToken::NO_TOKEN_PRODUCED).
               getErrorText());
-    EXPECT_EQ("no token produced",
-              MasterLexer::Token(MasterLexer::Token::NO_TOKEN_PRODUCED).
+    EXPECT_EQ("number out of range",
+              MasterToken(MasterToken::NUMBER_OUT_OF_RANGE).
               getErrorText());
+    EXPECT_EQ("not a valid number",
+              MasterToken(MasterToken::BAD_NUMBER).getErrorText());
 
     // getErrorCode/Text() isn't allowed for non number types
     EXPECT_THROW(token_num.getErrorCode(), isc::InvalidOperation);
     EXPECT_THROW(token_num.getErrorText(), isc::InvalidOperation);
 
-    // Only the pre-defined error code is accepted.  Hardcoding '5' (max code
+    // Only the pre-defined error code is accepted.  Hardcoding '7' (max code
     // + 1) is intentional; it'd be actually better if we notice it when we
     // update the enum list (which shouldn't happen too often).
-    EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(5)),
+    EXPECT_THROW(MasterToken(MasterToken::ErrorCode(7)),
                  isc::InvalidParameter);
 
     // Check the coexistence of "from number" and "from error-code"
     // constructors won't cause confusion.
-    EXPECT_EQ(MasterLexer::Token::NUMBER,
-              MasterLexer::Token(static_cast<uint32_t>(
-                                     MasterLexer::Token::NOT_STARTED)).
+    EXPECT_EQ(MasterToken::NUMBER,
+              MasterToken(static_cast<uint32_t>(MasterToken::NOT_STARTED)).
               getType());
 }
 }
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index eca6a73..b751da8 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -141,19 +141,19 @@ TEST_F(MasterLexerTest, getNextToken) {
     lexer.pushSource(ss);
 
     // First, the newline should get out.
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // Then the whitespace, if we specify the option.
-    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+    EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
     // The newline
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // The (quoted) string
-    EXPECT_EQ(MasterLexer::Token::QSTRING,
+    EXPECT_EQ(MasterToken::QSTRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
 
     // And the end of line and file
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
 }
 
 // Test we correctly find end of file.
@@ -162,12 +162,12 @@ TEST_F(MasterLexerTest, eof) {
     lexer.pushSource(ss);
 
     // The first one is found to be EOF
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
     // And it stays on EOF for any following attempts
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
     // And we can step back one token, but that is the EOF too.
     lexer.ungetToken();
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
 }
 
 // Check we properly return error when there's an opened parentheses and no
@@ -177,12 +177,12 @@ TEST_F(MasterLexerTest, getUnbalancedParen) {
     lexer.pushSource(ss);
 
     // The string gets out first
-    EXPECT_EQ(MasterLexer::Token::STRING, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
     // Then an unbalanced parenthesis
-    EXPECT_EQ(MasterLexer::Token::UNBALANCED_PAREN,
+    EXPECT_EQ(MasterToken::UNBALANCED_PAREN,
               lexer.getNextToken().getErrorCode());
     // And then EOF
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
 }
 
 // Check we properly return error when there's an opened quoted string and no
@@ -192,10 +192,10 @@ TEST_F(MasterLexerTest, getUnbalancedString) {
     lexer.pushSource(ss);
 
     // Then an unbalanced qstring (reported as an unexpected end)
-    EXPECT_EQ(MasterLexer::Token::UNEXPECTED_END,
+    EXPECT_EQ(MasterToken::UNEXPECTED_END,
               lexer.getNextToken(MasterLexer::QSTRING).getErrorCode());
     // And then EOF
-    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
 }
 
 // Test ungetting tokens works
@@ -204,28 +204,28 @@ TEST_F(MasterLexerTest, ungetToken) {
     lexer.pushSource(ss);
 
     // Try getting the newline
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // Return it and get again
     lexer.ungetToken();
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // Get the string and return it back
-    EXPECT_EQ(MasterLexer::Token::QSTRING,
+    EXPECT_EQ(MasterToken::QSTRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
     lexer.ungetToken();
     // But if we change the options, it honors them
-    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+    EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::QSTRING |
                                  MasterLexer::INITIAL_WS).getType());
     // Get to the "more" string
-    EXPECT_EQ(MasterLexer::Token::QSTRING,
+    EXPECT_EQ(MasterToken::QSTRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
-    EXPECT_EQ(MasterLexer::Token::STRING,
+    EXPECT_EQ(MasterToken::STRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
     // Return it back. It should get inside the parentheses.
     // Upon next attempt to get it again, the newline inside the parentheses
     // should be still ignored.
     lexer.ungetToken();
-    EXPECT_EQ(MasterLexer::Token::STRING,
+    EXPECT_EQ(MasterToken::STRING,
               lexer.getNextToken(MasterLexer::QSTRING).getType());
 }
 
@@ -235,16 +235,16 @@ TEST_F(MasterLexerTest, ungetRealOptions) {
     ss << "\n    \n";
     lexer.pushSource(ss);
     // Skip the first newline
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
 
     // If we call it the usual way, it skips up to the newline and returns
     // it
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
 
     // Now we return it. If we call it again, but with different options,
     // we get the initial whitespace.
     lexer.ungetToken();
-    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+    EXPECT_EQ(MasterToken::INITIAL_WS,
               lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
 }
 
@@ -253,7 +253,7 @@ TEST_F(MasterLexerTest, ungetTwice) {
     ss << "\n";
     lexer.pushSource(ss);
 
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // Unget the token. It can be done once
     lexer.ungetToken();
     // But not twice
@@ -271,17 +271,157 @@ TEST_F(MasterLexerTest, ungetBeforeGet) {
 TEST_F(MasterLexerTest, ungetAfterSwitch) {
     ss << "\n\n";
     lexer.pushSource(ss);
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // Switch the source
     std::stringstream ss2;
     ss2 << "\n\n";
     lexer.pushSource(ss2);
     EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
     // We can get from the new source
-    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
     // And when we drop the current source, we can't unget again
     lexer.popSource();
     EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
 }
 
+// Common checks for the case when getNextToken() should result in LexerError
+void
+lexerErrorCheck(MasterLexer& lexer, MasterToken::Type expect,
+                MasterToken::ErrorCode expected_error)
+{
+    bool thrown = false;
+    try {
+        lexer.getNextToken(expect);
+    } catch (const MasterLexer::LexerError& error) {
+        EXPECT_EQ(expected_error, error.token_.getErrorCode());
+        thrown = true;
+    }
+    EXPECT_TRUE(thrown);
+}
+
+// Common checks regarding expected/unexpected end-of-line
+void
+eolCheck(MasterLexer& lexer, MasterToken::Type expect) {
+    // If EOL is found and eol_ok is true, we get it.
+    EXPECT_EQ(MasterToken::END_OF_LINE,
+              lexer.getNextToken(expect, true).getType());
+    // We'll see the second '\n'; by default it will fail.
+    EXPECT_THROW(lexer.getNextToken(expect), MasterLexer::LexerError);
+    // Same if eol_ok is explicitly set to false.  This also checks the
+    // offending '\n' was "ungotten".
+    EXPECT_THROW(lexer.getNextToken(expect, false), MasterLexer::LexerError);
+
+    // And also check the error token set in the exception object.
+    lexerErrorCheck(lexer, expect, MasterToken::UNEXPECTED_END);
+}
+
+// Common checks regarding expected/unexpected end-of-file
+void
+eofCheck(MasterLexer& lexer, MasterToken::Type expect) {
+    EXPECT_EQ(MasterToken::END_OF_FILE,
+              lexer.getNextToken(expect, true).getType());
+    EXPECT_THROW(lexer.getNextToken(expect), MasterLexer::LexerError);
+    EXPECT_THROW(lexer.getNextToken(expect, false), MasterLexer::LexerError);
+}
+
+TEST_F(MasterLexerTest, getNextTokenString) {
+    ss << "normal-string\n";
+    ss << "\n";
+    ss << "another-string";
+    lexer.pushSource(ss);
+
+    // Normal successful case: Expecting a string and get one.
+    EXPECT_EQ("normal-string",
+              lexer.getNextToken(MasterToken::STRING).getString());
+    eolCheck(lexer, MasterToken::STRING);
+
+    // Skip the 2nd '\n'
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+
+    // Same set of tests but for end-of-file
+    EXPECT_EQ("another-string",
+              lexer.getNextToken(MasterToken::STRING, true).getString());
+    eofCheck(lexer, MasterToken::STRING);
+}
+
+TEST_F(MasterLexerTest, getNextTokenQString) {
+    ss << "\"quoted-string\"\n";
+    ss << "\n";
+    ss << "normal-string";
+    lexer.pushSource(ss);
+
+    // Expecting a quoted string and get one.
+    EXPECT_EQ("quoted-string",
+              lexer.getNextToken(MasterToken::QSTRING).getString());
+    eolCheck(lexer, MasterToken::QSTRING);
+
+    // Skip the 2nd '\n'
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+
+    // Expecting a quoted string but see a normal string.  It's okay.
+    EXPECT_EQ("normal-string",
+              lexer.getNextToken(MasterToken::QSTRING).getString());
+    eofCheck(lexer, MasterToken::QSTRING);
+}
+
+TEST_F(MasterLexerTest, getNextTokenNumber) {
+    ss << "3600\n";
+    ss << "\n";
+    ss << "4294967296 ";        // =2^32, out of range
+    ss << "not-a-number ";
+    ss << "86400";
+    lexer.pushSource(ss);
+
+    // Expecting a number string and get one.
+    EXPECT_EQ(3600,
+              lexer.getNextToken(MasterToken::NUMBER).getNumber());
+    eolCheck(lexer, MasterToken::NUMBER);
+
+    // Skip the 2nd '\n'
+    EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+
+    // Expecting a number, but it's too big for uint32.
+    lexerErrorCheck(lexer, MasterToken::NUMBER,
+                    MasterToken::NUMBER_OUT_OF_RANGE);
+    // The token should have been "ungotten".  Re-read and skip it.
+    EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+
+    // Expecting a number, but see a string.
+    lexerErrorCheck(lexer, MasterToken::NUMBER, MasterToken::BAD_NUMBER);
+    // The unexpected string should have been "ungotten".  Re-read and skip it.
+    EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+
+    // Unless we specify NUMBER, decimal number string should be recognized
+    // as a string.
+    EXPECT_EQ("86400",
+              lexer.getNextToken(MasterToken::STRING).getString());
+    eofCheck(lexer, MasterToken::NUMBER);
+}
+
+TEST_F(MasterLexerTest, getNextTokenErrors) {
+    // Check miscellaneous error cases
+
+    ss << ") ";                 // unbalanced parenthesis
+    ss << "string-after-error ";
+    lexer.pushSource(ss);
+
+    // Only string/qstring/number can be "expected".
+    EXPECT_THROW(lexer.getNextToken(MasterToken::END_OF_LINE),
+                 isc::InvalidParameter);
+    EXPECT_THROW(lexer.getNextToken(MasterToken::END_OF_FILE),
+                 isc::InvalidParameter);
+    EXPECT_THROW(lexer.getNextToken(MasterToken::INITIAL_WS),
+                 isc::InvalidParameter);
+    EXPECT_THROW(lexer.getNextToken(MasterToken::ERROR),
+                 isc::InvalidParameter);
+
+    // If it encounters a syntax error, it results in LexerError exception.
+    lexerErrorCheck(lexer, MasterToken::STRING, MasterToken::UNBALANCED_PAREN);
+
+    // Unlike the NUMBER_OUT_OF_RANGE case, the error part has been skipped
+    // within getNextToken().  We should be able to get the next token.
+    EXPECT_EQ("string-after-error",
+              lexer.getNextToken(MasterToken::STRING).getString());
+}
+
 }
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 0465a01..0264d48 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -14,13 +14,26 @@
 
 #include <dns/master_loader_callbacks.h>
 #include <dns/master_loader.h>
-
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/name.h>
 
 #include <gtest/gtest.h>
 #include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
 
+#include <string>
+#include <vector>
+#include <list>
+#include <fstream>
+
 using namespace isc::dns;
+using std::vector;
+using std::string;
+using std::list;
+using std::ofstream;
+using std::endl;
 
 class MasterLoaderTest : public ::testing::Test {
 public:
@@ -33,42 +46,200 @@ public:
 
     /// Concatenate file, line, and reason, and add it to either errors
     /// or warnings
-    void
-    callback(bool error, const std::string& file, size_t line,
-             const std::string reason)
+    void callback(bool error, const std::string& file, size_t line,
+                  const std::string reason)
     {
         std::stringstream ss;
-        ss << file << line << reason;
+        ss << reason << " [" << file << ":" << line << "]";
         if (error) {
-            errors.push_back(ss.str());
+            errors_.push_back(ss.str());
         } else {
-            warnings.push_back(ss.str());
+            warnings_.push_back(ss.str());
         }
     }
 
-    void addRRset(const RRsetPtr&) {
-        // TODO
+    void addRRset(const Name& name, const RRClass& rrclass,
+                  const RRType& rrtype, const RRTTL& rrttl,
+                  const rdata::RdataPtr& data) {
+        const RRsetPtr rrset(new BasicRRset(name, rrclass, rrtype, rrttl));
+        rrset->addRdata(data);
+        rrsets_.push_back(rrset);
     }
 
-    void
-    setLoader(const char* file, const Name& origin, const RRClass rrclass,
-              const MasterLoader::Options options)
+    void setLoader(const char* file, const Name& origin, const RRClass rrclass,
+                   const MasterLoader::Options options)
     {
-        loader.reset(new MasterLoader(file, origin, rrclass, callbacks_,
-                                      boost::bind(&MasterLoaderTest::addRRset,
-                                                  this, _1), options));
+        loader_.reset(new MasterLoader(file, origin, rrclass, callbacks_,
+                                       boost::bind(&MasterLoaderTest::addRRset,
+                                                   this, _1, _2, _3, _4, _5),
+                                       options));
+    }
+
+    void prepareBrokenZone(const string& filename, const string& line) {
+        ofstream out(filename.c_str(),
+                     std::ios_base::out | std::ios_base::trunc);
+        ASSERT_FALSE(out.fail());
+        out << "example.org. 3600 IN SOA ns1.example.org. "
+            "admin.example.org. 1234 3600 1800 2419200 7200" << endl;
+        out << line << endl;
+        out << "correct 3600    IN  A 192.0.2.2" << endl;
+        out.close();
+    }
+
+    void clear() {
+        warnings_.clear();
+        errors_.clear();
+        rrsets_.clear();
+    }
+
+    // Check the next RR in the ones produced by the loader
+    // Other than passed arguments are checked to be the default for the tests
+    void checkRR(const string& name, const RRType& type, const string& data) {
+        ASSERT_FALSE(rrsets_.empty());
+        RRsetPtr current = rrsets_.front();
+        rrsets_.pop_front();
+
+        EXPECT_EQ(Name(name), current->getName());
+        ASSERT_EQ(type, current->getType());
+        ASSERT_EQ(1, current->getRdataCount());
+        EXPECT_EQ(data, current->getRdataIterator()->getCurrent().toText());
     }
 
     MasterLoaderCallbacks callbacks_;
-    boost::scoped_ptr<MasterLoader> loader;
-    std::vector<std::string> errors;
-    std::vector<std::string> warnings;
+    boost::scoped_ptr<MasterLoader> loader_;
+    vector<string> errors_;
+    vector<string> warnings_;
+    list<RRsetPtr> rrsets_;
 };
 
+// Test simple loading. The zone file contains no tricky things, and nothing is
+// omitted. No RRset contains more than one RR Also no errors or warnings.
 TEST_F(MasterLoaderTest, basicLoad) {
-    setLoader("testdata/loader_test.txt",
-              Name("example.com."),
-              RRClass::IN(),
-              MasterLoader::MANY_ERRORS);
-    ASSERT_FALSE(loader->loadIncremental(1));
+    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+              RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+
+    EXPECT_TRUE(errors_.empty());
+    EXPECT_TRUE(warnings_.empty());
+
+    checkRR("example.org", RRType::SOA(), "ns1.example.org. admin.example.org. "
+            "1234 3600 1800 2419200 7200");
+    checkRR("example.org", RRType::NS(), "ns1.example.org.");
+    checkRR("www.example.org", RRType::A(), "192.0.2.1");
+}
+
+// Try loading data incrementally.
+TEST_F(MasterLoaderTest, incrementalLoad) {
+    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+              RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+    EXPECT_FALSE(loader_->loadIncremental(2));
+
+    EXPECT_TRUE(errors_.empty());
+    EXPECT_TRUE(warnings_.empty());
+
+    checkRR("example.org", RRType::SOA(), "ns1.example.org. admin.example.org. "
+            "1234 3600 1800 2419200 7200");
+    checkRR("example.org", RRType::NS(), "ns1.example.org.");
+
+    // The third one is not loaded yet
+    EXPECT_TRUE(rrsets_.empty());
+
+    // Load the rest.
+    EXPECT_TRUE(loader_->loadIncremental(20));
+
+    EXPECT_TRUE(errors_.empty());
+    EXPECT_TRUE(warnings_.empty());
+
+    checkRR("www.example.org", RRType::A(), "192.0.2.1");
+}
+
+// Try loading from file that doesn't exist. There should be single error
+// saying so.
+TEST_F(MasterLoaderTest, invalidFile) {
+    setLoader("This file doesn't exist at all",
+              Name("exmaple.org."), RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+    // Nothing yet. The loader is dormant until invoked.
+    // Is it really what we want?
+    EXPECT_TRUE(errors_.empty());
+
+    loader_->load();
+
+    EXPECT_TRUE(warnings_.empty());
+    EXPECT_TRUE(rrsets_.empty());
+    ASSERT_EQ(1, errors_.size());
+    EXPECT_EQ(0, errors_[0].find("Error opening the input source file: ")) <<
+        "Different error: " << errors_[0];
+}
+
+struct ErrorCase {
+    const char* line;
+    const char* problem;
+} error_cases[] = {
+    { "www...   3600    IN  A   192.0.2.1", "Invalid name" },
+    { "www      FORTNIGHT   IN  A   192.0.2.1", "Invalid TTL" },
+    { "www      3600    XX  A   192.0.2.1", "Invalid class" },
+    { "www      3600    IN  A   bad_ip", "Invalid Rdata" },
+    { "www      3600    IN", "Unexpected EOLN" },
+    { "www      3600    CH  TXT nothing", "Class mismatch" },
+    { NULL, NULL }
+};
+
+// Test a broken zone is handled properly. We test several problems,
+// both in strict and lenient mode.
+TEST_F(MasterLoaderTest, brokenZone) {
+    const string filename(TEST_DATA_BUILDDIR "/broken.zone");
+    for (const ErrorCase* ec = error_cases; ec->line != NULL; ++ec) {
+        SCOPED_TRACE(ec->problem);
+        prepareBrokenZone(filename, ec->line);
+
+        {
+            SCOPED_TRACE("Strict mode");
+            clear();
+            setLoader(filename.c_str(), Name("example.org."), RRClass::IN(),
+                      MasterLoader::DEFAULT);
+            loader_->load();
+            EXPECT_EQ(1, errors_.size());
+            EXPECT_TRUE(warnings_.empty());
+
+            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+                    "admin.example.org. 1234 3600 1800 2419200 7200");
+            // In the strict mode, it is aborted. The last RR is not
+            // even attempted.
+            EXPECT_TRUE(rrsets_.empty());
+        }
+
+        {
+            SCOPED_TRACE("Lenient mode");
+            clear();
+            setLoader(filename.c_str(), Name("example.org."), RRClass::IN(),
+                      MasterLoader::MANY_ERRORS);
+            loader_->load();
+            EXPECT_EQ(1, errors_.size());
+            EXPECT_TRUE(warnings_.empty());
+            checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+                    "admin.example.org. 1234 3600 1800 2419200 7200");
+            // This one is below the error one.
+            checkRR("correct.example.org", RRType::A(), "192.0.2.2");
+            EXPECT_TRUE(rrsets_.empty());
+        }
+    }
+}
+
+// Test the constructor rejects empty add callback.
+TEST_F(MasterLoaderTest, emptyCallback) {
+    EXPECT_THROW(MasterLoader(TEST_DATA_SRCDIR "/example.org",
+                              Name("example.org"), RRClass::IN(), callbacks_,
+                              AddRRCallback()), isc::InvalidParameter);
+}
+
+// Check it throws when we try to load after loading was complete.
+TEST_F(MasterLoaderTest, loadTwice) {
+    setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+              RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_THROW(loader_->load(), isc::InvalidOperation);
 }
diff --git a/src/lib/dns/tests/rdata_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
index 521bec5..9bb64b7 100644
--- a/src/lib/dns/tests/rdata_afsdb_unittest.cc
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -114,6 +114,16 @@ TEST_F(Rdata_AFSDB_Test, createFromWire) {
                  DNSMessageFORMERR);
 }
 
+TEST_F(Rdata_AFSDB_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_afsdb.compare(
+        *test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+                                     afsdb_text)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+                                             "1root.example.com."));
+}
+
 TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
     // construct actual data
     rdata_afsdb.toWire(obuffer);
diff --git a/src/lib/dns/tests/rdata_cname_unittest.cc b/src/lib/dns/tests/rdata_cname_unittest.cc
index 2cce9bc..6451f72 100644
--- a/src/lib/dns/tests/rdata_cname_unittest.cc
+++ b/src/lib/dns/tests/rdata_cname_unittest.cc
@@ -87,6 +87,12 @@ TEST_F(Rdata_CNAME_Test, createFromWire) {
                  InvalidRdataLength);
 }
 
+TEST_F(Rdata_CNAME_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_cname.compare(
+        *test::createRdataUsingLexer(RRType::CNAME(), RRClass::IN(),
+                                     "cn.example.com")));
+}
+
 TEST_F(Rdata_CNAME_Test, toWireBuffer) {
     rdata_cname.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 38b1459..8d56c0e 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -63,6 +63,16 @@ TEST_F(Rdata_DHCID_Test, createFromWire) {
     // TBD: more tests
 }
 
+TEST_F(Rdata_DHCID_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_dhcid.compare(
+        *test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
+                                     string_dhcid)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
+                                             "00"));
+}
+
 TEST_F(Rdata_DHCID_Test, toWireRenderer) {
     rdata_dhcid.toWire(renderer);
 
diff --git a/src/lib/dns/tests/rdata_dname_unittest.cc b/src/lib/dns/tests/rdata_dname_unittest.cc
index eb221d8..c4e517c 100644
--- a/src/lib/dns/tests/rdata_dname_unittest.cc
+++ b/src/lib/dns/tests/rdata_dname_unittest.cc
@@ -89,6 +89,12 @@ TEST_F(Rdata_DNAME_Test, createFromWire) {
                  InvalidRdataLength);
 }
 
+TEST_F(Rdata_DNAME_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_dname.compare(
+        *test::createRdataUsingLexer(RRType::DNAME(), RRClass::IN(),
+                                     "dn.example.com")));
+}
+
 TEST_F(Rdata_DNAME_Test, toWireBuffer) {
     rdata_dname.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index 86b8f69..58d29bf 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -82,6 +82,17 @@ TEST_F(Rdata_DNSKEY_Test, DISABLED_badText) {
                  InvalidRdataText);
 }
 
+TEST_F(Rdata_DNSKEY_Test, createFromLexer) {
+    generic::DNSKEY rdata_dnskey(dnskey_txt);
+    EXPECT_EQ(0, rdata_dnskey.compare(
+        *test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
+                                     dnskey_txt)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
+                                             "257 3 5"));
+}
+
 TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
     renderer.skip(2);
     generic::DNSKEY rdata_dnskey(dnskey_txt);
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index 6172431..28a2e17 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -85,6 +85,16 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
                                           "rdata_ds_fromWire")));
 }
 
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
+    EXPECT_EQ(0, this->rdata_ds_like.compare(
+        *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+                                     ds_like_txt)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+                                             "99999 5 2 BEEF"));
+}
+
 TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
     TypeParam copy((string(ds_like_txt)));
     copy = this->rdata_ds_like;
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index c934a4f..7469047 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -77,6 +77,17 @@ TEST_F(Rdata_HINFO_Test, createFromWire) {
     EXPECT_EQ(string("Linux"), hinfo.getOS());
 }
 
+TEST_F(Rdata_HINFO_Test, createFromLexer) {
+    HINFO rdata_hinfo(hinfo_str);
+    EXPECT_EQ(0, rdata_hinfo.compare(
+        *test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+                                     hinfo_str)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+                                             "\"Pentium\"\"Linux\""));
+}
+
 TEST_F(Rdata_HINFO_Test, toText) {
     HINFO hinfo(hinfo_str);
     EXPECT_EQ(hinfo_str, hinfo.toText());
diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc
index 2fea9a3..7ab3423 100644
--- a/src/lib/dns/tests/rdata_in_a_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -68,6 +68,11 @@ TEST_F(Rdata_IN_A_Test, createFromWire) {
                  DNSMessageFORMERR);
 }
 
+TEST_F(Rdata_IN_A_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_in_a.compare(
+        *test::createRdataUsingLexer(RRType::A(), RRClass::IN(), "192.0.2.1")));
+}
+
 TEST_F(Rdata_IN_A_Test, toWireBuffer) {
     rdata_in_a.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
index d8ed1d6..74a3e3f 100644
--- a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -66,6 +66,12 @@ TEST_F(Rdata_IN_AAAA_Test, createFromWire) {
                  DNSMessageFORMERR);
 }
 
+TEST_F(Rdata_IN_AAAA_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_in_aaaa.compare(
+        *test::createRdataUsingLexer(RRType::AAAA(), RRClass::IN(),
+                                     "2001:db8::1234")));
+}
+
 TEST_F(Rdata_IN_AAAA_Test, toWireBuffer) {
     rdata_in_aaaa.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
index 78e8325..2f717fe 100644
--- a/src/lib/dns/tests/rdata_minfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -103,6 +103,12 @@ TEST_F(Rdata_MINFO_Test, createFromWire) {
                  DNSMessageFORMERR);
 }
 
+TEST_F(Rdata_MINFO_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_minfo.compare(
+        *test::createRdataUsingLexer(RRType::MINFO(), RRClass::IN(),
+                                     minfo_txt)));
+}
+
 TEST_F(Rdata_MINFO_Test, assignment) {
     generic::MINFO copy((string(minfo_txt2)));
     copy = rdata_minfo;
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
index 7dc774d..6c6039a 100644
--- a/src/lib/dns/tests/rdata_mx_unittest.cc
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -62,6 +62,16 @@ TEST_F(Rdata_MX_Test, createFromWire) {
     // TBD: more tests
 }
 
+TEST_F(Rdata_MX_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_mx.compare(
+        *test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+                                     "10 mx.example.com")));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+                                             "10 mx. example.com"));
+}
+
 TEST_F(Rdata_MX_Test, toWireRenderer) {
     renderer.writeName(Name("example.com"));
     rdata_mx.toWire(renderer);
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index 5abcaef..fed98cb 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -128,6 +128,19 @@ TEST_F(Rdata_NAPTR_Test, createFromWire) {
     EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
 }
 
+TEST_F(Rdata_NAPTR_Test, createFromLexer) {
+    const NAPTR rdata_naptr(naptr_str);
+
+    EXPECT_EQ(0, rdata_naptr.compare(
+        *test::createRdataUsingLexer(RRType::NAPTR(), RRClass::IN(),
+                                     naptr_str)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::NAPTR(), RRClass::IN(),
+                                             "65536 10 S SIP \"\" "
+                                             "_sip._udp.example.com."));
+}
+
 TEST_F(Rdata_NAPTR_Test, toWire) {
     NAPTR naptr(naptr_str);
     naptr.toWire(obuffer);
diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc
index 47582ce..d536393 100644
--- a/src/lib/dns/tests/rdata_ns_unittest.cc
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -86,6 +86,16 @@ TEST_F(Rdata_NS_Test, createFromWire) {
                  InvalidRdataLength);
 }
 
+TEST_F(Rdata_NS_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_ns.compare(
+        *test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+                                     "ns.example.com")));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+                                             ""));
+}
+
 TEST_F(Rdata_NS_Test, toWireBuffer) {
     rdata_ns.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index edd2d4b..0fec3eb 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -130,6 +130,18 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
     }
 }
 
+TEST_F(Rdata_NSEC3_Test, createFromLexer) {
+    const generic::NSEC3 rdata_nsec3(nsec3_txt);
+    EXPECT_EQ(0, rdata_nsec3.compare(
+        *test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+                                     nsec3_txt)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+                                             "1 1 1 ADDAFEEE CPNMU=== "
+                                             "A NS SOA"));
+}
+
 TEST_F(Rdata_NSEC3_Test, assign) {
     generic::NSEC3 rdata_nsec3(nsec3_txt);
     generic::NSEC3 other_nsec3 = rdata_nsec3;
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
index 51fef01..23d6d0e 100644
--- a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -208,6 +208,17 @@ TYPED_TEST(NSEC3PARAMLikeTest, createFromWire) {
     EXPECT_EQ(0, this->convert(*rdata).getSalt().size());
 }
 
+TYPED_TEST(NSEC3PARAMLikeTest, createFromLexer) {
+    EXPECT_EQ(0, this->fromText(this->salt_txt).compare(
+        *test::createRdataUsingLexer(this->getType(), RRClass::IN(),
+                                     this->salt_txt)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(this->getType(), RRClass::IN(),
+                                             "1000000 1 1 ADDAFEEE" +
+                                             this->getCommonText()));
+}
+
 template <typename OUTPUT_TYPE>
 void
 toWireCheck(RRType rrtype, OUTPUT_TYPE& output, const string& data_file) {
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 7558b42..115d3d3 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -86,6 +86,13 @@ TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
     }
 }
 
+TEST_F(Rdata_NSEC3PARAM_Test, createFromLexer) {
+    const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
+    EXPECT_EQ(0, rdata_nsec3param.compare(
+        *test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
+                                     nsec3param_txt)));
+}
+
 TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
     renderer.skip(2);
     const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index 88c6201..4092c6d 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -66,6 +66,17 @@ TEST_F(Rdata_NSEC_Test, createFromWire_NSEC) {
     // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
 }
 
+TEST_F(Rdata_NSEC_Test, createFromLexer_NSEC) {
+    const generic::NSEC rdata_nsec(nsec_txt);
+    EXPECT_EQ(0, rdata_nsec.compare(
+        *test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+                                     nsec_txt)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+                                             "www.isc.org."));
+}
+
 TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
     renderer.skip(2);
     const generic::NSEC rdata_nsec(nsec_txt);
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index 698d586..5699259 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -56,6 +56,13 @@ TEST_F(Rdata_OPT_Test, createFromWire) {
                  InvalidRdataLength);
 }
 
+TEST_F(Rdata_OPT_Test, createFromLexer) {
+    // OPT RR cannot be created from text. Exceptions cause NULL to be
+    // returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::OPT(), RRClass::IN(),
+                                             "this does not matter"));
+}
+
 TEST_F(Rdata_OPT_Test, toWireBuffer) {
     rdata_opt.toWire(obuffer);
     EXPECT_EQ(0, obuffer.getLength());
diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc
index 86160fb..44b849a 100644
--- a/src/lib/dns/tests/rdata_ptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -90,6 +90,12 @@ TEST_F(Rdata_PTR_Test, createFromWire) {
                  InvalidRdataLength);
 }
 
+TEST_F(Rdata_PTR_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_ptr.compare(
+        *test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(),
+                                     "ns.example.com")));
+}
+
 TEST_F(Rdata_PTR_Test, toWireBuffer) {
     rdata_ptr.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
index 20f32b9..5508d9c 100644
--- a/src/lib/dns/tests/rdata_rp_unittest.cc
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -106,6 +106,17 @@ TEST_F(Rdata_RP_Test, createFromParams) {
     EXPECT_EQ(text_name, generic::RP(mailbox_name, text_name).getText());
 }
 
+TEST_F(Rdata_RP_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_rp.compare(
+        *test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
+                                     "root.example.com. "
+                                     "rp-text.example.com.")));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
+                                             "mailbox.example.com."));
+}
+
 TEST_F(Rdata_RP_Test, toWireBuffer) {
     // construct expected data
     UnitTestUtil::readWireData("rdata_rp_toWire1.wire", expected_wire);
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index 3324b99..d758ff3 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -36,16 +36,21 @@ using namespace isc::dns::rdata;
 
 namespace {
 class Rdata_RRSIG_Test : public RdataTest {
-    // there's nothing to specialize
+public:
+    Rdata_RRSIG_Test() :
+        rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+                  "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+                  "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+                  "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+                  "f49t+sXKPzbipN9g+s1ZPiIyofc="),
+        rdata_rrsig(rrsig_txt)
+    {}
+
+    const string rrsig_txt;
+    const generic::RRSIG rdata_rrsig;
 };
 
 TEST_F(Rdata_RRSIG_Test, fromText) {
-    string rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
-                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
-                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
-                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
-    generic::RRSIG rdata_rrsig(rrsig_txt);
     EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
     EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
 }
@@ -96,35 +101,37 @@ TEST_F(Rdata_RRSIG_Test, DISABLED_badText) {
                                 "8496isc.org. ofc="), InvalidRdataText);
 }
 
+TEST_F(Rdata_RRSIG_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_rrsig.compare(
+        *test::createRdataUsingLexer(RRType::RRSIG(), RRClass::IN(),
+                                     rrsig_txt)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::RRSIG(), RRClass::IN(),
+                                             "INVALIDINPUT"));
+}
+
 TEST_F(Rdata_RRSIG_Test, toWireRenderer) {
-    string rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
-                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
-                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
-                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
-    generic::RRSIG rdata_rrsig(rrsig_txt);
+    // FIXME: This doesn't check the result.
     rdata_rrsig.toWire(renderer);
 }
 
 TEST_F(Rdata_RRSIG_Test, toWireBuffer) {
-    string rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
-                     "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
-                     "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
-                     "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
-                     "f49t+sXKPzbipN9g+s1ZPiIyofc=");
-    generic::RRSIG rdata_rrsig(rrsig_txt);
+    // FIXME: This doesn't check the result.
     rdata_rrsig.toWire(obuffer);
 }
 
 TEST_F(Rdata_RRSIG_Test, createFromWire) {
-    string rrsig_txt("A 5 2 43200 20100327070149 20100225070149 2658 isc.org. "
-                "HkJk/xZTvzePU8NENl/ley8bbUumhk1hXciyqhLnz1VQFzkDooej6neX"
-                "ZgWZzQKeTKPOYWrnYtdZW4PnPQFeUl3orgLev7F8J6FZlDn0y/J/ThR5"
-                "m36Mo2/Gdxjj8lJ/IjPVkdpKyBpcnYND8KEIma5MyNCNeyO1UkfPQZGHNSQ=");
-    EXPECT_EQ(rrsig_txt, rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"),
-                             "rdata_rrsig_fromWire1")->toText());
-    generic::RRSIG rdata_rrsig(rrsig_txt);
-    EXPECT_EQ(0, rdata_rrsig.compare(
+    const string rrsig_txt2(
+        "A 5 2 43200 20100327070149 20100225070149 2658 isc.org. "
+        "HkJk/xZTvzePU8NENl/ley8bbUumhk1hXciyqhLnz1VQFzkDooej6neX"
+        "ZgWZzQKeTKPOYWrnYtdZW4PnPQFeUl3orgLev7F8J6FZlDn0y/J/ThR5"
+        "m36Mo2/Gdxjj8lJ/IjPVkdpKyBpcnYND8KEIma5MyNCNeyO1UkfPQZGHNSQ=");
+    EXPECT_EQ(rrsig_txt2,
+              rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"),
+                                   "rdata_rrsig_fromWire1")->toText());
+    const generic::RRSIG rdata_rrsig2(rrsig_txt2);
+    EXPECT_EQ(0, rdata_rrsig2.compare(
                       *rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"),
                                           "rdata_rrsig_fromWire1")));
 
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index a9d782c..2df711c 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -49,6 +49,13 @@ TEST_F(Rdata_SOA_Test, createFromWire) {
     // TBD: more tests
 }
 
+TEST_F(Rdata_SOA_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_soa.compare(
+        *test::createRdataUsingLexer(RRType::SOA(), RRClass::IN(),
+                                     "ns.example.com. root.example.com. "
+                                     "2010012601 3600 300 3600000 1200")));
+}
+
 TEST_F(Rdata_SOA_Test, toWireRenderer) {
     renderer.skip(2);
     rdata_soa.toWire(renderer);
diff --git a/src/lib/dns/tests/rdata_srv_unittest.cc b/src/lib/dns/tests/rdata_srv_unittest.cc
index b194b1c..066755f 100644
--- a/src/lib/dns/tests/rdata_srv_unittest.cc
+++ b/src/lib/dns/tests/rdata_srv_unittest.cc
@@ -119,6 +119,17 @@ TEST_F(Rdata_SRV_Test, createFromWire) {
                                       "rdata_srv_fromWire", 89)));
 }
 
+TEST_F(Rdata_SRV_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_srv.compare(
+        *test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+                                     "1 5 1500 a.example.com.")));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+                                             "1 5 281474976710656 "
+                                             "a.example.com."));
+}
+
 TEST_F(Rdata_SRV_Test, toWireBuffer) {
     rdata_srv.toWire(obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index dccd8b1..6c13ad9 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -68,6 +68,12 @@ TEST_F(Rdata_SSHFP_Test, createFromText) {
     EXPECT_EQ(0, rdata_sshfp4.compare(rdata_sshfp));
 }
 
+TEST_F(Rdata_SSHFP_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_sshfp.compare(
+        *test::createRdataUsingLexer(RRType::SSHFP(), RRClass::IN(),
+                                     "2 1 123456789abcdef67890123456789abcdef67890")));
+}
+
 TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
     // Some of these may not be RFC conformant, but we relax the check
     // in our code to work with algorithm and fingerprint types that may
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
index c8ee8ac..df35842 100644
--- a/src/lib/dns/tests/rdata_tsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -247,6 +247,16 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
                  isc::InvalidParameter);
 }
 
+TEST_F(Rdata_TSIG_Test, createFromLexer) {
+    EXPECT_EQ(0, rdata_tsig.compare(
+        *test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
+                                     valid_text1)));
+
+    // Exceptions cause NULL to be returned.
+    EXPECT_FALSE(test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
+                                             "foo 0 0 0 0 BADKEY 0 0"));
+}
+
 TEST_F(Rdata_TSIG_Test, assignment) {
     any::TSIG copy((string(valid_text2)));
     copy = rdata_tsig;
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index 981265e..7ff9ac9 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -185,6 +185,18 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
                  DNSMessageFORMERR);
 }
 
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
+    EXPECT_EQ(0, this->rdata_txt_like.compare(
+        *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+                                     "Test String")));
+    EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
+        *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+                                     "")));
+    EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(
+        *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+                                     "\"Test String\"")));
+}
+
 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
     this->rdata_txt_like.toWire(this->obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index bf1f5f7..bb39d05 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -28,6 +28,9 @@
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
 
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
 using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
@@ -54,6 +57,146 @@ RdataTest::rdataFactoryFromFile(const RRType& rrtype, const RRClass& rrclass,
     uint16_t rdlen = buffer.readUint16();
     return (createRdata(rrtype, rrclass, buffer, rdlen));
 }
+
+namespace test {
+
+void
+dummyCallback(const string&, size_t, const string&) {
+}
+
+RdataPtr
+createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
+                      const std::string& str)
+{
+    std::stringstream ss(str);
+    MasterLexer lexer;
+    lexer.pushSource(ss);
+
+    const MasterLoaderCallbacks::IssueCallback callback
+        (boost::bind(&dummyCallback, _1, _2, _3));
+    MasterLoaderCallbacks callbacks(callback, callback);
+    const Name origin("example.org.");
+
+    return (createRdata(rrtype, rrclass, lexer, &origin,
+                        MasterLoader::MANY_ERRORS, callbacks));
+}
+
+} // end of namespace isc::dns::rdata::test
+
+// A mock class to check parameters passed via loader callbacks.  Its callback
+// records the passed parameters, allowing the test to check them later via
+// the check() method.
+class CreateRdataCallback {
+public:
+    enum CallbackType { NONE, ERROR, WARN };
+    CreateRdataCallback() : type_(NONE), line_(0) {}
+    void callback(CallbackType type, const string& source, size_t line,
+                  const string& reason_txt) {
+        type_ = type;
+        source_ = source;
+        line_ = line;
+        reason_txt_ = reason_txt;
+    }
+
+    void clear() {
+        type_ = NONE;
+        source_.clear();
+        line_ = 0;
+        reason_txt_.clear();
+    }
+
+    // Return if callback is called since the previous call to clear().
+    bool isCalled() const { return (type_ != NONE); }
+
+    void check(const string& expected_srcname, size_t expected_line,
+               CallbackType expected_type, const string& expected_reason)
+        const
+    {
+        EXPECT_EQ(expected_srcname, source_);
+        EXPECT_EQ(expected_line, line_);
+        EXPECT_EQ(expected_type, type_);
+        EXPECT_EQ(expected_reason, reason_txt_);
+    }
+
+private:
+    CallbackType type_;
+    string source_;
+    size_t line_;
+    string reason_txt_;
+};
+
+// Test class/type-independent behavior of createRdata().
+TEST_F(RdataTest, createRdataWithLexer) {
+    const in::AAAA aaaa_rdata("2001:db8::1");
+
+    stringstream ss;
+    const string src_name = "stream-" + boost::lexical_cast<string>(&ss);
+    ss << aaaa_rdata.toText() << "\n"; // valid case
+    ss << aaaa_rdata.toText() << " extra-token\n"; // extra token
+    ss << aaaa_rdata.toText() << " extra token\n"; // 2 extra tokens
+    ss << ")\n"; // causing lexer error in parsing the RDATA text
+    ss << "192.0.2.1\n"; // semantics error: IPv4 address is given for AAAA
+    ss << aaaa_rdata.toText();  // valid, but end with EOF, not EOL
+    lexer.pushSource(ss);
+
+    CreateRdataCallback callback;
+    MasterLoaderCallbacks callbacks(
+        boost::bind(&CreateRdataCallback::callback, &callback,
+                    CreateRdataCallback::ERROR, _1, _2, _3),
+        boost::bind(&CreateRdataCallback::callback, &callback,
+                    CreateRdataCallback::WARN,  _1, _2, _3));
+
+    // Valid case.
+    ConstRdataPtr rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer,
+                                      NULL, MasterLoader::MANY_ERRORS,
+                                      callbacks);
+    EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+    EXPECT_FALSE(callback.isCalled());
+
+    // Broken RDATA text: extra token.  createRdata() returns NULL, error
+    // callback is called.
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, 2, CreateRdataCallback::ERROR,
+                   "createRdata from text failed near 'extra-token': "
+                   "extra input text");
+
+    // Similar to the previous case, but only the first extra token triggers
+    // callback.
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, 3, CreateRdataCallback::ERROR,
+                   "createRdata from text failed near 'extra': "
+                   "extra input text");
+
+    // Lexer error will happen, corresponding error callback will be triggered.
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, 4, CreateRdataCallback::ERROR,
+                   "createRdata from text failed: unbalanced parentheses");
+
+    // Semantics level error will happen, corresponding error callback will be
+    // triggered.
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, 5, CreateRdataCallback::ERROR,
+                   "createRdata from text failed: Failed to convert "
+                   "'192.0.2.1' to IN/AAAA RDATA");
+
+    // Input is valid and parse will succeed, but with a warning that the
+    // file is not ended with a newline.
+    callback.clear();
+    rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                        MasterLoader::MANY_ERRORS, callbacks);
+    EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+    callback.check(src_name, 6, CreateRdataCallback::WARN,
+                   "file does not end with newline");
+}
+
 }
 }
 }
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index f593609..af19311 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -20,6 +20,7 @@
 #include <dns/rrclass.h>
 #include <dns/rrtype.h>
 #include <dns/rdata.h>
+#include <dns/master_lexer.h>
 
 #include <gtest/gtest.h>
 
@@ -40,12 +41,20 @@ protected:
     /// This is an RDATA object of some "unknown" RR type so that it can be
     /// used to test the compare() method against a well-known RR type.
     RdataPtr rdata_nomatch;
+    MasterLexer lexer;
 };
+
+namespace test {
+RdataPtr
+createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
+                      const std::string& str);
+}
+
 }
 }
 }
 #endif // RDATA_UNITTEST_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
index c155b53..0ae0a76 100644
--- a/src/lib/dns/tests/rrparamregistry_unittest.cc
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -24,6 +24,10 @@
 #include <dns/rdataclass.h>
 #include <dns/rrparamregistry.h>
 #include <dns/rrtype.h>
+#include <dns/master_loader.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
 
 using namespace std;
 using namespace isc::dns;
@@ -104,6 +108,7 @@ TEST_F(RRParamRegistryTest, addError) {
 
 class TestRdataFactory : public AbstractRdataFactory {
 public:
+    using AbstractRdataFactory::create;
     virtual RdataPtr create(const string& rdata_str) const
     { return (RdataPtr(new in::A(rdata_str))); }
     virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const
@@ -152,4 +157,39 @@ TEST_F(RRParamRegistryTest, addRemoveFactory) {
                      RRType(test_type_code)));
 }
 
+void
+dummyCallback(const string&, size_t, const string&) {
+}
+
+RdataPtr
+createRdataHelper(const std::string& str) {
+    boost::scoped_ptr<AbstractRdataFactory> rdf(new TestRdataFactory);
+
+    std::stringstream ss(str);
+    MasterLexer lexer;
+    lexer.pushSource(ss);
+
+    const MasterLoaderCallbacks::IssueCallback callback
+        (boost::bind(&dummyCallback, _1, _2, _3));
+    MasterLoaderCallbacks callbacks(callback, callback);
+    const Name origin("example.org.");
+
+    return (rdf->create(lexer, &origin,
+                        MasterLoader::MANY_ERRORS,
+                        callbacks));
+}
+
+TEST_F(RRParamRegistryTest, createFromLexer) {
+    // This test basically checks that the string version of
+    // AbstractRdataFactory::create() is called by the MasterLexer
+    // variant of create().
+    EXPECT_EQ(0, in::A("192.168.0.1").compare(
+              *createRdataHelper("192.168.0.1")));
+
+    // This should parse only up to the end of line. Everything that
+    // comes afterwards is not parsed.
+    EXPECT_EQ(0, in::A("192.168.0.42").compare(
+              *createRdataHelper("192.168.0.42\na b c d e f")));
+}
+
 }
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 94cadec..d312e07 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -170,6 +170,7 @@ EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec
 EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec
 EXTRA_DIST += tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec
 EXTRA_DIST += tsig_verify10.spec
+EXTRA_DIST += example.org
 
 .spec.wire:
 	$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/example.org b/src/lib/dns/tests/testdata/example.org
new file mode 100644
index 0000000..deef7d5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/example.org
@@ -0,0 +1,14 @@
+example.org.        3600    IN  SOA ( ; The SOA, split across lines for testing
+    ns1.example.org.
+    admin.example.org.
+    1234
+    3600
+    1800
+    2419200
+    7200
+    )
+example.org.        3600    IN  NS ns1.example.org.
+
+
+; Some empty lines here. They are to make sure the loader can skip them.
+www                 3600    IN  A 192.0.2.1 ; Test a relative name as well.



More information about the bind10-changes mailing list