summaryrefslogtreecommitdiffstats
path: root/src/lib/dns
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 14:53:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 14:53:22 +0000
commit52c021ee0b0c6ad2128ed550c694aad0d11d4c3f (patch)
tree83cf8627b94336cf4bee7479b9749263bbfd3a06 /src/lib/dns
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.5.7.upstream/2.5.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dns')
-rw-r--r--src/lib/dns/Makefile.am95
-rw-r--r--src/lib/dns/Makefile.in1304
-rw-r--r--src/lib/dns/char_string.cc264
-rw-r--r--src/lib/dns/char_string.h136
-rw-r--r--src/lib/dns/edns.cc169
-rw-r--r--src/lib/dns/edns.h431
-rw-r--r--src/lib/dns/exceptions.cc26
-rw-r--r--src/lib/dns/exceptions.h72
-rw-r--r--src/lib/dns/labelsequence.cc468
-rw-r--r--src/lib/dns/labelsequence.h452
-rw-r--r--src/lib/dns/master_lexer.cc608
-rw-r--r--src/lib/dns/master_lexer.h676
-rw-r--r--src/lib/dns/master_lexer_inputsource.cc220
-rw-r--r--src/lib/dns/master_lexer_inputsource.h185
-rw-r--r--src/lib/dns/master_lexer_state.h134
-rw-r--r--src/lib/dns/master_loader.cc742
-rw-r--r--src/lib/dns/master_loader.h186
-rw-r--r--src/lib/dns/master_loader_callbacks.h121
-rw-r--r--src/lib/dns/message.cc1167
-rw-r--r--src/lib/dns/message.h688
-rw-r--r--src/lib/dns/messagerenderer.cc393
-rw-r--r--src/lib/dns/messagerenderer.h391
-rw-r--r--src/lib/dns/name.cc720
-rw-r--r--src/lib/dns/name.h758
-rw-r--r--src/lib/dns/name_internal.h31
-rw-r--r--src/lib/dns/opcode.cc62
-rw-r--r--src/lib/dns/opcode.h286
-rw-r--r--src/lib/dns/question.cc81
-rw-r--r--src/lib/dns/question.h286
-rw-r--r--src/lib/dns/rcode.cc95
-rw-r--r--src/lib/dns/rcode.h342
-rw-r--r--src/lib/dns/rdata.cc388
-rw-r--r--src/lib/dns/rdata.h575
-rw-r--r--src/lib/dns/rdataclass.cc2462
-rw-r--r--src/lib/dns/rdataclass.h618
-rw-r--r--src/lib/dns/rrclass.cc73
-rw-r--r--src/lib/dns/rrclass.h339
-rw-r--r--src/lib/dns/rrparamregistry.cc644
-rw-r--r--src/lib/dns/rrparamregistry.h542
-rw-r--r--src/lib/dns/rrset.cc465
-rw-r--r--src/lib/dns/rrset.h952
-rw-r--r--src/lib/dns/rrttl.cc214
-rw-r--r--src/lib/dns/rrttl.h313
-rw-r--r--src/lib/dns/rrtype.cc64
-rw-r--r--src/lib/dns/rrtype.h368
-rw-r--r--src/lib/dns/serial.cc70
-rw-r--r--src/lib/dns/serial.h150
-rw-r--r--src/lib/dns/tests/Makefile.am72
-rw-r--r--src/lib/dns/tests/Makefile.in1835
-rw-r--r--src/lib/dns/tests/dns_exceptions_unittest.cc65
-rw-r--r--src/lib/dns/tests/edns_unittest.cc258
-rw-r--r--src/lib/dns/tests/labelsequence_unittest.cc1242
-rw-r--r--src/lib/dns/tests/master_lexer_inputsource_unittest.cc368
-rw-r--r--src/lib/dns/tests/master_lexer_state_unittest.cc607
-rw-r--r--src/lib/dns/tests/master_lexer_token_unittest.cc162
-rw-r--r--src/lib/dns/tests/master_lexer_unittest.cc521
-rw-r--r--src/lib/dns/tests/master_loader_callbacks_test.cc79
-rw-r--r--src/lib/dns/tests/master_loader_unittest.cc967
-rw-r--r--src/lib/dns/tests/message_unittest.cc1156
-rw-r--r--src/lib/dns/tests/messagerenderer_unittest.cc292
-rw-r--r--src/lib/dns/tests/name_unittest.cc794
-rw-r--r--src/lib/dns/tests/opcode_unittest.cc100
-rw-r--r--src/lib/dns/tests/question_unittest.cc198
-rw-r--r--src/lib/dns/tests/rcode_unittest.cc126
-rw-r--r--src/lib/dns/tests/rdata_char_string_data_unittest.cc181
-rw-r--r--src/lib/dns/tests/rdata_char_string_unittest.cc246
-rw-r--r--src/lib/dns/tests/rdata_dhcid_unittest.cc165
-rw-r--r--src/lib/dns/tests/rdata_in_a_unittest.cc157
-rw-r--r--src/lib/dns/tests/rdata_in_aaaa_unittest.cc151
-rw-r--r--src/lib/dns/tests/rdata_ns_unittest.cc145
-rw-r--r--src/lib/dns/tests/rdata_opt_unittest.cc198
-rw-r--r--src/lib/dns/tests/rdata_ptr_unittest.cc145
-rw-r--r--src/lib/dns/tests/rdata_rrsig_unittest.cc369
-rw-r--r--src/lib/dns/tests/rdata_soa_unittest.cc249
-rw-r--r--src/lib/dns/tests/rdata_tkey_unittest.cc450
-rw-r--r--src/lib/dns/tests/rdata_tsig_unittest.cc423
-rw-r--r--src/lib/dns/tests/rdata_txt_like_unittest.cc394
-rw-r--r--src/lib/dns/tests/rdata_unittest.cc471
-rw-r--r--src/lib/dns/tests/rdata_unittest.h87
-rw-r--r--src/lib/dns/tests/rrclass_unittest.cc204
-rw-r--r--src/lib/dns/tests/rrparamregistry_unittest.cc189
-rw-r--r--src/lib/dns/tests/rrset_unittest.cc441
-rw-r--r--src/lib/dns/tests/rrttl_unittest.cc279
-rw-r--r--src/lib/dns/tests/rrtype_unittest.cc290
-rw-r--r--src/lib/dns/tests/run_unittests.cc24
-rw-r--r--src/lib/dns/tests/serial_unittest.cc173
-rw-r--r--src/lib/dns/tests/testdata/Makefile.am122
-rw-r--r--src/lib/dns/tests/testdata/Makefile.in654
-rw-r--r--src/lib/dns/tests/testdata/broken.zone3
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire1.spec5
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire1.wire9
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire2.spec5
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire2.wire9
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire3.spec7
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire3.wire9
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire4.spec7
-rw-r--r--src/lib/dns/tests/testdata/edns_toWire4.wire9
-rw-r--r--src/lib/dns/tests/testdata/example.org17
-rw-r--r--src/lib/dns/tests/testdata/masterload.txt5
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire122
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire10.spec13
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire10.wire19
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire11.spec15
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire11.wire19
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire12.spec21
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire12.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire13.spec20
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire13.wire35
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire14.spec21
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire14.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire15.spec22
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire15.wire30
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire16.spec21
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire16.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire17.spec22
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire17.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire18.spec23
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire18.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire19.spec20
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire19.wire28
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire222
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire20.spec20
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire20.wire28
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire21.spec20
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire21.wire28
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire22.spec14
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire22.wire20
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire322
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire423
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire533
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire623
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire727
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire822
-rw-r--r--src/lib/dns/tests/testdata/message_fromWire922
-rw-r--r--src/lib/dns/tests/testdata/message_toText1.spec24
-rw-r--r--src/lib/dns/tests/testdata/message_toText1.txt14
-rw-r--r--src/lib/dns/tests/testdata/message_toText1.wire28
-rw-r--r--src/lib/dns/tests/testdata/message_toText2.spec14
-rw-r--r--src/lib/dns/tests/testdata/message_toText2.txt8
-rw-r--r--src/lib/dns/tests/testdata/message_toText2.wire19
-rw-r--r--src/lib/dns/tests/testdata/message_toText3.spec31
-rw-r--r--src/lib/dns/tests/testdata/message_toText3.txt17
-rw-r--r--src/lib/dns/tests/testdata/message_toText3.wire39
-rw-r--r--src/lib/dns/tests/testdata/message_toWire122
-rw-r--r--src/lib/dns/tests/testdata/message_toWire2.spec21
-rw-r--r--src/lib/dns/tests/testdata/message_toWire2.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_toWire3.spec22
-rw-r--r--src/lib/dns/tests/testdata/message_toWire3.wire30
-rw-r--r--src/lib/dns/tests/testdata/message_toWire4.spec27
-rw-r--r--src/lib/dns/tests/testdata/message_toWire4.wire24
-rw-r--r--src/lib/dns/tests/testdata/message_toWire5.spec36
-rw-r--r--src/lib/dns/tests/testdata/message_toWire5.wire34
-rw-r--r--src/lib/dns/tests/testdata/message_toWire648
-rw-r--r--src/lib/dns/tests/testdata/message_toWire735
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire114
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire1012
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire1112
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire1213
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire135
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire147
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire215
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire3_111
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire3_213
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire445
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire614
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire76
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire827
-rw-r--r--src/lib/dns/tests/testdata/name_fromWire912
-rw-r--r--src/lib/dns/tests/testdata/name_toWire112
-rw-r--r--src/lib/dns/tests/testdata/name_toWire214
-rw-r--r--src/lib/dns/tests/testdata/name_toWire314
-rw-r--r--src/lib/dns/tests/testdata/name_toWire416
-rw-r--r--src/lib/dns/tests/testdata/name_toWire5.spec19
-rw-r--r--src/lib/dns/tests/testdata/name_toWire5.wire12
-rw-r--r--src/lib/dns/tests/testdata/name_toWire6.spec19
-rw-r--r--src/lib/dns/tests/testdata/name_toWire6.wire12
-rw-r--r--src/lib/dns/tests/testdata/name_toWire710
-rw-r--r--src/lib/dns/tests/testdata/name_toWire87
-rw-r--r--src/lib/dns/tests/testdata/name_toWire913
-rw-r--r--src/lib/dns/tests/testdata/omitcheck.txt1
-rw-r--r--src/lib/dns/tests/testdata/origincheck.txt5
-rw-r--r--src/lib/dns/tests/testdata/question_fromWire33
-rw-r--r--src/lib/dns/tests/testdata/question_toWire114
-rw-r--r--src/lib/dns/tests/testdata/question_toWire214
-rw-r--r--src/lib/dns/tests/testdata/rdata_dhcid_fromWire12
-rw-r--r--src/lib/dns/tests/testdata/rdata_dhcid_toWire7
-rw-r--r--src/lib/dns/tests/testdata/rdata_in_a_fromWire19
-rw-r--r--src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire18
-rw-r--r--src/lib/dns/tests/testdata/rdata_ns_fromWire44
-rw-r--r--src/lib/dns/tests/testdata/rdata_opt_fromWire115
-rw-r--r--src/lib/dns/tests/testdata/rdata_opt_fromWire24
-rw-r--r--src/lib/dns/tests/testdata/rdata_opt_fromWire38
-rw-r--r--src/lib/dns/tests/testdata/rdata_opt_fromWire49
-rw-r--r--src/lib/dns/tests/testdata/rdata_rrsig_fromWire113
-rw-r--r--src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire12
-rw-r--r--src/lib/dns/tests/testdata/rdata_soa_fromWire20
-rw-r--r--src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec10
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec10
-rw-r--r--src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec15
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire19
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_unknown_fromWire13
-rw-r--r--src/lib/dns/tests/testdata/rrcode16_fromWire14
-rw-r--r--src/lib/dns/tests/testdata/rrcode16_fromWire24
-rw-r--r--src/lib/dns/tests/testdata/rrcode32_fromWire14
-rw-r--r--src/lib/dns/tests/testdata/rrcode32_fromWire24
-rw-r--r--src/lib/dns/tests/testdata/rrset_toWire123
-rw-r--r--src/lib/dns/tests/testdata/rrset_toWire238
-rw-r--r--src/lib/dns/tests/testdata/rrset_toWire312
-rw-r--r--src/lib/dns/tests/testdata/rrset_toWire412
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify1.spec19
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify1.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify10.spec22
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify10.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify11.spec24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify11.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify2.spec32
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify2.wire31
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify3.spec26
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify3.wire25
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify4.spec27
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify4.wire29
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify5.spec26
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify5.wire29
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify6.spec21
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify6.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify7.spec21
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify7.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify8.spec23
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify8.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify9.spec21
-rw-r--r--src/lib/dns/tests/testdata/tsig_verify9.wire24
-rw-r--r--src/lib/dns/tests/testdata/tsigrecord_toWire1.spec16
-rw-r--r--src/lib/dns/tests/testdata/tsigrecord_toWire1.wire14
-rw-r--r--src/lib/dns/tests/testdata/tsigrecord_toWire2.spec19
-rw-r--r--src/lib/dns/tests/testdata/tsigrecord_toWire2.wire20
-rw-r--r--src/lib/dns/tests/time_utils_unittest.cc147
-rw-r--r--src/lib/dns/tests/tsig_unittest.cc1177
-rw-r--r--src/lib/dns/tests/tsigerror_unittest.cc126
-rw-r--r--src/lib/dns/tests/tsigkey_unittest.cc347
-rw-r--r--src/lib/dns/tests/tsigrecord_unittest.cc158
-rw-r--r--src/lib/dns/tests/unittest_util.cc140
-rw-r--r--src/lib/dns/tests/unittest_util.h55
-rw-r--r--src/lib/dns/time_utils.cc195
-rw-r--r--src/lib/dns/time_utils.h168
-rw-r--r--src/lib/dns/tsig.cc577
-rw-r--r--src/lib/dns/tsig.h441
-rw-r--r--src/lib/dns/tsigerror.cc66
-rw-r--r--src/lib/dns/tsigerror.h378
-rw-r--r--src/lib/dns/tsigkey.cc354
-rw-r--r--src/lib/dns/tsigkey.h387
-rw-r--r--src/lib/dns/tsigrecord.cc140
-rw-r--r--src/lib/dns/tsigrecord.h299
-rw-r--r--src/lib/dns/txt_like.h233
317 files changed, 43761 insertions, 0 deletions
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
new file mode 100644
index 0000000..eefa489
--- /dev/null
+++ b/src/lib/dns/Makefile.am
@@ -0,0 +1,95 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+BUILT_SOURCES = rrclass.h rrtype.h rrparamregistry.cc
+BUILT_SOURCES += rdataclass.h rdataclass.cc
+
+lib_LTLIBRARIES = libkea-dns++.la
+
+libkea_dns___la_LDFLAGS = -no-undefined -version-info 55:0:0
+libkea_dns___la_LDFLAGS += $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+libkea_dns___la_SOURCES =
+libkea_dns___la_SOURCES += edns.h edns.cc
+libkea_dns___la_SOURCES += exceptions.h exceptions.cc
+libkea_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
+libkea_dns___la_SOURCES += labelsequence.h labelsequence.cc
+libkea_dns___la_SOURCES += master_lexer.h master_lexer.cc
+libkea_dns___la_SOURCES += master_lexer_state.h
+libkea_dns___la_SOURCES += master_loader.h master_loader.cc
+libkea_dns___la_SOURCES += message.h message.cc
+libkea_dns___la_SOURCES += messagerenderer.h messagerenderer.cc
+libkea_dns___la_SOURCES += name.h name.cc
+libkea_dns___la_SOURCES += name_internal.h
+libkea_dns___la_SOURCES += opcode.h opcode.cc
+libkea_dns___la_SOURCES += rcode.h rcode.cc
+libkea_dns___la_SOURCES += rdata.h rdata.cc
+libkea_dns___la_SOURCES += rrclass.cc
+libkea_dns___la_SOURCES += rrparamregistry.h
+libkea_dns___la_SOURCES += rrset.h rrset.cc
+libkea_dns___la_SOURCES += rrttl.h rrttl.cc
+libkea_dns___la_SOURCES += rrtype.cc
+libkea_dns___la_SOURCES += question.h question.cc
+libkea_dns___la_SOURCES += serial.h serial.cc
+libkea_dns___la_SOURCES += time_utils.h time_utils.cc
+libkea_dns___la_SOURCES += tsig.h tsig.cc
+libkea_dns___la_SOURCES += tsigerror.h tsigerror.cc
+libkea_dns___la_SOURCES += tsigkey.h tsigkey.cc
+libkea_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
+libkea_dns___la_SOURCES += master_loader_callbacks.h
+libkea_dns___la_SOURCES += master_loader.h
+libkea_dns___la_SOURCES += txt_like.h
+libkea_dns___la_SOURCES += char_string.h char_string.cc
+
+
+libkea_dns___la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dns___la_LIBADD = $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_dns___la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_dns___la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_dns___la_LIBADD += $(CRYPTO_LIBS)
+
+# The following files used to be generated, but they are now part of the git tree:
+# rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc
+libkea_dns___la_SOURCES += rdataclass.h rrclass.h rrtype.h
+libkea_dns___la_SOURCES += rdataclass.cc rrparamregistry.cc
+
+libdns___includedir = $(pkgincludedir)/dns
+libdns___include_HEADERS = \
+ char_string.h \
+ edns.h \
+ exceptions.h \
+ labelsequence.h \
+ master_lexer.h \
+ master_lexer_inputsource.h \
+ master_lexer_state.h \
+ master_loader.h \
+ master_loader_callbacks.h \
+ message.h \
+ messagerenderer.h \
+ name.h \
+ opcode.h \
+ question.h \
+ rcode.h \
+ rdata.h \
+ rdataclass.h \
+ rrclass.h \
+ rrparamregistry.h \
+ rrset.h \
+ rrttl.h \
+ rrtype.h \
+ serial.h \
+ time_utils.h \
+ tsig.h \
+ tsigerror.h \
+ tsigkey.h \
+ tsigrecord.h \
+ txt_like.h
+# Purposely not installing these headers:
+# name_internal.h: used only internally, and not actually DNS specific
diff --git a/src/lib/dns/Makefile.in b/src/lib/dns/Makefile.in
new file mode 100644
index 0000000..89ab0a1
--- /dev/null
+++ b/src/lib/dns/Makefile.in
@@ -0,0 +1,1304 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/dns
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp14.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(libdns___include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" \
+ "$(DESTDIR)$(libdns___includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_dns___la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1)
+am_libkea_dns___la_OBJECTS = libkea_dns___la-edns.lo \
+ libkea_dns___la-exceptions.lo \
+ libkea_dns___la-master_lexer_inputsource.lo \
+ libkea_dns___la-labelsequence.lo \
+ libkea_dns___la-master_lexer.lo \
+ libkea_dns___la-master_loader.lo libkea_dns___la-message.lo \
+ libkea_dns___la-messagerenderer.lo libkea_dns___la-name.lo \
+ libkea_dns___la-opcode.lo libkea_dns___la-rcode.lo \
+ libkea_dns___la-rdata.lo libkea_dns___la-rrclass.lo \
+ libkea_dns___la-rrset.lo libkea_dns___la-rrttl.lo \
+ libkea_dns___la-rrtype.lo libkea_dns___la-question.lo \
+ libkea_dns___la-serial.lo libkea_dns___la-time_utils.lo \
+ libkea_dns___la-tsig.lo libkea_dns___la-tsigerror.lo \
+ libkea_dns___la-tsigkey.lo libkea_dns___la-tsigrecord.lo \
+ libkea_dns___la-char_string.lo libkea_dns___la-rdataclass.lo \
+ libkea_dns___la-rrparamregistry.lo
+libkea_dns___la_OBJECTS = $(am_libkea_dns___la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libkea_dns___la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_dns___la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libkea_dns___la-char_string.Plo \
+ ./$(DEPDIR)/libkea_dns___la-edns.Plo \
+ ./$(DEPDIR)/libkea_dns___la-exceptions.Plo \
+ ./$(DEPDIR)/libkea_dns___la-labelsequence.Plo \
+ ./$(DEPDIR)/libkea_dns___la-master_lexer.Plo \
+ ./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo \
+ ./$(DEPDIR)/libkea_dns___la-master_loader.Plo \
+ ./$(DEPDIR)/libkea_dns___la-message.Plo \
+ ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo \
+ ./$(DEPDIR)/libkea_dns___la-name.Plo \
+ ./$(DEPDIR)/libkea_dns___la-opcode.Plo \
+ ./$(DEPDIR)/libkea_dns___la-question.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rcode.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rdata.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rdataclass.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrclass.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrset.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrttl.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrtype.Plo \
+ ./$(DEPDIR)/libkea_dns___la-serial.Plo \
+ ./$(DEPDIR)/libkea_dns___la-time_utils.Plo \
+ ./$(DEPDIR)/libkea_dns___la-tsig.Plo \
+ ./$(DEPDIR)/libkea_dns___la-tsigerror.Plo \
+ ./$(DEPDIR)/libkea_dns___la-tsigkey.Plo \
+ ./$(DEPDIR)/libkea_dns___la-tsigrecord.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_dns___la_SOURCES)
+DIST_SOURCES = $(libkea_dns___la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libdns___include_HEADERS)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DPKG = @DPKG@
+DPKGQUERY = @DPKGQUERY@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+XMLLINT = @XMLLINT@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AUTOMAKE_OPTIONS = subdir-objects
+SUBDIRS = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+BUILT_SOURCES = rrclass.h rrtype.h rrparamregistry.cc rdataclass.h \
+ rdataclass.cc
+lib_LTLIBRARIES = libkea-dns++.la
+libkea_dns___la_LDFLAGS = -no-undefined -version-info 55:0:0 \
+ $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+# The following files used to be generated, but they are now part of the git tree:
+# rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc
+libkea_dns___la_SOURCES = edns.h edns.cc exceptions.h exceptions.cc \
+ master_lexer_inputsource.h master_lexer_inputsource.cc \
+ labelsequence.h labelsequence.cc master_lexer.h \
+ master_lexer.cc master_lexer_state.h master_loader.h \
+ master_loader.cc message.h message.cc messagerenderer.h \
+ messagerenderer.cc name.h name.cc name_internal.h opcode.h \
+ opcode.cc rcode.h rcode.cc rdata.h rdata.cc rrclass.cc \
+ rrparamregistry.h rrset.h rrset.cc rrttl.h rrttl.cc rrtype.cc \
+ question.h question.cc serial.h serial.cc time_utils.h \
+ time_utils.cc tsig.h tsig.cc tsigerror.h tsigerror.cc \
+ tsigkey.h tsigkey.cc tsigrecord.h tsigrecord.cc \
+ master_loader_callbacks.h master_loader.h txt_like.h \
+ char_string.h char_string.cc rdataclass.h rrclass.h rrtype.h \
+ rdataclass.cc rrparamregistry.cc
+libkea_dns___la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dns___la_LIBADD = \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(CRYPTO_LIBS)
+libdns___includedir = $(pkgincludedir)/dns
+libdns___include_HEADERS = \
+ char_string.h \
+ edns.h \
+ exceptions.h \
+ labelsequence.h \
+ master_lexer.h \
+ master_lexer_inputsource.h \
+ master_lexer_state.h \
+ master_loader.h \
+ master_loader_callbacks.h \
+ message.h \
+ messagerenderer.h \
+ name.h \
+ opcode.h \
+ question.h \
+ rcode.h \
+ rdata.h \
+ rdataclass.h \
+ rrclass.h \
+ rrparamregistry.h \
+ rrset.h \
+ rrttl.h \
+ rrtype.h \
+ serial.h \
+ time_utils.h \
+ tsig.h \
+ tsigerror.h \
+ tsigkey.h \
+ tsigrecord.h \
+ txt_like.h
+
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/dns/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dns/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libkea-dns++.la: $(libkea_dns___la_OBJECTS) $(libkea_dns___la_DEPENDENCIES) $(EXTRA_libkea_dns___la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_dns___la_LINK) -rpath $(libdir) $(libkea_dns___la_OBJECTS) $(libkea_dns___la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-char_string.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-edns.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-exceptions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-labelsequence.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-master_lexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-master_loader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-name.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-opcode.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-question.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rcode.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rdata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rdataclass.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrclass.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrset.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrttl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrtype.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-serial.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-time_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsig.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsigerror.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsigkey.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsigrecord.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libkea_dns___la-edns.lo: edns.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-edns.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-edns.Tpo -c -o libkea_dns___la-edns.lo `test -f 'edns.cc' || echo '$(srcdir)/'`edns.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-edns.Tpo $(DEPDIR)/libkea_dns___la-edns.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='edns.cc' object='libkea_dns___la-edns.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-edns.lo `test -f 'edns.cc' || echo '$(srcdir)/'`edns.cc
+
+libkea_dns___la-exceptions.lo: exceptions.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-exceptions.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-exceptions.Tpo -c -o libkea_dns___la-exceptions.lo `test -f 'exceptions.cc' || echo '$(srcdir)/'`exceptions.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-exceptions.Tpo $(DEPDIR)/libkea_dns___la-exceptions.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='exceptions.cc' object='libkea_dns___la-exceptions.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-exceptions.lo `test -f 'exceptions.cc' || echo '$(srcdir)/'`exceptions.cc
+
+libkea_dns___la-master_lexer_inputsource.lo: master_lexer_inputsource.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-master_lexer_inputsource.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Tpo -c -o libkea_dns___la-master_lexer_inputsource.lo `test -f 'master_lexer_inputsource.cc' || echo '$(srcdir)/'`master_lexer_inputsource.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Tpo $(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_inputsource.cc' object='libkea_dns___la-master_lexer_inputsource.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-master_lexer_inputsource.lo `test -f 'master_lexer_inputsource.cc' || echo '$(srcdir)/'`master_lexer_inputsource.cc
+
+libkea_dns___la-labelsequence.lo: labelsequence.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-labelsequence.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-labelsequence.Tpo -c -o libkea_dns___la-labelsequence.lo `test -f 'labelsequence.cc' || echo '$(srcdir)/'`labelsequence.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-labelsequence.Tpo $(DEPDIR)/libkea_dns___la-labelsequence.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labelsequence.cc' object='libkea_dns___la-labelsequence.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-labelsequence.lo `test -f 'labelsequence.cc' || echo '$(srcdir)/'`labelsequence.cc
+
+libkea_dns___la-master_lexer.lo: master_lexer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-master_lexer.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_lexer.Tpo -c -o libkea_dns___la-master_lexer.lo `test -f 'master_lexer.cc' || echo '$(srcdir)/'`master_lexer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_lexer.Tpo $(DEPDIR)/libkea_dns___la-master_lexer.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer.cc' object='libkea_dns___la-master_lexer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-master_lexer.lo `test -f 'master_lexer.cc' || echo '$(srcdir)/'`master_lexer.cc
+
+libkea_dns___la-master_loader.lo: master_loader.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-master_loader.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_loader.Tpo -c -o libkea_dns___la-master_loader.lo `test -f 'master_loader.cc' || echo '$(srcdir)/'`master_loader.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_loader.Tpo $(DEPDIR)/libkea_dns___la-master_loader.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader.cc' object='libkea_dns___la-master_loader.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-master_loader.lo `test -f 'master_loader.cc' || echo '$(srcdir)/'`master_loader.cc
+
+libkea_dns___la-message.lo: message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-message.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-message.Tpo -c -o libkea_dns___la-message.lo `test -f 'message.cc' || echo '$(srcdir)/'`message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-message.Tpo $(DEPDIR)/libkea_dns___la-message.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message.cc' object='libkea_dns___la-message.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-message.lo `test -f 'message.cc' || echo '$(srcdir)/'`message.cc
+
+libkea_dns___la-messagerenderer.lo: messagerenderer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-messagerenderer.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-messagerenderer.Tpo -c -o libkea_dns___la-messagerenderer.lo `test -f 'messagerenderer.cc' || echo '$(srcdir)/'`messagerenderer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-messagerenderer.Tpo $(DEPDIR)/libkea_dns___la-messagerenderer.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='messagerenderer.cc' object='libkea_dns___la-messagerenderer.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-messagerenderer.lo `test -f 'messagerenderer.cc' || echo '$(srcdir)/'`messagerenderer.cc
+
+libkea_dns___la-name.lo: name.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-name.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-name.Tpo -c -o libkea_dns___la-name.lo `test -f 'name.cc' || echo '$(srcdir)/'`name.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-name.Tpo $(DEPDIR)/libkea_dns___la-name.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='name.cc' object='libkea_dns___la-name.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-name.lo `test -f 'name.cc' || echo '$(srcdir)/'`name.cc
+
+libkea_dns___la-opcode.lo: opcode.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-opcode.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-opcode.Tpo -c -o libkea_dns___la-opcode.lo `test -f 'opcode.cc' || echo '$(srcdir)/'`opcode.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-opcode.Tpo $(DEPDIR)/libkea_dns___la-opcode.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opcode.cc' object='libkea_dns___la-opcode.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-opcode.lo `test -f 'opcode.cc' || echo '$(srcdir)/'`opcode.cc
+
+libkea_dns___la-rcode.lo: rcode.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rcode.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rcode.Tpo -c -o libkea_dns___la-rcode.lo `test -f 'rcode.cc' || echo '$(srcdir)/'`rcode.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rcode.Tpo $(DEPDIR)/libkea_dns___la-rcode.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rcode.cc' object='libkea_dns___la-rcode.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rcode.lo `test -f 'rcode.cc' || echo '$(srcdir)/'`rcode.cc
+
+libkea_dns___la-rdata.lo: rdata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rdata.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rdata.Tpo -c -o libkea_dns___la-rdata.lo `test -f 'rdata.cc' || echo '$(srcdir)/'`rdata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rdata.Tpo $(DEPDIR)/libkea_dns___la-rdata.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata.cc' object='libkea_dns___la-rdata.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rdata.lo `test -f 'rdata.cc' || echo '$(srcdir)/'`rdata.cc
+
+libkea_dns___la-rrclass.lo: rrclass.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrclass.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrclass.Tpo -c -o libkea_dns___la-rrclass.lo `test -f 'rrclass.cc' || echo '$(srcdir)/'`rrclass.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrclass.Tpo $(DEPDIR)/libkea_dns___la-rrclass.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrclass.cc' object='libkea_dns___la-rrclass.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrclass.lo `test -f 'rrclass.cc' || echo '$(srcdir)/'`rrclass.cc
+
+libkea_dns___la-rrset.lo: rrset.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrset.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrset.Tpo -c -o libkea_dns___la-rrset.lo `test -f 'rrset.cc' || echo '$(srcdir)/'`rrset.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrset.Tpo $(DEPDIR)/libkea_dns___la-rrset.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset.cc' object='libkea_dns___la-rrset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrset.lo `test -f 'rrset.cc' || echo '$(srcdir)/'`rrset.cc
+
+libkea_dns___la-rrttl.lo: rrttl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrttl.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrttl.Tpo -c -o libkea_dns___la-rrttl.lo `test -f 'rrttl.cc' || echo '$(srcdir)/'`rrttl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrttl.Tpo $(DEPDIR)/libkea_dns___la-rrttl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrttl.cc' object='libkea_dns___la-rrttl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrttl.lo `test -f 'rrttl.cc' || echo '$(srcdir)/'`rrttl.cc
+
+libkea_dns___la-rrtype.lo: rrtype.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrtype.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrtype.Tpo -c -o libkea_dns___la-rrtype.lo `test -f 'rrtype.cc' || echo '$(srcdir)/'`rrtype.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrtype.Tpo $(DEPDIR)/libkea_dns___la-rrtype.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrtype.cc' object='libkea_dns___la-rrtype.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrtype.lo `test -f 'rrtype.cc' || echo '$(srcdir)/'`rrtype.cc
+
+libkea_dns___la-question.lo: question.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-question.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-question.Tpo -c -o libkea_dns___la-question.lo `test -f 'question.cc' || echo '$(srcdir)/'`question.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-question.Tpo $(DEPDIR)/libkea_dns___la-question.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='question.cc' object='libkea_dns___la-question.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-question.lo `test -f 'question.cc' || echo '$(srcdir)/'`question.cc
+
+libkea_dns___la-serial.lo: serial.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-serial.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-serial.Tpo -c -o libkea_dns___la-serial.lo `test -f 'serial.cc' || echo '$(srcdir)/'`serial.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-serial.Tpo $(DEPDIR)/libkea_dns___la-serial.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='serial.cc' object='libkea_dns___la-serial.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-serial.lo `test -f 'serial.cc' || echo '$(srcdir)/'`serial.cc
+
+libkea_dns___la-time_utils.lo: time_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-time_utils.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-time_utils.Tpo -c -o libkea_dns___la-time_utils.lo `test -f 'time_utils.cc' || echo '$(srcdir)/'`time_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-time_utils.Tpo $(DEPDIR)/libkea_dns___la-time_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utils.cc' object='libkea_dns___la-time_utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-time_utils.lo `test -f 'time_utils.cc' || echo '$(srcdir)/'`time_utils.cc
+
+libkea_dns___la-tsig.lo: tsig.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsig.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsig.Tpo -c -o libkea_dns___la-tsig.lo `test -f 'tsig.cc' || echo '$(srcdir)/'`tsig.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsig.Tpo $(DEPDIR)/libkea_dns___la-tsig.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsig.cc' object='libkea_dns___la-tsig.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsig.lo `test -f 'tsig.cc' || echo '$(srcdir)/'`tsig.cc
+
+libkea_dns___la-tsigerror.lo: tsigerror.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsigerror.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsigerror.Tpo -c -o libkea_dns___la-tsigerror.lo `test -f 'tsigerror.cc' || echo '$(srcdir)/'`tsigerror.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsigerror.Tpo $(DEPDIR)/libkea_dns___la-tsigerror.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigerror.cc' object='libkea_dns___la-tsigerror.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsigerror.lo `test -f 'tsigerror.cc' || echo '$(srcdir)/'`tsigerror.cc
+
+libkea_dns___la-tsigkey.lo: tsigkey.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsigkey.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsigkey.Tpo -c -o libkea_dns___la-tsigkey.lo `test -f 'tsigkey.cc' || echo '$(srcdir)/'`tsigkey.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsigkey.Tpo $(DEPDIR)/libkea_dns___la-tsigkey.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigkey.cc' object='libkea_dns___la-tsigkey.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsigkey.lo `test -f 'tsigkey.cc' || echo '$(srcdir)/'`tsigkey.cc
+
+libkea_dns___la-tsigrecord.lo: tsigrecord.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsigrecord.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsigrecord.Tpo -c -o libkea_dns___la-tsigrecord.lo `test -f 'tsigrecord.cc' || echo '$(srcdir)/'`tsigrecord.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsigrecord.Tpo $(DEPDIR)/libkea_dns___la-tsigrecord.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigrecord.cc' object='libkea_dns___la-tsigrecord.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsigrecord.lo `test -f 'tsigrecord.cc' || echo '$(srcdir)/'`tsigrecord.cc
+
+libkea_dns___la-char_string.lo: char_string.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-char_string.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-char_string.Tpo -c -o libkea_dns___la-char_string.lo `test -f 'char_string.cc' || echo '$(srcdir)/'`char_string.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-char_string.Tpo $(DEPDIR)/libkea_dns___la-char_string.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='char_string.cc' object='libkea_dns___la-char_string.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-char_string.lo `test -f 'char_string.cc' || echo '$(srcdir)/'`char_string.cc
+
+libkea_dns___la-rdataclass.lo: rdataclass.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rdataclass.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rdataclass.Tpo -c -o libkea_dns___la-rdataclass.lo `test -f 'rdataclass.cc' || echo '$(srcdir)/'`rdataclass.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rdataclass.Tpo $(DEPDIR)/libkea_dns___la-rdataclass.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdataclass.cc' object='libkea_dns___la-rdataclass.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rdataclass.lo `test -f 'rdataclass.cc' || echo '$(srcdir)/'`rdataclass.cc
+
+libkea_dns___la-rrparamregistry.lo: rrparamregistry.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrparamregistry.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrparamregistry.Tpo -c -o libkea_dns___la-rrparamregistry.lo `test -f 'rrparamregistry.cc' || echo '$(srcdir)/'`rrparamregistry.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrparamregistry.Tpo $(DEPDIR)/libkea_dns___la-rrparamregistry.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrparamregistry.cc' object='libkea_dns___la-rrparamregistry.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrparamregistry.lo `test -f 'rrparamregistry.cc' || echo '$(srcdir)/'`rrparamregistry.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libdns___includeHEADERS: $(libdns___include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libdns___include_HEADERS)'; test -n "$(libdns___includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdns___includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdns___includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libdns___includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libdns___includedir)" || exit $$?; \
+ done
+
+uninstall-libdns___includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libdns___include_HEADERS)'; test -n "$(libdns___includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libdns___includedir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libdns___includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-recursive
+install-exec: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-recursive
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_dns___la-char_string.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-edns.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-exceptions.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-labelsequence.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-master_loader.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-message.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-name.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-opcode.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-question.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rcode.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rdata.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rdataclass.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrclass.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrset.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrttl.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrtype.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-serial.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-time_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsig.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsigerror.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsigkey.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsigrecord.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-libdns___includeHEADERS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/libkea_dns___la-char_string.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-edns.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-exceptions.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-labelsequence.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-master_loader.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-message.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-name.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-opcode.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-question.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rcode.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rdata.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rdataclass.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrclass.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrset.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrttl.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrtype.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-serial.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-time_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsig.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsigerror.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsigkey.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-tsigrecord.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES \
+ uninstall-libdns___includeHEADERS
+
+.MAKE: $(am__recursive_targets) all check install install-am \
+ install-exec install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic \
+ clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-libLTLIBRARIES \
+ install-libdns___includeHEADERS install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-libLTLIBRARIES uninstall-libdns___includeHEADERS
+
+.PRECIOUS: Makefile
+
+# Purposely not installing these headers:
+# name_internal.h: used only internally, and not actually DNS specific
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/dns/char_string.cc b/src/lib/dns/char_string.cc
new file mode 100644
index 0000000..25cb1f1
--- /dev/null
+++ b/src/lib/dns/char_string.cc
@@ -0,0 +1,264 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+#include <dns/char_string.h>
+#include <util/buffer.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cctype>
+#include <cstring>
+#include <vector>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+namespace {
+// Convert a DDD form to the corresponding integer
+int
+decimalToNumber(const char* s, const char* s_end) {
+ if (s_end - s < 3) {
+ isc_throw(InvalidRdataText, "Escaped digits too short");
+ }
+
+ const std::string num_str(s, s + 3);
+ try {
+ const int i = boost::lexical_cast<int>(num_str);
+ if (i > 255) {
+ isc_throw(InvalidRdataText, "Escaped digits too large: "
+ << num_str);
+ }
+ return (i);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText,
+ "Invalid form for escaped digits: " << num_str);
+ }
+}
+}
+
+void
+stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result) {
+ // make a space for the 1-byte length field; filled in at the end
+ result.push_back(0);
+
+ bool escape = false;
+ const char* s = str_region.beg;
+ const char* const s_end = str_region.beg + str_region.len;
+
+ for (size_t n = str_region.len; n != 0; --n, ++s) {
+ int c = (*s & 0xff);
+ if (escape && std::isdigit(c) != 0) {
+ c = decimalToNumber(s, s_end);
+ // decimalToNumber() already throws if (s_end - s) is less
+ // than 3. 'n' is an unsigned type (size_t) and can underflow.
+ // 'n' and 's' are also updated by 1 in the for statement's
+ // expression, so we update them by 2 instead of 3 here.
+ n -= 2;
+ s += 2;
+ } else if (!escape && c == '\\') {
+ escape = true;
+ continue;
+ }
+ escape = false;
+ result.push_back(c);
+ }
+ if (escape) { // terminated by non-escaped '\'
+ isc_throw(InvalidRdataText, "character-string ends with '\\'");
+ }
+ if (result.size() > MAX_CHARSTRING_LEN + 1) { // '+ 1' due to the len field
+ isc_throw(CharStringTooLong, "character-string is too long: " <<
+ (result.size() - 1) << "(+1) characters");
+ }
+ result[0] = result.size() - 1;
+}
+
+void
+stringToCharStringData(const MasterToken::StringRegion& str_region,
+ CharStringData& result) {
+ bool escape = false;
+ const char* s = str_region.beg;
+ const char* const s_end = str_region.beg + str_region.len;
+
+ for (size_t n = str_region.len; n != 0; --n, ++s) {
+ int c = (*s & 0xff);
+ if (escape && std::isdigit(c) != 0) {
+ c = decimalToNumber(s, s_end);
+ // decimalToNumber() already throws if (s_end - s) is less
+ // than 3. 'n' is an unsigned type (size_t) and can underflow.
+ // 'n' and 's' are also updated by 1 in the for statement's
+ // expression, so we update them by 2 instead of 3 here.
+ n -= 2;
+ s += 2;
+ } else if (!escape && c == '\\') {
+ escape = true;
+ continue;
+ }
+ escape = false;
+ result.push_back(c);
+ }
+ if (escape) { // terminated by non-escaped '\'
+ isc_throw(InvalidRdataText, "character-string ends with '\\'");
+ }
+}
+
+std::string
+charStringToString(const CharString& char_string) {
+ std::string s;
+ bool first = true;
+ for (auto const& it : char_string) {
+ if (first) {
+ first = false;
+ continue;
+ }
+ const uint8_t ch = it;
+ if ((ch < 0x20) || (ch >= 0x7f)) {
+ // convert to escaped \xxx (decimal) format
+ s.push_back('\\');
+ s.push_back('0' + ((ch / 100) % 10));
+ s.push_back('0' + ((ch / 10) % 10));
+ s.push_back('0' + (ch % 10));
+ continue;
+ }
+ if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+ s.push_back('\\');
+ }
+ s.push_back(ch);
+ }
+
+ return (s);
+}
+
+std::string
+charStringDataToString(const CharStringData& char_string) {
+ std::string s;
+ for (auto const& it : char_string) {
+ const uint8_t ch = it;
+ if ((ch < 0x20) || (ch >= 0x7f)) {
+ // convert to escaped \xxx (decimal) format
+ s.push_back('\\');
+ s.push_back('0' + ((ch / 100) % 10));
+ s.push_back('0' + ((ch / 10) % 10));
+ s.push_back('0' + (ch % 10));
+ continue;
+ }
+ if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+ s.push_back('\\');
+ }
+ s.push_back(ch);
+ }
+
+ return (s);
+}
+
+int compareCharStrings(const detail::CharString& self,
+ const detail::CharString& other) {
+ if (self.size() == 0 && other.size() == 0) {
+ return (0);
+ }
+ if (self.size() == 0) {
+ return (-1);
+ }
+ if (other.size() == 0) {
+ return (1);
+ }
+ const size_t self_len = self[0];
+ const size_t other_len = other[0];
+ const size_t cmp_len = std::min(self_len, other_len);
+ if (cmp_len == 0) {
+ if (self_len < other_len) {
+ return (-1);
+ } else if (self_len > other_len) {
+ return (1);
+ } else {
+ return (0);
+ }
+ }
+ const int cmp = std::memcmp(&self[1], &other[1], cmp_len);
+ if (cmp < 0) {
+ return (-1);
+ } else if (cmp > 0) {
+ return (1);
+ } else if (self_len < other_len) {
+ return (-1);
+ } else if (self_len > other_len) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+int compareCharStringDatas(const detail::CharStringData& self,
+ const detail::CharStringData& other) {
+ if (self.size() == 0 && other.size() == 0) {
+ return (0);
+ }
+ if (self.size() == 0) {
+ return (-1);
+ }
+ if (other.size() == 0) {
+ return (1);
+ }
+ const size_t self_len = self.size();
+ const size_t other_len = other.size();
+ const size_t cmp_len = std::min(self_len, other_len);
+ const int cmp = std::memcmp(&self[0], &other[0], cmp_len);
+ if (cmp < 0) {
+ return (-1);
+ } else if (cmp > 0) {
+ return (1);
+ } else if (self_len < other_len) {
+ return (-1);
+ } else if (self_len > other_len) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+size_t
+bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target) {
+ if (rdata_len < 1 || buffer.getLength() - buffer.getPosition() < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "insufficient data to read character-string length");
+ }
+ const uint8_t len = buffer.readUint8();
+ if (rdata_len < len + 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "character string length is too large: " <<
+ static_cast<int>(len));
+ }
+ if (buffer.getLength() - buffer.getPosition() < len) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "not enough data in buffer to read character-string of len"
+ << static_cast<int>(len));
+ }
+
+ target.resize(len + 1);
+ target[0] = len;
+ buffer.readData(&target[0] + 1, len);
+
+ return (len + 1);
+}
+
+} // end of detail
+} // end of generic
+} // end of rdata
+} // end of dns
+} // end of isc
diff --git a/src/lib/dns/char_string.h b/src/lib/dns/char_string.h
new file mode 100644
index 0000000..61c9925
--- /dev/null
+++ b/src/lib/dns/char_string.h
@@ -0,0 +1,136 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DNS_RDATA_CHARSTRING_H
+#define DNS_RDATA_CHARSTRING_H
+
+#include <dns/master_lexer.h>
+
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Type for DNS character string.
+///
+/// A character string can contain any unsigned 8-bit value, so this cannot
+/// be the bare char basis.
+typedef std::vector<uint8_t> CharString;
+
+/// \brief Type for DNS character string without the length prefix.
+typedef std::vector<uint8_t> CharStringData;
+
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This helper function takes a string object that is expected to be a
+/// textual representation of a valid DNS character-string, and dumps
+/// the corresponding binary sequence in the given placeholder (passed
+/// via the \c result parameter). It handles escape notations of
+/// character-strings with a backslash ('\'), and checks the length
+/// restriction.
+///
+/// \throw CharStringTooLong The resulting binary data are too large for a
+/// valid character-string.
+/// \throw InvalidRdataText Other syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored. Expected to be empty, but it's not checked.
+void stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result);
+
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This method functions similar to \c stringToCharString() except it
+/// does not include the 1-octet length prefix in the \c result, and the
+/// result is not limited to MAX_CHARSTRING_LEN octets.
+///
+/// \throw InvalidRdataText Upon syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored. Expected to be empty, but it's not checked.
+void stringToCharStringData(const MasterToken::StringRegion& str_region,
+ CharStringData& result);
+
+/// \brief Convert a CharString into a textual DNS character-string.
+///
+/// This method converts a binary 8-bit representation of a DNS
+/// character string into a textual string representation, escaping any
+/// special characters in the process. For example, characters like
+/// double-quotes, semi-colon and backspace are prefixed with backspace
+/// character, and characters not in the printable range of [0x20, 0x7e]
+/// (inclusive) are converted to the \\xxx 3-digit decimal
+/// representation.
+///
+/// \param char_string The \c CharString to convert.
+/// \return A string representation of \c char_string.
+std::string charStringToString(const CharString& char_string);
+
+/// \brief Convert a CharStringData into a textual DNS character-string.
+///
+/// Reverse of \c stringToCharStringData(). See \c stringToCharString()
+/// vs. \c stringToCharStringData().
+///
+/// \param char_string The \c CharStringData to convert.
+/// \return A string representation of \c char_string.
+std::string charStringDataToString(const CharStringData& char_string);
+
+/// \brief Compare two CharString objects
+///
+/// \param self The CharString field to compare
+/// \param other The CharString field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+/// 1 if \c self would be sorted after \c other
+/// 0 if \c self and \c other are equal
+int compareCharStrings(const CharString& self, const CharString& other);
+
+/// \brief Compare two CharStringData objects
+///
+/// \param self The CharStringData field to compare
+/// \param other The CharStringData field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+/// 1 if \c self would be sorted after \c other
+/// 0 if \c self and \c other are equal
+int compareCharStringDatas(const CharStringData& self,
+ const CharStringData& other);
+
+/// \brief Convert a buffer containing a character-string to CharString
+///
+/// This method reads one character-string from the given buffer (in wire
+/// format) and places the result in the given \c CharString object.
+/// Since this is expected to be used in message parsing, the exception it
+/// raises is of that type.
+///
+/// On success, the buffer position is advanced to the end of the char-string,
+/// and the number of bytes read is returned.
+///
+/// \param buffer The buffer to read from.
+/// \param rdata_len The total size of the rr's rdata currently being read
+/// (used for integrity checks in the wire data)
+/// \param target The \c CharString where the result will be stored. Any
+/// existing data in the target will be overwritten.
+/// \throw DNSMessageFORMERR If the available data is not enough to read
+/// the character-string, or if the character-string length is out of bounds
+/// \return The number of bytes read
+size_t bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target);
+
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif // DNS_RDATA_CHARSTRING_H
diff --git a/src/lib/dns/edns.cc b/src/lib/dns/edns.cc
new file mode 100644
index 0000000..6bb7604
--- /dev/null
+++ b/src/lib/dns/edns.cc
@@ -0,0 +1,169 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+#include <stdint.h>
+#include <boost/lexical_cast.hpp>
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+using namespace std;
+using boost::lexical_cast;
+
+namespace isc {
+namespace dns {
+
+namespace {
+// This diagram shows the wire-format representation of the TTL field of
+// OPT RR and its relationship with implementation specific parameters.
+//
+// 0 7 15 31
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | EXTENDED-RCODE| VERSION |D| Z |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// <= VERSION_SHIFT (16 bits)
+// <= EXTRCODE_SHIFT (24 bits)
+//EXTFLAG_DO:0 0 0 ....................... 0 1 0 0 0 0.....................0
+//VER_MASK: 0 0 0 ........0 1 1 1 1 1 1 1 1 0 0 ..........................0
+
+const unsigned int VERSION_SHIFT = 16;
+const unsigned int EXTRCODE_SHIFT = 24;
+const uint32_t VERSION_MASK = 0x00ff0000;
+const uint32_t EXTFLAG_DO = 0x00008000;
+}
+
+EDNS::EDNS(const uint8_t version) :
+ version_(version),
+ udp_size_(Message::DEFAULT_MAX_UDPSIZE),
+ dnssec_aware_(false) {
+ if (version_ > SUPPORTED_VERSION) {
+ isc_throw(isc::InvalidParameter,
+ "failed to construct EDNS: unsupported version: " <<
+ static_cast<unsigned int>(version_));
+ }
+}
+
+EDNS::EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, const Rdata&) :
+ version_((ttl.getValue() & VERSION_MASK) >> VERSION_SHIFT) {
+ if (rrtype != RRType::OPT()) {
+ isc_throw(isc::InvalidParameter,
+ "EDNS is being created with incompatible RR type: "
+ << rrtype);
+ }
+
+ if (version_ > EDNS::SUPPORTED_VERSION) {
+ isc_throw(DNSMessageBADVERS, "unsupported EDNS version: " <<
+ static_cast<unsigned int>(version_));
+ }
+
+ if (name != Name::ROOT_NAME()) {
+ isc_throw(DNSMessageFORMERR, "invalid owner name for EDNS OPT RR: " <<
+ name);
+ }
+
+ dnssec_aware_ = ((ttl.getValue() & EXTFLAG_DO) != 0);
+ udp_size_ = rrclass.getCode();
+}
+
+string
+EDNS::toText() const {
+ string ret = "; EDNS: version: ";
+
+ ret += lexical_cast<string>(static_cast<int>(getVersion()));
+ ret += ", flags:";
+ if (getDNSSECAwareness()) {
+ ret += " do";
+ }
+ ret += "; udp: " + lexical_cast<string>(getUDPSize()) + "\n";
+
+ return (ret);
+}
+
+namespace {
+/// Helper function to define unified implementation for the public versions
+/// of toWire().
+template <typename Output>
+uint32_t
+toWireCommon(Output& output, const uint8_t version,
+ const uint16_t udp_size, const bool dnssec_aware,
+ const uint8_t extended_rcode) {
+ // Render EDNS OPT RR
+ uint32_t extrcode_flags = extended_rcode << EXTRCODE_SHIFT;
+ extrcode_flags |= (version << VERSION_SHIFT) & VERSION_MASK;
+ if (dnssec_aware) {
+ extrcode_flags |= EXTFLAG_DO;
+ }
+
+ // Construct an RRset corresponding to the EDNS.
+ // We don't support any options for now, so the OPT RR can be empty.
+ RRsetPtr edns_rrset(new RRset(Name::ROOT_NAME(), RRClass(udp_size),
+ RRType::OPT(), RRTTL(extrcode_flags)));
+ edns_rrset->addRdata(ConstRdataPtr(new generic::OPT()));
+
+ edns_rrset->toWire(output);
+
+ return (1);
+}
+}
+
+uint32_t
+EDNS::toWire(AbstractMessageRenderer& renderer,
+ const uint8_t extended_rcode) const {
+ // If adding the OPT RR would exceed the size limit, don't do it.
+ // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte)
+ // (RDATA is empty in this simple implementation)
+ if (renderer.getLength() + 11 > renderer.getLengthLimit()) {
+ return (0);
+ }
+
+ return (toWireCommon(renderer, version_, udp_size_, dnssec_aware_,
+ extended_rcode));
+}
+
+uint32_t
+EDNS::toWire(isc::util::OutputBuffer& buffer,
+ const uint8_t extended_rcode) const {
+ return (toWireCommon(buffer, version_, udp_size_, dnssec_aware_,
+ extended_rcode));
+}
+
+EDNS*
+createEDNSFromRR(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl,
+ const Rdata& rdata,
+ uint8_t& extended_rcode) {
+ // Create a new EDNS object first for exception guarantee.
+ EDNS* edns = new EDNS(name, rrclass, rrtype, ttl, rdata);
+
+ // At this point we can update extended_rcode safely.
+ extended_rcode = ttl.getValue() >> EXTRCODE_SHIFT;
+
+ return (edns);
+}
+
+ostream&
+operator<<(std::ostream& os, const EDNS& edns) {
+ os << edns.toText();
+ return (os);
+}
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/edns.h b/src/lib/dns/edns.h
new file mode 100644
index 0000000..b604a9b
--- /dev/null
+++ b/src/lib/dns/edns.h
@@ -0,0 +1,431 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef EDNS_H
+#define EDNS_H
+
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <ostream>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class AbstractMessageRenderer;
+class RRClass;
+class RRTTL;
+class RRType;
+class Rcode;
+
+/// \brief A pointer-like type pointing to an \c EDNS object.
+typedef boost::shared_ptr<EDNS> EDNSPtr;
+
+/// \brief A pointer-like type pointing to an immutable \c EDNS object.
+typedef boost::shared_ptr<const EDNS> ConstEDNSPtr;
+
+/// The \c EDNS class represents the %EDNS OPT RR defined in RFC2671.
+///
+/// This class encapsulates various optional features of %EDNS such as
+/// the UDP payload size or the DNSSEC DO bit, and provides interfaces
+/// to manage these features. It is also responsible for conversion
+/// to and from wire-format OPT RR.
+/// One important exception is about the extended RCODE:
+/// The \c EDNS class is only responsible for extracting the 8-bit part
+/// of the 12-bit extended RCODE from the OPT RR's TTL field of an
+/// incoming message, and for setting the 8-bit part into the OPT RR TTL
+/// of an outgoing message. It's not supposed to know how to construct the
+/// complete RCODE, much less maintain the RCODE in it.
+/// It is the caller's responsibility (typically the \c Message class).
+///
+/// When converting wire-format OPT RR into an \c EDNS object, it normalizes
+/// the information, i.e., unknown flags will be ignored on construction.
+///
+/// This class is also supposed to support %EDNS options such as NSID,
+/// but the initial implementation does not include it. This is a near term
+/// TODO item.
+///
+/// <b>Notes to developers</b>
+///
+/// The rest of the description is for developers who need to or want to
+/// understand the design of this API.
+///
+/// Representing %EDNS is tricky. An OPT RR is no different from other RRs
+/// in terms of the wire format syntax, and in that sense we could use the
+/// generic \c RRset class to represent an OPT RR (BIND 9 adopts this
+/// approach). But the resulting interface would be inconvenient for
+/// developers. For example, the developer would need to know that the
+/// UDP size is encoded in the RR Class field. It's better to provide
+/// a more abstract interface along with the special semantics of OPT RR.
+///
+/// Another approach would be to realize each optional feature of EDNS
+/// as an attribute of the DNS message.
+/// NLnet Labs' ldns takes this approach.
+/// This way an operation for specifying the UDP size would be written
+/// like this:
+/// \code message->setUDPSize(4096); \endcode
+/// which should be more intuitive.
+/// A drawback of this approach is that OPT RR is itself optional and the
+/// separate parameters may not necessarily indicate whether to include an
+/// OPT RR per se.
+/// For example, consider what should be done with this code:
+/// \code message->setUDPSize(512); \endcode
+/// Since the payload size of 512 is the default, it may mean the OPT RR
+/// should be skipped. But it might also mean the caller intentionally
+/// (for some reason) wants to insert an OPT RR specifying the default UDP
+/// size explicitly.
+///
+/// So, we use a separate class that encapsulates the EDNS semantics and
+/// knows the mapping between the semantics and the wire format representation.
+/// This way the interface can be semantics-based and is intuitive:
+/// \code edns->setUDPSize(4096); \endcode
+/// while we can explicitly specify whether to include an OPT RR by setting
+/// (or not setting) an \c EDNS object in a message:
+/// \code message->setEDNS(edns); // unless we do this OPT RR is skipped
+/// \endcode
+///
+/// There is still a non trivial point: How to manage extended RCODEs.
+/// An OPT RR encodes the upper 8 bits of extended 12-bit RCODE.
+/// In general, it would be better to provide a unified interface to get
+/// access to RCODEs whether or not they are traditional 4 bit codes or
+/// extended ones that have non 0 upper bits.
+/// However, since an OPT RR may not appear in a message the RCODE cannot be
+/// maintained in the \c EDNS class.
+/// But it would not be desirable to maintain the extended RCODEs completely
+/// in the \c Message class, either, because we wanted to hide the mapping
+/// between %EDNS semantics and its wire format representation within the
+/// \c EDNS class; if we moved the responsibility about RCODEs to the
+/// \c Message class, it would have to parse and render the upper 8 bits of
+/// the RCODEs, dealing with wire representation of OPT RR.
+/// This is suboptimal in the sense of encapsulation.
+///
+/// As a compromise, our decision is to separate the knowledge about the
+/// relationship with RCODE from the knowledge about the wire format as
+/// noted in the beginning of this description.
+///
+/// This decoupling is based on the observation that the extended RCODE
+/// is a very special case where %EDNS only has partial information.
+/// If a future version of the %EDNS protocol introduces further relationship
+/// between the message and the %EDNS, we might reconsider the interface,
+/// probably with higher abstraction.
+class EDNS {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// We use the default copy constructor, default copy assignment operator,
+ /// and default destructors intentionally.
+ ///
+ /// Note about copyability: This version of this class is copyable,
+ /// but we may want to change it once we support EDNS options, when
+ /// we want to revise this class using the pimpl idiom.
+ /// But we should be careful about that: the python binding currently
+ /// assumes this class is copyable.
+ //@{
+ /// Constructor with the EDNS version.
+ /// An application would use this constructor to specify EDNS parameters
+ /// and/or options for outgoing DNS messages.
+ ///
+ /// All other parameters than the version number will be initialized to
+ /// reasonable defaults.
+ /// Specifically, the UDP payload size is set to
+ /// \c Message::DEFAULT_MAX_UDPSIZE, and DNSSEC is assumed to be not
+ /// supported.
+ /// These parameters can be altered via setter methods of this class.
+ /// Note, however, that the version number cannot be changed once
+ /// constructed.
+ ///
+ /// The version number parameter can be omitted, in which case the highest
+ /// supported version in this implementation will be assumed.
+ /// When specified, if it is larger than the highest supported version,
+ /// an exception of class \c isc::InvalidParameter will be thrown.
+ ///
+ /// This constructor throws no other exception.
+ ///
+ /// \param version The version number of the EDNS to be constructed.
+ explicit EDNS(const uint8_t version = SUPPORTED_VERSION);
+
+ /// \brief Constructor from resource record (RR) parameters.
+ ///
+ /// This constructor is intended to be used to construct an EDNS object
+ /// from an OPT RR contained in an incoming DNS message.
+ ///
+ /// Unlike many other constructors for this purpose, this constructor
+ /// does not take the bare wire-format %data in the form of an
+ /// \c InputBuffer object. This is because parsing incoming EDNS is
+ /// highly context dependent and it's not feasible to handle it in a
+ /// completely polymorphic way. For example, a DNS message parser would
+ /// have to check an OPT RR appears at most once in the message, and if
+ /// it appears it should be in the additional section. So, the parser
+ /// needs to have an explicit check to see if an RR is of type OPT, and
+ /// then (if other conditions are met) construct a corresponding \c EDNS
+ /// object. At that point the parser would have already converted the
+ /// wire %data into corresponding objects of \c Name, \c RRClass,
+ /// \c RRType, etc, and it makes more sense to pass them directly to the
+ /// constructor.
+ ///
+ /// In practice, top level applications rarely need to use this
+ /// constructor directly. It should normally suffice to have a higher
+ /// level class such as \c Message do that job.
+ ///
+ /// This constructor checks the passed parameters to see if they are
+ /// valid in terms of the EDNS protocol specification.
+ /// \c name must be the root name ("."); otherwise, an exception of
+ /// class \c DNSMessageFORMERR will be thrown.
+ /// \c rrtype must specify the OPT RR type; otherwise, an exception of
+ /// class \c isc::InvalidParameter will be thrown.
+ /// The ENDS version number is extracted from \c rrttl. If it is larger
+ /// than the higher supported version, an exception of class
+ /// \c DNSMessageBADVERS will be thrown. Note that this is different from
+ /// the case of the same error in the other constructor.
+ /// This is intentional, so that the application can transparently convert
+ /// the exception to a response RCODE according to the protocol
+ /// specification.
+ ///
+ /// This initial implementation does not support EDNS options at all,
+ /// and \c rdata is simply ignored. Future versions will support
+ /// options, and may throw exceptions while validating the given parameter.
+ ///
+ /// \b Note: since no other type than OPT for \c rrtype is allowed, this
+ /// parameter could actually have been omitted. But it is intentionally
+ /// included as a parameter so that invalid usage of the construction
+ /// can be detected. As noted above the caller should normally have
+ /// the corresponding \c RRType object at the time of call to this
+ /// constructor, so the overhead of having the additional parameter
+ /// should be marginal.
+ ///
+ /// \param name The owner name of the OPT RR. This must be the root name.
+ /// \param rrclass The RR class of the OPT RR.
+ /// \param rrtype This must specify the OPT RR type.
+ /// \param ttl The TTL of the OPT RR.
+ /// \param rdata The RDATA of the OPT RR.
+ EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, const rdata::Rdata& rdata);
+ //@}
+
+ ///
+ /// \name Getter and Setter Methods
+ ///
+ //@{
+ /// \brief Returns the version of EDNS.
+ ///
+ /// This method never throws an exception.
+ uint8_t getVersion() const { return (version_); }
+
+ /// \brief Returns the maximum payload size of UDP messages for the sender
+ /// of the message containing this \c EDNS.
+ ///
+ /// This method never throws an exception.
+ uint16_t getUDPSize() const { return (udp_size_); }
+
+ /// \brief Specify the maximum payload size of UDP messages that use
+ /// this EDNS.
+ ///
+ /// Unless explicitly specified, \c DEFAULT_MAX_UDPSIZE will be assumed
+ /// for the maximum payload size, regardless of whether EDNS OPT RR is
+ /// included or not. This means if an application wants to send a message
+ /// with an EDNS OPT RR for specifying a larger UDP size, it must
+ /// explicitly specify the value using this method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param udp_size The maximum payload size of UDP messages for the sender
+ /// of the message containing this \c EDNS.
+ void setUDPSize(const uint16_t udp_size) { udp_size_ = udp_size; }
+
+ /// \brief Returns whether the message sender is DNSSEC aware.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return true if DNSSEC is supported; otherwise false.
+ bool getDNSSECAwareness() const { return (dnssec_aware_); }
+
+ /// \brief Specifies whether the sender of the message containing this
+ /// \c EDNS is DNSSEC aware.
+ ///
+ /// If the parameter is true, a subsequent call to \c toWire() will
+ /// set the DNSSEC DO bit on for the corresponding OPT RR.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param is_aware \c true if DNSSEC is supported; \c false otherwise.
+ void setDNSSECAwareness(const bool is_aware) { dnssec_aware_ = is_aware; }
+ //@}
+
+ ///
+ /// \name Converter Methods
+ ///
+ //@{
+ /// \brief Render the \c EDNS in the wire format.
+ ///
+ /// This method renders the \c EDNS object as a form of DNS OPT RR
+ /// via \c renderer, which encapsulates output buffer and other rendering
+ /// contexts.
+ /// Since the \c EDNS object does not maintain the extended RCODE
+ /// information, a separate parameter \c extended_rcode must be passed to
+ /// this method.
+ ///
+ /// If by adding the OPT RR the message size would exceed the limit
+ /// maintained in \c renderer, this method skips rendering the RR
+ /// and returns 0; otherwise it returns 1, which is the number of RR
+ /// rendered.
+ ///
+ /// In the current implementation the return value is either 0 or 1, but
+ /// the return type is <code>unsigned int</code> to be consistent with
+ /// \c RRset::toWire(). In any case the caller shouldn't assume these are
+ /// only possible return values from this method.
+ ///
+ /// This method is mostly exception free, but it requires memory
+ /// allocation and if it fails a corresponding standard exception will be
+ /// thrown.
+ ///
+ /// In practice, top level applications rarely need to use this
+ /// method directly. It should normally suffice to have a higher
+ /// level class such as \c Message do that job.
+ ///
+ /// <b>Note to developer:</b> the current implementation constructs an
+ /// \c RRset object for the OPT RR and calls its \c toWire() method,
+ /// which is inefficient. In future, we may want to optimize this method
+ /// by caching the rendered image and having the application reuse the
+ /// same \c EDNS object when possible.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as
+ /// part of the EDNS OPT RR.
+ /// \return 1 if the OPT RR fits in the message size limit; otherwise 0.
+ uint32_t toWire(AbstractMessageRenderer& renderer,
+ const uint8_t extended_rcode) const;
+
+ /// \brief Render the \c EDNS in the wire format.
+ ///
+ /// This method is same as \c toWire(MessageRenderer&,uint8_t)const
+ /// except it renders the OPT RR in an \c OutputBuffer and therefore
+ /// does not care about message size limit.
+ /// As a consequence it always returns 1.
+ uint32_t toWire(isc::util::OutputBuffer& buffer,
+ const uint8_t extended_rcode) const;
+
+ /// \brief Convert the EDNS to a string.
+ ///
+ /// The format of the resulting string is as follows:
+ /// \code ; EDNS: version: <version>, flags: <edns flags>; udp: <udp size>
+ /// \endcode
+ /// where
+ /// - \em version is the EDNS version number (integer).
+ /// - <em>edns flags</em> is a sequence of EDNS flag bits. The only
+ /// possible flag is the "DNSSEC OK", which is represented as "do".
+ /// - <em>udp size</em> is sender's UDP payload size in bytes.
+ ///
+ /// The string will be terminated with a trailing newline character.
+ ///
+ /// When EDNS options are supported the output of this method will be
+ /// extended.
+ ///
+ /// This method is mostly exception free, but it may require memory
+ /// allocation and if it fails a corresponding standard exception will be
+ /// thrown.
+ ///
+ /// \return A string representation of \c EDNS. See above for the format.
+ std::string toText() const;
+ //@}
+
+ // TBD: This method is currently not implemented. We'll eventually need
+ // something like this.
+ //void addOption();
+
+public:
+ /// \brief The highest EDNS version this implementation supports.
+ static const uint8_t SUPPORTED_VERSION = 0;
+private:
+ // We may eventually want to migrate to pimpl, especially when we support
+ // EDNS options. In this initial implementation, we keep it simple.
+ const uint8_t version_;
+ uint16_t udp_size_;
+ bool dnssec_aware_;
+};
+
+/// \brief Create a new \c EDNS object from a set of RR parameters, also
+/// providing the extended RCODE value.
+///
+/// This function is similar to the EDNS class constructor
+/// \c EDNS::EDNS(const Name&, const RRClass&, const RRType&, const RRTTL&, const rdata::Rdata&)
+/// but is different in that
+/// - It dynamically creates a new object
+/// - It returns (via a reference argument) the topmost 8 bits of the extended
+/// RCODE encoded in the \c ttl.
+///
+/// On success, \c extended_rcode will be updated with the 8-bit part of
+/// the extended RCODE encoded in the TTL of the OPT RR.
+///
+/// The intended usage of this function is to parse an OPT RR of an incoming
+/// DNS message, while updating the RCODE of the message.
+/// One common usage pattern is as follows:
+///
+/// \code Message msg;
+/// ...
+/// uint8_t extended_rcode;
+/// ConstEDNSPtr edns = ConstEDNSPtr(createEDNSFromRR(..., extended_rcode));
+/// rcode = Rcode(msg.getRcode().getCode(), extended_rcode);
+/// \endcode
+/// (although, like the \c EDNS constructor, normal applications wouldn't have
+/// to use this function directly).
+///
+/// This function provides the strong exception guarantee: Unless an
+/// exception is thrown \c extended_code won't be modified.
+///
+/// This function validates the given parameters and throws exceptions on
+/// failure in the same way as the \c EDNS class constructor.
+/// In addition, if memory allocation for the new object fails it throws the
+/// corresponding standard exception.
+///
+/// Note that this function returns a bare pointer to the newly allocated
+/// object, not a shared pointer object enclosing the pointer.
+/// The caller is responsible for deleting the object after the use of it
+/// (typically, the caller would immediately encapsulate the returned pointer
+/// in a shared pointer object, \c EDNSPtr or \c ConstEDNSPtr).
+/// It returns a bare pointer so that it can be used where the use of a shared
+/// pointer is impossible or not desirable.
+///
+/// Note to developers: there is no strong technical reason why this function
+/// cannot be a constructor of the \c EDNS class or even integrated into the
+/// constructor. But we decided to make it a separate free function so that
+/// constructors will be free from side effects (which is in itself a matter
+/// of preference).
+///
+/// \param name The owner name of the OPT RR. This must be the root name.
+/// \param rrclass The RR class of the OPT RR.
+/// \param rrtype This must specify the OPT RR type.
+/// \param ttl The TTL of the OPT RR.
+/// \param rdata The RDATA of the OPT RR.
+/// \param extended_rcode A placeholder to store the topmost 8 bits of the
+/// extended Rcode.
+/// \return A pointer to the created \c EDNS object.
+EDNS* createEDNSFromRR(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl,
+ const rdata::Rdata& rdata, uint8_t& extended_rcode);
+
+/// \brief Insert the \c EDNS as a string into stream.
+///
+/// This method convert \c edns into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param edns A reference to an \c EDNS object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const EDNS& edns);
+}
+}
+#endif // EDNS_H
diff --git a/src/lib/dns/exceptions.cc b/src/lib/dns/exceptions.cc
new file mode 100644
index 0000000..e164348
--- /dev/null
+++ b/src/lib/dns/exceptions.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/exceptions.h>
+#include <dns/rcode.h>
+
+namespace isc {
+namespace dns {
+
+const Rcode&
+DNSMessageFORMERR::getRcode() const {
+ return (Rcode::FORMERR());
+}
+
+const Rcode&
+DNSMessageBADVERS::getRcode() const {
+ return (Rcode::BADVERS());
+}
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/exceptions.h b/src/lib/dns/exceptions.h
new file mode 100644
index 0000000..8c3477e
--- /dev/null
+++ b/src/lib/dns/exceptions.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// XXX: we have another exceptions.h, so we need to use a prefix "DNS_" in
+// the include guard. More preferably, we should define a consistent naming
+// style for the header guide (e.g. module-name_file-name_H) throughout the
+// package.
+
+#ifndef DNS_EXCEPTIONS_H
+#define DNS_EXCEPTIONS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+
+///
+/// \brief A standard DNS module exception ...[TBD]
+///
+class Rcode; // forward declaration
+
+class Exception : public isc::Exception {
+public:
+ Exception(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
+/// \brief Base class for all sorts of text parse errors.
+///
+class DNSTextError : public isc::dns::Exception {
+public:
+ DNSTextError(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// \brief Base class for name parser exceptions.
+///
+class NameParserException : public DNSTextError {
+public:
+ NameParserException(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+class DNSProtocolError : public isc::dns::Exception {
+public:
+ DNSProtocolError(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+ virtual const Rcode& getRcode() const = 0;
+};
+
+class DNSMessageFORMERR : public DNSProtocolError {
+public:
+ DNSMessageFORMERR(const char* file, size_t line, const char* what) :
+ DNSProtocolError(file, line, what) {}
+ virtual const Rcode& getRcode() const;
+};
+
+class DNSMessageBADVERS : public DNSProtocolError {
+public:
+ DNSMessageBADVERS(const char* file, size_t line, const char* what) :
+ DNSProtocolError(file, line, what) {}
+ virtual const Rcode& getRcode() const;
+};
+
+}
+}
+#endif // DNS_EXCEPTIONS_H
diff --git a/src/lib/dns/labelsequence.cc b/src/lib/dns/labelsequence.cc
new file mode 100644
index 0000000..c0b8778
--- /dev/null
+++ b/src/lib/dns/labelsequence.cc
@@ -0,0 +1,468 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/labelsequence.h>
+#include <dns/name_internal.h>
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+
+#include <boost/functional/hash.hpp>
+
+#include <cstring>
+
+namespace isc {
+namespace dns {
+
+LabelSequence::LabelSequence(const void* buf) {
+#ifdef ENABLE_DEBUG
+ // In non-debug mode, dereferencing the null pointer further below
+ // will lead to a crash, so disabling this check is not
+ // unsafe. Except for a programming mistake, this case should not
+ // happen.
+ if (!buf) {
+ isc_throw(BadValue,
+ "Null pointer passed to LabelSequence constructor");
+ }
+#endif
+
+ const uint8_t* bp = reinterpret_cast<const uint8_t*>(buf);
+ first_label_ = 0;
+ const uint8_t offsets_len = *bp++;
+
+#ifdef ENABLE_DEBUG
+ if (offsets_len == 0 || offsets_len > Name::MAX_LABELS) {
+ isc_throw(BadValue,
+ "Bad offsets len in serialized LabelSequence data: "
+ << static_cast<unsigned int>(offsets_len));
+ }
+#endif
+
+ last_label_ = offsets_len - 1;
+ offsets_ = bp;
+ data_ = bp + offsets_len;
+
+#ifdef ENABLE_DEBUG
+ // Check the integrity on the offsets and the name data
+ const uint8_t* dp = data_;
+ for (size_t cur_offset = 0; cur_offset < offsets_len; ++cur_offset) {
+ if (dp - data_ != offsets_[cur_offset] || *dp > Name::MAX_LABELLEN) {
+ isc_throw(BadValue,
+ "Broken offset or name data in serialized "
+ "LabelSequence data");
+ }
+ dp += (1 + *dp);
+ }
+#endif
+}
+
+LabelSequence::LabelSequence(const LabelSequence& src,
+ uint8_t buf[MAX_SERIALIZED_LENGTH]) {
+ size_t data_len;
+ const uint8_t *data = src.getData(&data_len);
+ std::memcpy(buf, data, data_len);
+
+ for (size_t i = 0; i < src.getLabelCount(); ++i) {
+ buf[Name::MAX_WIRE + i] = src.offsets_[i + src.first_label_] -
+ src.offsets_[src.first_label_];
+ }
+
+ first_label_ = 0;
+ last_label_ = src.last_label_ - src.first_label_;
+ data_ = buf;
+ offsets_ = &buf[Name::MAX_WIRE];
+}
+
+
+const uint8_t*
+LabelSequence::getData(size_t *len) const {
+ *len = getDataLength();
+ return (&data_[offsets_[first_label_]]);
+}
+
+size_t
+LabelSequence::getDataLength() const {
+ const size_t last_label_len = data_[offsets_[last_label_]] + 1;
+ return (offsets_[last_label_] - offsets_[first_label_] + last_label_len);
+}
+
+size_t
+LabelSequence::getSerializedLength() const {
+ return (1 + getLabelCount() + getDataLength());
+}
+
+namespace {
+// Check if buf is not in the range of [bp, ep), which means
+// - end of buffer is before bp, or
+// - beginning of buffer is on or after ep
+bool
+isOutOfRange(const uint8_t* bp, const uint8_t* ep,
+ const uint8_t* buf, size_t buf_len) {
+ return (bp >= buf + buf_len || // end of buffer is before bp
+ ep <= buf); // beginning of buffer is on or after ep
+}
+}
+
+void
+LabelSequence::serialize(void* buf, size_t buf_len) const {
+ const size_t expected_size = getSerializedLength();
+ if (expected_size > buf_len) {
+ isc_throw(BadValue, "buffer too short for LabelSequence::serialize");
+ }
+
+ const size_t offsets_len = getLabelCount();
+ isc_throw_assert(offsets_len < 256); // should be in the 8-bit range
+
+ // Overridden check. Buffer shouldn't overwrap the offset of name data
+ // regions.
+ uint8_t* bp = reinterpret_cast<uint8_t*>(buf);
+ const size_t ndata_len = getDataLength();
+ if (!isOutOfRange(offsets_, offsets_ + offsets_len, bp, buf_len) ||
+ !isOutOfRange(data_, data_ + ndata_len, bp, buf_len)) {
+ isc_throw(BadValue, "serialize would break the source sequence");
+ }
+
+ *bp++ = offsets_len;
+ for (size_t i = 0; i < offsets_len; ++i) {
+ *bp++ = offsets_[first_label_ + i] - offsets_[first_label_];
+ }
+ std::memcpy(bp, &data_[offsets_[first_label_]], ndata_len);
+ bp += ndata_len;
+
+ isc_throw_assert(bp - reinterpret_cast<const uint8_t*>(buf) == expected_size);
+}
+
+bool
+LabelSequence::equals(const LabelSequence& other, bool case_sensitive) const {
+ size_t len, other_len;
+ const uint8_t* data = getData(&len);
+ const uint8_t* other_data = other.getData(&other_len);
+
+ if (len != other_len) {
+ return (false);
+ }
+ if (case_sensitive) {
+ return (std::memcmp(data, other_data, len) == 0);
+ }
+
+ // As long as the data was originally validated as (part of) a name,
+ // label length must never be a capital ascii character, so we can
+ // simply compare them after converting to lower characters.
+ for (size_t i = 0; i < len; ++i) {
+ const uint8_t ch = data[i];
+ const uint8_t other_ch = other_data[i];
+ if (isc::dns::name::internal::maptolower[ch] !=
+ isc::dns::name::internal::maptolower[other_ch]) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+NameComparisonResult
+LabelSequence::compare(const LabelSequence& other,
+ bool case_sensitive) const {
+ // Determine the relative ordering under the DNSSEC order relation of
+ // 'this' and 'other', and also determine the hierarchical relationship
+ // of the labels.
+
+ unsigned int nlabels = 0;
+ int l1 = getLabelCount();
+ int l2 = other.getLabelCount();
+ const int ldiff = static_cast<int>(l1) - static_cast<int>(l2);
+ unsigned int l = (ldiff < 0) ? l1 : l2;
+
+ while (l > 0) {
+ --l;
+ --l1;
+ --l2;
+ size_t pos1 = offsets_[l1 + first_label_];
+ size_t pos2 = other.offsets_[l2 + other.first_label_];
+ unsigned int count1 = data_[pos1++];
+ unsigned int count2 = other.data_[pos2++];
+
+ // We don't support any extended label types including now-obsolete
+ // bitstring labels.
+ isc_throw_assert(count1 <= Name::MAX_LABELLEN && count2 <= Name::MAX_LABELLEN);
+
+ const int cdiff = static_cast<int>(count1) - static_cast<int>(count2);
+ unsigned int count = (cdiff < 0) ? count1 : count2;
+
+ while (count > 0) {
+ const uint8_t label1 = data_[pos1];
+ const uint8_t label2 = other.data_[pos2];
+ int chdiff;
+
+ if (case_sensitive) {
+ chdiff = static_cast<int>(label1) - static_cast<int>(label2);
+ } else {
+ chdiff = static_cast<int>(
+ isc::dns::name::internal::maptolower[label1]) -
+ static_cast<int>(
+ isc::dns::name::internal::maptolower[label2]);
+ }
+
+ if (chdiff != 0) {
+ return (NameComparisonResult(
+ chdiff, nlabels,
+ nlabels == 0 ? NameComparisonResult::NONE :
+ NameComparisonResult::COMMONANCESTOR));
+ }
+ --count;
+ ++pos1;
+ ++pos2;
+ }
+ if (cdiff != 0) {
+ return (NameComparisonResult(
+ cdiff, nlabels,
+ nlabels == 0 ? NameComparisonResult::NONE :
+ NameComparisonResult::COMMONANCESTOR));
+ }
+ ++nlabels;
+ }
+
+ if (ldiff < 0) {
+ return (NameComparisonResult(ldiff, nlabels,
+ NameComparisonResult::SUPERDOMAIN));
+ } else if (ldiff > 0) {
+ return (NameComparisonResult(ldiff, nlabels,
+ NameComparisonResult::SUBDOMAIN));
+ }
+
+ return (NameComparisonResult(ldiff, nlabels, NameComparisonResult::EQUAL));
+}
+
+void
+LabelSequence::stripLeft(size_t i) {
+ if (i >= getLabelCount()) {
+ isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i <<
+ " (labelcount: " << getLabelCount() << ")");
+ }
+ first_label_ += i;
+}
+
+void
+LabelSequence::stripRight(size_t i) {
+ if (i >= getLabelCount()) {
+ isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i <<
+ " (labelcount: " << getLabelCount() << ")");
+ }
+ last_label_ -= i;
+}
+
+bool
+LabelSequence::isAbsolute() const {
+ return (data_[offsets_[last_label_]] == 0);
+}
+
+size_t
+LabelSequence::getHash(bool case_sensitive) const {
+ size_t length;
+ const uint8_t* s = getData(&length);
+ if (length > 16) {
+ length = 16;
+ }
+
+ size_t hash_val = 0;
+ while (length > 0) {
+ const uint8_t c = *s++;
+ boost::hash_combine(hash_val, case_sensitive ? c :
+ isc::dns::name::internal::maptolower[c]);
+ --length;
+ }
+ return (hash_val);
+}
+
+std::string
+LabelSequence::toRawText(bool omit_final_dot) const {
+ const uint8_t* np = &data_[offsets_[first_label_]];
+ const uint8_t* np_end = np + getDataLength();
+
+ // use for integrity check
+ unsigned int labels = getLabelCount();
+ // init with an impossible value to catch error cases in the end:
+ unsigned int count = Name::MAX_LABELLEN + 1;
+
+ // result string: it will roughly have the same length as the wire format
+ // label sequence data. reserve that length to minimize reallocation.
+ std::string result;
+ result.reserve(getDataLength());
+
+ while (np != np_end) {
+ labels--;
+ count = *np++;
+
+ if (count == 0) {
+ // We've reached the "final dot". If we've not dumped any
+ // character, the entire label sequence is the root name.
+ // In that case we don't omit the final dot.
+ if (!omit_final_dot || result.empty()) {
+ result.push_back('.');
+ }
+ break;
+ }
+
+ if (count <= Name::MAX_LABELLEN) {
+ isc_throw_assert(np_end - np >= count);
+
+ if (!result.empty()) {
+ // just after a non-empty label. add a separating dot.
+ result.push_back('.');
+ }
+
+ while (count-- > 0) {
+ const uint8_t c = *np++;
+ result.push_back(c);
+ }
+ } else {
+ isc_throw(BadLabelType, "unknown label type in name data");
+ }
+ }
+
+ // We should be at the end of the data and have consumed all labels.
+ isc_throw_assert(np == np_end);
+ isc_throw_assert(labels == 0);
+
+ return (result);
+}
+
+
+std::string
+LabelSequence::toText(bool omit_final_dot) const {
+ const uint8_t* np = &data_[offsets_[first_label_]];
+ const uint8_t* np_end = np + getDataLength();
+
+ // use for integrity check
+ unsigned int labels = getLabelCount();
+ // init with an impossible value to catch error cases in the end:
+ unsigned int count = Name::MAX_LABELLEN + 1;
+
+ // result string: it will roughly have the same length as the wire format
+ // label sequence data. reserve that length to minimize reallocation.
+ std::string result;
+ result.reserve(getDataLength());
+
+ while (np != np_end) {
+ labels--;
+ count = *np++;
+
+ if (count == 0) {
+ // We've reached the "final dot". If we've not dumped any
+ // character, the entire label sequence is the root name.
+ // In that case we don't omit the final dot.
+ if (!omit_final_dot || result.empty()) {
+ result.push_back('.');
+ }
+ break;
+ }
+
+ if (count <= Name::MAX_LABELLEN) {
+ isc_throw_assert(np_end - np >= count);
+
+ if (!result.empty()) {
+ // just after a non-empty label. add a separating dot.
+ result.push_back('.');
+ }
+
+ while (count-- > 0) {
+ const uint8_t c = *np++;
+ switch (c) {
+ case 0x22: // '"'
+ case 0x28: // '('
+ case 0x29: // ')'
+ case 0x2E: // '.'
+ case 0x3B: // ';'
+ case 0x5C: // '\\'
+ // Special modifiers in zone files.
+ case 0x40: // '@'
+ case 0x24: // '$'
+ result.push_back('\\');
+ result.push_back(c);
+ break;
+ default:
+ if (c > 0x20 && c < 0x7f) {
+ // append printable characters intact
+ result.push_back(c);
+ } else {
+ // encode non-printable characters in the form of \DDD
+ result.push_back(0x5c);
+ result.push_back(0x30 + ((c / 100) % 10));
+ result.push_back(0x30 + ((c / 10) % 10));
+ result.push_back(0x30 + (c % 10));
+ }
+ }
+ }
+ } else {
+ isc_throw(BadLabelType, "unknown label type in name data");
+ }
+ }
+
+ // We should be at the end of the data and have consumed all labels.
+ isc_throw_assert(np == np_end);
+ isc_throw_assert(labels == 0);
+
+ return (result);
+}
+
+std::string
+LabelSequence::toText() const {
+ return (toText(!isAbsolute()));
+}
+
+void
+LabelSequence::extend(const LabelSequence& labels,
+ uint8_t buf[MAX_SERIALIZED_LENGTH]) {
+ // collect data to perform steps before anything is changed
+ size_t label_count = last_label_ + 1;
+ // Since we may have been stripped, do not use getDataLength(), but
+ // calculate actual data size this labelsequence currently uses
+ size_t data_pos = offsets_[last_label_] + data_[offsets_[last_label_]] + 1;
+
+ // If this labelsequence is absolute, virtually strip the root label.
+ if (isAbsolute()) {
+ data_pos--;
+ label_count--;
+ }
+ const size_t append_label_count = labels.getLabelCount();
+ size_t data_len;
+ const uint8_t *data = labels.getData(&data_len);
+
+ // Sanity checks
+ if (data_ != buf || offsets_ != &buf[Name::MAX_WIRE]) {
+ isc_throw(BadValue,
+ "extend() called with unrelated buffer");
+ }
+ // Check MAX_LABELS before MAX_WIRE or it will be never reached
+ if (label_count + append_label_count > Name::MAX_LABELS) {
+ isc_throw(BadValue,
+ "extend() would exceed maximum number of labels");
+ }
+ if (data_pos + data_len > Name::MAX_WIRE) {
+ isc_throw(BadValue,
+ "extend() would exceed maximum wire length");
+ }
+
+ // All seems to be reasonably ok, let's proceed.
+ std::memmove(&buf[data_pos], data, data_len);
+
+ for (size_t i = 0; i < append_label_count; ++i) {
+ buf[Name::MAX_WIRE + label_count + i] =
+ data_pos +
+ labels.offsets_[i + labels.first_label_] -
+ labels.offsets_[labels.first_label_];
+ }
+ last_label_ = label_count + append_label_count - 1;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const LabelSequence& label_sequence) {
+ os << label_sequence.toText();
+ return (os);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/labelsequence.h b/src/lib/dns/labelsequence.h
new file mode 100644
index 0000000..660b70d
--- /dev/null
+++ b/src/lib/dns/labelsequence.h
@@ -0,0 +1,452 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LABELSEQUENCE_H
+#define LABELSEQUENCE_H
+
+#include <dns/name.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace dns {
+
+/// \brief Light-weight Accessor to Name data.
+///
+/// The purpose of this class is to easily match Names and parts of Names,
+/// without needing to copy the underlying data on each label strip.
+///
+/// It can only work on existing Name objects, or data as provided by the
+/// Name object or another LabelSequence, and the data or Name MUST
+/// remain in scope during the entire lifetime of its associated
+/// LabelSequence(s).
+///
+/// Upon creation of a LabelSequence, it records the offsets of the
+/// labels in the wireformat data of the Name. When stripLeft() or
+/// stripRight() is called on the LabelSequence, no changes in the
+/// original data occur, but the internal pointers of the
+/// LabelSequence are modified.
+///
+/// LabelSequences can be compared to other LabelSequences, and their
+/// data can be requested (which then points to part of the original
+/// data of the original Name object).
+class LabelSequence {
+ // Name calls the private toText(bool) method of LabelSequence.
+ friend std::string Name::toText(bool) const;
+
+public:
+ /// \brief Max possible size of serialized image generated by \c serialize
+ ///
+ /// A fixed length buffer of this size can be always passed to
+ /// \c serialize() safely. (But the application shouldn't use the
+ /// specific size value; it must use this constant variable).
+ static const size_t MAX_SERIALIZED_LENGTH =
+ Name::MAX_WIRE + Name::MAX_LABELS + 1;
+
+ ///
+ /// \name Well-known LabelSequence constants
+ ///
+ //@{
+ /// Wildcard label ("*")
+ static const LabelSequence& WILDCARD();
+ //@}
+
+ /// \brief Constructs a LabelSequence for the given name
+ ///
+ /// \note The associated Name MUST remain in scope during the lifetime
+ /// of this LabelSequence, since getData() refers to data from the
+ /// Name object (the only data the LabelSequence stores are pointers
+ /// to the labels in the Name object).
+ ///
+ /// \param name The Name to construct a LabelSequence for
+ explicit LabelSequence(const Name& name):
+ data_(&name.ndata_[0]),
+ offsets_(&name.offsets_[0]),
+ first_label_(0),
+ last_label_(name.getLabelCount() - 1) {
+ }
+
+ /// \brief Constructor from serialized image.
+ ///
+ /// This constructor restores a \c LabelSequence object from a serialized
+ /// binary image previously generated by \c serialize(). Any other input
+ /// to this constructor will result in undefined behavior.
+ ///
+ /// The binary data passed to this constructor MUST remain in scope and
+ /// MUST NOT be modified during the lifetime of this LabelSequence.
+ ///
+ /// As long as the data were previously generated by a call to
+ /// \c serialize() on a valid \c LabelSequence object, this constructor
+ /// should succeed. While any other case is undefined, this constructor
+ /// may perform some validity checks internally for safety. Nevertheless,
+ /// applications must not rely on such checks.
+ ///
+ /// \param buf Pointer to the serialized image generated by \c serialize().
+ explicit LabelSequence(const void* buf);
+
+ /// \brief Construct 'extendable' LabelSequence
+ ///
+ /// This form of LabelSequence copies the data from the given
+ /// labelsequence into the given external buffer, which is subsequently
+ /// extendable by calling extend()
+ ///
+ /// The data is placed into the given buffer as follows:
+ /// - binary sequence of name data, starting at position 0,
+ /// length determined by source LabelSequence
+ /// - offsets, starting at position Name::MAX_WIRE, length
+ /// determined by source LabelSequence
+ /// The offsets are updated to be correct for the potentially partial
+ /// name data (as stripLeft() and stripRight may have been called on
+ /// the source LabelSequence).
+ ///
+ /// \note The given buf MUST remain in scope during the lifetime of
+ /// the LabelSequence created here.
+ /// \note The buffer should never be modified except through
+ /// calls to extend().
+ /// \note Also, only associate the buffer with at most one
+ /// LabelSequence. Behaviour is undefined if two LabelSequences are
+ /// using the same buffer.
+ ///
+ /// \param src LabelSequence to copy the initial data from
+ /// \param buf external buffer to store this labelsequence's data in
+ LabelSequence(const LabelSequence& src, uint8_t buf[MAX_SERIALIZED_LENGTH]);
+
+ /// \brief Copy constructor.
+ ///
+ /// \note The associated data MUST remain in scope during the lifetime
+ /// of this LabelSequence, since only the pointers are copied.
+ ///
+ /// \note No validation is done on the given data upon construction;
+ /// use with care.
+ ///
+ /// \param ls The LabelSequence to construct a LabelSequence from
+ LabelSequence(const LabelSequence& ls):
+ data_(ls.data_),
+ offsets_(ls.offsets_),
+ first_label_(ls.first_label_),
+ last_label_(ls.last_label_) {
+ }
+
+ /// \brief Assignment operator.
+ ///
+ /// \note The associated data MUST remain in scope during the lifetime
+ /// of this LabelSequence, since only the pointers are copied.
+ ///
+ /// \note No validation is done on the given data upon construction;
+ /// use with care.
+ ///
+ /// \param other The LabelSequence to assign a LabelSequence from
+ LabelSequence& operator=(const LabelSequence& other) {
+ if (this != &other) {
+ // Not self-assignment.
+ data_ = other.data_;
+ offsets_ = other.offsets_;
+ first_label_ = other.first_label_;
+ last_label_ = other.last_label_;
+ }
+ return (*this);
+ }
+
+ /// \brief Return the wire-format data for this LabelSequence
+ ///
+ /// The data is returned as a pointer to (the part of) the original
+ /// wireformat data, from either the original Name object, or the
+ /// raw data given in the constructor, and the given len value is
+ /// set to the number of octets that match this labelsequence.
+ ///
+ /// \note The data pointed to is only valid if the original Name
+ /// object or data is still in scope
+ ///
+ /// \param len Pointer to a size_t where the length of the data
+ /// will be stored (in number of octets)
+ /// \return Pointer to the wire-format data of this label sequence
+ const uint8_t* getData(size_t* len) const;
+
+ /// \brief Return the length of the wire-format data of this LabelSequence
+ ///
+ /// This method returns the number of octets for the data that would
+ /// be returned by the \c getData() method.
+ ///
+ /// Note that the return value of this method is always positive.
+ /// Note also that if the return value of this method is 1, it means the
+ /// sequence consists of the null label, i.e., a single "dot", and vice
+ /// versa.
+ ///
+ /// \note The data pointed to is only valid if the original Name
+ /// object or data is still in scope
+ ///
+ /// \return The length of the data of the label sequence in octets.
+ size_t getDataLength() const;
+
+ /// \brief Return the size of serialized image of the \c LabelSequence.
+ ///
+ /// This method calculates the size of necessary storage to store
+ /// serialized image of this \c LabelSequence (which would be dumped by
+ /// \c serialize()) and returns it. The size is in bytes.
+ ///
+ /// \throw none.
+ ///
+ /// \return The size of serialized image of the \c LabelSequence.
+ size_t getSerializedLength() const;
+
+ /// \brief Serialize the \c LabelSequence object in to a buffer.
+ ///
+ /// This method dumps a serialized image of this \c LabelSequence
+ /// that would be restored by the corresponding constructor into the
+ /// given buffer. The buffer size must be at least equal to
+ /// the value returned by getSerializedLength() (it can be larger than
+ /// that).
+ ///
+ /// Be careful about where the buffer is located; due to the nature
+ /// of the buffer, it's quite possible that the memory region is being used
+ /// to construct another active \c LabelSequence. In such a case
+ /// the serialization would silently break that sequence object, and
+ /// it will be very difficult to identify the cause. This method
+ /// has minimal level checks to avoid such disruption: If the serialization
+ /// would break "this" \c LabelSequence object, it doesn't write anything
+ /// to the given buffer and throw a \c isc::BadValue exception.
+ ///
+ /// In general, it should be safe to call this method on a
+ /// \c LabelSequence object constructed from a \c Name object or
+ /// a copy of such \c LabelSequence. When you construct \c LabelSequence
+ /// from pre-serialized data, calling this method on it can be unsafe.
+ /// One safe (but a bit less efficient) way in such a case is to make
+ /// the source \c LabelSequence temporary and immediately create a
+ /// local copy using an explicit buffer, and call this method on the
+ /// latter:
+ /// \code
+ /// // don't do this, it's not safe (and would result in exception):
+ /// // LabelSequence(buf).serialize(buf, buf_len);
+ ///
+ /// // The following are the safe way:
+ /// uint8_t ext_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+ /// LabelSequence seq(LabelSequence(buf), ext_buf);
+ /// ... (strip the labels, etc)
+ /// seq.serialize(buf, buf_len); // it's safe to override buf here
+ /// \endcode
+ ///
+ /// The serialized image would be as follows:
+ /// - olen: number of offsets (1 byte)
+ /// - binary sequence of offsets (olen bytes, verbatim copy of offsets_
+ /// of this size)
+ /// - binary sequence of name data (length determined by itself, verbatim
+ /// copy of data_ of the corresponding size)
+ ///
+ /// Applications must use the resulting image as opaque value and must not
+ /// use it for other purposes than input to the corresponding constructor
+ /// to restore it. Application behavior that assumes the specific
+ /// organization of the image is not guaranteed.
+ ///
+ /// \throw isc::BadValue buf_len is too short (this method never throws
+ /// otherwise) or the serialization would override internal data of
+ /// of the source LabelSequence.
+ ///
+ /// \param buf Pointer to the placeholder to dump the serialized image
+ /// \param buf_len The size of available region in \c buf
+ void serialize(void* buf, size_t buf_len) const;
+
+ /// \brief Compares two label sequences for equality.
+ ///
+ /// Performs a (optionally case-sensitive) comparison between this
+ /// LabelSequence and another LabelSequence for equality.
+ ///
+ /// \param other The LabelSequence to compare with
+ /// \param case_sensitive If true, comparison is case-sensitive
+ /// \return true if The label sequences consist are the same length,
+ /// and contain the same data.
+ bool equals(const LabelSequence& other, bool case_sensitive = false) const;
+
+ /// \brief Compares two label sequences for equality (case ignored).
+ ///
+ /// This is equivalent to <code>this->equals(other)</code>.
+ ///
+ /// The operator version is convenient some specific cases such as in
+ /// unit tests.
+ bool operator==(const LabelSequence& other) const {
+ return (equals(other));
+ }
+
+ /// \brief Compares two label sequences.
+ ///
+ /// Performs a (optionally case-insensitive) comparison between this
+ /// LabelSequence and another LabelSequence.
+ ///
+ /// \param other The LabelSequence to compare with
+ /// \param case_sensitive If true, comparison is case-insensitive
+ /// \return a <code>NameComparisonResult</code> object representing the
+ /// comparison result.
+ NameComparisonResult compare(const LabelSequence& other,
+ bool case_sensitive = false) const;
+
+ /// \brief Remove labels from the front of this LabelSequence
+ ///
+ /// \note No actual memory is changed, this operation merely updates the
+ /// internal pointers based on the offsets in the Name object.
+ ///
+ /// \exception OutOfRange if i is greater than or equal to the number
+ /// of labels currently pointed to by this LabelSequence
+ ///
+ /// \param i The number of labels to remove.
+ void stripLeft(size_t i);
+
+ /// \brief Remove labels from the end of this LabelSequence
+ ///
+ /// \note No actual memory is changed, this operation merely updates the
+ /// internal pointers based on the offsets originally provided.
+ ///
+ /// \exception OutOfRange if i is greater than or equal to the number
+ /// of labels currently pointed to by this LabelSequence
+ ///
+ /// \param i The number of labels to remove.
+ void stripRight(size_t i);
+
+ /// \brief Returns the current number of labels for this LabelSequence
+ ///
+ /// \return The number of labels
+ size_t getLabelCount() const {
+ return (last_label_ - first_label_ + 1);
+ }
+
+ /// \brief Convert the LabelSequence to a string.
+ ///
+ /// This method returns a <code>std::string</code> object representing the
+ /// LabelSequence as a string. The returned string ends with a dot
+ /// '.' if the label sequence is absolute.
+ ///
+ /// This function assumes the underlying data is in proper
+ /// uncompressed wire format. If it finds an unexpected label
+ /// character including compression pointer, an exception of class
+ /// \c BadLabelType will be thrown. In addition, if resource
+ /// allocation for the result string fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \return a string representation of the <code>LabelSequence</code>.
+ std::string toText() const;
+
+ /// \brief Convert the LabelSequence to a string without escape sequences.
+ ///
+ /// The string returned will contain a single character value for any
+ /// escape sequences in the label(s).
+ ///
+ /// \param omit_final_dot whether to omit the trailing dot in the output.
+ /// \return a string representation of the <code>LabelSequence</code>
+ /// that does not contain escape sequences.
+ std::string toRawText(bool omit_final_dot) const;
+
+ /// \brief Extend this LabelSequence with the given labelsequence
+ ///
+ /// The given labels are appended to the name data, and internal
+ /// offset data is updated accordingly.
+ ///
+ /// The data from the given LabelSequence is copied into the buffer
+ /// associated with this LabelSequence; the appended LabelSequence
+ /// (the 'labels' argument) can be released if it is not needed for
+ /// other operations anymore.
+ ///
+ /// If this LabelSequence is absolute, its root label will be stripped
+ /// before the given LabelSequence is appended; after extend(),
+ /// this LabelSequence will be absolute if, and only if, the appended
+ /// LabelSequence was. A side-effect of this property is that adding
+ /// the root label to an absolute LabelSequence has no effect (the
+ /// root label is stripped, then added again).
+ ///
+ /// Some minimal checking is done on the data, but internal integrity
+ /// is not assumed. Do NOT modify the given buffer except through calls
+ /// to this method, and do NOT call this method if the buffer is
+ /// associated to another LabelSequence (behaviour of the other
+ /// LabelSequence is undefined in that scenario).
+ ///
+ /// \exception BadValue If the buffer does not appear to be associated
+ /// with this LabelSequence, or if the maximum wire length or maximum
+ /// number of labels would be exceeded by this operation
+ ///
+ /// \param labels The labels to append to this LabelSequence
+ /// \param buf The buffer associated with this LabelSequence
+ void extend(const LabelSequence& labels,
+ uint8_t buf[MAX_SERIALIZED_LENGTH]);
+
+private:
+ /// \brief Convert the LabelSequence to a string.
+ ///
+ /// This method is a version of the zero-argument toText() method,
+ /// that accepts a <code>omit_final_dot</code> argument. The
+ /// returned string ends with a dot '.' if
+ /// <code>omit_final_dot</code> is <code>false</code>.
+ ///
+ /// This method is used as a helper for <code>Name::toText()</code>
+ /// only.
+ ///
+ /// \param omit_final_dot whether to omit the trailing dot in the output.
+ /// \return a string representation of the <code>LabelSequence</code>.
+ std::string toText(bool omit_final_dot) const;
+public:
+ /// \brief Calculate a simple hash for the label sequence.
+ ///
+ /// This method calculates a hash value for the label sequence as binary
+ /// data. If \c case_sensitive is false, it ignores the case stored in
+ /// the labels; specifically, it normalizes the labels by converting all
+ /// upper case characters to lower case ones and calculates the hash value
+ /// for the result.
+ ///
+ /// This method is intended to provide a lightweight way to store a
+ /// relatively small number of label sequences in a hash table.
+ /// For this reason it only takes into account data up to 16 octets
+ /// (16 was derived from BIND 9's implementation). Also, the function does
+ /// not provide any unpredictability; a specific sequence will always have
+ /// the same hash value. It should therefore not be used in the context
+ /// where an untrusted third party can mount a denial of service attack by
+ /// forcing the application to create a very large number of label
+ /// sequences that have the same hash value and expected to be stored in
+ /// a hash table.
+ ///
+ /// \exception None
+ ///
+ /// \param case_sensitive
+ /// \return A hash value for this label sequence.
+ size_t getHash(bool case_sensitive) const;
+
+ /// \brief Checks whether the label sequence is absolute
+ ///
+ /// \return true if the last label is the root label
+ bool isAbsolute() const;
+
+private:
+ const uint8_t* data_; // wire-format name data
+ const uint8_t* offsets_; // an array of offsets in data_ for the labels
+ size_t first_label_; // index of offsets_ for the first label
+ size_t last_label_; // index of offsets_ for the last label.
+ // can be equal to first_label_, but must not
+ // be smaller (the class ensures that)
+};
+
+
+///
+/// \brief Insert the label sequence as a string into stream.
+///
+/// This method convert the \c label_sequence into a string and inserts
+/// it into the output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described in
+/// ostream::operator<< but applied to \c LabelSequence objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param label_sequence The \c LabelSequence object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const LabelSequence& label_sequence);
+
+inline const LabelSequence&
+LabelSequence::WILDCARD() {
+ static const uint8_t wildcard_buf[4] = { 0x01, 0x00, 0x01, '*' };
+ static const LabelSequence wild_ls(wildcard_buf);
+ return (wild_ls);
+}
+
+} // end namespace dns
+} // end namespace isc
+
+#endif
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
new file mode 100644
index 0000000..d4acac0
--- /dev/null
+++ b/src/lib/dns/master_lexer.cc
@@ -0,0 +1,608 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <dns/master_lexer.h>
+#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer_state.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <bitset>
+#include <limits>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+
+// The definition of SOURCE_SIZE_UNKNOWN. Note that we initialize it using
+// a method of another library. Technically, this could trigger a static
+// initialization fiasco. But in this particular usage it's very unlikely
+// to happen because this value is expected to be used only as a return
+// value of a MasterLexer's method, and its constructor needs definitions
+// here.
+const size_t MasterLexer::SOURCE_SIZE_UNKNOWN =
+ std::numeric_limits<size_t>::max();
+
+namespace {
+typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
+} // end unnamed namespace
+using namespace master_lexer_internal;
+
+struct MasterLexer::MasterLexerImpl {
+ MasterLexerImpl() : source_(0), token_(MasterToken::NOT_STARTED),
+ total_size_(0), popped_size_(0),
+ paren_count_(0), last_was_eol_(true),
+ has_previous_(false),
+ previous_paren_count_(0),
+ previous_was_eol_(false) {
+ separators_.set('\r');
+ separators_.set('\n');
+ separators_.set(' ');
+ separators_.set('\t');
+ separators_.set('(');
+ separators_.set(')');
+ separators_.set('"');
+ esc_separators_.set('\r');
+ esc_separators_.set('\n');
+ }
+
+ // A helper method to skip possible comments toward the end of EOL or EOF.
+ // commonly used by state classes. It returns the corresponding "end-of"
+ // character in case it's a comment; otherwise it simply returns the
+ // current character.
+ int skipComment(int c, bool escaped = false) {
+ if (c == ';' && !escaped) {
+ while (true) {
+ c = source_->getChar();
+ if (c == '\n' || c == InputSource::END_OF_STREAM) {
+ return (c);
+ }
+ }
+ }
+ return (c);
+ }
+
+ bool isTokenEnd(int c, bool escaped) {
+ // Special case of EOF (end of stream); this is not in the bitmaps
+ if (c == InputSource::END_OF_STREAM) {
+ return (true);
+ }
+ // In this implementation we only ensure the behavior for unsigned
+ // range of characters, so we restrict the range of the values up to
+ // 0x7f = 127
+ return (escaped ? esc_separators_.test(c & 0x7f) :
+ separators_.test(c & 0x7f));
+ }
+
+ void setTotalSize() {
+ isc_throw_assert(source_);
+ if (total_size_ != SOURCE_SIZE_UNKNOWN) {
+ const size_t current_size = source_->getSize();
+ if (current_size != SOURCE_SIZE_UNKNOWN) {
+ total_size_ += current_size;
+ } else {
+ total_size_ = SOURCE_SIZE_UNKNOWN;
+ }
+ }
+ }
+
+ std::vector<InputSourcePtr> sources_;
+ InputSource* source_; // current source (null if sources_ is empty)
+ MasterToken token_; // currently recognized token (set by a state)
+ std::vector<char> data_; // placeholder for string data
+
+ // Keep track of the total size of all sources and characters that have
+ // been read from sources already popped.
+ size_t total_size_; // accumulated size (# of chars) of sources
+ size_t popped_size_; // total size of sources that have been popped
+
+ // These are used in states, and defined here only as a placeholder.
+ // The main lexer class does not need these members.
+ size_t paren_count_; // nest count of the parentheses
+ bool last_was_eol_; // whether the lexer just passed an end-of-line
+
+ // Bitmaps that gives whether a given (positive) character should be
+ // considered a separator of a string/number token. The esc_ version
+ // is a subset of the other, excluding characters that can be ignored
+ // if escaped by a backslash. See isTokenEnd() for the bitmap size.
+ std::bitset<128> separators_;
+ std::bitset<128> esc_separators_;
+
+ // These are to allow restoring state before previous token.
+ bool has_previous_;
+ size_t previous_paren_count_;
+ bool previous_was_eol_;
+};
+
+MasterLexer::MasterLexer() : impl_(new MasterLexerImpl()) {
+}
+
+MasterLexer::~MasterLexer() {
+}
+
+bool
+MasterLexer::pushSource(const char* filename, std::string* error) {
+ if (!filename) {
+ isc_throw(InvalidParameter,
+ "null filename for MasterLexer::pushSource");
+ }
+ try {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(filename)));
+ } catch (const InputSource::OpenError& ex) {
+ if (error) {
+ *error = ex.what();
+ }
+ return (false);
+ }
+
+ impl_->source_ = impl_->sources_.back().get();
+ impl_->has_previous_ = false;
+ impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
+ return (true);
+}
+
+void
+MasterLexer::pushSource(std::istream& input) {
+ try {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+ } catch (const InputSource::OpenError& ex) {
+ // Convert the "internal" exception to public one.
+ isc_throw(Unexpected, "Failed to push a stream to lexer: " <<
+ ex.what());
+ }
+ impl_->source_ = impl_->sources_.back().get();
+ impl_->has_previous_ = false;
+ impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
+}
+
+void
+MasterLexer::popSource() {
+ if (impl_->sources_.empty()) {
+ isc_throw(InvalidOperation,
+ "MasterLexer::popSource on an empty source");
+ }
+ impl_->popped_size_ += impl_->source_->getPosition();
+ impl_->sources_.pop_back();
+ impl_->source_ = impl_->sources_.empty() ? 0 :
+ impl_->sources_.back().get();
+ impl_->has_previous_ = false;
+}
+
+size_t
+MasterLexer::getSourceCount() const {
+ return (impl_->sources_.size());
+}
+
+std::string
+MasterLexer::getSourceName() const {
+ if (impl_->sources_.empty()) {
+ return (std::string());
+ }
+ return (impl_->sources_.back()->getName());
+}
+
+size_t
+MasterLexer::getSourceLine() const {
+ if (impl_->sources_.empty()) {
+ return (0);
+ }
+ return (impl_->sources_.back()->getCurrentLine());
+}
+
+size_t
+MasterLexer::getTotalSourceSize() const {
+ return (impl_->total_size_);
+}
+
+size_t
+MasterLexer::getPosition() const {
+ size_t position = impl_->popped_size_;
+ for (auto const& src : impl_->sources_) {
+ position += src->getPosition();
+ }
+ return (position);
+}
+
+const MasterToken&
+MasterLexer::getNextToken(Options options) {
+ if (!impl_->source_) {
+ isc_throw(isc::InvalidOperation, "No source to read tokens from");
+ }
+ // Store the current state so we can restore it in ungetToken
+ impl_->previous_paren_count_ = impl_->paren_count_;
+ impl_->previous_was_eol_ = impl_->last_was_eol_;
+ impl_->source_->mark();
+ impl_->has_previous_ = true;
+ // Reset the token now. This is to check a token was actually produced.
+ // This is debugging aid.
+ impl_->token_ = MasterToken(MasterToken::NO_TOKEN_PRODUCED);
+ // And get the token
+
+ // This actually handles EOF internally too.
+ const State* state = State::start(*this, options);
+ if (state) {
+ state->handle(*this);
+ }
+ // Make sure a token was produced. Since this Can Not Happen, we assert
+ // here instead of throwing.
+ isc_throw_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));
+ }
+ isc_throw_assert(expect == MasterToken::NUMBER);
+ throw LexerError(__FILE__, __LINE__,
+ MasterToken(MasterToken::BAD_NUMBER));
+ }
+
+ return (impl_->token_);
+}
+
+void
+MasterLexer::ungetToken() {
+ if (impl_->has_previous_) {
+ impl_->has_previous_ = false;
+ impl_->source_->ungetAll();
+ impl_->last_was_eol_ = impl_->previous_was_eol_;
+ impl_->paren_count_ = impl_->previous_paren_count_;
+ } else {
+ isc_throw(isc::InvalidOperation, "No token to unget ready");
+ }
+}
+
+namespace {
+const char* const error_text[] = {
+ "lexer not started", // NOT_STARTED
+ "unbalanced parentheses", // UNBALANCED_PAREN
+ "unexpected end of input", // UNEXPECTED_END
+ "unbalanced quotes", // UNBALANCED_QUOTES
+ "no token produced", // NO_TOKEN_PRODUCED
+ "number out of range", // NUMBER_OUT_OF_RANGE
+ "not a valid number", // BAD_NUMBER
+ "unexpected quotes" // UNEXPECTED_QUOTES
+};
+const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
+} // end unnamed namespace
+
+std::string
+MasterToken::getErrorText() const {
+ if (type_ != ERROR) {
+ isc_throw(InvalidOperation,
+ "MasterToken::getErrorText() for non error type");
+ }
+
+ // The class integrity ensures the following:
+ isc_throw_assert(val_.error_code_ < error_text_max_count);
+ return (error_text[val_.error_code_]);
+}
+
+namespace master_lexer_internal {
+// Below we implement state classes for state transitions of MasterLexer.
+// Note that these need to be defined here so that they can refer to
+// the details of MasterLexerImpl.
+
+bool
+State::wasLastEOL(const MasterLexer& lexer) const {
+ return (lexer.impl_->last_was_eol_);
+}
+
+const MasterToken&
+State::getToken(const MasterLexer& lexer) const {
+ return (lexer.impl_->token_);
+}
+
+size_t
+State::getParenCount(const MasterLexer& lexer) const {
+ return (lexer.impl_->paren_count_);
+}
+
+namespace {
+class CRLF : public State {
+public:
+ CRLF() {}
+ virtual ~CRLF() {} // see the base class for the destructor
+ virtual void handle(MasterLexer& lexer) const {
+ // We've just seen '\r'. If this is part of a sequence of '\r\n',
+ // we combine them as a single END-OF-LINE. Otherwise we treat the
+ // single '\r' as an EOL and continue tokenization from the character
+ // immediately after '\r'. One tricky case is that there's a comment
+ // between '\r' and '\n'. This implementation combines these
+ // characters and treats them as a single EOL (the behavior derived
+ // from BIND 9). Technically this may not be correct, but in practice
+ // the caller wouldn't distinguish this case from the case it has
+ // two EOLs, so we simplify the process.
+ const int c = getLexerImpl(lexer)->skipComment(
+ getLexerImpl(lexer)->source_->getChar());
+ if (c != '\n') {
+ getLexerImpl(lexer)->source_->ungetChar();
+ }
+ getLexerImpl(lexer)->token_ = MasterToken(MasterToken::END_OF_LINE);
+ getLexerImpl(lexer)->last_was_eol_ = true;
+ }
+};
+
+class String : public State {
+public:
+ String() {}
+ virtual ~String() {} // see the base class for the destructor
+ virtual void handle(MasterLexer& lexer) const;
+};
+
+class QString : public State {
+public:
+ QString() {}
+ virtual ~QString() {} // see the base class for the destructor
+ virtual void handle(MasterLexer& lexer) const;
+};
+
+class Number : public State {
+public:
+ Number() {}
+ virtual ~Number() {}
+ virtual void handle(MasterLexer& lexer) const;
+};
+
+// We use a common instance of a each state in a singleton-like way to save
+// construction overhead. They are not singletons in its strict sense as
+// we don't prohibit direct construction of these objects. But that doesn't
+// matter much anyway, because the definitions are completely hidden within
+// this file.
+const CRLF CRLF_STATE;
+const String STRING_STATE;
+const QString QSTRING_STATE;
+const Number NUMBER_STATE;
+} // end unnamed namespace
+
+const State&
+State::getInstance(ID state_id) {
+ switch (state_id) {
+ case CRLF:
+ return (CRLF_STATE);
+ case String:
+ return (STRING_STATE);
+ case QString:
+ return (QSTRING_STATE);
+ case Number:
+ return (NUMBER_STATE);
+ }
+
+ // This is a bug of the caller, and this method is only expected to be
+ // used by tests, so we just forcefully make it fail by asserting the
+ // condition.
+ isc_throw_assert(false);
+ return (STRING_STATE); // a dummy return, to silence some compilers.
+}
+
+const State*
+State::start(MasterLexer& lexer, MasterLexer::Options options) {
+ // define some shortcuts
+ MasterLexer::MasterLexerImpl& lexerimpl = *lexer.impl_;
+ size_t& paren_count = lexerimpl.paren_count_;
+
+ // Note: the if-else in the loop is getting complicated. When we complete
+ // #2374, revisit the organization to see if we need a fundamental
+ // refactoring.
+ while (true) {
+ const int c = lexerimpl.skipComment(lexerimpl.source_->getChar());
+ if (c == InputSource::END_OF_STREAM) {
+ lexerimpl.last_was_eol_ = false;
+ if (paren_count != 0) {
+ lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN);
+ paren_count = 0; // reset to 0; this helps in lenient mode.
+ return (0);
+ }
+ lexerimpl.token_ = MasterToken(MasterToken::END_OF_FILE);
+ return (0);
+ } 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_ = MasterToken(MasterToken::INITIAL_WS);
+ return (0);
+ }
+ } else if (c == '\n') {
+ lexerimpl.last_was_eol_ = true;
+ if (paren_count == 0) { // we don't recognize EOL if we are in ()
+ lexerimpl.token_ = MasterToken(MasterToken::END_OF_LINE);
+ return (0);
+ }
+ } else if (c == '\r') {
+ if (paren_count == 0) { // check if we are in () (see above)
+ return (&CRLF_STATE);
+ }
+ } else if (c == '"') {
+ if ((options & MasterLexer::QSTRING) != 0) {
+ lexerimpl.last_was_eol_ = false;
+ return (&QSTRING_STATE);
+ } else {
+ lexerimpl.token_ = MasterToken(MasterToken::UNEXPECTED_QUOTES);
+ return (0);
+ }
+ } else if (c == '(') {
+ lexerimpl.last_was_eol_ = false;
+ ++paren_count;
+ } else if (c == ')') {
+ lexerimpl.last_was_eol_ = false;
+ if (paren_count == 0) {
+ lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN);
+ return (0);
+ }
+ --paren_count;
+ } 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();
+ return (&NUMBER_STATE);
+ } else {
+ // this character will be handled in the string state
+ lexerimpl.source_->ungetChar();
+ lexerimpl.last_was_eol_ = false;
+ return (&STRING_STATE);
+ }
+ // no code should be here; we just continue the loop.
+ }
+}
+
+void
+String::handle(MasterLexer& lexer) const {
+ std::vector<char>& data = getLexerImpl(lexer)->data_;
+ data.clear();
+
+ bool escaped = false;
+ while (true) {
+ const int c = getLexerImpl(lexer)->skipComment(
+ getLexerImpl(lexer)->source_->getChar(), escaped);
+
+ 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_ =
+ MasterToken(&data.at(0), data.size() - 1);
+ return;
+ }
+ escaped = (c == '\\' && !escaped);
+ data.push_back(c);
+ }
+}
+
+void
+QString::handle(MasterLexer& lexer) const {
+ MasterToken& token = getLexerImpl(lexer)->token_;
+ std::vector<char>& data = getLexerImpl(lexer)->data_;
+ data.clear();
+
+ bool escaped = false;
+ while (true) {
+ const int c = getLexerImpl(lexer)->source_->getChar();
+ if (c == InputSource::END_OF_STREAM) {
+ token = MasterToken(MasterToken::UNEXPECTED_END);
+ return;
+ } else if (c == '"') {
+ if (escaped) {
+ // found escaped '"'. overwrite the preceding backslash.
+ isc_throw_assert(!data.empty());
+ escaped = false;
+ data.back() = '"';
+ } else {
+ // 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 = MasterToken(MasterToken::UNBALANCED_QUOTES);
+ return;
+ } else {
+ escaped = (c == '\\' && !escaped);
+ data.push_back(c);
+ }
+ }
+}
+
+void
+Number::handle(MasterLexer& lexer) const {
+ MasterToken& token = getLexerImpl(lexer)->token_;
+
+ // It may yet turn out to be a string, so we first
+ // collect all the data
+ bool digits_only = true;
+ std::vector<char>& data = getLexerImpl(lexer)->data_;
+ data.clear();
+ bool escaped = false;
+
+ while (true) {
+ const int c = getLexerImpl(lexer)->skipComment(
+ getLexerImpl(lexer)->source_->getChar(), escaped);
+ if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
+ getLexerImpl(lexer)->source_->ungetChar();
+ // We need to close the string whether it's digits-only (for
+ // lexical_cast) or not (see String::handle()).
+ data.push_back('\0');
+ if (digits_only) {
+ 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 = MasterToken(&data.at(0), data.size() - 1);
+ }
+ return;
+ }
+ if (!isdigit(c)) {
+ digits_only = false;
+ }
+ escaped = (c == '\\' && !escaped);
+ data.push_back(c);
+ }
+}
+
+} // namespace master_lexer_internal
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
new file mode 100644
index 0000000..8a4a53f
--- /dev/null
+++ b/src/lib/dns/master_lexer.h
@@ -0,0 +1,676 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MASTER_LEXER_H
+#define MASTER_LEXER_H
+
+#include <dns/exceptions.h>
+
+#include <istream>
+#include <string>
+
+#include <stdint.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+class State;
+}
+
+/// \brief Tokens for \c MasterLexer
+///
+/// This is a simple value-class encapsulating a type of a lexer token and
+/// (if it has a value) its value. Essentially, the class provides
+/// constructors corresponding to different types of tokens, and corresponding
+/// getter methods. The type and value are fixed at the time of construction
+/// and will never be modified throughout the lifetime of the object.
+/// The getter methods are still provided to maximize the safety; an
+/// application cannot refer to a value that is invalid for the type of token.
+///
+/// This class is intentionally implemented as copyable and assignable
+/// (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 MasterToken {
+public:
+ /// \brief Enumeration for token types
+ ///
+ /// \note At the time of initial implementation, all numeric tokens
+ /// that would be extracted from \c MasterLexer should be represented
+ /// as an unsigned 32-bit integer. If we see the need for larger integers
+ /// or negative numbers, we can then extend the token types.
+ enum Type {
+ END_OF_LINE, ///< End of line detected
+ END_OF_FILE, ///< End of file detected
+ INITIAL_WS, ///< White spaces at the beginning of a line after an
+ ///< end of line or at the beginning of file (if asked
+ // for detecting it)
+ NOVALUE_TYPE_MAX = INITIAL_WS, ///< Max integer corresponding to
+ /// no-value (type only) types.
+ /// Mainly for internal use.
+ STRING, ///< A single string
+ QSTRING, ///< A single string quoted by double-quotes (").
+ NUMBER, ///< A decimal number (unsigned 32-bit)
+ ERROR ///< Error detected in getting a token
+ };
+
+ /// \brief Enumeration for lexer error codes
+ enum ErrorCode {
+ NOT_STARTED, ///< The lexer is just initialized and has no token
+ UNBALANCED_PAREN, ///< Unbalanced parentheses detected
+ UNEXPECTED_END, ///< The lexer reaches the end of line or file
+ /// unexpectedly
+ 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
+ UNEXPECTED_QUOTES, ///< Unexpected quotes character detected
+ MAX_ERROR_CODE ///< Max integer corresponding to valid error codes.
+ /// (excluding this one). Mainly for internal use.
+ };
+
+ /// \brief A simple representation of a range of a string.
+ ///
+ /// This is a straightforward pair of the start pointer of a string
+ /// and its length. The \c STRING and \c QSTRING types of tokens
+ /// will be primarily represented in this form.
+ ///
+ /// Any character can be stored in the valid range of the region.
+ /// In particular, there can be a nul character (\0) in the middle of
+ /// the region. 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
+ };
+
+ /// \brief Constructor for non-value type of token.
+ ///
+ /// \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 MasterToken(Type type) : type_(type) {
+ if (type > NOVALUE_TYPE_MAX) {
+ isc_throw(InvalidParameter, "Token per-type constructor "
+ "called with invalid type: " << type);
+ }
+ }
+
+ /// \brief Constructor for string and quoted-string types of token.
+ ///
+ /// The optional \c quoted parameter specifies whether it's a quoted or
+ /// non quoted string.
+ ///
+ /// The string is specified as a pair of a pointer to the start address
+ /// and its length. Any character can be contained in any position of
+ /// the valid range (see \c StringRegion).
+ ///
+ /// When it's a quoted string, the quotation marks must be excluded
+ /// from the specified range.
+ ///
+ /// \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.
+ MasterToken(const char* str_beg, size_t str_len, bool quoted = false) :
+ type_(quoted ? QSTRING : STRING) {
+ val_.str_region_.beg = str_beg;
+ val_.str_region_.len = str_len;
+ }
+
+ /// \brief Constructor for number type of token.
+ ///
+ /// \brief number An unsigned 32-bit integer corresponding to the token
+ /// value.
+ explicit MasterToken(uint32_t number) : type_(NUMBER) {
+ val_.number_ = number;
+ }
+
+ /// \brief Constructor for error type of token.
+ ///
+ /// \throw InvalidParameter Invalid error code value is specified.
+ /// \brief error_code A pre-defined constant of \c ErrorCode.
+ explicit MasterToken(ErrorCode error_code) : type_(ERROR) {
+ if (!(error_code < MAX_ERROR_CODE)) {
+ isc_throw(InvalidParameter, "Invalid master lexer error code: "
+ << error_code);
+ }
+ val_.error_code_ = error_code;
+ }
+
+ /// \brief Return the token type.
+ ///
+ /// \throw none
+ Type getType() const {
+ return (type_);
+ }
+
+ /// \brief Return the value of a string-variant token.
+ ///
+ /// \throw InvalidOperation Called on a non string-variant types of token.
+ /// \return A reference to \c StringRegion corresponding to the string
+ /// token value.
+ const StringRegion& getStringRegion() const {
+ if (type_ != STRING && type_ != QSTRING) {
+ isc_throw(InvalidOperation,
+ "Token::getStringRegion() for non string-variant type");
+ }
+ return (val_.str_region_);
+ }
+
+ /// \brief Return the value of a string-variant token as a string object.
+ ///
+ /// Note that the underlying string may contain a nul (\0) character
+ /// in the middle. The returned string object will contain all characters
+ /// of the valid range of the underlying string. So some string
+ /// operations such as c_str() may not work as expected.
+ ///
+ /// \throw InvalidOperation Called on a non string-variant types of token.
+ /// \throw std::bad_alloc Resource allocation failure in constructing the
+ /// string object.
+ /// \return A std::string object corresponding to the string token value.
+ std::string getString() const {
+ std::string ret;
+ getString(ret);
+ return (ret);
+ }
+
+ /// \brief Fill in a string with the value of a string-variant token.
+ ///
+ /// This is similar to the other version of \c getString(), but
+ /// the caller is supposed to pass a placeholder string object.
+ /// This will be more efficient if the caller uses the same
+ /// \c MasterLexer repeatedly and needs to get string token in the
+ /// form of a string object many times as this version could reuse
+ /// the existing internal storage of the passed string.
+ ///
+ /// Any existing content of the passed string will be removed.
+ ///
+ /// \throw InvalidOperation Called on a non string-variant types of token.
+ /// \throw std::bad_alloc Resource allocation failure in constructing the
+ /// string object.
+ ///
+ /// \param ret A string object to be filled with the token string.
+ void getString(std::string& ret) const {
+ if (type_ != STRING && type_ != QSTRING) {
+ isc_throw(InvalidOperation,
+ "Token::getString() for non string-variant type");
+ }
+ ret.assign(val_.str_region_.beg,
+ val_.str_region_.beg + val_.str_region_.len);
+ }
+
+ /// \brief Return the value of a string-variant token as a string object.
+ ///
+ /// \throw InvalidOperation Called on a non number type of token.
+ /// \return The integer corresponding to the number token value.
+ uint32_t getNumber() const {
+ if (type_ != NUMBER) {
+ isc_throw(InvalidOperation,
+ "Token::getNumber() for non number type");
+ }
+ return (val_.number_);
+ }
+
+ /// \brief Return the error code of a error type token.
+ ///
+ /// \throw InvalidOperation Called on a non error type of token.
+ /// \return The error code of the token.
+ ErrorCode getErrorCode() const {
+ if (type_ != ERROR) {
+ isc_throw(InvalidOperation,
+ "Token::getErrorCode() for non error type");
+ }
+ return (val_.error_code_);
+ };
+
+ /// \brief Return a textual description of the error of a error type token.
+ ///
+ /// The returned string would be useful to produce a log message when
+ /// a zone file parser encounters an error.
+ ///
+ /// \throw InvalidOperation Called on a non error type of token.
+ /// \throw std::bad_alloc Resource allocation failure in constructing the
+ /// string object.
+ /// \return A string object that describes the meaning of the error.
+ std::string getErrorText() const;
+
+private:
+ Type type_; // this is not const so the class can be assignable
+
+ // We use a union to represent different types of token values via the
+ // unified Token class. The class integrity should ensure valid operation
+ // on the union; getter methods should only refer to the member set at
+ // the construction.
+ union {
+ StringRegion str_region_;
+ uint32_t number_;
+ ErrorCode error_code_;
+ } 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 : public boost::noncopyable {
+ 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 isc::dns::Exception {
+ public:
+ LexerError(const char* file, size_t line, MasterToken error_token) :
+ isc::dns::Exception(file, line, error_token.getErrorText().c_str()),
+ token_(error_token)
+ {}
+ const MasterToken token_;
+ };
+
+ /// \brief Special value for input source size meaning "unknown".
+ ///
+ /// This constant value will be used as a return value of
+ /// \c getTotalSourceSize() when the size of one of the pushed sources
+ /// is unknown. Note that this value itself is a valid integer in the
+ /// range of the type, so there's still a small possibility of
+ /// ambiguity. In practice, however, the value should be sufficiently
+ /// large that should eliminate the possibility.
+ static const size_t SOURCE_SIZE_UNKNOWN;
+
+ /// \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 = 0);
+
+ /// \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.
+ ///
+ /// The data in \c input must be complete at the time of this call.
+ /// The behavior of the lexer is undefined if the caller builds or adds
+ /// data in \c input after pushing it.
+ ///
+ /// Except for rare case system errors such as memory allocation failure,
+ /// this method is generally expected to be exception free. However,
+ /// it can still throw if it encounters an unexpected failure when it
+ /// tries to identify the "size" of the input source (see
+ /// \c getTotalSourceSize()). It's an unexpected result unless the
+ /// caller intentionally passes a broken stream; otherwise it would mean
+ /// some system-dependent unexpected behavior or possibly an internal bug.
+ /// In these cases it throws an \c Unexpected exception. Note that
+ /// this version of the method doesn't return a boolean unlike the
+ /// other version that takes a file name; since this failure is really
+ /// unexpected and can be critical, it doesn't make sense to give the
+ /// caller an option to continue (other than by explicitly catching the
+ /// exception).
+ ///
+ /// \throw Unexpected An unexpected failure happens in initialization.
+ ///
+ /// \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 Get number of sources inside the lexer.
+ ///
+ /// This method never throws.
+ size_t getSourceCount() const;
+
+ /// \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 Return the total size of pushed sources.
+ ///
+ /// This method returns the sum of the size of sources that have been
+ /// pushed to the lexer by the time of the call. It would give the
+ /// caller some hint about the amount of data the lexer is working on.
+ ///
+ /// The size of a normal file is equal to the file size at the time of
+ /// the source is pushed. The size of other type of input stream is
+ /// the size of the data available in the stream at the time of the
+ /// source is pushed.
+ ///
+ /// In some special cases, it's possible that the size of the file or
+ /// stream is unknown. It happens, for example, if the standard input
+ /// is associated with a pipe from the output of another process and it's
+ /// specified as an input source. If the size of some of the pushed
+ /// source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
+ ///
+ /// The total size won't change when a source is popped. So the return
+ /// values of this method will monotonically increase or
+ /// \c SOURCE_SIZE_UNKNOWN; once it returns \c SOURCE_SIZE_UNKNOWN,
+ /// any subsequent call will also result in that value, by the above
+ /// definition.
+ ///
+ /// Before pushing any source, it returns 0.
+ ///
+ /// \throw None
+ size_t getTotalSourceSize() const;
+
+ /// \brief Return the position of lexer in the pushed sources so far.
+ ///
+ /// This method returns the position in terms of the number of recognized
+ /// characters from all sources that have been pushed by the time of the
+ /// call. Conceptually, the position in a single source is the offset
+ /// from the beginning of the file or stream to the current "read cursor"
+ /// of the lexer. The return value of this method is the sum of the
+ /// positions in all the pushed sources. If any of the sources has
+ /// already been popped, the position of the source at the time of the
+ /// pop operation will be used for the calculation.
+ ///
+ /// If the lexer reaches the end for each of all the pushed sources,
+ /// the return value should be equal to that of \c getTotalSourceSize().
+ /// It's generally expected that a source is popped when the lexer
+ /// reaches the end of the source. So, when the application of this
+ /// class parses all contents of all sources, possibly with multiple
+ /// pushes and pops, the return value of this method and
+ /// \c getTotalSourceSize() should be identical (unless the latter
+ /// returns SOURCE_SIZE_UNKNOWN). But this is not necessarily
+ /// guaranteed as the application can pop a source in the middle of
+ /// parsing it.
+ ///
+ /// Before pushing any source, it returns 0.
+ ///
+ /// The return values of this method and \c getTotalSourceSize() would
+ /// give the caller an idea of the progress of the lexer at the time of
+ /// the call. Note, however, that since it's not predictable whether
+ /// more sources will be pushed after the call, the progress determined
+ /// this way may not make much sense; it can only give an informational
+ /// hint of the progress.
+ ///
+ /// Note that the conceptual "read cursor" would move backward after a
+ /// call to \c ungetToken(), in which case this method will return a
+ /// smaller value. That is, unlike \c getTotalSourceSize(), return
+ /// values of this method may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() 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.
+ /// - A string with quotation marks is not recognized as a
+ /// - MasterToken::STRING. You have to get it as a
+ /// - MasterToken::QSTRING.
+ /// - 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;
+ boost::shared_ptr<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_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
new file mode 100644
index 0000000..05cf633
--- /dev/null
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -0,0 +1,220 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/isc_assert.h>
+#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
+
+#include <istream>
+#include <iostream>
+#include <cerrno>
+#include <cstring>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+
+namespace { // unnamed namespace
+
+std::string
+createStreamName(const std::istream& input_stream) {
+ std::stringstream ss;
+ ss << "stream-" << &input_stream;
+ return (ss.str());
+}
+
+size_t
+getStreamSize(std::istream& is) {
+ errno = 0; // see below
+ is.seekg(0, std::ios_base::end);
+ if (is.bad()) {
+ // This means the istream has an integrity error. It doesn't make
+ // sense to continue from this point, so we treat it as a fatal error.
+ isc_throw(InputSource::OpenError,
+ "failed to seek end of input source");
+ } else if (is.fail() || errno != 0) {
+ // This is an error specific to seekg(). There can be several
+ // reasons, but the most likely cause in this context is that the
+ // stream is associated with a special type of file such as a pipe.
+ // In this case, it's more likely that other main operations of
+ // the input source work fine, so we continue with just setting
+ // the stream size to "unknown".
+ //
+ // (At least some versions of) Solaris + SunStudio shows deviant
+ // behavior here: seekg() apparently calls lseek(2) internally, but
+ // even if it fails it doesn't set the error bits of istream. That will
+ // confuse the rest of this function, so, as a heuristic workaround
+ // we check errno and handle any non 0 value as fail().
+ is.clear(); // clear this error not to confuse later ops.
+ return (MasterLexer::SOURCE_SIZE_UNKNOWN);
+ }
+ const std::streampos len = is.tellg();
+ size_t ret = len;
+ if (len == static_cast<std::streampos>(-1)) { // cast for some compilers
+ if (!is.fail()) {
+ // tellg() returns -1 if istream::fail() would be true, but it's
+ // not guaranteed that it shouldn't be returned in other cases.
+ // In fact, with the combination of SunStudio and stlport,
+ // a stringstream created by the default constructor showed that
+ // behavior. We treat such cases as an unknown size.
+ ret = MasterLexer::SOURCE_SIZE_UNKNOWN;
+ } else {
+ isc_throw(InputSource::OpenError, "failed to get input size");
+ }
+ }
+ is.seekg(0, std::ios::beg);
+ if (is.fail()) {
+ isc_throw(InputSource::OpenError,
+ "failed to seek beginning of input source");
+ }
+ isc_throw_assert(len >= 0 || ret == MasterLexer::SOURCE_SIZE_UNKNOWN);
+ return (ret);
+}
+
+} // end of unnamed namespace
+
+// Explicit definition of class static constant. The value is given in the
+// declaration so it's not needed here.
+const int InputSource::END_OF_STREAM;
+
+InputSource::InputSource(std::istream& input_stream) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ total_pos_(0),
+ name_(createStreamName(input_stream)),
+ input_(input_stream),
+ input_size_(getStreamSize(input_)) {
+}
+
+namespace {
+// A helper to initialize InputSource::input_ in the member initialization
+// list.
+std::istream&
+openFileStream(std::ifstream& file_stream, const char* filename) {
+ errno = 0;
+ file_stream.open(filename);
+ if (file_stream.fail()) {
+ std::string error_txt("Error opening the input source file: ");
+ error_txt += filename;
+ if (errno != 0) {
+ error_txt += "; possible cause: ";
+ error_txt += std::strerror(errno);
+ }
+ isc_throw(InputSource::OpenError, error_txt);
+ }
+
+ return (file_stream);
+}
+}
+
+InputSource::InputSource(const char* filename) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ total_pos_(0),
+ name_(filename),
+ input_(openFileStream(file_stream_, filename)),
+ input_size_(getStreamSize(input_)) {
+}
+
+InputSource::~InputSource() {
+ if (file_stream_.is_open()) {
+ file_stream_.close();
+ }
+}
+
+int
+InputSource::getChar() {
+ if (buffer_pos_ == buffer_.size()) {
+ // We may have reached EOF at the last call to
+ // getChar(). at_eof_ will be set then. We then simply return
+ // early.
+ if (at_eof_) {
+ return (END_OF_STREAM);
+ }
+ // We are not yet at EOF. Read from the stream.
+ const int c = input_.get();
+ // Have we reached EOF now? If so, set at_eof_ and return early,
+ // but don't modify buffer_pos_ (which should still be equal to
+ // the size of buffer_).
+ if (input_.eof()) {
+ at_eof_ = true;
+ return (END_OF_STREAM);
+ }
+ // This has to come after the .eof() check as some
+ // implementations seem to check the eofbit also in .fail().
+ if (input_.fail()) {
+ isc_throw(MasterLexer::ReadError,
+ "Error reading from the input stream: " << getName());
+ }
+ buffer_.push_back(c);
+ }
+
+ const int c = buffer_[buffer_pos_];
+ ++buffer_pos_;
+ ++total_pos_;
+ if (c == '\n') {
+ ++line_;
+ }
+
+ return (c);
+}
+
+void
+InputSource::ungetChar() {
+ if (at_eof_) {
+ at_eof_ = false;
+ } else if (buffer_pos_ == 0) {
+ isc_throw(UngetBeforeBeginning,
+ "Cannot skip before the start of buffer");
+ } else {
+ --buffer_pos_;
+ --total_pos_;
+ if (buffer_[buffer_pos_] == '\n') {
+ --line_;
+ }
+ }
+}
+
+void
+InputSource::ungetAll() {
+ isc_throw_assert(total_pos_ >= buffer_pos_);
+ total_pos_ -= buffer_pos_;
+ buffer_pos_ = 0;
+ line_ = saved_line_;
+ at_eof_ = false;
+}
+
+void
+InputSource::saveLine() {
+ saved_line_ = line_;
+}
+
+void
+InputSource::compact() {
+ if (buffer_pos_ == buffer_.size()) {
+ buffer_.clear();
+ } else {
+ buffer_.erase(buffer_.begin(), buffer_.begin() + buffer_pos_);
+ }
+
+ buffer_pos_ = 0;
+}
+
+void
+InputSource::mark() {
+ saveLine();
+ compact();
+}
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h
new file mode 100644
index 0000000..2d6a613
--- /dev/null
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -0,0 +1,185 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DNS_INPUTSOURCE_H
+#define DNS_INPUTSOURCE_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+
+/// \brief An input source that is used internally by MasterLexer.
+///
+/// This is a helper internal class for MasterLexer, and represents
+/// state of a single source of the entire zone data to be
+/// parsed. Normally this means the master zone file, but MasterLexer
+/// can have multiple InputSources if $INCLUDE is used. The source can
+/// also be generic input stream (std::istream).
+///
+/// This class is not meant for public use. We also enforce that
+/// instances are non-copyable.
+class InputSource : boost::noncopyable {
+public:
+ /// \brief Returned by getChar() when end of stream is reached.
+ ///
+ /// \note C++ allows a static const class member of an integral type to
+ /// be used without explicit definition as long as its address isn't
+ /// required. But, since this is a public member variable and we cannot
+ /// assume how it's used, we give a definition in the implementation.
+ static const int END_OF_STREAM = -1;
+
+ /// \brief Exception thrown when ungetChar() is made to go before
+ /// the start of buffer.
+ struct UngetBeforeBeginning : public OutOfRange {
+ UngetBeforeBeginning(const char* file, size_t line, const char* what) :
+ OutOfRange(file, line, what)
+ {}
+ };
+
+ /// \brief Exception thrown when we fail to open the input file.
+ struct OpenError : public Unexpected {
+ OpenError(const char* file, size_t line, const char* what) :
+ Unexpected(file, line, what)
+ {}
+ };
+
+ /// \brief Constructor which takes an input stream. The stream is
+ /// read-from, but it is not closed.
+ ///
+ /// \throws OpenError If the data size of the input stream cannot be
+ /// detected.
+ explicit InputSource(std::istream& input_stream);
+
+ /// \brief Constructor which takes a filename to read from. The
+ /// associated file stream is managed internally.
+ ///
+ /// \throws OpenError when opening the input file fails or the size of
+ /// the file cannot be detected.
+ explicit InputSource(const char* filename);
+
+ /// \brief Destructor
+ ~InputSource();
+
+ /// \brief Returns a name for the InputSource. Typically this is the
+ /// filename, but if the InputSource was constructed for an
+ /// \c std::istream, it returns a name in the format "stream-%p".
+ const std::string& getName() const {
+ return (name_);
+ }
+
+ /// \brief Returns the size of the input source in bytes.
+ ///
+ /// If the size is unknown, it returns \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// See \c MasterLexer::getTotalSourceSize() for the definition of
+ /// the size of sources and for when the size can be unknown.
+ ///
+ /// \throw None
+ size_t getSize() const {
+ return (input_size_);
+ }
+
+ /// \brief Returns the current read position in the input source.
+ ///
+ /// This method returns the position of the character that was last
+ /// retrieved from the source. Unless some characters have been
+ /// "ungotten" by \c ungetChar() or \c ungetAll(), this value is equal
+ /// to the number of calls to \c getChar() until it reaches the
+ /// END_OF_STREAM. Note that the position of the first character in
+ /// the source is 1. At the point of the last character, the return value
+ /// of this method should be equal to that of \c getSize(), and
+ /// recognizing END_OF_STREAM doesn't increase the position.
+ ///
+ /// If \c ungetChar() or \c ungetAll() is called, the position is
+ /// decreased by the number of "ungotten" characters. So the return
+ /// values may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() const {
+ return (total_pos_);
+ }
+
+ /// \brief Returns if the input source is at end of file.
+ bool atEOF() const {
+ return (at_eof_);
+ }
+
+ /// \brief Returns the current line number being read.
+ size_t getCurrentLine() const {
+ return (line_);
+ }
+
+ /// \brief Saves the current line being read. Later, when
+ /// \c ungetAll() is called, it skips back to the last-saved line.
+ ///
+ /// TODO: Please make this method private if it is unused after the
+ /// MasterLexer implementation is complete (and only \c mark() is
+ /// used instead).
+ void saveLine();
+
+ /// Removes buffered content before the current location in the
+ /// \c InputSource. It's not possible to \c ungetChar() after this,
+ /// unless we read more data using \c getChar().
+ ///
+ /// TODO: Please make this method private if it is unused after the
+ /// MasterLexer implementation is complete (and only \c mark() is
+ /// used instead).
+ void compact();
+
+ /// Calls \c saveLine() and \c compact() in sequence.
+ void mark();
+
+ /// \brief Returns a single character from the input source. If end
+ /// of file is reached, \c END_OF_STREAM is returned.
+ ///
+ /// \throws MasterLexer::ReadError when reading from the input stream or
+ /// file fails.
+ int getChar();
+
+ /// \brief Skips backward a single character in the input
+ /// source. The last-read character is unget.
+ ///
+ /// \throws UngetBeforeBeginning if we go backwards past the start
+ /// of reading, or backwards past the last time compact() was
+ /// called.
+ void ungetChar();
+
+ /// Forgets what was read, and skips back to the position where
+ /// \c compact() was last called. If \c compact() was not called, it
+ /// skips back to where reading started. If \c saveLine() was called
+ /// previously, it sets the current line number to the line number
+ /// saved.
+ void ungetAll();
+
+private:
+ bool at_eof_;
+ size_t line_;
+ size_t saved_line_;
+
+ std::vector<char> buffer_;
+ size_t buffer_pos_;
+ size_t total_pos_;
+
+ const std::string name_;
+ std::ifstream file_stream_;
+ std::istream& input_;
+ const size_t input_size_;
+};
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
+
+#endif // DNS_INPUTSOURCE_H
diff --git a/src/lib/dns/master_lexer_state.h b/src/lib/dns/master_lexer_state.h
new file mode 100644
index 0000000..2165e84
--- /dev/null
+++ b/src/lib/dns/master_lexer_state.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MASTER_LEXER_STATE_H
+#define MASTER_LEXER_STATE_H
+
+#include <dns/master_lexer.h>
+
+namespace isc {
+namespace dns {
+
+namespace master_lexer_internal {
+
+/// \brief Tokenization state for \c MasterLexer.
+///
+/// This is a base class of classes that represent various states of a single
+/// tokenization session of \c MasterLexer, i.e., the states used for a
+/// single call to \c MasterLexer::getNextToken().
+///
+/// It follows the convention of the state design pattern: each derived class
+/// corresponds to a specific state, and the state transition takes place
+/// through the virtual method named \c handle(). The \c handle() method
+/// takes the main \c MasterLexer object that holds all necessary internal
+/// context, and updates it as necessary; each \c State derived class is
+/// completely stateless.
+///
+/// The initial transition takes place in a static method of the base class,
+/// \c start(). This is mainly for implementation convenience; we need to
+/// pass options given to \c MasterLexer::getNextToken() for the initial
+/// state, so it makes more sense to separate the interface for the transition
+/// from the initial state.
+///
+/// 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.
+/// This is intentional; essentially its a part of \c MasterLexer and
+/// is defined as a separate class only for implementation clarity and better
+/// testability. It's defined in a publicly visible header, but that's only
+/// for testing purposes. No normal application or even no other classes of
+/// this library are expected to use this class.
+class State {
+public:
+ /// \brief Virtual destructor.
+ ///
+ /// In our usage this actually doesn't matter, but some compilers complain
+ /// about it and we need to silence them.
+ virtual ~State() {}
+
+ /// \brief Begin state transitions to get the next token.
+ ///
+ /// This is the first method that \c MasterLexer needs to call for a
+ /// tokenization session. The lexer passes a reference to itself
+ /// and options given in \c getNextToken().
+ ///
+ /// \throw MasterLexer::ReadError Unexpected I/O error
+ /// \throw std::bad_alloc Internal resource allocation failure
+ ///
+ /// \param lexer The lexer object that holds the main context.
+ /// \param options The options passed to getNextToken().
+ /// \return A pointer to the next state object or null if the transition
+ /// is completed.
+ static const State* start(MasterLexer& lexer,
+ MasterLexer::Options options);
+
+ /// \brief Handle the process of one specific state.
+ ///
+ /// This method is expected to be called on the object returned by
+ /// start(). In the usual state transition design pattern, it would
+ /// return the next state. But as we noticed, we never have another
+ /// state, so we simplify it by not returning anything instead of
+ /// returning null every time.
+ ///
+ /// \throw MasterLexer::ReadError Unexpected I/O error
+ /// \throw std::bad_alloc Internal resource allocation failure
+ ///
+ /// \param lexer The lexer object that holds the main context.
+ virtual void handle(MasterLexer& lexer) const = 0;
+
+ /// \brief Types of states.
+ ///
+ /// Specific states are basically hidden within the implementation,
+ /// but we'd like to allow tests to examine them, so we provide
+ /// a way to get an instance of a specific state.
+ enum ID {
+ CRLF, ///< Just seen a carriage-return character
+ String, ///< Handling a string token
+ QString, ///< Handling a quoted string token
+ Number ///< Handling a number
+ };
+
+ /// \brief Returns a \c State instance of the given state.
+ ///
+ /// This is provided only for testing purposes so tests can check
+ /// the behavior of each state separately. \c MasterLexer shouldn't
+ /// need this method.
+ static const State& getInstance(ID state_id);
+
+ /// \name Read-only accessors for testing purposes.
+ ///
+ /// These allow tests to inspect some selected portion of the internal
+ /// states of \c MasterLexer. These shouldn't be used except for testing
+ /// purposes.
+ ///@{
+ bool wasLastEOL(const MasterLexer& lexer) const;
+ const MasterToken& getToken(const MasterLexer& lexer) const;
+ size_t getParenCount(const MasterLexer& lexer) const;
+ ///@}
+
+protected:
+ /// \brief An accessor to the internal implementation class of
+ /// \c MasterLexer.
+ ///
+ /// This is provided for specific derived classes as they are not direct
+ /// friends of \c MasterLexer.
+ ///
+ /// \param lexer The lexer object that holds the main context.
+ /// \return A pointer to the implementation class object of the given
+ /// lexer. This is never null.
+ MasterLexer::MasterLexerImpl* getLexerImpl(MasterLexer& lexer) const {
+ return (lexer.impl_.get());
+ }
+};
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
+#endif // MASTER_LEXER_STATE_H
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
new file mode 100644
index 0000000..e771bd3
--- /dev/null
+++ b/src/lib/dns/master_loader.cc
@@ -0,0 +1,742 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/isc_assert.h>
+#include <dns/master_loader.h>
+#include <dns/master_lexer.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+#include <boost/format.hpp>
+#include <boost/algorithm/string/predicate.hpp> // for iequals
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <memory>
+#include <vector>
+
+#include <cstdio> // for sscanf()
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using std::pair;
+using boost::algorithm::iequals;
+using boost::shared_ptr;
+
+namespace isc {
+namespace dns {
+
+namespace {
+
+// An internal exception, used to control the code flow in case of errors.
+// It is thrown during the loading and caught later, not to be propagated
+// outside of the file.
+class InternalException : public isc::Exception {
+public:
+ InternalException(const char* filename, size_t line, const char* what) :
+ Exception(filename, line, what)
+ {}
+};
+
+} // end unnamed namespace
+
+/// \brief Private implementation class for the \c MasterLoader
+///
+/// This class is used internally by the \c MasterLoader and is not
+/// publicly visible. It is present to avoid polluting the public API
+/// with internal implementation details of the \c MasterLoader.
+// cppcheck-suppress noConstructor
+class MasterLoader::MasterLoaderImpl {
+public:
+ /// \brief Constructor.
+ ///
+ /// \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.
+ /// Usually, the callback carries a filename and line number of the
+ /// input where the problem happens. There's a special case of empty
+ /// filename and zero line in case the opening of the top-level master
+ /// file fails.
+ /// \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.
+ MasterLoaderImpl(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ MasterLoader::Options options) :
+ lexer_(),
+ zone_origin_(zone_origin),
+ active_origin_(zone_origin),
+ zone_class_(zone_class),
+ callbacks_(callbacks),
+ add_callback_(add_callback),
+ options_(options),
+ master_file_(master_file),
+ initialized_(false),
+ ok_(true),
+ many_errors_((options & MANY_ERRORS) != 0),
+ previous_name_(false),
+ complete_(false),
+ seen_error_(false),
+ warn_rfc1035_ttl_(true),
+ rr_count_(0) {
+ }
+
+ /// \brief Wrapper around \c MasterLexer::pushSource() (file version)
+ ///
+ /// This method is used as a wrapper around the lexer's
+ /// \c pushSource() to also save the current origin and the last
+ /// seen name (to be restored upon \c popSource()). It also calls
+ /// \c pushSource(). See \c doInclude() implementation for more
+ /// details.
+ ///
+ /// \param filename Path to the file to push as a new source.
+ /// \param current_origin The current origin name to save.
+ void pushSource(const std::string& filename, const Name& current_origin) {
+ std::string error;
+ if (!lexer_.pushSource(filename.c_str(), &error)) {
+ if (initialized_) {
+ isc_throw(InternalException, error.c_str());
+ } else {
+ // Top-level file
+ reportError("", 0, error);
+ ok_ = false;
+ }
+ }
+ // Store the current status, so we can recover it upon popSource
+ include_info_.push_back(IncludeInfo(current_origin, last_name_));
+ initialized_ = true;
+ previous_name_ = false;
+ }
+
+ /// \brief Wrapper around \c MasterLexer::pushSource() (stream version)
+ ///
+ /// Similar to \c pushSource(). This method need not save the
+ /// current origin as it is not used with $INCLUDE processing.
+ ///
+ /// \param stream The input stream to use as a new source.
+ void pushStreamSource(std::istream& stream) {
+ lexer_.pushSource(stream);
+ initialized_ = true;
+ }
+
+ /// \brief Implementation of \c MasterLoader::loadIncremental()
+ ///
+ /// See \c MasterLoader::loadIncremental() for details.
+ bool loadIncremental(size_t count_limit);
+
+ /// \brief Return the total size of the input sources pushed so
+ /// far. See \c MasterLexer::getTotalSourceSize().
+ size_t getSize() const {
+ return (lexer_.getTotalSourceSize());
+ }
+
+ /// \brief Return the line number being parsed in the pushed input
+ /// sources. See \c MasterLexer::getPosition().
+ size_t getPosition() const {
+ return (lexer_.getPosition());
+ }
+
+private:
+ /// \brief Report an error using the callbacks that were supplied
+ /// during \c MasterLoader construction. Note that this method also
+ /// throws \c MasterLoaderError exception if necessary, so the
+ /// caller need not throw it.
+ void reportError(const std::string& filename, size_t line,
+ const std::string& reason) {
+ seen_error_ = true;
+ callbacks_.error(filename, line, reason);
+ if (!many_errors_) {
+ // In case we don't have the lenient mode, every error is fatal
+ // and we throw
+ ok_ = false;
+ complete_ = true;
+ isc_throw(MasterLoaderError, reason.c_str());
+ }
+ }
+
+ /// \brief Wrapper around \c MasterLexer::popSource()
+ ///
+ /// This method is used as a wrapper around the lexer's
+ /// \c popSource() to also restore the current origin and the last
+ /// seen name (at time of push). It also calls \c popSource(). See
+ /// \c doInclude() implementation for more details.
+ bool popSource() {
+ if (lexer_.getSourceCount() == 1) {
+ return (false);
+ }
+ lexer_.popSource();
+ // Restore original origin and last seen name
+
+ // We move in tandem, there's an extra item included during the
+ // initialization, so we can never run out of them
+ isc_throw_assert(!include_info_.empty());
+ const IncludeInfo& info(include_info_.back());
+ active_origin_ = info.first;
+ last_name_ = info.second;
+ include_info_.pop_back();
+ previous_name_ = false;
+ return (true);
+ }
+
+ /// \brief Get a string token. Handle it as error if it is not string.
+ const string getString() {
+ lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
+ return (string_token_);
+ }
+
+ /// \brief Parse the initial token at the beginning of a line in a
+ /// master file (or stream).
+ ///
+ /// A helper method of \c loadIncremental(), parsing the first token
+ /// of a new line. If it looks like an RR, detect its owner name
+ /// and return a string token for the next field of the RR.
+ ///
+ /// Otherwise, return either \c END_OF_LINE or \c END_OF_FILE token
+ /// depending on whether the loader continues to the next line or
+ /// completes the load, respectively. Other corner cases including
+ /// $-directive handling is done here.
+ ///
+ /// For unexpected errors, it throws an exception, which will be
+ /// handled in \c loadIncremental().
+ MasterToken handleInitialToken();
+
+ /// \brief Process the $ORIGIN directive.
+ void doOrigin(bool is_optional) {
+ // Parse and create the new origin. It is relative to the previous
+ // one.
+ const MasterToken&
+ name_tok(lexer_.getNextToken(MasterToken::QSTRING, is_optional));
+
+ if (name_tok.getType() == MasterToken::QSTRING ||
+ name_tok.getType() == MasterToken::STRING) {
+
+ const MasterToken::StringRegion&
+ name_string(name_tok.getStringRegion());
+ active_origin_ = Name(name_string.beg, name_string.len,
+ &active_origin_);
+ if (name_string.len > 0 &&
+ name_string.beg[name_string.len - 1] != '.') {
+ callbacks_.warning(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "The new origin is relative, did you really"
+ " mean " + active_origin_.toText() + "?");
+ }
+ } else {
+ // If it is not optional, we must not get anything but
+ // a string token.
+ isc_throw_assert(is_optional);
+
+ // We return the newline there. This is because we want to
+ // behave the same if there is or isn't the name, leaving the
+ // newline there.
+ lexer_.ungetToken();
+ }
+ }
+
+ /// \brief Process the $INCLUDE directive.
+ void doInclude() {
+ // First, get the filename to include
+ const string
+ filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
+
+ // There optionally can be an origin, that applies before the include.
+ // We need to save the currently active origin before calling
+ // doOrigin(), because it would update active_origin_ while we need
+ // to pass the active origin before recognizing the new origin to
+ // pushSource. Note: RFC 1035 is not really clear on this: it reads
+ // "regardless of changes... within the included file", but the new
+ // origin is not really specified "within the included file".
+ // Nevertheless, this behavior is probably more likely to be the
+ // intent of the RFC, and it's compatible with BIND 9.
+ const Name current_origin = active_origin_;
+ doOrigin(true);
+
+ pushSource(filename, current_origin);
+ }
+
+ /// \brief Parse RR fields (TTL, CLASS and TYPE).
+ ///
+ /// A helper method for \c loadIncremental(). It parses part of an
+ /// RR until it finds the RR type field. If TTL or RR class is
+ /// specified before the RR type, it also recognizes and validates
+ /// them.
+ ///
+ /// \param explicit_ttl will be set to true if this method finds a
+ /// valid TTL field.
+ /// \param rrparam_token Pass the current (parsed) token here.
+ RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) {
+ // Find TTL, class and type. Both TTL and class are
+ // optional and may occur in any order if they exist. TTL
+ // and class come before type which must exist.
+ //
+ // [<TTL>] [<class>] <type> <RDATA>
+ // [<class>] [<TTL>] <type> <RDATA>
+
+ // named-signzone outputs TTL first, so try parsing it in order
+ // first.
+ if (setCurrentTTL(rrparam_token.getString())) {
+ explicit_ttl = true;
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ } else {
+ // If it's not a TTL here, continue and try again
+ // after the RR class below.
+ }
+
+ boost::scoped_ptr<RRClass> rrclass
+ (RRClass::createFromText(rrparam_token.getString()));
+ if (rrclass) {
+ if (*rrclass != zone_class_) {
+ isc_throw(InternalException, "Class mismatch: " << *rrclass <<
+ " vs. " << zone_class_);
+ }
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ }
+
+ // If we couldn't parse TTL earlier in the stream (above), try
+ // again at current location.
+ if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
+ explicit_ttl = true;
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ }
+
+ // Return the current string token's value as the RRType.
+ return (RRType(rrparam_token.getString()));
+ }
+
+ /// \brief Check and limit TTL to maximum value.
+ ///
+ /// Upper limit check when recognizing a specific TTL value from the
+ /// zone file ($TTL, the RR's TTL field, or the SOA minimum). RFC2181
+ /// Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
+ /// and prohibits transmitting a TTL field exceeding this range. We
+ /// guarantee that by limiting the value at the time of zone
+ /// parsing/loading, following what BIND 9 does. Resetting it to 0
+ /// at this point may not be exactly what the RFC states (depending on
+ /// the meaning of 'received'), but the end result would be the same (i.e.,
+ /// the guarantee on transmission). Again, we follow the BIND 9's behavior
+ /// here.
+ ///
+ /// \param ttl the TTL to check. If it is larger than the maximum
+ /// allowed, it is set to 0.
+ /// \param post_parsing should be true iff this method is called
+ /// after parsing the entire RR and the lexer is positioned at the
+ /// next line. It's just for calculating the accurate source line
+ /// when callback is necessary.
+ void limitTTL(RRTTL& ttl, bool post_parsing) {
+ if (ttl > RRTTL::MAX_TTL()) {
+ const size_t src_line = lexer_.getSourceLine() -
+ (post_parsing ? 1 : 0);
+ callbacks_.warning(lexer_.getSourceName(), src_line,
+ "TTL " + ttl.toText() + " > MAXTTL, "
+ "setting to 0 per RFC2181");
+ ttl = RRTTL(0);
+ }
+ }
+
+ /// \brief Set/reset the default TTL.
+ ///
+ /// This should be from either $TTL or SOA minimum TTL (it's the
+ /// caller's responsibility; this method doesn't care about where it
+ /// comes from). See \c limitTTL() for parameter post_parsing.
+ void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
+ assignTTL(default_ttl_, ttl);
+ limitTTL(*default_ttl_, post_parsing);
+ }
+
+ /// \brief Try to set/reset the current TTL from candidate TTL text.
+ ///
+ /// It's possible it that the text does not actually represent a TTL
+ /// (which is not immediately considered an error). Returns \c true
+ /// iff it's recognized as a valid TTL (and only in which case the
+ /// current TTL is set).
+ ///
+ /// \param ttl_txt The text to parse as a TTL.
+ /// \return true if a TTL was parsed (and set as the current TTL).
+ bool setCurrentTTL(const string& ttl_txt) {
+ // We use the factory version instead of RRTTL constructor as we
+ // need to expect cases where ttl_txt does not actually represent a TTL
+ // but an RR class or type.
+ RRTTL* rrttl = RRTTL::createFromText(ttl_txt);
+ if (rrttl) {
+ current_ttl_.reset(rrttl);
+ limitTTL(*current_ttl_, false);
+ return (true);
+ }
+ return (false);
+ }
+
+ /// \brief Determine the TTL of the current RR based on the given
+ /// parsing context.
+ ///
+ /// \c explicit_ttl is true iff the TTL is explicitly specified for that RR
+ /// (in which case current_ttl_ is set to that TTL).
+ /// \c rrtype is the type of the current RR, and \c rdata is its RDATA. They
+ /// only matter if the type is SOA and no available TTL is known. In this
+ /// case the minimum TTL of the SOA will be used as the TTL of that SOA
+ /// and the default TTL for subsequent RRs.
+ const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
+ const rdata::ConstRdataPtr& rdata) {
+ // We've completed parsing the full of RR, and the lexer is already
+ // positioned at the next line. If we need to call callback,
+ // we need to adjust the line number.
+ const size_t current_line = lexer_.getSourceLine() - 1;
+
+ if (!current_ttl_ && !default_ttl_) {
+ if (rrtype == RRType::SOA()) {
+ callbacks_.warning(lexer_.getSourceName(), current_line,
+ "no TTL specified; "
+ "using SOA MINTTL instead");
+ const uint32_t ttl_val =
+ dynamic_cast<const rdata::generic::SOA&>(*rdata).
+ getMinimum();
+ setDefaultTTL(RRTTL(ttl_val), true);
+ assignTTL(current_ttl_, *default_ttl_);
+ } else {
+ // On catching the exception we'll try to reach EOL again,
+ // so we need to unget it now.
+ lexer_.ungetToken();
+ throw InternalException(__FILE__, __LINE__,
+ "no TTL specified; load rejected");
+ }
+ } else if (!explicit_ttl && default_ttl_) {
+ assignTTL(current_ttl_, *default_ttl_);
+ } else if (!explicit_ttl && warn_rfc1035_ttl_) {
+ // Omitted (class and) TTL values are default to the last
+ // explicitly stated values (RFC 1035, Sec. 5.1).
+ callbacks_.warning(lexer_.getSourceName(), current_line,
+ "using RFC1035 TTL semantics; default to the "
+ "last explicitly stated TTL");
+ warn_rfc1035_ttl_ = false; // we only warn about this once
+ }
+ isc_throw_assert(current_ttl_);
+ return (*current_ttl_);
+ }
+
+ /// \brief Handle a $DIRECTIVE
+ ///
+ /// This method is called when a $DIRECTIVE is encountered in the
+ /// input stream.
+ void handleDirective(const char* directive, size_t length) {
+ if (iequals(directive, "INCLUDE")) {
+ doInclude();
+ } else if (iequals(directive, "ORIGIN")) {
+ doOrigin(false);
+ eatUntilEOL(true);
+ } else if (iequals(directive, "TTL")) {
+ setDefaultTTL(RRTTL(getString()), false);
+ eatUntilEOL(true);
+ } else {
+ isc_throw(InternalException, "Unknown directive '" <<
+ string(directive, directive + length) << "'");
+ }
+ }
+
+ /// \brief Skip tokens until end-of-line.
+ void eatUntilEOL(bool reportExtra) {
+ // We want to continue. Try to read until the end of line
+ for (;;) {
+ const MasterToken& token(lexer_.getNextToken());
+ switch (token.getType()) {
+ case MasterToken::END_OF_FILE:
+ callbacks_.warning(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "File does not end with newline");
+ // We don't pop here. The End of file will stay there,
+ // and we'll handle it in the next iteration of
+ // loadIncremental properly.
+ return;
+ case MasterToken::END_OF_LINE:
+ // Found the end of the line. Good.
+ return;
+ default:
+ // Some other type of token.
+ if (reportExtra) {
+ reportExtra = false;
+ reportError(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "Extra tokens at the end of line");
+ }
+ break;
+ }
+ }
+ }
+
+ /// \brief Assign the right RRTTL's value to the left RRTTL. If one
+ /// doesn't exist in the scoped_ptr, make a new RRTTL copy of the
+ /// right argument.
+ static void assignTTL(boost::scoped_ptr<RRTTL>& left, const RRTTL& right) {
+ if (!left) {
+ left.reset(new RRTTL(right));
+ } else {
+ *left = right;
+ }
+ }
+
+private:
+ MasterLexer lexer_;
+ const Name zone_origin_;
+ Name active_origin_; // The origin used during parsing
+ // (modifiable by $ORIGIN)
+ shared_ptr<Name> last_name_; // Last seen name (for INITIAL_WS handling)
+ const RRClass zone_class_;
+ MasterLoaderCallbacks callbacks_;
+ const AddRRCallback add_callback_;
+ boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
+ // unspecified. If null no default
+ // is known.
+ boost::scoped_ptr<RRTTL> current_ttl_; // The TTL used most recently.
+ // Initially unset. Once set
+ // always stores a valid
+ // RRTTL.
+ const MasterLoader::Options options_;
+ const std::string master_file_;
+ std::string string_token_;
+ bool initialized_;
+ bool ok_; // Is it OK to continue loading?
+ const bool many_errors_; // Are many errors allowed (or should we abort
+ // on the first)
+ // Some info about the outer files from which we include.
+ // The first one is current origin, the second is the last seen name
+ // in that file.
+ typedef pair<Name, shared_ptr<Name> > IncludeInfo;
+ vector<IncludeInfo> include_info_;
+ bool previous_name_; // True if there was a previous name in this file
+ // (false at the beginning or after an $INCLUDE line)
+
+public:
+ bool complete_; // All work done.
+ bool seen_error_; // Was there at least one error during the
+ // load?
+ bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
+ // from the previous RR is used.
+ size_t rr_count_; // number of RRs successfully loaded
+};
+
+MasterToken
+MasterLoader::MasterLoaderImpl::handleInitialToken() {
+ const MasterToken& initial_token =
+ lexer_.getNextToken(MasterLexer::QSTRING | MasterLexer::INITIAL_WS);
+
+ // The most likely case is INITIAL_WS, and then string/qstring. We
+ // handle them first.
+ if (initial_token.getType() == MasterToken::INITIAL_WS) {
+ const MasterToken& next_token = lexer_.getNextToken();
+ if (next_token.getType() == MasterToken::END_OF_LINE) {
+ return (next_token); // blank line
+ } else if (next_token.getType() == MasterToken::END_OF_FILE) {
+ lexer_.ungetToken(); // handle it in the next iteration.
+ eatUntilEOL(true); // effectively warn about the unexpected EOF.
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+
+ // This means the same name as previous.
+ if (!last_name_) {
+ isc_throw(InternalException, "No previous name to use in "
+ "place of initial whitespace");
+ } else if (!previous_name_) {
+ callbacks_.warning(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Owner name omitted around $INCLUDE, the result "
+ "might not be as expected");
+ }
+ return (next_token);
+ } else if (initial_token.getType() == MasterToken::STRING ||
+ initial_token.getType() == MasterToken::QSTRING) {
+ // If it is name (or directive), handle it.
+ const MasterToken::StringRegion&
+ name_string(initial_token.getStringRegion());
+
+ if (name_string.len > 0 && name_string.beg[0] == '$') {
+ // This should have either thrown (and the error handler
+ // will read up until the end of line) or read until the
+ // end of line.
+
+ // Exclude the $ from the string on this point.
+ handleDirective(name_string.beg + 1, name_string.len - 1);
+ // So, get to the next line, there's nothing more interesting
+ // in this one.
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+
+ // This should be an RR, starting with an owner name. Construct the
+ // name, and some string token should follow.
+ last_name_.reset(new Name(name_string.beg, name_string.len,
+ &active_origin_));
+ previous_name_ = true;
+ return (lexer_.getNextToken(MasterToken::STRING));
+ }
+
+ switch (initial_token.getType()) { // handle less common cases
+ case MasterToken::END_OF_FILE:
+ if (!popSource()) {
+ return (initial_token);
+ } else {
+ // We try to read a token from the popped source
+ // So continue to the next line of that source, but first, make
+ // sure the source is at EOL
+ eatUntilEOL(true);
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+ case MasterToken::END_OF_LINE:
+ return (initial_token); // empty line
+ case MasterToken::ERROR:
+ // Error token here.
+ isc_throw(InternalException, initial_token.getErrorText());
+ default:
+ // Some other token (what could that be?)
+ isc_throw(InternalException, "Parser got confused (unexpected "
+ "token " << initial_token.getType() << ")");
+ }
+}
+
+bool
+MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
+ if (count_limit == 0) {
+ isc_throw(isc::InvalidParameter, "Count limit set to 0");
+ }
+ if (complete_) {
+ isc_throw(isc::InvalidOperation,
+ "Trying to load when already loaded");
+ }
+ if (!initialized_) {
+ pushSource(master_file_, active_origin_);
+ }
+ size_t count = 0;
+ while (ok_ && count < count_limit) {
+ try {
+ const MasterToken next_token = handleInitialToken();
+ if (next_token.getType() == MasterToken::END_OF_FILE) {
+ return (true); // we are done
+ } else if (next_token.getType() == MasterToken::END_OF_LINE) {
+ continue; // nothing more to do in this line
+ }
+ // We are going to parse an RR, have known the owner name,
+ // and are now seeing the next string token in the rest of the RR.
+ isc_throw_assert(next_token.getType() == MasterToken::STRING);
+
+ bool explicit_ttl = false;
+ const RRType rrtype = parseRRParams(explicit_ttl, next_token);
+ // TODO: Check if it is SOA, it should be at the origin.
+
+ const rdata::RdataPtr rdata =
+ rdata::createRdata(rrtype, zone_class_, lexer_,
+ &active_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 (rdata) {
+ add_callback_(*last_name_, zone_class_, rrtype,
+ getCurrentTTL(explicit_ttl, rrtype, rdata),
+ rdata);
+ // Good, we loaded another one
+ ++count;
+ ++rr_count_;
+ } else {
+ seen_error_ = true;
+ if (!many_errors_) {
+ ok_ = false;
+ complete_ = true;
+ // We don't have the exact error here, but it was reported
+ // by the error callback.
+ isc_throw(MasterLoaderError, "Invalid RR data");
+ }
+ }
+ } catch (const isc::dns::DNSTextError& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ } catch (const MasterLexer::ReadError& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ } catch (const MasterLexer::LexerError& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ } catch (const InternalException& e) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ }
+ }
+ // When there was a fatal error and ok is false, we say we are done.
+ return (!ok_);
+}
+
+MasterLoader::MasterLoader(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options) {
+ if (!add_callback) {
+ isc_throw(isc::InvalidParameter, "Empty add RR callback");
+ }
+ impl_.reset(new MasterLoaderImpl(master_file, zone_origin,
+ zone_class, callbacks, add_callback, options));
+}
+
+MasterLoader::MasterLoader(std::istream& stream,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options) {
+ if (!add_callback) {
+ isc_throw(isc::InvalidParameter, "Empty add RR callback");
+ }
+ impl_.reset(new MasterLoaderImpl("", zone_origin, zone_class,
+ callbacks, add_callback, options));
+ impl_->pushStreamSource(stream);
+}
+
+MasterLoader::~MasterLoader() {
+}
+
+bool
+MasterLoader::loadIncremental(size_t count_limit) {
+ const bool result = impl_->loadIncremental(count_limit);
+ impl_->complete_ = result;
+ return (result);
+}
+
+bool
+MasterLoader::loadedSuccessfully() const {
+ return (impl_->complete_ && !impl_->seen_error_);
+}
+
+size_t
+MasterLoader::getSize() const {
+ return (impl_->getSize());
+}
+
+size_t
+MasterLoader::getPosition() const {
+ return (impl_->getPosition());
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
new file mode 100644
index 0000000..3cb8aff
--- /dev/null
+++ b/src/lib/dns/master_loader.h
@@ -0,0 +1,186 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MASTER_LOADER_H
+#define MASTER_LOADER_H
+
+#include <dns/exceptions.h>
+#include <dns/master_loader_callbacks.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <memory>
+
+namespace isc {
+namespace dns {
+
+class Name;
+class RRClass;
+
+/// \brief Error while loading by MasterLoader without specifying the
+/// MANY_ERRORS option.
+class MasterLoaderError : public isc::Exception {
+public:
+ MasterLoaderError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \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, ///< Nothing special.
+ MANY_ERRORS = 1 ///< Lenient mode (see documentation of MasterLoader
+ /// constructor).
+ };
+
+ /// \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.
+ /// Usually, the callback carries a filename and line number of the
+ /// input where the problem happens. There's a special case of empty
+ /// filename and zero line in case the opening of the top-level master
+ /// file fails.
+ /// \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 AddRRCallback& add_callback,
+ Options options = DEFAULT);
+
+ /// \brief Constructor from a stream
+ ///
+ /// This is a constructor very similar to the previous one. The only
+ /// difference is it doesn't take a filename, but an input stream
+ /// to read the data from. It is expected to be mostly used in tests,
+ /// but it is public as it may possibly be useful for other currently
+ /// unknown purposes.
+ MasterLoader(std::istream& input,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ 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.
+ /// \throw MasterLoaderError when there's an error in the input master
+ /// file and the MANY_ERRORS is not specified. It never throws this
+ /// in case MANY_ERRORS is specified.
+ bool loadIncremental(size_t count_limit);
+
+ /// \brief Load everything
+ ///
+ /// This simply calls loadIncremental until the loading is done.
+ /// \throw isc::InvalidOperation when called after loading was done
+ /// already.
+ /// \throw MasterLoaderError when there's an error in the input master
+ /// file and the MANY_ERRORS is not specified. It never throws this
+ /// in case MANY_ERRORS is specified.
+ void load() {
+ while (!loadIncremental(1000)) { // 1000 = arbitrary largish number
+ // Body intentionally left blank
+ }
+ }
+
+ /// \brief Was the loading successful?
+ ///
+ /// \return true if and only if the loading was complete (after a call of
+ /// load or after loadIncremental returned true) and there was no
+ /// error. In other cases, return false.
+ /// \note While this method works even before the loading is complete (by
+ /// returning false in that case), it is meant to be called only after
+ /// finishing the load.
+ bool loadedSuccessfully() const;
+
+ /// \brief Return the total size of the zone files and streams.
+ ///
+ /// This method returns the size of the source of the zone to be loaded
+ /// (master zone files or streams) that is known at the time of the call.
+ /// For a zone file, it's the size of the file; for a stream, it's the
+ /// size of the data (in bytes) available at the start of the load.
+ /// If separate zone files are included via the $INCLUDE directive, the
+ /// sum of the sizes of these files are added.
+ ///
+ /// If the loader is constructed with a stream, the size can be
+ /// "unknown" as described for \c MasterLexer::getTotalSourceSize().
+ /// In this case this method always returns
+ /// \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// If the loader is constructed with a zone file, this method
+ /// initially returns 0. So until either \c load() or \c loadIncremental()
+ /// is called, the value is meaningless.
+ ///
+ /// Note that when the source includes separate files, this method
+ /// cannot take into account the included files that the loader has not
+ /// recognized at the time of call. So it's possible that this method
+ /// returns different values at different times of call.
+ ///
+ /// \throw None
+ size_t getSize() const;
+
+ /// \brief Return the position of the loader in zone.
+ ///
+ /// This method returns a conceptual "position" of the loader in the
+ /// zone to be loaded. Specifically, it returns the total number of
+ /// characters contained in the zone files and streams and recognized
+ /// by the loader. Before starting the load it returns 0; on successful
+ /// completion it will be equal to the return value of \c getSize()
+ /// (unless the latter returns \c MasterLexer::SOURCE_SIZE_UNKNOWN).
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
+private:
+ class MasterLoaderImpl;
+ std::unique_ptr<MasterLoaderImpl> impl_;
+};
+
+} // end namespace dns
+} // end namespace isc
+
+#endif // MASTER_LOADER_H
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
new file mode 100644
index 0000000..3f5be01
--- /dev/null
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MASTER_LOADER_CALLBACKS_H
+#define MASTER_LOADER_CALLBACKS_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace dns {
+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 RR.
+///
+/// This type of callback is used by the loader to report another loaded
+/// RR. The Rdata is no longer preserved by the loader and is fully
+/// owned by the callback.
+///
+/// \param name The domain name where the RR belongs.
+/// \param rrclass The class of the RR.
+/// \param rrtype Type of the RR.
+/// \param rrttl Time to live of the RR.
+/// \param rdata The actual carried data of the RR.
+typedef std::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.
+///
+/// This holds a set of callbacks by which a loader (such as MasterLoader)
+/// can report loaded RRsets, errors and other unusual conditions.
+///
+/// All the callbacks must be set.
+class MasterLoaderCallbacks {
+public:
+ /// \brief Type of one callback to report problems.
+ ///
+ /// This is the type of one callback used to report an unusual
+ /// condition or error.
+ ///
+ /// \param source_name The name of the source where the problem happened.
+ /// This is usually a file name.
+ /// \param source_line Position of the problem, counted in lines from the
+ /// beginning of the source.
+ /// \param reason Human readable description of what happened.
+ typedef std::function<void(const std::string& source_name,
+ size_t source_line,
+ const std::string& reason)> IssueCallback;
+
+ /// \brief Constructor
+ ///
+ /// Initializes the callbacks.
+ ///
+ /// \param error The error callback to use.
+ /// \param warning The warning callback to use.
+ /// \throw isc::InvalidParameter if any of the callbacks is empty.
+ MasterLoaderCallbacks(const IssueCallback& error,
+ const IssueCallback& warning) :
+ error_(error),
+ warning_(warning) {
+ if (!error_ || !warning_) {
+ isc_throw(isc::InvalidParameter,
+ "Empty function passed as callback");
+ }
+ }
+
+ /// \brief Call callback for serious errors
+ ///
+ /// This is called whenever there's a serious problem which makes the data
+ /// being loaded unusable. Further processing may or may not happen after
+ /// this (for example to detect further errors), but the data should not
+ /// be used.
+ ///
+ /// It calls whatever was passed to the error parameter to the constructor.
+ ///
+ /// If the caller of the loader wants to abort, it is possible to throw
+ /// from the callback, which aborts the load.
+ void error(const std::string& source_name, size_t source_line,
+ const std::string& reason) const {
+ error_(source_name, source_line, reason);
+ }
+
+ /// \brief Call callback for potential problems
+ ///
+ /// This is called whenever a minor problem is discovered. This might mean
+ /// the data is completely OK, it just looks suspicious.
+ ///
+ /// It calls whatever was passed to the warn parameter to the constructor.
+ ///
+ /// The loading will continue after the callback. If the caller wants to
+ /// abort (which is probably not a very good idea, since warnings
+ /// may be false positives), it is possible to throw from inside the
+ /// callback.
+ void warning(const std::string& source_name, size_t source_line,
+ const std::string& reason) const {
+ warning_(source_name, source_line, reason);
+ }
+private:
+ const IssueCallback error_;
+ const IssueCallback warning_;
+};
+
+}
+}
+
+#endif // MASTER_LOADER_CALLBACKS_H
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
new file mode 100644
index 0000000..913240c
--- /dev/null
+++ b/src/lib/dns/message.cc
@@ -0,0 +1,1167 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/question.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+#include <dns/tsig.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+#include <algorithm>
+#include <cassert>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+using namespace std;
+using boost::lexical_cast;
+
+namespace isc {
+namespace dns {
+
+namespace {
+// protocol constants
+const size_t HEADERLEN = 12;
+
+const unsigned int OPCODE_MASK = 0x7800;
+const unsigned int OPCODE_SHIFT = 11;
+const unsigned int RCODE_MASK = 0x000f;
+
+// This diagram shows the wire-format representation of the 2nd 16 bits of
+// the DNS header section, which contain all defined flag bits.
+//
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// |QR| Opcode |AA|TC|RD|RA| |AD|CD| RCODE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// 1 0 0 0| 0 1 1 1| 1 0 1 1| 0 0 0 0|
+// 0x8 0x7 0xb 0x0
+//
+// This mask covers all the flag bits, and those bits only.
+// Note: we reject a "flag" the is not covered by this mask in some of the
+// public methods. This means our current definition is not fully extendable;
+// applications cannot introduce a new flag bit temporarily without modifying
+// the source code.
+const unsigned int HEADERFLAG_MASK = 0x87b0;
+
+// This is a set of flag bits that should be preserved when building a reply
+// from a request.
+// Note: we assume the specific definition of HEADERFLAG_xx. We may change
+// the definition in future, in which case we need to adjust this definition,
+// too (see also the description about the Message::HeaderFlag type).
+const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD |
+ Message::HEADERFLAG_CD);
+
+const char* const sectiontext[] = {
+ "QUESTION",
+ "ANSWER",
+ "AUTHORITY",
+ "ADDITIONAL"
+};
+}
+
+class MessageImpl {
+public:
+ MessageImpl(Message::Mode mode);
+ // Open issues: should we rather have a header in wire-format
+ // for efficiency?
+ Message::Mode mode_;
+ qid_t qid_;
+
+ // We want to use null for [op,r]code_ to mean the code being not
+ // correctly parsed or set. We store the real code object in
+ // xxcode_placeholder_ and have xxcode_ refer to it when the object
+ // is valid.
+ const Rcode* rcode_;
+ Rcode rcode_placeholder_;
+ const Opcode* opcode_;
+ Opcode opcode_placeholder_;
+
+ uint16_t flags_; // wire-format representation of header flags.
+
+ bool header_parsed_;
+ static const unsigned int NUM_SECTIONS = 4; // TODO: revisit this design
+ int counts_[NUM_SECTIONS]; // TODO: revisit this definition
+ vector<QuestionPtr> questions_;
+ vector<RRsetPtr> rrsets_[NUM_SECTIONS];
+ ConstEDNSPtr edns_;
+ ConstTSIGRecordPtr tsig_rr_;
+
+ // RRsetsSorter* sorter_; : TODO
+
+ void init();
+ void setOpcode(const Opcode& opcode);
+ void setRcode(const Rcode& rcode);
+ int parseQuestion(InputBuffer& buffer);
+ int parseSection(const Message::Section section, InputBuffer& buffer,
+ Message::ParseOptions options);
+ void addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, ConstRdataPtr rdata,
+ Message::ParseOptions options);
+ // There are also times where an RR needs to be added that
+ // represents an empty RRset. There is no Rdata in that case
+ void addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, Message::ParseOptions options);
+ void addEDNS(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, const Rdata& rdata);
+ void addTSIG(Message::Section section, unsigned int count,
+ const InputBuffer& buffer, size_t start_position,
+ const Name& name, const RRClass& rrclass,
+ const RRTTL& ttl, const Rdata& rdata);
+ void toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx);
+};
+
+/// @brief Pointer to the @ref MessageImpl object.
+typedef boost::shared_ptr<MessageImpl> MessageImplPtr;
+
+MessageImpl::MessageImpl(Message::Mode mode) :
+ mode_(mode),
+ rcode_placeholder_(Rcode(0)), // for placeholders the value doesn't matter
+ opcode_placeholder_(Opcode(0)) {
+ init();
+}
+
+void
+MessageImpl::init() {
+ flags_ = 0;
+ qid_ = 0;
+ rcode_ = 0;
+ opcode_ = 0;
+ edns_ = EDNSPtr();
+ tsig_rr_ = ConstTSIGRecordPtr();
+
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ counts_[i] = 0;
+ }
+
+ header_parsed_ = false;
+ questions_.clear();
+ rrsets_[Message::SECTION_ANSWER].clear();
+ rrsets_[Message::SECTION_AUTHORITY].clear();
+ rrsets_[Message::SECTION_ADDITIONAL].clear();
+}
+
+void
+MessageImpl::setOpcode(const Opcode& opcode) {
+ opcode_placeholder_ = opcode;
+ opcode_ = &opcode_placeholder_;
+}
+
+void
+MessageImpl::setRcode(const Rcode& rcode) {
+ rcode_placeholder_ = rcode;
+ rcode_ = &rcode_placeholder_;
+}
+
+namespace {
+// This helper class is used by MessageImpl::toWire() to render a set of
+// RRsets of a specific section of message to a given MessageRenderer.
+//
+// A RenderSection object is expected to be used with a QuestionIterator or
+// SectionIterator. Its operator() is called for each RRset as the iterator
+// iterates over the corresponding section, and it renders the RRset to
+// the given MessageRenderer, while counting the number of RRs (note: not
+// RRsets) successfully rendered. If the MessageRenderer reports the need
+// for truncation (via its isTruncated() method), the RenderSection object
+// stops rendering further RRsets. In addition, unless partial_ok (given on
+// construction) is true, it removes any RRs that are partially rendered
+// from the MessageRenderer.
+//
+// On the completion of rendering the entire section, the owner of the
+// RenderSection object can get the number of rendered RRs via the
+// getTotalCount() method.
+template <typename T>
+struct RenderSection {
+ RenderSection(AbstractMessageRenderer& renderer, const bool partial_ok) :
+ counter_(0), renderer_(renderer), partial_ok_(partial_ok),
+ truncated_(false) {
+ }
+
+ void operator()(const T& entry) {
+ // If it's already truncated, ignore the rest of the section.
+ if (truncated_) {
+ return;
+ }
+ const size_t pos0 = renderer_.getLength();
+ counter_ += entry->toWire(renderer_);
+ if (renderer_.isTruncated()) {
+ truncated_ = true;
+ if (!partial_ok_) {
+ // roll back to the end of the previous RRset.
+ renderer_.trim(renderer_.getLength() - pos0);
+ }
+ }
+ }
+
+ unsigned int getTotalCount() {
+ return (counter_);
+ }
+
+ unsigned int counter_;
+
+ AbstractMessageRenderer& renderer_;
+
+ const bool partial_ok_;
+
+ bool truncated_;
+};
+}
+
+void
+MessageImpl::toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx) {
+ if (mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "Message rendering attempted in non render mode");
+ }
+ if (!rcode_) {
+ isc_throw(InvalidMessageOperation,
+ "Message rendering attempted without Rcode set");
+ }
+ if (!opcode_) {
+ isc_throw(InvalidMessageOperation,
+ "Message rendering attempted without Opcode set");
+ }
+
+ // Reserve the space for TSIG (if needed) so that we can handle truncation
+ // case correctly later when that happens. orig_xxx variables remember
+ // some configured parameters of renderer in case they are needed in
+ // truncation processing below.
+ const size_t tsig_len = (tsig_ctx ? tsig_ctx->getTSIGLength() : 0);
+ const size_t orig_msg_len_limit = renderer.getLengthLimit();
+ const AbstractMessageRenderer::CompressMode orig_compress_mode =
+ renderer.getCompressMode();
+
+ // We are going to skip soon, so we need to clear the renderer
+ // But we'll leave the length limit and the compress mode intact
+ // (or shortened in case of TSIG)
+ renderer.clear();
+ renderer.setCompressMode(orig_compress_mode);
+
+ if (tsig_len > 0) {
+ if (tsig_len > orig_msg_len_limit) {
+ isc_throw(InvalidParameter, "Failed to render DNS message: "
+ "too small limit for a TSIG (" <<
+ orig_msg_len_limit << ")");
+ }
+ renderer.setLengthLimit(orig_msg_len_limit - tsig_len);
+ } else {
+ renderer.setLengthLimit(orig_msg_len_limit);
+ }
+
+ // reserve room for the header
+ if (renderer.getLengthLimit() < HEADERLEN) {
+ isc_throw(InvalidParameter, "Failed to render DNS message: "
+ "too small limit for a Header");
+ }
+ renderer.skip(HEADERLEN);
+
+ uint16_t qdcount =
+ for_each(questions_.begin(), questions_.end(),
+ RenderSection<QuestionPtr>(renderer, false)).getTotalCount();
+
+ // TODO: sort RRsets in each section based on configuration policy.
+ uint16_t ancount = 0;
+ if (!renderer.isTruncated()) {
+ ancount =
+ for_each(rrsets_[Message::SECTION_ANSWER].begin(),
+ rrsets_[Message::SECTION_ANSWER].end(),
+ RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
+ }
+ uint16_t nscount = 0;
+ if (!renderer.isTruncated()) {
+ nscount =
+ for_each(rrsets_[Message::SECTION_AUTHORITY].begin(),
+ rrsets_[Message::SECTION_AUTHORITY].end(),
+ RenderSection<RRsetPtr>(renderer, true)).getTotalCount();
+ }
+ uint16_t arcount = 0;
+ if (renderer.isTruncated()) {
+ flags_ |= Message::HEADERFLAG_TC;
+ } else {
+ arcount =
+ for_each(rrsets_[Message::SECTION_ADDITIONAL].begin(),
+ rrsets_[Message::SECTION_ADDITIONAL].end(),
+ RenderSection<RRsetPtr>(renderer, false)).getTotalCount();
+ }
+
+ // Add EDNS OPT RR if necessary. Basically, we add it only when EDNS
+ // has been explicitly set. However, if the RCODE would require it and
+ // no EDNS has been set we generate a temporary local EDNS and use it.
+ if (!renderer.isTruncated()) {
+ ConstEDNSPtr local_edns = edns_;
+ if (!local_edns && rcode_->getExtendedCode() != 0) {
+ local_edns = ConstEDNSPtr(new EDNS());
+ }
+ if (local_edns) {
+ arcount += local_edns->toWire(renderer, rcode_->getExtendedCode());
+ }
+ }
+
+ // If we're adding a TSIG to a truncated message, clear all RRsets
+ // from the message except for the question before adding the TSIG.
+ // If even (some of) the question doesn't fit, don't include it.
+ if (tsig_ctx && renderer.isTruncated()) {
+ renderer.clear();
+ renderer.setLengthLimit(orig_msg_len_limit - tsig_len);
+ renderer.setCompressMode(orig_compress_mode);
+ renderer.skip(HEADERLEN);
+ qdcount = for_each(questions_.begin(), questions_.end(),
+ RenderSection<QuestionPtr>(renderer,
+ false)).getTotalCount();
+ ancount = 0;
+ nscount = 0;
+ arcount = 0;
+ }
+
+ // Adjust the counter buffer.
+ // XXX: these may not be equal to the number of corresponding entries
+ // in rrsets_[] or questions_ if truncation occurred or an EDNS OPT RR
+ // was inserted. This is not good, and we should revisit the entire
+ // design.
+ counts_[Message::SECTION_QUESTION] = qdcount;
+ counts_[Message::SECTION_ANSWER] = ancount;
+ counts_[Message::SECTION_AUTHORITY] = nscount;
+ counts_[Message::SECTION_ADDITIONAL] = arcount;
+
+ // fill in the header
+ size_t header_pos = 0;
+ renderer.writeUint16At(qid_, header_pos);
+ header_pos += sizeof(uint16_t);
+
+ uint16_t codes_and_flags =
+ (opcode_->getCode() << OPCODE_SHIFT) & OPCODE_MASK;
+ codes_and_flags |= (rcode_->getCode() & RCODE_MASK);
+ codes_and_flags |= (flags_ & HEADERFLAG_MASK);
+ renderer.writeUint16At(codes_and_flags, header_pos);
+ header_pos += sizeof(uint16_t);
+ // TODO: should avoid repeated pattern
+ renderer.writeUint16At(qdcount, header_pos);
+ header_pos += sizeof(uint16_t);
+ renderer.writeUint16At(ancount, header_pos);
+ header_pos += sizeof(uint16_t);
+ renderer.writeUint16At(nscount, header_pos);
+ header_pos += sizeof(uint16_t);
+ renderer.writeUint16At(arcount, header_pos);
+
+ // Add TSIG, if necessary, at the end of the message.
+ if (tsig_ctx) {
+ // Release the reserved space in the renderer.
+ renderer.setLengthLimit(orig_msg_len_limit);
+
+ const int tsig_count =
+ tsig_ctx->sign(qid_, renderer.getData(),
+ renderer.getLength())->toWire(renderer);
+ if (tsig_count != 1) {
+ isc_throw(Unexpected, "Failed to render a TSIG RR");
+ }
+
+ // update the ARCOUNT for the TSIG RR. Note that for a sane DNS
+ // message arcount should never overflow to 0.
+ renderer.writeUint16At(++arcount, header_pos);
+ }
+}
+
+Message::Message(Mode mode) :
+ impl_(new MessageImpl(mode)) {
+}
+
+bool
+Message::getHeaderFlag(const HeaderFlag flag) const {
+ if (flag == 0 || (flag & ~HEADERFLAG_MASK) != 0) {
+ isc_throw(InvalidParameter,
+ "Message::getHeaderFlag:: Invalid flag is specified: " <<
+ "0x" << std::hex << flag);
+ }
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Message::setHeaderFlag(const HeaderFlag flag, const bool on) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "setHeaderFlag performed in non-render mode");
+ }
+ if (flag == 0 || (flag & ~HEADERFLAG_MASK) != 0) {
+ isc_throw(InvalidParameter,
+ "Message::getHeaderFlag:: Invalid flag is specified: " <<
+ "0x" << std::hex << static_cast<int>(flag));
+ }
+ if (on) {
+ impl_->flags_ |= flag;
+ } else {
+ impl_->flags_ &= ~flag;
+ }
+}
+
+qid_t
+Message::getQid() const {
+ return (impl_->qid_);
+}
+
+void
+Message::setQid(qid_t qid) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "setQid performed in non-render mode");
+ }
+ impl_->qid_ = qid;
+}
+
+const Rcode&
+Message::getRcode() const {
+ if (!impl_->rcode_) {
+ isc_throw(InvalidMessageOperation, "getRcode attempted before set");
+ }
+ return (*impl_->rcode_);
+}
+
+void
+Message::setRcode(const Rcode& rcode) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "setRcode performed in non-render mode");
+ }
+ impl_->setRcode(rcode);
+}
+
+const Opcode&
+Message::getOpcode() const {
+ if (!impl_->opcode_) {
+ isc_throw(InvalidMessageOperation, "getOpcode attempted before set");
+ }
+ return (*impl_->opcode_);
+}
+
+void
+Message::setOpcode(const Opcode& opcode) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "setOpcode performed in non-render mode");
+ }
+ impl_->setOpcode(opcode);
+}
+
+ConstEDNSPtr
+Message::getEDNS() const {
+ return (impl_->edns_);
+}
+
+void
+Message::setEDNS(ConstEDNSPtr edns) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "setEDNS performed in non-render mode");
+ }
+ impl_->edns_ = edns;
+}
+
+const TSIGRecord*
+Message::getTSIGRecord() const {
+ if (impl_->mode_ != Message::PARSE) {
+ isc_throw(InvalidMessageOperation,
+ "getTSIGRecord performed in non-parse mode");
+ }
+
+ return (impl_->tsig_rr_.get());
+}
+
+unsigned int
+Message::getRRCount(const Section section) const {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+ return (impl_->counts_[section]);
+}
+
+void
+Message::addRRset(const Section section, RRsetPtr rrset) {
+ if (!rrset) {
+ isc_throw(InvalidParameter,
+ "null RRset is given to Message::addRRset");
+ }
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "addRRset performed in non-render mode");
+ }
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+
+ impl_->rrsets_[section].push_back(rrset);
+ impl_->counts_[section] += rrset->getRdataCount();
+ impl_->counts_[section] += rrset->getRRsigDataCount();
+}
+
+bool
+Message::hasRRset(const Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype) const {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+
+ for (auto const& r : impl_->rrsets_[section]) {
+ if (r->getClass() == rrclass &&
+ r->getType() == rrtype &&
+ r->getName() == name) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+bool
+Message::hasRRset(const Section section, const RRsetPtr& rrset) const {
+ return (hasRRset(section, rrset->getName(),
+ rrset->getClass(), rrset->getType()));
+}
+
+bool
+Message::removeRRset(const Section section, RRsetIterator& iterator) {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+
+ bool removed = false;
+ for (auto i = impl_->rrsets_[section].begin(); i != impl_->rrsets_[section].end(); ++i) {
+ if (((*i)->getName() == (*iterator)->getName()) &&
+ ((*i)->getClass() == (*iterator)->getClass()) &&
+ ((*i)->getType() == (*iterator)->getType())) {
+
+ // Found the matching RRset so remove it & ignore rest
+ impl_->counts_[section] -= (*iterator)->getRdataCount();
+ impl_->counts_[section] -= (*iterator)->getRRsigDataCount();
+ impl_->rrsets_[section].erase(i);
+ removed = true;
+ break;
+ }
+ }
+
+ return (removed);
+}
+
+void
+Message::clearSection(const Section section) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "clearSection performed in non-render mode");
+ }
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+ if (section == Message::SECTION_QUESTION) {
+ impl_->questions_.clear();
+ } else {
+ impl_->rrsets_[section].clear();
+ }
+ impl_->counts_[section] = 0;
+}
+
+void
+Message::addQuestion(const QuestionPtr question) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "addQuestion performed in non-render mode");
+ }
+
+ impl_->questions_.push_back(question);
+ ++impl_->counts_[SECTION_QUESTION];
+}
+
+void
+Message::addQuestion(const Question& question) {
+ addQuestion(QuestionPtr(new Question(question)));
+}
+
+void
+Message::toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx) {
+ impl_->toWire(renderer, tsig_ctx);
+}
+
+void
+Message::parseHeader(InputBuffer& buffer) {
+ if (impl_->mode_ != Message::PARSE) {
+ isc_throw(InvalidMessageOperation,
+ "Message parse attempted in non parse mode");
+ }
+
+ if (impl_->header_parsed_) {
+ return;
+ }
+
+ if ((buffer.getLength() - buffer.getPosition()) < HEADERLEN) {
+ isc_throw(MessageTooShort, "Malformed DNS message (short length): "
+ << buffer.getLength() - buffer.getPosition());
+ }
+
+ impl_->qid_ = buffer.readUint16();
+ const uint16_t codes_and_flags = buffer.readUint16();
+ impl_->setOpcode(Opcode((codes_and_flags & OPCODE_MASK) >> OPCODE_SHIFT));
+ impl_->setRcode(Rcode(codes_and_flags & RCODE_MASK));
+ impl_->flags_ = (codes_and_flags & HEADERFLAG_MASK);
+ impl_->counts_[SECTION_QUESTION] = buffer.readUint16();
+ impl_->counts_[SECTION_ANSWER] = buffer.readUint16();
+ impl_->counts_[SECTION_AUTHORITY] = buffer.readUint16();
+ impl_->counts_[SECTION_ADDITIONAL] = buffer.readUint16();
+
+ impl_->header_parsed_ = true;
+}
+
+void
+Message::fromWire(InputBuffer& buffer, ParseOptions options) {
+ if (impl_->mode_ != Message::PARSE) {
+ isc_throw(InvalidMessageOperation,
+ "Message parse attempted in non parse mode");
+ }
+
+ // Clear any old parsed data
+ clear(Message::PARSE);
+
+ buffer.setPosition(0);
+ parseHeader(buffer);
+
+ impl_->counts_[SECTION_QUESTION] = impl_->parseQuestion(buffer);
+ impl_->counts_[SECTION_ANSWER] =
+ impl_->parseSection(SECTION_ANSWER, buffer, options);
+ impl_->counts_[SECTION_AUTHORITY] =
+ impl_->parseSection(SECTION_AUTHORITY, buffer, options);
+ impl_->counts_[SECTION_ADDITIONAL] =
+ impl_->parseSection(SECTION_ADDITIONAL, buffer, options);
+}
+
+int
+MessageImpl::parseQuestion(InputBuffer& buffer) {
+ unsigned int added = 0;
+
+ for (unsigned int count = 0;
+ count < counts_[Message::SECTION_QUESTION];
+ ++count) {
+ const Name name(buffer);
+
+ if ((buffer.getLength() - buffer.getPosition()) <
+ 2 * sizeof(uint16_t)) {
+ isc_throw(DNSMessageFORMERR, "Question section too short: " <<
+ (buffer.getLength() - buffer.getPosition()) << " bytes");
+ }
+ const RRType rrtype(buffer.readUint16());
+ const RRClass rrclass(buffer.readUint16());
+
+ // XXX: need a duplicate check. We might also want to have an
+ // optimized algorithm that requires the question section contain
+ // exactly one RR.
+
+ questions_.push_back(QuestionPtr(new Question(name, rrclass, rrtype)));
+ ++added;
+ }
+
+ return (added);
+}
+
+namespace {
+struct MatchRR {
+ MatchRR(const Name& name, const RRType& rrtype, const RRClass& rrclass) :
+ name_(name), rrtype_(rrtype), rrclass_(rrclass) {
+ }
+
+ bool operator()(const RRsetPtr& rrset) const {
+ return (rrset->getType() == rrtype_ &&
+ rrset->getClass() == rrclass_ &&
+ rrset->getName() == name_);
+ }
+
+ const Name& name_;
+
+ const RRType& rrtype_;
+
+ const RRClass& rrclass_;
+};
+}
+
+// Note about design decision:
+// we need some type specific processing here, including EDNS and TSIG.
+// how much we should generalize/hardcode the special logic is subject
+// to discussion. In terms of modularity it would be ideal to introduce
+// an abstract class (say "MessageAttribute") and let other such
+// concrete notions as EDNS or TSIG inherit from it. Then we would
+// just do:
+// message->addAttribute(rrtype, rrclass, buffer);
+// to create and attach type-specific concrete object to the message.
+//
+// A major downside of this approach is, as usual, complexity due to
+// indirection and performance penalty. Also, it may not be so easy
+// to separate the processing logic because in many cases we'll need
+// parse context for which the message class is responsible (e.g.
+// to check the EDNS OPT RR only appears in the additional section,
+// and appears only once).
+//
+// Another point to consider is that we may not need so many special
+// types other than EDNS and TSIG (and when and if we implement it,
+// SIG(0)); newer optional attributes of the message would more likely
+// be standardized as new flags or options of EDNS. If that's the case,
+// introducing an abstract class with all the overhead and complexity
+// may not make much sense.
+//
+// Conclusion: don't over-generalize type-specific logic for now.
+// introduce separate concrete classes, and move context-independent
+// logic to that class; processing logic dependent on parse context
+// is hardcoded here.
+int
+MessageImpl::parseSection(const Message::Section section,
+ InputBuffer& buffer, Message::ParseOptions options) {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+
+ unsigned int added = 0;
+
+ for (unsigned int count = 0; count < counts_[section]; ++count) {
+ // We need to remember the start position for TSIG processing
+ const size_t start_position = buffer.getPosition();
+
+ const Name name(buffer);
+
+ // buffer must store at least RR TYPE, RR CLASS, TTL, and RDLEN.
+ if ((buffer.getLength() - buffer.getPosition()) <
+ 3 * sizeof(uint16_t) + sizeof(uint32_t)) {
+ isc_throw(DNSMessageFORMERR, sectiontext[section] <<
+ " section too short: " <<
+ (buffer.getLength() - buffer.getPosition()) << " bytes");
+ }
+
+ const RRType rrtype(buffer.readUint16());
+ const RRClass rrclass(buffer.readUint16());
+ const RRTTL ttl(buffer.readUint32());
+ const size_t rdlen = buffer.readUint16();
+
+ // If class is ANY or NONE, rdlength may be zero, to signal
+ // an empty RRset.
+ // (the class check must be done to differentiate from RRTypes
+ // that can have zero length rdata
+ if ((rrclass == RRClass::ANY() || rrclass == RRClass::NONE()) &&
+ rdlen == 0) {
+ addRR(section, name, rrclass, rrtype, ttl, options);
+ ++added;
+ continue;
+ }
+ ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
+
+ if (rrtype == RRType::OPT()) {
+ addEDNS(section, name, rrclass, rrtype, ttl, *rdata);
+ } else if (rrtype == RRType::TSIG()) {
+ addTSIG(section, count, buffer, start_position, name, rrclass, ttl,
+ *rdata);
+ } else {
+ addRR(section, name, rrclass, rrtype, ttl, rdata, options);
+ ++added;
+ }
+ }
+
+ return (added);
+}
+
+void
+MessageImpl::addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, ConstRdataPtr rdata,
+ Message::ParseOptions options) {
+ if ((options & Message::PRESERVE_ORDER) == 0) {
+ vector<RRsetPtr>::iterator it =
+ find_if(rrsets_[section].begin(), rrsets_[section].end(),
+ MatchRR(name, rrtype, rrclass));
+ if (it != rrsets_[section].end()) {
+ (*it)->setTTL(min((*it)->getTTL(), ttl));
+ (*it)->addRdata(rdata);
+ return;
+ }
+ }
+ RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl));
+ rrset->addRdata(rdata);
+ rrsets_[section].push_back(rrset);
+}
+
+void
+MessageImpl::addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, Message::ParseOptions options) {
+ if ((options & Message::PRESERVE_ORDER) == 0) {
+ vector<RRsetPtr>::iterator it =
+ find_if(rrsets_[section].begin(), rrsets_[section].end(),
+ MatchRR(name, rrtype, rrclass));
+ if (it != rrsets_[section].end()) {
+ (*it)->setTTL(min((*it)->getTTL(), ttl));
+ return;
+ }
+ }
+ RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl));
+ rrsets_[section].push_back(rrset);
+}
+
+void
+MessageImpl::addEDNS(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, const Rdata& rdata) {
+ if (section != Message::SECTION_ADDITIONAL) {
+ isc_throw(DNSMessageFORMERR,
+ "EDNS OPT RR found in an invalid section");
+ }
+ if (edns_) {
+ isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found");
+ }
+
+ uint8_t extended_rcode;
+ edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl, rdata,
+ extended_rcode));
+ setRcode(Rcode(rcode_->getCode(), extended_rcode));
+}
+
+void
+MessageImpl::addTSIG(Message::Section section, unsigned int count,
+ const InputBuffer& buffer, size_t start_position,
+ const Name& name, const RRClass& rrclass,
+ const RRTTL& ttl, const Rdata& rdata) {
+ if (section != Message::SECTION_ADDITIONAL) {
+ isc_throw(DNSMessageFORMERR,
+ "TSIG RR found in an invalid section");
+ }
+ if (count != counts_[section] - 1) {
+ isc_throw(DNSMessageFORMERR, "TSIG RR is not the last record");
+ }
+ // This check will never fail as the multiple TSIG RR case is
+ // caught before by the not the last record check...
+ if (tsig_rr_) {
+ isc_throw(DNSMessageFORMERR, "multiple TSIG RRs found");
+ }
+ tsig_rr_ = ConstTSIGRecordPtr(new TSIGRecord(name, rrclass,
+ ttl, rdata,
+ buffer.getPosition() -
+ start_position));
+}
+
+namespace {
+template <typename T>
+struct SectionFormatter {
+ SectionFormatter(const Message::Section section, string& output) :
+ section_(section), output_(output) {
+ }
+
+ void operator()(const T& entry) {
+ if (section_ == Message::SECTION_QUESTION) {
+ output_ += ";";
+ output_ += entry->toText();
+ output_ += "\n";
+ } else {
+ output_ += entry->toText();
+ }
+ }
+
+ const Message::Section section_;
+
+ string& output_;
+};
+}
+
+string
+Message::toText() const {
+ if (!impl_->rcode_) {
+ isc_throw(InvalidMessageOperation,
+ "Message::toText() attempted without Rcode set");
+ }
+ if (!impl_->opcode_) {
+ isc_throw(InvalidMessageOperation,
+ "Message::toText() attempted without Opcode set");
+ }
+
+ string s;
+
+ s += ";; ->>HEADER<<- opcode: " + impl_->opcode_->toText();
+ // for simplicity we don't consider extended rcode (unlike BIND9)
+ s += ", status: " + impl_->rcode_->toText();
+ s += ", id: " + boost::lexical_cast<string>(impl_->qid_);
+ s += "\n;; flags:";
+ if (getHeaderFlag(HEADERFLAG_QR)) {
+ s += " qr";
+ }
+ if (getHeaderFlag(HEADERFLAG_AA)) {
+ s += " aa";
+ }
+ if (getHeaderFlag(HEADERFLAG_TC)) {
+ s += " tc";
+ }
+ if (getHeaderFlag(HEADERFLAG_RD)) {
+ s += " rd";
+ }
+ if (getHeaderFlag(HEADERFLAG_RA)) {
+ s += " ra";
+ }
+ if (getHeaderFlag(HEADERFLAG_AD)) {
+ s += " ad";
+ }
+ if (getHeaderFlag(HEADERFLAG_CD)) {
+ s += " cd";
+ }
+
+ // for simplicity, don't consider the update case for now
+ s += "; QUERY: " + // note: not "QUESTION" to be compatible with BIND 9 dig
+ lexical_cast<string>(impl_->counts_[SECTION_QUESTION]);
+ s += ", ANSWER: " +
+ lexical_cast<string>(impl_->counts_[SECTION_ANSWER]);
+ s += ", AUTHORITY: " +
+ lexical_cast<string>(impl_->counts_[SECTION_AUTHORITY]);
+
+ unsigned int arcount = impl_->counts_[SECTION_ADDITIONAL];
+ if (impl_->edns_) {
+ ++arcount;
+ }
+ if (impl_->tsig_rr_) {
+ ++arcount;
+ }
+ s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
+
+ if (impl_->edns_) {
+ s += "\n;; OPT PSEUDOSECTION:\n";
+ s += impl_->edns_->toText();
+ }
+
+ if (!impl_->questions_.empty()) {
+ s += "\n;; " +
+ string(sectiontext[SECTION_QUESTION]) + " SECTION:\n";
+ for_each(impl_->questions_.begin(), impl_->questions_.end(),
+ SectionFormatter<QuestionPtr>(SECTION_QUESTION, s));
+ }
+ if (!impl_->rrsets_[SECTION_ANSWER].empty()) {
+ s += "\n;; " +
+ string(sectiontext[SECTION_ANSWER]) + " SECTION:\n";
+ for_each(impl_->rrsets_[SECTION_ANSWER].begin(),
+ impl_->rrsets_[SECTION_ANSWER].end(),
+ SectionFormatter<RRsetPtr>(SECTION_ANSWER, s));
+ }
+ if (!impl_->rrsets_[SECTION_AUTHORITY].empty()) {
+ s += "\n;; " +
+ string(sectiontext[SECTION_AUTHORITY]) + " SECTION:\n";
+ for_each(impl_->rrsets_[SECTION_AUTHORITY].begin(),
+ impl_->rrsets_[SECTION_AUTHORITY].end(),
+ SectionFormatter<RRsetPtr>(SECTION_AUTHORITY, s));
+ }
+ if (!impl_->rrsets_[SECTION_ADDITIONAL].empty()) {
+ s += "\n;; " +
+ string(sectiontext[SECTION_ADDITIONAL]) +
+ " SECTION:\n";
+ for_each(impl_->rrsets_[SECTION_ADDITIONAL].begin(),
+ impl_->rrsets_[SECTION_ADDITIONAL].end(),
+ SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s));
+ }
+
+ if (impl_->tsig_rr_) {
+ s += "\n;; TSIG PSEUDOSECTION:\n";
+ s += impl_->tsig_rr_->toText();
+ }
+
+ return (s);
+}
+
+void
+Message::clear(Mode mode) {
+ impl_->init();
+ impl_->mode_ = mode;
+}
+
+void
+Message::appendSection(const Section section, const Message& source) {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+
+ if (section == SECTION_QUESTION) {
+ for (auto qi = source.beginQuestion(); qi != source.endQuestion(); ++qi) {
+ addQuestion(*qi);
+ }
+ } else {
+ for (auto rrsi = source.beginSection(section); rrsi != source.endSection(section); ++rrsi) {
+ addRRset(section, *rrsi);
+ }
+ }
+}
+
+void
+Message::makeResponse() {
+ if (impl_->mode_ != Message::PARSE) {
+ isc_throw(InvalidMessageOperation,
+ "makeResponse() is performed in non-parse mode");
+ }
+
+ impl_->mode_ = Message::RENDER;
+
+ impl_->edns_ = EDNSPtr();
+ impl_->flags_ &= MESSAGE_REPLYPRESERVE;
+ setHeaderFlag(HEADERFLAG_QR, true);
+
+ impl_->rrsets_[SECTION_ANSWER].clear();
+ impl_->counts_[SECTION_ANSWER] = 0;
+ impl_->rrsets_[SECTION_AUTHORITY].clear();
+ impl_->counts_[SECTION_AUTHORITY] = 0;
+ impl_->rrsets_[SECTION_ADDITIONAL].clear();
+ impl_->counts_[SECTION_ADDITIONAL] = 0;
+}
+
+///
+/// Template version of Section Iterator
+///
+template <typename T>
+struct SectionIteratorImpl {
+ SectionIteratorImpl(const typename vector<T>::const_iterator& it) :
+ it_(it) {
+ }
+
+ typename vector<T>::const_iterator it_;
+};
+
+template <typename T>
+SectionIterator<T>::SectionIterator(const SectionIteratorImpl<T>& impl) {
+ impl_ = new SectionIteratorImpl<T>(impl.it_);
+}
+
+template <typename T>
+SectionIterator<T>::~SectionIterator() {
+ delete impl_;
+}
+
+template <typename T>
+SectionIterator<T>::SectionIterator(const SectionIterator<T>& source) :
+ impl_(new SectionIteratorImpl<T>(source.impl_->it_)) {
+}
+
+template <typename T>
+void
+SectionIterator<T>::operator=(const SectionIterator<T>& source) {
+ if (impl_ == source.impl_) {
+ return;
+ }
+ SectionIteratorImpl<T>* newimpl =
+ new SectionIteratorImpl<T>(source.impl_->it_);
+ delete impl_;
+ impl_ = newimpl;
+}
+
+template <typename T>
+SectionIterator<T>&
+SectionIterator<T>::operator++() {
+ ++(impl_->it_);
+ return (*this);
+}
+
+template <typename T>
+SectionIterator<T>
+SectionIterator<T>::operator++(int) {
+ SectionIterator<T> tmp(*this);
+ ++(*this);
+ return (tmp);
+}
+
+template <typename T>
+const T&
+SectionIterator<T>::operator*() const {
+ return (*(impl_->it_));
+}
+
+template <typename T>
+const T*
+SectionIterator<T>::operator->() const {
+ return (&(operator*()));
+}
+
+template <typename T>
+bool
+SectionIterator<T>::operator==(const SectionIterator<T>& other) const {
+ return (impl_->it_ == other.impl_->it_);
+}
+
+template <typename T>
+bool
+SectionIterator<T>::operator!=(const SectionIterator<T>& other) const {
+ return (impl_->it_ != other.impl_->it_);
+}
+
+///
+/// We need to explicitly instantiate these template classes because these
+/// are public classes but defined in this implementation file.
+///
+template class SectionIterator<QuestionPtr>;
+template class SectionIterator<RRsetPtr>;
+
+namespace {
+typedef SectionIteratorImpl<QuestionPtr> QuestionIteratorImpl;
+typedef SectionIteratorImpl<RRsetPtr> RRsetIteratorImpl;
+}
+
+///
+/// Question iterator
+///
+const QuestionIterator
+Message::beginQuestion() const {
+ return (QuestionIterator(QuestionIteratorImpl(impl_->questions_.begin())));
+}
+
+const QuestionIterator
+Message::endQuestion() const {
+ return (QuestionIterator(QuestionIteratorImpl(impl_->questions_.end())));
+}
+
+///
+/// RRsets iterators
+///
+const SectionIterator<RRsetPtr>
+Message::beginSection(const Section section) const {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+ if (section == SECTION_QUESTION) {
+ isc_throw(InvalidMessageSection,
+ "RRset iterator is requested for question");
+ }
+
+ return (RRsetIterator(RRsetIteratorImpl(impl_->rrsets_[section].begin())));
+}
+
+const SectionIterator<RRsetPtr>
+Message::endSection(const Section section) const {
+ if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) {
+ isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section));
+ }
+ if (section == SECTION_QUESTION) {
+ isc_throw(InvalidMessageSection,
+ "RRset iterator is requested for question");
+ }
+
+ return (RRsetIterator(RRsetIteratorImpl(impl_->rrsets_[section].end())));
+}
+
+ostream&
+operator<<(ostream& os, const Message& message) {
+ return (os << message.toText());
+}
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
new file mode 100644
index 0000000..be5d0af
--- /dev/null
+++ b/src/lib/dns/message.h
@@ -0,0 +1,688 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MESSAGE_H
+#define MESSAGE_H
+
+#include <stdint.h>
+
+#include <iterator>
+#include <string>
+#include <ostream>
+
+#include <dns/exceptions.h>
+#include <util/buffer.h>
+
+#include <dns/edns.h>
+#include <dns/question.h>
+#include <dns/rrset.h>
+
+namespace isc {
+namespace dns {
+class TSIGContext;
+class TSIGRecord;
+
+///
+/// \brief A standard DNS module exception that is thrown if a wire format
+/// message parser encounters a short length of data that don't even contain
+/// the full header section.
+///
+class MessageTooShort : public isc::dns::Exception {
+public:
+ MessageTooShort(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if a section iterator
+/// is being constructed for an incompatible section. Specifically, this
+/// happens RRset iterator is being constructed for a Question section.
+///
+class InvalidMessageSection : public isc::dns::Exception {
+public:
+ InvalidMessageSection(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if a \c Message
+/// class method is called that is prohibited for the current mode of
+/// the message.
+///
+class InvalidMessageOperation : public isc::dns::Exception {
+public:
+ InvalidMessageOperation(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if a UDP buffer size
+/// smaller than the standard default maximum (DEFAULT_MAX_UDPSIZE) is
+/// being specified for the message.
+///
+class InvalidMessageUDPSize : public isc::dns::Exception {
+public:
+ InvalidMessageUDPSize(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+typedef uint16_t qid_t;
+
+class AbstractMessageRenderer;
+class Message;
+class MessageImpl;
+class Opcode;
+class Rcode;
+
+template <typename T>
+struct SectionIteratorImpl;
+
+/// \c SectionIterator is a templated class to provide standard-compatible
+/// iterators for Questions and RRsets for a given DNS message section.
+/// The template parameter is either \c QuestionPtr (for the question section)
+/// or \c RRsetPtr (for the answer, authority, or additional section).
+template <typename T>
+class SectionIterator {
+public:
+ // Aliases used to enable iterator behavior on this class
+ using iterator_category = std::input_iterator_tag;
+ using value_type = T;
+ using difference_type = std::ptrdiff_t;
+ using pointer = T*;
+ using reference = T&;
+
+ SectionIterator() : impl_(0) {
+ }
+ SectionIterator(const SectionIteratorImpl<T>& impl);
+ ~SectionIterator();
+ SectionIterator(const SectionIterator<T>& source);
+ void operator=(const SectionIterator<T>& source);
+ SectionIterator<T>& operator++();
+ SectionIterator<T> operator++(int);
+ const T& operator*() const;
+ const T* operator->() const;
+ bool operator==(const SectionIterator<T>& other) const;
+ bool operator!=(const SectionIterator<T>& other) const;
+private:
+ SectionIteratorImpl<T>* impl_;
+};
+
+typedef SectionIterator<QuestionPtr> QuestionIterator;
+typedef SectionIterator<RRsetPtr> RRsetIterator;
+
+class MessageImpl;
+/// @brief Pointer to the @ref MessageImpl object.
+typedef boost::shared_ptr<MessageImpl> MessageImplPtr;
+
+/// \brief The \c Message class encapsulates a standard DNS message.
+///
+/// Details of the design and interfaces of this class are still in flux.
+/// Here are some notes about the current design.
+///
+/// Since many realistic DNS applications deal with messages, message objects
+/// will be frequently used, and can be performance sensitive. To minimize
+/// the performance overhead of constructing and destructing the objects,
+/// this class is designed to be reusable. The \c clear() method is provided
+/// for this purpose.
+///
+/// A \c Message class object is in either the \c PARSE or the \c RENDER mode.
+/// A \c PARSE mode object is intended to be used to convert wire-format
+/// message data into a complete \c Message object.
+/// A \c RENDER mode object is intended to be used to convert a \c Message
+/// object into wire-format data.
+/// Some of the method functions of this class are limited to a specific mode.
+/// In general, "set" type operations are only allowed for \c RENDER mode
+/// objects.
+/// The initial mode must be specified on construction, and can be changed
+/// through some method functions.
+///
+/// This class uses the "pimpl" idiom, and hides detailed implementation
+/// through the \c impl_ pointer. Since a \c Message object is expected to
+/// be reused, the construction overhead of this approach should be acceptable.
+///
+/// Open issues (among other things):
+/// - We may want to provide an "iterator" for all RRsets/RRs for convenience.
+/// This will be for applications that do not care about performance much,
+/// so the implementation can only be moderately efficient.
+/// - We may want to provide a "find" method for a specified type
+/// of RR in the message.
+class Message {
+public:
+ /// Constants to specify the operation mode of the \c Message.
+ enum Mode {
+ PARSE = 0, ///< Parse mode (handling an incoming message)
+ RENDER = 1 ///< Render mode (building an outgoing message)
+ };
+
+ /// \brief Constants for flag bit fields of a DNS message header.
+ ///
+ /// Only the defined constants are valid where a header flag is required
+ /// in this library (e.g., in \c Message::setHeaderFlag()).
+ /// Since these are enum constants, however, an invalid value could be
+ /// passed via casting without an error at compilation time.
+ /// It is generally the callee's responsibility to check and reject invalid
+ /// values.
+ /// Of course, applications shouldn't pass invalid values even if the
+ /// callee does not perform proper validation; the result in such usage
+ /// is undefined.
+ ///
+ /// In the current implementation, the defined values happen to be
+ /// a 16-bit integer with one bit being set corresponding to the
+ /// specified flag in the second 16 bits of the DNS Header section
+ /// in order to make the internal implementation simpler.
+ /// For example, \c HEADERFLAG_QR is defined to be 0x8000 as the QR
+ /// bit is the most significant bit of the second 16 bits of the header.
+ /// However, applications should not assume this coincidence and
+ /// must solely use the enum representations.
+ /// Any usage based on the assumption of the underlying values is invalid
+ /// and the result is undefined.
+ ///
+ /// Likewise, bit wise operations such as AND or OR on the flag values
+ /// are invalid and are not guaranteed to work, even if it could compile
+ /// with casting.
+ /// For example, the following code will compile:
+ /// \code const uint16_t combined_flags =
+ /// static_cast<uint16_t>(Message::HEADERFLAG_AA) |
+ /// static_cast<uint16_t>(Message::HEADERFLAG_CD);
+ /// message->setHeaderFlag(static_cast<Message::HeaderFlag>(combined_flags));
+ /// \endcode
+ /// and (with the current definition) happens to work as if it were
+ /// validly written as follows:
+ /// \code message->setHeaderFlag(Message::HEADERFLAG_AA);
+ /// message->setHeaderFlag(Message::HEADERFLAG_CD);
+ /// \endcode
+ /// But the former notation is invalid and may not work in future versions.
+ /// We did not try to prohibit such usage at compilation time, e.g., by
+ /// introducing a separately defined class considering the balance
+ /// between the complexity and advantage, but hopefully the cast notation
+ /// is sufficiently ugly to prevent proliferation of the usage.
+ enum HeaderFlag {
+ HEADERFLAG_QR = 0x8000, ///< Query (if cleared) or response (if set)
+ HEADERFLAG_AA = 0x0400, ///< Authoritative answer
+ HEADERFLAG_TC = 0x0200, ///< Truncation
+ HEADERFLAG_RD = 0x0100, ///< Recursion desired
+ HEADERFLAG_RA = 0x0080, ///< Recursion available
+ HEADERFLAG_AD = 0x0020, ///< Authentic %data (RFC4035)
+ HEADERFLAG_CD = 0x0010 ///< DNSSEC checking disabled (RFC4035)
+ };
+
+ /// \brief Constants to specify sections of a DNS message.
+ ///
+ /// The sections are those defined in RFC 1035 excluding the Header
+ /// section; the fields of the Header section are accessed via specific
+ /// methods of the \c Message class (e.g., \c getQid()).
+ ///
+ /// <b>Open Design Issue:</b>
+ /// In the current implementation the values for the constants are
+ /// sorted in the order of appearance in DNS messages, i.e.,
+ /// from %Question to Additional.
+ /// So, for example,
+ /// code <code>section >= Message::SECTION_AUTHORITY</code> can be
+ /// used to do something in or after the Authority section.
+ /// This would be convenient, but it is not clear if it's really a good
+ /// idea to rely on relationship between the underlying values of enum
+ /// constants. At the moment, applications are discouraged to rely on
+ /// this implementation detail. We will see if such usage is sufficiently
+ /// common to officially support it.
+ ///
+ /// Note also that since we don't define \c operator++ for this enum,
+ /// the following code intending to iterate over all sections will
+ /// \b not compile:
+ /// \code for (Section s; s <= SECTION_ADDITIONAL; ++s) { // ++s undefined
+ /// // do something
+ /// } \endcode
+ /// This is intentional at this moment, and we'll see if we need to allow
+ /// that as we have more experiences with this library.
+ ///
+ /// <b>Future Extension:</b> We'll probably also define constants for
+ /// the section names used in dynamic updates in future versions.
+ enum Section {
+ SECTION_QUESTION = 0, ///< %Question section
+ SECTION_ANSWER = 1, ///< Answer section
+ SECTION_AUTHORITY = 2, ///< Authority section
+ SECTION_ADDITIONAL = 3 ///< Additional section
+ };
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private.
+ /// The intended use case wouldn't require copies of a \c Message object;
+ /// once created, it would normally be expected to be reused, changing the
+ /// mode from \c PARSE to \c RENDER, and vice versa.
+ //@{
+public:
+ /// \brief The constructor.
+ /// The mode of the message is specified by the \c mode parameter.
+ Message(Mode mode);
+ /// \brief The destructor.
+ ~Message() = default;
+private:
+ Message(const Message& source);
+ Message& operator=(const Message& source);
+ //@}
+public:
+ /// \brief Return whether the specified header flag bit is set in the
+ /// header section.
+ ///
+ /// This method is basically exception free, but if
+ /// \c flag is not a valid constant of the \c HeaderFlag type,
+ /// an exception of class \c InvalidParameter will be thrown.
+ ///
+ /// \param flag The header flag constant to test.
+ /// \return \c true if the specified flag is set; otherwise \c false.
+ bool getHeaderFlag(const HeaderFlag flag) const;
+
+ /// \brief Set or clear the specified header flag bit in the header
+ /// section.
+ ///
+ /// The optional parameter \c on indicates the operation mode,
+ /// set or clear; if it's \c true the corresponding flag will be set;
+ /// otherwise the flag will be cleared.
+ /// In either case the original state of the flag does not affect the
+ /// operation; for example, if a flag is already set and the "set"
+ /// operation is attempted, it effectively results in no operation.
+ ///
+ /// The parameter \c on can be omitted, in which case a value of \c true
+ /// (i.e., set operation) will be assumed.
+ /// This is based on the observation that the flag would have to be set
+ /// in the vast majority of the cases where an application needs to
+ /// use this method.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ ///
+ /// If \c flag is not a valid constant of the \c HeaderFlag type,
+ /// an exception of class \c InvalidParameter will be thrown.
+ ///
+ /// \param flag The header flag constant to set or clear.
+ /// \param on If \c true the flag will be set; otherwise the flag will be
+ /// cleared.
+ void setHeaderFlag(const HeaderFlag flag, const bool on = true);
+
+ /// \brief Return the query ID given in the header section of the message.
+ qid_t getQid() const;
+
+ /// \brief Set the query ID of the header section of the message.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ void setQid(qid_t qid);
+
+ /// \brief Return the Response Code of the message.
+ ///
+ /// This includes extended codes specified by an EDNS OPT RR (when
+ /// included). In the \c PARSE mode, if the received message contains
+ /// an EDNS OPT RR, the corresponding extended code is identified and
+ /// returned.
+ ///
+ /// The message must have been properly parsed (in the case of the
+ /// \c PARSE mode) or an \c Rcode has been set (in the case of the
+ /// \c RENDER mode) beforehand. Otherwise, an exception of class
+ /// \c InvalidMessageOperation will be thrown.
+ const Rcode& getRcode() const;
+
+ /// \brief Set the Response Code of the message.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ ///
+ /// If the specified code is an EDNS extended RCODE, an EDNS OPT RR will be
+ /// included in the message.
+ void setRcode(const Rcode& rcode);
+
+ /// \brief Return the OPCODE given in the header section of the message.
+ ///
+ /// The message must have been properly parsed (in the case of the
+ /// \c PARSE mode) or an \c Opcode has been set (in the case of the
+ /// \c RENDER mode) beforehand. Otherwise, an exception of class
+ /// \c InvalidMessageOperation will be thrown.
+ const Opcode& getOpcode() const;
+
+ /// \brief Set the OPCODE of the header section of the message.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ void setOpcode(const Opcode& opcode);
+
+ /// \brief Return, if any, the EDNS associated with the message.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A shared pointer to the EDNS. This will be a null shared
+ /// pointer if the message is not associated with EDNS.
+ ConstEDNSPtr getEDNS() const;
+
+ /// \brief Set EDNS for the message.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ ///
+ /// \param edns A shared pointer to an \c EDNS object to be set in
+ /// \c Message.
+ void setEDNS(ConstEDNSPtr edns);
+
+ /// \brief Return, if any, the TSIG record contained in the received
+ /// message.
+ ///
+ /// Currently, this method is only intended to return a TSIG record
+ /// for an incoming message built via the \c fromWire() method in the
+ /// PARSE mode. A call to this method in the RENDER mode is invalid and
+ /// result in an exception. Also, calling this method is meaningless
+ /// unless \c fromWire() is performed.
+ ///
+ /// The returned pointer is valid only during the lifetime of the
+ /// \c Message object and until \c clear() is called. The \c Message
+ /// object retains the ownership of \c TSIGRecord; the caller must not
+ /// try to delete it.
+ ///
+ /// \exception InvalidMessageOperation Message is not in the PARSE mode.
+ ///
+ /// \return A pointer to the stored \c TSIGRecord or null.
+ const TSIGRecord* getTSIGRecord() const;
+
+ /// \brief Returns the number of RRs contained in the given section.
+ ///
+ /// In the \c PARSE mode, the returned value may not be identical to
+ /// the actual number of RRs of the incoming message that is parsed.
+ /// The \c Message class handles some "meta" RRs such as EDNS OPT RR
+ /// separately. This method doesn't include such RRs.
+ /// Also, a future version of the parser will detect and unify duplicate
+ /// RRs (which should be rare in practice though), in which case
+ /// the stored RRs in the \c Message object will be fewer than the RRs
+ /// originally contained in the incoming message.
+ ///
+ /// Likewise, in the \c RENDER mode, even if \c EDNS is set in the
+ /// \c Message, this method doesn't count the corresponding OPT RR
+ /// in the Additional section.
+ ///
+ /// This method is basically exception free, but if
+ /// \c section is not a valid constant of the \c Section type,
+ /// an exception of class \c OutOfRange will be thrown.
+ ///
+ /// \param section The section in the message where RRs should be
+ /// counted.
+ /// \return The number of RRs stored in the specified section of the
+ /// message.
+ unsigned int getRRCount(const Section section) const;
+
+ /// \brief Return an iterator corresponding to the beginning of the
+ /// Question section of the message.
+ const QuestionIterator beginQuestion() const;
+
+ /// \brief Return an iterator corresponding to the end of the
+ /// Question section of the message.
+ const QuestionIterator endQuestion() const;
+
+ /// \brief Return an iterator corresponding to the beginning of the
+ /// given section (other than Question) of the message.
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ const RRsetIterator beginSection(const Section section) const;
+
+ /// \brief Return an iterator corresponding to the end of the
+ /// given section (other than Question) of the message.
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ const RRsetIterator endSection(const Section section) const;
+
+ /// \brief Add a (pointer like object of) Question to the message.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ void addQuestion(QuestionPtr question);
+
+ /// \brief Add a (pointer like object of) Question to the message.
+ ///
+ /// This version internally creates a \c QuestionPtr object from the
+ /// given \c question and calls the other version of this method.
+ /// So this is inherently less efficient, but is provided because this
+ /// form may be more intuitive and may make more sense for performance
+ /// insensitive applications.
+ ///
+ /// This method is only allowed in the \c RENDER mode;
+ /// if the \c Message is in other mode, an exception of class
+ /// InvalidMessageOperation will be thrown.
+ void addQuestion(const Question& question);
+
+ /// \brief Add a (pointer like object of) RRset to the given section
+ /// of the message.
+ ///
+ /// Note that \c addRRset() does not currently check for duplicate
+ /// data before inserting RRsets. The caller is responsible for
+ /// checking for these (see \c hasRRset() below).
+ ///
+ /// \throw InvalidParameter rrset is null
+ /// \throw InvalidMessageOperation The message is not in the \c RENDER
+ /// mode.
+ /// \throw OutOfRange \c section doesn't specify a valid \c Section value.
+ ///
+ /// \param section The message section to which the rrset is to be added
+ /// \param rrset The rrset to be added. Must not be null.
+ void addRRset(const Section section, RRsetPtr rrset);
+
+ /// \brief Determine whether the given section already has an RRset
+ /// matching the given name, RR class and RR type.
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ ///
+ /// This should probably be extended to be a "find" method that returns
+ /// a matching RRset if found.
+ bool hasRRset(const Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype) const;
+
+ /// \brief Determine whether the given section already has an RRset
+ /// matching the one pointed to by the argument
+ ///
+ /// \c section must be a valid constant of the \c Section type;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ bool hasRRset(const Section section, const RRsetPtr& rrset) const;
+
+ /// \brief Remove RRSet from Message
+ ///
+ /// Removes the RRset identified by the section iterator from the message.
+ /// Note: if,.for some reason, the RRset is duplicated in the section, only
+ /// one occurrence is removed.
+ ///
+ /// If the operation is successful, all iterators into the section are
+ /// invalidated.
+ ///
+ /// \param section Section to which the iterator belongs
+ /// \param iterator Iterator pointing to the element to be removed
+ ///
+ /// \return true if the element was removed, false if the iterator was not
+ /// found in the specified section.
+ bool removeRRset(const Section section, RRsetIterator& iterator);
+
+ /// \brief Remove all RRSets from the given Section
+ ///
+ /// This method is only allowed in the \c RENDER mode, and the given
+ /// section must be valid.
+ ///
+ /// \throw InvalidMessageOperation Message is not in the \c RENDER mode
+ /// \throw OutOfRange The specified section is not valid
+ ///
+ /// \param section Section to remove all rrsets from
+ void clearSection(const Section section);
+
+ // The following methods are not currently implemented.
+ //void removeQuestion(QuestionPtr question);
+ // notyet:
+ //void addRR(const Section section, const RR& rr);
+ //void removeRR(const Section section, const RR& rr);
+
+ /// \brief Clear the message content (if any) and reinitialize it in the
+ /// specified mode.
+ void clear(Mode mode);
+
+ /// \brief Adds all rrsets from the source the given section in the
+ /// source message to the same section of this message
+ ///
+ /// \param section the section to append
+ /// \param source The source Message
+ void appendSection(const Section section, const Message& source);
+
+ /// \brief Prepare for making a response from a request.
+ ///
+ /// This will clear the DNS header except those fields that should be kept
+ /// for the response, and clear answer and the following sections.
+ /// See also dns_message_reply() of BIND9.
+ void makeResponse();
+
+ /// \brief Convert the Message to a string.
+ ///
+ /// At least \c Opcode and \c Rcode must be validly set in the \c Message
+ /// (as a result of parse in the \c PARSE mode or by explicitly setting
+ /// in the \c RENDER mode); otherwise, an exception of
+ /// class \c InvalidMessageOperation will be thrown.
+ std::string toText() const;
+
+ /// \brief Render the message in wire formant into a message renderer
+ /// object with (or without) TSIG.
+ ///
+ /// This \c Message must be in the \c RENDER mode and both \c Opcode and
+ /// \c Rcode must have been set beforehand; otherwise, an exception of
+ /// class \c InvalidMessageOperation will be thrown.
+ ///
+ /// If a non-null \c tsig_ctx is passed, it will also add a TSIG RR
+ /// with (in many cases) the TSIG MAC for the message along with the
+ /// given TSIG context (\c tsig_ctx). The TSIG RR will be placed at
+ /// the end of \c renderer. The \c TSIGContext at \c tsig_ctx will
+ /// be updated based on the fact it was used for signing and with
+ /// the latest MAC.
+ ///
+ /// \exception InvalidMessageOperation The message is not in the Render
+ /// mode, or either Rcode or Opcode is not set.
+ /// \exception InvalidParameter The allowable limit of \c renderer is too
+ /// small for a TSIG or the Header section. Note that this shouldn't
+ /// happen with parameters as defined in the standard protocols,
+ /// so it's more likely a program bug.
+ /// \exception Unexpected Rendering the TSIG RR fails. The implementation
+ /// internally makes sure this doesn't happen, so if that ever occurs
+ /// it should mean a bug either in the TSIG context or in the renderer
+ /// implementation.
+ ///
+ /// \note The renderer's internal buffers and data are automatically
+ /// cleared, keeping the length limit and the compression mode intact.
+ /// In case truncation is triggered, the renderer is cleared completely.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ /// \param tsig_ctx A TSIG context that is to be used for signing the
+ /// message
+ void toWire(AbstractMessageRenderer& renderer,
+ TSIGContext* tsig_ctx = 0);
+
+ /// Parse options.
+ ///
+ /// describe PRESERVE_ORDER: note doesn't affect EDNS or TSIG.
+ ///
+ /// The option values are used as a parameter for \c fromWire().
+ /// These are values of a bitmask type. Bitwise operations can be
+ /// performed on these values to express compound options.
+ enum ParseOptions {
+ PARSE_DEFAULT = 0, ///< The default options
+ PRESERVE_ORDER = 1 ///< Preserve RR order and don't combine them
+ };
+
+ /// \brief Parse the header section of the \c Message.
+ ///
+ /// NOTE: If the header has already been parsed by a previous call
+ /// to this method, this method simply returns (i.e., it does not
+ /// read from the \c buffer).
+ void parseHeader(isc::util::InputBuffer& buffer);
+
+ /// \brief (Re)build a \c Message object from wire-format data.
+ ///
+ /// This method parses the given wire format data to build a
+ /// complete Message object. On success, the values of the header section
+ /// fields can be accessible via corresponding get methods, and the
+ /// question and following sections can be accessible via the
+ /// corresponding iterators. If the message contains an EDNS or TSIG,
+ /// they can be accessible via \c getEDNS() and \c getTSIGRecord(),
+ /// respectively.
+ ///
+ /// This \c Message must be in the \c PARSE mode.
+ ///
+ /// This method performs strict validation on the given message based
+ /// on the DNS protocol specifications. If the given message data is
+ /// invalid, this method throws an exception (see the exception list).
+ ///
+ /// By default, this method combines RRs of the same name, RR type and
+ /// RR class in a section into a single RRset, even if they are interleaved
+ /// with a different type of RR (though it would be a rare case in
+ /// practice). If the \c PRESERVE_ORDER option is specified, it handles
+ /// each RR separately, in the appearing order, and converts it to a
+ /// separate RRset (so this RRset should contain exactly one Rdata).
+ /// This mode will be necessary when the higher level protocol is
+ /// ordering conscious. For example, in AXFR and IXFR, the position of
+ /// the SOA RRs are crucial.
+ ///
+ /// \exception InvalidMessageOperation \c Message is in the RENDER mode
+ /// \exception DNSMessageFORMERR The given message data is syntactically
+ /// \exception MessageTooShort The given data is shorter than a valid
+ /// header section
+ /// \exception std::bad_alloc Memory allocation failure
+ /// \exception Others \c Name, \c Rdata, and \c EDNS classes can also throw
+ ///
+ /// \param buffer A input buffer object that stores the wire
+ /// data. This method reads from position 0 in the passed buffer.
+ /// \param options Parse options
+ void fromWire(isc::util::InputBuffer& buffer, ParseOptions options = PARSE_DEFAULT);
+
+ ///
+ /// \name Protocol constants
+ ///
+ //@{
+ /// \brief The default maximum size of UDP DNS messages that don't cause
+ /// truncation.
+ ///
+ /// With EDNS the maximum size can be increased per message.
+ static const uint16_t DEFAULT_MAX_UDPSIZE = 512;
+
+ /// \brief The default maximum size of UDP DNS messages we can handle
+ static const uint16_t DEFAULT_MAX_EDNS0_UDPSIZE = 4096;
+ //@}
+
+private:
+ MessageImplPtr impl_;
+};
+
+/// \brief Pointer-like type pointing to a \c Message
+///
+/// This type is expected to be used as an argument in asynchronous
+/// callback functions. The internal reference-counting will ensure that
+/// that ongoing state information will not be lost if the object
+/// that originated the asynchronous call falls out of scope.
+typedef boost::shared_ptr<Message> MessagePtr;
+typedef boost::shared_ptr<const Message> ConstMessagePtr;
+
+/// Insert the \c Message as a string into stream.
+///
+/// This method convert \c message into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param message A \c Message object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const Message& message);
+
+} // namespace dns
+} // namespace isc
+
+#endif // MESSAGE_H
diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc
new file mode 100644
index 0000000..4e1dfea
--- /dev/null
+++ b/src/lib/dns/messagerenderer.cc
@@ -0,0 +1,393 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <dns/name.h>
+#include <dns/name_internal.h>
+#include <dns/labelsequence.h>
+#include <dns/messagerenderer.h>
+#include <util/buffer.h>
+
+#include <boost/array.hpp>
+#include <boost/static_assert.hpp>
+#include <limits>
+#include <cassert>
+#include <vector>
+
+using namespace isc::util;
+using isc::dns::name::internal::maptolower;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+
+namespace { // hide internal-only names from the public namespaces
+///
+/// \brief The \c OffsetItem class represents a pointer to a name
+/// rendered in the internal buffer for the \c MessageRendererImpl object.
+///
+/// A \c MessageRendererImpl object maintains a set of \c OffsetItem
+/// objects in a hash table, and searches the table for the position of the
+/// longest match (ancestor) name against each new name to be rendered into
+/// the buffer.
+struct OffsetItem {
+ OffsetItem(size_t hash, size_t pos, size_t len) :
+ hash_(hash), pos_(pos), len_(len) {
+ }
+
+ /// The hash value for the stored name calculated by LabelSequence.getHash.
+ /// This will help make name comparison in \c NameCompare more efficient.
+ size_t hash_;
+
+ /// The position (offset from the beginning) in the buffer where the
+ /// name starts.
+ uint16_t pos_;
+
+ /// The length of the corresponding sequence (which is a domain name).
+ uint16_t len_;
+};
+
+/// \brief The \c NameCompare class is a functor that checks equality
+/// between the name corresponding to an \c OffsetItem object and the name
+/// consists of labels represented by a \c LabelSequence object.
+///
+/// Template parameter CASE_SENSITIVE determines whether to ignore the case
+/// of the names. This policy doesn't change throughout the lifetime of
+/// this object, so we separate these using template to avoid unnecessary
+/// condition check.
+template <bool CASE_SENSITIVE>
+struct NameCompare {
+ /// \brief Constructor
+ ///
+ /// \param buffer The buffer for rendering used in the caller renderer
+ /// \param name_buf An input buffer storing the wire-format data of the
+ /// name to be newly rendered (and only that data).
+ /// \param hash The hash value for the name.
+ NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf,
+ size_t hash) :
+ buffer_(&buffer), name_buf_(&name_buf), hash_(hash) {
+ }
+
+ bool operator()(const OffsetItem& item) const {
+ // Trivial inequality check. If either the hash or the total length
+ // doesn't match, the names are obviously different.
+ if (item.hash_ != hash_ || item.len_ != name_buf_->getLength()) {
+ return (false);
+ }
+
+ // Compare the name data, character-by-character.
+ // item_pos keeps track of the position in the buffer corresponding to
+ // the character to compare. item_label_len is the number of
+ // characters in the labels where the character pointed by item_pos
+ // belongs. When it reaches zero, nextPosition() identifies the
+ // position for the subsequent label, taking into account name
+ // compression, and resets item_label_len to the length of the new
+ // label.
+ name_buf_->setPosition(0); // buffer can be reused, so reset position
+ uint16_t item_pos = item.pos_;
+ uint16_t item_label_len = 0;
+ for (size_t i = 0; i < item.len_; ++i, ++item_pos) {
+ item_pos = nextPosition(*buffer_, item_pos, item_label_len);
+ const uint8_t ch1 = (*buffer_)[item_pos];
+ const uint8_t ch2 = name_buf_->readUint8();
+ if (CASE_SENSITIVE) {
+ if (ch1 != ch2) {
+ return (false);
+ }
+ } else {
+ if (maptolower[ch1] != maptolower[ch2]) {
+ return (false);
+ }
+ }
+ }
+
+ return (true);
+ }
+
+private:
+ static uint16_t nextPosition(const OutputBuffer& buffer,
+ uint16_t pos, uint16_t& llen) {
+ if (llen == 0) {
+ size_t i = 0;
+
+ while ((buffer[pos] & Name::COMPRESS_POINTER_MARK8) ==
+ Name::COMPRESS_POINTER_MARK8) {
+ pos = (buffer[pos] & ~Name::COMPRESS_POINTER_MARK8) *
+ 256 + buffer[pos + 1];
+
+ // This loop should stop as long as the buffer has been
+ // constructed validly and the search/insert argument is based
+ // on a valid name, which is an assumption for this class.
+ // But we'll abort if a bug could cause an infinite loop.
+ i += 2;
+ isc_throw_assert(i < Name::MAX_WIRE);
+ }
+ llen = buffer[pos];
+ } else {
+ --llen;
+ }
+ return (pos);
+ }
+
+ const OutputBuffer* buffer_;
+ InputBuffer* name_buf_;
+ const size_t hash_;
+};
+}
+
+///
+/// \brief The \c MessageRendererImpl class is the actual implementation of
+/// \c MessageRenderer.
+///
+/// The implementation is hidden from applications. We can refer to specific
+/// members of this class only within the implementation source file.
+///
+/// It internally holds a hash table for OffsetItem objects corresponding
+/// to portions of names rendered in this renderer. The offset information
+/// is used to compress subsequent names to be rendered.
+struct MessageRenderer::MessageRendererImpl {
+ // The size of hash buckets and number of hash entries per bucket for
+ // which space is preallocated and kept reserved for subsequent rendering
+ // to provide better performance. These values are derived from the
+ // BIND 9 implementation that uses a similar hash table.
+ static const size_t BUCKETS = 64;
+ static const size_t RESERVED_ITEMS = 16;
+ static const uint16_t NO_OFFSET = 65535; // used as a marker of 'not found'
+
+ /// \brief Constructor
+ MessageRendererImpl() :
+ msglength_limit_(512), truncated_(false),
+ compress_mode_(MessageRenderer::CASE_INSENSITIVE) {
+ // Reserve some spaces for hash table items.
+ for (size_t i = 0; i < BUCKETS; ++i) {
+ table_[i].reserve(RESERVED_ITEMS);
+ }
+ }
+
+ uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf,
+ size_t hash, bool case_sensitive) const {
+ // Find a matching entry, if any. We use some heuristics here: often
+ // the same name appears consecutively (like repeating the same owner
+ // name for a single RRset), so in case there's a collision in the
+ // bucket it will be more likely to find it in the tail side of the
+ // bucket.
+ const size_t bucket_id = hash % BUCKETS;
+ vector<OffsetItem>::const_reverse_iterator found;
+ if (case_sensitive) {
+ found = find_if(table_[bucket_id].rbegin(),
+ table_[bucket_id].rend(),
+ NameCompare<true>(buffer, name_buf, hash));
+ } else {
+ found = find_if(table_[bucket_id].rbegin(),
+ table_[bucket_id].rend(),
+ NameCompare<false>(buffer, name_buf, hash));
+ }
+ if (found != table_[bucket_id].rend()) {
+ return (found->pos_);
+ }
+ return (NO_OFFSET);
+ }
+
+ void addOffset(size_t hash, size_t offset, size_t len) {
+ table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len));
+ }
+
+ // The hash table for the (offset + position in the buffer) entries
+ vector<OffsetItem> table_[BUCKETS];
+ /// The maximum length of rendered data that can fit without
+ /// truncation.
+ uint16_t msglength_limit_;
+ /// A boolean flag that indicates truncation has occurred while rendering
+ /// the data.
+ bool truncated_;
+ /// The name compression mode.
+ CompressMode compress_mode_;
+
+ // Placeholder for hash values as they are calculated in writeName().
+ // Note: we may want to make it a local variable of writeName() if it
+ // works more efficiently.
+ boost::array<size_t, Name::MAX_LABELS> seq_hashes_;
+};
+
+MessageRenderer::MessageRenderer() :
+ AbstractMessageRenderer(),
+ impl_(new MessageRendererImpl()) {
+}
+
+MessageRenderer::~MessageRenderer() {
+}
+
+void
+MessageRenderer::clear() {
+ AbstractMessageRenderer::clear();
+ impl_->msglength_limit_ = 512;
+ impl_->truncated_ = false;
+ impl_->compress_mode_ = CASE_INSENSITIVE;
+
+ // Clear the hash table. We reserve the minimum space for possible
+ // subsequent use of the renderer.
+ for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) {
+ if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) {
+ // Trim excessive capacity: swap ensures the new capacity is only
+ // reasonably large for the reserved space.
+ vector<OffsetItem> new_table;
+ new_table.reserve(MessageRendererImpl::RESERVED_ITEMS);
+ new_table.swap(impl_->table_[i]);
+ }
+ impl_->table_[i].clear();
+ }
+}
+
+size_t
+MessageRenderer::getLengthLimit() const {
+ return (impl_->msglength_limit_);
+}
+
+void
+MessageRenderer::setLengthLimit(const size_t len) {
+ impl_->msglength_limit_ = len;
+}
+
+bool
+MessageRenderer::isTruncated() const {
+ return (impl_->truncated_);
+}
+
+void
+MessageRenderer::setTruncated() {
+ impl_->truncated_ = true;
+}
+
+MessageRenderer::CompressMode
+MessageRenderer::getCompressMode() const {
+ return (impl_->compress_mode_);
+}
+
+void
+MessageRenderer::setCompressMode(const CompressMode mode) {
+ if (getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "compress mode cannot be changed during rendering");
+ }
+ impl_->compress_mode_ = mode;
+}
+
+void
+MessageRenderer::writeName(const LabelSequence& ls, const bool compress) {
+ LabelSequence sequence(ls);
+ const size_t nlabels = sequence.getLabelCount();
+ size_t data_len;
+ const uint8_t* data;
+
+ // Find the offset in the offset table whose name gives the longest
+ // match against the name to be rendered.
+ size_t nlabels_uncomp;
+ uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET;
+ const bool case_sensitive = (impl_->compress_mode_ ==
+ MessageRenderer::CASE_SENSITIVE);
+ for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) {
+ if (nlabels_uncomp > 0) {
+ sequence.stripLeft(1);
+ }
+
+ data = sequence.getData(&data_len);
+ if (data_len == 1) { // trailing dot.
+ ++nlabels_uncomp;
+ break;
+ }
+ // write with range check for safety
+ impl_->seq_hashes_.at(nlabels_uncomp) =
+ sequence.getHash(impl_->compress_mode_);
+ InputBuffer name_buf(data, data_len);
+ ptr_offset = impl_->findOffset(getBuffer(), name_buf,
+ impl_->seq_hashes_[nlabels_uncomp],
+ case_sensitive);
+ if (ptr_offset != MessageRendererImpl::NO_OFFSET) {
+ break;
+ }
+ }
+
+ // Record the current offset before updating the offset table
+ size_t offset = getLength();
+ // Write uncompress part:
+ if (nlabels_uncomp > 0 || !compress) {
+ LabelSequence uncomp_sequence(ls);
+ if (compress && nlabels > nlabels_uncomp) {
+ // If there's compressed part, strip off that part.
+ uncomp_sequence.stripRight(nlabels - nlabels_uncomp);
+ }
+ data = uncomp_sequence.getData(&data_len);
+ writeData(data, data_len);
+ }
+ // And write compression pointer if available:
+ if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) {
+ ptr_offset |= Name::COMPRESS_POINTER_MARK16;
+ writeUint16(ptr_offset);
+ }
+
+ // Finally, record the offset and length for each uncompressed sequence
+ // in the hash table. The renderer's buffer has just stored the
+ // corresponding data, so we use the rendered data to get the length
+ // of each label of the names.
+ size_t seqlen = ls.getDataLength();
+ for (size_t i = 0; i < nlabels_uncomp; ++i) {
+ const uint8_t label_len = getBuffer()[offset];
+ if (label_len == 0) { // offset for root doesn't need to be stored.
+ break;
+ }
+ if (offset > Name::MAX_COMPRESS_POINTER) {
+ break;
+ }
+ // Store the tuple of <hash, offset, len> to the table. Note that we
+ // already know the hash value for each name.
+ impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen);
+ offset += (label_len + 1);
+ seqlen -= (label_len + 1);
+ }
+}
+
+void
+MessageRenderer::writeName(const Name& name, const bool compress) {
+ const LabelSequence ls(name);
+ writeName(ls, compress);
+}
+
+AbstractMessageRenderer::AbstractMessageRenderer() :
+ local_buffer_(0), buffer_(&local_buffer_) {
+}
+
+void
+AbstractMessageRenderer::setBuffer(OutputBuffer* buffer) {
+ if (buffer && buffer_->getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "MessageRenderer buffer cannot be set when in use");
+ }
+ if (!buffer && buffer_ == &local_buffer_) {
+ isc_throw(isc::InvalidParameter,
+ "Default MessageRenderer buffer cannot be reset");
+ }
+
+ if (!buffer) {
+ // Reset to the default buffer, then clear other internal resources.
+ // The order is important; we need to keep the used buffer intact.
+ buffer_ = &local_buffer_;
+ clear();
+ } else {
+ buffer_ = buffer;
+ }
+}
+
+void
+AbstractMessageRenderer::clear() {
+ buffer_->clear();
+}
+
+}
+}
diff --git a/src/lib/dns/messagerenderer.h b/src/lib/dns/messagerenderer.h
new file mode 100644
index 0000000..4b105cd
--- /dev/null
+++ b/src/lib/dns/messagerenderer.h
@@ -0,0 +1,391 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MESSAGERENDERER_H
+#define MESSAGERENDERER_H
+
+#include <util/buffer.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <memory>
+
+namespace isc {
+namespace dns {
+// forward declarations
+class Name;
+class LabelSequence;
+
+/// \brief The \c AbstractMessageRenderer class is an abstract base class
+/// that provides common interfaces for rendering a DNS message into a buffer
+/// in wire format.
+///
+/// A specific derived class of \c AbstractMessageRenderer (we call it
+/// a renderer class hereafter) is simply responsible for name compression at
+/// least in the current design. A renderer class object (conceptually)
+/// manages the positions of names rendered in some sort of buffer and uses
+/// that information to render subsequent names with compression.
+///
+/// A renderer class is mainly intended to be used as a helper for a more
+/// comprehensive \c Message class internally; normal applications won't have
+/// to care about details of this class.
+///
+/// By default any (derived) renderer class object is associated with
+/// an internal buffer, and subsequent write operations will be performed
+/// on that buffer. The rendering result can be retrieved via the
+/// \c getData() method.
+///
+/// If an application wants a separate buffer can be (normally temporarily)
+/// set for rendering operations via the \c setBuffer() method. In that case,
+/// it is generally expected that all rendering operations are performed via
+/// that object. If the application modifies the buffer in
+/// parallel with the renderer, the result will be undefined.
+///
+/// Note to developers: we introduced a separate class for name compression
+/// because previous benchmark with BIND9 showed compression affects overall
+/// response performance very much. By having a separate class dedicated for
+/// this purpose, we'll be able to change the internal implementation of name
+/// compression in the future without affecting other part of the API and
+/// implementation.
+///
+/// In addition, by introducing a class hierarchy from
+/// \c AbstractMessageRenderer, we allow an application to use a customized
+/// renderer class for specific purposes. For example, a high performance
+/// DNS server may want to use an optimized renderer class assuming some
+/// specific underlying data representation.
+///
+/// \note Some functions (like writeUint8) are not virtual. It is because
+/// it is hard to imagine any version of message renderer that would
+/// do anything else than just putting the data into a buffer, so we
+/// provide a default implementation and having them virtual would only
+/// hurt the performance with no real gain. If it would happen a different
+/// implementation is really needed, we can make them virtual in future.
+/// The only one that is virtual is writeName and it's because this
+/// function is much more complicated, therefore there's a lot of space
+/// for different implementations or different behavior.
+class AbstractMessageRenderer {
+public:
+ /// \brief Compression mode constants.
+ ///
+ /// The \c CompressMode enum type represents the name compression mode
+ /// for renderer classes.
+ /// \c CASE_INSENSITIVE means compress names in case-insensitive manner;
+ /// \c CASE_SENSITIVE means compress names in case-sensitive manner.
+ /// By default, a renderer compresses names in case-insensitive
+ /// manner.
+ /// Compression mode can be dynamically modified by the
+ /// \c setCompressMode() method.
+ /// The mode can be changed even in the middle of rendering, although this
+ /// is not an intended usage. In this case the names already compressed
+ /// are intact; only names being compressed after the mode change are
+ /// affected by the change.
+ /// If a renderer class object is reinitialized by the \c clear()
+ /// method, the compression mode will be reset to the default, which is
+ /// \c CASE_INSENSITIVE
+ ///
+ /// One specific case where case-sensitive compression is required is
+ /// AXFR as described in draft-ietf-dnsext-axfr-clarify. A primary
+ /// authoritative DNS server implementation using this API would specify
+ /// \c CASE_SENSITIVE before rendering outgoing AXFR messages.
+ ///
+ enum CompressMode {
+ CASE_INSENSITIVE, //!< Compress names case-insensitive manner (default)
+ CASE_SENSITIVE //!< Compress names case-sensitive manner
+ };
+protected:
+ ///
+ /// \name Constructors and Destructor
+ //@{
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ AbstractMessageRenderer();
+
+public:
+ /// \brief The destructor.
+ virtual ~AbstractMessageRenderer() {}
+ //@}
+protected:
+ /// \brief Return the output buffer we render into.
+ const isc::util::OutputBuffer& getBuffer() const { return (*buffer_); }
+ isc::util::OutputBuffer& getBuffer() { return (*buffer_); }
+private:
+ /// \brief Local (default) buffer to store data.
+ isc::util::OutputBuffer local_buffer_;
+
+ /// \brief Buffer to store data.
+ ///
+ /// Note that the class interface ensures this pointer is never null;
+ /// it either refers to \c local_buffer_ or to an application-supplied
+ /// buffer by \c setBuffer().
+ ///
+ /// It was decided that there's no need to have this in every subclass,
+ /// at least not now, and this reduces code size and gives compiler a
+ /// better chance to optimize.
+ isc::util::OutputBuffer* buffer_;
+public:
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Return a pointer to the head of the data stored in the internal
+ /// buffer.
+ ///
+ /// This method works exactly same as the same method of the \c OutputBuffer
+ /// class; all notes for \c OutputBuffer apply.
+ const void* getData() const {
+ return (buffer_->getData());
+ }
+
+ /// \brief Return the length of data written in the internal buffer.
+ size_t getLength() const {
+ return (buffer_->getLength());
+ }
+
+ /// \brief Return whether truncation has occurred while rendering.
+ ///
+ /// Once the return value of this method is \c true, it doesn't make sense
+ /// to try rendering more data, although this class itself doesn't reject
+ /// the attempt.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return true if truncation has occurred; otherwise \c false.
+ virtual bool isTruncated() const = 0;
+
+ /// \brief Return the maximum length of rendered data that can fit in the
+ /// corresponding DNS message without truncation.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The maximum length in bytes.
+ virtual size_t getLengthLimit() const = 0;
+
+ /// \brief Return the compression mode of the renderer class object.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The current compression mode.
+ virtual CompressMode getCompressMode() const = 0;
+ //@}
+
+ ///
+ /// \name Setter Methods
+ ///
+ //@{
+ /// \brief Set or reset a temporary output buffer.
+ ///
+ /// This method can be used for an application that manages an output
+ /// buffer separately from the message renderer and wants to keep reusing
+ /// the renderer. When the renderer is associated with the default buffer
+ /// and the given pointer is non null, the given buffer will be
+ /// (temporarily) used for subsequent message rendering; if the renderer
+ /// is associated with a temporary buffer and the given pointer is null,
+ /// the renderer will be reset with the default buffer. In the latter
+ /// case any additional resources (possibly specific to a derived renderer
+ /// class) will be cleared, but the temporary buffer is kept as the latest
+ /// state (which would normally store the rendering result).
+ ///
+ /// This method imposes some restrictions to prevent accidental misuse
+ /// that could cause disruption such as dereferencing an invalid object.
+ /// First, a temporary buffer must not be set when the associated buffer
+ /// is in use, that is, any data are stored in the buffer. Also, the
+ /// default buffer cannot be "reset"; when null is specified a temporary
+ /// buffer must have been set beforehand. If these conditions aren't met
+ /// an isc::InvalidParameter exception will be thrown. This method is
+ /// exception free otherwise.
+ ///
+ /// \throw isc::InvalidParameter A restrictions of the method usage isn't
+ /// met.
+ ///
+ /// \param buffer A pointer to a temporary output buffer or null for reset
+ /// it.
+ void setBuffer(isc::util::OutputBuffer* buffer);
+
+ /// \brief Mark the renderer to indicate truncation has occurred while
+ /// rendering.
+ ///
+ /// This method never throws an exception.
+ virtual void setTruncated() = 0;
+
+ /// \brief Set the maximum length of rendered data that can fit in the
+ /// corresponding DNS message without truncation.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param len The maximum length in bytes.
+ virtual void setLengthLimit(size_t len) = 0;
+
+ /// \brief Set the compression mode of the renderer class object.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param mode A \c CompressMode value representing the compression mode.
+ virtual void setCompressMode(CompressMode mode) = 0;
+ //@}
+
+ ///
+ /// \name Methods for writing data into the internal buffer.
+ ///
+ //@{
+ /// \brief Insert a specified length of gap at the end of the buffer.
+ ///
+ /// The caller should not assume any particular value to be inserted.
+ /// This method is provided as a shortcut to make a hole in the buffer
+ /// that is to be filled in later, e.g, by \ref writeUint16At().
+ ///
+ /// \param len The length of the gap to be inserted in bytes.
+ void skip(size_t len) {
+ buffer_->skip(len);
+ }
+
+ /// \brief Trim the specified length of data from the end of the internal
+ /// buffer.
+ ///
+ /// This method is provided for such cases as DNS message truncation.
+ ///
+ /// The specified length must not exceed the current data size of the
+ /// buffer; otherwise an exception of class \c isc::OutOfRange will
+ /// be thrown.
+ ///
+ /// \param len The length of data that should be trimmed.
+ void trim(size_t len) {
+ buffer_->trim(len);
+ }
+
+ /// \brief Clear the internal buffer and other internal resources.
+ ///
+ /// This method can be used to re-initialize and reuse the renderer
+ /// without constructing a new one.
+ virtual void clear();
+
+ /// \brief Write an unsigned 8-bit integer into the internal buffer.
+ ///
+ /// \param data The 8-bit integer to be written into the internal buffer.
+ void writeUint8(const uint8_t data) {
+ buffer_->writeUint8(data);
+ }
+
+ /// \brief Write an unsigned 16-bit integer in host byte order into the
+ /// internal buffer in network byte order.
+ ///
+ /// \param data The 16-bit integer to be written into the buffer.
+ void writeUint16(uint16_t data) {
+ buffer_->writeUint16(data);
+ }
+
+ /// \brief Write an unsigned 16-bit integer in host byte order at the
+ /// specified position of the internal buffer in network byte order.
+ ///
+ /// The buffer must have a sufficient room to store the given data at the
+ /// given position, that is, <code>pos + 2 < getLength()</code>;
+ /// otherwise an exception of class \c isc::OutOfRange will be thrown.
+ /// Note also that this method never extends the internal buffer.
+ ///
+ /// \param data The 16-bit integer to be written into the internal buffer.
+ /// \param pos The beginning position in the buffer to write the data.
+ void writeUint16At(uint16_t data, size_t pos) {
+ buffer_->writeUint16At(data, pos);
+ }
+
+ /// \brief Write an unsigned 32-bit integer in host byte order into the
+ /// internal buffer in network byte order.
+ ///
+ /// \param data The 32-bit integer to be written into the buffer.
+ void writeUint32(uint32_t data) {
+ buffer_->writeUint32(data);
+ }
+
+ /// \brief Copy an arbitrary length of data into the internal buffer
+ /// of the renderer object.
+ ///
+ /// No conversion on the copied data is performed.
+ ///
+ /// \param data A pointer to the data to be copied into the internal buffer.
+ /// \param len The length of the data in bytes.
+ void writeData(const void *data, size_t len) {
+ buffer_->writeData(data, len);
+ }
+
+ /// \brief Write a \c Name object into the internal buffer in wire format,
+ /// with or without name compression.
+ ///
+ /// If the optional parameter \c compress is \c true, this method tries to
+ /// compress the \c name if possible, searching the entire message that has
+ /// been rendered. Otherwise name compression is omitted. Its default
+ /// value is \c true.
+ ///
+ /// Note: even if \c compress is \c true, the position of the \c name (and
+ /// possibly its ancestor names) in the message is recorded and may be used
+ /// for compressing subsequent names.
+ ///
+ /// \param name A \c Name object to be written.
+ /// \param compress A boolean indicating whether to enable name
+ /// compression.
+ virtual void writeName(const Name& name, bool compress = true) = 0;
+
+ /// \brief Write a \c LabelSequence object into the internal buffer
+ /// in wire format, with or without name compression.
+ ///
+ /// This is the same as the other version, which takes \c Name instead
+ /// of \c LabelSequence, except for the parameter type. The passed
+ /// \c LabelSequence must be absolute.
+ ///
+ /// \param ls A \c LabelSequence object to be written.
+ /// \param compress A boolean indicating whether to enable name
+ /// compression.
+ virtual void writeName(const LabelSequence& ls, bool compress = true) = 0;
+ //@}
+};
+
+/// The \c MessageRenderer is a concrete derived class of
+/// \c AbstractMessageRenderer as a general purpose implementation of the
+/// renderer interfaces.
+///
+/// A \c MessageRenderer object is constructed with a \c OutputBuffer
+/// object, which is the buffer into which the rendered %data will be written.
+/// Normally the buffer is expected to be empty on construction, but it doesn't
+/// have to be so; the renderer object will start rendering from the
+/// end of the buffer at the time of construction. However, if the
+/// pre-existing portion of the buffer contains DNS names, these names won't
+/// be considered for name compression.
+class MessageRenderer : public AbstractMessageRenderer,
+ public boost::noncopyable { // Can crash if copied
+public:
+ using AbstractMessageRenderer::CASE_INSENSITIVE;
+ using AbstractMessageRenderer::CASE_SENSITIVE;
+
+ MessageRenderer();
+
+ virtual ~MessageRenderer();
+ virtual bool isTruncated() const;
+ virtual size_t getLengthLimit() const;
+ virtual CompressMode getCompressMode() const;
+ virtual void setTruncated();
+ virtual void setLengthLimit(size_t len);
+
+ /// This implementation does not allow this call in the middle of
+ /// rendering (i.e. after at least one name is rendered) due to
+ /// restriction specific to the internal implementation. Such attempts
+ /// will result in an \c isc::InvalidParameter exception.
+ ///
+ /// This shouldn't be too restrictive in practice; there's no known
+ /// practical case for such a mixed compression policy in a single
+ /// message.
+ virtual void setCompressMode(CompressMode mode);
+
+ virtual void clear();
+ virtual void writeName(const Name& name, bool compress = true);
+ virtual void writeName(const LabelSequence& ls, bool compress = true);
+
+private:
+ struct MessageRendererImpl;
+ std::unique_ptr<MessageRendererImpl> impl_;
+};
+}
+}
+#endif // MESSAGERENDERER_H
diff --git a/src/lib/dns/name.cc b/src/lib/dns/name.cc
new file mode 100644
index 0000000..ac48205
--- /dev/null
+++ b/src/lib/dns/name.cc
@@ -0,0 +1,720 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/isc_assert.h>
+#include <dns/name.h>
+#include <dns/name_internal.h>
+#include <dns/messagerenderer.h>
+#include <dns/labelsequence.h>
+
+#include <cctype>
+#include <iterator>
+#include <functional>
+#include <vector>
+#include <iostream>
+#include <algorithm>
+
+using namespace isc::util;
+using namespace isc::dns::name::internal;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+
+namespace {
+///
+/// These are shortcut arrays for efficient character conversion.
+/// digitvalue converts a digit character to the corresponding integer.
+/// maptolower convert uppercase alphabets to their lowercase counterparts.
+/// We once used a helper non-local static object to avoid hardcoding the
+/// array members, but we then realized it's susceptible to static
+/// initialization order fiasco: Since these constants are used in a Name
+/// constructor, a non-local static Name object defined in another translation
+/// unit than this file may not be initialized correctly.
+/// There are several ways to address this issue, but in this specific case
+/// we chose the naive but simple hardcoding approach.
+///
+/// These definitions are derived from BIND 9's libdns module.
+/// Note: we could use the standard tolower() function instead of the
+/// maptolower array, but a benchmark indicated that the private array could
+/// improve the performance of message rendering (which internally uses the
+/// array heavily) about 27%. Since we want to achieve very good performance
+/// for message rendering in some cases, we'll keep using it.
+const signed char digitvalue[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 48
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 64
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 256
+};
+}
+
+namespace name {
+namespace internal {
+const uint8_t maptolower[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // ..., 'A' - 'G'
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // 'H' - 'O'
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // 'P' - 'W'
+ 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, // 'X' - 'Z', ...
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+} // end of internal
+} // end of name
+
+namespace {
+///
+/// Textual name parser states.
+///
+typedef enum {
+ ft_init = 0, // begin of the name
+ ft_start, // begin of a label
+ ft_ordinary, // parsing an ordinary label
+ ft_initialescape, // just found '\'
+ ft_escape, // begin of handling a '\'-escaped sequence
+ ft_escdecimal // parsing a '\DDD' octet.
+} ft_state;
+
+// The parser of name from a string. It is a template, because
+// some parameters are used with two different types, while others
+// are private type aliases.
+template<class Iterator, class Offsets, class Data>
+void
+stringParse(Iterator s, Iterator send, bool downcase, Offsets& offsets,
+ Data& ndata)
+{
+ const Iterator orig_s(s);
+ //
+ // Initialize things to make the compiler happy; they're not required.
+ //
+ unsigned int digits = 0;
+ unsigned int value = 0;
+ unsigned int count = 0;
+
+ //
+ // Set up the state machine.
+ //
+ bool done = false;
+ bool is_root = false;
+ const bool empty = s == send;
+ ft_state state = ft_init;
+
+ // Prepare the output buffers.
+ offsets.reserve(Name::MAX_LABELS);
+ offsets.push_back(0);
+ ndata.reserve(Name::MAX_WIRE);
+
+ // should we refactor this code using, e.g, the state pattern? Probably
+ // not at this point, as this is based on proved code (derived from BIND9)
+ // and it's less likely that we'll have more variations in the domain name
+ // syntax. If this ever happens next time, we should consider refactor
+ // the code, rather than adding more states and cases below.
+ while (ndata.size() < Name::MAX_WIRE && s != send && !done) {
+ unsigned char c = *s++;
+
+ switch (state) {
+ case ft_init:
+ //
+ // Is this the root name?
+ //
+ if (c == '.') {
+ if (s != send) {
+ isc_throw(EmptyLabel,
+ "non terminating empty label in " <<
+ string(orig_s, send));
+ }
+ is_root = true;
+ } else if (c == '@' && s == send) {
+ // handle a single '@' as the root name.
+ is_root = true;
+ }
+
+ if (is_root) {
+ ndata.push_back(0);
+ done = true;
+ break;
+ }
+
+ // FALLTHROUGH
+ case ft_start:
+ ndata.push_back(0); // placeholder for the label length field
+ count = 0;
+ if (c == '\\') {
+ state = ft_initialescape;
+ break;
+ }
+ state = ft_ordinary;
+ isc_throw_assert(ndata.size() < Name::MAX_WIRE);
+ // FALLTHROUGH
+ case ft_ordinary:
+ if (c == '.') {
+ if (count == 0) {
+ isc_throw(EmptyLabel,
+ "duplicate period in " << string(orig_s, send));
+ }
+ ndata.at(offsets.back()) = count;
+ offsets.push_back(ndata.size());
+ if (s == send) {
+ ndata.push_back(0);
+ done = true;
+ }
+ state = ft_start;
+ } else if (c == '\\') {
+ state = ft_escape;
+ } else {
+ if (++count > Name::MAX_LABELLEN) {
+ isc_throw(TooLongLabel,
+ "label is too long in " << string(orig_s, send));
+ }
+ ndata.push_back(downcase ? maptolower[c] : c);
+ }
+ break;
+ case ft_initialescape:
+ if (c == '[') {
+ // This looks like a bitstring label, which was deprecated.
+ // Intentionally drop it.
+ isc_throw(BadLabelType,
+ "invalid label type in " << string(orig_s, send));
+ }
+ // FALLTHROUGH
+ case ft_escape:
+ if (!isdigit(c & 0xff)) {
+ if (++count > Name::MAX_LABELLEN) {
+ isc_throw(TooLongLabel,
+ "label is too long in " << string(orig_s, send));
+ }
+ ndata.push_back(downcase ? maptolower[c] : c);
+ state = ft_ordinary;
+ break;
+ }
+ digits = 0;
+ value = 0;
+ state = ft_escdecimal;
+ // FALLTHROUGH
+ case ft_escdecimal:
+ if (!isdigit(c & 0xff)) {
+ isc_throw(BadEscape,
+ "mixture of escaped digit and non-digit in "
+ << string(orig_s, send));
+ }
+ value *= 10;
+ value += digitvalue[c];
+ digits++;
+ if (digits == 3) {
+ if (value > 255) {
+ isc_throw(BadEscape,
+ "escaped decimal is too large in "
+ << string(orig_s, send));
+ }
+ if (++count > Name::MAX_LABELLEN) {
+ isc_throw(TooLongLabel,
+ "label is too long in " << string(orig_s, send));
+ }
+ ndata.push_back(downcase ? maptolower[value] : value);
+ state = ft_ordinary;
+ }
+ break;
+ default:
+ // impossible case
+ isc_throw_assert(false);
+ }
+ }
+
+ if (!done) { // no trailing '.' was found.
+ if (ndata.size() == Name::MAX_WIRE) {
+ isc_throw(TooLongName,
+ "name is too long for termination in " <<
+ string(orig_s, send));
+ }
+ isc_throw_assert(s == send);
+ if (state != ft_ordinary) {
+ isc_throw(IncompleteName,
+ "incomplete textual name in " <<
+ (empty ? "<empty>" : string(orig_s, send)));
+ }
+ if (state == ft_ordinary) {
+ isc_throw_assert(count != 0);
+ ndata.at(offsets.back()) = count;
+
+ offsets.push_back(ndata.size());
+ // add a trailing \0
+ ndata.push_back('\0');
+ }
+ }
+}
+
+}
+
+Name::Name(const std::string &namestring, bool downcase) {
+ // Prepare inputs for the parser
+ const std::string::const_iterator s = namestring.begin();
+ const std::string::const_iterator send = namestring.end();
+
+ // Prepare outputs
+ NameOffsets offsets;
+ NameString ndata;
+
+ // To the parsing
+ stringParse(s, send, downcase, offsets, ndata);
+
+ // And get the output
+ labelcount_ = offsets.size();
+ isc_throw_assert(labelcount_ > 0 && labelcount_ <= Name::MAX_LABELS);
+ ndata_.assign(ndata.data(), ndata.size());
+ length_ = ndata_.size();
+ offsets_.assign(offsets.begin(), offsets.end());
+}
+
+Name::Name(const char* namedata, size_t data_len, const Name* origin,
+ bool downcase) {
+ // Check validity of data
+ if (!namedata || data_len == 0) {
+ isc_throw(isc::InvalidParameter,
+ "No data provided to Name constructor");
+ }
+ // If the last character is not a dot, it is a relative to origin.
+ // It is safe to check now, we know there's at least one character.
+ const bool absolute = (namedata[data_len - 1] == '.');
+ // If we are not absolute, we need the origin to complete the name.
+ if (!absolute && !origin) {
+ isc_throw(MissingNameOrigin,
+ "No origin available and name is relative");
+ }
+ // Prepare inputs for the parser
+ const char* end = namedata + data_len;
+
+ // Prepare outputs
+ NameOffsets offsets;
+ NameString ndata;
+
+ // Do the actual parsing
+ stringParse(namedata, end, downcase, offsets, ndata);
+
+ // Get the output
+ labelcount_ = offsets.size();
+ isc_throw_assert(labelcount_ > 0 && labelcount_ <= Name::MAX_LABELS);
+ ndata_.assign(ndata.data(), ndata.size());
+ length_ = ndata_.size();
+ offsets_.assign(offsets.begin(), offsets.end());
+
+ if (!absolute) {
+ // Now, extend the data with the ones from origin. But eat the
+ // last label (the empty one).
+
+ // Drop the last character of the data (the \0) and append a copy of
+ // the origin's data
+ ndata_.erase(ndata_.end() - 1);
+ ndata_.append(origin->ndata_);
+
+ // Do a similar thing with offsets. However, we need to move them
+ // so they point after the prefix we parsed before.
+ size_t offset = offsets_.back();
+ offsets_.pop_back();
+ size_t offset_count = offsets_.size();
+ offsets_.insert(offsets_.end(), origin->offsets_.begin(),
+ origin->offsets_.end());
+ for (auto it(offsets_.begin() + offset_count); it != offsets_.end(); ++it) {
+ *it += offset;
+ }
+
+ // Adjust sizes.
+ length_ = ndata_.size();
+ labelcount_ = offsets_.size();
+
+ // And check the sizes are OK.
+ if (labelcount_ > Name::MAX_LABELS || length_ > Name::MAX_WIRE) {
+ isc_throw(TooLongName, "Combined name is too long");
+ }
+ }
+}
+
+namespace {
+///
+/// Wire-format name parser states.
+///
+typedef enum {
+ fw_start = 0, // beginning of a label
+ fw_ordinary, // inside an ordinary (non compressed) label
+ fw_newcurrent // beginning of a compression pointer
+} fw_state;
+}
+
+Name::Name(InputBuffer& buffer, bool downcase) {
+ NameOffsets offsets;
+ offsets.reserve(Name::MAX_LABELS);
+
+ /*
+ * Initialize things to make the compiler happy; they're not required.
+ */
+ unsigned int n = 0;
+
+ //
+ // Set up.
+ //
+ bool done = false;
+ unsigned int nused = 0;
+ bool seen_pointer = false;
+ fw_state state = fw_start;
+
+ unsigned int cused = 0; // Bytes of compressed name data used
+ unsigned int current = buffer.getPosition();
+ unsigned int pos_begin = current;
+ unsigned int biggest_pointer = current;
+
+ // Make the compiler happy; this is not required.
+ // XXX: bad style in that we initialize it with a dummy value and define
+ // it far from where it's used. But alternatives seemed even worse.
+ unsigned int new_current = 0;
+
+ //
+ // Note: The following code is not optimized for speed, but
+ // rather for correctness. Speed will be addressed in the future.
+ //
+ while (current < buffer.getLength() && !done) {
+ unsigned int c = buffer.readUint8();
+ current++;
+ if (!seen_pointer) {
+ cused++;
+ }
+
+ switch (state) {
+ case fw_start:
+ if (c <= MAX_LABELLEN) {
+ offsets.push_back(nused);
+ if (nused + c + 1 > Name::MAX_WIRE) {
+ isc_throw(DNSMessageFORMERR, "wire name is too long: "
+ << nused + c + 1 << " bytes");
+ }
+ nused += c + 1;
+ ndata_.push_back(c);
+ if (c == 0) {
+ done = true;
+ }
+ n = c;
+ state = fw_ordinary;
+ } else if ((c & COMPRESS_POINTER_MARK8) == COMPRESS_POINTER_MARK8) {
+ //
+ // Ordinary 14-bit pointer.
+ //
+ new_current = c & ~COMPRESS_POINTER_MARK8;
+ n = 1;
+ state = fw_newcurrent;
+ } else {
+ // this case includes local compression pointer, which hasn't
+ // been standardized.
+ isc_throw(DNSMessageFORMERR, "unknown label character: " << c);
+ }
+ break;
+ case fw_ordinary:
+ if (downcase) {
+ c = maptolower[c];
+ }
+ ndata_.push_back(c);
+ if (--n == 0) {
+ state = fw_start;
+ }
+ break;
+ case fw_newcurrent:
+ new_current *= 256;
+ new_current += c;
+ if (--n != 0) {
+ break;
+ }
+ if (new_current >= biggest_pointer) {
+ isc_throw(DNSMessageFORMERR,
+ "bad compression pointer (out of range): " <<
+ new_current);
+ }
+ biggest_pointer = new_current;
+ current = new_current;
+ buffer.setPosition(current);
+ seen_pointer = true;
+ state = fw_start;
+ break;
+ default:
+ isc_throw_assert(false);
+ }
+ }
+
+ if (!done) {
+ isc_throw(DNSMessageFORMERR, "incomplete wire-format name");
+ }
+
+ labelcount_ = offsets.size();
+ length_ = nused;
+ offsets_.assign(offsets.begin(), offsets.end());
+ buffer.setPosition(pos_begin + cused);
+}
+
+void
+Name::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(ndata_.data(), ndata_.size());
+}
+
+void
+Name::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(*this);
+}
+
+std::string
+Name::toText(bool omit_final_dot) const {
+ LabelSequence ls(*this);
+ return (ls.toText(omit_final_dot));
+}
+
+std::string
+Name::toRawText(bool omit_final_dot) const {
+ LabelSequence ls(*this);
+ return (ls.toRawText(omit_final_dot));
+}
+
+NameComparisonResult
+Name::compare(const Name& other) const {
+ const LabelSequence ls1(*this);
+ const LabelSequence ls2(other);
+ return (ls1.compare(ls2));
+}
+
+bool
+Name::equals(const Name& other) const {
+ if (length_ != other.length_ || labelcount_ != other.labelcount_) {
+ return (false);
+ }
+
+ for (unsigned int l = labelcount_, pos = 0; l > 0; --l) {
+ uint8_t count = ndata_[pos];
+ if (count != other.ndata_[pos]) {
+ return (false);
+ }
+ ++pos;
+
+ while (count-- > 0) {
+ uint8_t label1 = ndata_[pos];
+ uint8_t label2 = other.ndata_[pos];
+
+ if (maptolower[label1] != maptolower[label2]) {
+ return (false);
+ }
+ ++pos;
+ }
+ }
+
+ return (true);
+}
+
+bool
+Name::leq(const Name& other) const {
+ return (compare(other).getOrder() <= 0);
+}
+
+bool
+Name::geq(const Name& other) const {
+ return (compare(other).getOrder() >= 0);
+}
+
+bool
+Name::lthan(const Name& other) const {
+ return (compare(other).getOrder() < 0);
+}
+
+bool
+Name::gthan(const Name& other) const {
+ return (compare(other).getOrder() > 0);
+}
+
+bool
+Name::isWildcard() const {
+ return (length_ >= 2 && ndata_[0] == 1 && ndata_[1] == '*');
+}
+
+Name
+Name::concatenate(const Name& suffix) const {
+ isc_throw_assert(length_ > 0 && suffix.length_ > 0);
+ isc_throw_assert(labelcount_ > 0 && suffix.labelcount_ > 0);
+
+ unsigned int length = length_ + suffix.length_ - 1;
+ if (length > Name::MAX_WIRE) {
+ isc_throw(TooLongName, "names are too long to concatenate");
+ }
+
+ Name retname;
+ retname.ndata_.reserve(length);
+ retname.ndata_.assign(ndata_, 0, length_ - 1);
+ retname.ndata_.insert(retname.ndata_.end(),
+ suffix.ndata_.begin(), suffix.ndata_.end());
+ isc_throw_assert(retname.ndata_.size() == length);
+ retname.length_ = length;
+
+ //
+ // Setup the offsets vector. Copy the offsets of this (prefix) name,
+ // excluding that for the trailing dot, and append the offsets of the
+ // suffix name with the additional offset of the length of the prefix.
+ //
+ unsigned int labels = labelcount_ + suffix.labelcount_ - 1;
+ isc_throw_assert(labels <= Name::MAX_LABELS);
+ retname.offsets_.reserve(labels);
+ retname.offsets_.assign(&offsets_[0], &offsets_[0] + labelcount_ - 1);
+ transform(suffix.offsets_.begin(), suffix.offsets_.end(),
+ back_inserter(retname.offsets_),
+ [this] (char x) { return (x + length_ - 1); });
+ isc_throw_assert(retname.offsets_.size() == labels);
+ retname.labelcount_ = labels;
+
+ return (retname);
+}
+
+Name
+Name::reverse() const {
+ Name retname;
+ //
+ // Set up offsets: The size of the string and number of labels will
+ // be the same in as in the original.
+ //
+ retname.offsets_.reserve(labelcount_);
+ retname.ndata_.reserve(length_);
+
+ // Copy the original name, label by label, from tail to head.
+ NameOffsets::const_reverse_iterator rit0 = offsets_.rbegin();
+ NameOffsets::const_reverse_iterator rit1 = rit0 + 1;
+ NameString::const_iterator n0 = ndata_.begin();
+ retname.offsets_.push_back(0);
+ while (rit1 != offsets_.rend()) {
+ retname.ndata_.append(n0 + *rit1, n0 + *rit0);
+ retname.offsets_.push_back(retname.ndata_.size());
+ ++rit0;
+ ++rit1;
+ }
+ retname.ndata_.push_back(0);
+
+ retname.labelcount_ = labelcount_;
+ retname.length_ = length_;
+
+ return (retname);
+}
+
+Name
+Name::split(const unsigned int first, const unsigned int n) const {
+ if (n == 0 || n > labelcount_ || first > labelcount_ - n) {
+ isc_throw(OutOfRange, "Name::split: invalid split range");
+ }
+
+ Name retname;
+ // If the specified range doesn't include the trailing dot, we need one
+ // more label for that.
+ unsigned int newlabels = (first + n == labelcount_) ? n : n + 1;
+
+ //
+ // Set up offsets: copy the corresponding range of the original offsets
+ // with subtracting an offset of the prefix length.
+ //
+ retname.offsets_.reserve(newlabels);
+ transform(offsets_.begin() + first, offsets_.begin() + first + newlabels,
+ back_inserter(retname.offsets_),
+ [&](char x) { return (x - offsets_[first]); });
+
+ //
+ // Set up the new name. At this point the tail of the new offsets specifies
+ // the position of the trailing dot, which should be equal to the length of
+ // the extracted portion excluding the dot. First copy that part from the
+ // original name, and append the trailing dot explicitly.
+ //
+ retname.ndata_.reserve(retname.offsets_.back() + 1);
+ retname.ndata_.assign(ndata_, offsets_[first], retname.offsets_.back());
+ retname.ndata_.push_back(0);
+
+ retname.length_ = retname.ndata_.size();
+ retname.labelcount_ = retname.offsets_.size();
+ isc_throw_assert(retname.labelcount_ == newlabels);
+
+ return (retname);
+}
+
+Name
+Name::split(const unsigned int level) const {
+ if (level >= getLabelCount()) {
+ isc_throw(OutOfRange, "invalid level for name split (" << level
+ << ") for name " << *this);
+ }
+
+ return (split(level, getLabelCount() - level));
+}
+
+Name&
+Name::downcase() {
+ unsigned int nlen = length_;
+ unsigned int labels = labelcount_;
+ unsigned int pos = 0;
+
+ while (labels > 0 && nlen > 0) {
+ --labels;
+ --nlen;
+
+ // we assume a valid name, and do abort() if the assumption fails
+ // rather than throwing an exception.
+ unsigned int count = ndata_.at(pos++);
+ isc_throw_assert(count <= MAX_LABELLEN);
+ isc_throw_assert(nlen >= count);
+
+ while (count > 0) {
+ ndata_.at(pos) =
+ maptolower[ndata_.at(pos)];
+ ++pos;
+ --nlen;
+ --count;
+ }
+ }
+
+ return (*this);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Name& name) {
+ os << name.toText();
+ return (os);
+}
+
+}
+}
diff --git a/src/lib/dns/name.h b/src/lib/dns/name.h
new file mode 100644
index 0000000..1a64f19
--- /dev/null
+++ b/src/lib/dns/name.h
@@ -0,0 +1,758 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NAME_H
+#define NAME_H
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/exceptions.h>
+
+namespace isc {
+namespace dns {
+///
+/// \brief A standard DNS module exception that is thrown if the name parser
+/// encounters an empty label in the middle of a name.
+///
+class EmptyLabel : public NameParserException {
+public:
+ EmptyLabel(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if the name parser
+/// encounters too long a name.
+///
+class TooLongName : public NameParserException {
+public:
+ TooLongName(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if the name parser
+/// encounters too long a label.
+///
+class TooLongLabel : public NameParserException {
+public:
+ TooLongLabel(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if the name parser
+/// encounters an obsolete or incomplete label type. In effect "obsolete" only
+/// applies to bitstring labels, which would begin with "\[". Incomplete cases
+/// include an incomplete escaped sequence such as "\12".
+///
+class BadLabelType : public NameParserException {
+public:
+ BadLabelType(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if the name parser
+/// fails to decode a back-slash escaped sequence.
+///
+class BadEscape : public NameParserException {
+public:
+ BadEscape(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if the name parser
+/// finds the input (string or wire-format %data) is incomplete.
+///
+/// An attempt of constructing a name from an empty string will trigger this
+/// exception.
+///
+class IncompleteName : public NameParserException {
+public:
+ IncompleteName(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+/// \brief Thrown when origin is null and is needed.
+///
+/// The exception is thrown when the Name constructor for master file
+/// is used, the provided data is relative and the origin parameter is
+/// set to null.
+class MissingNameOrigin : public NameParserException {
+public:
+ MissingNameOrigin(const char* file, size_t line, const char* what) :
+ NameParserException(file, line, what) {}
+};
+
+///
+/// This is a supplemental class used only as a return value of
+/// Name::compare() and LabelSequence::compare().
+/// It encapsulate a tuple of the comparison: ordering, number of common
+/// labels, and relationship as follows:
+/// - ordering: relative ordering under the DNSSEC order relation
+/// - labels: the number of common significant labels of the two names or
+/// two label sequences being compared
+/// - relationship: see NameComparisonResult::NameRelation
+///
+/// Note that the ordering is defined for two label sequences that have no
+/// hierarchical relationship (in which case the relationship will be NONE).
+/// For example, two non absolute (or "relative") sequences "example.com" and
+/// "example.org" have no hierarchical relationship, and the former should be
+/// sorted before (i.e. "smaller") than the latter.
+class NameComparisonResult {
+public:
+ /// The relation of two names under comparison.
+ /// Its semantics for the case of
+ /// <code>name1->compare(name2)</code> (where name1 and name2 are instances
+ /// of the \c Name or \c LabelSequence class) is as follows:
+ /// - SUPERDOMAIN: name1 properly contains name2; name2 is a proper
+ /// subdomain of name1
+ /// - SUBDOMAIN: name1 is a proper subdomain of name2
+ /// - EQUAL: name1 and name2 are equal
+ /// - COMMONANCESTOR: name1 and name2 share a common ancestor
+ /// - NONE: There's no hierarchical relationship between name1 and name2
+ ///
+ /// Note that there's always a hierarchical relationship between any two
+ /// names since all names (not generic label sequences) are absolute and
+ /// they at least share the trailing empty label.
+ /// So, for example, the relationship between "com." and "net." is
+ /// "commonancestor". The relationship of "NONE" can only happen for
+ /// comparison between two label sequences (\c LabelSequence objects);
+ /// usually only SUPERDOMAIN, SUBDOMAIN or EQUAL are important relationship
+ /// between two names.
+ ///
+ /// When two \c LabelSequence objects are compared, it's generally expected
+ /// they are either both absolute or both non absolute; if one is absolute
+ /// and the other is not, the resulting relationship will be NONE.
+ enum NameRelation {
+ SUPERDOMAIN = 0,
+ SUBDOMAIN = 1,
+ EQUAL = 2,
+ COMMONANCESTOR = 3,
+ NONE = 4
+ };
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+ /// \brief Constructor from a comparison tuple
+ ///
+ /// This constructor simply initializes the object in the straightforward
+ /// way.
+ NameComparisonResult(int order, unsigned int nlabels,
+ NameRelation relation) :
+ order_(order), nlabels_(nlabels), relation_(relation) {}
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// Returns the ordering of the comparison result
+ int getOrder() const { return (order_); }
+ /// Returns the number of common labels of the comparison result
+ unsigned int getCommonLabels() const { return (nlabels_); }
+ /// Returns the NameRelation of the comparison result
+ NameRelation getRelation() const { return (relation_); }
+ //@}
+private:
+ int order_;
+ unsigned int nlabels_;
+ NameRelation relation_;
+};
+
+///
+/// The \c Name class encapsulates DNS names.
+///
+/// It provides interfaces to construct a name from string or wire-format %data,
+/// transform a name into a string or wire-format %data, compare two names, get
+/// access to various properties of a name, etc.
+///
+/// Notes to developers: Internally, a name object maintains the name %data
+/// in wire format as an instance of \c std::string. Since many string
+/// implementations adopt copy-on-write %data sharing, we expect this approach
+/// will make copying a name less expensive in typical cases. If this is
+/// found to be a significant performance bottleneck later, we may reconsider
+/// the internal representation or perhaps the API.
+///
+/// A name object also maintains a vector of offsets (\c offsets_ member),
+/// each of which is the offset to a label of the name: The n-th element of
+/// the vector specifies the offset to the n-th label. For example, if the
+/// object represents "www.example.com", the elements of the offsets vector
+/// are 0, 4, 12, and 16. Note that the offset to the trailing dot (16) is
+/// included. In the BIND9 DNS library from which this implementation is
+/// derived, the offsets are optional, probably due to performance
+/// considerations (in fact, offsets can always be calculated from the name
+/// %data, and in that sense are redundant). In our implementation, however,
+/// we always build and maintain the offsets. We believe we need more low
+/// level, specialized %data structure and interface where we really need to
+/// pursue performance, and would rather keep this generic API and
+/// implementation simpler.
+///
+/// While many other DNS APIs introduce an "absolute or relative"
+/// attribute of names as defined in RFC1035, names are always "absolute" in
+/// the initial design of this API.
+/// In fact, separating absolute and relative would confuse API users
+/// unnecessarily. For example, it's not so intuitive to consider the
+/// comparison result of an absolute name with a relative name.
+/// We've looked into how the concept of absolute names is used in BIND9,
+/// and found that in many cases names are generally absolute.
+/// The only reasonable case of separating absolute and relative is in a master
+/// file parser, where a relative name must be a complete name with an "origin"
+/// name, which must be absolute. So, in this initial design, we chose a
+/// simpler approach: the API generally handles names as absolute; when we
+/// introduce a parser of master files, we'll introduce the notion of relative
+/// names as a special case.
+///
+class Name {
+ // LabelSequences use knowledge about the internal data structure
+ // of this class for efficiency (they use the offsets_ vector and
+ // the ndata_ string)
+ friend class LabelSequence;
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+private:
+ /// \brief Name data string
+ typedef std::basic_string<uint8_t> NameString;
+ /// \brief Name offsets type
+ typedef std::vector<uint8_t> NameOffsets;
+
+ /// The default constructor
+ ///
+ /// This is used internally in the class implementation, but at least at
+ /// the moment defined as private because it will construct an incomplete
+ /// object in that it doesn't have any labels. We may reconsider this
+ /// design choice as we see more applications of the class.
+ Name() : length_(0), labelcount_(0) {}
+public:
+ /// Constructor from a string
+ ///
+ /// If the given string does not represent a valid DNS name, an exception
+ /// of class \c EmptyLabel, \c TooLongLabel, \c BadLabelType, \c BadEscape,
+ /// \c TooLongName, or \c IncompleteName will be thrown.
+ /// In addition, if resource allocation for the new name fails, a
+ /// corresponding standard exception will be thrown.
+ ///
+ /// \param namestr A string representation of the name to be constructed.
+ /// \param downcase Whether to convert upper case alphabets to lower case.
+ explicit Name(const std::string& namestr, bool downcase = false);
+
+ /// \brief Constructor for master file parser
+ ///
+ /// This acts similar to the above. But the data is passed as raw C-string
+ /// instead of wrapped-up C++ std::string.
+ ///
+ /// Also, when the origin is non-null and the name_data is not ending with
+ /// a dot, it is considered relative and the origin is appended to it.
+ ///
+ /// If the name_data is equal to "@", the content of origin is copied.
+ ///
+ /// \param name_data The raw data of the name.
+ /// \param data_len How many bytes in name_data is valid and considered
+ /// part of the name.
+ /// \param origin If non-null, it is taken as the origin to complete
+ /// relative names.
+ /// \param downcase Whether to convert upper case letters to lower case.
+ /// \throw NameParserException or any of its descendants in case the
+ /// input data is invalid.
+ /// \throw isc::InvalidParameter In case name_data is null or
+ /// data_len is 0.
+ /// \throw std::bad_alloc In case allocation fails.
+ /// \note This constructor is specially designed for the use of master
+ /// file parser. It mimics the behaviour of names in the master file
+ /// and accepts raw data. It is not recommended to be used by anything
+ /// else.
+ /// \todo Should we make it private and the parser a friend, to hide the
+ /// constructor?
+ Name(const char* name_data, size_t data_len, const Name* origin,
+ bool downcase = false);
+
+ /// Constructor from wire-format %data.
+ ///
+ /// The \c buffer parameter normally stores a complete DNS message
+ /// containing the name to be constructed. The current read position of
+ /// the buffer points to the head of the name.
+ ///
+ /// The input %data may or may not be compressed; if it's compressed, this
+ /// method will automatically decompress it.
+ ///
+ /// If the given %data does not represent a valid DNS name, an exception
+ /// of class \c DNSMessageFORMERR will be thrown.
+ /// In addition, if resource allocation for the new name fails, a
+ /// corresponding standard exception will be thrown.
+ ///
+ /// \param buffer A buffer storing the wire format %data.
+ /// \param downcase Whether to convert upper case alphabets to lower case.
+ explicit Name(isc::util::InputBuffer& buffer, bool downcase = false);
+ ///
+ /// We use the default copy constructor intentionally.
+ //@}
+ /// We use the default copy assignment operator intentionally.
+ ///
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Provides one-byte name %data in wire format at the specified
+ /// position.
+ ///
+ /// This method returns the unsigned 8-bit value of wire-format \c Name
+ /// %data at the given position from the head.
+ ///
+ /// For example, if \c n is a \c Name object for "example.com",
+ /// \c n.at(3) would return \c 'a', and \c n.at(7) would return \c 'e'.
+ /// Note that \c n.at(0) would be 7 (decimal), the label length of
+ /// "example", instead of \c 'e', because it returns a %data portion
+ /// in wire-format. Likewise, \c n.at(8) would return 3 (decimal)
+ /// instead of <code>'.'</code>
+ ///
+ /// This method would be useful for an application to examine the
+ /// wire-format name %data without dumping the %data into a buffer,
+ /// which would involve %data copies and would be less efficient.
+ /// One common usage of this method would be something like this:
+ /// \code for (size_t i = 0; i < name.getLength(); ++i) {
+ /// uint8_t c = name.at(i);
+ /// // do something with c
+ /// } \endcode
+ ///
+ /// Parameter \c pos must be in the valid range of the name %data, that is,
+ /// must be less than \c Name.getLength(). Otherwise, an exception of
+ /// class \c OutOfRange will be thrown.
+ /// This method never throws an exception in other ways.
+ ///
+ /// \param pos The position in the wire format name %data to be returned.
+ /// \return An unsigned 8-bit integer corresponding to the name %data
+ /// at the position of \c pos.
+ uint8_t at(size_t pos) const
+ {
+ if (pos >= length_) {
+ isc_throw(OutOfRange, "Out of range access in Name::at()");
+ }
+ return (ndata_[pos]);
+ }
+
+ /// \brief Gets the length of the <code>Name</code> in its wire format.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return the length (the number of octets in wire format) of the
+ /// <code>Name</code>
+ size_t getLength() const { return (length_); }
+
+ /// \brief Returns the number of labels contained in the <code>Name</code>.
+ ///
+ /// Note that an empty label (corresponding to a trailing '.') is counted
+ /// as a single label, so the return value of this method must be >0.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return the number of labels
+ unsigned int getLabelCount() const { return (labelcount_); }
+ //@}
+
+ ///
+ /// \name Converter methods
+ ///
+ //@{
+ /// \brief Convert the Name to a string.
+ ///
+ /// This method returns a <code>std::string</code> object representing the
+ /// Name as a string. Unless <code>omit_final_dot</code> is
+ /// <code>true</code>, the returned string ends with a dot '.'; the default
+ /// is <code>false</code>. The default value of this parameter is
+ /// <code>true</code>; converted names will have a trailing dot by default.
+ ///
+ /// This function assumes the name is in proper uncompressed wire format.
+ /// If it finds an unexpected label character including compression pointer,
+ /// an exception of class \c BadLabelType will be thrown.
+ /// In addition, if resource allocation for the result string fails, a
+ /// corresponding standard exception will be thrown.
+ //
+ /// \param omit_final_dot whether to omit the trailing dot in the output.
+ /// \return a string representation of the <code>Name</code>.
+ std::string toText(bool omit_final_dot = false) const;
+
+ /// \brief Convert the LabelSequence to a string without escape sequences.
+ ///
+ /// The string returned will contain a single character value for any
+ /// escape sequences in the label(s).
+ ///
+ /// \param omit_final_dot whether to omit the trailing dot in the output.
+ /// \return a string representation of the <code>LabelSequence</code>
+ /// that does not contain escape sequences. Default value is false.
+ std::string toRawText(bool omit_final_dot = false) const;
+
+ /// \brief Render the <code>Name</code> in the wire format with compression.
+ ///
+ /// This method dumps the Name in wire format with help of \c renderer,
+ /// which encapsulates output buffer and name compression algorithm to
+ /// render the name.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ void toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the <code>Name</code> in the wire format without
+ /// compression.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown. This can be avoided by preallocating
+ /// a sufficient size of \c buffer. Specifically, if
+ /// <code>buffer.getCapacity() - buffer.getLength() >= Name::MAX_WIRE</code>
+ /// then this method should not throw an exception.
+ ///
+ /// \param buffer An output buffer to store the wire %data.
+ void toWire(isc::util::OutputBuffer& buffer) const;
+ //@}
+
+ ///
+ /// \name Comparison methods
+ ///
+ //@{
+ /// \brief Compare two <code>Name</code>s.
+ ///
+ /// This method compares the <code>Name</code> and <code>other</code> and
+ /// returns the result in the form of a <code>NameComparisonResult</code>
+ /// object.
+ ///
+ /// Note that this is case-insensitive comparison.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the right-hand operand to compare against.
+ /// \return a <code>NameComparisonResult</code> object representing the
+ /// comparison result.
+ NameComparisonResult compare(const Name& other) const;
+
+public:
+ /// \brief Return true iff two names are equal.
+ ///
+ /// Semantically this could be implemented based on the result of the
+ /// \c compare() method, but the actual implementation uses different code
+ /// that simply performs character-by-character comparison (case
+ /// insensitive for the name label parts) on the two names. This is because
+ /// it would be much faster and the simple equality check would be pretty
+ /// common.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the <code>Name</code> object to compare against.
+ /// \return true if the two names are equal; otherwise false.
+ bool equals(const Name& other) const;
+
+ /// Same as equals()
+ bool operator==(const Name& other) const { return (equals(other)); }
+
+ /// \brief Return true iff two names are not equal.
+ ///
+ /// This method simply negates the result of \c equal() method, and in that
+ /// sense it's redundant. The separate method is provided just for
+ /// convenience.
+ bool nequals(const Name& other) const { return (!(equals(other))); }
+
+ /// Same as nequals()
+ bool operator!=(const Name& other) const { return (nequals(other)); }
+
+ /// \brief Less-than or equal comparison for Name against <code>other</code>
+ ///
+ /// The comparison is based on the result of the \c compare() method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the <code>Name</code> object to compare against.
+ /// \return true if <code>compare(other).getOrder() <= 0</code>;
+ /// otherwise false.
+ bool leq(const Name& other) const;
+
+ /// Same as leq()
+ bool operator<=(const Name& other) const { return (leq(other)); }
+
+ /// \brief Greater-than or equal comparison for Name against
+ /// <code>other</code>
+ ///
+ /// The comparison is based on the result of the \c compare() method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the <code>Name</code> object to compare against.
+ /// \return true if <code>compare(other).getOrder() >= 0</code>;
+ /// otherwise false.
+ bool geq(const Name& other) const;
+
+ /// Same as geq()
+ bool operator>=(const Name& other) const { return (geq(other)); }
+
+ /// \brief Less-than comparison for Name against <code>other</code>
+ ///
+ /// The comparison is based on the result of the \c compare() method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the <code>Name</code> object to compare against.
+ /// \return true if <code>compare(other).getOrder() < 0</code>;
+ /// otherwise false.
+ bool lthan(const Name& other) const;
+
+ /// Same as lthan()
+ bool operator<(const Name& other) const { return (lthan(other)); }
+
+ /// \brief Greater-than comparison for Name against <code>other</code>
+ ///
+ /// The comparison is based on the result of the \c compare() method.
+ ////
+ /// This method never throws an exception.
+ ///
+ /// \param other the <code>Name</code> object to compare against.
+ /// \return true if <code>compare(other).getOrder() > 0</code>;
+ /// otherwise false.
+ bool gthan(const Name& other) const;
+
+ /// Same as gthan()
+ bool operator>(const Name& other) const { return (gthan(other)); }
+ //@}
+
+ ///
+ /// \name Transformer methods
+ ///
+ //@{
+ /// \brief Extract a specified subpart of Name.
+ ///
+ /// <code>name.split(first, n)</code> constructs a new name starting from
+ /// the <code>first</code>-th label of the \c name, and subsequent \c n
+ /// labels including the \c first one. Since names in this current
+ /// implementation are always "absolute", if the specified range doesn't
+ /// contain the trailing dot of the original \c name, then a dot will be
+ /// appended to the resulting name. As a result, the number of labels
+ /// will be <code>n + 1</code>, rather than \c n. For example,
+ /// when \c n is <code>Name("www.example.com")</code>,
+ /// both <code>n.split(1, 2)</code> and <code>n.split(1, 3)</code>
+ /// will produce a name corresponding to "example.com.", which has 3 labels.
+ /// Note also that labels are counted from 0, and so <code>first = 1</code>
+ /// in this example specified the label "example", not "www".
+ ///
+ /// Parameter \c n must be larger than 0, and the range specified by
+ /// \c first and \c n must not exceed the valid range of the original name;
+ /// otherwise, an exception of class \c OutOfRange will be thrown.
+ ///
+ /// Note to developers: we may want to have different versions (signatures)
+ /// of this method. For example, we want to split the Name based on a given
+ /// suffix name.
+ ///
+ /// \param first The start position (in labels) of the extracted name
+ /// \param n Number of labels of the extracted name
+ /// \return A new Name object based on the Name containing <code>n</code>
+ /// labels including and following the <code>first</code> label.
+ Name split(unsigned int first, unsigned int n) const;
+
+ /// \brief Extract a specified super domain name of Name.
+ ///
+ /// This function constructs a new \c Name object that is a super domain
+ /// of \c this name.
+ /// The new name is \c level labels upper than \c this name.
+ /// For example, when \c name is www.example.com,
+ /// <code>name.split(1)</code> will return a \c Name object for example.com.
+ /// \c level can be 0, in which case this method returns a copy of
+ /// \c this name.
+ /// The possible maximum value for \c level is
+ /// <code>this->getLabelCount()-1</code>, in which case this method
+ /// returns a root name.
+ ///
+ /// One common expected usage of this method is to iterate over super
+ /// domains of a given name, label by label, as shown in the following
+ /// sample code:
+ /// \code // if name is www.example.com...
+ /// for (int i = 0; i < name.getLabelCount(); ++i) {
+ /// Name upper_name(name.split(i));
+ /// // upper_name'll be www.example.com., example.com., com., and then .
+ /// }
+ /// \endcode
+ ///
+ /// \c level must be smaller than the number of labels of \c this name;
+ /// otherwise an exception of class \c OutOfRange will be thrown.
+ /// In addition, if resource allocation for the new name fails, a
+ /// corresponding standard exception will be thrown.
+ ///
+ /// Note to developers: probably as easily imagined, this method is a
+ /// simple wrapper to one usage of the other
+ /// <code>split(unsigned int, unsigned int) const</code> method and is
+ /// redundant in some sense.
+ /// We provide the "redundant" method for convenience, however, because
+ /// the expected usage shown above seems to be common, and the parameters
+ /// to the other \c split(unsigned int, unsigned int) const to implement
+ /// it may not be very intuitive.
+ ///
+ /// We are also aware that it is generally discouraged to add a public
+ /// member function that could be implemented using other member functions.
+ /// We considered making it a non member function, but we could not come
+ /// up with an intuitive function name to represent the specific service.
+ /// Some other developers argued, probably partly because of the
+ /// counter intuitive function name, a different signature of \c split
+ /// would be better to improve code readability.
+ /// While that may be a matter of personal preference, we accepted the
+ /// argument. One major goal of public APIs like this is wider acceptance
+ /// from internal/external developers, so unless there is a clear advantage
+ /// it would be better to respect the preference of the API users.
+ ///
+ /// Since this method doesn't have to be a member function in other way,
+ /// it is intentionally implemented only using public interfaces of the
+ /// \c Name class; it doesn't refer to private members of the class even if
+ /// it could.
+ /// This way we hope we can avoid damaging the class encapsulation,
+ /// which is a major drawback of public member functions.
+ /// As such if and when this "method" has to be extended, it should be
+ /// implemented without the privilege of being a member function unless
+ /// there is a very strong reason to do so. In particular a minor
+ /// performance advantage shouldn't justify that approach.
+ ///
+ /// \param level The number of labels to be removed from \c this name to
+ /// create the super domain name.
+ /// (0 <= \c level < <code>this->getLabelCount()</code>)
+ /// \return A new \c Name object to be created.
+ Name split(unsigned int level) const;
+
+ /// \brief Reverse the labels of a name
+ ///
+ /// This method reverses the labels of a name. For example, if
+ /// \c this is "www.example.com.", this method will return
+ /// "com.example.www." (This is useful because DNSSEC sort order
+ /// is equivalent to a lexical sort of label-reversed names.)
+ Name reverse() const;
+
+ /// \brief Concatenate two names.
+ ///
+ /// This method appends \c suffix to \c this Name. The trailing dot of
+ /// \c this Name will be removed. For example, if \c this is "www."
+ /// and \c suffix is "example.com.", a successful return of this method
+ /// will be a name of "www.example.com."
+ ///
+ ///The resulting length of the concatenated name must not exceed
+ /// \c Name::MAX_WIRE; otherwise an exception of class
+ /// \c TooLongName will be thrown.
+ ///
+ /// \param suffix a Name object to be appended to the Name.
+ /// \return a new Name object concatenating \c suffix to \c this Name.
+ Name concatenate(const Name& suffix) const;
+
+ /// \brief Downcase all upper case alphabet characters in the name.
+ ///
+ /// This method modifies the calling object so that it can perform the
+ /// conversion as fast as possible and can be exception free.
+ ///
+ /// The return value of this version of \c downcase() is a reference to
+ /// the calling object (i.e., \c *this) so that the caller can use the
+ /// result of downcasing in a single line. For example, if variable
+ /// \c n is a \c Name class object possibly containing upper case
+ /// characters, and \c b is an \c OutputBuffer class object, then the
+ /// following code will dump the name in wire format to \c b with
+ /// downcasing upper case characters:
+ ///
+ /// \code n.downcase().toWire(b); \endcode
+ ///
+ /// Since this method modifies the calling object, a \c const name object
+ /// cannot call it. If \c n is a \c const Name class object, it must first
+ /// be copied to a different object and the latter must be used for the
+ /// downcase modification.
+ ///
+ /// \return A reference to the calling object with being downcased.
+ Name& downcase();
+ //@}
+
+ ///
+ /// \name Testing methods
+ ///
+ //@{
+ /// \brief Test if this is a wildcard name.
+ ///
+ /// \return \c true if the least significant label of this Name is
+ /// <code>'*'</code>; otherwise \c false.
+ bool isWildcard() const;
+ //@}
+
+ ///
+ /// \name Protocol constants
+ ///
+ //@{
+ /// \brief Max allowable length of domain names.
+ static const size_t MAX_WIRE = 255;
+
+ /// \brief Max allowable labels of domain names.
+ ///
+ /// This is <code>ceil(MAX_WIRE / 2)</code>, and is equal to the number of
+ /// labels of name "a.a.a.a....a." (127 "a"'s and trailing dot).
+ static const size_t MAX_LABELS = 128;
+
+ /// \brief Max allowable length of labels of a domain name.
+ static const size_t MAX_LABELLEN = 63;
+
+ /// \brief Max possible pointer value for name compression.
+ ///
+ /// This is the highest number of 14-bit unsigned integer. Name compression
+ /// pointers are identified as a 2-byte value starting with the upper two
+ /// bit being 11.
+ static const uint16_t MAX_COMPRESS_POINTER = 0x3fff;
+ /// \brief A 8-bit masked value indicating a start of compression pointer.
+ static const uint16_t COMPRESS_POINTER_MARK8 = 0xc0;
+ /// \brief A 16-bit masked value indicating a start of compression pointer.
+ static const uint16_t COMPRESS_POINTER_MARK16 = 0xc000;
+ //@}
+
+ ///
+ /// \name Well-known name constants
+ ///
+ //@{
+ /// \brief Root name (i.e. ".").
+ static const Name& ROOT_NAME();
+ //@}
+
+private:
+ NameString ndata_;
+ NameOffsets offsets_;
+ unsigned int length_;
+ unsigned int labelcount_;
+};
+
+inline const Name&
+Name::ROOT_NAME() {
+ static Name root_name(".");
+ return (root_name);
+}
+
+///
+/// \brief Insert the name as a string into stream.
+///
+/// This method convert the \c name into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described in
+/// ostream::operator<< but applied to \c Name objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param name The \c Name object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const Name& name);
+
+}
+}
+#endif // NAME_H
diff --git a/src/lib/dns/name_internal.h b/src/lib/dns/name_internal.h
new file mode 100644
index 0000000..5cf001c
--- /dev/null
+++ b/src/lib/dns/name_internal.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NAME_INTERNAL_H
+#define NAME_INTERNAL_H
+
+// This is effectively a "private" namespace for the Name class implementation,
+// but exposed publicly so the definitions in it can be shared with other
+// modules of the library (as of its introduction, used by LabelSequence and
+// MessageRenderer). It's not expected to be used even by normal applications.
+// This header file is therefore not expected to be installed as part of the
+// library.
+//
+// Note: if it turns out that we need this shortcut for many other places
+// we may even want to make it expose to other Kea modules, but for now
+// we'll keep it semi-private (note also that except for very performance
+// sensitive applications the standard std::tolower() function should be just
+// sufficient).
+namespace isc {
+namespace dns {
+namespace name {
+namespace internal {
+extern const uint8_t maptolower[];
+} // end of internal
+} // end of name
+} // end of dns
+} // end of isc
+#endif // NAME_INTERNAL_H
diff --git a/src/lib/dns/opcode.cc b/src/lib/dns/opcode.cc
new file mode 100644
index 0000000..c6e051a
--- /dev/null
+++ b/src/lib/dns/opcode.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2010-2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+#include <ostream>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/opcode.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+namespace {
+const char *opcodetext[] = {
+ "QUERY",
+ "IQUERY",
+ "STATUS",
+ "RESERVED3",
+ "NOTIFY",
+ "UPDATE",
+ "RESERVED6",
+ "RESERVED7",
+ "RESERVED8",
+ "RESERVED9",
+ "RESERVED10",
+ "RESERVED11",
+ "RESERVED12",
+ "RESERVED13",
+ "RESERVED14",
+ "RESERVED15"
+};
+
+// OPCODEs are 4-bit values. So 15 is the highest code.
+const uint8_t MAX_OPCODE = 15;
+}
+
+Opcode::Opcode(const uint8_t code) : code_(static_cast<CodeValue>(code)) {
+ if (code > MAX_OPCODE) {
+ isc_throw(OutOfRange,
+ "DNS Opcode is too large to construct: "
+ << static_cast<unsigned>(code));
+ }
+}
+
+string
+Opcode::toText() const {
+ return (opcodetext[code_]);
+}
+
+ostream&
+operator<<(std::ostream& os, const Opcode& opcode) {
+ return (os << opcode.toText());
+}
+}
+}
diff --git a/src/lib/dns/opcode.h b/src/lib/dns/opcode.h
new file mode 100644
index 0000000..f92dcde
--- /dev/null
+++ b/src/lib/dns/opcode.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <stdint.h>
+
+#include <ostream>
+
+#ifndef OPCODE_H
+#define OPCODE_H
+
+namespace isc {
+namespace dns {
+
+/// \brief The \c Opcode class objects represent standard OPCODEs
+/// of the header section of DNS messages as defined in RFC1035.
+///
+/// This is a straightforward value class encapsulating the OPCODE code
+/// values. Since OPCODEs are 4-bit integers that are used in limited
+/// places and it's unlikely that new code values will be assigned, we could
+/// represent them as simple integers (via constant variables or enums).
+/// However, we define a separate class so that we can benefit from C++
+/// type safety as much as possible. For convenience we also provide
+/// an enum type for standard OPCDE values, but it is generally advisable
+/// to handle OPCODEs through this class. In fact, public interfaces of
+/// this library uses this class to pass or return OPCODEs instead of the
+/// bare code values.
+class Opcode {
+public:
+ /// Constants for standard OPCODE values.
+ enum CodeValue {
+ QUERY_CODE = 0, ///< 0: Standard query (RFC1035)
+ IQUERY_CODE = 1, ///< 1: Inverse query (RFC1035)
+ STATUS_CODE = 2, ///< 2: Server status request (RFC1035)
+ RESERVED3_CODE = 3, ///< 3: Reserved for future use (RFC1035)
+ NOTIFY_CODE = 4, ///< 4: Notify (RFC1996)
+ UPDATE_CODE = 5, ///< 5: Dynamic update (RFC2136)
+ RESERVED6_CODE = 6, ///< 6: Reserved for future use (RFC1035)
+ RESERVED7_CODE = 7, ///< 7: Reserved for future use (RFC1035)
+ RESERVED8_CODE = 8, ///< 8: Reserved for future use (RFC1035)
+ RESERVED9_CODE = 9, ///< 9: Reserved for future use (RFC1035)
+ RESERVED10_CODE = 10, ///< 10: Reserved for future use (RFC1035)
+ RESERVED11_CODE = 11, ///< 11: Reserved for future use (RFC1035)
+ RESERVED12_CODE = 12, ///< 12: Reserved for future use (RFC1035)
+ RESERVED13_CODE = 13, ///< 13: Reserved for future use (RFC1035)
+ RESERVED14_CODE = 14, ///< 14: Reserved for future use (RFC1035)
+ RESERVED15_CODE = 15 ///< 15: Reserved for future use (RFC1035)
+ };
+
+ /// \name Constructors and Destructor
+ ///
+ /// We use the default versions of destructor, copy constructor,
+ /// and assignment operator.
+ ///
+ /// The default constructor is hidden as a result of defining the other
+ /// constructors. This is intentional; we don't want to allow an
+ /// \c Opcode object to be constructed with an invalid state.
+ //@{
+ /// \brief Constructor from the code value.
+ ///
+ /// Since OPCODEs are 4-bit values, parameters larger than 15 are invalid.
+ /// If \c code is larger than 15 an exception of class \c isc::OutOfRange
+ /// will be thrown.
+ ///
+ /// \param code The underlying code value of the \c Opcode.
+ explicit Opcode(const uint8_t code);
+ //@}
+
+ /// \brief Returns the \c Opcode code value.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The underlying code value corresponding to the \c Opcode.
+ CodeValue getCode() const {
+ return (code_);
+ }
+
+ /// \brief Return true iff two Opcodes are equal.
+ ///
+ /// Two Opcodes are equal iff their type codes are equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c Opcode object to compare against.
+ /// \return true if the two Opcodes are equal; otherwise false.
+ bool equals(const Opcode& other) const {
+ return (code_ == other.code_);
+ }
+
+ /// \brief Same as \c equals().
+ bool operator==(const Opcode& other) const {
+ return (equals(other));
+ }
+
+ /// \brief Return true iff two Opcodes are not equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c Opcode object to compare against.
+ /// \return true if the two Opcodes are not equal; otherwise false.
+ bool nequals(const Opcode& other) const {
+ return (code_ != other.code_);
+ }
+
+ /// \brief Same as \c nequals().
+ bool operator!=(const Opcode& other) const {
+ return (nequals(other));
+ }
+
+ /// \brief Convert the \c Opcode to a string.
+ ///
+ /// This method returns a string representation of the "mnemonic' used
+ /// for the enum and constant objects. For example, the string for
+ /// code value 0 is "QUERY", etc.
+ ///
+ /// If resource allocation for the string fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \return A string representation of the \c Opcode.
+ std::string toText() const;
+
+ /// A constant object for the QUERY Opcode.
+ static const Opcode& QUERY();
+
+ /// A constant object for the IQUERY Opcode.
+ static const Opcode& IQUERY();
+
+ /// A constant object for the STATUS Opcode.
+ static const Opcode& STATUS();
+
+ /// A constant object for a reserved (code 3) Opcode.
+ static const Opcode& RESERVED3();
+
+ /// A constant object for the NOTIFY Opcode.
+ static const Opcode& NOTIFY();
+
+ /// A constant object for the UPDATE Opcode.
+ static const Opcode& UPDATE();
+
+ /// A constant object for a reserved (code 6) Opcode.
+ static const Opcode& RESERVED6();
+
+ /// A constant object for a reserved (code 7) Opcode.
+ static const Opcode& RESERVED7();
+
+ /// A constant object for a reserved (code 8) Opcode.
+ static const Opcode& RESERVED8();
+
+ /// A constant object for a reserved (code 9) Opcode.
+ static const Opcode& RESERVED9();
+
+ /// A constant object for a reserved (code 10) Opcode.
+ static const Opcode& RESERVED10();
+
+ /// A constant object for a reserved (code 11) Opcode.
+ static const Opcode& RESERVED11();
+
+ /// A constant object for a reserved (code 12) Opcode.
+ static const Opcode& RESERVED12();
+
+ /// A constant object for a reserved (code 13) Opcode.
+ static const Opcode& RESERVED13();
+
+ /// A constant object for a reserved (code 14) Opcode.
+ static const Opcode& RESERVED14();
+
+ /// A constant object for a reserved (code 15) Opcode.
+ static const Opcode& RESERVED15();
+private:
+ CodeValue code_;
+};
+
+inline const Opcode&
+Opcode::QUERY() {
+ static Opcode c(0);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::IQUERY() {
+ static Opcode c(1);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::STATUS() {
+ static Opcode c(2);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED3() {
+ static Opcode c(3);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::NOTIFY() {
+ static Opcode c(4);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::UPDATE() {
+ static Opcode c(5);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED6() {
+ static Opcode c(6);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED7() {
+ static Opcode c(7);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED8() {
+ static Opcode c(8);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED9() {
+ static Opcode c(9);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED10() {
+ static Opcode c(10);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED11() {
+ static Opcode c(11);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED12() {
+ static Opcode c(12);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED13() {
+ static Opcode c(13);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED14() {
+ static Opcode c(14);
+ return (c);
+}
+
+inline const Opcode&
+Opcode::RESERVED15() {
+ static Opcode c(15);
+ return (c);
+}
+
+/// \brief Insert the \c Opcode as a string into stream.
+///
+/// This method convert \c opcode into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param opcode A reference to an \c Opcode object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const Opcode& opcode);
+}
+}
+#endif // OPCODE_H
diff --git a/src/lib/dns/question.cc b/src/lib/dns/question.cc
new file mode 100644
index 0000000..af52842
--- /dev/null
+++ b/src/lib/dns/question.cc
@@ -0,0 +1,81 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <util/buffer.h>
+
+#include <iostream>
+#include <string>
+
+using namespace isc::util;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+Question::Question(InputBuffer& buffer) :
+ name_(buffer), rrtype_(0), rrclass_(0) {
+ // In theory, we could perform this in the member initialization list,
+ // and it would be a little bit more efficient. We don't do this, however,
+ // because the initialization ordering is crucial (type must be first)
+ // and the ordering in the initialization list depends on the appearance
+ // order of member variables. It's fragile to rely on such an implicit
+ // dependency, so we make the initialization order explicit.
+ rrtype_ = RRType(buffer);
+ rrclass_ = RRClass(buffer);
+}
+
+std::string
+Question::toText(bool newline) const {
+ std::string r(name_.toText() + " " + rrclass_.toText() + " " +
+ rrtype_.toText());
+ if (newline) {
+ r.append("\n");
+ }
+
+ return (r);
+}
+
+uint32_t
+Question::toWire(OutputBuffer& buffer) const {
+ name_.toWire(buffer);
+ rrtype_.toWire(buffer);
+ rrclass_.toWire(buffer); // number of "entries", which is always 1
+
+ return (1);
+}
+
+uint32_t
+Question::toWire(AbstractMessageRenderer& renderer) const {
+ const size_t pos0 = renderer.getLength();
+
+ renderer.writeName(name_);
+ rrtype_.toWire(renderer);
+ rrclass_.toWire(renderer);
+
+ // Make sure the renderer has a room for the question
+ if (renderer.getLength() > renderer.getLengthLimit()) {
+ renderer.trim(renderer.getLength() - pos0);
+ renderer.setTruncated();
+ return (0);
+ }
+
+ return (1); // number of "entries"
+}
+
+ostream&
+operator<<(std::ostream& os, const Question& question) {
+ os << question.toText();
+ return (os);
+}
+}
+}
diff --git a/src/lib/dns/question.h b/src/lib/dns/question.h
new file mode 100644
index 0000000..efd6c3f
--- /dev/null
+++ b/src/lib/dns/question.h
@@ -0,0 +1,286 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef QUESTION_H
+#define QUESTION_H
+
+#include <iostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace dns {
+class Question;
+
+/// \brief A pointer-like type pointing to an \c Question object.
+typedef boost::shared_ptr<Question> QuestionPtr;
+
+/// \brief A pointer-like type pointing to an (immutable) \c Question object.
+typedef boost::shared_ptr<const Question> ConstQuestionPtr;
+
+/// \brief The \c Question class encapsulates the common search key of DNS
+/// lookup, consisting of owner name, RR type and RR class.
+///
+/// The primarily intended use case of this class is an entry of the question
+/// section of DNS messages.
+/// This could also be used as a general purpose lookup key, e.g., in a
+/// custom implementation of DNS database.
+///
+/// In this initial implementation, the \c Question class is defined as
+/// a <em>concrete class</em>; it's not expected to be inherited by
+/// a user-defined customized class.
+/// It may be worth noting that it's different from the design of the
+/// RRset classes (\c AbstractRRset and its derived classes).
+/// The RRset classes form an inheritance hierarchy from the base abstract
+/// class.
+/// This may look odd in that an "RRset" and "Question" are similar from the
+/// protocol point of view: Both are used as a semantics unit of DNS messages;
+/// both share the same set of components (name, RR type and RR class).
+///
+/// In fact, BIND9 didn't introduce a separate data structure for Questions,
+/// and use the same \c "rdataset" structure for both RRsets and Questions.
+/// We could take the same approach, but chose to adopt the different design.
+/// One reason for that is because a Question and an RRset are still
+/// different, and a Question might not be cleanly defined, e.g., if it were
+/// a derived class of some "RRset-like" class.
+/// For example, we couldn't give a reasonable semantics for \c %getTTL() or
+/// \c %setTTL() methods for a Question, since it's not associated with the
+/// TTL.
+/// In fact, the BIND9 implementation ended up often separating the case where
+/// a \c "rdataset" is from the Question section of a DNS message and the
+/// case where it comes from other sections.
+/// If we cannot treat them completely transparently anyway, separating the
+/// class (type) would make more sense because we can exploit compilation
+/// time type checks.
+///
+/// On the other hand, we do not expect a strong need for customizing the
+/// \c Question class, unlike the RRset.
+/// Handling the "Question" section of a DNS message is relatively a
+/// simple work comparing to RRset-involved operations, so a unified
+/// straightforward implementation should suffice for any use cases
+/// including performance sensitive ones.
+///
+/// We may, however, still want to have a customized version of Question
+/// for, e.g, highly optimized behavior, and may revisit this design choice
+/// as we have more experience with this implementation.
+///
+/// One disadvantage of defining RRsets and Questions as unrelated classes
+/// is that we cannot handle them in a polymorphic way.
+/// For example, we might want to iterate over DNS message sections and
+/// render the data in the wire format, whether it's an RRset or a Question.
+/// If a \c Question were a derived class of some common RRset-like class,
+/// we could do this by calling <code>rrset_or_question->%toWire()</code>.
+/// But the actual design doesn't allow this approach, which may require
+/// duplicate code for almost the same operation.
+/// To mitigate this problem, we intentionally used the same names
+/// with the same signature for some common methods of \c Question and
+/// \c AbstractRRset such as \c %getName() or \c %toWire().
+/// So the user class may use a template function that is applicable to both
+/// \c Question and \c RRset to avoid writing duplicate code logic.
+class Question {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// We use the default versions of destructor, copy constructor,
+ /// and assignment operator.
+ ///
+ /// The default constructor is hidden as a result of defining the other
+ /// constructors. This is intentional; we don't want to allow a
+ /// \c Question object to be constructed with an invalid state.
+ //@{
+public:
+ /// \brief Constructor from wire-format data.
+ ///
+ /// It simply constructs a set of \c Name, \c RRType, and \c RRClass
+ /// object from the \c buffer in this order, and constructs a
+ /// \c Question object in a straightforward way.
+ ///
+ /// It may throw an exception if the construction of these component
+ /// classes fails.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ Question(isc::util::InputBuffer& buffer);
+
+ /// \brief Constructor from fixed parameters of the \c Question.
+ ///
+ /// This constructor is basically expected to be exception free, but
+ /// copying the name may involve resource allocation, and if it fails
+ /// the corresponding standard exception will be thrown.
+ ///
+ /// \param name The owner name of the \c Question.
+ /// \param rrclass The RR class of the \c Question.
+ /// \param rrtype The RR type of the \c Question.
+ Question(const Name& name, const RRClass& rrclass, const RRType& rrtype) :
+ name_(name), rrtype_(rrtype), rrclass_(rrclass) {
+ }
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Returns the owner name of the \c Question.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// \c Question owner name.
+ const Name& getName() const {
+ return (name_);
+ }
+
+ /// \brief Returns the RR Class of the \c Question.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c RRClass class object corresponding to the
+ /// RR class of the \c Question.
+ const RRType& getType() const {
+ return (rrtype_);
+ }
+
+ /// \brief Returns the RR Type of the \c Question.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c RRType class object corresponding to the
+ /// RR type of the \c Question.
+ const RRClass& getClass() const {
+ return (rrclass_);
+ }
+ //@}
+
+ ///
+ /// \name Converter Methods
+ ///
+ //@{
+ /// \brief Convert the Question to a string.
+ ///
+ /// When \c newline argument is \c true, this method terminates the
+ /// resulting string with a trailing newline character (following
+ /// the BIND9 convention).
+ ///
+ /// This method simply calls the \c %toText() methods of the corresponding
+ /// \c Name, \c RRType and \c RRClass classes for this \c Question, and
+ /// these methods may throw an exception.
+ /// In particular, if resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \param newline Whether to add a trailing newline. If true, a
+ /// trailing newline is added. If false, no trailing newline is
+ /// added.
+ ///
+ /// \return A string representation of the \c Question.
+ std::string toText(bool newline = false) const;
+
+ /// \brief Render the Question in the wire format with name compression.
+ ///
+ /// This method simply calls the \c %toWire() methods of the corresponding
+ /// \c Name, \c RRType and \c RRClass classes for this \c Question, and
+ /// these methods may throw an exception.
+ /// In particular, if resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// This method returns 1, which is the number of "questions" contained
+ /// in the \c Question.
+ /// This is a meaningless value, but is provided to be consistent with
+ /// the corresponding method of \c AbstractRRset (see the detailed
+ /// class description).
+ ///
+ /// The owner name will be compressed if possible, although it's an
+ /// unlikely event in practice because the Question section a DNS
+ /// message normally doesn't contain multiple question entries and
+ /// it's located right after the Header section.
+ /// Nevertheless, \c renderer records the information of the owner name
+ /// so that it can be pointed by other RRs in other sections (which is
+ /// more likely to happen).
+ ///
+ /// It could be possible, though very rare in practice, that
+ /// an attempt to render a Question may cause truncation
+ /// (when the Question section contains a large number of entries).
+ /// In such a case this method avoid the rendering and indicate the
+ /// truncation in the \c renderer. This method returns 0 in this case.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ ///
+ /// \return 1 on success; 0 if it causes truncation
+ uint32_t toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the Question in the wire format without name compression.
+ ///
+ /// This method behaves like the render version except it doesn't compress
+ /// the owner name.
+ /// See \c toWire(AbstractMessageRenderer& renderer)const.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ /// \return 1
+ uint32_t toWire(isc::util::OutputBuffer& buffer) const;
+ //@}
+
+ ///
+ /// \name Comparison Operators
+ ///
+ //@{
+ /// A "less than" operator is needed for this class so it can
+ /// function as an index to std::map.
+ bool operator <(const Question& rhs) const {
+ return (rrclass_ < rhs.rrclass_ ||
+ (rrclass_ == rhs.rrclass_ &&
+ (rrtype_ < rhs.rrtype_ ||
+ (rrtype_ == rhs.rrtype_ && (name_ < rhs.name_)))));
+ }
+
+ /// Equality operator. Primarily used to compare the question section in
+ /// a response to that in the query.
+ ///
+ /// \param rhs Question to compare against
+ /// \return true if name, class and type are equal, false otherwise
+ bool operator==(const Question& rhs) const {
+ return ((rrclass_ == rhs.rrclass_) && (rrtype_ == rhs.rrtype_) &&
+ (name_ == rhs.name_));
+ }
+
+ /// Inequality operator. Primarily used to compare the question section in
+ /// a response to that in the query.
+ ///
+ /// \param rhs Question to compare against
+ /// \return true if one or more of the name, class and type do not match,
+ /// false otherwise.
+ bool operator!=(const Question& rhs) const {
+ return (!operator==(rhs));
+ }
+ //@}
+
+private:
+ Name name_;
+ RRType rrtype_;
+ RRClass rrclass_;
+};
+
+/// \brief Insert the \c Question as a string into stream.
+///
+/// This method convert the \c question into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global \c operator<< to behave as described in
+/// \c %ostream::%operator<< but applied to Question objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param question A reference to a \c Question object output by the
+/// operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const Question& question);
+} // end of namespace dns
+} // end of namespace isc
+#endif // QUESTION_H
diff --git a/src/lib/dns/rcode.cc b/src/lib/dns/rcode.cc
new file mode 100644
index 0000000..34d93ce
--- /dev/null
+++ b/src/lib/dns/rcode.cc
@@ -0,0 +1,95 @@
+// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+#include <sstream>
+#include <ostream>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+namespace {
+// This diagram shows the wire-format representation of the 12-bit extended
+// form RCODEs and its relationship with implementation specific parameters.
+//
+// 0 3 11 15
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |UNUSED | EXTENDED-RCODE | RCODE |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// <= EXTRCODE_SHIFT (4 bits)
+const unsigned int EXTRCODE_SHIFT = 4;
+const unsigned int RCODE_MASK = 0x000f;
+
+
+// EDNS-extended RCODEs are 12-bit unsigned integers. 0xfff is the highest.
+const uint16_t MAX_RCODE = 0xfff;
+
+const char* const rcodetext[] = {
+ "NOERROR",
+ "FORMERR",
+ "SERVFAIL",
+ "NXDOMAIN",
+ "NOTIMP",
+ "REFUSED",
+ "YXDOMAIN",
+ "YXRRSET",
+ "NXRRSET",
+ "NOTAUTH",
+ "NOTZONE",
+ "RESERVED11",
+ "RESERVED12",
+ "RESERVED13",
+ "RESERVED14",
+ "RESERVED15",
+ "BADVERS"
+};
+}
+
+Rcode::Rcode(const uint16_t code) : code_(code) {
+ if (code_ > MAX_RCODE) {
+ isc_throw(OutOfRange, "Rcode is too large to construct");
+ }
+}
+
+Rcode::Rcode(const uint8_t code, const uint8_t extended_code) :
+ code_((extended_code << EXTRCODE_SHIFT) | (code & RCODE_MASK))
+{
+ if (code > RCODE_MASK) {
+ isc_throw(OutOfRange,
+ "Base Rcode is too large to construct: "
+ << static_cast<unsigned int>(code));
+ }
+}
+
+uint8_t
+Rcode::getExtendedCode() const {
+ return (code_ >> EXTRCODE_SHIFT);
+}
+
+string
+Rcode::toText() const {
+ if (code_ < sizeof(rcodetext) / sizeof (const char*)) {
+ return (rcodetext[code_]);
+ }
+
+ ostringstream oss;
+ oss << code_;
+ return (oss.str());
+}
+
+ostream&
+operator<<(std::ostream& os, const Rcode& rcode) {
+ return (os << rcode.toText());
+}
+}
+}
diff --git a/src/lib/dns/rcode.h b/src/lib/dns/rcode.h
new file mode 100644
index 0000000..391456c
--- /dev/null
+++ b/src/lib/dns/rcode.h
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <stdint.h>
+
+#include <ostream>
+
+#ifndef RCODE_H
+#define RCODE_H
+
+namespace isc {
+namespace dns {
+
+/// \brief DNS Response Codes (RCODEs) class.
+///
+/// The \c Rcode class objects represent standard Response Codes
+/// (RCODEs) of the header section of DNS messages, and extended response
+/// codes as defined in the EDNS specification.
+///
+/// Originally RCODEs were defined as 4-bit integers (RFC1035), and then
+/// extended to 12 bits as part of the %EDNS specification (RFC2671).
+/// This API uses the 12-bit version of the definition from the beginning;
+/// applications don't have to aware of the original definition except when
+/// dealing with the wire-format representation of the %EDNS OPT RR
+/// (which is rare).
+///
+/// Like the \c Opcode class, Rcodes could be represented as bare integers,
+/// but we define a separate class to benefit from C++ type safety.
+///
+/// For convenience we also provide
+/// an enum type for pre-defined RCODE values, but it is generally advisable
+/// to handle RCODEs through this class. In fact, public interfaces of
+/// this library uses this class to pass or return RCODEs instead of the
+/// bare code values.
+class Rcode {
+public:
+ /// Constants for pre-defined RCODE values.
+ enum CodeValue {
+ NOERROR_CODE = 0, ///< 0: No error (RFC1035)
+ FORMERR_CODE = 1, ///< 1: Format error (RFC1035)
+ SERVFAIL_CODE = 2, ///< 2: Server failure (RFC1035)
+ NXDOMAIN_CODE = 3, ///< 3: Name Error (RFC1035)
+ NOTIMP_CODE = 4, ///< 4: Not Implemented (RFC1035)
+ REFUSED_CODE = 5, ///< 5: Refused (RFC1035)
+ YXDOMAIN_CODE = 6, ///< 6: Name unexpectedly exists (RFC2136)
+ YXRRSET_CODE = 7, ///< 7: RRset unexpectedly exists (RFC2136)
+ NXRRSET_CODE = 8, ///< 8: RRset should exist but not (RFC2136)
+ NOTAUTH_CODE = 9, ///< 9: Server isn't authoritative (RFC2136)
+ NOTZONE_CODE = 10, ///< 10: Name is not within the zone (RFC2136)
+ RESERVED11_CODE = 11, ///< 11: Reserved for future use (RFC1035)
+ RESERVED12_CODE = 12, ///< 12: Reserved for future use (RFC1035)
+ RESERVED13_CODE = 13, ///< 13: Reserved for future use (RFC1035)
+ RESERVED14_CODE = 14, ///< 14: Reserved for future use (RFC1035)
+ RESERVED15_CODE = 15, ///< 15: Reserved for future use (RFC1035)
+ BADVERS_CODE = 16 ///< 16: EDNS version not implemented (RFC2671)
+ };
+
+ /// \name Constructors and Destructor
+ ///
+ /// We use the default versions of destructor, copy constructor,
+ /// and assignment operator.
+ ///
+ /// The default constructor is hidden as a result of defining the other
+ /// constructors. This is intentional; we don't want to allow an
+ /// \c Rcode object to be constructed with an invalid state.
+ //@{
+ /// \brief Constructor from the code value.
+ ///
+ /// Since RCODEs are 12-bit values, parameters larger than 0xfff are
+ /// invalid.
+ /// If \c code is larger than 0xfff an exception of class
+ /// \c isc::OutOfRange will be thrown.
+ ///
+ /// \param code The underlying 12-bit code value of the \c Rcode.
+ explicit Rcode(const uint16_t code);
+
+ /// \brief Constructor from a pair of base and extended parts of code.
+ ///
+ /// This constructor takes two parameters, one for the lower 4 bits of
+ /// the code value, the other for the upper 8 bits, and combines them
+ /// to build a complete 12-bit code value.
+ ///
+ /// The first parameter, \c code, is the lower 4 bits, and therefore must
+ /// not exceed 15. Otherwise, an exception of class
+ /// \c isc::OutOfRange will be thrown.
+ ///
+ /// This version of constructor is provided specifically for constructing
+ /// an Rcode from a DNS header and an %EDNS OPT RR. Normal applications
+ /// won't have to use this constructor.
+ ///
+ /// \param code The lower 4 bits of the underlying code value.
+ /// \param extended_code The upper 8 bits of the underlying code value.
+ Rcode(const uint8_t code, const uint8_t extended_code);
+ //@}
+
+ /// \brief Returns the \c Rcode code value.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The underlying code value corresponding to the \c Rcode.
+ uint16_t getCode() const {
+ return (code_);
+ }
+
+ /// \brief Returns the upper 8-bit of the \c Rcode code value.
+ ///
+ /// Normal applications won't have to use this method. This is provided
+ /// in case the upper 8 bits are necessary for the EDNS protocol
+ /// processing.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The upper 8-bit of the underlying code value.
+ uint8_t getExtendedCode() const;
+
+ /// \brief Return true iff two Rcodes are equal.
+ ///
+ /// Two Rcodes are equal iff their type codes are equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c Rcode object to compare against.
+ /// \return true if the two Rcodes are equal; otherwise false.
+ bool equals(const Rcode& other) const {
+ return (code_ == other.code_);
+ }
+
+ /// \brief Same as \c equals().
+ bool operator==(const Rcode& other) const {
+ return (equals(other));
+ }
+
+ /// \brief Return true iff two Rcodes are not equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c Rcode object to compare against.
+ /// \return true if the two Rcodes are not equal; otherwise false.
+ bool nequals(const Rcode& other) const {
+ return (code_ != other.code_);
+ }
+
+ /// \brief Same as \c nequals().
+ bool operator!=(const Rcode& other) const {
+ return (nequals(other));
+ }
+
+ /// \brief Convert the \c Rcode to a string.
+ ///
+ /// For pre-defined code values (see Rcode::CodeValue),
+ /// this method returns a string representation of the "mnemonic' used
+ /// for the enum and constant objects. For example, the string for
+ /// code value 0 is "NOERROR", etc.
+ /// For other code values it returns a string representation of the decimal
+ /// number of the value, e.g. "32", "100", etc.
+ ///
+ /// If resource allocation for the string fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \return A string representation of the \c Rcode.
+ std::string toText() const;
+
+ /// A constant object for the NOERROR Rcode (see \c Rcode::NOERROR_CODE).
+ static const Rcode& NOERROR();
+
+ /// A constant object for the FORMERR Rcode (see \c Rcode::FORMERR_CODE).
+ static const Rcode& FORMERR();
+
+ /// A constant object for the SERVFAIL Rcode (see \c Rcode::SERVFAIL_CODE).
+ static const Rcode& SERVFAIL();
+
+ /// A constant object for the NXDOMAIN Rcode (see \c Rcode::NXDOMAIN_CODE).
+ static const Rcode& NXDOMAIN();
+
+ /// A constant object for the NOTIMP Rcode (see \c Rcode::NOTIMP_CODE).
+ static const Rcode& NOTIMP();
+
+ /// A constant object for the REFUSED Rcode (see \c Rcode::REFUSED_CODE).
+ static const Rcode& REFUSED();
+
+ /// A constant object for the YXDOMAIN Rcode (see \c Rcode::YXDOMAIN_CODE).
+ static const Rcode& YXDOMAIN();
+
+ /// A constant object for the YXRRSET Rcode (see \c Rcode::YXRRSET_CODE).
+ static const Rcode& YXRRSET();
+
+ /// A constant object for the NXRRSET Rcode (see \c Rcode::NXRRSET_CODE).
+ static const Rcode& NXRRSET();
+
+ /// A constant object for the NOTAUTH Rcode (see \c Rcode::NOTAUTH_CODE).
+ static const Rcode& NOTAUTH();
+
+ /// A constant object for the NOTZONE Rcode (see \c Rcode::NOTZONE_CODE).
+ static const Rcode& NOTZONE();
+
+ /// A constant object for a reserved (code 11) Rcode.
+ /// (see \c Rcode::RESERVED11_CODE).
+ static const Rcode& RESERVED11();
+
+ /// A constant object for a reserved (code 12) Rcode.
+ /// (see \c Rcode::RESERVED12_CODE).
+ static const Rcode& RESERVED12();
+
+ /// A constant object for a reserved (code 13) Rcode.
+ /// (see \c Rcode::RESERVED13_CODE).
+ static const Rcode& RESERVED13();
+
+ /// A constant object for a reserved (code 14) Rcode.
+ /// (see \c Rcode::RESERVED14_CODE).
+ static const Rcode& RESERVED14();
+
+ /// A constant object for a reserved (code 15) Rcode.
+ /// (see \c Rcode::RESERVED15_CODE).
+ static const Rcode& RESERVED15();
+
+ /// A constant object for the BADVERS Rcode (see \c Rcode::BADVERS_CODE).
+ static const Rcode& BADVERS();
+private:
+ uint16_t code_;
+};
+
+inline const Rcode&
+Rcode::NOERROR() {
+ static Rcode c(0);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::FORMERR() {
+ static Rcode c(1);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::SERVFAIL() {
+ static Rcode c(2);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::NXDOMAIN() {
+ static Rcode c(3);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::NOTIMP() {
+ static Rcode c(4);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::REFUSED() {
+ static Rcode c(5);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::YXDOMAIN() {
+ static Rcode c(6);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::YXRRSET() {
+ static Rcode c(7);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::NXRRSET() {
+ static Rcode c(8);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::NOTAUTH() {
+ static Rcode c(9);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::NOTZONE() {
+ static Rcode c(10);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::RESERVED11() {
+ static Rcode c(11);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::RESERVED12() {
+ static Rcode c(12);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::RESERVED13() {
+ static Rcode c(13);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::RESERVED14() {
+ static Rcode c(14);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::RESERVED15() {
+ static Rcode c(15);
+ return (c);
+}
+
+inline const Rcode&
+Rcode::BADVERS() {
+ static Rcode c(16);
+ return (c);
+}
+
+/// \brief Insert the \c Rcode as a string into stream.
+///
+/// This method convert \c rcode into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param rcode A reference to an \c Rcode object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const Rcode& rcode);
+}
+}
+#endif // RCODE_H
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
new file mode 100644
index 0000000..b175dfa
--- /dev/null
+++ b/src/lib/dns/rdata.cc
@@ -0,0 +1,388 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.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>
+#include <util/buffer.h>
+#include <util/encode/encode.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <ios>
+#include <ostream>
+#include <vector>
+#include <stdint.h>
+#include <string.h>
+
+using namespace isc::util;
+
+using namespace std;
+using boost::lexical_cast;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+
+uint16_t
+Rdata::getLength() const {
+ OutputBuffer obuffer(0);
+
+ toWire(obuffer);
+
+ return (obuffer.getLength());
+}
+
+// XXX: we need to specify std:: for string to help doxygen match the
+// function signature with that given in the header file.
+RdataPtr
+createRdata(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& rdata_string) {
+ return (RRParamRegistry::getRegistry().createRdata(rrtype, rrclass,
+ rdata_string));
+}
+
+RdataPtr
+createRdata(const RRType& rrtype, const RRClass& rrclass,
+ InputBuffer& buffer, size_t len) {
+ if (len > MAX_RDLENGTH) {
+ isc_throw(InvalidRdataLength, "RDLENGTH too large");
+ }
+
+ size_t old_pos = buffer.getPosition();
+
+ 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);
+ }
+
+ return (rdata);
+}
+
+RdataPtr
+createRdata(const RRType& rrtype, const RRClass& rrclass, const Rdata& source) {
+ return (RRParamRegistry::getRegistry().createRdata(rrtype, rrclass,
+ 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) {
+ 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:
+ // This case shouldn't happen based on how we use MasterLexer in
+ // createRdata(), so we could assert() that here. But since it
+ // depends on detailed behavior of other classes, we treat the case
+ // in a bit less harsh way.
+ isc_throw(Unexpected, "bug: createRdata() saw unexpected token type");
+ }
+}
+}
+
+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, 0, 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
+ isc_throw_assert(false);
+ return (RdataPtr()); // add explicit return to silence some compilers
+}
+
+int
+compareNames(const Name& n1, const Name& n2) {
+ size_t len1 = n1.getLength();
+ size_t len2 = n2.getLength();
+ size_t cmplen = min(len1, len2);
+
+ for (size_t i = 0; i < cmplen; ++i) {
+ uint8_t c1 = tolower(n1.at(i));
+ uint8_t c2 = tolower(n2.at(i));
+ if (c1 < c2) {
+ return (-1);
+ } else if (c1 > c2) {
+ return (1);
+ }
+ }
+
+ return ((len1 == len2) ? 0 : (len1 < len2) ? -1 : 1);
+}
+
+namespace generic {
+struct GenericImpl {
+ GenericImpl(const vector<uint8_t>& data) : data_(data) {}
+ vector<uint8_t> data_;
+};
+
+Generic::Generic(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len > MAX_RDLENGTH) {
+ isc_throw(InvalidRdataLength, "RDLENGTH too large");
+ }
+
+ vector<uint8_t> data(rdata_len);
+ if (rdata_len > 0) {
+ buffer.readData(&data[0], rdata_len);
+ }
+
+ impl_.reset(new GenericImpl(data));
+}
+
+std::unique_ptr<GenericImpl>
+Generic::constructFromLexer(MasterLexer& lexer) {
+ const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+ if (token.getString() != "\\#") {
+ isc_throw(InvalidRdataText,
+ "Missing the special token (\\#) for "
+ "unknown RDATA encoding");
+ }
+
+ // Initialize with an absurd value.
+ uint32_t rdlen = 65536;
+
+ try {
+ rdlen = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ } catch (const MasterLexer::LexerError&) {
+ isc_throw(InvalidRdataLength,
+ "Unknown RDATA length is invalid");
+ }
+
+ if (rdlen > 65535) {
+ isc_throw(InvalidRdataLength,
+ "Unknown RDATA length is out of range: " << rdlen);
+ }
+
+ vector<uint8_t> data;
+
+ if (rdlen > 0) {
+ string hex_txt;
+ string hex_part;
+ // Whitespace is allowed within hex data, so read to the end of input.
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ // Unget the last read token as createRdata() expects us
+ // to leave it at the end-of-line or end-of-file when we
+ // return.
+ lexer.ungetToken();
+ break;
+ }
+ token.getString(hex_part);
+ hex_txt.append(hex_part);
+ }
+
+ try {
+ encode::decodeHex(hex_txt, data);
+ } catch (const isc::BadValue& ex) {
+ isc_throw(InvalidRdataText,
+ "Invalid hex encoding of generic RDATA: " << ex.what());
+ }
+ }
+
+ if (data.size() != rdlen) {
+ isc_throw(InvalidRdataLength,
+ "Size of unknown RDATA hex data doesn't match RDLENGTH: "
+ << data.size() << " vs. " << rdlen);
+ }
+
+ return (std::unique_ptr<GenericImpl>(new GenericImpl(data)));
+}
+
+Generic::Generic(const std::string& rdata_string) {
+ try {
+ std::istringstream ss(rdata_string);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ = constructFromLexer(lexer);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for unknown RDATA: "
+ << rdata_string);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct unknown RDATA "
+ "from '" << rdata_string << "': " << ex.what());
+ }
+}
+
+Generic::Generic(MasterLexer& lexer, const Name*,
+ MasterLoader::Options,
+ MasterLoaderCallbacks&) {
+ impl_ = constructFromLexer(lexer);
+}
+
+Generic::~Generic() {
+}
+
+Generic::Generic(const Generic& source) :
+ Rdata(), impl_(new GenericImpl(*source.impl_)) {
+}
+
+Generic&
+// Our check is better than the usual if (this == &source),
+// but cppcheck doesn't recognize it.
+// cppcheck-suppress operatorEqToSelf
+Generic::operator=(const Generic& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
+ }
+
+ impl_.reset(new GenericImpl(*source.impl_));
+
+ return (*this);
+}
+
+namespace {
+class UnknownRdataDumper {
+public:
+ UnknownRdataDumper(ostringstream& oss) : oss_(&oss) {}
+ void operator()(const unsigned char d)
+ {
+ *oss_ << setw(2) << static_cast<unsigned int>(d);
+ }
+private:
+ ostringstream* oss_;
+};
+}
+
+string
+Generic::toText() const {
+ ostringstream oss;
+
+ oss << "\\# " << impl_->data_.size() << " ";
+ oss.fill('0');
+ oss << right << hex;
+ for_each(impl_->data_.begin(), impl_->data_.end(), UnknownRdataDumper(oss));
+
+ return (oss.str());
+}
+
+void
+Generic::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+void
+Generic::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+namespace {
+inline int
+compare_internal(const GenericImpl& lhs, const GenericImpl& rhs) {
+ size_t this_len = lhs.data_.size();
+ size_t other_len = rhs.data_.size();
+ size_t len = (this_len < other_len) ? this_len : other_len;
+ int cmp;
+
+ // TODO: is there a need to check len - should we just assert?
+ // (Depends if it is possible for rdata to have zero length)
+ if ((len != 0) &&
+ ((cmp = memcmp(&lhs.data_[0], &rhs.data_[0], len)) != 0)) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 :
+ (this_len < other_len) ? -1 : 1);
+ }
+}
+}
+
+int
+Generic::compare(const Rdata& other) const {
+ const Generic& other_rdata = dynamic_cast<const Generic&>(other);
+
+ return (compare_internal(*impl_, *other_rdata.impl_));
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Generic& rdata) {
+ return (os << rdata.toText());
+}
+} // end of namespace generic
+
+} // end of namespace rdata
+}
+}
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
new file mode 100644
index 0000000..af294f0
--- /dev/null
+++ b/src/lib/dns/rdata.h
@@ -0,0 +1,575 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RDATA_H
+#define RDATA_H
+
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/exceptions.h>
+#include <util/buffer.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <memory>
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+class AbstractMessageRenderer;
+class RRType;
+class RRClass;
+class Name;
+
+namespace rdata {
+
+///
+/// \brief A standard DNS module exception that is thrown if RDATA parser
+/// encounters an invalid or inconsistent data length.
+///
+class InvalidRdataLength : public DNSTextError {
+public:
+ InvalidRdataLength(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if RDATA parser
+/// fails to recognize a given textual representation.
+///
+class InvalidRdataText : public DNSTextError {
+public:
+ InvalidRdataText(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if RDATA parser
+/// encounters a character-string (as defined in RFC1035) exceeding
+/// the maximum allowable length (\c MAX_CHARSTRING_LEN).
+///
+class CharStringTooLong : public DNSTextError {
+public:
+ CharStringTooLong(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+// Forward declaration to define RdataPtr.
+class Rdata;
+
+///
+/// The \c RdataPtr type is a pointer-like type, pointing to an
+/// object of some concrete derived class of \c Rdata.
+///
+typedef boost::shared_ptr<Rdata> RdataPtr;
+typedef boost::shared_ptr<const Rdata> ConstRdataPtr;
+
+/// \brief Possible maximum length of RDATA, which is the maximum unsigned
+/// 16 bit value.
+const size_t MAX_RDLENGTH = 65535;
+
+/// \brief The maximum allowable length of character-string containing in
+/// RDATA as defined in RFC1035, not including the 1-byte length field.
+const unsigned int MAX_CHARSTRING_LEN = 255;
+
+/// \brief The \c Rdata class is an abstract base class that provides
+/// a set of common interfaces to manipulate concrete RDATA objects.
+///
+/// Generally, a separate derived class directly inherited from the base
+/// \c Rdata class is defined for each well known RDATA.
+/// Each of such classes will define the common logic based on the
+/// corresponding protocol standard.
+///
+/// Since some types of RRs are class specific and the corresponding RDATA
+/// may have different semantics (e.g. type A for class IN and type A for
+/// class CH have different representations and semantics), we separate
+/// \c Rdata derived classes for such RR types in different namespaces.
+/// The namespace of types specific to a class is named the lower-cased class
+/// name; for example, RDATA of class IN-specific types are defined in the
+/// \c in namespace, and RDATA of class CH-specific types are defined in
+/// the \c ch namespace, and so on.
+/// The derived classes are named using the RR type name (upper cased) such as
+/// \c A or \c AAAA.
+/// Thus RDATA of type A RR for class IN and CH are defined as \c in::A and
+/// \c ch::A, respectively.
+/// Many other RR types are class independent; the derived \c Rdata classes
+/// for such RR types are defined in the \c generic namespace. Examples are
+/// \c generic::NS and \c generic::SOA.
+///
+/// If applications need to refer to these derived classes, it is generally
+/// recommended to prepend at least some part of the namespace because the
+/// same class name can be used in different namespaces.
+/// So, instead of doing
+/// \code using namespace isc::dns::rdata::in;
+/// A& rdata_type_a; \endcode
+/// it is advisable to prepend at least \c in from the namespace:
+/// \code using namespace isc::dns::rdata;
+/// in::A& rdata_type_a; \endcode
+///
+/// In many cases, however, an application doesn't have to care about such
+/// derived classes.
+/// For instance, to parse an incoming DNS message an application wouldn't
+/// have to perform type specific operation unless the application is
+/// specifically concerned about a particular type.
+/// So, this API generally handles \c Rdata in a polymorphic way through
+/// a pointer or reference to this base abstract class.
+class Rdata {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private. Concrete classes should generally specialize their
+ /// own versions of these methods.
+ //@{
+protected:
+ /// The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class). In many
+ /// cases, the derived class wouldn't define a public default constructor
+ /// either, because an \c Rdata object without concrete data isn't
+ /// meaningful.
+ Rdata() {}
+private:
+ Rdata(const Rdata& source);
+ void operator=(const Rdata& source);
+public:
+ /// The destructor.
+ virtual ~Rdata() {};
+ //@}
+
+ ///
+ /// \name Converter methods
+ ///
+ //@{
+ /// \brief Convert an \c Rdata to a string.
+ ///
+ /// This method returns a \c std::string object representing the \c Rdata.
+ ///
+ /// This is a pure virtual method without the definition; the actual
+ /// representation is specific to each derived concrete class and
+ /// should be explicitly defined in the derived class.
+ ///
+ /// \return A string representation of \c Rdata.
+ virtual std::string toText() const = 0;
+
+ /// \brief Render the \c Rdata in the wire format into a buffer.
+ ///
+ /// This is a pure virtual method without the definition; the actual
+ /// conversion is specific to each derived concrete class and
+ /// should be explicitly defined in the derived class.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ virtual void toWire(isc::util::OutputBuffer& buffer) const = 0;
+
+ /// \brief Render the \c Rdata in the wire format into a
+ /// \c MessageRenderer object.
+ ///
+ /// This is a pure virtual method without the definition; the actual
+ /// conversion is specific to each derived concrete class and
+ /// should be explicitly defined in the derived class.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer in which the \c Rdata is to be stored.
+ virtual void toWire(AbstractMessageRenderer& renderer) const = 0;
+ //@}
+
+ ///
+ /// \name Comparison method
+ ///
+ //@{
+ /// \brief Compare two instances of \c Rdata.
+ ///
+ /// This method compares \c this and the \c other Rdata objects
+ /// in terms of the DNSSEC sorting order as defined in RFC4034, and returns
+ /// the result as an integer.
+ ///
+ /// This is a pure virtual method without the definition; the actual
+ /// comparison logic is specific to each derived concrete class and
+ /// should be explicitly defined in the derived class.
+ ///
+ /// Specific implementations of this method must confirm that \c this
+ /// and the \c other are objects of the same concrete derived class of
+ /// \c Rdata. This is normally done by \c dynamic_cast in the
+ /// implementation. It also means if the assumption isn't met
+ /// an exception of class \c std::bad_cast will be thrown.
+ ///
+ /// Here is an implementation choice: instead of relying on
+ /// \c dynamic_cast, we could first convert the data into wire-format
+ /// and compare the pair as opaque data. This would be more polymorphic,
+ /// but might involve significant overhead, especially for a large size
+ /// of RDATA.
+ ///
+ /// \param other the right-hand operand to compare against.
+ /// \return < 0 if \c this would be sorted before \c other.
+ /// \return 0 if \c this is identical to \c other in terms of sorting order.
+ /// \return > 0 if \c this would be sorted after \c other.
+ virtual int compare(const Rdata& other) const = 0;
+ //@}
+
+ /// \brief Get the wire format length of an Rdata.
+ ///
+ /// IMPLEMENTATION NOTE: Currently this base class implementation is
+ /// non-optimal as it renders the wire data to a buffer and returns
+ /// the buffer's length. What would perform better is to add
+ /// implementations of \c getLength() method to every RDATA
+ /// type. This is why this method is virtual. Once all Rdata types
+ /// have \c getLength() implementations, this base class
+ /// implementation must be removed and the method should become a
+ /// pure interface.
+ ///
+ /// \return The length of the wire format representation of the
+ /// RDATA.
+ virtual uint16_t getLength() const;
+};
+
+namespace generic {
+
+/// \brief The \c GenericImpl class is the actual implementation of the
+/// \c generic::Generic class.
+///
+/// The implementation is hidden from applications. This approach requires
+/// dynamic memory allocation on construction, copy, or assignment, but
+/// we believe it should be acceptable as "unknown" RDATA should be pretty
+/// rare.
+struct GenericImpl;
+
+/// \brief The \c generic::Generic class represents generic "unknown" RDATA.
+///
+/// This class is used as a placeholder for all non well-known type of RDATA.
+/// By definition, the stored data is regarded as opaque binary without
+/// assuming any structure.
+class Generic : public Rdata {
+public:
+ ///
+ /// \name Constructors, Assignment Operator and Destructor.
+ ///
+ //@{
+ /// \brief Constructor from a string.
+ ///
+ /// This method constructs a \c generic::Generic object from a textual
+ /// representation as defined in RFC3597.
+ ///
+ /// If \c rdata_string isn't a valid textual representation of this type
+ /// of RDATA, an exception of class \c InvalidRdataText or
+ /// \c InvalidRdataLength will be thrown.
+ /// If resource allocation to store the data fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \param rdata_string A string of textual representation of generic
+ /// RDATA.
+ explicit Generic(const std::string& rdata_string);
+
+ ///
+ /// \brief Constructor from wire-format data.
+ ///
+ /// The \c buffer parameter normally stores a complete DNS message
+ /// containing the generic RDATA to be constructed.
+ /// The current read position of the buffer points to the head of the
+ /// data.
+ ///
+ /// This method reads \c rdata_len bytes from the \c buffer, and internally
+ /// stores the data as an opaque byte sequence.
+ ///
+ /// \c rdata_len must not exceed \c MAX_RDLENGTH; otherwise, an exception
+ /// of class \c InvalidRdataLength will be thrown.
+ /// If resource allocation to hold the data fails, a corresponding
+ /// standard exception will be thrown; if the \c buffer doesn't
+ /// contain \c rdata_len bytes of unread data, an exception of
+ /// class \c isc::OutOfRange will be thrown.
+ ///
+ /// \param buffer A reference to an \c InputBuffer object storing the
+ /// \c Rdata to parse.
+ /// \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();
+ ///
+ /// \brief The copy constructor.
+ ///
+ /// If resource allocation to copy the data fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \param source A reference to a \c generic::Generic object to copy from.
+ Generic(const Generic& source);
+
+ ///
+ /// \brief The assignment operator.
+ ///
+ /// If resource allocation to copy the data fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \param source A reference to a \c generic::Generic object to copy from.
+ Generic& operator=(const Generic& source);
+ //@}
+
+ ///
+ /// \name Converter methods
+ ///
+ //@{
+ /// \brief Convert an \c generic::Generic object to a string.
+ ///
+ /// This method converts a generic "unknown" RDATA object into a textual
+ /// representation of such unknown data as defined in RFC3597.
+ ///
+ /// If resource allocation to copy the data fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \return A string representation of \c generic::Generic.
+ virtual std::string toText() const;
+
+ ///
+ /// \brief Render the \c generic::Generic in the wire format into a buffer.
+ ///
+ /// This will require \c rdata_len bytes of remaining capacity in the
+ /// \c buffer. If this is not the case and resource allocation for the
+ /// necessary memory space fails, a corresponding standard exception will
+ /// be thrown.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ virtual void toWire(isc::util::OutputBuffer& buffer) const;
+
+ /// \brief Render the \c generic::Generic in the wire format into a
+ /// \c MessageRenderer object.
+ ///
+ /// This will require \c rdata_len bytes of remaining capacity in the
+ /// \c buffer. If this is not the case and resource allocation for the
+ /// necessary memory space fails, a corresponding standard exception will
+ /// be thrown.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer in which the \c Generic object is to be stored.
+ virtual void toWire(AbstractMessageRenderer& renderer) const;
+ //@}
+
+ ///
+ /// \name Comparison method
+ ///
+ //@{
+ /// \brief Compare two instances of \c generic::Generic objects.
+ ///
+ /// As defined in RFC4034, this method simply compares the wire-format
+ /// representations of the two objects as left-justified unsigned octet
+ /// sequences.
+ ///
+ /// The object referenced by \c other must have been instantiated as
+ /// a c generic::Generic class object; otherwise, an exception of class
+ /// \c std::bad_cast will be thrown.
+ /// Note that the comparison is RR type/class agnostic: this method doesn't
+ /// check whether the two \c Rdata objects to compare are of the comparable
+ /// RR type/class. For example, \c this object may come from an \c RRset
+ /// of \c RRType x, and the \c other may come from a different \c RRset
+ /// of \c RRType y (where x != y). This situation would be considered a
+ /// bug, but this method cannot detect this type of error.
+ /// The caller must ensure this condition.
+ ///
+ /// \param other the right-hand operand to compare against.
+ /// \return < 0 if \c this would be sorted before \c other.
+ /// \return 0 if \c this is identical to \c other in terms of sorting order.
+ /// \return > 0 if \c this would be sorted after \c other.
+ virtual int compare(const Rdata& other) const;
+ //@}
+
+private:
+ std::unique_ptr<GenericImpl> constructFromLexer(MasterLexer& lexer);
+
+ std::unique_ptr<GenericImpl> impl_;
+};
+
+///
+/// \brief Insert the name as a string into stream.
+///
+/// This method convert the \c rdata into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global \c operator<< to behave as described in
+/// \c ostream::operator<< but applied to \c generic::Generic Rdata objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param rdata The \c Generic object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const Generic& rdata);
+} // end of namespace "generic"
+
+//
+// Non class-member functions related to Rdata
+//
+
+///
+/// \name Parameterized Polymorphic RDATA Factories
+///
+/// This set of global functions provide a unified interface to create an
+/// \c Rdata object in a parameterized polymorphic way,
+/// that is, these functions take a pair of \c RRType and \c RRClass
+/// objects and data specific to that pair, and create an object of
+/// the corresponding concrete derived class of \c Rdata.
+///
+/// These will be useful when parsing/constructing a DNS message or
+/// parsing a master file, where information for a specific type of RDATA
+/// is given but the resulting object, once created, should better be used
+/// in a polymorphic way.
+///
+/// For example, if a master file parser encounters an NS RR
+/// \verbatim example.com. 3600 IN NS ns.example.com.\endverbatim
+/// it stores the text fragments "IN", "NS", and "ns.example.com." in
+/// \c std::string objects \c class_txt, \c type_txt, and \c nsname_txt,
+/// respectively, then it would create a new \c RdataPtr object as follows:
+/// \code RdataPtr rdata = createRdata(RRType(type_txt), RRClass(class_txt),
+/// nsname_txt); \endcode
+/// On success, \c rdata will point to an object of the \c generic::NS class
+/// that internally holds a domain name of "ns.example.com."
+///
+/// Internally, these functions uses the corresponding
+/// \c RRParamRegistry::createRdata methods of the \c RRParamRegistry.
+/// See also the description on these methods for related notes.
+//@{
+/// \brief Create RDATA of a given pair of RR type and class from a string.
+///
+/// This method creates from a string an \c Rdata object of the given pair
+/// of RR type and class.
+///
+/// \param rrtype An \c RRType object specifying the type/class pair.
+/// \param rrclass An \c RRClass object specifying the type/class pair.
+/// \param rdata_string A string of textual representation of the \c Rdata.
+/// \return An \c RdataPtr object pointing to the created \c Rdata
+/// object.
+RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& rdata_string);
+
+/// \brief Create RDATA of a given pair of RR type and class from
+/// wire-format data.
+///
+/// This method creates from wire-format binary data an \c Rdata object
+/// of the given pair of RR type and class.
+///
+/// \c len must not exceed the protocol defined maximum value, \c MAX_RDLENGTH;
+/// otherwise, an exception of class \c InvalidRdataLength will be thrown.
+///
+/// In some cases, the length of the RDATA is determined without the
+/// information of \c len. For example, the RDATA length of an IN/A RR
+/// must always be 4. If \c len is not equal to the actual length in such
+/// cases, an exception of class InvalidRdataLength will be thrown.
+///
+/// \param rrtype An \c RRType object specifying the type/class pair.
+/// \param rrclass An \c RRClass object specifying the type/class pair.
+/// \param buffer A reference to an \c InputBuffer object storing the
+/// \c Rdata to parse.
+/// \param len The length in buffer of the \c Rdata. In bytes.
+/// \return An \c RdataPtr object pointing to the created \c Rdata
+/// object.
+RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+ isc::util::InputBuffer& buffer, size_t len);
+
+/// \brief Create RDATA of a given pair of RR type and class, copying
+/// of another RDATA of same kind.
+///
+/// This method creates an \c Rdata object of the given pair of
+/// RR type and class, copying the content of the given \c Rdata,
+/// \c source.
+///
+/// \param rrtype An \c RRType object specifying the type/class pair.
+/// \param rrclass An \c RRClass object specifying the type/class pair.
+/// \param source A reference to an \c Rdata object whose content
+/// is to be copied to the created \c Rdata object.
+/// \return An \c RdataPtr object pointing to the created
+/// \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 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);
+
+//@}
+
+///
+/// \brief Gives relative ordering of two names in terms of DNSSEC RDATA
+/// ordering.
+///
+/// This method compares two names as defined in Sections 6.2 and 6.3 of
+/// RFC4034: Comparing two names in their canonical form
+/// (i.e., converting upper case ASCII characters to lower ones) and
+/// as a left-justified unsigned octet sequence. Note that the ordering is
+/// different from that for owner names. For example, "a.example" should be
+/// sorted before "example" as RDATA, but the ordering is the opposite when
+/// compared as owner names.
+///
+/// Normally, applications would not need this function directly.
+/// This is mostly an internal helper function for \c Rdata related classes
+/// to implement their \c compare() method.
+/// This function is publicly open, however, for the convenience of
+/// external developers who want to implement new or experimental RR types.
+///
+/// This function never throws an exception as long as the given names are
+/// valid \c Name objects.
+///
+/// Additional note about applicability: In fact, BIND9's similar function,
+/// \c dns_name_rdatacompare(), is only used in rdata implementations and
+/// for testing purposes.
+///
+/// \param n1,n2 \c Name class objects to compare.
+/// \return -1 if \c n1 would be sorted before \c n2.
+/// \return 0 if \c n1 is identical to \c n2 in terms of sorting order.
+/// \return 1 if \c n1 would be sorted after \c n2.
+///
+int compareNames(const Name& n1, const Name& n2);
+
+} // end of namespace "rdata"
+}
+}
+#endif // RDATA_H
diff --git a/src/lib/dns/rdataclass.cc b/src/lib/dns/rdataclass.cc
new file mode 100644
index 0000000..b2d05f5
--- /dev/null
+++ b/src/lib/dns/rdataclass.cc
@@ -0,0 +1,2462 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <dns/exceptions.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrtype.h>
+#include <dns/time_utils.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigerror.h>
+#include <dns/txt_like.h>
+#include <util/buffer.h>
+#include <util/encode/encode.h>
+#include <util/buffer.h>
+
+#include <cerrno>
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/socket.h> // for AF_INET/AF_INET6
+#include <time.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+using namespace std;
+using boost::lexical_cast;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace any {
+
+// straightforward representation of TSIG RDATA fields
+struct TSIGImpl {
+ TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ vector<uint8_t>& mac, uint16_t original_id, uint16_t error,
+ vector<uint8_t>& other_data) :
+ algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge),
+ mac_(mac), original_id_(original_id), error_(error),
+ other_data_(other_data) {
+ }
+ TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ size_t macsize, const void* mac, uint16_t original_id,
+ uint16_t error, size_t other_len, const void* other_data) :
+ algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge),
+ mac_(static_cast<const uint8_t*>(mac),
+ static_cast<const uint8_t*>(mac) + macsize),
+ original_id_(original_id), error_(error),
+ other_data_(static_cast<const uint8_t*>(other_data),
+ static_cast<const uint8_t*>(other_data) + other_len) {
+ }
+ template <typename Output>
+ void toWireCommon(Output& output) const;
+
+ const Name algorithm_;
+ const uint64_t time_signed_;
+ const uint16_t fudge_;
+ const vector<uint8_t> mac_;
+ const uint16_t original_id_;
+ const uint16_t error_;
+ const vector<uint8_t> other_data_;
+};
+
+// helper function for string and lexer constructors
+std::unique_ptr<TSIGImpl>
+TSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const Name& algorithm =
+ createNameFromLexer(lexer, origin ? origin : &Name::ROOT_NAME());
+ const Name& canonical_algorithm_name =
+ (algorithm == TSIGKey::HMACMD5_SHORT_NAME()) ?
+ TSIGKey::HMACMD5_NAME() : algorithm;
+
+ const string& time_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint64_t time_signed;
+ try {
+ time_signed = boost::lexical_cast<uint64_t>(time_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TSIG Time");
+ }
+ if ((time_signed >> 48) != 0) {
+ isc_throw(InvalidRdataText, "TSIG Time out of range");
+ }
+
+ const uint32_t fudge = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fudge > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Fudge out of range");
+ }
+ const uint32_t macsize =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (macsize > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG MAC Size out of range");
+ }
+
+ const string& mac_txt = (macsize > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> mac;
+ decodeBase64(mac_txt, mac);
+ if (mac.size() != macsize) {
+ isc_throw(InvalidRdataText, "TSIG MAC Size and data are inconsistent");
+ }
+
+ const uint32_t orig_id =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (orig_id > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Original ID out of range");
+ }
+
+ const string& error_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint32_t error = 0;
+ // XXX: In the initial implementation we hardcode the mnemonics.
+ // We'll soon generalize this.
+ if (error_txt == "NOERROR") {
+ error = Rcode::NOERROR_CODE;
+ } else if (error_txt == "BADSIG") {
+ error = TSIGError::BAD_SIG_CODE;
+ } else if (error_txt == "BADKEY") {
+ error = TSIGError::BAD_KEY_CODE;
+ } else if (error_txt == "BADTIME") {
+ error = TSIGError::BAD_TIME_CODE;
+ } else if (error_txt == "BADMODE") {
+ error = TSIGError::BAD_MODE_CODE;
+ } else if (error_txt == "BADNAME") {
+ error = TSIGError::BAD_NAME_CODE;
+ } else if (error_txt == "BADALG") {
+ error = TSIGError::BAD_ALG_CODE;
+ } else if (error_txt == "BADTRUNC") {
+ error = TSIGError::BAD_TRUNC_CODE;
+ } else {
+ /// we cast to uint32_t and range-check, because casting directly to
+ /// uint16_t will convert negative numbers to large positive numbers
+ try {
+ error = boost::lexical_cast<uint32_t>(error_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TSIG Error");
+ }
+ if (error > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Error out of range");
+ }
+ }
+
+ const uint32_t otherlen =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (otherlen > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Other Len out of range");
+ }
+ const string otherdata_txt = (otherlen > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> other_data;
+ decodeBase64(otherdata_txt, other_data);
+ if (other_data.size() != otherlen) {
+ isc_throw(InvalidRdataText,
+ "TSIG Other Data length does not match Other Len");
+ }
+ // RFC2845 says Other Data is "empty unless Error == BADTIME".
+ // However, we don't enforce that.
+
+ return (std::unique_ptr<TSIGImpl>(new TSIGImpl(canonical_algorithm_name, time_signed, fudge, mac,
+ orig_id, error, other_data)));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid TSIG RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// \c tsig_str must be formatted as follows:
+/// \code <Algorithm Name> <Time Signed> <Fudge> <MAC Size> [<MAC>]
+/// <Original ID> <Error> <Other Len> [<Other Data>]
+/// \endcode
+///
+/// Note that, since the Algorithm Name field is defined to be "in domain name
+/// syntax", but it is not actually a domain name, it does not have to be
+/// fully qualified.
+///
+/// The Error field is an unsigned 16-bit decimal integer or a valid mnemonic
+/// as specified in RFC2845. Currently, "NOERROR", "BADSIG", "BADKEY", and
+/// "BADTIME" are supported (case sensitive). In future versions other
+/// representations that are compatible with the DNS RCODE may be supported.
+///
+/// The MAC and Other Data fields are base-64 encoded strings that do not
+/// contain space characters.
+/// If the MAC Size field is 0, the MAC field must not appear in \c tsig_str.
+/// If the Other Len field is 0, the Other Data field must not appear in
+/// \c tsig_str.
+/// The decoded data of the MAC field is MAC Size bytes of binary stream.
+/// The decoded data of the Other Data field is Other Len bytes of binary
+/// stream.
+///
+/// An example of valid string is:
+/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
+/// In this example Other Data is missing because Other Len is 0.
+///
+/// Note that RFC2845 does not define the standard presentation format
+/// of %TSIG RR, so the above syntax is implementation specific.
+/// This is, however, compatible with the format acceptable to BIND 9's
+/// RDATA parser.
+///
+/// \throw Others Exception from the Name constructors.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+/// \throw BadValue if MAC or Other Data is not validly encoded in base-64.
+///
+/// \param tsig_str A string containing the RDATA to be created
+TSIG::TSIG(const std::string& tsig_str) {
+ try {
+ std::istringstream ss(tsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ = constructFromLexer(lexer, 0);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for TSIG: " << tsig_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct TSIG from '" << tsig_str << "': "
+ << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an TSIG RDATA.
+///
+/// See \c TSIG::TSIG(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TSIG::TSIG(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ impl_ = constructFromLexer(lexer, origin);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// When a read operation on \c buffer fails (e.g., due to a corrupted
+/// message) a corresponding exception from the \c InputBuffer class will
+/// be thrown.
+/// If the wire-format data does not begin with a valid domain name,
+/// a corresponding exception from the \c Name class will be thrown.
+/// In addition, this constructor internally involves resource allocation,
+/// and if it fails a corresponding standard exception will be thrown.
+///
+/// According to RFC3597, the Algorithm field must be a non compressed form
+/// of domain name. But this implementation accepts a %TSIG RR even if that
+/// field is compressed.
+///
+/// \param buffer A buffer storing the wire format data.
+/// \param rdata_len The length of the RDATA in bytes, normally expected
+/// to be the value of the RDLENGTH field of the corresponding RR.
+/// But this constructor does not use this parameter; if necessary, the caller
+/// must check consistency between the length parameter and the actual
+/// RDATA length.
+TSIG::TSIG(InputBuffer& buffer, size_t) {
+ Name algorithm(buffer);
+
+ uint8_t time_signed_buf[6];
+ buffer.readData(time_signed_buf, sizeof(time_signed_buf));
+ const uint64_t time_signed =
+ (static_cast<uint64_t>(time_signed_buf[0]) << 40 |
+ static_cast<uint64_t>(time_signed_buf[1]) << 32 |
+ static_cast<uint64_t>(time_signed_buf[2]) << 24 |
+ static_cast<uint64_t>(time_signed_buf[3]) << 16 |
+ static_cast<uint64_t>(time_signed_buf[4]) << 8 |
+ static_cast<uint64_t>(time_signed_buf[5]));
+
+ const uint16_t fudge = buffer.readUint16();
+
+ const uint16_t mac_size = buffer.readUint16();
+ vector<uint8_t> mac(mac_size);
+ if (mac_size > 0) {
+ buffer.readData(&mac[0], mac_size);
+ }
+
+ const uint16_t original_id = buffer.readUint16();
+ const uint16_t error = buffer.readUint16();
+
+ const uint16_t other_len = buffer.readUint16();
+ vector<uint8_t> other_data(other_len);
+ if (other_len > 0) {
+ buffer.readData(&other_data[0], other_len);
+ }
+
+ const Name& canonical_algorithm_name =
+ (algorithm == TSIGKey::HMACMD5_SHORT_NAME()) ?
+ TSIGKey::HMACMD5_NAME() : algorithm;
+ impl_.reset(new TSIGImpl(canonical_algorithm_name, time_signed, fudge, mac,
+ original_id, error, other_data));
+}
+
+TSIG::TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ uint16_t mac_size, const void* mac, uint16_t original_id,
+ uint16_t error, uint16_t other_len, const void* other_data) {
+ // Time Signed is a 48-bit value.
+ if ((time_signed >> 48) != 0) {
+ isc_throw(OutOfRange, "TSIG Time Signed is too large: " <<
+ time_signed);
+ }
+ if ((mac_size == 0 && mac) || (mac_size > 0 && !mac)) {
+ isc_throw(InvalidParameter, "TSIG MAC size and data inconsistent");
+ }
+ if ((other_len == 0 && other_data) || (other_len > 0 && !other_data)) {
+ isc_throw(InvalidParameter,
+ "TSIG Other data length and data inconsistent");
+ }
+ const Name& canonical_algorithm_name =
+ (algorithm == TSIGKey::HMACMD5_SHORT_NAME()) ?
+ TSIGKey::HMACMD5_NAME() : algorithm;
+ impl_.reset(new TSIGImpl(canonical_algorithm_name, time_signed, fudge, mac_size,
+ mac, original_id, error, other_len, other_data));
+}
+
+/// \brief The copy constructor.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This constructor never throws an exception otherwise.
+TSIG::TSIG(const TSIG& source) : Rdata(), impl_(new TSIGImpl(*source.impl_)) {
+}
+
+TSIG&
+TSIG::operator=(const TSIG& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ impl_.reset(new TSIGImpl(*source.impl_));
+
+ return (*this);
+}
+
+TSIG::~TSIG() {
+}
+
+/// \brief Convert the \c TSIG to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c TSIG(const std::string&))).
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+///
+/// \return A \c string object that represents the \c TSIG object.
+std::string
+TSIG::toText() const {
+ string result;
+
+ result += impl_->algorithm_.toText() + " " +
+ lexical_cast<string>(impl_->time_signed_) + " " +
+ lexical_cast<string>(impl_->fudge_) + " " +
+ lexical_cast<string>(impl_->mac_.size()) + " ";
+ if (!impl_->mac_.empty()) {
+ result += encodeBase64(impl_->mac_) + " ";
+ }
+ result += lexical_cast<string>(impl_->original_id_) + " ";
+ result += TSIGError(impl_->error_).toText() + " ";
+ result += lexical_cast<string>(impl_->other_data_.size());
+ if (!impl_->other_data_.empty()) {
+ result += " " + encodeBase64(impl_->other_data_);
+ }
+
+ return (result);
+}
+
+// Common sequence of toWire() operations used for the two versions of
+// toWire().
+template <typename Output>
+void
+TSIGImpl::toWireCommon(Output& output) const {
+ output.writeUint16(time_signed_ >> 32);
+ output.writeUint32(time_signed_ & 0xffffffff);
+ output.writeUint16(fudge_);
+ const uint16_t mac_size = mac_.size();
+ output.writeUint16(mac_size);
+ if (mac_size > 0) {
+ output.writeData(&mac_[0], mac_size);
+ }
+ output.writeUint16(original_id_);
+ output.writeUint16(error_);
+ const uint16_t other_len = other_data_.size();
+ output.writeUint16(other_len);
+ if (other_len > 0) {
+ output.writeData(&other_data_[0], other_len);
+ }
+}
+
+/// \brief Render the \c TSIG in the wire format without name compression.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+TSIG::toWire(OutputBuffer& buffer) const {
+ impl_->algorithm_.toWire(buffer);
+ impl_->toWireCommon<OutputBuffer>(buffer);
+}
+
+/// \brief Render the \c TSIG in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, the Algorithm field (a domain name) will not
+/// be compressed. However, the domain name could be a target of compression
+/// of other compressible names (though pretty unlikely), the offset
+/// information of the algorithm name may be recorded in \c renderer.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+TSIG::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(impl_->algorithm_, false);
+ impl_->toWireCommon<AbstractMessageRenderer>(renderer);
+}
+
+// A helper function commonly used for TSIG::compare().
+int
+vectorComp(const vector<uint8_t>& v1, const vector<uint8_t>& v2) {
+ const size_t this_size = v1.size();
+ const size_t other_size = v2.size();
+ if (this_size != other_size) {
+ return (this_size < other_size ? -1 : 1);
+ }
+ if (this_size > 0) {
+ return (memcmp(&v1[0], &v2[0], this_size));
+ }
+ return (0);
+}
+
+/// \brief Compare two instances of \c TSIG RDATA.
+///
+/// This method compares \c this and the \c other \c TSIG objects
+/// in terms of the DNSSEC sorting order as defined in RFC4034, and returns
+/// the result as an integer.
+///
+/// This method is expected to be used in a polymorphic way, and the
+/// parameter to compare against is therefore of the abstract \c Rdata class.
+/// However, comparing two \c Rdata objects of different RR types
+/// is meaningless, and \c other must point to a \c TSIG object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param other the right-hand operand to compare against.
+/// \return < 0 if \c this would be sorted before \c other.
+/// \return 0 if \c this is identical to \c other in terms of sorting order.
+/// \return > 0 if \c this would be sorted after \c other.
+int
+TSIG::compare(const Rdata& other) const {
+ const TSIG& other_tsig = dynamic_cast<const TSIG&>(other);
+
+ const int ncmp = compareNames(impl_->algorithm_,
+ other_tsig.impl_->algorithm_);
+ if (ncmp != 0) {
+ return (ncmp);
+ }
+
+ if (impl_->time_signed_ != other_tsig.impl_->time_signed_) {
+ return (impl_->time_signed_ < other_tsig.impl_->time_signed_ ? -1 : 1);
+ }
+ if (impl_->fudge_ != other_tsig.impl_->fudge_) {
+ return (impl_->fudge_ < other_tsig.impl_->fudge_ ? -1 : 1);
+ }
+ const int vcmp = vectorComp(impl_->mac_, other_tsig.impl_->mac_);
+ if (vcmp != 0) {
+ return (vcmp);
+ }
+ if (impl_->original_id_ != other_tsig.impl_->original_id_) {
+ return (impl_->original_id_ < other_tsig.impl_->original_id_ ? -1 : 1);
+ }
+ if (impl_->error_ != other_tsig.impl_->error_) {
+ return (impl_->error_ < other_tsig.impl_->error_ ? -1 : 1);
+ }
+ return (vectorComp(impl_->other_data_, other_tsig.impl_->other_data_));
+}
+
+const Name&
+TSIG::getAlgorithm() const {
+ return (impl_->algorithm_);
+}
+
+uint64_t
+TSIG::getTimeSigned() const {
+ return (impl_->time_signed_);
+}
+
+uint16_t
+TSIG::getFudge() const {
+ return (impl_->fudge_);
+}
+
+uint16_t
+TSIG::getMACSize() const {
+ return (impl_->mac_.size());
+}
+
+const void*
+TSIG::getMAC() const {
+ if (!impl_->mac_.empty()) {
+ return (&impl_->mac_[0]);
+ } else {
+ return (0);
+ }
+}
+
+uint16_t
+TSIG::getOriginalID() const {
+ return (impl_->original_id_);
+}
+
+uint16_t
+TSIG::getError() const {
+ return (impl_->error_);
+}
+
+uint16_t
+TSIG::getOtherLen() const {
+ return (impl_->other_data_.size());
+}
+
+const void*
+TSIG::getOtherData() const {
+ if (!impl_->other_data_.empty()) {
+ return (&impl_->other_data_[0]);
+ } else {
+ return (0);
+ }
+}
+
+} // end of namespace "any"
+
+namespace ch {
+
+A::A(const std::string&) {
+ // TBD
+}
+
+A::A(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ // TBD
+}
+
+A::A(InputBuffer&, size_t) {
+ // TBD
+}
+
+A::A(const A&) : Rdata() {
+ // TBD
+}
+
+void
+A::toWire(OutputBuffer&) const {
+ // TBD
+}
+
+void
+A::toWire(AbstractMessageRenderer&) const {
+ // TBD
+}
+
+string
+A::toText() const {
+ // TBD
+ isc_throw(InvalidRdataText, "Not implemented yet");
+}
+
+int
+A::compare(const Rdata&) const {
+ // TBD
+ return (0);
+}
+
+} // end of namespace "ch"
+
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NS RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The NSDNAME must be absolute since there's no parameter that
+/// specifies the origin name; if it is not absolute, \c
+/// MissingNameOrigin exception will be thrown. These must not be
+/// represented as a quoted string.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
+NS::NS(const std::string& namestr) :
+ // Fill in dummy name and replace them soon below.
+ nsname_(Name::ROOT_NAME()) {
+ try {
+ std::istringstream ss(namestr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ nsname_ = createNameFromLexer(lexer, 0);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for NS: "
+ << namestr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct NS from '" <<
+ namestr << "': " << ex.what());
+ }
+}
+
+NS::NS(InputBuffer& buffer, size_t) :
+ nsname_(buffer) {
+ // we don't need rdata_len for parsing. if necessary, the caller will
+ // check consistency.
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NS RDATA. The NSDNAME field can be
+/// non-absolute if \c origin is non-null, in which case \c origin is
+/// used to make it absolute. It must not be represented as a quoted
+/// string.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \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 NSDNAME when it
+/// is non-absolute.
+NS::NS(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ nsname_(createNameFromLexer(lexer, origin)) {
+}
+
+NS::NS(const NS& other) :
+ Rdata(), nsname_(other.nsname_) {
+}
+
+void
+NS::toWire(OutputBuffer& buffer) const {
+ nsname_.toWire(buffer);
+}
+
+void
+NS::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(nsname_);
+}
+
+string
+NS::toText() const {
+ return (nsname_.toText());
+}
+
+int
+NS::compare(const Rdata& other) const {
+ const NS& other_ns = dynamic_cast<const NS&>(other);
+
+ return (compareNames(nsname_, other_ns.nsname_));
+}
+
+const Name&
+NS::getNSName() const {
+ return (nsname_);
+}
+
+/// \brief Constructor.
+OPT::PseudoRR::PseudoRR(uint16_t code,
+ boost::shared_ptr<std::vector<uint8_t> >& data) :
+ code_(code),
+ data_(data) {
+}
+
+uint16_t
+OPT::PseudoRR::getCode() const {
+ return (code_);
+}
+
+const uint8_t*
+OPT::PseudoRR::getData() const {
+ return (&(*data_)[0]);
+}
+
+uint16_t
+OPT::PseudoRR::getLength() const {
+ return (data_->size());
+}
+
+struct OPTImpl {
+ OPTImpl() :
+ rdlength_(0) {
+ }
+
+ uint16_t rdlength_;
+ std::vector<OPT::PseudoRR> pseudo_rrs_;
+};
+
+/// \brief Default constructor.
+OPT::OPT() :
+ impl_(new OPTImpl()) {
+}
+
+/// \brief Constructor from string.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
+OPT::OPT(const std::string&) {
+ isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
+OPT::OPT(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
+OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
+ impl_.reset(new OPTImpl());
+
+ while (true) {
+ if (rdata_len == 0) {
+ break;
+ }
+
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength,
+ "Pseudo OPT RR record too short: "
+ << rdata_len << " bytes");
+ }
+
+ const uint16_t option_code = buffer.readUint16();
+ const uint16_t option_length = buffer.readUint16();
+ rdata_len -= 4;
+
+ if (static_cast<uint16_t>(impl_->rdlength_ + option_length) <
+ impl_->rdlength_) {
+ isc_throw(InvalidRdataText,
+ "Option length " << option_length
+ << " would overflow OPT RR RDLEN (currently "
+ << impl_->rdlength_ << ").");
+ }
+
+ if (rdata_len < option_length) {
+ isc_throw(InvalidRdataLength, "Corrupt pseudo OPT RR record");
+ }
+
+ boost::shared_ptr<std::vector<uint8_t> >
+ option_data(new std::vector<uint8_t>(option_length));
+ buffer.readData(&(*option_data)[0], option_length);
+ impl_->pseudo_rrs_.push_back(PseudoRR(option_code, option_data));
+ impl_->rdlength_ += option_length;
+ rdata_len -= option_length;
+ }
+}
+
+OPT::OPT(const OPT& other) :
+ Rdata(), impl_(new OPTImpl(*other.impl_)) {
+}
+
+OPT&
+OPT::operator=(const OPT& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ impl_.reset(new OPTImpl(*source.impl_));
+
+ return (*this);
+}
+
+OPT::~OPT() {
+}
+
+std::string
+OPT::toText() const {
+ isc_throw(isc::InvalidOperation,
+ "OPT RRs do not have a presentation format");
+}
+
+void
+OPT::toWire(OutputBuffer& buffer) const {
+ for (auto const& pseudo_rr : impl_->pseudo_rrs_) {
+ buffer.writeUint16(pseudo_rr.getCode());
+ const uint16_t length = pseudo_rr.getLength();
+ buffer.writeUint16(length);
+ if (length > 0) {
+ buffer.writeData(pseudo_rr.getData(), length);
+ }
+ }
+}
+
+void
+OPT::toWire(AbstractMessageRenderer& renderer) const {
+ for (auto const& pseudo_rr : impl_->pseudo_rrs_) {
+ renderer.writeUint16(pseudo_rr.getCode());
+ const uint16_t length = pseudo_rr.getLength();
+ renderer.writeUint16(length);
+ if (length > 0) {
+ renderer.writeData(pseudo_rr.getData(), length);
+ }
+ }
+}
+
+int
+OPT::compare(const Rdata&) const {
+ isc_throw(isc::InvalidOperation,
+ "It is meaningless to compare a set of OPT pseudo RRs; "
+ "they have unspecified order");
+ return (0);
+}
+
+void
+OPT::appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length) {
+ // See if it overflows 16-bit length field. We only worry about the
+ // pseudo-RR length here, not the whole message length (which should
+ // be checked and enforced elsewhere).
+ if (static_cast<uint16_t>(impl_->rdlength_ + length) <
+ impl_->rdlength_) {
+ isc_throw(isc::InvalidParameter,
+ "Option length " << length
+ << " would overflow OPT RR RDLEN (currently "
+ << impl_->rdlength_ << ").");
+ }
+
+ boost::shared_ptr<std::vector<uint8_t> >
+ option_data(new std::vector<uint8_t>(length));
+ if (length != 0) {
+ std::memcpy(&(*option_data)[0], data, length);
+ }
+ impl_->pseudo_rrs_.push_back(PseudoRR(code, option_data));
+ impl_->rdlength_ += length;
+}
+
+const std::vector<OPT::PseudoRR>&
+OPT::getPseudoRRs() const {
+ return (impl_->pseudo_rrs_);
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid PTR RDATA. There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The PTRDNAME must be absolute since there's no parameter that
+/// specifies the origin name; if it is not absolute, \c
+/// MissingNameOrigin exception will be thrown. These must not be
+/// represented as a quoted string.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
+PTR::PTR(const std::string& type_str) :
+ // Fill in dummy name and replace them soon below.
+ ptr_name_(Name::ROOT_NAME()) {
+ try {
+ std::istringstream ss(type_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ ptr_name_ = createNameFromLexer(lexer, 0);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for PTR: "
+ << type_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct PTR from '" <<
+ type_str << "': " << ex.what());
+ }
+}
+
+PTR::PTR(InputBuffer& buffer, size_t) :
+ ptr_name_(buffer) {
+ // we don't need rdata_len for parsing. if necessary, the caller will
+ // check consistency.
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of a PTR RDATA. The PTRDNAME field can be
+/// non-absolute if \c origin is non-null, in which case \c origin is
+/// used to make it absolute. It must not be represented as a quoted
+/// string.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \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 PTRDNAME when it
+/// is non-absolute.
+PTR::PTR(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ ptr_name_(createNameFromLexer(lexer, origin)) {
+}
+
+PTR::PTR(const PTR& source) :
+ Rdata(), ptr_name_(source.ptr_name_) {
+}
+
+std::string
+PTR::toText() const {
+ return (ptr_name_.toText());
+}
+
+void
+PTR::toWire(OutputBuffer& buffer) const {
+ ptr_name_.toWire(buffer);
+}
+
+void
+PTR::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(ptr_name_);
+}
+
+int
+PTR::compare(const Rdata& other) const {
+ // The compare method normally begins with this dynamic cast.
+ const PTR& other_ptr = dynamic_cast<const PTR&>(other);
+
+ return (compareNames(ptr_name_, other_ptr.ptr_name_));
+
+}
+
+const Name&
+PTR::getPTRName() const {
+ return (ptr_name_);
+}
+
+namespace {
+// This is the minimum necessary length of all wire-format RRSIG RDATA:
+// - two 8-bit fields (algorithm and labels)
+// - two 16-bit fields (covered and tag)
+// - three 32-bit fields (original TTL, expire and inception)
+const size_t RRSIG_MINIMUM_LEN = 2 * sizeof(uint8_t) + 2 * sizeof(uint16_t) +
+ 3 * sizeof(uint32_t);
+}
+
+struct RRSIGImpl {
+ // straightforward representation of RRSIG RDATA fields
+ RRSIGImpl(const RRType& covered, uint8_t algorithm, uint8_t labels,
+ uint32_t originalttl, uint32_t timeexpire,
+ uint32_t timeinception, uint16_t tag, const Name& signer,
+ const vector<uint8_t>& signature) :
+ covered_(covered), algorithm_(algorithm), labels_(labels),
+ originalttl_(originalttl), timeexpire_(timeexpire),
+ timeinception_(timeinception), tag_(tag), signer_(signer),
+ signature_(signature) {
+ }
+
+ const RRType covered_;
+ uint8_t algorithm_;
+ uint8_t labels_;
+ uint32_t originalttl_;
+ uint32_t timeexpire_;
+ uint32_t timeinception_;
+ uint16_t tag_;
+ const Name signer_;
+ const vector<uint8_t> signature_;
+};
+
+// helper function for string and lexer constructors
+std::unique_ptr<RRSIGImpl>
+RRSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const RRType covered(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 0xff) {
+ isc_throw(InvalidRdataText, "RRSIG algorithm out of range");
+ }
+ const uint32_t labels =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (labels > 0xff) {
+ isc_throw(InvalidRdataText, "RRSIG labels out of range");
+ }
+ const uint32_t originalttl =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ const uint32_t timeexpire =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t timeinception =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t tag =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (tag > 0xffff) {
+ isc_throw(InvalidRdataText, "RRSIG key tag out of range");
+ }
+ const Name& signer = createNameFromLexer(lexer, origin);
+
+ string signature_txt;
+ string signature_part;
+ // Whitespace is allowed within base64 text, so read to the end of input.
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(signature_part);
+ signature_txt.append(signature_part);
+ }
+ lexer.ungetToken();
+
+ vector<uint8_t> signature;
+ // missing signature is okay
+ if (signature_txt.size() > 0) {
+ decodeBase64(signature_txt, signature);
+ }
+
+ return (std::unique_ptr<RRSIGImpl>(new RRSIGImpl(covered,
+ algorithm,
+ labels,
+ originalttl,
+ timeexpire,
+ timeinception,
+ static_cast<uint16_t>(tag),
+ signer,
+ signature)));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid RRSIG RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The Signer's Name must be absolute since there's no parameter that
+/// specifies the origin name; if this is not absolute, \c MissingNameOrigin
+/// exception will be thrown. This must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name constructor.
+/// \throw InvalidRdataText Other general syntax errors.
+RRSIG::RRSIG(const std::string& rrsig_str) {
+ // We use unique_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the RRSIGImpl that constructFromLexer() returns.
+ boost::shared_ptr<RRSIGImpl> impl_ptr;
+
+ try {
+ std::istringstream iss(rrsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ impl_ = constructFromLexer(lexer, 0);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RRSIG: "
+ << rrsig_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RRSIG from '" <<
+ rrsig_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RRSIG RDATA. The Signer's Name fields can be non absolute if \c
+/// origin is non null, in which case \c origin is used to make it absolute.
+/// This must not be represented as a quoted string.
+///
+/// The Original TTL field is a valid decimal representation of an unsigned
+/// 32-bit integer. Note that alternate textual representations of \c RRTTL,
+/// such as "1H" for 3600 seconds, are not allowed here.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name constructor if
+/// construction of textual fields as these objects fail.
+///
+/// \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 Signer's Name when
+/// it is non absolute.
+RRSIG::RRSIG(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ impl_ = constructFromLexer(lexer, origin);
+}
+
+RRSIG::RRSIG(InputBuffer& buffer, size_t rdata_len) {
+ size_t pos = buffer.getPosition();
+
+ if (rdata_len < RRSIG_MINIMUM_LEN) {
+ isc_throw(InvalidRdataLength, "RRSIG too short");
+ }
+
+ RRType covered(buffer);
+ uint8_t algorithm = buffer.readUint8();
+ uint8_t labels = buffer.readUint8();
+ uint32_t originalttl = buffer.readUint32();
+ uint32_t timeexpire = buffer.readUint32();
+ uint32_t timeinception = buffer.readUint32();
+ uint16_t tag = buffer.readUint16();
+ Name signer(buffer);
+
+ // rdata_len must be sufficiently large to hold non empty signature data.
+ if (rdata_len <= buffer.getPosition() - pos) {
+ isc_throw(InvalidRdataLength, "RRSIG too short");
+ }
+ rdata_len -= (buffer.getPosition() - pos);
+
+ vector<uint8_t> signature(rdata_len);
+ buffer.readData(&signature[0], rdata_len);
+
+ impl_.reset(new RRSIGImpl(covered, algorithm, labels,
+ originalttl, timeexpire, timeinception, tag,
+ signer, signature));
+}
+
+RRSIG::RRSIG(const RRSIG& source) :
+ Rdata(), impl_(new RRSIGImpl(*source.impl_)) {
+}
+
+RRSIG&
+RRSIG::operator=(const RRSIG& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ impl_.reset(new RRSIGImpl(*source.impl_));
+
+ return (*this);
+}
+
+RRSIG::~RRSIG() {
+}
+
+string
+RRSIG::toText() const {
+ return (impl_->covered_.toText() +
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_))
+ + " " + boost::lexical_cast<string>(static_cast<int>(impl_->labels_))
+ + " " + boost::lexical_cast<string>(impl_->originalttl_)
+ + " " + timeToText32(impl_->timeexpire_)
+ + " " + timeToText32(impl_->timeinception_)
+ + " " + boost::lexical_cast<string>(impl_->tag_)
+ + " " + impl_->signer_.toText()
+ + " " + encodeBase64(impl_->signature_));
+}
+
+void
+RRSIG::toWire(OutputBuffer& buffer) const {
+ impl_->covered_.toWire(buffer);
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeUint8(impl_->labels_);
+ buffer.writeUint32(impl_->originalttl_);
+ buffer.writeUint32(impl_->timeexpire_);
+ buffer.writeUint32(impl_->timeinception_);
+ buffer.writeUint16(impl_->tag_);
+ impl_->signer_.toWire(buffer);
+ buffer.writeData(&impl_->signature_[0], impl_->signature_.size());
+}
+
+void
+RRSIG::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->covered_.toWire(renderer);
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeUint8(impl_->labels_);
+ renderer.writeUint32(impl_->originalttl_);
+ renderer.writeUint32(impl_->timeexpire_);
+ renderer.writeUint32(impl_->timeinception_);
+ renderer.writeUint16(impl_->tag_);
+ renderer.writeName(impl_->signer_, false);
+ renderer.writeData(&impl_->signature_[0], impl_->signature_.size());
+}
+
+int
+RRSIG::compare(const Rdata& other) const {
+ const RRSIG& other_rrsig = dynamic_cast<const RRSIG&>(other);
+
+ if (impl_->covered_.getCode() != other_rrsig.impl_->covered_.getCode()) {
+ return (impl_->covered_.getCode() <
+ other_rrsig.impl_->covered_.getCode() ? -1 : 1);
+ }
+ if (impl_->algorithm_ != other_rrsig.impl_->algorithm_) {
+ return (impl_->algorithm_ < other_rrsig.impl_->algorithm_ ? -1 : 1);
+ }
+ if (impl_->labels_ != other_rrsig.impl_->labels_) {
+ return (impl_->labels_ < other_rrsig.impl_->labels_ ? -1 : 1);
+ }
+ if (impl_->originalttl_ != other_rrsig.impl_->originalttl_) {
+ return (impl_->originalttl_ < other_rrsig.impl_->originalttl_ ?
+ -1 : 1);
+ }
+ if (impl_->timeexpire_ != other_rrsig.impl_->timeexpire_) {
+ return (impl_->timeexpire_ < other_rrsig.impl_->timeexpire_ ?
+ -1 : 1);
+ }
+ if (impl_->timeinception_ != other_rrsig.impl_->timeinception_) {
+ return (impl_->timeinception_ < other_rrsig.impl_->timeinception_ ?
+ -1 : 1);
+ }
+ if (impl_->tag_ != other_rrsig.impl_->tag_) {
+ return (impl_->tag_ < other_rrsig.impl_->tag_ ? -1 : 1);
+ }
+
+ int cmp = compareNames(impl_->signer_, other_rrsig.impl_->signer_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+
+ size_t this_len = impl_->signature_.size();
+ size_t other_len = other_rrsig.impl_->signature_.size();
+ size_t cmplen = min(this_len, other_len);
+ cmp = memcmp(&impl_->signature_[0], &other_rrsig.impl_->signature_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+const RRType&
+RRSIG::typeCovered() const {
+ return (impl_->covered_);
+}
+
+SOA::SOA(InputBuffer& buffer, size_t) :
+ mname_(buffer), rname_(buffer) {
+ // we don't need rdata_len for parsing. if necessary, the caller will
+ // check consistency.
+ buffer.readData(numdata_, sizeof(numdata_));
+}
+
+namespace {
+void
+fillParameters(MasterLexer& lexer, uint8_t numdata[20]) {
+ // Copy serial, refresh, retry, expire, minimum. We accept the extended
+ // TTL-compatible style for the latter four.
+ OutputBuffer buffer(20);
+ buffer.writeUint32(lexer.getNextToken(MasterToken::NUMBER).getNumber());
+ for (int i = 0; i < 4; ++i) {
+ buffer.writeUint32(RRTTL(lexer.getNextToken(MasterToken::STRING).
+ getString()).getValue());
+ }
+ memcpy(numdata, buffer.getData(), buffer.getLength());
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SOA RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The MNAME and RNAME must be absolute since there's no parameter that
+/// specifies the origin name; if these are not absolute, \c MissingNameOrigin
+/// exception will be thrown. These must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
+SOA::SOA(const std::string& soastr) :
+ // Fill in dummy name and replace them soon below.
+ mname_(Name::ROOT_NAME()), rname_(Name::ROOT_NAME()) {
+ try {
+ std::istringstream ss(soastr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mname_ = createNameFromLexer(lexer, 0);
+ rname_ = createNameFromLexer(lexer, 0);
+ fillParameters(lexer, numdata_);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SOA: "
+ << soastr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SOA from '" <<
+ soastr << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SOA RDATA. The MNAME and RNAME fields can be non absolute if
+/// \c origin is non null, in which case \c origin is used to make them
+/// absolute. These must not be represented as a quoted string.
+///
+/// The REFRESH, RETRY, EXPIRE, and MINIMUM fields can be either a valid
+/// decimal representation of an unsigned 32-bit integer or other
+/// valid textual representation of \c RRTTL such as "1H" (which means 3600).
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \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 MNAME and RNAME when
+/// they are non absolute.
+SOA::SOA(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mname_(createNameFromLexer(lexer, origin)),
+ rname_(createNameFromLexer(lexer, origin)) {
+ fillParameters(lexer, numdata_);
+}
+
+SOA::SOA(const Name& mname, const Name& rname, uint32_t serial,
+ uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) :
+ mname_(mname), rname_(rname) {
+ OutputBuffer b(20);
+ b.writeUint32(serial);
+ b.writeUint32(refresh);
+ b.writeUint32(retry);
+ b.writeUint32(expire);
+ b.writeUint32(minimum);
+ isc_throw_assert(b.getLength() == sizeof(numdata_));
+ memcpy(numdata_, b.getData(), sizeof(numdata_));
+}
+
+SOA::SOA(const SOA& other) :
+ Rdata(), mname_(other.mname_), rname_(other.rname_) {
+ memcpy(numdata_, other.numdata_, sizeof(numdata_));
+}
+
+void
+SOA::toWire(OutputBuffer& buffer) const {
+ mname_.toWire(buffer);
+ rname_.toWire(buffer);
+ buffer.writeData(numdata_, sizeof(numdata_));
+}
+
+void
+SOA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(mname_);
+ renderer.writeName(rname_);
+ renderer.writeData(numdata_, sizeof(numdata_));
+}
+
+Serial
+SOA::getSerial() const {
+ InputBuffer b(numdata_, sizeof(numdata_));
+ return (Serial(b.readUint32()));
+}
+
+uint32_t
+SOA::getMinimum() const {
+ // Make sure the buffer access is safe.
+ BOOST_STATIC_ASSERT(sizeof(numdata_) ==
+ sizeof(uint32_t) * 4 + sizeof(uint32_t));
+
+ InputBuffer b(&numdata_[sizeof(uint32_t) * 4], sizeof(uint32_t));
+ return (b.readUint32());
+}
+
+string
+SOA::toText() const {
+ InputBuffer b(numdata_, sizeof(numdata_));
+ uint32_t serial = b.readUint32();
+ uint32_t refresh = b.readUint32();
+ uint32_t retry = b.readUint32();
+ uint32_t expire = b.readUint32();
+ uint32_t minimum = b.readUint32();
+
+ return (mname_.toText() + " " + rname_.toText() + " " +
+ lexical_cast<string>(serial) + " " +
+ lexical_cast<string>(refresh) + " " +
+ lexical_cast<string>(retry) + " " +
+ lexical_cast<string>(expire) + " " +
+ lexical_cast<string>(minimum));
+}
+
+int
+SOA::compare(const Rdata& other) const {
+ const SOA& other_soa = dynamic_cast<const SOA&>(other);
+
+ int order = compareNames(mname_, other_soa.mname_);
+ if (order != 0) {
+ return (order);
+ }
+
+ order = compareNames(rname_, other_soa.rname_);
+ if (order != 0) {
+ return (order);
+ }
+
+ return (memcmp(numdata_, other_soa.numdata_, sizeof(numdata_)));
+}
+
+const uint16_t TKEY::GSS_API_MODE = 3;
+
+// straightforward representation of TKEY RDATA fields
+struct TKEYImpl {
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// \param algorithm The DNS name of the algorithm e.g. gss-tsig.
+ /// \param inception The inception time (in seconds since 1970).
+ /// \param expire The expire time (in seconds since 1970).
+ /// \param mode The mode e.g. Diffie-Hellman (2) or GSS-API (3).
+ /// \param error The error code (extended error space shared with TSIG).
+ /// \param key The key (can be empty).
+ /// \param other_data The other data (can be and usually is empty).
+ TKEYImpl(const Name& algorithm, uint32_t inception, uint32_t expire,
+ uint16_t mode, uint16_t error, vector<uint8_t>& key,
+ vector<uint8_t>& other_data) :
+ algorithm_(algorithm), inception_(inception), expire_(expire),
+ mode_(mode), error_(error), key_(key), other_data_(other_data) {
+ }
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// \param algorithm The DNS name of the algorithm e.g. gss-tsig.
+ /// \param inception The inception time (in seconds since 1970).
+ /// \param expire The expire time (in seconds since 1970).
+ /// \param mode The mode e.g. Diffie-Hellman (2) or GSS-API (3).
+ /// \param error The error code (extended error space shared with TSIG).
+ /// \param key_len The key length (0 means no key).
+ /// \param key The key (can be 0).
+ /// \param other_len The other data length (0 means no other data).
+ /// \param other_data The other data (can be and usually is 0).
+ TKEYImpl(const Name& algorithm, uint32_t inception, uint32_t expire,
+ uint16_t mode, uint16_t error, size_t key_len,
+ const void* key, size_t other_len, const void* other_data) :
+ algorithm_(algorithm), inception_(inception), expire_(expire),
+ mode_(mode), error_(error),
+ key_(key_len > 0 ?
+ vector<uint8_t>(static_cast<const uint8_t*>(key),
+ static_cast<const uint8_t*>(key) + key_len) :
+ vector<uint8_t>(key_len)),
+ other_data_(other_len > 0 ?
+ vector<uint8_t>(static_cast<const uint8_t*>(other_data),
+ static_cast<const uint8_t*>(other_data) +
+ other_len) :
+ vector<uint8_t>(other_len)) {
+ }
+
+ /// \brief Common part of toWire methods.
+ /// \tparam Output \c OutputBuffer or \c AbstractMessageRenderer.
+ template <typename Output>
+ void toWireCommon(Output& output) const;
+
+ /// \brief The DNS name of the algorithm e.g. gss-tsig.
+ const Name algorithm_;
+
+ /// \brief The inception time (in seconds since 1970).
+ const uint32_t inception_;
+
+ /// \brief The expire time (in seconds since 1970).
+ const uint32_t expire_;
+
+ /// \brief The mode e.g. Diffie-Hellman (2) or GSS-API (3).
+ const uint16_t mode_;
+
+ /// \brief The error code (extended error space shared with TSIG).
+ const uint16_t error_;
+
+ /// \brief The key (can be empty).
+ const vector<uint8_t> key_;
+
+ /// \brief The other data (can be and usually is empty).
+ const vector<uint8_t> other_data_;
+};
+
+// helper function for string and lexer constructors
+std::unique_ptr<TKEYImpl>
+TKEY::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const Name& algorithm =
+ createNameFromLexer(lexer, origin ? origin : &Name::ROOT_NAME());
+
+ const uint32_t inception =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+
+ const uint32_t expire =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+
+ /// The mode is either a mnemonic (only one is defined: GSS-API) or
+ /// a number.
+ const string& mode_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint32_t mode = 0;
+ if (mode_txt == "GSS-API") {
+ mode = GSS_API_MODE;
+ } else {
+ /// we cast to uint32_t and range-check, because casting directly to
+ /// uint16_t will convert negative numbers to large positive numbers
+ try {
+ mode = boost::lexical_cast<uint32_t>(mode_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TKEY Mode");
+ }
+ if (mode > 0xffff) {
+ isc_throw(InvalidRdataText, "TKEY Mode out of range");
+ }
+ }
+
+ const string& error_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint32_t error = 0;
+ // XXX: In the initial implementation we hardcode the mnemonics.
+ // We'll soon generalize this.
+ if (error_txt == "NOERROR") {
+ error = Rcode::NOERROR_CODE;
+ } else if (error_txt == "BADSIG") {
+ error = TSIGError::BAD_SIG_CODE;
+ } else if (error_txt == "BADKEY") {
+ error = TSIGError::BAD_KEY_CODE;
+ } else if (error_txt == "BADTIME") {
+ error = TSIGError::BAD_TIME_CODE;
+ } else if (error_txt == "BADMODE") {
+ error = TSIGError::BAD_MODE_CODE;
+ } else if (error_txt == "BADNAME") {
+ error = TSIGError::BAD_NAME_CODE;
+ } else if (error_txt == "BADALG") {
+ error = TSIGError::BAD_ALG_CODE;
+ } else if (error_txt == "BADTRUNC") {
+ error = TSIGError::BAD_TRUNC_CODE;
+ } else {
+ /// we cast to uint32_t and range-check, because casting directly to
+ /// uint16_t will convert negative numbers to large positive numbers
+ try {
+ error = boost::lexical_cast<uint32_t>(error_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TKEY Error");
+ }
+ if (error > 0xffff) {
+ isc_throw(InvalidRdataText, "TKEY Error out of range");
+ }
+ }
+
+ const uint32_t keylen =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (keylen > 0xffff) {
+ isc_throw(InvalidRdataText, "TKEY Key Len out of range");
+ }
+ const string keydata_txt = (keylen > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> key_data;
+ decodeBase64(keydata_txt, key_data);
+ if (key_data.size() != keylen) {
+ isc_throw(InvalidRdataText,
+ "TKEY Key Data length does not match Other Len");
+ }
+
+ const uint32_t otherlen =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (otherlen > 0xffff) {
+ isc_throw(InvalidRdataText, "TKEY Other Len out of range");
+ }
+ const string otherdata_txt = (otherlen > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> other_data;
+ decodeBase64(otherdata_txt, other_data);
+ if (other_data.size() != otherlen) {
+ isc_throw(InvalidRdataText,
+ "TKEY Other Data length does not match Other Len");
+ }
+ // RFC2845 says Other Data is "empty unless Error == BADTIME".
+ // However, we don't enforce that.
+
+ return (std::unique_ptr<TKEYImpl>(new TKEYImpl(algorithm, inception,
+ expire, mode, error,
+ key_data, other_data)));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid TKEY RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// \c tkey_str must be formatted as follows:
+/// \code <Algorithm Name> <Inception> <Expire> <Mode> <Error>
+/// <Key Len> [<Key Data>] <Other Len> [<Other Data>]
+/// \endcode
+///
+/// Note that, since the Algorithm Name field is defined to be "in domain name
+/// syntax", but it is not actually a domain name, it does not have to be
+/// fully qualified.
+///
+/// The Mode field is an unsigned 16-bit decimal integer as specified
+/// in RFC2930 or a common mnemonic. Currently only "GSS-API" (case sensitive)
+/// is supported ("Diffie-Hellman" is not).
+///
+/// The Error field is an unsigned 16-bit decimal integer or a valid mnemonic
+/// as specified in RFC2845. Currently, "NOERROR", "BADSIG", "BADKEY",
+/// "BADTIME", "BADMODE", "BADNAME", and "BADALG" are supported
+/// (case sensitive). In future versions other representations that
+/// are compatible with the DNS RCODE may be supported.
+///
+/// The Key Data and Other Data fields are base-64 encoded strings that do not
+/// contain space characters.
+/// If the Key Len field is 0, the Key Data field must not appear in
+/// \c tkey_str.
+/// If the Other Len field is 0, the Other Data field must not appear in
+/// \c tkey_str.
+/// The decoded data of the Key Data field is Key Len bytes of binary stream.
+/// The decoded data of the Other Data field is Other Len bytes of binary
+/// stream.
+///
+/// An example of valid string is:
+/// \code "gss-tsig. 20210501120000 20210501130000 0 3 aabbcc 0" \endcode
+/// In this example Other Data is missing because Other Len is 0.
+///
+/// Note that RFC2930 does not define the standard presentation format
+/// of %TKEY RR, so the above syntax is implementation specific.
+/// This is, however, compatible with the format acceptable to BIND 9's
+/// RDATA parser.
+///
+/// \throw Others Exception from the Name constructors.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+/// \throw BadValue if Key Data or Other Data is not validly encoded
+/// in base-64.
+///
+/// \param tkey_str A string containing the RDATA to be created
+TKEY::TKEY(const std::string& tkey_str) {
+
+ try {
+ std::istringstream ss(tkey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ = constructFromLexer(lexer, 0);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for TKEY: " << tkey_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct TKEY from '" << tkey_str << "': "
+ << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an TKEY RDATA.
+///
+/// See \c TKEY::TKEY(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TKEY::TKEY(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ impl_ = constructFromLexer(lexer, origin);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// When a read operation on \c buffer fails (e.g., due to a corrupted
+/// message) a corresponding exception from the \c InputBuffer class will
+/// be thrown.
+/// If the wire-format data does not begin with a valid domain name,
+/// a corresponding exception from the \c Name class will be thrown.
+/// In addition, this constructor internally involves resource allocation,
+/// and if it fails a corresponding standard exception will be thrown.
+///
+/// According to RFC3597, the Algorithm field must be a non compressed form
+/// of domain name. But this implementation accepts a %TKEY RR even if that
+/// field is compressed.
+///
+/// \param buffer A buffer storing the wire format data.
+/// \param rdata_len The length of the RDATA in bytes, normally expected
+/// to be the value of the RDLENGTH field of the corresponding RR.
+/// But this constructor does not use this parameter; if necessary, the caller
+/// must check consistency between the length parameter and the actual
+/// RDATA length.
+TKEY::TKEY(InputBuffer& buffer, size_t) {
+ Name algorithm(buffer);
+
+ const uint32_t inception = buffer.readUint32();
+
+ const uint32_t expire = buffer.readUint32();
+
+ const uint16_t mode = buffer.readUint16();
+
+ const uint16_t error = buffer.readUint16();
+
+ const uint16_t key_len = buffer.readUint16();
+ vector<uint8_t> key(key_len);
+ if (key_len > 0) {
+ buffer.readData(&key[0], key_len);
+ }
+
+ const uint16_t other_len = buffer.readUint16();
+ vector<uint8_t> other_data(other_len);
+ if (other_len > 0) {
+ buffer.readData(&other_data[0], other_len);
+ }
+
+ impl_.reset(new TKEYImpl(algorithm, inception, expire, mode, error,
+ key, other_data));
+}
+
+TKEY::TKEY(const Name& algorithm, uint32_t inception, uint32_t expire,
+ uint16_t mode, uint16_t error, uint16_t key_len,
+ const void* key, uint16_t other_len, const void* other_data) {
+ if ((key_len == 0 && key != 0) || (key_len > 0 && key == 0)) {
+ isc_throw(InvalidParameter, "TKEY Key length and data inconsistent");
+ }
+ if ((other_len == 0 && other_data != 0) ||
+ (other_len > 0 && other_data == 0)) {
+ isc_throw(InvalidParameter,
+ "TKEY Other data length and data inconsistent");
+ }
+ impl_.reset(new TKEYImpl(algorithm, inception, expire, mode, error,
+ key_len, key, other_len, other_data));
+}
+
+/// \brief The copy constructor.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This constructor never throws an exception otherwise.
+TKEY::TKEY(const TKEY& source) : Rdata(), impl_(new TKEYImpl(*source.impl_)) {
+}
+
+TKEY&
+TKEY::operator=(const TKEY& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ impl_.reset(new TKEYImpl(*source.impl_));
+
+ return (*this);
+}
+
+TKEY::~TKEY() {
+}
+
+/// \brief Convert the \c TKEY to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c TKEY(const std::string&))).
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+///
+/// \return A \c string object that represents the \c TKEY object.
+std::string
+TKEY::toText() const {
+ string result;
+
+ result += impl_->algorithm_.toText() + " " +
+ timeToText32(impl_->inception_) + " " +
+ timeToText32(impl_->expire_) + " ";
+ if (impl_->mode_ == GSS_API_MODE) {
+ result += "GSS-API ";
+ } else {
+ result += lexical_cast<string>(impl_->mode_) + " ";
+ }
+ result += TSIGError(impl_->error_).toText() + " " +
+ lexical_cast<string>(impl_->key_.size()) + " ";
+ if (!impl_->key_.empty()) {
+ result += encodeBase64(impl_->key_) + " ";
+ }
+ result += lexical_cast<string>(impl_->other_data_.size());
+ if (!impl_->other_data_.empty()) {
+ result += " " + encodeBase64(impl_->other_data_);
+ }
+
+ return (result);
+}
+
+// Common sequence of toWire() operations used for the two versions of
+// toWire().
+template <typename Output>
+void
+TKEYImpl::toWireCommon(Output& output) const {
+ output.writeUint32(inception_);
+ output.writeUint32(expire_);
+ output.writeUint16(mode_);
+ output.writeUint16(error_);
+ const uint16_t key_len = key_.size();
+ output.writeUint16(key_len);
+ if (key_len > 0) {
+ output.writeData(&key_[0], key_len);
+ }
+ const uint16_t other_len = other_data_.size();
+ output.writeUint16(other_len);
+ if (other_len > 0) {
+ output.writeData(&other_data_[0], other_len);
+ }
+}
+
+/// \brief Render the \c TKEY in the wire format without name compression.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+TKEY::toWire(OutputBuffer& buffer) const {
+ impl_->algorithm_.toWire(buffer);
+ impl_->toWireCommon<OutputBuffer>(buffer);
+}
+
+/// \brief Render the \c TKEY in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, the Algorithm field (a domain name) will not
+/// be compressed. However, the domain name could be a target of compression
+/// of other compressible names (though pretty unlikely), the offset
+/// information of the algorithm name may be recorded in \c renderer.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+TKEY::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(impl_->algorithm_, false);
+ impl_->toWireCommon<AbstractMessageRenderer>(renderer);
+}
+
+// A helper function commonly used for TKEY::compare().
+int
+vectorComp(const vector<uint8_t>& v1, const vector<uint8_t>& v2) {
+ const size_t this_size = v1.size();
+ const size_t other_size = v2.size();
+ if (this_size != other_size) {
+ return (this_size < other_size ? -1 : 1);
+ }
+ if (this_size > 0) {
+ return (memcmp(&v1[0], &v2[0], this_size));
+ }
+ return (0);
+}
+
+/// \brief Compare two instances of \c TKEY RDATA.
+///
+/// This method compares \c this and the \c other \c TKEY objects
+/// in terms of the DNSSEC sorting order as defined in RFC4034, and returns
+/// the result as an integer.
+///
+/// This method is expected to be used in a polymorphic way, and the
+/// parameter to compare against is therefore of the abstract \c Rdata class.
+/// However, comparing two \c Rdata objects of different RR types
+/// is meaningless, and \c other must point to a \c TKEY object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param other the right-hand operand to compare against.
+/// \return < 0 if \c this would be sorted before \c other.
+/// \return 0 if \c this is identical to \c other in terms of sorting order.
+/// \return > 0 if \c this would be sorted after \c other.
+int
+TKEY::compare(const Rdata& other) const {
+ const TKEY& other_tkey = dynamic_cast<const TKEY&>(other);
+
+ const int ncmp = compareNames(impl_->algorithm_,
+ other_tkey.impl_->algorithm_);
+ if (ncmp != 0) {
+ return (ncmp);
+ }
+
+ if (impl_->inception_ != other_tkey.impl_->inception_) {
+ return (impl_->inception_ < other_tkey.impl_->inception_ ? -1 : 1);
+ }
+ if (impl_->expire_ != other_tkey.impl_->expire_) {
+ return (impl_->expire_ < other_tkey.impl_->expire_ ? -1 : 1);
+ }
+ if (impl_->mode_ != other_tkey.impl_->mode_) {
+ return (impl_->mode_ < other_tkey.impl_->mode_ ? -1 : 1);
+ }
+ if (impl_->error_ != other_tkey.impl_->error_) {
+ return (impl_->error_ < other_tkey.impl_->error_ ? -1 : 1);
+ }
+
+ const int vcmp = vectorComp(impl_->key_, other_tkey.impl_->key_);
+ if (vcmp != 0) {
+ return (vcmp);
+ }
+ return (vectorComp(impl_->other_data_, other_tkey.impl_->other_data_));
+}
+
+const Name&
+TKEY::getAlgorithm() const {
+ return (impl_->algorithm_);
+}
+
+uint32_t
+TKEY::getInception() const {
+ return (impl_->inception_);
+}
+
+string
+TKEY::getInceptionDate() const {
+ return (timeToText32(impl_->inception_));
+}
+
+uint32_t
+TKEY::getExpire() const {
+ return (impl_->expire_);
+}
+
+string
+TKEY::getExpireDate() const {
+ return (timeToText32(impl_->expire_));
+}
+
+uint16_t
+TKEY::getMode() const {
+ return (impl_->mode_);
+}
+
+uint16_t
+TKEY::getError() const {
+ return (impl_->error_);
+}
+
+uint16_t
+TKEY::getKeyLen() const {
+ return (impl_->key_.size());
+}
+
+const void*
+TKEY::getKey() const {
+ if (!impl_->key_.empty()) {
+ return (&impl_->key_[0]);
+ } else {
+ return (0);
+ }
+}
+
+uint16_t
+TKEY::getOtherLen() const {
+ return (impl_->other_data_.size());
+}
+
+const void*
+TKEY::getOtherData() const {
+ if (!impl_->other_data_.empty()) {
+ return (&impl_->other_data_[0]);
+ } else {
+ return (0);
+ }
+}
+
+
+TXT&
+TXT::operator=(const TXT& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ impl_.reset(new TXTImpl(*source.impl_));
+
+ return (*this);
+}
+
+TXT::~TXT() {
+}
+
+TXT::TXT(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new TXTImpl(buffer, rdata_len)) {
+}
+
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+TXT::TXT(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(new TXTImpl(lexer)) {
+}
+
+TXT::TXT(const std::string& txtstr) :
+ impl_(new TXTImpl(txtstr)) {
+}
+
+TXT::TXT(const TXT& other) :
+ Rdata(), impl_(new TXTImpl(*other.impl_)) {
+}
+
+void
+TXT::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+void
+TXT::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+string
+TXT::toText() const {
+ return (impl_->toText());
+}
+
+int
+TXT::compare(const Rdata& other) const {
+ const TXT& other_txt = dynamic_cast<const TXT&>(other);
+
+ return (impl_->compare(*other_txt.impl_));
+}
+
+} // end of namespace "generic"
+
+namespace in {
+
+namespace {
+void
+convertToIPv4Addr(const char* src, size_t src_len, uint32_t* dst) {
+ // This check specifically rejects invalid input that begins with valid
+ // address text followed by a nul character (and possibly followed by
+ // further garbage). It cannot be detected by inet_pton().
+ //
+ // Note that this is private subroutine of the in::A constructors, which
+ // pass std::string.size() or StringRegion::len as src_len, so it should
+ // be equal to strlen() unless there's an intermediate nul character.
+ if (src_len != strlen(src)) {
+ isc_throw(InvalidRdataText,
+ "Bad IN/A RDATA text: unexpected nul in string: '"
+ << src << "'");
+ }
+ const int result = inet_pton(AF_INET, src, dst);
+ if (result == 0) {
+ isc_throw(InvalidRdataText, "Bad IN/A RDATA text: '" << src << "'");
+ } else if (result < 0) {
+ isc_throw(isc::Unexpected,
+ "Unexpected failure in parsing IN/A RDATA text: '"
+ << src << "': " << std::strerror(errno));
+ }
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must be a valid textual representation of an IPv4
+/// address as specified in RFC1035, that is, four decimal numbers separated
+/// by dots without any embedded spaces. Note that it excludes abbreviated
+/// forms such as "10.1" to mean "10.0.0.1".
+///
+/// Internally, this implementation uses the standard inet_pton() library
+/// function for the AF_INET family to parse and convert the textual
+/// representation. While standard compliant implementations of this function
+/// should accept exactly what this constructor expects, specific
+/// implementation may behave differently, in which case this constructor
+/// will simply accept the result of inet_pton(). In any case, the user of
+/// the class shouldn't assume such specific implementation behavior of
+/// inet_pton().
+///
+/// No extra character should be contained in \c addrstr other than the
+/// textual address. These include spaces and the nul character.
+///
+/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
+/// a valid IPv4 address.
+/// \throw Unexpected Unexpected system error in conversion (this should be
+/// very rare).
+///
+/// \param addrstr Textual representation of IPv4 address to be used as the
+/// RDATA.
+A::A(const std::string& addrstr) {
+ convertToIPv4Addr(addrstr.c_str(), addrstr.size(), &addr_);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of a class IN A RDATA.
+///
+/// The acceptable form of the textual address is generally the same as the
+/// string version of the constructor, but this version accepts beginning
+/// spaces and trailing spaces or other characters. Trailing non space
+/// characters would be considered an invalid form in an RR representation,
+/// but handling such errors is not the responsibility of this constructor.
+/// It also accepts other unusual syntax that would be considered valid
+/// in the context of DNS master file; for example, it accepts an IPv4
+/// address surrounded by parentheses, such as "(192.0.2.1)", although it's
+/// very unlikely to be used for this type of RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
+/// a valid IPv4 address.
+/// \throw Unexpected Unexpected system error in conversion (this should be
+/// very rare).
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+A::A(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+ convertToIPv4Addr(token.getStringRegion().beg, token.getStringRegion().len,
+ &addr_);
+}
+
+A::A(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len != sizeof(addr_)) {
+ isc_throw(DNSMessageFORMERR,
+ "IN/A RDATA construction from wire failed: Invalid length: "
+ << rdata_len);
+ }
+ if (buffer.getLength() - buffer.getPosition() < sizeof(addr_)) {
+ isc_throw(DNSMessageFORMERR,
+ "IN/A RDATA construction from wire failed: "
+ "insufficient buffer length: "
+ << buffer.getLength() - buffer.getPosition());
+ }
+ buffer.readData(&addr_, sizeof(addr_));
+}
+
+/// \brief Copy constructor.
+A::A(const A& other) : Rdata(), addr_(other.addr_) {
+}
+
+void
+A::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(&addr_, sizeof(addr_));
+}
+
+void
+A::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeData(&addr_, sizeof(addr_));
+}
+
+/// \brief Return a textual form of the underlying IPv4 address of the RDATA.
+string
+A::toText() const {
+ char addr_string[sizeof("255.255.255.255")];
+
+ if (inet_ntop(AF_INET, &addr_, addr_string, sizeof(addr_string)) == 0) {
+ isc_throw(Unexpected,
+ "Failed to convert IN/A RDATA to textual IPv4 address");
+ }
+
+ return (addr_string);
+}
+
+/// \brief Compare two in::A RDATAs.
+///
+/// In effect, it compares the two RDATA as an unsigned 32-bit integer.
+int
+A::compare(const Rdata& other) const {
+ const A& other_a = dynamic_cast<const A&>(other);
+ return (memcmp(&addr_, &other_a.addr_, sizeof(addr_)));
+}
+
+namespace {
+void
+convertToIPv6Addr(const char* src, size_t src_len, void* dst) {
+ // See a_1.cc for this check.
+ if (src_len != strlen(src)) {
+ isc_throw(InvalidRdataText,
+ "Bad IN/AAAA RDATA text: unexpected nul in string: '"
+ << src << "'");
+ }
+ const int result = inet_pton(AF_INET6, src, dst);
+ if (result == 0) {
+ isc_throw(InvalidRdataText, "Bad IN/AAAA RDATA text: '" << src << "'");
+ } else if (result < 0) {
+ isc_throw(isc::Unexpected,
+ "Unexpected failure in parsing IN/AAAA RDATA text: '"
+ << src << "': " << std::strerror(errno));
+ }
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must be a valid textual representation of an IPv6
+/// address as specified in RFC1886.
+///
+/// No extra character should be contained in \c addrstr other than the
+/// textual address. These include spaces and the nul character.
+///
+/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
+/// a valid IPv6 address.
+/// \throw Unexpected Unexpected system error in conversion (this should be
+/// very rare).
+///
+/// \param addrstr Textual representation of IPv6 address to be used as the
+/// RDATA.
+AAAA::AAAA(const std::string& addrstr) {
+ convertToIPv6Addr(addrstr.c_str(), addrstr.size(), addr_);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of a class IN AAAA RDATA.
+///
+/// The acceptable form of the textual address is generally the same as the
+/// string version of the constructor, but this version is slightly more
+/// flexible. See the similar constructor of \c in::A class; the same
+/// notes apply here.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
+/// a valid IPv6 address.
+/// \throw Unexpected Unexpected system error in conversion (this should be
+/// very rare).
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+AAAA::AAAA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+ convertToIPv6Addr(token.getStringRegion().beg, token.getStringRegion().len,
+ addr_);
+}
+
+/// \brief Copy constructor.
+AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len != sizeof(addr_)) {
+ isc_throw(DNSMessageFORMERR,
+ "IN/AAAA RDATA construction from wire failed: "
+ "Invalid length: " << rdata_len);
+ }
+ if (buffer.getLength() - buffer.getPosition() < sizeof(addr_)) {
+ isc_throw(DNSMessageFORMERR,
+ "IN/AAAA RDATA construction from wire failed: "
+ "insufficient buffer length: "
+ << buffer.getLength() - buffer.getPosition());
+ }
+ buffer.readData(&addr_, sizeof(addr_));
+}
+
+AAAA::AAAA(const AAAA& other) : Rdata() {
+ memcpy(addr_, other.addr_, sizeof(addr_));
+}
+
+/// \brief Return a textual form of the underlying IPv6 address of the RDATA.
+void
+AAAA::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(&addr_, sizeof(addr_));
+}
+
+void
+AAAA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeData(&addr_, sizeof(addr_));
+}
+
+string
+AAAA::toText() const {
+ char addr_string[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+
+ if (inet_ntop(AF_INET6, &addr_, addr_string, sizeof(addr_string)) == 0) {
+ isc_throw(Unexpected,
+ "Failed to convert IN/AAAA RDATA to textual IPv6 address");
+ }
+
+ return (string(addr_string));
+}
+
+/// \brief Compare two in::AAAA RDATAs.
+///
+/// In effect, it compares the two RDATA as an unsigned 128-bit integer.
+int
+AAAA::compare(const Rdata& other) const {
+ const AAAA& other_a = dynamic_cast<const AAAA&>(other);
+ return (memcmp(&addr_, &other_a.addr_, sizeof(addr_)));
+}
+
+void
+DHCID::constructFromLexer(MasterLexer& lexer) {
+ string digest_txt = lexer.getNextToken(MasterToken::STRING).getString();
+
+ // Whitespace is allowed within base64 text, so read to the end of input.
+ string digest_part;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(digest_part);
+ digest_txt.append(digest_part);
+ }
+ lexer.ungetToken();
+
+ decodeBase64(digest_txt, digest_);
+}
+
+/// \brief Constructor from string.
+///
+/// \param dhcid_str A base-64 representation of the DHCID binary data.
+///
+/// \throw InvalidRdataText if the string could not be parsed correctly.
+DHCID::DHCID(const std::string& dhcid_str) {
+ try {
+ std::istringstream iss(dhcid_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ constructFromLexer(lexer);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for DHCID: "
+ << dhcid_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct DHCID from '" <<
+ dhcid_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of a DHCID RDATA.
+///
+/// \throw BadValue if the text is not valid base-64.
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+DHCID::DHCID(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ constructFromLexer(lexer);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// \param buffer A buffer storing the wire format data.
+/// \param rdata_len The length of the RDATA in bytes
+DHCID::DHCID(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len == 0) {
+ isc_throw(InvalidRdataLength, "Missing DHCID rdata");
+ }
+
+ digest_.resize(rdata_len);
+ buffer.readData(&digest_[0], rdata_len);
+}
+
+/// \brief The copy constructor.
+///
+/// This trivial copy constructor never throws an exception.
+DHCID::DHCID(const DHCID& other) : Rdata(), digest_(other.digest_) {
+}
+
+/// \brief Render the \c DHCID in the wire format.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+DHCID::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(&digest_[0], digest_.size());
+}
+
+/// \brief Render the \c DHCID in the wire format into a
+/// \c MessageRenderer object.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer in which the \c DHCID is to be stored.
+void
+DHCID::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeData(&digest_[0], digest_.size());
+}
+
+/// \brief Convert the \c DHCID to a string.
+///
+/// This method returns a \c std::string object representing the \c DHCID.
+///
+/// \return A string representation of \c DHCID.
+string
+DHCID::toText() const {
+ return (encodeBase64(digest_));
+}
+
+/// \brief Compare two instances of \c DHCID RDATA.
+///
+/// See documentation in \c Rdata.
+int
+DHCID::compare(const Rdata& other) const {
+ const DHCID& other_dhcid = dynamic_cast<const DHCID&>(other);
+
+ size_t this_len = digest_.size();
+ size_t other_len = other_dhcid.digest_.size();
+ size_t cmplen = min(this_len, other_len);
+ int cmp = memcmp(&digest_[0], &other_dhcid.digest_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+/// \brief Accessor method to get the DHCID digest
+///
+/// \return A reference to the binary DHCID data
+const std::vector<uint8_t>&
+DHCID::getDigest() const {
+ return (digest_);
+}
+
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
diff --git a/src/lib/dns/rdataclass.h b/src/lib/dns/rdataclass.h
new file mode 100644
index 0000000..4af7e75
--- /dev/null
+++ b/src/lib/dns/rdataclass.h
@@ -0,0 +1,618 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H
+
+#include <dns/master_loader.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <dns/serial.h>
+#include <util/buffer.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <memory>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+
+class Name;
+class MasterLexer;
+class MasterLoaderCallbacks;
+class AbstractMessageRenderer;
+
+namespace rdata {
+namespace any {
+
+struct TSIGImpl;
+
+/// \brief \c rdata::TSIG class represents the TSIG RDATA as defined %in
+/// RFC2845.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// TSIG RDATA.
+class TSIG : public Rdata {
+public:
+ explicit TSIG(const std::string& type_str);
+ TSIG(isc::util::InputBuffer& buffer, size_t rdata_len);
+ TSIG(const TSIG& other);
+ TSIG(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// The parameters are a straightforward mapping of %TSIG RDATA
+ /// fields as defined %in RFC2845, but there are some implementation
+ /// specific notes as follows.
+ ///
+ /// \c algorithm is a \c Name object that specifies the algorithm.
+ /// For example, if the algorithm is HMAC-SHA256, \c algorithm would be
+ /// \c Name("hmac-sha256").
+ ///
+ /// \c time_signed corresponds to the Time Signed field, which is of
+ /// 48-bit unsigned integer type, and therefore cannot exceed 2^48-1;
+ /// otherwise, an exception of type \c OutOfRange will be thrown.
+ ///
+ /// \c mac_size and \c mac correspond to the MAC Size and MAC fields,
+ /// respectively. When the MAC field is empty, \c mac must be null.
+ /// \c mac_size and \c mac must be consistent %in that \c mac_size is 0 if
+ /// and only if \c mac is null; otherwise an exception of type
+ /// InvalidParameter will be thrown.
+ ///
+ /// The same restriction applies to \c other_len and \c other_data,
+ /// which correspond to the Other Len and Other Data fields, respectively.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+ uint16_t mac_size, const void* mac, uint16_t original_id,
+ uint16_t error, uint16_t other_len, const void* other_data);
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
+ TSIG& operator=(const TSIG& source);
+
+ /// \brief The destructor.
+ ~TSIG();
+
+ /// \brief Return the algorithm name.
+ ///
+ /// This method never throws an exception.
+ const Name& getAlgorithm() const;
+
+ /// \brief Return the value of the Time Signed field.
+ ///
+ /// The returned value does not exceed 2^48-1.
+ ///
+ /// This method never throws an exception.
+ uint64_t getTimeSigned() const;
+
+ /// \brief Return the value of the Fudge field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getFudge() const;
+
+ /// \brief Return the value of the MAC Size field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getMACSize() const;
+
+ /// \brief Return the value of the MAC field.
+ ///
+ /// If the MAC field is empty, it returns null.
+ /// Otherwise, the memory region beginning at the address returned by
+ /// this method is valid up to the bytes specified by the return value
+ /// of \c getMACSize().
+ /// The memory region is only valid while the corresponding \c TSIG
+ /// object is valid. The caller must hold the \c TSIG object while
+ /// it needs to refer to the region or it must make a local copy of the
+ /// region.
+ ///
+ /// This method never throws an exception.
+ const void* getMAC() const;
+
+ /// \brief Return the value of the Original ID field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getOriginalID() const;
+
+ /// \brief Return the value of the Error field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getError() const;
+
+ /// \brief Return the value of the Other Len field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getOtherLen() const;
+
+ /// \brief Return the value of the Other Data field.
+ ///
+ /// The same note as \c getMAC() applies.
+ ///
+ /// This method never throws an exception.
+ const void* getOtherData() const;
+private:
+ std::unique_ptr<TSIGImpl> constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ std::unique_ptr<TSIGImpl> impl_;
+};
+
+} // end of namespace "any"
+
+namespace ch {
+
+class A : public Rdata {
+public:
+ explicit A(const std::string& type_str);
+ A(isc::util::InputBuffer& buffer, size_t rdata_len);
+ A(const A& other);
+ A(
+ 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;
+ virtual int compare(const Rdata& other) const;
+};
+
+} // end of namespace "ch"
+
+namespace generic {
+namespace detail {
+
+/// \brief Construct a Name object using a master lexer and optional origin.
+///
+/// This is a convenient shortcut of commonly used code pattern that would
+/// be used to build RDATA that contain a domain name field.
+///
+/// Note that this function throws an exception against invalid input.
+/// The (direct or indirect) caller's responsibility needs to expect and
+/// handle exceptions appropriately.
+///
+/// \throw MasterLexer::LexerError The next token from lexer is not string.
+/// \throw Other Exceptions from the \c Name class constructor if the next
+/// string token from the lexer does not represent a valid name.
+///
+/// \param lexer A \c MasterLexer object. Its next token is expected to be
+/// a string that represent a domain name.
+/// \param origin If non null, specifies the origin of the name to be
+/// constructed.
+///
+/// \return A new Name object that corresponds to the next string token of
+/// the \c lexer.
+inline Name
+createNameFromLexer(MasterLexer& lexer, const Name* origin) {
+ const MasterToken::StringRegion& str_region =
+ lexer.getNextToken(MasterToken::STRING).getStringRegion();
+ return (Name(str_region.beg, str_region.len, origin));
+}
+
+template<class Type, uint16_t typeCode>
+class TXTLikeImpl;
+
+} // namespace detail
+
+class NS : public Rdata {
+public:
+ explicit NS(const std::string& type_str);
+ NS(isc::util::InputBuffer& buffer, size_t rdata_len);
+ NS(const NS& other);
+ NS(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ /// Specialized constructor
+ ///
+ explicit NS(const Name& nsname) : nsname_(nsname) {
+ }
+ ///
+ /// Specialized methods
+ ///
+ const Name& getNSName() const;
+private:
+ Name nsname_;
+};
+
+struct OPTImpl;
+
+class OPT : public Rdata {
+public:
+ explicit OPT(const std::string& type_str);
+ OPT(isc::util::InputBuffer& buffer, size_t rdata_len);
+ OPT(const OPT& other);
+ OPT(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ // The default constructor makes sense for OPT as it can be empty.
+ OPT();
+ OPT& operator=(const OPT& source);
+ ~OPT();
+
+ /// \brief A class representing a pseudo RR (or option) within an
+ /// OPT RR (see RFC 6891).
+ class PseudoRR {
+ public:
+ /// \brief Constructor.
+ /// \param code The OPTION-CODE field of the pseudo RR.
+ /// \param data The OPTION-DATA field of the pseudo
+ /// RR. OPTION-LENGTH is set to the length of this vector.
+ PseudoRR(uint16_t code,
+ boost::shared_ptr<std::vector<uint8_t> >& data);
+
+ /// \brief Return the option code of this pseudo RR.
+ uint16_t getCode() const;
+
+ /// \brief Return the option data of this pseudo RR.
+ const uint8_t* getData() const;
+
+ /// \brief Return the length of the option data of this
+ /// pseudo RR.
+ uint16_t getLength() const;
+
+ private:
+ uint16_t code_;
+ boost::shared_ptr<std::vector<uint8_t>> data_;
+ };
+
+ /// \brief Append a pseudo RR (option) in this OPT RR.
+ ///
+ /// \param code The OPTION-CODE field of the pseudo RR.
+ /// \param data The OPTION-DATA field of the pseudo RR.
+ /// \param length The size of the \c data argument. OPTION-LENGTH is
+ /// set to this size.
+ /// \throw isc::InvalidParameter if this pseudo RR would cause
+ /// the OPT RDATA to overflow its RDLENGTH.
+ void appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length);
+
+ /// \brief Return a vector of the pseudo RRs (options) in this
+ /// OPT RR.
+ ///
+ /// Note: The returned reference is only valid during the lifetime
+ /// of this \c generic::OPT object. It should not be used
+ /// afterwards.
+ const std::vector<PseudoRR>& getPseudoRRs() const;
+
+private:
+ std::unique_ptr<OPTImpl> impl_;
+};
+
+class PTR : public Rdata {
+public:
+ explicit PTR(const std::string& type_str);
+ PTR(isc::util::InputBuffer& buffer, size_t rdata_len);
+ PTR(const PTR& other);
+ PTR(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ ///
+ /// Specialized constructor
+ ///
+ explicit PTR(const Name& ptr_name) : ptr_name_(ptr_name) {
+ }
+ ///
+ /// Specialized methods
+ ///
+ const Name& getPTRName() const;
+private:
+ Name ptr_name_;
+};
+
+struct RRSIGImpl;
+
+/// \brief \c rdata::RRSIG class represents the RRSIG RDATA as defined %in
+/// RFC4034.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RRSIG RDATA.
+class RRSIG : public Rdata {
+public:
+ explicit RRSIG(const std::string& type_str);
+ RRSIG(isc::util::InputBuffer& buffer, size_t rdata_len);
+ RRSIG(const RRSIG& other);
+ RRSIG(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ RRSIG& operator=(const RRSIG& source);
+ ~RRSIG();
+
+ // specialized methods
+ const RRType& typeCovered() const;
+private:
+ // helper function for string and lexer constructors
+ std::unique_ptr<RRSIGImpl> constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ std::unique_ptr<RRSIGImpl> impl_;
+};
+
+class SOA : public Rdata {
+public:
+ explicit SOA(const std::string& type_str);
+ SOA(isc::util::InputBuffer& buffer, size_t rdata_len);
+ SOA(const SOA& other);
+ SOA(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ SOA(const Name& mname, const Name& rname, uint32_t serial,
+ uint32_t refresh, uint32_t retry, uint32_t expire,
+ uint32_t minimum);
+
+ /// \brief Returns the serial stored in the SOA.
+ Serial getSerial() const;
+
+ /// brief Returns the minimum TTL field value of the SOA.
+ uint32_t getMinimum() const;
+private:
+ /// Note: this is a prototype version; we may reconsider
+ /// this representation later.
+ Name mname_;
+ Name rname_;
+ /// serial, refresh, retry, expire, minimum, stored in network byte order
+ uint8_t numdata_[20];
+};
+
+struct TKEYImpl;
+
+/// \brief \c rdata::TKEY class represents the TKEY RDATA as defined %in
+/// RFC2930.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// TKEY RDATA.
+class TKEY : public Rdata {
+public:
+ explicit TKEY(const std::string& type_str);
+ TKEY(isc::util::InputBuffer& buffer, size_t rdata_len);
+ TKEY(const TKEY& other);
+ TKEY(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// The parameters are a straightforward mapping of %TKEY RDATA
+ /// fields as defined %in RFC2930.
+ ///
+ /// This RR is pretty close to the TSIG RR with 32 bit timestamps,
+ /// or the RRSIG RR with a second "other" data field.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ ///
+ /// \param algorithm The DNS name of the algorithm e.g. gss-tsig.
+ /// \param inception The inception time (in seconds since 1970).
+ /// \param expire The expire time (in seconds since 1970).
+ /// \param mode The mode e.g. Diffie-Hellman (2) or GSS-API (3).
+ /// \param error The error code (extended error space shared with TSIG).
+ /// \param key_len The key length (0 means no key).
+ /// \param key The key (can be 0).
+ /// \param other_len The other data length (0 means no other data).
+ /// \param other_data The other data (can be and usually is 0).
+ TKEY(const Name& algorithm, uint32_t inception, uint32_t expire,
+ uint16_t mode, uint16_t error, uint16_t key_len,
+ const void* key, uint16_t other_len, const void* other_data);
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
+ TKEY& operator=(const TKEY& source);
+
+ /// \brief The destructor.
+ ~TKEY();
+
+ /// \brief Return the algorithm name.
+ ///
+ /// This method never throws an exception.
+ const Name& getAlgorithm() const;
+
+ /// \brief Return the value of the Inception field as a number.
+ ///
+ /// This method never throws an exception.
+ uint32_t getInception() const;
+
+ /// \brief Return the value of the Inception field as a string.
+ std::string getInceptionDate() const;
+
+ /// \brief Return the value of the Expire field as a number.
+ ///
+ /// This method never throws an exception.
+ uint32_t getExpire() const;
+
+ /// \brief Return the value of the Expire field as a string.
+ std::string getExpireDate() const;
+
+ /// \brief Return the value of the Mode field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getMode() const;
+
+ /// \brief Return the value of the Error field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getError() const;
+
+ /// \brief Return the value of the Key Len field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getKeyLen() const;
+
+ /// \brief Return the value of the Key field.
+ ///
+ /// This method never throws an exception.
+ const void* getKey() const;
+
+ /// \brief Return the value of the Other Len field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getOtherLen() const;
+
+ /// \brief Return the value of the Other Data field.
+ ///
+ /// The same note as \c getMAC() applies.
+ ///
+ /// This method never throws an exception.
+ const void* getOtherData() const;
+
+ /// \brief The GSS_API constant for the Mode field.
+ static const uint16_t GSS_API_MODE;
+
+private:
+ std::unique_ptr<TKEYImpl> constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ std::unique_ptr<TKEYImpl> impl_;
+};
+
+class TXT : public Rdata {
+public:
+ explicit TXT(const std::string& type_str);
+ TXT(isc::util::InputBuffer& buffer, size_t rdata_len);
+ TXT(const TXT& other);
+ TXT(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ TXT& operator=(const TXT& source);
+ ~TXT();
+
+private:
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl;
+ std::unique_ptr<TXTImpl> impl_;
+};
+} // namespace generic
+
+
+namespace in {
+
+class A : public Rdata {
+public:
+ explicit A(const std::string& type_str);
+ A(isc::util::InputBuffer& buffer, size_t rdata_len);
+ A(const A& other);
+ A(
+ 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;
+ virtual int compare(const Rdata& other) const;
+private:
+ uint32_t addr_; // raw IPv4 address (network byte order)
+};
+
+class AAAA : public Rdata {
+public:
+ explicit AAAA(const std::string& type_str);
+ AAAA(isc::util::InputBuffer& buffer, size_t rdata_len);
+ AAAA(const AAAA& other);
+ AAAA(
+ 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;
+ virtual int compare(const Rdata& other) const;
+private:
+ uint8_t addr_[16]; // raw IPv6 address (network byte order)
+};
+
+/// \brief \c rdata::DHCID class represents the DHCID RDATA as defined %in
+/// RFC4701.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DHCID RDATA.
+class DHCID : public Rdata {
+public:
+ explicit DHCID(const std::string& type_str);
+ DHCID(isc::util::InputBuffer& buffer, size_t rdata_len);
+ DHCID(const DHCID& other);
+ DHCID(
+ 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;
+ virtual int compare(const Rdata& other) const;
+
+ /// \brief Return the digest.
+ ///
+ /// This method never throws an exception.
+ const std::vector<uint8_t>& getDigest() const;
+
+private:
+ // helper for string and lexer constructors
+ void constructFromLexer(MasterLexer& lexer);
+
+ /// \brief Private data representation
+ ///
+ /// Opaque data at least 3 octets long as per RFC4701.
+ ///
+ std::vector<uint8_t> digest_;
+};
+
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // DNS_RDATACLASS_H
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
new file mode 100644
index 0000000..f4f5c49
--- /dev/null
+++ b/src/lib/dns/rrclass.cc
@@ -0,0 +1,73 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrclass.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+#include <string>
+
+using namespace isc::dns;
+using namespace isc::util;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+
+RRClass::RRClass(const std::string& class_str) {
+ uint16_t classcode;
+ if (!RRParamRegistry::getRegistry().textToClassCode(class_str, classcode)) {
+ isc_throw(InvalidRRClass,
+ "Unrecognized RR class string: " + class_str);
+ }
+ classcode_ = classcode;
+}
+
+RRClass::RRClass(InputBuffer& buffer) {
+ if (buffer.getLength() - buffer.getPosition() < sizeof(uint16_t)) {
+ isc_throw(IncompleteRRClass, "incomplete wire-format RR class");
+ }
+ classcode_ = buffer.readUint16();
+}
+
+const string
+RRClass::toText() const {
+ return (RRParamRegistry::getRegistry().codeToClassText(classcode_));
+}
+
+void
+RRClass::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(classcode_);
+}
+
+void
+RRClass::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(classcode_);
+}
+
+RRClass*
+RRClass::createFromText(const string& class_str) {
+ uint16_t class_code;
+ if (RRParamRegistry::getRegistry().textToClassCode(class_str,
+ class_code)) {
+ return (new RRClass(class_code));
+ }
+ return (0);
+}
+
+ostream&
+operator<<(ostream& os, const RRClass& rrclass) {
+ os << rrclass.toText();
+ return (os);
+}
+}
+}
diff --git a/src/lib/dns/rrclass.h b/src/lib/dns/rrclass.h
new file mode 100644
index 0000000..e57d458
--- /dev/null
+++ b/src/lib/dns/rrclass.h
@@ -0,0 +1,339 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RRCLASS_H
+#define RRCLASS_H
+
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+#include <string>
+#include <ostream>
+
+#include <dns/exceptions.h>
+
+#include <boost/optional.hpp>
+
+// Undefine the macro IN which is defined in some operating systems
+// but conflicts the IN RR class.
+
+#ifdef IN
+#undef IN
+#endif
+
+namespace isc {
+
+namespace dns {
+
+// forward declarations
+class AbstractMessageRenderer;
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRClass object
+/// is being constructed from an unrecognized string.
+///
+class InvalidRRClass : public DNSTextError {
+public:
+ InvalidRRClass(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRClass object
+/// is being constructed from a incomplete (too short) wire-format data.
+///
+class IncompleteRRClass : public isc::dns::Exception {
+public:
+ IncompleteRRClass(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// The \c RRClass class encapsulates DNS resource record classes.
+///
+/// This class manages the 16-bit integer class codes in quite a straightforward
+/// way. The only non trivial task is to handle textual representations of
+/// RR classes, such as "IN", "CH", or "CLASS65534".
+///
+/// This class consults a helper \c RRParamRegistry class, which is a registry
+/// of RR related parameters and has the singleton object. This registry
+/// provides a mapping between RR class codes and their "well-known" textual
+/// representations.
+/// Parameters of RR classes defined by DNS protocol standards are automatically
+/// registered at initialization time and are ensured to be always available for
+/// applications unless the application explicitly modifies the registry.
+///
+/// For convenience, this class defines constant class objects corresponding to
+/// standard RR classes. These are generally referred to as the form of
+/// <code>RRClass::{class-text}()</code>.
+/// For example, \c RRClass::IN() is an \c RRClass object corresponding to the
+/// IN class (class code 1).
+/// Note that these constants are used through a "proxy" function.
+/// This is because they may be used to initialize another non-local (e.g.
+/// global or namespace-scope) static object as follows:
+///
+/// \code
+/// namespace foo {
+/// const RRClass default_class = RRClass::IN();
+/// } \endcode
+///
+/// In order to ensure that the constant RRClass object has been initialized
+/// before the initialization for \c default_class, we need help from
+/// the proxy function.
+///
+/// Note to developers: same note as \c RRType applies.
+class RRClass {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+ /// Constructor from an integer class code.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param classcode An 16-bit integer code corresponding to the RRClass.
+ explicit RRClass(uint16_t classcode) : classcode_(classcode) {
+ }
+ ///
+ /// A valid string is one of "well-known" textual class representations
+ /// such as "IN" or "CH", or in the standard format for "unknown"
+ /// classes as defined in RFC3597, i.e., "CLASSnnnn".
+ ///
+ /// More precisely, the "well-known" representations are the ones stored
+ /// in the \c RRParamRegistry registry (see the class description).
+ ///
+ /// As for the format of "CLASSnnnn", "nnnn" must represent a valid 16-bit
+ /// unsigned integer, which may contain leading 0's as long as it consists
+ /// of at most 5 characters (inclusive).
+ /// For example, "CLASS1" and "CLASS0001" are valid and represent the same
+ /// class, but "CLASS65536" and "CLASS000001" are invalid.
+ /// A "CLASSnnnn" representation is valid even if the corresponding class
+ /// code is registered in the \c RRParamRegistry object. For example, both
+ /// "IN" and "CLASS1" are valid and represent the same class.
+ ///
+ /// All of these representations are case insensitive; "IN" and "in", and
+ /// "CLASS1" and "class1" are all valid and represent the same classes,
+ /// respectively.
+ ///
+ /// If the given string is not recognized as a valid representation of
+ /// an RR class, an exception of class \c InvalidRRClass will be thrown.
+ ///
+ /// \param class_str A string representation of the \c RRClass
+ explicit RRClass(const std::string& class_str);
+ /// Constructor from wire-format data.
+ ///
+ /// The \c buffer parameter normally stores a complete DNS message
+ /// containing the RRClass to be constructed. The current read position of
+ /// the buffer points to the head of the class.
+ ///
+ /// If the given data does not large enough to contain a 16-bit integer,
+ /// an exception of class \c IncompleteRRClass will be thrown.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ explicit RRClass(isc::util::InputBuffer& buffer);
+
+ /// A separate factory of RRClass from text.
+ ///
+ /// This static method is similar to the constructor that takes a
+ /// string object, but works as a factory and reports parsing
+ /// failure in the form of the return value. Normally the
+ /// constructor version should suffice, but in some cases the caller
+ /// may have to expect mixture of valid and invalid input, and may
+ /// want to minimize the overhead of possible exception handling.
+ /// This version is provided for such purpose.
+ ///
+ /// For the format of the \c class_str argument, see the
+ /// <code>RRClass(const std::string&)</code> constructor.
+ ///
+ /// If the given text represents a valid RRClass, it returns a
+ /// pointer to a new \c RRClass object. If the given text does not
+ /// represent a valid RRClass, it returns null.
+ ///
+ /// One main purpose of this function is to minimize the overhead
+ /// when the given text does not represent a valid RR class. For
+ /// this reason this function intentionally omits the capability of
+ /// delivering a detailed reason for the parse failure, such as in the
+ /// \c want() string when exception is thrown from the constructor
+ /// (it will internally require a creation of string object, which
+ /// is relatively expensive). If such detailed information is
+ /// necessary, the constructor version should be used to catch the
+ /// resulting exception.
+ ///
+ /// This function never throws the \c InvalidRRClass exception.
+ ///
+ /// \param class_str A string representation of the \c RRClass.
+ /// \return A new RRClass object for the given text or a null value.
+ static RRClass* createFromText(const std::string& class_str);
+
+ ///
+ /// We use the default copy constructor intentionally.
+ //@}
+ /// We use the default copy assignment operator intentionally.
+ ///
+
+ ///
+ /// \name Converter methods
+ ///
+ //@{
+ /// \brief Convert the \c RRClass to a string.
+ ///
+ /// If a "well known" textual representation for the class code is
+ /// registered in the RR parameter registry (see the class description),
+ /// that will be used as the return value of this method. Otherwise, this
+ /// method creates a new string for an "unknown" class in the format defined
+ /// in RFC3597, i.e., "CLASSnnnn", and returns it.
+ ///
+ /// If resource allocation for the string fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \return A string representation of the \c RRClass.
+ const std::string toText() const;
+ /// \brief Render the \c RRClass in the wire format.
+ ///
+ /// This method renders the class code in network byte order via
+ /// \c renderer, which encapsulates output buffer and other rendering
+ /// contexts.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer in which the RRClass is to be stored.
+ void toWire(AbstractMessageRenderer& renderer) const;
+ /// \brief Render the \c RRClass in the wire format.
+ ///
+ /// This method renders the class code in network byte order into the
+ /// \c buffer.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ void toWire(isc::util::OutputBuffer& buffer) const;
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Returns the RR class code as a 16-bit unsigned integer.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return An 16-bit integer code corresponding to the RRClass.
+ uint16_t getCode() const {
+ return (classcode_);
+ }
+ //@}
+
+ ///
+ /// \name Comparison methods
+ ///
+ //@{
+ /// \brief Return true iff two RRClasses are equal.
+ ///
+ /// Two RRClasses are equal iff their class codes are equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRClass object to compare against.
+ /// \return true if the two RRClasses are equal; otherwise false.
+ bool equals(const RRClass& other) const {
+ return (classcode_ == other.classcode_);
+ }
+
+ /// \brief Same as \c equals().
+ bool operator==(const RRClass& other) const {
+ return (equals(other));
+ }
+
+ /// \brief Return true iff two RRClasses are not equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRClass object to compare against.
+ /// \return true if the two RRClasses are not equal; otherwise false.
+ bool nequals(const RRClass& other) const {
+ return (classcode_ != other.classcode_);
+ }
+ /// \brief Same as \c nequals().
+ bool operator!=(const RRClass& other) const {
+ return (nequals(other));
+ }
+
+ /// \brief Less-than comparison for RRClass against \c other
+ ///
+ /// We define the less-than relationship based on their class codes;
+ /// one RRClass is less than the other iff the code of the former is less
+ /// than that of the other as unsigned integers.
+ /// The relationship is meaningless in terms of DNS protocol; the only
+ /// reason we define this method is that RRClass objects can be stored in
+ /// STL containers without requiring user-defined less-than relationship.
+ /// We therefore don't define other comparison operators.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRClass object to compare against.
+ /// \return true if \c this RRClass is less than the \c other; otherwise
+ /// false.
+ bool operator<(const RRClass& other) const {
+ return (classcode_ < other.classcode_);
+ }
+
+ static const RRClass& ANY();
+ static const RRClass& IN();
+ static const RRClass& CH();
+ static const RRClass& NONE();
+
+private:
+ uint16_t classcode_;
+};
+
+inline const RRClass&
+RRClass::ANY() {
+ static RRClass rrclass(255);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::IN() {
+ static RRClass rrclass(1);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::CH() {
+ static RRClass rrclass(3);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::NONE() {
+ static RRClass rrclass(254);
+ return (rrclass);
+}
+
+///
+/// \brief Insert the \c RRClass as a string into stream.
+///
+/// This method convert the \c rrclass into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described in
+/// ostream::operator<< but applied to \c RRClass objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param rrclass The \c RRClass object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const RRClass& rrclass);
+}
+}
+#endif // RRCLASS_H
diff --git a/src/lib/dns/rrparamregistry.cc b/src/lib/dns/rrparamregistry.cc
new file mode 100644
index 0000000..9352d41
--- /dev/null
+++ b/src/lib/dns/rrparamregistry.cc
@@ -0,0 +1,644 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <cassert>
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <map>
+#include <stdint.h>
+#include <string>
+#include <sstream>
+#include <utility>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+
+namespace {
+///
+/// The following function and class are a helper to define case-insensitive
+/// equivalence relationship on strings. They are used in the mapping
+/// containers below.
+///
+bool
+CICharLess(char c1, char c2) {
+ return (tolower(static_cast<unsigned char>(c1)) <
+ tolower(static_cast<unsigned char>(c2)));
+}
+
+struct CIStringLess {
+ bool operator()(const string& s1, const string& s2) const {
+ return (lexicographical_compare(s1.begin(), s1.end(),
+ s2.begin(), s2.end(), CICharLess));
+ }
+};
+
+struct RRTypeParam {
+ RRTypeParam(const string& code_string, uint16_t code) :
+ code_string_(code_string), code_(code) {}
+ string code_string_;
+ uint16_t code_;
+
+ /// magic constants
+ static const unsigned int MAX_CODE = 0xffff;
+ static const string& UNKNOWN_PREFIX();
+ static size_t UNKNOWN_PREFIXLEN();
+ static const string& UNKNOWN_MAX();
+ static size_t UNKNOWN_MAXLEN();
+};
+
+typedef boost::shared_ptr<RRTypeParam> RRTypeParamPtr;
+typedef map<string, RRTypeParamPtr, CIStringLess> StrRRTypeMap;
+typedef map<uint16_t, RRTypeParamPtr> CodeRRTypeMap;
+
+inline const string&
+RRTypeParam::UNKNOWN_PREFIX() {
+ static const string p("TYPE");
+ return (p);
+}
+
+inline size_t
+RRTypeParam::UNKNOWN_PREFIXLEN() {
+ static size_t plen = UNKNOWN_PREFIX().size();
+ return (plen);
+}
+
+inline const string&
+RRTypeParam::UNKNOWN_MAX() {
+ static const string p("TYPE65535");
+ return (p);
+}
+
+inline size_t
+RRTypeParam::UNKNOWN_MAXLEN() {
+ static size_t plen = UNKNOWN_MAX().size();
+ return (plen);
+}
+
+struct RRClassParam {
+ RRClassParam(const string& code_string, uint16_t code) :
+ code_string_(code_string), code_(code) {}
+ string code_string_;
+ uint16_t code_;
+
+ /// magic constants
+ static const unsigned int MAX_CODE = 0xffff;
+ static const string& UNKNOWN_PREFIX();
+ static size_t UNKNOWN_PREFIXLEN();
+ static const string& UNKNOWN_MAX();
+ static size_t UNKNOWN_MAXLEN();
+};
+
+typedef boost::shared_ptr<RRClassParam> RRClassParamPtr;
+typedef map<string, RRClassParamPtr, CIStringLess> StrRRClassMap;
+typedef map<uint16_t, RRClassParamPtr> CodeRRClassMap;
+
+inline const string&
+RRClassParam::UNKNOWN_PREFIX() {
+ static const string p("CLASS");
+ return (p);
+}
+
+inline size_t
+RRClassParam::UNKNOWN_PREFIXLEN() {
+ static size_t plen = UNKNOWN_PREFIX().size();
+ return (plen);
+}
+
+inline const string&
+RRClassParam::UNKNOWN_MAX() {
+ static const string p("CLASS65535");
+ return (p);
+}
+
+inline size_t
+RRClassParam::UNKNOWN_MAXLEN() {
+ static size_t plen = UNKNOWN_MAX().size();
+ return (plen);
+}
+} // end of anonymous namespace
+
+/// Note: the element ordering in the type/class pair is intentional.
+/// The standard library will perform inequality comparison (i.e, '<')
+/// in the way that the second elements (RRClass) are compared only when
+/// the first elements are equivalent.
+/// In practice, when we compare two pairs of RRType and RRClass, RRClass
+/// would be the same (and, in particular, be class IN) in the majority of
+/// cases. So this comparison ordering should be more efficient in common
+/// cases.
+typedef pair<RRType, RRClass> RRTypeClass;
+typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap;
+typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap;
+
+template <typename T>
+class RdataFactory : public AbstractRdataFactory {
+public:
+ virtual RdataPtr create(const string& rdata_str) const {
+ return (RdataPtr(new T(rdata_str)));
+ }
+
+ virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const {
+ return (RdataPtr(new T(buffer, rdata_len)));
+ }
+
+ virtual RdataPtr create(const Rdata& source) const {
+ return (RdataPtr(new T(dynamic_cast<const T&>(source))));
+ }
+
+ 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.
+///
+/// The implementation is hidden from applications. We can refer to specific
+/// members of this class only within the implementation source file.
+///
+struct RRParamRegistryImpl {
+ /// Mappings from RR type codes to textual representations.
+ StrRRTypeMap str2typemap;
+ /// Mappings from textual representations of RR types to integer codes.
+ CodeRRTypeMap code2typemap;
+ /// Mappings from RR class codes to textual representations.
+ StrRRClassMap str2classmap;
+ /// Mappings from textual representations of RR classes to integer codes.
+ CodeRRClassMap code2classmap;
+ RdataFactoryMap rdata_factories;
+ GenericRdataFactoryMap genericrdata_factories;
+};
+
+RRParamRegistry::RRParamRegistry() : impl_(new RRParamRegistryImpl()) {
+
+ // set up parameters for well-known RRs
+ try {
+ // ANY class well known RR types.
+ add("TSIG", 250, "ANY", 255, RdataFactoryPtr(new RdataFactory<any::TSIG>()));
+ // CH class well known RR types.
+ add("A", 1, "CH", 3, RdataFactoryPtr(new RdataFactory<ch::A>()));
+ // IN class well known RR types.
+ add("A", 1, "IN", 1, RdataFactoryPtr(new RdataFactory<in::A>()));
+;
+ add("NS", 2, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NS>()));
+ add("SOA", 6, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SOA>()));
+ add("PTR", 12, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::PTR>()));
+ add("TXT", 16, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TXT>()));
+ add("AAAA", 28, "IN", 1, RdataFactoryPtr(new RdataFactory<in::AAAA>()));
+ add("OPT", 41, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::OPT>()));
+ add("RRSIG", 46, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::RRSIG>()));
+ add("DHCID", 49, "IN", 1, RdataFactoryPtr(new RdataFactory<in::DHCID>()));
+ add("TKEY", 249, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TKEY>()));
+ // Generic well known RR types.
+ add("NS", 2, RdataFactoryPtr(new RdataFactory<generic::NS>()));
+ add("SOA", 6, RdataFactoryPtr(new RdataFactory<generic::SOA>()));
+ add("PTR", 12, RdataFactoryPtr(new RdataFactory<generic::PTR>()));
+ add("TXT", 16, RdataFactoryPtr(new RdataFactory<generic::TXT>()));
+ add("OPT", 41, RdataFactoryPtr(new RdataFactory<generic::OPT>()));
+ add("RRSIG", 46, RdataFactoryPtr(new RdataFactory<generic::RRSIG>()));
+ add("TKEY", 249, RdataFactoryPtr(new RdataFactory<generic::TKEY>()));
+ // Meta and Not supported RR classes.
+ addClass("HS", 4);
+ addClass("NONE", 254);
+ // Meta and non-implemented RR types
+ addType("MD", 3);
+ addType("MF", 4);
+ addType("CNAME", 5);
+ addType("MB", 7);
+ addType("MG", 8);
+ addType("MR", 9);
+ addType("NULL", 10);
+ addType("WKS", 11);
+ addType("HINFO", 13);
+ addType("MINFO", 14);
+ addType("MX", 15);
+ addType("RP", 17);
+ addType("AFSDB", 18);
+ addType("X25", 19);
+ addType("ISDN", 20);
+ addType("RT", 21);
+ addType("NSAP", 22);
+ addType("NSAP-PTR", 23);
+ addType("SIG", 24);
+ addType("KEY", 25);
+ addType("PX", 26);
+ addType("GPOS", 27);
+ addType("LOC", 29);
+ addType("NXT", 30);
+ addType("EID", 31);
+ addType("NIMLOC", 32);
+ addType("SRV", 33);
+ addType("ATMA", 34);
+ addType("NAPTR", 35);
+ addType("KX", 36);
+ addType("CERT", 37);
+ addType("A6", 38);
+ addType("DNAME", 39);
+ addType("SINK", 40);
+ addType("APL", 42);
+ addType("DS", 43);
+ addType("SSHFP", 44);
+ addType("IPSECKEY", 45);
+ addType("NSEC", 47);
+ addType("DNSKEY", 48);
+ addType("NSEC3", 50);
+ addType("NSEC3PARAM", 51);
+ addType("TLSA", 52);
+ addType("SMIMEA", 53);
+ // Unassigned 54
+ addType("HIP", 55);
+ addType("NINFO", 56);
+ addType("RKEY", 57);
+ addType("TALINK", 58);
+ addType("CDS", 59);
+ addType("CDNSKEY", 60);
+ addType("OPENPGPKEY", 61);
+ addType("CSYNC", 62);
+ addType("ZONEMD", 63);
+ addType("SVCB", 64);
+ addType("HTTPS", 65);
+ // Unassigned 66-98
+ addType("SPF", 99);
+ addType("UINFO", 100);
+ addType("UID", 101);
+ addType("GID", 102);
+ addType("UNSPEC", 103);
+ addType("NID", 104);
+ addType("L32", 105);
+ addType("L64", 106);
+ addType("LP", 107);
+ addType("EUI48", 108);
+ addType("EUI64", 109);
+ // Unassigned 110-248
+ addType("IXFR", 251);
+ addType("AXFR", 252);
+ addType("MAILB", 253);
+ addType("MAILA", 254);
+ addType("ANY", 255); // also known as "*"
+ addType("URI", 256);
+ addType("CAA", 257);
+ addType("AVC", 258);
+ addType("DOA", 259);
+ addType("AMTRELAY", 260);
+ addType("RESINFO", 261);
+ // Unassigned 262-32767
+ addType("TA", 32768);
+ addType("DLV", 32769);
+ } catch (...) {
+ throw;
+ }
+}
+
+RRParamRegistry::~RRParamRegistry() {
+}
+
+RRParamRegistry&
+RRParamRegistry::getRegistry() {
+ static RRParamRegistry registry;
+
+ return (registry);
+}
+
+void
+RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
+ RdataFactoryPtr rdata_factory) {
+ bool type_added = false;
+ try {
+ type_added = addType(typecode_string, typecode);
+ impl_->genericrdata_factories.insert(pair<RRType, RdataFactoryPtr>(
+ RRType(typecode),
+ rdata_factory));
+ } catch (...) {
+ if (type_added) {
+ removeType(typecode);
+ }
+ throw;
+ }
+}
+
+void
+RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
+ const std::string& classcode_string, uint16_t classcode,
+ RdataFactoryPtr rdata_factory) {
+ // Rollback logic on failure is complicated. If adding the new type or
+ // class fails, we should revert to the original state, cleaning up
+ // intermediate state. But we need to make sure that we don't remove
+ // existing data. addType()/addClass() will simply ignore an attempt to
+ // add the same data, so the cleanup should be performed only when we add
+ // something new but we fail in other part of the process.
+ bool type_added = false;
+ bool class_added = false;
+
+ try {
+ type_added = addType(typecode_string, typecode);
+ class_added = addClass(classcode_string, classcode);
+ impl_->rdata_factories.insert(pair<RRTypeClass, RdataFactoryPtr>(
+ RRTypeClass(RRType(typecode),
+ RRClass(classcode)),
+ rdata_factory));
+ } catch (...) {
+ if (type_added) {
+ removeType(typecode);
+ }
+ if (class_added) {
+ removeClass(classcode);
+ }
+ throw;
+ }
+}
+
+bool
+RRParamRegistry::removeRdataFactory(const RRType& rrtype,
+ const RRClass& rrclass) {
+ RdataFactoryMap::iterator found =
+ impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass));
+ if (found != impl_->rdata_factories.end()) {
+ impl_->rdata_factories.erase(found);
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+RRParamRegistry::removeRdataFactory(const RRType& rrtype) {
+ GenericRdataFactoryMap::iterator found =
+ impl_->genericrdata_factories.find(rrtype);
+ if (found != impl_->genericrdata_factories.end()) {
+ impl_->genericrdata_factories.erase(found);
+ return (true);
+ }
+
+ return (false);
+}
+
+namespace {
+///
+/// These are helper functions to implement case-insensitive string comparison.
+/// 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)));
+}
+
+bool
+caseStringEqual(const string& s1, const string& s2, size_t n) {
+ isc_throw_assert(s1.size() >= n && s2.size() >= n);
+
+ return (mismatch(s1.begin(), s1.begin() + n, s2.begin(), CICharEqual).first
+ == s1.begin() + n);
+}
+
+/// Code logic for RRTypes and RRClasses is mostly common except (C++) type and
+/// member names. So we define type-independent templates to describe the
+/// common logic and let concrete classes use it to avoid code duplicates.
+/// The following summarize template parameters used in the set of template
+/// functions:
+/// PT: parameter type, either RRTypeParam or RRClassParam
+/// MC: type of mapping class from code: either CodeRRTypeMap or CodeRRClassMap
+/// MS: type of mapping class from string: either StrRRTypeMap or StrRRClassMap
+/// ET: exception type for error handling: either InvalidRRType or
+/// InvalidRRClass
+template <typename PT, typename MC, typename MS, typename ET>
+inline bool
+addParam(const string& code_string, uint16_t code, MC& codemap, MS& stringmap) {
+ // Duplicate type check
+ typename MC::const_iterator found = codemap.find(code);
+ if (found != codemap.end()) {
+ if (found->second->code_string_ != code_string) {
+ isc_throw(ET, "Duplicate RR parameter registration");
+ }
+ return (false);
+ }
+
+ typedef boost::shared_ptr<PT> ParamPtr;
+ typedef pair<string, ParamPtr> StrParamPair;
+ typedef pair<uint16_t, ParamPtr> CodeParamPair;
+ ParamPtr param = ParamPtr(new PT(code_string, code));
+ try {
+ stringmap.insert(StrParamPair(code_string, param));
+ codemap.insert(CodeParamPair(code, param));
+ } catch (...) {
+ // Rollback to the previous state: not all of the erase operations will
+ // find the entry, but we don't care.
+ stringmap.erase(code_string);
+ codemap.erase(code);
+ throw;
+ }
+
+ return (true);
+}
+
+template <typename MC, typename MS>
+inline bool
+removeParam(uint16_t code, MC& codemap, MS& stringmap) {
+ typename MC::iterator found = codemap.find(code);
+
+ if (found != codemap.end()) {
+ size_t erased = stringmap.erase(found->second->code_string_);
+ // We must have a corresponding entry of the str2 map exists
+ isc_throw_assert(erased == 1);
+
+ codemap.erase(found);
+
+ return (true);
+ }
+
+ return (false);
+}
+
+template <typename PT, typename MS>
+inline bool
+textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) {
+ typename MS::const_iterator found;
+
+ found = stringmap.find(code_str);
+ if (found != stringmap.end()) {
+ ret_code = found->second->code_;
+ return (true);
+ }
+
+ size_t l = code_str.size();
+ if (l > PT::UNKNOWN_PREFIXLEN() &&
+ l <= PT::UNKNOWN_MAXLEN() &&
+ caseStringEqual(code_str, PT::UNKNOWN_PREFIX(),
+ PT::UNKNOWN_PREFIXLEN())) {
+ unsigned int code;
+ istringstream iss(code_str.substr(PT::UNKNOWN_PREFIXLEN(),
+ l - PT::UNKNOWN_PREFIXLEN()));
+ iss >> dec >> code;
+ if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) {
+ ret_code = code;
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+template <typename PT, typename MC>
+inline string
+codeToText(uint16_t code, MC& codemap) {
+ typename MC::const_iterator found;
+
+ found = codemap.find(code);
+ if (found != codemap.end()) {
+ return (found->second->code_string_);
+ }
+
+ ostringstream ss;
+ ss << code;
+ return (PT::UNKNOWN_PREFIX() + ss.str());
+}
+}
+
+bool
+RRParamRegistry::addType(const string& type_string, uint16_t code) {
+ return (addParam<RRTypeParam, CodeRRTypeMap, StrRRTypeMap, RRTypeExists>
+ (type_string, code, impl_->code2typemap, impl_->str2typemap));
+}
+
+bool
+RRParamRegistry::removeType(uint16_t code) {
+ return (removeParam<CodeRRTypeMap, StrRRTypeMap>(code, impl_->code2typemap,
+ impl_->str2typemap));
+}
+
+bool
+RRParamRegistry::textToTypeCode(const string& type_string,
+ uint16_t& type_code) const {
+ return (textToCode<RRTypeParam, StrRRTypeMap>
+ (type_string, impl_->str2typemap, type_code));
+}
+
+string
+RRParamRegistry::codeToTypeText(uint16_t code) const {
+ return (codeToText<RRTypeParam, CodeRRTypeMap>(code, impl_->code2typemap));
+}
+
+bool
+RRParamRegistry::addClass(const string& class_string, uint16_t code) {
+ return (addParam<RRClassParam, CodeRRClassMap, StrRRClassMap, RRClassExists>
+ (class_string, code, impl_->code2classmap, impl_->str2classmap));
+}
+
+bool
+RRParamRegistry::removeClass(uint16_t code) {
+ return (removeParam<CodeRRClassMap, StrRRClassMap>(code,
+ impl_->code2classmap,
+ impl_->str2classmap));
+}
+
+bool
+RRParamRegistry::textToClassCode(const string& class_string,
+ uint16_t& class_code) const {
+ return (textToCode<RRClassParam, StrRRClassMap>
+ (class_string, impl_->str2classmap, class_code));
+}
+
+string
+RRParamRegistry::codeToClassText(uint16_t code) const {
+ return (codeToText<RRClassParam, CodeRRClassMap>(code,
+ impl_->code2classmap));
+}
+
+namespace {
+inline const AbstractRdataFactory*
+findRdataFactory(RRParamRegistryImpl* reg_impl,
+ const RRType& rrtype, const RRClass& rrclass) {
+ RdataFactoryMap::const_iterator found;
+ found = reg_impl->rdata_factories.find(RRTypeClass(rrtype, rrclass));
+ if (found != reg_impl->rdata_factories.end()) {
+ return (found->second.get());
+ }
+
+ GenericRdataFactoryMap::const_iterator genfound =
+ reg_impl->genericrdata_factories.find(rrtype);
+ if (genfound != reg_impl->genericrdata_factories.end()) {
+ return (genfound->second.get());
+ }
+
+ return (0);
+}
+}
+
+RdataPtr
+RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& rdata_string) {
+ // If the text indicates that it's rdata of an "unknown" type (beginning
+ // with '\# n'), parse it that way. (TBD)
+
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_.get(), rrtype, rrclass);
+ if (factory) {
+ return (factory->create(rdata_string));
+ }
+
+ return (RdataPtr(new generic::Generic(rdata_string)));
+}
+
+RdataPtr
+RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
+ InputBuffer& buffer, size_t rdata_len) {
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_.get(), rrtype, rrclass);
+ if (factory) {
+ return (factory->create(buffer, rdata_len));
+ }
+
+ return (RdataPtr(new generic::Generic(buffer, rdata_len)));
+}
+
+RdataPtr
+RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
+ const Rdata& source) {
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_.get(), rrtype, rrclass);
+ if (factory) {
+ return (factory->create(source));
+ }
+
+ 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) {
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_.get(), rrtype, rrclass);
+ if (factory) {
+ return (factory->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
new file mode 100644
index 0000000..01d9059
--- /dev/null
+++ b/src/lib/dns/rrparamregistry.h
@@ -0,0 +1,542 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RRPARAMREGISTRY_H
+#define RRPARAMREGISTRY_H
+
+#include <string>
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/exceptions.h>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+
+// forward declarations
+struct RRParamRegistryImpl;
+
+///
+/// \brief A standard DNS module exception that is thrown if a new RR type is
+/// being registered with a different type string.
+///
+class RRTypeExists : public isc::dns::Exception {
+public:
+ RRTypeExists(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if a new RR class is
+/// being registered with a different type string.
+///
+class RRClassExists : public isc::dns::Exception {
+public:
+ RRClassExists(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+namespace rdata {
+/// \brief The \c AbstractRdataFactory class is an abstract base class to
+/// encapsulate a set of Rdata factory methods in a polymorphic way.
+///
+/// An external developer who wants to introduce a new or experimental RR type
+/// is expected to define a corresponding derived class of \c
+/// AbstractRdataFactory and register it via \c RRParamRegistry.
+///
+/// Other users of this API normally do not have to care about this class
+/// or its derived classes; this class is generally intended to be used
+/// as an internal utility of the API implementation.
+class AbstractRdataFactory {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+protected:
+ /// The default constructor
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ AbstractRdataFactory() {}
+public:
+ /// The destructor.
+ virtual ~AbstractRdataFactory() {};
+ //@}
+
+ ///
+ /// \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
+ /// corresponding to the specific derived class of \c AbstractRdataFactory.
+ ///
+ /// \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
+ /// of specific class corresponding to the specific derived class of
+ /// \c AbstractRdataFactory.
+ ///
+ /// \param buffer A reference to an \c InputBuffer object storing the
+ /// \c Rdata to parse.
+ /// \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
+ /// to the specific derived class of \c AbstractRdataFactory, copying the
+ /// content of the given \c Rdata, \c source.
+ ///
+ /// \c source must be an object of the concrete derived class corresponding
+ /// to the specific derived class of \c AbstractRdataFactory;
+ /// otherwise, an exception of class \c std::bad_cast will be thrown.
+ ///
+ /// \param source A reference to an \c Rdata object whose content is to
+ /// 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.
+ virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) const = 0;
+ //@}
+};
+
+///
+/// The \c RdataFactoryPtr type is a pointer-like type, pointing to an
+/// object of some concrete derived class of \c AbstractRdataFactory.
+///
+typedef boost::shared_ptr<AbstractRdataFactory> RdataFactoryPtr;
+} // end of namespace rdata
+
+///
+/// The \c RRParamRegistry class represents a registry of parameters to
+/// manipulate DNS resource records (RRs).
+///
+/// A \c RRParamRegistry class object stores a mapping between RR types or
+/// classes and their textual representations. It will also have knowledge of
+/// how to create an RDATA object for a specific pair of RR type and class
+/// (not implemented in this version).
+///
+/// Normal applications that only handle standard DNS protocols won't have to
+/// care about this class. This is mostly an internal class to the DNS library
+/// to manage standard parameters. Some advanced applications may still need
+/// to use this class explicitly. For example, if an application wants to
+/// define and use an experimental non-standard RR type, it may want to register
+/// related protocol parameters for its convenience. This class is designed to
+/// allow such usage without modifying the library source code or rebuilding
+/// the library.
+///
+/// It is assumed that at most one instance of this class can exist so that
+/// the application uses the consistent set of registered parameters. To ensure
+/// this, this class is designed and implemented as a "singleton class": the
+/// constructor is intentionally private, and applications must get access to
+/// the single instance via the \c getRegistry() static member function.
+///
+/// In the current implementation, access to the singleton \c RRParamRegistry
+/// object is not thread safe.
+/// The application should ensure that multiple threads don't race in the
+/// first invocation of \c getRegistry(), and, if the registry needs to
+/// be changed dynamically, read and write operations are performed
+/// exclusively.
+/// Since this class should be static in common usage this restriction would
+/// be acceptable in practice.
+/// In the future, we may extend the implementation so that multiple threads can
+/// get access to the registry fully concurrently without any restriction.
+///
+/// Note: the implementation of this class is incomplete: we should at least
+/// add RDATA related parameters. This will be done in a near future version,
+/// at which point some of method signatures will be changed.
+class RRParamRegistry {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// These are intentionally hidden (see the class description).
+ //@{
+private:
+ RRParamRegistry();
+ RRParamRegistry(const RRParamRegistry& orig);
+ ~RRParamRegistry();
+ //@}
+public:
+ ///
+ /// \brief Return the singleton instance of \c RRParamRegistry.
+ ///
+ /// This method is a unified access point to the singleton instance of
+ /// the RR parameter registry (\c RRParamRegistry).
+ /// On first invocation it internally constructs an instance of the
+ /// \c RRParamRegistry class and returns a reference to it.
+ /// This is a static object inside this method and will remain valid
+ /// throughout the rest of the application lifetime.
+ /// On subsequent calls this method simply returns a reference to the
+ /// singleton object.
+ ///
+ /// If resource allocation fails in the first invocation,
+ /// a corresponding standard exception will be thrown.
+ /// This method never fails otherwise. In particular, this method
+ /// doesn't throw an exception once the singleton instance is constructed.
+ ///
+ /// \return A reference to the singleton instance of \c RRParamRegistry.
+ static RRParamRegistry& getRegistry();
+
+ ///
+ /// \name Registry Update Methods
+ ///
+ //@{
+ ///
+ /// \brief Add a set of parameters for a pair of RR type and class.
+ ///
+ /// This method adds to the registry a specified set of RR parameters,
+ /// including mappings between type/class codes and their textual
+ /// representations.
+ ///
+ /// Regarding the mappings between textual representations and integer
+ /// codes, this method behaves in the same way as \c addType() and
+ /// \c addClass(). That is, it ignores any overriding attempt as
+ /// long as the mapping is the same; otherwise the corresponding exception
+ /// will be thrown.
+ ///
+ /// This method provides the strong exception guarantee: unless an
+ /// exception is thrown the entire specified set of parameters must be
+ /// stored in the registry; if this method throws an exception the
+ /// registry will remain in the state before this method is called.
+ ///
+ /// \param type_string The textual representation of the RR type.
+ /// \param type_code The integer code of the RR type.
+ /// \param class_string The textual representation of the RR class.
+ /// \param class_code The integer code of the RR class.
+ /// \param rdata_factory An \c RdataFactoryPtr object pointing to a
+ /// concrete RDATA factory.
+ void add(const std::string& type_string, uint16_t type_code,
+ const std::string& class_string, uint16_t class_code,
+ rdata::RdataFactoryPtr rdata_factory);
+
+ /// \brief Add a set of parameters for a class-independent RR type.
+ ///
+ /// This method behaves as exactly same as the other \c add method except
+ /// that it handles class-independent types (such as NS, CNAME, or SOA).
+ ///
+ /// \param type_string The textual representation of the RR type.
+ /// \param type_code The integer code of the RR type.
+ /// \param rdata_factory An \c RdataFactoryPtr object pointing to a
+ /// concrete RDATA factory.
+ void add(const std::string& type_string, uint16_t type_code,
+ rdata::RdataFactoryPtr rdata_factory);
+
+ /// \brief Add mappings between RR type code and textual representation.
+ ///
+ /// This method adds a mapping from the type code of an RR to its textual
+ /// representation and the reverse mapping in the registry.
+ ///
+ /// If the given RR type is already registered with the same textual
+ /// representation, this method simply ignores the duplicate mapping;
+ /// if the given type is registered and a new pair with a different
+ /// textual representation is being added,an exception of class
+ /// \c RRTypeExist will be thrown.
+ /// To replace an existing mapping with a different textual representation,
+ /// the existing one must be removed by the \c removeType() method
+ /// beforehand.
+ ///
+ /// In addition, if resource allocation for the new mapping entries fails,
+ /// a corresponding standard exception will be thrown.
+ ///
+ /// This method provides the strong exception guarantee: unless an exception
+ /// is thrown the specified mappings must be stored in the registry
+ /// (although it may be an already existing one) on completion of the
+ /// method; if this method throws an exception the registry will remain
+ /// in the state before this method is called.
+ ///
+ /// \param type_string The textual representation of the RR type.
+ /// \param type_code The integer code of the RR type.
+ /// \return \c true if a new mapping is added to the repository; \c false
+ /// if the same mapping is already registered.
+ bool addType(const std::string& type_string, uint16_t type_code);
+
+ /// \brief Remove mappings between RR type code and textual representation
+ /// for a given type.
+ ///
+ /// This method can safely be called whether or not the specified mappings
+ /// exist in the registry. If not, this method simply ignores the attempt
+ /// and returns \c false.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param type_code The integer code of the RR type.
+ /// \return \c true if mappings for the specified RR type exists and is
+ /// removed; \c false if no such mapping is in the registry.
+ bool removeType(uint16_t type_code);
+
+ /// \brief Add mappings between RR class code and textual representation.
+ ///
+ /// This method adds a mapping from the class code of an RR to its textual
+ /// representation and the reverse mapping in the registry.
+ ///
+ /// If the given RR class is already registered with the same textual
+ /// representation, this method simply ignores the duplicate mapping;
+ /// if the given class is registered and a new pair with a different
+ /// textual representation is being added,an exception of class
+ /// \c RRClassExist will be thrown.
+ /// To replace an existing mapping with a different textual representation,
+ /// the existing one must be removed by the \c removeClass() method
+ /// beforehand.
+ ///
+ /// In addition, if resource allocation for the new mapping entries fails,
+ /// a corresponding standard exception will be thrown.
+ ///
+ /// This method provides the strong exception guarantee: unless an exception
+ /// is thrown the specified mappings must be stored in the registry
+ /// (although it may be an already existing one) on completion of the
+ /// method; if this method throws an exception the registry will remain
+ /// in the state before this method is called.
+ ///
+ /// \param class_string The textual representation of the RR class.
+ /// \param class_code The integer code of the RR class.
+ /// \return \c true if a new mapping is added to the repository; \c false
+ /// if the same mapping is already registered.
+ bool addClass(const std::string& class_string, uint16_t class_code);
+
+ /// \brief Remove mappings between RR class code and textual representation
+ /// for a given class.
+ ///
+ /// This method can safely be called whether or not the specified mappings
+ /// exist in the registry. If not, this method simply ignores the attempt
+ /// and returns \c false.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param class_code The integer code of the RR class.
+ /// \return \c true if mappings for the specified RR type exists and is
+ /// removed; \c false if no such mapping is in the registry.
+ bool removeClass(uint16_t class_code);
+
+ /// \brief Remove registered RDATA factory for the given pair of \c RRType
+ /// and \c RRClass.
+ ///
+ /// This method can safely be called whether or not the specified factory
+ /// object exist in the registry. If not, this method simply ignores the
+ /// attempt and returns \c false.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param rrtype An \c RRType object specifying the type/class pair.
+ /// \param rrclass An \c RRClass object specifying the type/class pair.
+ /// \return \c true if a factory object for the specified RR type/class
+ /// pair exists and is removed; \c false if no such object is in the
+ /// registry.
+ bool removeRdataFactory(const RRType& rrtype, const RRClass& rrclass);
+
+ /// \brief Remove registered RDATA factory for the given pair of \c RRType
+ /// and \c RRClass.
+ ///
+ /// This method can safely be called whether or not the specified factory
+ /// object exist in the registry. If not, this method simply ignores the
+ /// attempt and returns \c false.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param rrtype An \c RRType object specifying the type/class pair.
+ /// \return \c true if a factory object for the specified RR type/class
+ /// pair exists and is removed; \c false if no such object is in the
+ /// registry.
+ bool removeRdataFactory(const RRType& rrtype);
+ //@}
+
+ ///
+ /// \name Parameter Conversion Methods
+ ///
+ //@{
+ /// \brief Convert a textual representation of an RR type to the
+ /// corresponding 16-bit integer code.
+ ///
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR type to the corresponding
+ /// integer code. If a mapping is found, it returns true with the
+ /// associated type code in \c type_code; otherwise, if the given
+ /// string is in the form of "TYPEnnnn", it returns true with the
+ /// corresponding number as the type code in \c type_code;
+ /// otherwise, it returns false and \c type_code is untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
+ ///
+ /// \param type_string The textual representation of the RR type.
+ /// \param type_code Returns the RR type code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToTypeCode(const std::string& type_string,
+ uint16_t& type_code) const;
+
+ /// \brief Convert type code into its textual representation.
+ ///
+ /// This method searches the \c RRParamRegistry for the mapping from the
+ /// given RR type code to its textual representation.
+ /// If a mapping is found, it returns (a copy of) the associated string;
+ /// otherwise, this method creates a new string in the form of "TYPEnnnn"
+ /// where "nnnn" is the decimal representation of the type code, and
+ /// returns the new string.
+ ///
+ /// If resource allocation for the returned string fails,
+ /// a corresponding standard exception will be thrown.
+ /// This method never fails otherwise.
+ ///
+ /// \param type_code The integer code of the RR type.
+ /// \return A textual representation of the RR type for code \c type_code.
+ std::string codeToTypeText(uint16_t type_code) const;
+
+ /// \brief Convert a textual representation of an RR class to the
+ /// corresponding 16-bit integer code.
+ ///
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR class to the
+ /// corresponding integer code. If a mapping is found, it returns
+ /// true with the associated class code in \c class_code; otherwise,
+ /// if the given string is in the form of "CLASSnnnn", it returns
+ /// true with the corresponding number as the class code in
+ /// \c class_code; otherwise, it returns false and \c class_code is
+ /// untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
+ ///
+ /// \param class_string The textual representation of the RR class.
+ /// \param class_code Returns the RR class code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToClassCode(const std::string& class_string,
+ uint16_t& class_code) const;
+
+ /// \brief Convert class code into its textual representation.
+ ///
+ /// This method searches the \c RRParamRegistry for the mapping from the
+ /// given RR class code to its textual representation.
+ /// If a mapping is found, it returns (a copy of) the associated string;
+ /// otherwise, this method creates a new string in the form of "CLASSnnnn"
+ /// where "nnnn" is the decimal representation of the class code, and
+ /// returns the new string.
+ ///
+ /// If resource allocation for the returned string fails,
+ /// a corresponding standard exception will be thrown.
+ /// This method never fails otherwise.
+ ///
+ /// \param class_code The integer code of the RR class.
+ /// \return A textual representation of the RR class for code \c class_code.
+ std::string codeToClassText(uint16_t class_code) const;
+ //@}
+
+ ///
+ /// \name RDATA Factories
+ ///
+ /// This set of methods provide a unified interface to create an
+ /// \c rdata::Rdata object in a parameterized polymorphic way,
+ /// that is, these methods take a pair of \c RRType and \c RRClass
+ /// objects and data specific to that pair, and create an object of
+ /// the corresponding concrete derived class of \c rdata::Rdata.
+ ///
+ /// These methods first search the \c RRParamRegistry for a factory
+ /// method (a member of a concrete derived class of
+ /// \c AbstractRdataFactory) for the given RR type and class pair.
+ /// If the search fails, they then search for a factory method for
+ /// the given type ignoring the class, in case a RRClass independent
+ /// factory method is registered.
+ /// If it still fails, these methods assume the RDATA is of an "unknown"
+ /// type, and creates a new object by calling a constructor of the
+ /// \c rdata::generic::Generic class.
+ ///
+ //@{
+ /// \brief Create RDATA of a given pair of RR type and class from a string.
+ ///
+ /// This method creates from a string an \c Rdata object of the given pair
+ /// of RR type and class.
+ ///
+ /// \param rrtype An \c RRType object specifying the type/class pair.
+ /// \param rrclass An \c RRClass object specifying the type/class pair.
+ /// \param rdata_string A string of textual representation of the \c Rdata.
+ /// \return An \c rdata::RdataPtr object pointing to the created \c Rdata
+ /// object.
+ rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& rdata_string);
+ /// \brief Create RDATA of a given pair of RR type and class from
+ /// wire-format data.
+ ///
+ /// This method creates from wire-format binary data an \c Rdata object
+ /// of the given pair of RR type and class.
+ ///
+ /// \param rrtype An \c RRType object specifying the type/class pair.
+ /// \param rrclass An \c RRClass object specifying the type/class pair.
+ /// \param buffer A reference to an \c InputBuffer object storing the
+ /// \c Rdata to parse.
+ /// \param len The length in buffer of the \c Rdata. In bytes.
+ /// \return An \c rdata::RdataPtr object pointing to the created \c Rdata
+ /// object.
+ rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+ isc::util::InputBuffer& buffer, size_t len);
+ /// \brief Create RDATA of a given pair of RR type and class, copying
+ /// of another RDATA of same kind.
+ ///
+ /// This method creates an \c Rdata object of the given pair of
+ /// RR type and class, copying the content of the given \c Rdata,
+ /// \c source.
+ ///
+ /// \c source must be an object of the concrete derived class of
+ /// \c rdata::Rdata for the given pair of RR type and class;
+ /// otherwise, an exception of class \c std::bad_cast will be thrown.
+ /// In case the \c RRParamRegistry doesn't have a factory method for
+ /// the given pair and it is assumed to be of an "unknown" type,
+ /// \c source must reference an object of class
+ /// \c rdata::generic::Generic; otherwise, an exception of class
+ /// \c std::bad_cast will be thrown.
+ ///
+ /// \param rrtype An \c RRType object specifying the type/class pair.
+ /// \param rrclass An \c RRClass object specifying the type/class pair.
+ /// \param source A reference to an \c rdata::Rdata object whose content
+ /// is to be copied to the created \c rdata::Rdata object.
+ /// \return An \c rdata::RdataPtr object pointing to the created
+ /// \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:
+ boost::shared_ptr<RRParamRegistryImpl> impl_;
+};
+
+}
+}
+#endif // RRPARAMREGISTRY_H
diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc
new file mode 100644
index 0000000..1360c12
--- /dev/null
+++ b/src/lib/dns/rrset.cc
@@ -0,0 +1,465 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/isc_assert.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+#include <util/buffer.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+void
+AbstractRRset::addRdata(const Rdata& rdata) {
+ addRdata(createRdata(getType(), getClass(), rdata));
+}
+
+string
+AbstractRRset::toText() const {
+ string s;
+ RdataIteratorPtr it = getRdataIterator();
+
+ // In the case of an empty rrset, just print name, ttl, class, and
+ // type
+ if (it->isLast()) {
+ // But only for class ANY or NONE
+ if (getClass() != RRClass::ANY() &&
+ getClass() != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "toText() is attempted for an empty RRset");
+ }
+
+ s += getName().toText() + " " + getTTL().toText() + " " +
+ getClass().toText() + " " + getType().toText() + "\n";
+ return (s);
+ }
+
+ do {
+ s += getName().toText() + " " + getTTL().toText() + " " +
+ getClass().toText() + " " + getType().toText() + " " +
+ it->getCurrent().toText() + "\n";
+ it->next();
+ } while (!it->isLast());
+
+ if (getRRsig()) {
+ s += getRRsig()->toText();
+ }
+
+ return (s);
+}
+
+namespace { // unnamed namespace
+
+// FIXME: This method's code should somehow be unified with
+// BasicRRsetImpl::toWire() below to avoid duplication.
+template <typename T>
+inline uint32_t
+rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
+ uint32_t n = 0;
+ RdataIteratorPtr it = rrset.getRdataIterator();
+
+ if (it->isLast()) {
+ // empty rrsets are only allowed for classes ANY and NONE
+ if (rrset.getClass() != RRClass::ANY() &&
+ rrset.getClass() != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset");
+ }
+
+ // For an empty RRset, write the name, type, class and TTL once,
+ // followed by empty rdata.
+ rrset.getName().toWire(output);
+ rrset.getType().toWire(output);
+ rrset.getClass().toWire(output);
+ rrset.getTTL().toWire(output);
+ output.writeUint16(0);
+ // Still counts as 1 'rr'; it does show up in the message
+ return (1);
+ }
+
+ // sort the set of Rdata based on rrset-order and sortlist, and possible
+ // other options. Details to be considered.
+ do {
+ const size_t pos0 = output.getLength();
+ isc_throw_assert(pos0 < 65536);
+
+ rrset.getName().toWire(output);
+ rrset.getType().toWire(output);
+ rrset.getClass().toWire(output);
+ rrset.getTTL().toWire(output);
+
+ const size_t pos = output.getLength();
+ output.skip(sizeof(uint16_t)); // leave the space for RDLENGTH
+ it->getCurrent().toWire(output);
+ output.writeUint16At(output.getLength() - pos - sizeof(uint16_t), pos);
+
+ if (limit > 0 && output.getLength() > limit) {
+ // truncation is needed
+ output.trim(output.getLength() - pos0);
+ return (n);
+ }
+
+ it->next();
+ ++n;
+ } while (!it->isLast());
+
+ return (n);
+}
+
+} // end of unnamed namespace
+
+uint32_t
+AbstractRRset::toWire(OutputBuffer& buffer) const {
+ return (rrsetToWire<OutputBuffer>(*this, buffer, 0));
+}
+
+uint32_t
+AbstractRRset::toWire(AbstractMessageRenderer& renderer) const {
+ const uint32_t rrs_written = rrsetToWire<AbstractMessageRenderer>(
+ *this, renderer, renderer.getLengthLimit());
+ if (getRdataCount() > rrs_written) {
+ renderer.setTruncated();
+ }
+ return (rrs_written);
+}
+
+bool
+AbstractRRset::isSameKind(const AbstractRRset& other) const {
+ // Compare classes last as they're likely to be identical. Compare
+ // names late in the list too, as these are expensive. So we compare
+ // types first, names second and classes last.
+ return (getType() == other.getType() &&
+ getName() == other.getName() &&
+ getClass() == other.getClass());
+}
+
+ostream&
+operator<<(ostream& os, const AbstractRRset& rrset) {
+ os << rrset.toText();
+ return (os);
+}
+
+/// \brief This encapsulates the actual implementation of the \c BasicRRset
+/// class. It's hidden from applications.
+class BasicRRsetImpl {
+public:
+ BasicRRsetImpl(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl) :
+ name_(name), rrclass_(rrclass), rrtype_(rrtype), ttl_(ttl) {}
+
+ uint32_t toWire(AbstractMessageRenderer& renderer, size_t limit) const;
+
+ Name name_;
+ RRClass rrclass_;
+ RRType rrtype_;
+ RRTTL ttl_;
+ // XXX: "list" is not a good name: It in fact isn't a list; more conceptual
+ // name than a data structure name is generally better. But since this
+ // is only used in the internal implementation we'll live with it.
+ vector<ConstRdataPtr> rdatalist_;
+};
+
+// FIXME: This method's code should somehow be unified with
+// rrsetToWire() above to avoid duplication.
+uint32_t
+BasicRRsetImpl::toWire(AbstractMessageRenderer& renderer, size_t limit) const {
+ if (rdatalist_.empty()) {
+ // empty rrsets are only allowed for classes ANY and NONE
+ if (rrclass_ != RRClass::ANY() &&
+ rrclass_ != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset");
+ }
+
+ // For an empty RRset, write the name, type, class and TTL once,
+ // followed by empty rdata.
+ name_.toWire(renderer);
+ rrtype_.toWire(renderer);
+ rrclass_.toWire(renderer);
+ ttl_.toWire(renderer);
+ renderer.writeUint16(0);
+ // Still counts as 1 'rr'; it does show up in the message
+ return (1);
+ }
+
+ uint32_t n = 0;
+
+ // sort the set of Rdata based on rrset-order and sortlist, and possible
+ // other options. Details to be considered.
+ for (auto const& rdata : rdatalist_) {
+ const size_t pos0 = renderer.getLength();
+ isc_throw_assert(pos0 < 65536);
+
+ name_.toWire(renderer);
+ rrtype_.toWire(renderer);
+ rrclass_.toWire(renderer);
+ ttl_.toWire(renderer);
+
+ const size_t pos = renderer.getLength();
+ renderer.skip(sizeof(uint16_t)); // leave the space for RDLENGTH
+ rdata->toWire(renderer);
+ renderer.writeUint16At(renderer.getLength() - pos - sizeof(uint16_t),
+ pos);
+
+ if (limit > 0 && renderer.getLength() > limit) {
+ // truncation is needed
+ renderer.trim(renderer.getLength() - pos0);
+ return (n);
+ }
+ ++n;
+ }
+
+ return (n);
+}
+
+BasicRRset::BasicRRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl) {
+ impl_.reset(new BasicRRsetImpl(name, rrclass, rrtype, ttl));
+}
+
+BasicRRset::~BasicRRset() {
+}
+
+void
+BasicRRset::addRdata(ConstRdataPtr rdata) {
+ impl_->rdatalist_.push_back(rdata);
+}
+
+void
+BasicRRset::addRdata(const Rdata& rdata) {
+ AbstractRRset::addRdata(rdata);
+}
+
+void
+BasicRRset::addRdata(const std::string& rdata_str) {
+ addRdata(createRdata(getType(), getClass(), rdata_str));
+}
+
+uint32_t
+BasicRRset::getRdataCount() const {
+ return (impl_->rdatalist_.size());
+}
+
+const Name&
+BasicRRset::getName() const {
+ return (impl_->name_);
+}
+
+const RRClass&
+BasicRRset::getClass() const {
+ return (impl_->rrclass_);
+}
+
+const RRType&
+BasicRRset::getType() const {
+ return (impl_->rrtype_);
+}
+
+const RRTTL&
+BasicRRset::getTTL() const {
+ return (impl_->ttl_);
+}
+
+void
+BasicRRset::setTTL(const RRTTL& ttl) {
+ impl_->ttl_ = ttl;
+}
+
+string
+BasicRRset::toText() const {
+ return (AbstractRRset::toText());
+}
+
+uint16_t
+BasicRRset::getLength() const {
+ uint16_t length = 0;
+ RdataIteratorPtr it = getRdataIterator();
+
+ if (it->isLast()) {
+ // empty rrsets are only allowed for classes ANY and NONE
+ if (getClass() != RRClass::ANY() &&
+ getClass() != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "getLength() is attempted for an empty RRset");
+ }
+
+ // For an empty RRset, write the name, type, class and TTL once,
+ // followed by empty rdata.
+ length += getName().getLength();
+ length += 2; // TYPE field
+ length += 2; // CLASS field
+ length += 4; // TTL field
+ length += 2; // RDLENGTH field (=0 in wire format)
+
+ return (length);
+ }
+
+ do {
+ // This is a size_t as some of the following additions may
+ // overflow due to a programming mistake somewhere.
+ size_t rrlen = 0;
+
+ rrlen += getName().getLength();
+ rrlen += 2; // TYPE field
+ rrlen += 2; // CLASS field
+ rrlen += 4; // TTL field
+ rrlen += 2; // RDLENGTH field
+ rrlen += it->getCurrent().getLength();
+
+ isc_throw_assert(length + rrlen < 65536);
+ length += rrlen;
+
+ it->next();
+ } while (!it->isLast());
+
+ return (length);
+}
+
+uint32_t
+BasicRRset::toWire(OutputBuffer& buffer) const {
+ return (AbstractRRset::toWire(buffer));
+}
+
+uint32_t
+BasicRRset::toWire(AbstractMessageRenderer& renderer) const {
+ const uint32_t rrs_written = impl_->toWire(renderer,
+ renderer.getLengthLimit());
+ if (impl_->rdatalist_.size() > rrs_written) {
+ renderer.setTruncated();
+ }
+ return (rrs_written);
+}
+
+RRset::RRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl) :
+ BasicRRset(name, rrclass, rrtype, ttl) {
+}
+
+RRset::~RRset() {
+}
+
+uint32_t
+RRset::getRRsigDataCount() const {
+ if (rrsig_) {
+ return (rrsig_->getRdataCount());
+ } else {
+ return (0);
+ }
+}
+
+uint16_t
+RRset::getLength() const {
+ uint16_t length = BasicRRset::getLength();
+
+ if (rrsig_) {
+ const uint16_t rrsigs_length = rrsig_->getLength();
+ // the uint16_ts are promoted to ints during addition below, so
+ // it won't overflow a 16-bit register.
+ isc_throw_assert(length + rrsigs_length < 65536);
+ length += rrsigs_length;
+ }
+
+ return (length);
+}
+
+uint32_t
+RRset::toWire(OutputBuffer& buffer) const {
+ uint32_t rrs_written = BasicRRset::toWire(buffer);
+ if (getRdataCount() > rrs_written) {
+ return (rrs_written);
+ }
+
+ if (rrsig_) {
+ rrs_written += rrsig_->toWire(buffer);
+ }
+
+ return (rrs_written);
+}
+
+uint32_t
+RRset::toWire(AbstractMessageRenderer& renderer) const {
+ uint32_t rrs_written = BasicRRset::toWire(renderer);
+ if (getRdataCount() > rrs_written) {
+ return (rrs_written);
+ }
+
+ if (rrsig_) {
+ rrs_written += rrsig_->toWire(renderer);
+
+ if (getRdataCount() + getRRsigDataCount() > rrs_written) {
+ renderer.setTruncated();
+ }
+ }
+
+ return (rrs_written);
+}
+
+namespace {
+
+class BasicRdataIterator : public RdataIterator {
+public:
+ /// @brief Constructor.
+ BasicRdataIterator(const std::vector<rdata::ConstRdataPtr>& datavector) :
+ datavector_(&datavector), it_(datavector_->begin()) {
+ }
+
+ /// @brief Destructor.
+ ~BasicRdataIterator() {
+ }
+
+ /// @brief Set iterator at first position.
+ virtual void first() {
+ it_ = datavector_->begin();
+ }
+
+ /// @brief Advance iterator.
+ virtual void next() {
+ ++it_;
+ }
+
+ /// @brief Get value at current iterator position.
+ ///
+ /// @return The value at current iterator position.
+ virtual const rdata::Rdata& getCurrent() const {
+ return (**it_);
+ }
+
+ /// @brief Check if iterator has reached the end.
+ ///
+ /// @return true if iterator has reached the end, false otherwise.
+ virtual bool isLast() const {
+ return (it_ == datavector_->end());
+ }
+
+private:
+ /// @brief Vector containing data.
+ const std::vector<rdata::ConstRdataPtr>* datavector_;
+
+ /// @brief Iterator used to retrieve data.
+ std::vector<rdata::ConstRdataPtr>::const_iterator it_;
+};
+
+}
+
+RdataIteratorPtr
+BasicRRset::getRdataIterator() const {
+ return (RdataIteratorPtr(new BasicRdataIterator(impl_->rdatalist_)));
+}
+
+}
+}
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
new file mode 100644
index 0000000..282ff8b
--- /dev/null
+++ b/src/lib/dns/rrset.h
@@ -0,0 +1,952 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RRSET_H
+#define RRSET_H
+
+#include <iostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/exceptions.h>
+
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace dns {
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRset object
+/// does not contain any RDATA where required.
+///
+class EmptyRRset : public isc::dns::Exception {
+public:
+ EmptyRRset(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+// forward declarations
+class Name;
+class RRType;
+class RRClass;
+class RRTTL;
+class AbstractMessageRenderer;
+class AbstractRRset;
+class BasicRRset;
+class RdataIterator;
+class BasicRRsetImpl;
+class RRset;
+
+/// \brief A pointer-like type pointing to an \c RRset object.
+///
+/// This type is commonly used as an argument of various functions defined
+/// in this library in order to handle RRsets in a polymorphic manner.
+typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
+
+/// \brief A pointer-like type pointing to an (immutable) \c RRset
+/// object.
+///
+/// This type is commonly used as an argument of various functions defined
+/// in this library in order to handle RRsets in a polymorphic manner.
+typedef boost::shared_ptr<const AbstractRRset> ConstRRsetPtr;
+
+/// \brief A pointer-like type point to an \c RdataIterator object.
+typedef boost::shared_ptr<RdataIterator> RdataIteratorPtr;
+
+/// \brief The \c AbstractRRset class is an abstract base class that
+/// models a DNS RRset.
+///
+/// An object of (a specific derived class of) \c AbstractRRset
+/// models an RRset as described in the DNS standard:
+/// A set of DNS resource records (RRs) of the same type and class.
+/// The standard requires the TTL of all RRs in an RRset be the same;
+/// this class follows that requirement.
+
+/// Note about duplicate RDATA: RFC2181 states that it's meaningless that an
+/// RRset contains two identical RRs and that name servers should suppress
+/// such duplicates.
+/// This class is not responsible for ensuring this requirement: For example,
+/// \c addRdata() method doesn't check if there's already RDATA identical
+/// to the one being added.
+/// This is because such checks can be expensive, and it's often easy to
+/// ensure the uniqueness requirement at the %data preparation phase
+/// (e.g. when loading a zone).
+/// When parsing an incoming DNS message, the uniqueness may not be guaranteed,
+/// so the application needs to detect and ignore any duplicate RDATA
+/// (the \c Message class of this library should provide this responsibility).
+///
+/// Another point to note is that \c AbstractRRset and its derived classes
+/// allow an object to have an empty set of RDATA.
+/// Even though there's no corresponding notion in the protocol specification,
+/// it would be more intuitive for a container-like %data structure
+/// to allow an empty set.
+///
+/// Since \c AbstractRRset is an abstract class, it is generally used
+/// via a pointer (or pointer like object) or a reference.
+/// In particular, \c RRsetPtr, a pointer like type for \c AbstractRRset,
+/// is used for polymorphic RRset operations throughout this library.
+///
+/// The \c AbstractRRset class is also intended to be a major customization
+/// point. For example, a high performance server implementation may want
+/// to define an optimized "pre-compiled" RRset and provide an optimized
+/// implementation of the \c toWire() method.
+///
+/// Note about design choice: In BIND9, a set of RDATA with a common tuple
+/// of RR class, RR type, and TTL was represented in a structure named
+/// \c rdataset. Unlike the RRset classes, an \c rdataset did not contain
+/// the information of the owner name.
+/// This might be advantageous if we want to handle "RRsets", that is,
+/// a set of different types of RRset for the same owner name, because
+/// a single "name" structure can be used for multiple RRsets, minimizing
+/// %data copy and memory footprint.
+/// On the other hand, it's inconvenient for API users since in many cases
+/// a pair of name and an \c rdataset must be maintained. It's also counter
+/// intuitive in implementing protocol operations as an RRset is often used
+/// as an atomic entity in DNS protocols while an \c rdataset is a component
+/// of an RRset.
+///
+/// We have therefore defined the notion of RRset explicitly in our initial
+/// API design. We believe memory footprint is not a big concern because
+/// RRsets are generally expected to be used as temporary objects, e.g.
+/// while parsing or constructing a DNS message, or searching a DNS %data
+/// source; for longer term purposes such as in-memory %data source entries,
+/// the corresponding %data would be represented in a different, memory
+/// optimized format. As for the concern about %data copy, we believe
+/// it can be mitigated by using copy-efficient implementation for the
+/// \c Name class implementation, such as reference counted objects.
+/// Later, We plan to perform benchmark tests later to see if this assumption
+/// is valid and to revisit the design if necessary.
+///
+/// Note about terminology: there has been a discussion at the IETF
+/// namedroppers ML about RRset vs RRSet (case of "s")
+/// [http://ops.ietf.org/lists/namedroppers/namedroppers.2009/msg02737.html].
+/// While RFC2181 uses the latter, many other RFCs use the former,
+/// and most of the list members who showed their opinion seem to prefer
+/// "RRset". We follow that preference in this implementation.
+///
+/// The current design of \c AbstractRRset is still in flux.
+/// There are many open questions in design details:
+/// - support more set-like operations, e.g, merge two RRsets of the same
+/// type?
+/// - more convenient methods or non member utility functions, e.g.
+/// "sort" and "search(find)" method?
+/// - what about comparing two RRsets of the same type? If we need this,
+/// should it compare rdata's as a set or as a list (i.e. compare
+/// each rdata one by one or as a whole)? c.f. NLnet Labs' ldns
+/// (http://www.nlnetlabs.nl/projects/ldns/doc/index.html)
+/// has \c ldns_rr_list_compare(), which takes the latter approach
+/// (seemingly assuming the caller sorts the lists beforehand).
+/// - BIND9 libdns has some special DNSSEC-related methods
+/// such as \c addnoqname() or \c addclosest(). Do we need these?
+/// (Probably not. We wouldn't want to make the class design too
+/// monolithic.)
+/// - Do we need to allow the user to remove specific Rdata?
+/// Probably not, according to the current usage of the BIND9 code.
+class AbstractRRset {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private to make it explicit that this is a pure base class.
+ //@{
+private:
+ AbstractRRset(const AbstractRRset& source);
+ AbstractRRset& operator=(const AbstractRRset& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ AbstractRRset() {}
+public:
+ /// The destructor.
+ virtual ~AbstractRRset() {}
+ //@}
+
+ ///
+ /// \name Getter and Setter Methods
+ ///
+ /// These methods are generally expected to be exception free, but it's
+ /// not guaranteed at the interface level;
+ /// for example, some performance optimized derived class may manage
+ /// the information corresponding to the class "attributes" to get or set,
+ /// and may require dynamic memory allocation to execute the method.
+ /// Consult the derived class description to see if a specific derived
+ /// \c RRset class may throw an exception from these methods.
+ ///
+ /// Note that setter methods are not provided for \c RRClass and
+ /// \c RRType. This is intentional. Since the format and semantics of
+ /// \c Rdata are dependent on the RR type (and RR class for some RR types),
+ /// allowing dynamically modify these attributes can easily lead to a
+ /// bug where the RDATA and type and/or class become inconsistent.
+ /// We want to avoid that situation by restricting the access.
+ //@{
+ /// \brief Returns the number of \c Rdata objects contained in the \c RRset.
+ ///
+ /// Note that an \c RRset with an empty set of \c Rdata can exist, so
+ /// this method may return 0.
+ ///
+ /// \return The number of \c Rdata objects contained.
+ virtual uint32_t getRdataCount() const = 0;
+
+ /// \brief Get the wire format length of the \c AbstractRRset.
+ ///
+ /// This method returns the wire format length of the
+ /// \c AbstractRRset, which is calculated by summing the individual
+ /// lengths of the various fields that make up each RR.
+ ///
+ /// NOTE: When including name lengths, the allocation for
+ /// uncompressed name wire format representation is used.
+ ///
+ /// \return The length of the wire format representation of the
+ /// \c AbstractRRset.
+ /// \throw EmptyRRset if the \c AbstractRRset is empty.
+ virtual uint16_t getLength() const = 0;
+
+ /// \brief Returns the owner name of the \c RRset.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// \c RRset owner name.
+ virtual const Name& getName() const = 0;
+
+ /// \brief Returns the RR Class of the \c RRset.
+ ///
+ /// \return A reference to a \c RRClass class object corresponding to the
+ /// RR class of the \c RRset.
+ virtual const RRClass& getClass() const = 0;
+
+ /// \brief Returns the RR Type of the \c RRset.
+ ///
+ /// \return A reference to a \c RRType class object corresponding to the
+ /// RR type of the \c RRset.
+ virtual const RRType& getType() const = 0;
+
+ /// \brief Returns the TTL of the RRset.
+ ///
+ /// \return A reference to a \c RRTTL class object corresponding to the
+ /// TTL of the \c RRset.
+ virtual const RRTTL& getTTL() const = 0;
+
+ /// \brief Updates the TTL of the \c RRset.
+ ///
+ /// \param ttl A reference to a \c RRTTL class object to be copied as the
+ /// new TTL.
+ virtual void setTTL(const RRTTL& ttl) = 0;
+ //@}
+
+ ///
+ /// \name Converter Methods
+ ///
+ /// These methods have the default implementation that can be reused by
+ /// derived classes.
+ /// Since they are defined as pure virtual, derived classes
+ /// that want to reuse the default implementation must explicitly
+ /// invoke their base class version (see the description for
+ /// <code>addRdata(const rdata::Rdata&)</code>).
+ ///
+ /// Design Note: the default implementations are defined only using
+ /// other public methods of the \c AbstractRRset class, and could be
+ /// implemented as non member functions (as some C++ textbooks suggest).
+ /// However, since derived classes may want to provide customized versions
+ /// (especially of the \c toWire() method for performance reasons)
+ /// we chose to define them as virtual functions, and, as a result,
+ /// member functions.
+ //@{
+ /// \brief Convert the RRset to a string.
+ ///
+ /// Unlike other similar methods of this library, this method terminates
+ /// the resulting string with a trailing newline character.
+ /// (following the BIND9 convention)
+ ///
+ /// If any RRSIGs are associated with the RRset, they are also
+ /// appended to the returned string.
+ ///
+ /// If the class is not ANY or NONE, the RRset must contain some RDATA;
+ /// otherwise, an exception of class \c EmptyRRset will be thrown.
+ /// If resource allocation fails, a corresponding standard exception
+ /// will be thrown.
+ /// The default implementation may throw other exceptions if the
+ /// \c toText() method of the RDATA objects throws.
+ /// If a derived class of \c AbstractRRset overrides the default
+ /// implementation, the derived version may throw its own exceptions.
+ ///
+ /// Open issue: We may want to support multiple output formats as
+ /// BIND9 does. For example, we might want to allow omitting the owner
+ /// name when possible in the context of zone dump. This is a future
+ /// TODO item.
+ ///
+ /// \return A string representation of the RRset.
+ virtual std::string toText() const = 0;
+
+ /// \brief Render the RRset in the wire format with name compression and
+ /// truncation handling.
+ ///
+ /// This method compresses the owner name of the RRset and domain names
+ /// used in RDATA that should be compressed.
+ /// In addition, this method detects the case where rendering the entire
+ /// RRset would cause truncation, and handles the case appropriately
+ /// (this is a TODO item, and not implemented in this version).
+ ///
+ /// If any RRSIGs are associated with the RRset, they are also rendered.
+ ///
+ /// Note: perhaps we may want to add more arguments to convey optional
+ /// information such as an "rrset-order" policy or how to handle truncation
+ /// case. This is a TODO item.
+ ///
+ /// If resource allocation fails, a corresponding standard exception
+ /// will be thrown.
+ /// If the class is not ANY or NONE, the RRset must contain some RDATA;
+ /// otherwise, an exception of class \c EmptyRRset will be thrown.
+ /// The default implementation may throw other exceptions if the
+ /// \c toWire() method of the RDATA objects throws.
+ /// If a derived class of \c AbstractRRset overrides the default
+ /// implementation, the derived version may throw its own exceptions.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ /// \return The number of RRs rendered. If the truncation is necessary
+ /// this value may be different from the number of RDATA objects contained
+ /// in the RRset.
+ virtual uint32_t toWire(AbstractMessageRenderer& renderer) const = 0;
+
+ /// \brief Render the RRset in the wire format without any compression.
+ ///
+ /// See the other toWire() description about possible exceptions.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ /// \return The number of RRs rendered.
+ virtual uint32_t toWire(isc::util::OutputBuffer& buffer) const = 0;
+ //@}
+
+ ///
+ /// \name RDATA Manipulation Methods
+ ///
+ //@{
+ /// \brief Add an RDATA to the RRset (pointer version).
+ ///
+ /// This method adds the given RDATA (as a pointer-like type to a
+ /// derived class object of \c rdata::Rdata) to the \c RRset.
+ ///
+ /// \param rdata A pointer (like) type of \c rdata::RdataPtr to be added
+ /// to the \c RRset.
+ virtual void addRdata(rdata::ConstRdataPtr rdata) = 0;
+
+ /// \brief Add an RDATA to the RRset (reference version).
+ ///
+ /// This method adds the given RDATA (as a reference to a
+ /// derived class object of \c rdata::Rdata) to the \c RRset.
+ ///
+ /// This method has the default implementation that can be reused by
+ /// derived classes.
+ /// Since this method is defined as pure virtual, derived classes
+ /// that want to reuse the default implementation must explicitly
+ /// invoke this base class version.
+ /// For example, if the class \c CustomizedRRset, a derived class of
+ /// \c AbstractRRset, wants to reuse the default implementation of
+ /// \c %addRdata() (reference version), it would be defined as follows:
+ /// \code void
+ /// CustomizedRRset::addRdata(const rdata::Rdata& rdata)
+ /// {
+ /// AbstractRRset::addRdata(rdata);
+ /// }
+ /// \endcode
+ ///
+ /// This method is more strictly typed than the pointer version:
+ /// If \c rdata does not refer to the appropriate derived
+ /// \c Rdata class
+ /// for the \c RRType for this \c RRset, it throws an exception of class
+ /// \c std::bad_cast.
+ /// If resource allocation fails, a corresponding standard exception
+ /// will be thrown.
+ /// The RRset must contain some RDATA; otherwise, an exception of class
+ /// \c EmptyRRset will be thrown.
+ /// The default implementation may throw other exceptions if the
+ /// \c toWire() method of the RDATA objects throws.
+ /// If a derived class of \c AbstractRRset overrides the default
+ /// implementation, the derived version may throw its own exceptions.
+ ///
+ /// The default implementation simply constructs an \c rdata::RdataPtr
+ /// object from a newly allocated RDATA object copying from parameter
+ /// \c rdata, and calls the other version of
+ /// \c addRdata(const rdata::RdataPtr).
+ /// So it is inherently less efficient than the other version.
+ /// Still, this version would offer a more intuitive interface and is
+ /// provided as such.
+ ///
+ /// NOTE: Because a new Rdata object is constructed, this method can
+ /// throw a std::bad_cast exception if this RRset's class is NONE,
+ /// or if some other error occurs. If you want to be able to add
+ /// RDATA to an RRset whose class is NONE, please use the other
+ /// variant of \c addRdata() which accepts a \c ConstRdataPtr
+ /// argument.
+ ///
+ /// \param rdata A reference to a \c rdata::RdataPtr (derived) class
+ /// object, a copy of which is to be added to the \c RRset.
+ virtual void addRdata(const rdata::Rdata& rdata) = 0;
+
+ /// \brief Add an RDATA to the RRset (string version).
+ ///
+ /// This method constructs an Rdata object from the given
+ /// \c rdata_str in presentation format and adds it to the \c RRset.
+ ///
+ /// \param rdata_str RDATA string in presentation format.
+ /// \throw InvalidRdataText if the \c rdata_str is invalid for this
+ /// \c RRset.
+ virtual void addRdata(const std::string& rdata_str) = 0;
+
+ /// \brief Return an iterator to go through all RDATA stored in the
+ /// \c RRset.
+ ///
+ /// The rdata cursor of the returned iterator will point to the first
+ /// RDATA, that is, it effectively calls \c RdataIterator::first()
+ /// internally.
+ ///
+ /// Using the design pattern terminology, \c getRdataIterator()
+ /// is an example of a <em>factory method</em>.
+ ///
+ /// Whether this method throws an exception depends on the actual
+ /// implementation of the derived \c AbstractRRset class, but in general
+ /// it will involve resource allocation and can throw a standard exception
+ /// if it fails.
+ ///
+ /// \return A pointer-like object pointing to the derived \c RdataIterator
+ /// object.
+ virtual RdataIteratorPtr getRdataIterator() const = 0;
+ //@}
+
+ ///
+ /// \name Associated RRSIG methods
+ ///
+ /// These methods access an "associated" RRset, that containing the DNSSEC
+ /// signatures for this RRset. It can be argued that this is not a
+ /// fundamental part of the RRset abstraction, since RFC 2181 defined an
+ /// RRset as a group of records with the same label, class and type but
+ /// different data. However, BIND 10 had to deal with DNSSEC and in
+ /// practice, including the information at the AbstractRRset level makes
+ /// implementation easier. (If a class is ever needed that must be
+ /// ignorant of the idea of an associated RRSIG RRset - e.g. a specialised
+ /// RRSIG RRset class - these methods can just throw a "NotImplemented"
+ /// exception.) DNSSEC is unlikely to be ever needed in Kea, but it does
+ /// not make sense to redesign the abstract RRSet class now.
+ //@{
+ /// \brief Return pointer to this RRset's RRSIG RRset
+ ///
+ /// \return Pointer to the associated RRSIG RRset or null if there is none.
+ virtual RRsetPtr getRRsig() const = 0;
+
+ /// \brief Returns the number of \c RRSIG records associated with
+ /// the \c RRset.
+ ///
+ /// Note that an \c RRset with no RRSIG records may exist, so this
+ /// method may return 0.
+ ///
+ /// \return The number of \c RRSIG records associated.
+ virtual uint32_t getRRsigDataCount() const = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this
+ /// RRset. If one does not exist, it is created using the data given.
+ ///
+ /// \param rdata Pointer to RRSIG rdata to be added.
+ virtual void addRRsig(const rdata::ConstRdataPtr& rdata) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this
+ /// RRset. If one does not exist, it is created using the data given.
+ ///
+ /// (This overload is for an older version of boost that doesn't support
+ /// conversion from shared_ptr<X> to shared_ptr<const X>.)
+ ///
+ /// \param rdata Pointer to RRSIG rdata to be added.
+ virtual void addRRsig(const rdata::RdataPtr& rdata) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+ /// RRset associated with this RRset. If one does not exist, it is created
+ /// using the data given.
+ ///
+ /// \param sigs RRSIG RRset containing signatures to be added to the
+ /// RRSIG RRset associated with this class.
+ virtual void addRRsig(const AbstractRRset& sigs) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+ /// RRset associated with this RRset. If one does not exist, it is created
+ /// using the data given.
+ ///
+ /// \param sigs Pointer to a RRSIG RRset containing signatures to be added
+ /// to the RRSIG RRset associated with this class.
+ virtual void addRRsig(const ConstRRsetPtr& sigs) = 0;
+
+ /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+ ///
+ /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+ /// RRset associated with this RRset. If one does not exist, it is created
+ /// using the data given.
+ ///
+ /// (This overload is for an older version of boost that doesn't support
+ /// conversion from shared_ptr<X> to shared_ptr<const X>.)
+ ///
+ /// \param sigs Pointer to a RRSIG RRset containing signatures to be added
+ /// to the RRSIG RRset associated with this class.
+ virtual void addRRsig(const RRsetPtr& sigs) = 0;
+
+ /// \brief Clear the RRSIGs for this RRset
+ virtual void removeRRsig() = 0;
+
+ /// \brief Check whether two RRsets are of the same kind
+ ///
+ /// Checks if two RRsets have the same name, RR type, and RR class.
+ ///
+ /// \param other Pointer to another AbstractRRset to compare
+ /// against.
+ virtual bool isSameKind(const AbstractRRset& other) const;
+ //@}
+
+};
+
+/// \brief The \c RdataIterator class is an abstract base class that
+/// provides an interface for accessing RDATA objects stored in an RRset.
+///
+/// While different derived classes of \c AbstractRRset may maintain the RDATA
+/// objects in different ways, the \c RdataIterator class provides a
+/// unified interface to iterate over the RDATA objects in a polymorphic
+/// manner.
+///
+/// Each derived class of \c AbstractRRset is expected to provide a concrete
+/// derived class of \c RdataIterator, and each derived \c RdataIterator
+/// class implements the unified interface in a way specific to the
+/// implementation of the corresponding derived \c AbstractRRset class.
+/// Using the design pattern terminology, this is a typical example of
+/// the \e Iterator pattern.
+///
+/// The RDATA objects stored in the \c RRset are considered to form
+/// a unidirectional list from the \c RdataIterator point of view (while
+/// the actual implementation in the derived \c RRset may not use a list).
+/// We call this unidirectional list the <em>rdata list</em>.
+///
+/// An \c RdataIterator object internally (and conceptually) holds a
+/// <em>rdata cursor</em>, which points to a specific item of the rdata list.
+///
+/// Note about design choice: as is clear from the interface, \c RdataIterator
+/// is not compatible with the standard iterator classes.
+/// Although it would be useful (for example, we could then use STL algorithms)
+/// and is not necessarily impossible, it would make the iterator implementation
+/// much more complicated.
+/// For instance, any standard iterator must be assignable and
+/// copy-constructible.
+/// So we'd need to implement \c RdataIterator::operator=() in a polymorphic
+/// way. This will require non-trivial implementation tricks.
+/// We believe the simplified iterator interface as provided by the
+/// \c RdataIterator class is sufficient in practice:
+/// Most applications will simply go through the RDATA objects contained in
+/// an RRset, examining (and possibly using) each object, as one path
+/// operation.
+class RdataIterator {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private to make it explicit that this is a pure base class.
+ //@{
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ RdataIterator() {}
+public:
+ /// \brief Destructor
+ virtual ~RdataIterator() {}
+private:
+ RdataIterator(const RdataIterator& source);
+ RdataIterator& operator=(const RdataIterator& source);
+ //@}
+
+public:
+ /// \brief Move the rdata cursor to the first RDATA in the rdata list
+ /// (if any).
+ ///
+ /// This method can safely be called multiple times, even after moving
+ /// the rdata cursor forward by the \c next() method.
+ ///
+ /// This method should never throw an exception.
+ virtual void first() = 0;
+
+ /// \brief Move the rdata cursor to the next RDATA in the rdata list
+ /// (if any).
+ ///
+ /// This method should never throw an exception.
+ virtual void next() = 0;
+
+ /// \brief Return the current \c Rdata corresponding to the rdata cursor.
+ ///
+ /// \return A reference to an \c rdata::Rdata object corresponding
+ /// to the rdata cursor.
+ virtual const rdata::Rdata& getCurrent() const = 0;
+
+ /// \brief Return true iff the rdata cursor has reached the end of the
+ /// rdata list.
+ ///
+ /// Once this method returns \c true, the behavior of any subsequent
+ /// call to \c next() or \c getCurrent() is undefined.
+ /// Likewise, the result of \c isLast() call followed by such undefined
+ /// operations is also undefined.
+ ///
+ /// This method should never throw an exception.
+ ///
+ /// \return \c true if the rdata cursor has reached the end of the
+ /// rdata list; otherwise \c false.
+ virtual bool isLast() const = 0;
+};
+
+/// \brief The \c BasicRRset class is a concrete derived class of
+/// \c AbstractRRset that defines a straightforward RRset implementation.
+///
+/// This class is designed to be as portable as possible, and so it adopts
+/// the Pimpl idiom to hide as many details as possible.
+/// Performance is a secondary concern for this class.
+///
+/// This class is intended to be used by applications that only need
+/// moderate level of performance with full functionality provided by
+/// the \c AbstractRRset interfaces.
+/// Highly performance-sensitive applications, such as a large scale
+/// authoritative or caching name servers will implement and use a customized
+/// version of derived \c AbstractRRset class.
+class BasicRRset : public AbstractRRset {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are intentionally
+ /// defined as private. The intended use case wouldn't require copies of
+ /// a \c BasicRRset object; once created, it would normally be used
+ /// as a \c const object (via references).
+ //@{
+private:
+ BasicRRset(const BasicRRset& source);
+ BasicRRset& operator=(const BasicRRset& source);
+public:
+ /// \brief Constructor from (mostly) fixed parameters of the RRset.
+ ///
+ /// This constructor is normally expected to be exception free, but
+ /// copying the name may involve resource allocation, and if it fails
+ /// the corresponding standard exception will be thrown.
+ ///
+ /// \param name The owner name of the RRset.
+ /// \param rrclass The RR class of the RRset.
+ /// \param rrtype The RR type of the RRset.
+ /// \param ttl The TTL of the RRset.
+ BasicRRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl);
+ /// \brief The destructor.
+ virtual ~BasicRRset();
+ //@}
+
+ ///
+ /// \name Getter and Setter Methods
+ ///
+ //@{
+ /// \brief Returns the number of \c Rdata objects contained in the \c RRset.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The number of \c Rdata objects contained.
+ virtual uint32_t getRdataCount() const;
+
+ /// \brief Get the wire format length of the \c BasicRRset.
+ ///
+ /// \return The length of the wire format representation of the
+ /// \c BasicRRset.
+ /// \throw EmptyRRset if the \c BasicRRset is empty.
+ virtual uint16_t getLength() const;
+
+ /// \brief Returns the owner name of the \c RRset.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// \c RRset owner name.
+ virtual const Name& getName() const;
+
+ /// \brief Returns the RR Class of the \c RRset.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c RRClass class object corresponding to the
+ /// RR class of the \c RRset.
+ virtual const RRClass& getClass() const;
+
+ /// \brief Returns the RR Type of the \c RRset.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c RRType class object corresponding to the
+ /// RR type of the \c RRset.
+ virtual const RRType& getType() const;
+
+ /// \brief Returns the TTL of the \c RRset.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to a \c RRTTL class object corresponding to the
+ /// TTL of the \c RRset.
+ virtual const RRTTL& getTTL() const;
+
+ /// \brief Updates the TTL of the \c RRset.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param ttl A reference to a \c RRTTL class object to be copied as the
+ /// new TTL.
+ virtual void setTTL(const RRTTL& ttl);
+ //@}
+
+ ///
+ /// \name Converter Methods
+ ///
+ //@{
+ /// \brief Convert the RRset to a string.
+ ///
+ /// This method simply uses the default implementation.
+ /// See \c AbstractRRset::toText().
+ virtual std::string toText() const;
+
+ /// \brief Render the RRset in the wire format with name compression and
+ /// truncation handling.
+ ///
+ /// This method simply uses the default implementation.
+ /// See \c AbstractRRset::toWire(MessageRenderer&)const.
+ virtual uint32_t toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the RRset in the wire format without any compression.
+ ///
+ /// This method simply uses the default implementation.
+ /// See \c AbstractRRset::toWire(OutputBuffer&)const.
+ virtual uint32_t toWire(isc::util::OutputBuffer& buffer) const;
+ //@}
+
+ ///
+ /// \name RDATA manipulation methods
+ ///
+ //@{
+ /// \brief Add an RDATA to the RRset (pointer version).
+ ///
+ /// This method is normally expected to be exception free, but it may
+ /// involve resource allocation, and if it fails the corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param rdata A pointer (like) type of \c rdata::RdataPtr to be added
+ /// to the \c BasicRRset.
+ virtual void addRdata(rdata::ConstRdataPtr rdata);
+
+ /// \brief Add an RDATA to the RRset (reference version).
+ ///
+ /// This method simply uses the default implementation.
+ /// See \c AbstractRRset::addRdata(const rdata::Rdata&).
+ virtual void addRdata(const rdata::Rdata& rdata);
+
+ /// \brief Add an RDATA to the RRset (string version).
+ ///
+ /// \param rdata_str RDATA string in presentation format.
+ /// \throw InvalidRdataText if the \c rdata_str is invalid for this
+ /// \c RRset.
+ virtual void addRdata(const std::string& rdata_str);
+
+ /// \brief Return an iterator to go through all RDATA stored in the
+ /// \c BasicRRset.
+ ///
+ /// This is a concrete derived implementation of
+ /// \c AbstractRRset::getRdataIterator().
+ ///
+ /// This method dynamically allocates resources. If it fails it will
+ /// throw the corresponding standard exception.
+ /// The iterator methods for the \c BasicRRset class are exception free.
+ ///
+ /// \return A pointer-like object pointing to the derived \c RdataIterator
+ /// object for the \c BasicRRset class.
+ virtual RdataIteratorPtr getRdataIterator() const;
+ //@}
+
+ ///
+ /// \name Associated RRSIG methods
+ ///
+ /// The associated RRSIG RRset is not supported in BasicRRset. For
+ /// ease of use, getRRsig() returns a null pointer (indicating no RRset).
+ /// The addRRsig()/removeRRsig() methods throw a "NotImplemented"
+ /// exception - if you are using a BasicRRset, you should not be trying
+ /// to modify signatures on it.
+ //@{
+ /// \brief Return pointer to this RRset's RRSIG RRset
+ ///
+ /// \return Null pointer, as this class does not support RRSIG records.
+ virtual RRsetPtr getRRsig() const {
+ return (RRsetPtr());
+ }
+
+ /// \brief Returns the number of \c RRSIG records associated with
+ /// the \c RRset.
+ ///
+ /// \return Always returns 0. Associated RRSIG RRsets are not
+ /// supported in this class.
+ virtual uint32_t getRRsigDataCount() const {
+ return (0);
+ }
+
+ virtual void addRRsig(const rdata::ConstRdataPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const rdata::RdataPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const AbstractRRset&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const ConstRRsetPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void addRRsig(const RRsetPtr&) {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the addRRsig() method");
+ }
+
+ virtual void removeRRsig() {
+ isc_throw(NotImplemented,
+ "BasicRRset does not implement the removeRRsig() method");
+ }
+ //@}
+private:
+ boost::shared_ptr<BasicRRsetImpl> impl_;
+};
+
+/// \brief The \c RRset class is a concrete derived class of
+/// \c BasicRRset which contains a pointer to an additional RRset
+/// containing associated RRSIG records. This allows DNSSEC aware
+/// applications to treat data associated with a particular
+/// QNAME/QTYPE/QCLASS as a single object.
+class RRset : public BasicRRset {
+public:
+ RRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& ttl);
+
+ virtual ~RRset();
+
+ /// \brief Get the wire format length of the \c RRset.
+ ///
+ /// \return The length of the wire format representation of the
+ /// \c RRset.
+ /// \throw EmptyRRset if the \c RRset is empty.
+ virtual uint16_t getLength() const;
+
+ /// \brief Render the RRset in the wire format with name compression and
+ /// truncation handling.
+ ///
+ /// See \c AbstractRRset::toWire(MessageRenderer&)const.
+ virtual uint32_t toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the RRset in the wire format without any compression.
+ ///
+ /// See \c AbstractRRset::toWire(OutputBuffer&)const.
+ virtual uint32_t toWire(isc::util::OutputBuffer& buffer) const;
+
+ /// \brief Updates the owner name of the \c RRset, including RRSIGs if any
+ virtual void setTTL(const RRTTL& ttl) {
+ BasicRRset::setTTL(ttl);
+ if (rrsig_) {
+ rrsig_->setTTL(ttl);
+ }
+ }
+
+ /// \brief Adds an RRSIG RR to this RRset's signatures
+ virtual void addRRsig(const rdata::ConstRdataPtr& rdata) {
+ if (!rrsig_) {
+ rrsig_ = RRsetPtr(new RRset(getName(), getClass(),
+ RRType::RRSIG(), getTTL()));
+ }
+ rrsig_->addRdata(rdata);
+ }
+
+ // Workaround for older versions of boost: some don't support implicit
+ // conversion from shared_ptr<X> to shared_ptr<const X>. Note: we should
+ // revisit the interface of managing RRset signatures, at which point this
+ // problem may go away.
+ virtual void addRRsig(const rdata::RdataPtr& rdata) {
+ // Don't try to convert as a reference here. SunStudio will reject it.
+ addRRsig(static_cast<const rdata::ConstRdataPtr>(rdata));
+ }
+
+ /// \brief Adds an RRSIG RRset to this RRset
+ virtual void addRRsig(const AbstractRRset& sigs) {
+ RdataIteratorPtr it = sigs.getRdataIterator();
+
+ if (!rrsig_) {
+ rrsig_ = RRsetPtr(new RRset(getName(), getClass(),
+ RRType::RRSIG(), getTTL()));
+ }
+
+ for (it->first(); !it->isLast(); it->next()) {
+ rrsig_->addRdata(it->getCurrent());
+ }
+ }
+
+ virtual void addRRsig(const ConstRRsetPtr& sigs) { addRRsig(*sigs); }
+
+ // Another workaround for older boost (see above)
+ virtual void addRRsig(const RRsetPtr& sigs) { addRRsig(*sigs); }
+
+ /// \brief Clear the RRSIGs for this RRset
+ virtual void removeRRsig() { rrsig_ = RRsetPtr(); }
+
+ /// \brief Return a pointer to this RRset's RRSIG RRset
+ virtual RRsetPtr getRRsig() const { return (rrsig_); }
+
+ /// \brief Returns the number of \c RRSIG records associated with
+ /// the \c RRset.
+ ///
+ /// Note that an \c RRset with no RRSIG records may exist, so this
+ /// method may return 0.
+ ///
+ /// \return The number of \c RRSIG records associated.
+ virtual uint32_t getRRsigDataCount() const;
+
+private:
+ RRsetPtr rrsig_;
+};
+
+
+/// \brief Insert the \c RRset as a string into stream.
+///
+/// This method convert the \c rrset into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global \c operator<< to behave as described in
+/// \c %ostream::%operator<< but applied to RRset objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param rrset A reference to a (derived class of) \c AbstractRRset object
+/// output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const AbstractRRset& rrset);
+} // end of namespace dns
+} // end of namespace isc
+#endif // RRSET_H
+
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
new file mode 100644
index 0000000..983e8d0
--- /dev/null
+++ b/src/lib/dns/rrttl.cc
@@ -0,0 +1,214 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/isc_assert.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrttl.h>
+#include <util/buffer.h>
+
+#include <algorithm>
+#include <cctype>
+#include <stdint.h>
+#include <sstream>
+#include <ostream>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+
+namespace {
+
+// We wrap the C isalpha, because it seems to be overloaded with something.
+// Then the find_if doesn't work.
+bool
+myIsalpha(char c) {
+ return (isalpha(c) != 0);
+}
+
+// The conversion of units to their size
+struct Unit {
+ char unit;
+ uint32_t multiply;
+ uint32_t max_allowed;
+};
+
+Unit units[] = {
+ { 'S', 1, 0xffffffff / 1 },
+ { 'M', 60, 0xffffffff / 60 },
+ { 'H', 60 * 60, 0xffffffff / (60 * 60) },
+ { 'D', 24 * 60 * 60, 0xffffffff / (24 * 60 * 60) },
+ { 'W', 7 * 24 * 60 * 60, 0xffffffff / (7 * 24 * 60 * 60) }
+};
+
+}
+
+namespace isc {
+namespace dns {
+
+namespace {
+bool
+parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
+ if (ttlstr.empty()) {
+ if (error_txt) {
+ *error_txt = "Empty TTL string";
+ }
+ return (false);
+ }
+
+ // We use a larger data type to handle negative number cases.
+ uint64_t val = 0;
+ const string::const_iterator end = ttlstr.end();
+ string::const_iterator pos = ttlstr.begin();
+
+ try {
+ // When we detect we have some units
+ bool units_mode = false;
+
+ while (pos != end) {
+ // Find the first unit, if there's any.
+ const string::const_iterator unit = find_if(pos, end, myIsalpha);
+ // No unit
+ if (unit == end) {
+ if (units_mode) {
+ // We had some units before. The last one is missing unit.
+ if (error_txt) {
+ *error_txt = "Missing the last unit: " + ttlstr;
+ }
+ return (false);
+ } else {
+ // Case without any units at all. Just convert and store
+ // it.
+ val = boost::lexical_cast<uint64_t>(ttlstr);
+ break;
+ }
+ }
+ // There's a unit now.
+ units_mode = true;
+ // Find the unit and get the size.
+ uint32_t multiply = 1; // initialize to silence compiler warnings
+ uint32_t max_allowed = 0xffffffff;
+ bool found = false;
+ for (size_t i = 0; i < sizeof(units) / sizeof(*units); ++i) {
+ if (toupper(*unit) == units[i].unit) {
+ found = true;
+ multiply = units[i].multiply;
+ max_allowed = units[i].max_allowed;
+ break;
+ }
+ }
+ if (!found) {
+ if (error_txt) {
+ *error_txt = "Unknown unit used: " +
+ boost::lexical_cast<string>(*unit) + " in: " + ttlstr;
+ }
+ return (false);
+ }
+ // Now extract the number.
+ if (unit == pos) {
+ if (error_txt) {
+ *error_txt = "Missing number in TTL: " + ttlstr;
+ }
+ return (false);
+ }
+ const uint64_t value =
+ boost::lexical_cast<uint64_t>(string(pos, unit));
+ if (value > max_allowed) {
+ if (error_txt) {
+ *error_txt = "Part of TTL out of range: " + ttlstr;
+ }
+ return (false);
+ }
+
+ // seconds cannot be out of range at this point.
+ const uint64_t seconds = value * multiply;
+ isc_throw_assert(seconds <= 0xffffffff);
+
+ // Add what we found
+ val += seconds;
+ // Check the partial value is still in range (the value can only
+ // grow, so if we get out of range now, it won't get better, so
+ // there's no need to continue).
+ if (val < seconds || val > 0xffffffff) {
+ if (error_txt) {
+ *error_txt = "Part of TTL out of range: " + ttlstr;
+ }
+ return (false);
+ }
+ // Move to after the unit.
+ pos = unit + 1;
+ }
+ } catch (const boost::bad_lexical_cast&) {
+ if (error_txt) {
+ *error_txt = "invalid TTL: " + ttlstr;
+ }
+ return (false);
+ }
+
+ if (val <= 0xffffffff) {
+ ttlval = val;
+ } else {
+ // This could be due to negative numbers in input, etc.
+ if (error_txt) {
+ *error_txt = "TTL out of range: " + ttlstr;
+ }
+ return (false);
+ }
+
+ return (true);
+}
+}
+
+RRTTL::RRTTL(const std::string& ttlstr) {
+ string error_txt;
+ if (!parseTTLString(ttlstr, ttlval_, &error_txt)) {
+ isc_throw(InvalidRRTTL, error_txt);
+ }
+}
+
+RRTTL*
+RRTTL::createFromText(const string& ttlstr) {
+ uint32_t ttlval;
+ if (parseTTLString(ttlstr, ttlval, 0)) {
+ return (new RRTTL(ttlval));
+ }
+ return (0);
+}
+
+RRTTL::RRTTL(InputBuffer& buffer) {
+ if (buffer.getLength() - buffer.getPosition() < sizeof(uint32_t)) {
+ isc_throw(IncompleteRRTTL, "incomplete wire-format TTL value");
+ }
+ ttlval_ = buffer.readUint32();
+}
+
+const string
+RRTTL::toText() const {
+ ostringstream oss;
+ oss << ttlval_;
+ return (oss.str());
+}
+
+void
+RRTTL::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint32(ttlval_);
+}
+
+void
+RRTTL::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint32(ttlval_);
+}
+
+ostream&
+operator<<(ostream& os, const RRTTL& rrttl) {
+ os << rrttl.toText();
+ return (os);
+}
+}
+}
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
new file mode 100644
index 0000000..8f6b03e
--- /dev/null
+++ b/src/lib/dns/rrttl.h
@@ -0,0 +1,313 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RRTTL_H
+#define RRTTL_H
+
+#include <dns/exceptions.h>
+#include <util/buffer.h>
+
+#include <boost/optional.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+
+// forward declarations
+class AbstractMessageRenderer;
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRTTL object
+/// is being constructed from an unrecognized string.
+///
+class InvalidRRTTL : public DNSTextError {
+public:
+ InvalidRRTTL(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRTTL object
+/// is being constructed from a incomplete (too short) wire-format data.
+///
+class IncompleteRRTTL : public isc::dns::Exception {
+public:
+ IncompleteRRTTL(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// The \c RRTTL class encapsulates TTLs used in DNS resource records.
+///
+/// This is a straightforward class; an \c RRTTL object simply maintains a
+/// 32-bit unsigned integer corresponding to the TTL value. The main purpose
+/// of this class is to provide convenient interfaces to convert a textual
+/// representation into the integer TTL value and vice versa, and to handle
+/// wire-format representations.
+class RRTTL {
+public:
+ ///
+ /// \name Constructors, Factory and Destructor
+ ///
+ /// Note: We use the default copy constructor and the default copy
+ /// assignment operator intentionally.
+ //@{
+ /// Constructor from an integer TTL value.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param ttlval An 32-bit integer of the RRTTL.
+ explicit RRTTL(uint32_t ttlval) : ttlval_(ttlval) {
+ }
+
+ /// Constructor from a string.
+ ///
+ /// It accepts either a decimal number, specifying number of seconds. Or,
+ /// it can be given a sequence of numbers and units, like "2H" (meaning
+ /// two hours), "1W3D" (one week and 3 days). The allowed units are W
+ /// (week), D (day), H (hour), M (minute) and S (second). They can be also
+ /// specified in lower-case. No further restrictions are checked (so they
+ /// can be specified in arbitrary order and even things like "1D1D" can
+ /// be used to specify two days).
+ ///
+ /// \param ttlstr A string representation of the \c RRTTL.
+ ///
+ /// \throw InvalidRRTTL in case the string is not recognized as valid
+ /// TTL representation.
+ explicit RRTTL(const std::string& ttlstr);
+
+ /// Constructor from wire-format data.
+ ///
+ /// The \c buffer parameter normally stores a complete DNS message
+ /// containing the RRTTL to be constructed. The current read position of
+ /// the buffer points to the head of the type.
+ ///
+ /// If the given data does not large enough to contain a 16-bit integer,
+ /// an exception of class \c IncompleteRRTTL will be thrown.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ explicit RRTTL(isc::util::InputBuffer& buffer);
+
+ /// A separate factory of RRTTL from text.
+ ///
+ /// This static method is similar to the constructor that takes a string
+ /// object, but works as a factory and reports parsing failure in the
+ /// form of the return value. Normally the constructor version should
+ /// suffice, but in some cases the caller may have to expect mixture of
+ /// valid and invalid input, and may want to minimize the overhead of
+ /// possible exception handling. This version is provided for such
+ /// purpose.
+ ///
+ /// If the given text represents a valid RRTTL, it returns a pointer
+ /// to a new RRTTL object. If the given text does not represent a
+ /// valid RRTTL, it returns null..
+ ///
+ /// One main purpose of this function is to minimize the overhead
+ /// when the given text does not represent a valid RR TTL. For this
+ /// reason this function intentionally omits the capability of delivering
+ /// a detailed reason for the parse failure, such as in the \c want()
+ /// string when exception is thrown from the constructor (it will
+ /// internally require a creation of string object, which is relatively
+ /// expensive). If such detailed information is necessary, the constructor
+ /// version should be used to catch the resulting exception.
+ ///
+ /// This function never throws the \c InvalidRRTTL exception.
+ ///
+ /// \param ttlstr A string representation of the \c RRTTL.
+ /// \return A new RRTTL object for the given text or a null value.
+ static RRTTL* createFromText(const std::string& ttlstr);
+ ///
+ //@}
+
+ ///
+ /// \name Converter methods
+ ///
+ //@{
+ /// \brief Convert the \c RRTTL to a string.
+ ///
+ /// This version of implementation simply converts the TTL value into the
+ /// numeric textual representation. We may introduce more human-readable
+ /// format depending on the context in future versions.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \return A string representation of the \c RRTTL.
+ const std::string toText() const;
+ /// \brief Render the \c RRTTL in the wire format.
+ ///
+ /// This method renders the TTL value in network byte order via \c renderer,
+ /// which encapsulates output buffer and other rendering contexts.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer in which the RRTTL is to be stored.
+ void toWire(AbstractMessageRenderer& renderer) const;
+ /// \brief Render the \c RRTTL in the wire format.
+ ///
+ /// This method renders the TTL value in network byte order into the
+ /// \c buffer.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ void toWire(isc::util::OutputBuffer& buffer) const;
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Returns the TTL value as a 32-bit unsigned integer.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return An 32-bit integer corresponding to the RRTTL.
+ uint32_t getValue() const {
+ return (ttlval_);
+ }
+ //@}
+
+ ///
+ /// \name Comparison methods
+ ///
+ /// Comparison between two \c RRTTL objects is performed in a
+ /// straightforward way, that is, comparing the corresponding TTL values
+ /// (which is the result of the \c getValue() method) as 32-bit unsigned
+ /// integers.
+ //@{
+ /// \brief Return true iff two RRTTLs are equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRTTL object to compare against.
+ bool equals(const RRTTL& other) const {
+ return (ttlval_ == other.ttlval_);
+ }
+ /// \brief Same as \c equals().
+ bool operator==(const RRTTL& other) const {
+ return (ttlval_ == other.ttlval_);
+ }
+ /// \brief Return true iff two RRTTLs are not equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRTTL object to compare against.
+ bool nequals(const RRTTL& other) const {
+ return (ttlval_ != other.ttlval_);
+ }
+ /// \brief Same as \c nequals().
+ bool operator!=(const RRTTL& other) const {
+ return (ttlval_ != other.ttlval_);
+ }
+ /// \brief Less-than or equal comparison for RRTTL against \c other.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRTTL object to compare against.
+ /// \return true if \c this RRTTL is less than or equal to the \c other;
+ /// otherwise false.
+ bool leq(const RRTTL& other) const {
+ return (ttlval_ <= other.ttlval_);
+ }
+
+ /// Same as \c leq()
+ bool operator<=(const RRTTL& other) const {
+ return (ttlval_ <= other.ttlval_);
+ }
+
+ /// \brief Greater-than or equal comparison for RRTTL against \c other.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRTTL object to compare against.
+ /// \return true if \c this RRTTL is greater than or equal to the \c other;
+ /// otherwise false.
+ bool geq(const RRTTL& other) const {
+ return (ttlval_ >= other.ttlval_);
+ }
+
+ /// Same as \c geq()
+ bool operator>=(const RRTTL& other) const {
+ return (ttlval_ >= other.ttlval_);
+ }
+
+ /// \brief Less-than comparison for RRTTL against \c other.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRTTL object to compare against.
+ /// \return true if \c this RRTTL is less than the \c other;
+ /// otherwise false.
+ bool lthan(const RRTTL& other) const {
+ return (ttlval_ < other.ttlval_);
+ }
+
+ /// Same as \c lthan()
+ bool operator<(const RRTTL& other) const {
+ return (ttlval_ < other.ttlval_);
+ }
+
+ /// \brief Greater-than comparison for RRTTL against \c other.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRTTL object to compare against.
+ /// \return true if \c this RRTTL is greater than the \c other;
+ /// otherwise false.
+ bool gthan(const RRTTL& other) const {
+ return (ttlval_ > other.ttlval_);
+ }
+
+ /// Same as \c gthan()
+ bool operator>(const RRTTL& other) const {
+ return (ttlval_ > other.ttlval_);
+ }
+ //@}
+
+ ///
+ /// \name Protocol constants
+ ///
+ //@{
+ /// \brief The TTL of the max allowable value, per RFC2181 Section 8.
+ ///
+ /// The max value is the largest unsigned 31 bit integer, 2^31-1.
+ ///
+ /// \note At the moment an RRTTL object can have a value larger than
+ /// this limit. We may revisit it in a future version.
+ static const RRTTL& MAX_TTL() {
+ static const RRTTL max_ttl(0x7fffffff);
+ return (max_ttl);
+ }
+ //@}
+
+private:
+ uint32_t ttlval_;
+};
+
+///
+/// \brief Insert the \c RRTTL as a string into stream.
+///
+/// This method convert the \c rrttl into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described in
+/// ostream::operator<< but applied to \c RRTTL objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param rrttl The \c RRTTL object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const RRTTL& rrttl);
+}
+}
+#endif // RRTTL_H
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
new file mode 100644
index 0000000..d060c41
--- /dev/null
+++ b/src/lib/dns/rrtype.cc
@@ -0,0 +1,64 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrtype.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+#include <string>
+#include <ostream>
+
+using namespace isc::util;
+using isc::dns::RRType;
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+
+RRType::RRType(const std::string& type_str) {
+ uint16_t typecode;
+ if (!RRParamRegistry::getRegistry().textToTypeCode(type_str, typecode)) {
+ isc_throw(InvalidRRType,
+ "Unrecognized RR type string: " + type_str);
+ }
+ typecode_ = typecode;
+}
+
+RRType::RRType(InputBuffer& buffer) {
+ if (buffer.getLength() - buffer.getPosition() < sizeof(uint16_t)) {
+ isc_throw(IncompleteRRType, "incomplete wire-format RR type");
+ }
+ typecode_ = buffer.readUint16();
+}
+
+const string
+RRType::toText() const {
+ return (RRParamRegistry::getRegistry().codeToTypeText(typecode_));
+}
+
+void
+RRType::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(typecode_);
+}
+
+void
+RRType::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(typecode_);
+}
+
+ostream&
+operator<<(ostream& os, const RRType& rrtype) {
+ os << rrtype.toText();
+ return (os);
+}
+}
+}
diff --git a/src/lib/dns/rrtype.h b/src/lib/dns/rrtype.h
new file mode 100644
index 0000000..ae575d0
--- /dev/null
+++ b/src/lib/dns/rrtype.h
@@ -0,0 +1,368 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RRTYPE_H
+#define RRTYPE_H
+
+#include <stdint.h>
+
+#include <string>
+#include <ostream>
+
+#include <dns/exceptions.h>
+#include <util/buffer.h>
+
+// Solaris x86 defines DS in <sys/regset.h>, which gets pulled in by Boost
+#if defined(__sun) && defined(DS)
+# undef DS
+#endif
+
+namespace isc {
+
+namespace dns {
+
+// forward declarations
+class AbstractMessageRenderer;
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRType object
+/// is being constructed from an unrecognized string.
+///
+class InvalidRRType : public DNSTextError {
+public:
+ InvalidRRType(const char* file, size_t line, const char* what) :
+ DNSTextError(file, line, what) {}
+};
+
+///
+/// \brief A standard DNS module exception that is thrown if an RRType object
+/// is being constructed from a incomplete (too short) wire-format data.
+///
+class IncompleteRRType : public isc::dns::Exception {
+public:
+ IncompleteRRType(const char* file, size_t line, const char* what) :
+ isc::dns::Exception(file, line, what) {}
+};
+
+///
+/// The \c RRType class encapsulates DNS resource record types.
+///
+/// This class manages the 16-bit integer type codes in quite a straightforward
+/// way. The only non trivial task is to handle textual representations of
+/// RR types, such as "A", "AAAA", or "TYPE65534".
+///
+/// This class consults a helper \c RRParamRegistry class, which is a registry
+/// of RR related parameters and has the singleton object. This registry
+/// provides a mapping between RR type codes and their "well-known" textual
+/// representations.
+/// Parameters of RR types defined by DNS protocol standards are automatically
+/// registered at initialization time and are ensured to be always available for
+/// applications unless the application explicitly modifies the registry.
+///
+/// For convenience, this class defines constant class objects corresponding to
+/// standard RR types. These are generally referred to as the form of
+/// <code>RRType::{type-text}()</code>.
+/// For example, \c RRType::NS() is an \c RRType object corresponding to the NS
+/// resource record (type code 2).
+/// Note that these constants are used through a "proxy" function.
+/// This is because they may be used to initialize another non-local (e.g.
+/// global or namespace-scope) static object as follows:
+///
+/// \code
+/// namespace foo {
+/// const RRType default_type = RRType::A();
+/// } \endcode
+///
+/// In order to ensure that the constant RRType object has been initialized
+/// before the initialization for \c default_type, we need help from
+/// the proxy function.
+///
+/// In the current implementation, the initialization of the well-known
+/// static objects is not thread safe. The same consideration as the
+/// \c RRParamRegistry class applies. We may extend the implementation so
+/// that the initialization is ensured to be thread safe in a future version.
+///
+/// Note to developers: since it's expected that some of these constant
+/// \c RRType objects are frequently used in a performance sensitive path,
+/// we define these proxy functions as inline. This makes sense only when
+/// the corresponding static objects are defined only once even if they used
+/// in different source files. Sufficiently modern compilers should meet
+/// this assumption, but if we encounter memory bloat due to this problem with
+/// particular compilers we need to revisit the design or think about
+/// workaround.
+class RRType {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+ /// Constructor from an integer type code.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param typecode An 16-bit integer code corresponding to the RRType.
+ explicit RRType(uint16_t typecode) : typecode_(typecode) {
+ }
+ /// Constructor from a string.
+ ///
+ /// A valid string is one of "well-known" textual type representations
+ /// such as "A", "AAAA", or "NS", or in the standard format for "unknown"
+ /// RR types as defined in RFC3597, i.e., "TYPEnnnn".
+ ///
+ /// More precisely, the "well-known" representations are the ones stored
+ /// in the \c RRParamRegistry registry (see the class description).
+ ///
+ /// As for the format of "TYPEnnnn", "nnnn" must represent a valid 16-bit
+ /// unsigned integer, which may contain leading 0's as long as it consists
+ /// of at most 5 characters (inclusive).
+ /// For example, "TYPE1" and "TYPE001" are valid and represent the same
+ /// RR type, but "TYPE65536" and "TYPE000001" are invalid.
+ /// A "TYPEnnnn" representation is valid even if the corresponding type code
+ /// is registered in the \c RRParamRegistry object. For example, both
+ /// "A" and "TYPE1" are valid and represent the same RR type.
+ ///
+ /// All of these representations are case insensitive; "NS" and "ns", and
+ /// "TYPE1" and "type1" are all valid and represent the same RR types,
+ /// respectively.
+ ///
+ /// If the given string is not recognized as a valid representation of
+ /// an RR type, an exception of class \c InvalidRRType will be thrown.
+ ///
+ /// \param typestr A string representation of the \c RRType
+ explicit RRType(const std::string& typestr);
+ /// Constructor from wire-format data.
+ ///
+ /// The \c buffer parameter normally stores a complete DNS message
+ /// containing the RRType to be constructed. The current read position of
+ /// the buffer points to the head of the type.
+ ///
+ /// If the given data does not large enough to contain a 16-bit integer,
+ /// an exception of class \c IncompleteRRType will be thrown.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ explicit RRType(isc::util::InputBuffer& buffer);
+ ///
+ /// We use the default copy constructor intentionally.
+ //@}
+ /// We use the default copy assignment operator intentionally.
+ ///
+
+ ///
+ /// \name Converter methods
+ ///
+ //@{
+ /// \brief Convert the \c RRType to a string.
+ ///
+ /// If a "well known" textual representation for the type code is registered
+ /// in the RR parameter registry (see the class description), that will be
+ /// used as the return value of this method. Otherwise, this method creates
+ /// a new string for an "unknown" RR type in the format defined in RFC3597,
+ /// i.e., "TYPEnnnn", and returns it.
+ ///
+ /// If resource allocation for the string fails, a corresponding standard
+ /// exception will be thrown.
+ ///
+ /// \return A string representation of the \c RRType.
+ const std::string toText() const;
+ /// \brief Render the \c RRType in the wire format.
+ ///
+ /// This method renders the type code in network byte order via \c renderer,
+ /// which encapsulates output buffer and other rendering contexts.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer in which the RRType is to be stored.
+ void toWire(AbstractMessageRenderer& renderer) const;
+ /// \brief Render the \c RRType in the wire format.
+ ///
+ /// This method renders the type code in network byte order into the
+ /// \c buffer.
+ ///
+ /// If resource allocation in rendering process fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ void toWire(isc::util::OutputBuffer& buffer) const;
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Returns the RR type code as a 16-bit unsigned integer.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return An 16-bit integer code corresponding to the RRType.
+ uint16_t getCode() const {
+ return (typecode_);
+ }
+ //@}
+
+ ///
+ /// \name Comparison methods
+ ///
+ //@{
+ /// \brief Return true iff two RRTypes are equal.
+ ///
+ /// Two RRTypes are equal iff their type codes are equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRType object to compare against.
+ /// \return true if the two RRTypes are equal; otherwise false.
+ bool equals(const RRType& other) const {
+ return (typecode_ == other.typecode_);
+ }
+ /// \brief Same as \c equals().
+ bool operator==(const RRType& other) const {
+ return (equals(other));
+ }
+
+ /// \brief Return true iff two RRTypes are not equal.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRType object to compare against.
+ /// \return true if the two RRTypes are not equal; otherwise false.
+ bool nequals(const RRType& other) const {
+ return (typecode_ != other.typecode_);
+ }
+ /// \brief Same as \c nequals().
+ bool operator!=(const RRType& other) const {
+ return (nequals(other));
+ }
+
+ /// \brief Less-than comparison for RRType against \c other
+ ///
+ /// We define the less-than relationship based on their type codes;
+ /// one RRType is less than the other iff the code of the former is less
+ /// than that of the other as unsigned integers.
+ /// The relationship is meaningless in terms of DNS protocol; the only
+ /// reason we define this method is that RRType objects can be stored in
+ /// STL containers without requiring user-defined less-than relationship.
+ /// We therefore don't define other comparison operators.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param other the \c RRType object to compare against.
+ /// \return true if \c this RRType is less than the \c other; otherwise
+ /// false.
+ bool operator<(const RRType& other) const {
+ return (typecode_ < other.typecode_);
+ }
+ //@}
+
+ static const RRType& A();
+ static const RRType& NS();
+ static const RRType& SOA();
+ static const RRType& OPT();
+ static const RRType& PTR();
+ static const RRType& TXT();
+ static const RRType& AAAA();
+ static const RRType& RRSIG();
+ static const RRType& DHCID();
+ static const RRType& TKEY();
+ static const RRType& TSIG();
+ static const RRType& ANY();
+
+private:
+ uint16_t typecode_;
+};
+
+inline const RRType&
+RRType::A() {
+ static RRType rrtype(1);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NS() {
+ static RRType rrtype(2);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::SOA() {
+ static RRType rrtype(6);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::OPT() {
+ static RRType rrtype(41);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::PTR() {
+ static RRType rrtype(12);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::TXT() {
+ static RRType rrtype(16);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::AAAA() {
+ static RRType rrtype(28);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::RRSIG() {
+ static RRType rrtype(46);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::DHCID() {
+ static RRType rrtype(49);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::TKEY() {
+ static RRType rrtype(249);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::TSIG() {
+ static RRType rrtype(250);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::ANY() {
+ static RRType rrtype(255);
+ return (rrtype);
+}
+
+///
+/// \brief Insert the \c RRType as a string into stream.
+///
+/// This method convert the \c rrtype into a string and inserts it into the
+/// output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described in
+/// ostream::operator<< but applied to \c RRType objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param rrtype The \c RRType object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const RRType& rrtype);
+}
+}
+#endif // RRTYPE_H
diff --git a/src/lib/dns/serial.cc b/src/lib/dns/serial.cc
new file mode 100644
index 0000000..842bff8
--- /dev/null
+++ b/src/lib/dns/serial.cc
@@ -0,0 +1,70 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/serial.h>
+
+namespace isc {
+namespace dns {
+
+bool
+Serial::operator==(const Serial& other) const {
+ return (value_ == other.getValue());
+}
+
+bool
+Serial::operator!=(const Serial& other) const {
+ return (value_ != other.getValue());
+}
+
+bool
+Serial::operator<(const Serial& other) const {
+ uint32_t other_val = other.getValue();
+ bool result = false;
+ if (value_ < other_val) {
+ result = ((other_val - value_) <= MAX_SERIAL_INCREMENT);
+ } else if (other_val < value_) {
+ result = ((value_ - other_val) > MAX_SERIAL_INCREMENT);
+ }
+ return (result);
+}
+
+bool
+Serial::operator<=(const Serial& other) const {
+ return (operator==(other) || operator<(other));
+}
+
+bool
+Serial::operator>(const Serial& other) const {
+ return (!operator==(other) && !operator<(other));
+}
+
+bool
+Serial::operator>=(const Serial& other) const {
+ return (!operator<(other));
+}
+
+Serial
+Serial::operator+(uint32_t other_val) const {
+ uint64_t new_val = static_cast<uint64_t>(value_) +
+ static_cast<uint64_t>(other_val);
+ return Serial(static_cast<uint32_t>(new_val % MAX_SERIAL_VALUE));
+}
+
+Serial
+Serial::operator+(const Serial& other) const {
+ return (operator+(other.getValue()));
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Serial& serial) {
+ return (os << serial.getValue());
+}
+
+} // end namespace dns
+} // end namespace isc
+
diff --git a/src/lib/dns/serial.h b/src/lib/dns/serial.h
new file mode 100644
index 0000000..324c046
--- /dev/null
+++ b/src/lib/dns/serial.h
@@ -0,0 +1,150 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SERIAL_H
+#define SERIAL_H
+
+#include <stdint.h>
+#include <iostream>
+
+namespace isc {
+namespace dns {
+
+/// The maximum difference between two serial numbers. If the (plain uint32_t)
+/// difference between two serials is greater than this number, the smaller one
+/// is considered greater.
+const uint32_t MAX_SERIAL_INCREMENT = 2147483647;
+
+/// Maximum value a serial can have, used in + operator.
+const uint64_t MAX_SERIAL_VALUE = 4294967296ull;
+
+/// \brief This class defines DNS serial numbers and serial arithmetic.
+///
+/// DNS Serial number are in essence unsigned 32-bits numbers, with one
+/// catch; they should be compared using sequence space arithmetic.
+/// So given that they are 32-bits; as soon as the difference between two
+/// serial numbers is greater than 2147483647 (2^31 - 1), the lower number
+/// (in plain comparison) is considered the higher one.
+///
+/// In order to do this as transparently as possible, these numbers are
+/// stored in the Serial class, which overrides the basic comparison operators.
+///
+/// In this specific context, these operations are called 'serial number
+/// arithmetic', and they are defined in RFC 1982.
+///
+/// \note RFC 1982 defines everything based on the value SERIAL_BITS. Since
+/// the serial number has a fixed length of 32 bits, the values we use are
+/// hard-coded, and not computed based on variable bit lengths.
+class Serial {
+public:
+ /// \brief Constructor with value
+ ///
+ /// \param value The uint32_t value of the serial
+ explicit Serial(uint32_t value) : value_(value) {}
+
+ /// \brief Copy constructor
+ Serial(const Serial& other) : value_(other.getValue()) {}
+
+ /// \brief Direct assignment from other Serial
+ ///
+ /// \param other The Serial to assign the value from
+ Serial& operator=(const Serial& other) {
+ value_ = other.getValue();
+ return (*this);
+ }
+
+ /// \brief Direct assignment from value
+ ///
+ /// \param value the uint32_t value to assign
+ void operator=(uint32_t value) { value_ = value; }
+
+ /// \brief Returns the uint32_t representation of this serial value
+ ///
+ /// \return The uint32_t value of this Serial
+ uint32_t getValue() const { return (value_); }
+
+ /// \brief Returns true if the serial values are equal
+ ///
+ /// \return True if the values are equal
+ bool operator==(const Serial& other) const;
+
+ /// \brief Returns true if the serial values are not equal
+ ///
+ /// \return True if the values are not equal
+ bool operator!=(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is smaller than
+ /// the other, according to serial arithmetic as described in RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is smaller than the given value
+ bool operator<(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is equal to or
+ /// smaller than the other, according to serial arithmetic as described
+ /// in RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is smaller than or equal to the given value
+ bool operator<=(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is greater than
+ /// the other, according to serial arithmetic as described in RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is greater than the given value
+ bool operator>(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is equal to or
+ /// greater than the other, according to serial arithmetic as described in
+ /// RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is greater than or equal to the given value
+ bool operator>=(const Serial& other) const;
+
+ /// \brief Adds the given value to the serial number. If this would make
+ /// the number greater than 2^32-1, it is 'wrapped'.
+ /// \note According to the specification, an addition greater than
+ /// MAX_SERIAL_INCREMENT is undefined. We do NOT catch this error (so as not
+ /// to raise exceptions), but this behaviour remains undefined.
+ ///
+ /// \param other The Serial to add
+ ///
+ /// \return The result of the addition
+ Serial operator+(const Serial& other) const;
+
+ /// \brief Adds the given value to the serial number. If this would make
+ /// the number greater than 2^32-1, it is 'wrapped'.
+ ///
+ /// \note According to the specification, an addition greater than
+ /// MAX_SERIAL_INCREMENT is undefined. We do NOT catch this error (so as not
+ /// to raise exceptions), but this behaviour remains undefined.
+ ///
+ /// \param other_val The uint32_t value to add
+ ///
+ /// \return The result of the addition
+ Serial operator+(uint32_t other_val) const;
+
+private:
+ uint32_t value_;
+};
+
+/// \brief Helper operator for output streams, writes the value to the stream
+///
+/// \param os The ostream to write to
+/// \param serial The Serial to write
+/// \return the output stream
+std::ostream& operator<<(std::ostream& os, const Serial& serial);
+
+} // end namespace dns
+} // end namespace isc
+
+#endif // SERIAL_H
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
new file mode 100644
index 0000000..9594b93
--- /dev/null
+++ b/src/lib/dns/tests/Makefile.am
@@ -0,0 +1,72 @@
+SUBDIRS = testdata .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(abs_top_srcdir)/src/lib/dns/tests/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dns/tests/testdata\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = unittest_util.h unittest_util.cc
+run_unittests_SOURCES += dns_exceptions_unittest.cc
+run_unittests_SOURCES += edns_unittest.cc
+run_unittests_SOURCES += master_lexer_inputsource_unittest.cc
+run_unittests_SOURCES += labelsequence_unittest.cc
+run_unittests_SOURCES += messagerenderer_unittest.cc
+run_unittests_SOURCES += master_lexer_token_unittest.cc
+run_unittests_SOURCES += master_lexer_unittest.cc
+run_unittests_SOURCES += master_loader_unittest.cc
+run_unittests_SOURCES += master_lexer_state_unittest.cc
+run_unittests_SOURCES += name_unittest.cc
+run_unittests_SOURCES += rrttl_unittest.cc
+run_unittests_SOURCES += rrclass_unittest.cc
+run_unittests_SOURCES += rrtype_unittest.cc
+run_unittests_SOURCES += opcode_unittest.cc
+run_unittests_SOURCES += rcode_unittest.cc
+run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
+run_unittests_SOURCES += rdata_char_string_unittest.cc
+run_unittests_SOURCES += rdata_char_string_data_unittest.cc
+run_unittests_SOURCES += rdata_in_a_unittest.cc
+run_unittests_SOURCES += rdata_in_aaaa_unittest.cc
+run_unittests_SOURCES += rdata_ns_unittest.cc
+run_unittests_SOURCES += rdata_soa_unittest.cc
+run_unittests_SOURCES += rdata_txt_like_unittest.cc
+run_unittests_SOURCES += rdata_ptr_unittest.cc
+run_unittests_SOURCES += rdata_opt_unittest.cc
+run_unittests_SOURCES += rdata_dhcid_unittest.cc
+run_unittests_SOURCES += rdata_rrsig_unittest.cc
+run_unittests_SOURCES += rdata_tsig_unittest.cc
+run_unittests_SOURCES += rdata_tkey_unittest.cc
+run_unittests_SOURCES += rrset_unittest.cc
+run_unittests_SOURCES += question_unittest.cc
+run_unittests_SOURCES += rrparamregistry_unittest.cc
+run_unittests_SOURCES += message_unittest.cc
+run_unittests_SOURCES += serial_unittest.cc
+run_unittests_SOURCES += time_utils_unittest.cc
+run_unittests_SOURCES += tsig_unittest.cc
+run_unittests_SOURCES += tsigerror_unittest.cc
+run_unittests_SOURCES += tsigkey_unittest.cc
+run_unittests_SOURCES += tsigrecord_unittest.cc
+run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(top_builddir)/src/lib/dns/libkea-dns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(CRYPTO_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dns/tests/Makefile.in b/src/lib/dns/tests/Makefile.in
new file mode 100644
index 0000000..e7818a9
--- /dev/null
+++ b/src/lib/dns/tests/Makefile.in
@@ -0,0 +1,1835 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+TESTS = $(am__EXEEXT_1)
+@HAVE_GTEST_TRUE@am__append_1 = run_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/dns/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp14.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__run_unittests_SOURCES_DIST = unittest_util.h unittest_util.cc \
+ dns_exceptions_unittest.cc edns_unittest.cc \
+ master_lexer_inputsource_unittest.cc labelsequence_unittest.cc \
+ messagerenderer_unittest.cc master_lexer_token_unittest.cc \
+ master_lexer_unittest.cc master_loader_unittest.cc \
+ master_lexer_state_unittest.cc name_unittest.cc \
+ rrttl_unittest.cc rrclass_unittest.cc rrtype_unittest.cc \
+ opcode_unittest.cc rcode_unittest.cc rdata_unittest.h \
+ rdata_unittest.cc rdata_char_string_unittest.cc \
+ rdata_char_string_data_unittest.cc rdata_in_a_unittest.cc \
+ rdata_in_aaaa_unittest.cc rdata_ns_unittest.cc \
+ rdata_soa_unittest.cc rdata_txt_like_unittest.cc \
+ rdata_ptr_unittest.cc rdata_opt_unittest.cc \
+ rdata_dhcid_unittest.cc rdata_rrsig_unittest.cc \
+ rdata_tsig_unittest.cc rdata_tkey_unittest.cc \
+ rrset_unittest.cc question_unittest.cc \
+ rrparamregistry_unittest.cc message_unittest.cc \
+ serial_unittest.cc time_utils_unittest.cc tsig_unittest.cc \
+ tsigerror_unittest.cc tsigkey_unittest.cc \
+ tsigrecord_unittest.cc master_loader_callbacks_test.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-unittest_util.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-dns_exceptions_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-edns_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-master_lexer_inputsource_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-labelsequence_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-messagerenderer_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-master_lexer_token_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-master_lexer_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-master_loader_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-master_lexer_state_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-name_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrttl_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrclass_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrtype_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-opcode_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rcode_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_char_string_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_char_string_data_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_in_a_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_in_aaaa_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_ns_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_soa_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_txt_like_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_ptr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_opt_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_dhcid_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_rrsig_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_tsig_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_tkey_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrset_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-question_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrparamregistry_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-message_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-serial_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-time_utils_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tsig_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tsigerror_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tsigkey_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tsigrecord_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-master_loader_callbacks_test.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) $(LDFLAGS) \
+ -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po \
+ ./$(DEPDIR)/run_unittests-edns_unittest.Po \
+ ./$(DEPDIR)/run_unittests-labelsequence_unittest.Po \
+ ./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po \
+ ./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po \
+ ./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po \
+ ./$(DEPDIR)/run_unittests-master_lexer_unittest.Po \
+ ./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po \
+ ./$(DEPDIR)/run_unittests-master_loader_unittest.Po \
+ ./$(DEPDIR)/run_unittests-message_unittest.Po \
+ ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po \
+ ./$(DEPDIR)/run_unittests-name_unittest.Po \
+ ./$(DEPDIR)/run_unittests-opcode_unittest.Po \
+ ./$(DEPDIR)/run_unittests-question_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rcode_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrclass_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrset_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrttl_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrtype_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-serial_unittest.Po \
+ ./$(DEPDIR)/run_unittests-time_utils_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tsig_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tsigerror_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tsigkey_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po \
+ ./$(DEPDIR)/run_unittests-unittest_util.Po
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DPKG = @DPKG@
+DPKGQUERY = @DPKGQUERY@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+XMLLINT = @XMLLINT@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = testdata .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_SRCDIR=\"$(abs_top_srcdir)/src/lib/dns/tests/testdata\" \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dns/tests/testdata\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = unittest_util.h \
+@HAVE_GTEST_TRUE@ unittest_util.cc dns_exceptions_unittest.cc \
+@HAVE_GTEST_TRUE@ edns_unittest.cc \
+@HAVE_GTEST_TRUE@ master_lexer_inputsource_unittest.cc \
+@HAVE_GTEST_TRUE@ labelsequence_unittest.cc \
+@HAVE_GTEST_TRUE@ messagerenderer_unittest.cc \
+@HAVE_GTEST_TRUE@ master_lexer_token_unittest.cc \
+@HAVE_GTEST_TRUE@ master_lexer_unittest.cc \
+@HAVE_GTEST_TRUE@ master_loader_unittest.cc \
+@HAVE_GTEST_TRUE@ master_lexer_state_unittest.cc \
+@HAVE_GTEST_TRUE@ name_unittest.cc rrttl_unittest.cc \
+@HAVE_GTEST_TRUE@ rrclass_unittest.cc rrtype_unittest.cc \
+@HAVE_GTEST_TRUE@ opcode_unittest.cc rcode_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_unittest.h rdata_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_char_string_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_char_string_data_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_in_a_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_in_aaaa_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_ns_unittest.cc rdata_soa_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_txt_like_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_ptr_unittest.cc rdata_opt_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_dhcid_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_rrsig_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_tsig_unittest.cc rdata_tkey_unittest.cc \
+@HAVE_GTEST_TRUE@ rrset_unittest.cc question_unittest.cc \
+@HAVE_GTEST_TRUE@ rrparamregistry_unittest.cc \
+@HAVE_GTEST_TRUE@ message_unittest.cc serial_unittest.cc \
+@HAVE_GTEST_TRUE@ time_utils_unittest.cc tsig_unittest.cc \
+@HAVE_GTEST_TRUE@ tsigerror_unittest.cc tsigkey_unittest.cc \
+@HAVE_GTEST_TRUE@ tsigrecord_unittest.cc \
+@HAVE_GTEST_TRUE@ master_loader_callbacks_test.cc \
+@HAVE_GTEST_TRUE@ run_unittests.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LIBS) $(GTEST_LDADD)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/dns/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dns/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+run_unittests$(EXEEXT): $(run_unittests_OBJECTS) $(run_unittests_DEPENDENCIES) $(EXTRA_run_unittests_DEPENDENCIES)
+ @rm -f run_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(run_unittests_LINK) $(run_unittests_OBJECTS) $(run_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-edns_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-labelsequence_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_loader_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-message_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-name_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-opcode_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-question_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rcode_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrclass_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrset_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrttl_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrtype_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-serial_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-time_utils_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsig_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsigerror_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsigkey_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-unittest_util.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+run_unittests-unittest_util.o: unittest_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unittest_util.o -MD -MP -MF $(DEPDIR)/run_unittests-unittest_util.Tpo -c -o run_unittests-unittest_util.o `test -f 'unittest_util.cc' || echo '$(srcdir)/'`unittest_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unittest_util.Tpo $(DEPDIR)/run_unittests-unittest_util.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unittest_util.cc' object='run_unittests-unittest_util.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unittest_util.o `test -f 'unittest_util.cc' || echo '$(srcdir)/'`unittest_util.cc
+
+run_unittests-unittest_util.obj: unittest_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unittest_util.obj -MD -MP -MF $(DEPDIR)/run_unittests-unittest_util.Tpo -c -o run_unittests-unittest_util.obj `if test -f 'unittest_util.cc'; then $(CYGPATH_W) 'unittest_util.cc'; else $(CYGPATH_W) '$(srcdir)/unittest_util.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unittest_util.Tpo $(DEPDIR)/run_unittests-unittest_util.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unittest_util.cc' object='run_unittests-unittest_util.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unittest_util.obj `if test -f 'unittest_util.cc'; then $(CYGPATH_W) 'unittest_util.cc'; else $(CYGPATH_W) '$(srcdir)/unittest_util.cc'; fi`
+
+run_unittests-dns_exceptions_unittest.o: dns_exceptions_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dns_exceptions_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo -c -o run_unittests-dns_exceptions_unittest.o `test -f 'dns_exceptions_unittest.cc' || echo '$(srcdir)/'`dns_exceptions_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo $(DEPDIR)/run_unittests-dns_exceptions_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_exceptions_unittest.cc' object='run_unittests-dns_exceptions_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dns_exceptions_unittest.o `test -f 'dns_exceptions_unittest.cc' || echo '$(srcdir)/'`dns_exceptions_unittest.cc
+
+run_unittests-dns_exceptions_unittest.obj: dns_exceptions_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dns_exceptions_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo -c -o run_unittests-dns_exceptions_unittest.obj `if test -f 'dns_exceptions_unittest.cc'; then $(CYGPATH_W) 'dns_exceptions_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dns_exceptions_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo $(DEPDIR)/run_unittests-dns_exceptions_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_exceptions_unittest.cc' object='run_unittests-dns_exceptions_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dns_exceptions_unittest.obj `if test -f 'dns_exceptions_unittest.cc'; then $(CYGPATH_W) 'dns_exceptions_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dns_exceptions_unittest.cc'; fi`
+
+run_unittests-edns_unittest.o: edns_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-edns_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-edns_unittest.Tpo -c -o run_unittests-edns_unittest.o `test -f 'edns_unittest.cc' || echo '$(srcdir)/'`edns_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-edns_unittest.Tpo $(DEPDIR)/run_unittests-edns_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='edns_unittest.cc' object='run_unittests-edns_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-edns_unittest.o `test -f 'edns_unittest.cc' || echo '$(srcdir)/'`edns_unittest.cc
+
+run_unittests-edns_unittest.obj: edns_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-edns_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-edns_unittest.Tpo -c -o run_unittests-edns_unittest.obj `if test -f 'edns_unittest.cc'; then $(CYGPATH_W) 'edns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/edns_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-edns_unittest.Tpo $(DEPDIR)/run_unittests-edns_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='edns_unittest.cc' object='run_unittests-edns_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-edns_unittest.obj `if test -f 'edns_unittest.cc'; then $(CYGPATH_W) 'edns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/edns_unittest.cc'; fi`
+
+run_unittests-master_lexer_inputsource_unittest.o: master_lexer_inputsource_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_inputsource_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo -c -o run_unittests-master_lexer_inputsource_unittest.o `test -f 'master_lexer_inputsource_unittest.cc' || echo '$(srcdir)/'`master_lexer_inputsource_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_inputsource_unittest.cc' object='run_unittests-master_lexer_inputsource_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_inputsource_unittest.o `test -f 'master_lexer_inputsource_unittest.cc' || echo '$(srcdir)/'`master_lexer_inputsource_unittest.cc
+
+run_unittests-master_lexer_inputsource_unittest.obj: master_lexer_inputsource_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_inputsource_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo -c -o run_unittests-master_lexer_inputsource_unittest.obj `if test -f 'master_lexer_inputsource_unittest.cc'; then $(CYGPATH_W) 'master_lexer_inputsource_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_inputsource_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_inputsource_unittest.cc' object='run_unittests-master_lexer_inputsource_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_inputsource_unittest.obj `if test -f 'master_lexer_inputsource_unittest.cc'; then $(CYGPATH_W) 'master_lexer_inputsource_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_inputsource_unittest.cc'; fi`
+
+run_unittests-labelsequence_unittest.o: labelsequence_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labelsequence_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo -c -o run_unittests-labelsequence_unittest.o `test -f 'labelsequence_unittest.cc' || echo '$(srcdir)/'`labelsequence_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo $(DEPDIR)/run_unittests-labelsequence_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labelsequence_unittest.cc' object='run_unittests-labelsequence_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-labelsequence_unittest.o `test -f 'labelsequence_unittest.cc' || echo '$(srcdir)/'`labelsequence_unittest.cc
+
+run_unittests-labelsequence_unittest.obj: labelsequence_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labelsequence_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo -c -o run_unittests-labelsequence_unittest.obj `if test -f 'labelsequence_unittest.cc'; then $(CYGPATH_W) 'labelsequence_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labelsequence_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo $(DEPDIR)/run_unittests-labelsequence_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labelsequence_unittest.cc' object='run_unittests-labelsequence_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-labelsequence_unittest.obj `if test -f 'labelsequence_unittest.cc'; then $(CYGPATH_W) 'labelsequence_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labelsequence_unittest.cc'; fi`
+
+run_unittests-messagerenderer_unittest.o: messagerenderer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-messagerenderer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo -c -o run_unittests-messagerenderer_unittest.o `test -f 'messagerenderer_unittest.cc' || echo '$(srcdir)/'`messagerenderer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo $(DEPDIR)/run_unittests-messagerenderer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='messagerenderer_unittest.cc' object='run_unittests-messagerenderer_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-messagerenderer_unittest.o `test -f 'messagerenderer_unittest.cc' || echo '$(srcdir)/'`messagerenderer_unittest.cc
+
+run_unittests-messagerenderer_unittest.obj: messagerenderer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-messagerenderer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo -c -o run_unittests-messagerenderer_unittest.obj `if test -f 'messagerenderer_unittest.cc'; then $(CYGPATH_W) 'messagerenderer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/messagerenderer_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo $(DEPDIR)/run_unittests-messagerenderer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='messagerenderer_unittest.cc' object='run_unittests-messagerenderer_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-messagerenderer_unittest.obj `if test -f 'messagerenderer_unittest.cc'; then $(CYGPATH_W) 'messagerenderer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/messagerenderer_unittest.cc'; fi`
+
+run_unittests-master_lexer_token_unittest.o: master_lexer_token_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_token_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo -c -o run_unittests-master_lexer_token_unittest.o `test -f 'master_lexer_token_unittest.cc' || echo '$(srcdir)/'`master_lexer_token_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_token_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_token_unittest.cc' object='run_unittests-master_lexer_token_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_token_unittest.o `test -f 'master_lexer_token_unittest.cc' || echo '$(srcdir)/'`master_lexer_token_unittest.cc
+
+run_unittests-master_lexer_token_unittest.obj: master_lexer_token_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_token_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo -c -o run_unittests-master_lexer_token_unittest.obj `if test -f 'master_lexer_token_unittest.cc'; then $(CYGPATH_W) 'master_lexer_token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_token_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_token_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_token_unittest.cc' object='run_unittests-master_lexer_token_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_token_unittest.obj `if test -f 'master_lexer_token_unittest.cc'; then $(CYGPATH_W) 'master_lexer_token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_token_unittest.cc'; fi`
+
+run_unittests-master_lexer_unittest.o: master_lexer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo -c -o run_unittests-master_lexer_unittest.o `test -f 'master_lexer_unittest.cc' || echo '$(srcdir)/'`master_lexer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_unittest.cc' object='run_unittests-master_lexer_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_unittest.o `test -f 'master_lexer_unittest.cc' || echo '$(srcdir)/'`master_lexer_unittest.cc
+
+run_unittests-master_lexer_unittest.obj: master_lexer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo -c -o run_unittests-master_lexer_unittest.obj `if test -f 'master_lexer_unittest.cc'; then $(CYGPATH_W) 'master_lexer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_unittest.cc' object='run_unittests-master_lexer_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_unittest.obj `if test -f 'master_lexer_unittest.cc'; then $(CYGPATH_W) 'master_lexer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_unittest.cc'; fi`
+
+run_unittests-master_loader_unittest.o: master_loader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_unittest.Tpo -c -o run_unittests-master_loader_unittest.o `test -f 'master_loader_unittest.cc' || echo '$(srcdir)/'`master_loader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_unittest.Tpo $(DEPDIR)/run_unittests-master_loader_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_unittest.cc' object='run_unittests-master_loader_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_unittest.o `test -f 'master_loader_unittest.cc' || echo '$(srcdir)/'`master_loader_unittest.cc
+
+run_unittests-master_loader_unittest.obj: master_loader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_unittest.Tpo -c -o run_unittests-master_loader_unittest.obj `if test -f 'master_loader_unittest.cc'; then $(CYGPATH_W) 'master_loader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_unittest.Tpo $(DEPDIR)/run_unittests-master_loader_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_unittest.cc' object='run_unittests-master_loader_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_unittest.obj `if test -f 'master_loader_unittest.cc'; then $(CYGPATH_W) 'master_loader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_unittest.cc'; fi`
+
+run_unittests-master_lexer_state_unittest.o: master_lexer_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_state_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo -c -o run_unittests-master_lexer_state_unittest.o `test -f 'master_lexer_state_unittest.cc' || echo '$(srcdir)/'`master_lexer_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_state_unittest.cc' object='run_unittests-master_lexer_state_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_state_unittest.o `test -f 'master_lexer_state_unittest.cc' || echo '$(srcdir)/'`master_lexer_state_unittest.cc
+
+run_unittests-master_lexer_state_unittest.obj: master_lexer_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_state_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo -c -o run_unittests-master_lexer_state_unittest.obj `if test -f 'master_lexer_state_unittest.cc'; then $(CYGPATH_W) 'master_lexer_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_state_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_state_unittest.cc' object='run_unittests-master_lexer_state_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_state_unittest.obj `if test -f 'master_lexer_state_unittest.cc'; then $(CYGPATH_W) 'master_lexer_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_state_unittest.cc'; fi`
+
+run_unittests-name_unittest.o: name_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-name_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-name_unittest.Tpo -c -o run_unittests-name_unittest.o `test -f 'name_unittest.cc' || echo '$(srcdir)/'`name_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-name_unittest.Tpo $(DEPDIR)/run_unittests-name_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='name_unittest.cc' object='run_unittests-name_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-name_unittest.o `test -f 'name_unittest.cc' || echo '$(srcdir)/'`name_unittest.cc
+
+run_unittests-name_unittest.obj: name_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-name_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-name_unittest.Tpo -c -o run_unittests-name_unittest.obj `if test -f 'name_unittest.cc'; then $(CYGPATH_W) 'name_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/name_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-name_unittest.Tpo $(DEPDIR)/run_unittests-name_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='name_unittest.cc' object='run_unittests-name_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-name_unittest.obj `if test -f 'name_unittest.cc'; then $(CYGPATH_W) 'name_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/name_unittest.cc'; fi`
+
+run_unittests-rrttl_unittest.o: rrttl_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrttl_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrttl_unittest.Tpo -c -o run_unittests-rrttl_unittest.o `test -f 'rrttl_unittest.cc' || echo '$(srcdir)/'`rrttl_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrttl_unittest.Tpo $(DEPDIR)/run_unittests-rrttl_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrttl_unittest.cc' object='run_unittests-rrttl_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrttl_unittest.o `test -f 'rrttl_unittest.cc' || echo '$(srcdir)/'`rrttl_unittest.cc
+
+run_unittests-rrttl_unittest.obj: rrttl_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrttl_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrttl_unittest.Tpo -c -o run_unittests-rrttl_unittest.obj `if test -f 'rrttl_unittest.cc'; then $(CYGPATH_W) 'rrttl_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrttl_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrttl_unittest.Tpo $(DEPDIR)/run_unittests-rrttl_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrttl_unittest.cc' object='run_unittests-rrttl_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrttl_unittest.obj `if test -f 'rrttl_unittest.cc'; then $(CYGPATH_W) 'rrttl_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrttl_unittest.cc'; fi`
+
+run_unittests-rrclass_unittest.o: rrclass_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrclass_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrclass_unittest.Tpo -c -o run_unittests-rrclass_unittest.o `test -f 'rrclass_unittest.cc' || echo '$(srcdir)/'`rrclass_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrclass_unittest.Tpo $(DEPDIR)/run_unittests-rrclass_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrclass_unittest.cc' object='run_unittests-rrclass_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrclass_unittest.o `test -f 'rrclass_unittest.cc' || echo '$(srcdir)/'`rrclass_unittest.cc
+
+run_unittests-rrclass_unittest.obj: rrclass_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrclass_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrclass_unittest.Tpo -c -o run_unittests-rrclass_unittest.obj `if test -f 'rrclass_unittest.cc'; then $(CYGPATH_W) 'rrclass_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrclass_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrclass_unittest.Tpo $(DEPDIR)/run_unittests-rrclass_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrclass_unittest.cc' object='run_unittests-rrclass_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrclass_unittest.obj `if test -f 'rrclass_unittest.cc'; then $(CYGPATH_W) 'rrclass_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrclass_unittest.cc'; fi`
+
+run_unittests-rrtype_unittest.o: rrtype_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrtype_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrtype_unittest.Tpo -c -o run_unittests-rrtype_unittest.o `test -f 'rrtype_unittest.cc' || echo '$(srcdir)/'`rrtype_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrtype_unittest.Tpo $(DEPDIR)/run_unittests-rrtype_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrtype_unittest.cc' object='run_unittests-rrtype_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrtype_unittest.o `test -f 'rrtype_unittest.cc' || echo '$(srcdir)/'`rrtype_unittest.cc
+
+run_unittests-rrtype_unittest.obj: rrtype_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrtype_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrtype_unittest.Tpo -c -o run_unittests-rrtype_unittest.obj `if test -f 'rrtype_unittest.cc'; then $(CYGPATH_W) 'rrtype_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrtype_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrtype_unittest.Tpo $(DEPDIR)/run_unittests-rrtype_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrtype_unittest.cc' object='run_unittests-rrtype_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrtype_unittest.obj `if test -f 'rrtype_unittest.cc'; then $(CYGPATH_W) 'rrtype_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrtype_unittest.cc'; fi`
+
+run_unittests-opcode_unittest.o: opcode_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-opcode_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-opcode_unittest.Tpo -c -o run_unittests-opcode_unittest.o `test -f 'opcode_unittest.cc' || echo '$(srcdir)/'`opcode_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-opcode_unittest.Tpo $(DEPDIR)/run_unittests-opcode_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opcode_unittest.cc' object='run_unittests-opcode_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-opcode_unittest.o `test -f 'opcode_unittest.cc' || echo '$(srcdir)/'`opcode_unittest.cc
+
+run_unittests-opcode_unittest.obj: opcode_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-opcode_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-opcode_unittest.Tpo -c -o run_unittests-opcode_unittest.obj `if test -f 'opcode_unittest.cc'; then $(CYGPATH_W) 'opcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opcode_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-opcode_unittest.Tpo $(DEPDIR)/run_unittests-opcode_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opcode_unittest.cc' object='run_unittests-opcode_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-opcode_unittest.obj `if test -f 'opcode_unittest.cc'; then $(CYGPATH_W) 'opcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opcode_unittest.cc'; fi`
+
+run_unittests-rcode_unittest.o: rcode_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rcode_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rcode_unittest.Tpo -c -o run_unittests-rcode_unittest.o `test -f 'rcode_unittest.cc' || echo '$(srcdir)/'`rcode_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rcode_unittest.Tpo $(DEPDIR)/run_unittests-rcode_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rcode_unittest.cc' object='run_unittests-rcode_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rcode_unittest.o `test -f 'rcode_unittest.cc' || echo '$(srcdir)/'`rcode_unittest.cc
+
+run_unittests-rcode_unittest.obj: rcode_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rcode_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rcode_unittest.Tpo -c -o run_unittests-rcode_unittest.obj `if test -f 'rcode_unittest.cc'; then $(CYGPATH_W) 'rcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rcode_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rcode_unittest.Tpo $(DEPDIR)/run_unittests-rcode_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rcode_unittest.cc' object='run_unittests-rcode_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rcode_unittest.obj `if test -f 'rcode_unittest.cc'; then $(CYGPATH_W) 'rcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rcode_unittest.cc'; fi`
+
+run_unittests-rdata_unittest.o: rdata_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_unittest.Tpo -c -o run_unittests-rdata_unittest.o `test -f 'rdata_unittest.cc' || echo '$(srcdir)/'`rdata_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_unittest.Tpo $(DEPDIR)/run_unittests-rdata_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_unittest.cc' object='run_unittests-rdata_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_unittest.o `test -f 'rdata_unittest.cc' || echo '$(srcdir)/'`rdata_unittest.cc
+
+run_unittests-rdata_unittest.obj: rdata_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_unittest.Tpo -c -o run_unittests-rdata_unittest.obj `if test -f 'rdata_unittest.cc'; then $(CYGPATH_W) 'rdata_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_unittest.Tpo $(DEPDIR)/run_unittests-rdata_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_unittest.cc' object='run_unittests-rdata_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_unittest.obj `if test -f 'rdata_unittest.cc'; then $(CYGPATH_W) 'rdata_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_unittest.cc'; fi`
+
+run_unittests-rdata_char_string_unittest.o: rdata_char_string_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo -c -o run_unittests-rdata_char_string_unittest.o `test -f 'rdata_char_string_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_unittest.cc' object='run_unittests-rdata_char_string_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_unittest.o `test -f 'rdata_char_string_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_unittest.cc
+
+run_unittests-rdata_char_string_unittest.obj: rdata_char_string_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo -c -o run_unittests-rdata_char_string_unittest.obj `if test -f 'rdata_char_string_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_unittest.cc' object='run_unittests-rdata_char_string_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_unittest.obj `if test -f 'rdata_char_string_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_unittest.cc'; fi`
+
+run_unittests-rdata_char_string_data_unittest.o: rdata_char_string_data_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_data_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo -c -o run_unittests-rdata_char_string_data_unittest.o `test -f 'rdata_char_string_data_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_data_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_data_unittest.cc' object='run_unittests-rdata_char_string_data_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_data_unittest.o `test -f 'rdata_char_string_data_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_data_unittest.cc
+
+run_unittests-rdata_char_string_data_unittest.obj: rdata_char_string_data_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_data_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo -c -o run_unittests-rdata_char_string_data_unittest.obj `if test -f 'rdata_char_string_data_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_data_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_data_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_data_unittest.cc' object='run_unittests-rdata_char_string_data_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_data_unittest.obj `if test -f 'rdata_char_string_data_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_data_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_data_unittest.cc'; fi`
+
+run_unittests-rdata_in_a_unittest.o: rdata_in_a_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_a_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo -c -o run_unittests-rdata_in_a_unittest.o `test -f 'rdata_in_a_unittest.cc' || echo '$(srcdir)/'`rdata_in_a_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_a_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_a_unittest.cc' object='run_unittests-rdata_in_a_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_a_unittest.o `test -f 'rdata_in_a_unittest.cc' || echo '$(srcdir)/'`rdata_in_a_unittest.cc
+
+run_unittests-rdata_in_a_unittest.obj: rdata_in_a_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_a_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo -c -o run_unittests-rdata_in_a_unittest.obj `if test -f 'rdata_in_a_unittest.cc'; then $(CYGPATH_W) 'rdata_in_a_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_a_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_a_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_a_unittest.cc' object='run_unittests-rdata_in_a_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_a_unittest.obj `if test -f 'rdata_in_a_unittest.cc'; then $(CYGPATH_W) 'rdata_in_a_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_a_unittest.cc'; fi`
+
+run_unittests-rdata_in_aaaa_unittest.o: rdata_in_aaaa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_aaaa_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo -c -o run_unittests-rdata_in_aaaa_unittest.o `test -f 'rdata_in_aaaa_unittest.cc' || echo '$(srcdir)/'`rdata_in_aaaa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_aaaa_unittest.cc' object='run_unittests-rdata_in_aaaa_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_aaaa_unittest.o `test -f 'rdata_in_aaaa_unittest.cc' || echo '$(srcdir)/'`rdata_in_aaaa_unittest.cc
+
+run_unittests-rdata_in_aaaa_unittest.obj: rdata_in_aaaa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_aaaa_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo -c -o run_unittests-rdata_in_aaaa_unittest.obj `if test -f 'rdata_in_aaaa_unittest.cc'; then $(CYGPATH_W) 'rdata_in_aaaa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_aaaa_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_aaaa_unittest.cc' object='run_unittests-rdata_in_aaaa_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_aaaa_unittest.obj `if test -f 'rdata_in_aaaa_unittest.cc'; then $(CYGPATH_W) 'rdata_in_aaaa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_aaaa_unittest.cc'; fi`
+
+run_unittests-rdata_ns_unittest.o: rdata_ns_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ns_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo -c -o run_unittests-rdata_ns_unittest.o `test -f 'rdata_ns_unittest.cc' || echo '$(srcdir)/'`rdata_ns_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ns_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ns_unittest.cc' object='run_unittests-rdata_ns_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ns_unittest.o `test -f 'rdata_ns_unittest.cc' || echo '$(srcdir)/'`rdata_ns_unittest.cc
+
+run_unittests-rdata_ns_unittest.obj: rdata_ns_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ns_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo -c -o run_unittests-rdata_ns_unittest.obj `if test -f 'rdata_ns_unittest.cc'; then $(CYGPATH_W) 'rdata_ns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ns_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ns_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ns_unittest.cc' object='run_unittests-rdata_ns_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ns_unittest.obj `if test -f 'rdata_ns_unittest.cc'; then $(CYGPATH_W) 'rdata_ns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ns_unittest.cc'; fi`
+
+run_unittests-rdata_soa_unittest.o: rdata_soa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_soa_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo -c -o run_unittests-rdata_soa_unittest.o `test -f 'rdata_soa_unittest.cc' || echo '$(srcdir)/'`rdata_soa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_soa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_soa_unittest.cc' object='run_unittests-rdata_soa_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_soa_unittest.o `test -f 'rdata_soa_unittest.cc' || echo '$(srcdir)/'`rdata_soa_unittest.cc
+
+run_unittests-rdata_soa_unittest.obj: rdata_soa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_soa_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo -c -o run_unittests-rdata_soa_unittest.obj `if test -f 'rdata_soa_unittest.cc'; then $(CYGPATH_W) 'rdata_soa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_soa_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_soa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_soa_unittest.cc' object='run_unittests-rdata_soa_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_soa_unittest.obj `if test -f 'rdata_soa_unittest.cc'; then $(CYGPATH_W) 'rdata_soa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_soa_unittest.cc'; fi`
+
+run_unittests-rdata_txt_like_unittest.o: rdata_txt_like_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_txt_like_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo -c -o run_unittests-rdata_txt_like_unittest.o `test -f 'rdata_txt_like_unittest.cc' || echo '$(srcdir)/'`rdata_txt_like_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_txt_like_unittest.cc' object='run_unittests-rdata_txt_like_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_txt_like_unittest.o `test -f 'rdata_txt_like_unittest.cc' || echo '$(srcdir)/'`rdata_txt_like_unittest.cc
+
+run_unittests-rdata_txt_like_unittest.obj: rdata_txt_like_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_txt_like_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo -c -o run_unittests-rdata_txt_like_unittest.obj `if test -f 'rdata_txt_like_unittest.cc'; then $(CYGPATH_W) 'rdata_txt_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_txt_like_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_txt_like_unittest.cc' object='run_unittests-rdata_txt_like_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_txt_like_unittest.obj `if test -f 'rdata_txt_like_unittest.cc'; then $(CYGPATH_W) 'rdata_txt_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_txt_like_unittest.cc'; fi`
+
+run_unittests-rdata_ptr_unittest.o: rdata_ptr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ptr_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo -c -o run_unittests-rdata_ptr_unittest.o `test -f 'rdata_ptr_unittest.cc' || echo '$(srcdir)/'`rdata_ptr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ptr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ptr_unittest.cc' object='run_unittests-rdata_ptr_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ptr_unittest.o `test -f 'rdata_ptr_unittest.cc' || echo '$(srcdir)/'`rdata_ptr_unittest.cc
+
+run_unittests-rdata_ptr_unittest.obj: rdata_ptr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ptr_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo -c -o run_unittests-rdata_ptr_unittest.obj `if test -f 'rdata_ptr_unittest.cc'; then $(CYGPATH_W) 'rdata_ptr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ptr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ptr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ptr_unittest.cc' object='run_unittests-rdata_ptr_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ptr_unittest.obj `if test -f 'rdata_ptr_unittest.cc'; then $(CYGPATH_W) 'rdata_ptr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ptr_unittest.cc'; fi`
+
+run_unittests-rdata_opt_unittest.o: rdata_opt_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_opt_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo -c -o run_unittests-rdata_opt_unittest.o `test -f 'rdata_opt_unittest.cc' || echo '$(srcdir)/'`rdata_opt_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo $(DEPDIR)/run_unittests-rdata_opt_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_opt_unittest.cc' object='run_unittests-rdata_opt_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_opt_unittest.o `test -f 'rdata_opt_unittest.cc' || echo '$(srcdir)/'`rdata_opt_unittest.cc
+
+run_unittests-rdata_opt_unittest.obj: rdata_opt_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_opt_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo -c -o run_unittests-rdata_opt_unittest.obj `if test -f 'rdata_opt_unittest.cc'; then $(CYGPATH_W) 'rdata_opt_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_opt_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo $(DEPDIR)/run_unittests-rdata_opt_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_opt_unittest.cc' object='run_unittests-rdata_opt_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_opt_unittest.obj `if test -f 'rdata_opt_unittest.cc'; then $(CYGPATH_W) 'rdata_opt_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_opt_unittest.cc'; fi`
+
+run_unittests-rdata_dhcid_unittest.o: rdata_dhcid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dhcid_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo -c -o run_unittests-rdata_dhcid_unittest.o `test -f 'rdata_dhcid_unittest.cc' || echo '$(srcdir)/'`rdata_dhcid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dhcid_unittest.cc' object='run_unittests-rdata_dhcid_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_dhcid_unittest.o `test -f 'rdata_dhcid_unittest.cc' || echo '$(srcdir)/'`rdata_dhcid_unittest.cc
+
+run_unittests-rdata_dhcid_unittest.obj: rdata_dhcid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dhcid_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo -c -o run_unittests-rdata_dhcid_unittest.obj `if test -f 'rdata_dhcid_unittest.cc'; then $(CYGPATH_W) 'rdata_dhcid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dhcid_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dhcid_unittest.cc' object='run_unittests-rdata_dhcid_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_dhcid_unittest.obj `if test -f 'rdata_dhcid_unittest.cc'; then $(CYGPATH_W) 'rdata_dhcid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dhcid_unittest.cc'; fi`
+
+run_unittests-rdata_rrsig_unittest.o: rdata_rrsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_rrsig_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo -c -o run_unittests-rdata_rrsig_unittest.o `test -f 'rdata_rrsig_unittest.cc' || echo '$(srcdir)/'`rdata_rrsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_rrsig_unittest.cc' object='run_unittests-rdata_rrsig_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_rrsig_unittest.o `test -f 'rdata_rrsig_unittest.cc' || echo '$(srcdir)/'`rdata_rrsig_unittest.cc
+
+run_unittests-rdata_rrsig_unittest.obj: rdata_rrsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_rrsig_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo -c -o run_unittests-rdata_rrsig_unittest.obj `if test -f 'rdata_rrsig_unittest.cc'; then $(CYGPATH_W) 'rdata_rrsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_rrsig_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_rrsig_unittest.cc' object='run_unittests-rdata_rrsig_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_rrsig_unittest.obj `if test -f 'rdata_rrsig_unittest.cc'; then $(CYGPATH_W) 'rdata_rrsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_rrsig_unittest.cc'; fi`
+
+run_unittests-rdata_tsig_unittest.o: rdata_tsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tsig_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo -c -o run_unittests-rdata_tsig_unittest.o `test -f 'rdata_tsig_unittest.cc' || echo '$(srcdir)/'`rdata_tsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tsig_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tsig_unittest.cc' object='run_unittests-rdata_tsig_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tsig_unittest.o `test -f 'rdata_tsig_unittest.cc' || echo '$(srcdir)/'`rdata_tsig_unittest.cc
+
+run_unittests-rdata_tsig_unittest.obj: rdata_tsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tsig_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo -c -o run_unittests-rdata_tsig_unittest.obj `if test -f 'rdata_tsig_unittest.cc'; then $(CYGPATH_W) 'rdata_tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tsig_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tsig_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tsig_unittest.cc' object='run_unittests-rdata_tsig_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tsig_unittest.obj `if test -f 'rdata_tsig_unittest.cc'; then $(CYGPATH_W) 'rdata_tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tsig_unittest.cc'; fi`
+
+run_unittests-rdata_tkey_unittest.o: rdata_tkey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tkey_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo -c -o run_unittests-rdata_tkey_unittest.o `test -f 'rdata_tkey_unittest.cc' || echo '$(srcdir)/'`rdata_tkey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tkey_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tkey_unittest.cc' object='run_unittests-rdata_tkey_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tkey_unittest.o `test -f 'rdata_tkey_unittest.cc' || echo '$(srcdir)/'`rdata_tkey_unittest.cc
+
+run_unittests-rdata_tkey_unittest.obj: rdata_tkey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tkey_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo -c -o run_unittests-rdata_tkey_unittest.obj `if test -f 'rdata_tkey_unittest.cc'; then $(CYGPATH_W) 'rdata_tkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tkey_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tkey_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tkey_unittest.cc' object='run_unittests-rdata_tkey_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tkey_unittest.obj `if test -f 'rdata_tkey_unittest.cc'; then $(CYGPATH_W) 'rdata_tkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tkey_unittest.cc'; fi`
+
+run_unittests-rrset_unittest.o: rrset_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrset_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrset_unittest.Tpo -c -o run_unittests-rrset_unittest.o `test -f 'rrset_unittest.cc' || echo '$(srcdir)/'`rrset_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrset_unittest.Tpo $(DEPDIR)/run_unittests-rrset_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_unittest.cc' object='run_unittests-rrset_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrset_unittest.o `test -f 'rrset_unittest.cc' || echo '$(srcdir)/'`rrset_unittest.cc
+
+run_unittests-rrset_unittest.obj: rrset_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrset_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrset_unittest.Tpo -c -o run_unittests-rrset_unittest.obj `if test -f 'rrset_unittest.cc'; then $(CYGPATH_W) 'rrset_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrset_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrset_unittest.Tpo $(DEPDIR)/run_unittests-rrset_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_unittest.cc' object='run_unittests-rrset_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrset_unittest.obj `if test -f 'rrset_unittest.cc'; then $(CYGPATH_W) 'rrset_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrset_unittest.cc'; fi`
+
+run_unittests-question_unittest.o: question_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-question_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-question_unittest.Tpo -c -o run_unittests-question_unittest.o `test -f 'question_unittest.cc' || echo '$(srcdir)/'`question_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-question_unittest.Tpo $(DEPDIR)/run_unittests-question_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='question_unittest.cc' object='run_unittests-question_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-question_unittest.o `test -f 'question_unittest.cc' || echo '$(srcdir)/'`question_unittest.cc
+
+run_unittests-question_unittest.obj: question_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-question_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-question_unittest.Tpo -c -o run_unittests-question_unittest.obj `if test -f 'question_unittest.cc'; then $(CYGPATH_W) 'question_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/question_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-question_unittest.Tpo $(DEPDIR)/run_unittests-question_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='question_unittest.cc' object='run_unittests-question_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-question_unittest.obj `if test -f 'question_unittest.cc'; then $(CYGPATH_W) 'question_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/question_unittest.cc'; fi`
+
+run_unittests-rrparamregistry_unittest.o: rrparamregistry_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrparamregistry_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo -c -o run_unittests-rrparamregistry_unittest.o `test -f 'rrparamregistry_unittest.cc' || echo '$(srcdir)/'`rrparamregistry_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo $(DEPDIR)/run_unittests-rrparamregistry_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrparamregistry_unittest.cc' object='run_unittests-rrparamregistry_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrparamregistry_unittest.o `test -f 'rrparamregistry_unittest.cc' || echo '$(srcdir)/'`rrparamregistry_unittest.cc
+
+run_unittests-rrparamregistry_unittest.obj: rrparamregistry_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrparamregistry_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo -c -o run_unittests-rrparamregistry_unittest.obj `if test -f 'rrparamregistry_unittest.cc'; then $(CYGPATH_W) 'rrparamregistry_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrparamregistry_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo $(DEPDIR)/run_unittests-rrparamregistry_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrparamregistry_unittest.cc' object='run_unittests-rrparamregistry_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrparamregistry_unittest.obj `if test -f 'rrparamregistry_unittest.cc'; then $(CYGPATH_W) 'rrparamregistry_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrparamregistry_unittest.cc'; fi`
+
+run_unittests-message_unittest.o: message_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-message_unittest.Tpo -c -o run_unittests-message_unittest.o `test -f 'message_unittest.cc' || echo '$(srcdir)/'`message_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_unittest.Tpo $(DEPDIR)/run_unittests-message_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_unittest.cc' object='run_unittests-message_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_unittest.o `test -f 'message_unittest.cc' || echo '$(srcdir)/'`message_unittest.cc
+
+run_unittests-message_unittest.obj: message_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-message_unittest.Tpo -c -o run_unittests-message_unittest.obj `if test -f 'message_unittest.cc'; then $(CYGPATH_W) 'message_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_unittest.Tpo $(DEPDIR)/run_unittests-message_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_unittest.cc' object='run_unittests-message_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_unittest.obj `if test -f 'message_unittest.cc'; then $(CYGPATH_W) 'message_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_unittest.cc'; fi`
+
+run_unittests-serial_unittest.o: serial_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-serial_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-serial_unittest.Tpo -c -o run_unittests-serial_unittest.o `test -f 'serial_unittest.cc' || echo '$(srcdir)/'`serial_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-serial_unittest.Tpo $(DEPDIR)/run_unittests-serial_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='serial_unittest.cc' object='run_unittests-serial_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-serial_unittest.o `test -f 'serial_unittest.cc' || echo '$(srcdir)/'`serial_unittest.cc
+
+run_unittests-serial_unittest.obj: serial_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-serial_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-serial_unittest.Tpo -c -o run_unittests-serial_unittest.obj `if test -f 'serial_unittest.cc'; then $(CYGPATH_W) 'serial_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/serial_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-serial_unittest.Tpo $(DEPDIR)/run_unittests-serial_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='serial_unittest.cc' object='run_unittests-serial_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-serial_unittest.obj `if test -f 'serial_unittest.cc'; then $(CYGPATH_W) 'serial_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/serial_unittest.cc'; fi`
+
+run_unittests-time_utils_unittest.o: time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utils_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-time_utils_unittest.Tpo -c -o run_unittests-time_utils_unittest.o `test -f 'time_utils_unittest.cc' || echo '$(srcdir)/'`time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utils_unittest.Tpo $(DEPDIR)/run_unittests-time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utils_unittest.cc' object='run_unittests-time_utils_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-time_utils_unittest.o `test -f 'time_utils_unittest.cc' || echo '$(srcdir)/'`time_utils_unittest.cc
+
+run_unittests-time_utils_unittest.obj: time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utils_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-time_utils_unittest.Tpo -c -o run_unittests-time_utils_unittest.obj `if test -f 'time_utils_unittest.cc'; then $(CYGPATH_W) 'time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utils_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utils_unittest.Tpo $(DEPDIR)/run_unittests-time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utils_unittest.cc' object='run_unittests-time_utils_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-time_utils_unittest.obj `if test -f 'time_utils_unittest.cc'; then $(CYGPATH_W) 'time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utils_unittest.cc'; fi`
+
+run_unittests-tsig_unittest.o: tsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsig_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsig_unittest.Tpo -c -o run_unittests-tsig_unittest.o `test -f 'tsig_unittest.cc' || echo '$(srcdir)/'`tsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsig_unittest.Tpo $(DEPDIR)/run_unittests-tsig_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsig_unittest.cc' object='run_unittests-tsig_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsig_unittest.o `test -f 'tsig_unittest.cc' || echo '$(srcdir)/'`tsig_unittest.cc
+
+run_unittests-tsig_unittest.obj: tsig_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsig_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsig_unittest.Tpo -c -o run_unittests-tsig_unittest.obj `if test -f 'tsig_unittest.cc'; then $(CYGPATH_W) 'tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsig_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsig_unittest.Tpo $(DEPDIR)/run_unittests-tsig_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsig_unittest.cc' object='run_unittests-tsig_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsig_unittest.obj `if test -f 'tsig_unittest.cc'; then $(CYGPATH_W) 'tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsig_unittest.cc'; fi`
+
+run_unittests-tsigerror_unittest.o: tsigerror_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigerror_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo -c -o run_unittests-tsigerror_unittest.o `test -f 'tsigerror_unittest.cc' || echo '$(srcdir)/'`tsigerror_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo $(DEPDIR)/run_unittests-tsigerror_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigerror_unittest.cc' object='run_unittests-tsigerror_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigerror_unittest.o `test -f 'tsigerror_unittest.cc' || echo '$(srcdir)/'`tsigerror_unittest.cc
+
+run_unittests-tsigerror_unittest.obj: tsigerror_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigerror_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo -c -o run_unittests-tsigerror_unittest.obj `if test -f 'tsigerror_unittest.cc'; then $(CYGPATH_W) 'tsigerror_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigerror_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo $(DEPDIR)/run_unittests-tsigerror_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigerror_unittest.cc' object='run_unittests-tsigerror_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigerror_unittest.obj `if test -f 'tsigerror_unittest.cc'; then $(CYGPATH_W) 'tsigerror_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigerror_unittest.cc'; fi`
+
+run_unittests-tsigkey_unittest.o: tsigkey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigkey_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo -c -o run_unittests-tsigkey_unittest.o `test -f 'tsigkey_unittest.cc' || echo '$(srcdir)/'`tsigkey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo $(DEPDIR)/run_unittests-tsigkey_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigkey_unittest.cc' object='run_unittests-tsigkey_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigkey_unittest.o `test -f 'tsigkey_unittest.cc' || echo '$(srcdir)/'`tsigkey_unittest.cc
+
+run_unittests-tsigkey_unittest.obj: tsigkey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigkey_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo -c -o run_unittests-tsigkey_unittest.obj `if test -f 'tsigkey_unittest.cc'; then $(CYGPATH_W) 'tsigkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigkey_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo $(DEPDIR)/run_unittests-tsigkey_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigkey_unittest.cc' object='run_unittests-tsigkey_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigkey_unittest.obj `if test -f 'tsigkey_unittest.cc'; then $(CYGPATH_W) 'tsigkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigkey_unittest.cc'; fi`
+
+run_unittests-tsigrecord_unittest.o: tsigrecord_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigrecord_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo -c -o run_unittests-tsigrecord_unittest.o `test -f 'tsigrecord_unittest.cc' || echo '$(srcdir)/'`tsigrecord_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo $(DEPDIR)/run_unittests-tsigrecord_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigrecord_unittest.cc' object='run_unittests-tsigrecord_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigrecord_unittest.o `test -f 'tsigrecord_unittest.cc' || echo '$(srcdir)/'`tsigrecord_unittest.cc
+
+run_unittests-tsigrecord_unittest.obj: tsigrecord_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigrecord_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo -c -o run_unittests-tsigrecord_unittest.obj `if test -f 'tsigrecord_unittest.cc'; then $(CYGPATH_W) 'tsigrecord_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigrecord_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo $(DEPDIR)/run_unittests-tsigrecord_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigrecord_unittest.cc' object='run_unittests-tsigrecord_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigrecord_unittest.obj `if test -f 'tsigrecord_unittest.cc'; then $(CYGPATH_W) 'tsigrecord_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigrecord_unittest.cc'; fi`
+
+run_unittests-master_loader_callbacks_test.o: master_loader_callbacks_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_callbacks_test.o -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo -c -o run_unittests-master_loader_callbacks_test.o `test -f 'master_loader_callbacks_test.cc' || echo '$(srcdir)/'`master_loader_callbacks_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo $(DEPDIR)/run_unittests-master_loader_callbacks_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_callbacks_test.cc' object='run_unittests-master_loader_callbacks_test.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_callbacks_test.o `test -f 'master_loader_callbacks_test.cc' || echo '$(srcdir)/'`master_loader_callbacks_test.cc
+
+run_unittests-master_loader_callbacks_test.obj: master_loader_callbacks_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_callbacks_test.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo -c -o run_unittests-master_loader_callbacks_test.obj `if test -f 'master_loader_callbacks_test.cc'; then $(CYGPATH_W) 'master_loader_callbacks_test.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_callbacks_test.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo $(DEPDIR)/run_unittests-master_loader_callbacks_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_callbacks_test.cc' object='run_unittests-master_loader_callbacks_test.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_callbacks_test.obj `if test -f 'master_loader_callbacks_test.cc'; then $(CYGPATH_W) 'master_loader_callbacks_test.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_callbacks_test.cc'; fi`
+
+run_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+run_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-edns_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-labelsequence_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_loader_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-message_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-name_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-opcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-question_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrclass_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrset_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrttl_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrtype_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-serial_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsig_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsigerror_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsigkey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unittest_util.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-edns_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-labelsequence_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_lexer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po
+ -rm -f ./$(DEPDIR)/run_unittests-master_loader_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-message_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-name_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-opcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-question_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrclass_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrset_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrttl_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrtype_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-serial_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsig_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsigerror_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsigkey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unittest_util.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/dns/tests/dns_exceptions_unittest.cc b/src/lib/dns/tests/dns_exceptions_unittest.cc
new file mode 100644
index 0000000..0e9fc62
--- /dev/null
+++ b/src/lib/dns/tests/dns_exceptions_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/exceptions.h>
+
+#include <gtest/gtest.h>
+
+namespace { // begin unnamed namespace
+
+TEST(DNSExceptionsTest, checkExceptionsHierarchy) {
+ EXPECT_NO_THROW({
+ const isc::dns::Exception exception("", 0, "");
+ const isc::Exception& exception_cast =
+ dynamic_cast<const isc::Exception&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::DNSTextError exception("", 0, "");
+ const isc::dns::Exception& exception_cast =
+ dynamic_cast<const isc::dns::Exception&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::NameParserException exception("", 0, "");
+ const isc::dns::DNSTextError& exception_cast =
+ dynamic_cast<const isc::dns::DNSTextError&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::DNSMessageFORMERR exception("", 0, "");
+ const isc::dns::DNSProtocolError& exception_cast =
+ dynamic_cast<const isc::dns::DNSProtocolError&>(exception);
+ const isc::dns::Exception& exception_cast2 =
+ dynamic_cast<const isc::dns::Exception&>(exception);
+ // to avoid compiler warning
+ exception_cast.getRcode();
+ exception_cast.what();
+ exception_cast2.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::DNSMessageBADVERS exception("", 0, "");
+ const isc::dns::DNSProtocolError& exception_cast =
+ dynamic_cast<const isc::dns::DNSProtocolError&>(exception);
+ const isc::dns::Exception& exception_cast2 =
+ dynamic_cast<const isc::dns::Exception&>(exception);
+ // to avoid compiler warning
+ exception_cast.getRcode();
+ exception_cast.what();
+ exception_cast2.what();
+ });
+}
+
+} // end unnamed namespace
diff --git a/src/lib/dns/tests/edns_unittest.cc b/src/lib/dns/tests/edns_unittest.cc
new file mode 100644
index 0000000..239c2eb
--- /dev/null
+++ b/src/lib/dns/tests/edns_unittest.cc
@@ -0,0 +1,258 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <sstream>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+const uint8_t EDNS::SUPPORTED_VERSION;
+
+namespace {
+class EDNSTest : public ::testing::Test {
+protected:
+ EDNSTest() : rrtype(RRType::OPT()), buffer(0, 0), obuffer(0), rcode(0) {
+ opt_rdata = ConstRdataPtr(new generic::OPT());
+ edns_base.setUDPSize(4096);
+ }
+ RRType rrtype;
+ EDNS edns_base;
+ ConstEDNSPtr edns;
+ InputBuffer buffer;
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+ ConstRdataPtr opt_rdata;
+ Rcode rcode;
+ vector<unsigned char> wiredata;
+};
+
+// RRClass of EDNS OPT means UDP buffer size
+const RRClass rrclass(4096);
+// RRTTL of EDNS OPT encodes extended-rcode, version, and DO bit
+const RRTTL rrttl_do_on(0x00008000); // DO=on
+const RRTTL rrttl_do_off(0); // DO=off
+const RRTTL rrttl_badver(0x00018000); // version=1, DO=on
+
+TEST_F(EDNSTest, badVerConstruct) {
+ EXPECT_THROW(EDNS(1), isc::InvalidParameter);
+}
+
+TEST_F(EDNSTest, DNSSECDOBit) {
+ // tests for EDNS from RR
+
+ // DO bit is on, so DNSSEC should be considered to be supported.
+ EXPECT_TRUE(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+ rrttl_do_on, *opt_rdata).getDNSSECAwareness());
+
+ // DO bit is off. DNSSEC should be considered to be unsupported.
+ EXPECT_FALSE(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+ rrttl_do_off, *opt_rdata).getDNSSECAwareness());
+
+ // tests for EDNS constructed by hand
+ EXPECT_FALSE(edns_base.getDNSSECAwareness()); // false by default
+ edns_base.setDNSSECAwareness(true); // enable by hand
+ EXPECT_TRUE(edns_base.getDNSSECAwareness());
+ edns_base.setDNSSECAwareness(false); // disable by hand
+ EXPECT_FALSE(edns_base.getDNSSECAwareness());
+}
+
+TEST_F(EDNSTest, UDPSize) {
+ EXPECT_EQ(4096, EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on,
+ *opt_rdata).getUDPSize());
+
+ // EDNS UDP size smaller than the traditional max, 512. Unusual, but
+ // not prohibited.
+ edns_base.setUDPSize(511);
+ EXPECT_EQ(511, edns_base.getUDPSize());
+
+ // Even 0 is okay.
+ edns_base.setUDPSize(0);
+ EXPECT_EQ(0, edns_base.getUDPSize());
+
+ // Possible max value is also okay, although the higher layer app may
+ // adjust it to a reasonably lower value
+ edns_base.setUDPSize(65535);
+ EXPECT_EQ(65535, edns_base.getUDPSize());
+}
+
+TEST_F(EDNSTest, getVersion) {
+ // Constructed by hand
+ EXPECT_EQ(EDNS::SUPPORTED_VERSION, EDNS().getVersion());
+
+ // From RR
+ EXPECT_EQ(EDNS::SUPPORTED_VERSION,
+ EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on,
+ *opt_rdata).getVersion());
+}
+
+TEST_F(EDNSTest, BadWireData) {
+ // Incompatible RR type
+ EXPECT_THROW(EDNS(Name::ROOT_NAME(), rrclass, RRType::A(),
+ rrttl_do_on, *opt_rdata), isc::InvalidParameter);
+
+ // OPT RR of a non root name
+ EXPECT_THROW(EDNS(Name("example.com"), rrclass, rrtype,
+ rrttl_do_on, *opt_rdata), DNSMessageFORMERR);
+
+ // Unsupported Version
+ EXPECT_THROW(EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+ rrttl_badver, *opt_rdata), DNSMessageBADVERS);
+}
+
+TEST_F(EDNSTest, toText) {
+ // Typical case, disabling DNSSEC
+ EXPECT_EQ("; EDNS: version: 0, flags:; udp: 4096\n",
+ EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_off,
+ *opt_rdata).toText());
+
+ // Typical case, enabling DNSSEC
+ EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n",
+ EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on,
+ *opt_rdata).toText());
+
+ // Non-0 extended Rcode: ignored in the toText() output.
+ EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n",
+ EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+ RRTTL(0x01008000), *opt_rdata).toText());
+
+ // Unknown flag: ignored in the toText() output.
+ EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n",
+ EDNS(Name::ROOT_NAME(), rrclass, rrtype,
+ RRTTL(0x00008001), *opt_rdata).toText());
+}
+
+TEST_F(EDNSTest, toWireRenderer) {
+ // Typical case, (explicitly) disabling DNSSEC
+ edns_base.setDNSSECAwareness(false);
+ EXPECT_EQ(1, edns_base.toWire(renderer,
+ Rcode::NOERROR().getExtendedCode()));
+ UnitTestUtil::readWireData("edns_toWire1.wire", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ // Typical case, enabling DNSSEC
+ renderer.clear();
+ wiredata.clear();
+ edns_base.setDNSSECAwareness(true);
+ EXPECT_EQ(1, edns_base.toWire(renderer,
+ Rcode::NOERROR().getExtendedCode()));
+ UnitTestUtil::readWireData("edns_toWire2.wire", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ // Non-0 extended Rcode
+ renderer.clear();
+ wiredata.clear();
+ edns_base.setDNSSECAwareness(true);
+ EXPECT_EQ(1, edns_base.toWire(renderer,
+ Rcode::BADVERS().getExtendedCode()));
+ UnitTestUtil::readWireData("edns_toWire3.wire", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ // Uncommon UDP buffer size
+ renderer.clear();
+ wiredata.clear();
+ edns_base.setDNSSECAwareness(true);
+ edns_base.setUDPSize(511);
+ EXPECT_EQ(1, edns_base.toWire(renderer,
+ Rcode::NOERROR().getExtendedCode()));
+ UnitTestUtil::readWireData("edns_toWire4.wire", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ // From RR with unknown flag. If used for toWire(), the unknown flag
+ // should disappear.
+ renderer.clear();
+ wiredata.clear();
+ EXPECT_EQ(1, EDNS(Name::ROOT_NAME(), rrclass, rrtype, RRTTL(0x00008001),
+ *opt_rdata).toWire(renderer,
+ Rcode::NOERROR().getExtendedCode()));
+ UnitTestUtil::readWireData("edns_toWire2.wire", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ // If the available length in the renderer is not sufficient for the OPT
+ // RR, it shouldn't be inserted.
+ renderer.clear();
+ renderer.setLengthLimit(10); // 10 = minimum length of OPT RR - 1
+ edns_base.setDNSSECAwareness(true);
+ edns_base.setUDPSize(4096);
+ EXPECT_EQ(0, edns_base.toWire(renderer,
+ Rcode::NOERROR().getExtendedCode()));
+ // renderer should be intact
+ EXPECT_EQ(0, renderer.getLength());
+}
+
+TEST_F(EDNSTest, toWireBuffer) {
+ // "to renderer" and "to buffer" should generally produce the same result.
+ // for simplicity we only check one typical case to confirm that.
+ EXPECT_EQ(1, edns_base.toWire(obuffer,
+ Rcode::NOERROR().getExtendedCode()));
+ UnitTestUtil::readWireData("edns_toWire1.wire", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(EDNSTest, createFromRR) {
+ uint8_t extended_rcode;
+
+ // normal case
+ edns = ConstEDNSPtr(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype,
+ rrttl_do_on, *opt_rdata,
+ extended_rcode));
+ EXPECT_EQ(EDNS::SUPPORTED_VERSION, edns->getVersion());
+ EXPECT_TRUE(edns->getDNSSECAwareness());
+ EXPECT_EQ(4096, edns->getUDPSize());
+ EXPECT_EQ(0, static_cast<int>(extended_rcode));
+
+ // non-0 extended rcode
+ extended_rcode = 0;
+ ConstEDNSPtr(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype,
+ RRTTL(0x01008000), *opt_rdata,
+ extended_rcode));
+ EXPECT_EQ(1, static_cast<int>(extended_rcode));
+
+ // creation triggers an exception. extended_rcode must be intact.
+ extended_rcode = 0;
+ EXPECT_THROW(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype,
+ rrttl_badver, *opt_rdata, extended_rcode),
+ DNSMessageBADVERS);
+ EXPECT_EQ(0, static_cast<int>(extended_rcode));
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(EDNSTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << edns_base;
+ EXPECT_EQ(edns_base.toText(), oss.str());
+}
+}
diff --git a/src/lib/dns/tests/labelsequence_unittest.cc b/src/lib/dns/tests/labelsequence_unittest.cc
new file mode 100644
index 0000000..1e8f232
--- /dev/null
+++ b/src/lib/dns/tests/labelsequence_unittest.cc
@@ -0,0 +1,1242 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/buffer.h>
+
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/functional/hash.hpp>
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <set>
+
+using namespace isc::dns;
+using namespace std;
+
+// XXX: this is defined as class static constants, but some compilers
+// seemingly cannot find the symbols when used in the EXPECT_xxx macros.
+const size_t LabelSequence::MAX_SERIALIZED_LENGTH;
+
+namespace {
+
+// Common check that two labelsequences are equal
+void check_equal(const LabelSequence& ls1, const LabelSequence& ls2) {
+ NameComparisonResult result = ls1.compare(ls2);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation()) << ls1.toText() << " != " << ls2.toText();
+ EXPECT_EQ(0, result.getOrder()) << ls1.toText() << " != " << ls2.toText();
+ EXPECT_EQ(ls1.getLabelCount(), result.getCommonLabels());
+}
+
+// Common check for general comparison of two labelsequences
+void check_compare(const LabelSequence& ls1, const LabelSequence& ls2,
+ isc::dns::NameComparisonResult::NameRelation relation,
+ size_t common_labels, bool check_order, int order=0) {
+ NameComparisonResult result = ls1.compare(ls2);
+ EXPECT_EQ(relation, result.getRelation());
+ EXPECT_EQ(common_labels, result.getCommonLabels());
+ if (check_order) {
+ EXPECT_EQ(order, result.getOrder());
+ }
+}
+
+class LabelSequenceTest : public ::testing::Test {
+public:
+ LabelSequenceTest() : n1("example.org"), n2("example.com"),
+ n3("example.org"), n4("foo.bar.test.example"),
+ n5("example.ORG"), n6("ExAmPlE.org"),
+ n7("."), n8("foo.example.org.bar"),
+ n9("\\000xample.org"),
+ n10("\\000xample.org"),
+ n11("\\000xample.com"),
+ n12("\\000xamplE.com"),
+ n_maxlabel("0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6"),
+ ls1(n1), ls2(n2), ls3(n3), ls4(n4), ls5(n5),
+ ls6(n6), ls7(n7), ls8(n8),
+ ls9(n9), ls10(n10), ls11(n11), ls12(n12)
+ {};
+ // Need to keep names in scope for at least the lifetime of
+ // the labelsequences
+ Name n1, n2, n3, n4, n5, n6, n7, n8;
+ Name n9, n10, n11, n12;
+ const Name n_maxlabel;
+
+ LabelSequence ls1, ls2, ls3, ls4, ls5, ls6, ls7, ls8;
+ LabelSequence ls9, ls10, ls11, ls12;
+};
+
+// Check the assignment operator.
+TEST_F(LabelSequenceTest, assign) {
+ // Create the label sequence with example.org (n1 name).
+ LabelSequence ls(n1);
+ EXPECT_TRUE(ls == ls1);
+
+ // Assign the label sequence to example.com (n2 name).
+ ls = ls2;
+ EXPECT_FALSE(ls == ls1);
+ EXPECT_TRUE(ls == ls2);
+}
+
+// Basic equality tests
+TEST_F(LabelSequenceTest, equals_sensitive) {
+ EXPECT_TRUE(ls1.equals(ls1, true));
+ EXPECT_FALSE(ls1.equals(ls2, true));
+ EXPECT_TRUE(ls1.equals(ls3, true));
+ EXPECT_FALSE(ls1.equals(ls4, true));
+ EXPECT_FALSE(ls1.equals(ls5, true));
+ EXPECT_FALSE(ls1.equals(ls6, true));
+ EXPECT_FALSE(ls1.equals(ls7, true));
+ EXPECT_FALSE(ls1.equals(ls8, true));
+
+ EXPECT_FALSE(ls2.equals(ls1, true));
+ EXPECT_TRUE(ls2.equals(ls2, true));
+ EXPECT_FALSE(ls2.equals(ls3, true));
+ EXPECT_FALSE(ls2.equals(ls4, true));
+ EXPECT_FALSE(ls2.equals(ls5, true));
+ EXPECT_FALSE(ls2.equals(ls6, true));
+ EXPECT_FALSE(ls2.equals(ls7, true));
+ EXPECT_FALSE(ls2.equals(ls8, true));
+
+ EXPECT_FALSE(ls4.equals(ls1, true));
+ EXPECT_FALSE(ls4.equals(ls2, true));
+ EXPECT_FALSE(ls4.equals(ls3, true));
+ EXPECT_TRUE(ls4.equals(ls4, true));
+ EXPECT_FALSE(ls4.equals(ls5, true));
+ EXPECT_FALSE(ls4.equals(ls6, true));
+ EXPECT_FALSE(ls4.equals(ls7, true));
+ EXPECT_FALSE(ls4.equals(ls8, true));
+
+ EXPECT_FALSE(ls5.equals(ls1, true));
+ EXPECT_FALSE(ls5.equals(ls2, true));
+ EXPECT_FALSE(ls5.equals(ls3, true));
+ EXPECT_FALSE(ls5.equals(ls4, true));
+ EXPECT_TRUE(ls5.equals(ls5, true));
+ EXPECT_FALSE(ls5.equals(ls6, true));
+ EXPECT_FALSE(ls5.equals(ls7, true));
+ EXPECT_FALSE(ls5.equals(ls8, true));
+
+ EXPECT_TRUE(ls9.equals(ls10, true));
+ EXPECT_FALSE(ls9.equals(ls11, true));
+ EXPECT_FALSE(ls9.equals(ls12, true));
+ EXPECT_FALSE(ls11.equals(ls12, true));
+}
+
+TEST_F(LabelSequenceTest, equals_insensitive) {
+ EXPECT_TRUE(ls1.equals(ls1));
+ EXPECT_FALSE(ls1.equals(ls2));
+ EXPECT_TRUE(ls1.equals(ls3));
+ EXPECT_FALSE(ls1.equals(ls4));
+ EXPECT_TRUE(ls1.equals(ls5));
+ EXPECT_TRUE(ls1.equals(ls6));
+ EXPECT_FALSE(ls1.equals(ls7));
+
+ EXPECT_FALSE(ls2.equals(ls1));
+ EXPECT_TRUE(ls2.equals(ls2));
+ EXPECT_FALSE(ls2.equals(ls3));
+ EXPECT_FALSE(ls2.equals(ls4));
+ EXPECT_FALSE(ls2.equals(ls5));
+ EXPECT_FALSE(ls2.equals(ls6));
+ EXPECT_FALSE(ls2.equals(ls7));
+
+ EXPECT_TRUE(ls3.equals(ls1));
+ EXPECT_FALSE(ls3.equals(ls2));
+ EXPECT_TRUE(ls3.equals(ls3));
+ EXPECT_FALSE(ls3.equals(ls4));
+ EXPECT_TRUE(ls3.equals(ls5));
+ EXPECT_TRUE(ls3.equals(ls6));
+ EXPECT_FALSE(ls3.equals(ls7));
+
+ EXPECT_FALSE(ls4.equals(ls1));
+ EXPECT_FALSE(ls4.equals(ls2));
+ EXPECT_FALSE(ls4.equals(ls3));
+ EXPECT_TRUE(ls4.equals(ls4));
+ EXPECT_FALSE(ls4.equals(ls5));
+ EXPECT_FALSE(ls4.equals(ls6));
+ EXPECT_FALSE(ls4.equals(ls7));
+
+ EXPECT_TRUE(ls5.equals(ls1));
+ EXPECT_FALSE(ls5.equals(ls2));
+ EXPECT_TRUE(ls5.equals(ls3));
+ EXPECT_FALSE(ls5.equals(ls4));
+ EXPECT_TRUE(ls5.equals(ls5));
+ EXPECT_TRUE(ls5.equals(ls6));
+ EXPECT_FALSE(ls5.equals(ls7));
+
+ EXPECT_TRUE(ls9.equals(ls10));
+ EXPECT_FALSE(ls9.equals(ls11));
+ EXPECT_FALSE(ls9.equals(ls12));
+ EXPECT_TRUE(ls11.equals(ls12));
+}
+
+// operator==(). This is mostly trivial wrapper, so it should suffice to
+// check some basic cases.
+TEST_F(LabelSequenceTest, operatorEqual) {
+ // cppcheck-suppress duplicateExpression
+ EXPECT_TRUE(ls1 == ls1); // self equivalence
+ EXPECT_TRUE(ls1 == LabelSequence(n1)); // equivalent two different objects
+ EXPECT_FALSE(ls1 == ls2); // non equivalent objects
+ EXPECT_TRUE(ls1 == ls5); // it's always case insensitive
+}
+
+// Compare tests
+TEST_F(LabelSequenceTest, compare) {
+ // "example.org." and "example.org.", case sensitive
+ NameComparisonResult result = ls1.compare(ls3, true);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation());
+ EXPECT_EQ(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ // "example.org." and "example.ORG.", case sensitive
+ result = ls3.compare(ls5, true);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(1, result.getCommonLabels());
+
+ // "example.org." and "example.ORG.", case in-sensitive
+ result = ls3.compare(ls5);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation());
+ EXPECT_EQ(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ Name na("a.example.org");
+ Name nb("b.example.org");
+ LabelSequence lsa(na);
+ LabelSequence lsb(nb);
+
+ // "a.example.org." and "b.example.org.", case in-sensitive
+ result = lsa.compare(lsb);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ // "example.org." and "b.example.org.", case in-sensitive
+ lsa.stripLeft(1);
+ result = lsa.compare(lsb);
+ EXPECT_EQ(isc::dns::NameComparisonResult::SUPERDOMAIN,
+ result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ Name nc("g.f.e.d.c.example.org");
+ LabelSequence lsc(nc);
+
+ // "g.f.e.d.c.example.org." and "b.example.org" (not absolute), case
+ // in-sensitive; the absolute one is always smaller.
+ lsb.stripRight(1);
+ result = lsc.compare(lsb);
+ EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(0, result.getCommonLabels());
+
+ // "g.f.e.d.c.example.org." and "example.org.", case in-sensitive
+ result = lsc.compare(ls1);
+ EXPECT_EQ(isc::dns::NameComparisonResult::SUBDOMAIN,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ // "e.d.c.example.org." and "example.org.", case in-sensitive
+ lsc.stripLeft(2);
+ result = lsc.compare(ls1);
+ EXPECT_EQ(isc::dns::NameComparisonResult::SUBDOMAIN,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ // "example.org." and "example.org.", case in-sensitive
+ lsc.stripLeft(3);
+ result = lsc.compare(ls1);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation());
+ EXPECT_EQ(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ // "." and "example.org.", case in-sensitive
+ lsc.stripLeft(2);
+ result = lsc.compare(ls1);
+ EXPECT_EQ(isc::dns::NameComparisonResult::SUPERDOMAIN,
+ result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(1, result.getCommonLabels());
+
+ Name nd("a.b.c.isc.example.org");
+ LabelSequence lsd(nd);
+ Name ne("w.x.y.isc.EXAMPLE.org");
+ LabelSequence lse(ne);
+
+ // "a.b.c.isc.example.org." and "w.x.y.isc.EXAMPLE.org.",
+ // case sensitive
+ result = lsd.compare(lse, true);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(2, result.getCommonLabels());
+
+ // "a.b.c.isc.example.org." and "w.x.y.isc.EXAMPLE.org.",
+ // case in-sensitive
+ result = lsd.compare(lse);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(4, result.getCommonLabels());
+
+ // "isc.example.org." and "isc.EXAMPLE.org.", case sensitive
+ lsd.stripLeft(3);
+ lse.stripLeft(3);
+ result = lsd.compare(lse, true);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(2, result.getCommonLabels());
+
+ // "isc.example.org." and "isc.EXAMPLE.org.", case in-sensitive
+ result = lsd.compare(lse);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation());
+ EXPECT_EQ(0, result.getOrder());
+ EXPECT_EQ(4, result.getCommonLabels());
+
+ Name nf("a.b.c.isc.example.org");
+ LabelSequence lsf(nf);
+ Name ng("w.x.y.isc.EXAMPLE.org");
+ LabelSequence lsg(ng);
+
+ // lsf: "a.b.c.isc.example.org."
+ // lsg: "w.x.y.isc.EXAMPLE.org" (not absolute), case in-sensitive.
+ // the absolute one is always smaller.
+ lsg.stripRight(1);
+ result = lsg.compare(lsf); // lsg > lsf
+ EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(0, result.getCommonLabels());
+
+ // "a.b.c.isc.example.org" (not absolute) and
+ // "w.x.y.isc.EXAMPLE.org" (not absolute), case in-sensitive
+ lsf.stripRight(1);
+ result = lsg.compare(lsf);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(3, result.getCommonLabels());
+
+ // "a.b.c.isc.example" (not absolute) and
+ // "w.x.y.isc.EXAMPLE" (not absolute), case in-sensitive
+ lsf.stripRight(1);
+ lsg.stripRight(1);
+ result = lsg.compare(lsf);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_LT(0, result.getOrder());
+ EXPECT_EQ(2, result.getCommonLabels());
+
+ // lsf: "a.b.c" (not absolute) and
+ // lsg: "w.x.y" (not absolute), case in-sensitive; a.b.c < w.x.y;
+ // no common labels.
+ lsf.stripRight(2);
+ lsg.stripRight(2);
+ result = lsf.compare(lsg);
+ EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(0, result.getCommonLabels());
+
+ // lsf2: a.b.cc (not absolute); a.b.c < a.b.cc, no common labels.
+ const Name nf2("a.b.cc");
+ LabelSequence lsf2(nf2);
+ lsf2.stripRight(1);
+ result = lsf.compare(lsf2);
+ EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(0, result.getCommonLabels());
+
+ Name nh("aexample.org");
+ LabelSequence lsh(nh);
+ Name ni("bexample.org");
+ LabelSequence lsi(ni);
+
+ // "aexample.org" (not absolute) and
+ // "bexample.org" (not absolute), case in-sensitive
+ lsh.stripRight(1);
+ lsi.stripRight(1);
+ result = lsh.compare(lsi);
+ EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
+ result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(1, result.getCommonLabels());
+
+ // "aexample" (not absolute) and
+ // "bexample" (not absolute), case in-sensitive;
+ // aexample < bexample; no common labels.
+ lsh.stripRight(1);
+ lsi.stripRight(1);
+ result = lsh.compare(lsi);
+ EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation());
+ EXPECT_GT(0, result.getOrder());
+ EXPECT_EQ(0, result.getCommonLabels());
+
+ Name nj("example.org");
+ LabelSequence lsj(nj);
+ Name nk("example.org");
+ LabelSequence lsk(nk);
+
+ // "example.org" (not absolute) and
+ // "example.org" (not absolute), case in-sensitive
+ lsj.stripRight(1);
+ lsk.stripRight(1);
+ result = lsj.compare(lsk);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation());
+ EXPECT_EQ(0, result.getOrder());
+ EXPECT_EQ(2, result.getCommonLabels());
+
+ // "example" (not absolute) and
+ // "example" (not absolute), case in-sensitive
+ lsj.stripRight(1);
+ lsk.stripRight(1);
+ result = lsj.compare(lsk);
+ EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL,
+ result.getRelation());
+ EXPECT_EQ(0, result.getOrder());
+ EXPECT_EQ(1, result.getCommonLabels());
+}
+
+void
+getDataCheck(const uint8_t* expected_data, size_t expected_len,
+ const LabelSequence& ls)
+{
+ size_t len;
+ const uint8_t* data = ls.getData(&len);
+ ASSERT_EQ(expected_len, len) << "Expected data: " << expected_data <<
+ ", label sequence: " << ls;
+ EXPECT_EQ(expected_len, ls.getDataLength()) <<
+ "Expected data: " << expected_data <<
+ ", label sequence: " << ls;
+ for (size_t i = 0; i < len; ++i) {
+ EXPECT_EQ(expected_data[i], data[i]) <<
+ "Difference at pos " << i << ": Expected data: " << expected_data <<
+ ", label sequence: " << ls;
+ }
+}
+
+// Convenient data converter for expected data. Label data must be of
+// uint8_t*, while it's convenient if we can specify some test data in
+// plain string (which is of char*). This wrapper converts the latter to
+// the former in a safer way.
+void
+getDataCheck(const char* expected_char_data, size_t expected_len,
+ const LabelSequence& ls)
+{
+ const vector<uint8_t> expected_data(expected_char_data,
+ expected_char_data + expected_len);
+ getDataCheck(&expected_data[0], expected_len, ls);
+}
+
+TEST_F(LabelSequenceTest, getData) {
+ getDataCheck("\007example\003org\000", 13, ls1);
+ getDataCheck("\007example\003com\000", 13, ls2);
+ getDataCheck("\007example\003org\000", 13, ls3);
+ getDataCheck("\003foo\003bar\004test\007example\000", 22, ls4);
+ getDataCheck("\007example\003ORG\000", 13, ls5);
+ getDataCheck("\007ExAmPlE\003org\000", 13, ls6);
+ getDataCheck("\000", 1, ls7);
+};
+
+TEST_F(LabelSequenceTest, stripLeft) {
+ EXPECT_TRUE(ls1.equals(ls3));
+ ls1.stripLeft(0);
+ getDataCheck("\007example\003org\000", 13, ls1);
+ EXPECT_TRUE(ls1.equals(ls3));
+ ls1.stripLeft(1);
+ getDataCheck("\003org\000", 5, ls1);
+ EXPECT_FALSE(ls1.equals(ls3));
+ ls1.stripLeft(1);
+ getDataCheck("\000", 1, ls1);
+ EXPECT_TRUE(ls1.equals(ls7));
+
+ ls2.stripLeft(2);
+ getDataCheck("\000", 1, ls2);
+ EXPECT_TRUE(ls2.equals(ls7));
+}
+
+TEST_F(LabelSequenceTest, stripRight) {
+ EXPECT_TRUE(ls1.equals(ls3));
+ ls1.stripRight(1);
+ getDataCheck("\007example\003org", 12, ls1);
+ EXPECT_FALSE(ls1.equals(ls3));
+ ls1.stripRight(1);
+ getDataCheck("\007example", 8, ls1);
+ EXPECT_FALSE(ls1.equals(ls3));
+
+ ASSERT_FALSE(ls1.equals(ls2));
+ ls2.stripRight(2);
+ getDataCheck("\007example", 8, ls2);
+ EXPECT_TRUE(ls1.equals(ls2));
+}
+
+TEST_F(LabelSequenceTest, stripOutOfRange) {
+ EXPECT_THROW(ls1.stripLeft(100), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripLeft(5), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripLeft(4), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripLeft(3), isc::OutOfRange);
+ getDataCheck("\007example\003org\000", 13, ls1);
+
+ EXPECT_THROW(ls1.stripRight(100), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripRight(5), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripRight(4), isc::OutOfRange);
+ EXPECT_THROW(ls1.stripRight(3), isc::OutOfRange);
+ getDataCheck("\007example\003org\000", 13, ls1);
+}
+
+TEST_F(LabelSequenceTest, getLabelCount) {
+ EXPECT_EQ(3, ls1.getLabelCount());
+ ls1.stripLeft(0);
+ EXPECT_EQ(3, ls1.getLabelCount());
+ ls1.stripLeft(1);
+ EXPECT_EQ(2, ls1.getLabelCount());
+ ls1.stripLeft(1);
+ EXPECT_EQ(1, ls1.getLabelCount());
+
+ EXPECT_EQ(3, ls2.getLabelCount());
+ ls2.stripRight(1);
+ EXPECT_EQ(2, ls2.getLabelCount());
+ ls2.stripRight(1);
+ EXPECT_EQ(1, ls2.getLabelCount());
+
+ EXPECT_EQ(3, ls3.getLabelCount());
+ ls3.stripRight(2);
+ EXPECT_EQ(1, ls3.getLabelCount());
+
+ EXPECT_EQ(5, ls4.getLabelCount());
+ ls4.stripRight(3);
+ EXPECT_EQ(2, ls4.getLabelCount());
+
+ EXPECT_EQ(3, ls5.getLabelCount());
+ ls5.stripLeft(2);
+ EXPECT_EQ(1, ls5.getLabelCount());
+}
+
+TEST_F(LabelSequenceTest, comparePart) {
+ EXPECT_FALSE(ls1.equals(ls8));
+
+ // strip root label from example.org.
+ ls1.stripRight(1);
+ // strip foo from foo.example.org.bar.
+ ls8.stripLeft(1);
+ // strip bar. (i.e. bar and root) too
+ ls8.stripRight(2);
+
+ EXPECT_TRUE(ls1.equals(ls8));
+
+ // Data comparison
+ size_t len;
+ const uint8_t* data = ls1.getData(&len);
+ getDataCheck(data, len, ls8);
+}
+
+TEST_F(LabelSequenceTest, isAbsolute) {
+ ASSERT_TRUE(ls1.isAbsolute());
+
+ ls1.stripLeft(1);
+ ASSERT_TRUE(ls1.isAbsolute());
+ ls1.stripRight(1);
+ ASSERT_FALSE(ls1.isAbsolute());
+
+ ASSERT_TRUE(ls2.isAbsolute());
+ ls2.stripRight(1);
+ ASSERT_FALSE(ls2.isAbsolute());
+
+ ASSERT_TRUE(ls3.isAbsolute());
+ ls3.stripLeft(2);
+ ASSERT_TRUE(ls3.isAbsolute());
+}
+
+TEST_F(LabelSequenceTest, toText) {
+ EXPECT_EQ(".", ls7.toText());
+
+ EXPECT_EQ("example.org.", ls1.toText());
+ ls1.stripLeft(1);
+ EXPECT_EQ("org.", ls1.toText());
+ ls1.stripLeft(1);
+ EXPECT_EQ(".", ls1.toText());
+
+ EXPECT_EQ("example.com.", ls2.toText());
+ ls2.stripRight(1);
+ EXPECT_EQ("example.com", ls2.toText());
+ ls2.stripRight(1);
+ EXPECT_EQ("example", ls2.toText());
+
+ EXPECT_EQ("foo.example.org.bar.", ls8.toText());
+ ls8.stripRight(2);
+ EXPECT_EQ("foo.example.org", ls8.toText());
+
+ EXPECT_EQ(".", ls7.toText());
+ EXPECT_THROW(ls7.stripLeft(1), isc::OutOfRange);
+
+ Name n_long1("012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "0123456789012345678901234567890");
+ LabelSequence ls_long1(n_long1);
+
+ EXPECT_EQ("012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "0123456789012345678901234567890.", ls_long1.toText());
+ ls_long1.stripRight(1);
+ EXPECT_EQ("012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "012345678901234567890123456789012."
+ "012345678901234567890123456789"
+ "0123456789012345678901234567890", ls_long1.toText());
+
+ LabelSequence ls_long2(n_maxlabel);
+
+ EXPECT_EQ("0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.", ls_long2.toText());
+ ls_long2.stripRight(1);
+ EXPECT_EQ("0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9."
+ "0.1.2.3.4.5.6", ls_long2.toText());
+ ls_long2.stripRight(125);
+ EXPECT_EQ("0.1", ls_long2.toText());
+}
+
+// The following verifies that toRawText() returns a string
+// actual characters in place of escape sequences. We do not
+// bother with an exhaustive set of tests here as this is
+// not a primary use case.
+TEST_F(LabelSequenceTest, toRawText) {
+ Name n("a bc.$exa(m)ple.@org");
+ LabelSequence l(n);
+ EXPECT_EQ("a bc.$exa(m)ple.@org", l.toRawText(true));
+ EXPECT_EQ("a bc.$exa(m)ple.@org.", l.toRawText(false));
+
+ // toRawText is not supposed to do any sanity checks.
+ // Let's try with a very weird name.
+ Name n2("xtra\tchars\n.in.name");
+ LabelSequence l2(n2);
+ EXPECT_EQ("xtra\tchars\n.in.name.", l2.toRawText(false));
+}
+
+// The following are test data used in the getHash test below. Normally
+// we use example/documentation domain names for testing, but in this case
+// we'd specifically like to use more realistic data, and are intentionally
+// using real-world samples: They are the NS names of root and some top level
+// domains as of this test.
+const char* const root_servers[] = {
+ "a.root-servers.net", "b.root-servers.net", "c.root-servers.net",
+ "d.root-servers.net", "e.root-servers.net", "f.root-servers.net",
+ "g.root-servers.net", "h.root-servers.net", "i.root-servers.net",
+ "j.root-servers.net", "k.root-servers.net", "l.root-servers.net",
+ "m.root-servers.net", 0
+};
+
+const char* const jp_servers[] = {
+ "a.dns.jp", "b.dns.jp", "c.dns.jp", "d.dns.jp", "e.dns.jp",
+ "f.dns.jp", "g.dns.jp", 0
+};
+const char* const cn_servers[] = {
+ "a.dns.cn", "b.dns.cn", "c.dns.cn", "d.dns.cn", "e.dns.cn",
+ "ns.cernet.net", 0
+};
+const char* const ca_servers[] = {
+ "k.ca-servers.ca", "e.ca-servers.ca", "a.ca-servers.ca", "z.ca-servers.ca",
+ "tld.isc-sns.net", "c.ca-servers.ca", "j.ca-servers.ca", "l.ca-servers.ca",
+ "sns-pb.isc.org", "f.ca-servers.ca", 0
+};
+
+// A helper function used in the getHash test below.
+void
+hashDistributionCheck(const char* const* servers) {
+ const size_t BUCKETS = 64; // constant used in the MessageRenderer
+ set<Name> names;
+ vector<size_t> hash_counts(BUCKETS);
+
+ // Store all test names and their super domain names (excluding the
+ // "root" label) in the set, calculates their hash values, and increments
+ // the counter for the corresponding hash "bucket".
+ for (size_t i = 0; servers[i]; ++i) {
+ const Name name(servers[i]);
+ for (size_t l = 0; l < name.getLabelCount() - 1; ++l) {
+ pair<set<Name>::const_iterator, bool> ret =
+ names.insert(name.split(l));
+ if (ret.second) {
+ hash_counts[LabelSequence((*ret.first)).getHash(false) %
+ BUCKETS]++;
+ }
+ }
+ }
+
+ // See how many conflicts we have in the buckets. For the testing purpose
+ // we expect there's at most 2 conflicts in each set, which is an
+ // arbitrary choice (it should happen to succeed with the hash function
+ // and data we are using; if it's not the case, maybe with an update to
+ // the hash implementation, we should revise the test).
+ for (size_t i = 0; i < BUCKETS; ++i) {
+ EXPECT_GE(3, hash_counts[i]);
+ }
+}
+
+TEST_F(LabelSequenceTest, getHash) {
+ // Trivial case. The same sequence should have the same hash.
+ EXPECT_EQ(ls1.getHash(true), ls1.getHash(true));
+
+ // Check the case-insensitive mode behavior.
+ EXPECT_EQ(ls1.getHash(false), ls5.getHash(false));
+
+ // Check that the distribution of hash values is "not too bad" (such as
+ // everything has the same hash value due to a stupid bug). It's
+ // difficult to check such things reliably. We do some ad hoc tests here.
+ hashDistributionCheck(root_servers);
+ hashDistributionCheck(jp_servers);
+ hashDistributionCheck(cn_servers);
+ hashDistributionCheck(ca_servers);
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(LabelSequenceTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << ls1;
+ EXPECT_EQ(ls1.toText(), oss.str());
+}
+
+TEST_F(LabelSequenceTest, serialize) {
+ // placeholder for serialized data. We use a sufficiently large space
+ // for testing the overwrapping cases below.
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH * 3];
+
+ // vector to store expected and actual data
+ vector<LabelSequence> actual_labelseqs;
+ typedef pair<size_t, const uint8_t*> DataPair;
+ vector<DataPair> expected;
+
+ // An absolute sequence directly constructed from a valid name.
+ // labels = 3, offset sequence = 0, 8, 12, data = "example.com."
+ actual_labelseqs.push_back(ls1);
+ const uint8_t expected_data1[] = {
+ 3, 0, 8, 12, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'o', 'r', 'g', 0 };
+ expected.push_back(DataPair(sizeof(expected_data1), expected_data1));
+
+ // Strip the original one from right.
+ // labels = 2, offset sequence = 0, 8, data = "example.com" (non absolute)
+ LabelSequence ls_rstripped = ls1;
+ ls_rstripped.stripRight(1);
+ actual_labelseqs.push_back(ls_rstripped);
+ const uint8_t expected_data2[] = {
+ 2, 0, 8, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 3, 'o', 'r', 'g'};
+ expected.push_back(DataPair(sizeof(expected_data2), expected_data2));
+
+ // Strip the original one from left.
+ // labels = 2, offset sequence = 0, 4, data = "com."
+ // Note that offsets are adjusted so that they begin with 0.
+ LabelSequence ls_lstripped = ls1;
+ ls_lstripped.stripLeft(1);
+ actual_labelseqs.push_back(ls_lstripped);
+ const uint8_t expected_data3[] = { 2, 0, 4, 3, 'o', 'r', 'g', 0 };
+ expected.push_back(DataPair(sizeof(expected_data3), expected_data3));
+
+ // Root label.
+ LabelSequence ls_root(Name::ROOT_NAME());
+ actual_labelseqs.push_back(ls_root);
+ const uint8_t expected_data4[] = { 1, 0, 0 };
+ expected.push_back(DataPair(sizeof(expected_data4), expected_data4));
+
+ // Non absolute single-label.
+ LabelSequence ls_single = ls_rstripped;
+ ls_single.stripRight(1);
+ actual_labelseqs.push_back(ls_single);
+ const uint8_t expected_data5[] = {
+ 1, 0, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e' };
+ expected.push_back(DataPair(sizeof(expected_data5), expected_data5));
+
+ // Labels containing a longest possible label
+ const Name name_longlabel(std::string(63, 'x')); // 63 'x's
+ LabelSequence ls_longlabel(name_longlabel);
+ actual_labelseqs.push_back(ls_longlabel);
+ vector<uint8_t> expected_data6;
+ expected_data6.push_back(2); // 2 labels
+ expected_data6.push_back(0); // 1st offset
+ expected_data6.push_back(64); // 2nd offset
+ expected_data6.push_back(63); // 1st label length
+ expected_data6.insert(expected_data6.end(), 63, 'x'); // 1st label: 63 'x's
+ expected_data6.push_back(0); // 2nd label: trailing 0
+ expected.push_back(DataPair(expected_data6.size(), &expected_data6[0]));
+
+ // Max number of labels and longest possible name
+ EXPECT_EQ(Name::MAX_WIRE, n_maxlabel.getLength());
+ LabelSequence ls_maxlabel(n_maxlabel);
+ actual_labelseqs.push_back(ls_maxlabel);
+ vector<uint8_t> expected_data7;
+ expected_data7.push_back(Name::MAX_LABELS); // number of labels
+ for (size_t i = 0; i < Name::MAX_LABELS; ++i) {
+ expected_data7.push_back(i * 2); // each label has length and 1 byte
+ }
+ // Copy wire data of the name
+ isc::util::OutputBuffer ob(0);
+ n_maxlabel.toWire(ob);
+ expected_data7.insert(expected_data7.end(),
+ ob.getVector().cbegin(),
+ ob.getVector().cend());
+ expected.push_back(DataPair(expected_data7.size(), &expected_data7[0]));
+
+ // For each data set, serialize the labels and compare the data to the
+ // expected one.
+ vector<DataPair>::const_iterator it = expected.begin();
+ vector<LabelSequence>::const_iterator itl = actual_labelseqs.begin();
+ for (; it != expected.end(); ++it, ++itl) {
+ SCOPED_TRACE(itl->toText());
+
+ const size_t serialized_len = itl->getSerializedLength();
+
+ ASSERT_GE(LabelSequence::MAX_SERIALIZED_LENGTH, serialized_len);
+ itl->serialize(labels_buf, serialized_len);
+ EXPECT_EQ(it->first, serialized_len);
+ EXPECT_EQ(0, memcmp(it->second, labels_buf, serialized_len));
+
+ EXPECT_EQ(NameComparisonResult::EQUAL,
+ LabelSequence(labels_buf).compare(*itl).getRelation());
+
+ // Shift the data to the middle of the buffer for overwrap check
+ uint8_t* const bp = labels_buf;
+ std::memcpy(bp + serialized_len, bp, serialized_len);
+ // Memory layout is now as follows:
+ // <- ser_len -> <- ser_len ------>
+ // bp bp+ser_len bp+(ser_len*2)
+ // olen,odata,ndata
+
+ // end of buffer would be the first byte of offsets: invalid.
+ EXPECT_THROW(LabelSequence(bp + serialized_len).
+ serialize(bp + 2, serialized_len),
+ isc::BadValue);
+ // begin of buffer would be the last byte of ndata: invalid.
+ EXPECT_THROW(LabelSequence(bp + serialized_len).
+ serialize(bp + (2 * serialized_len) - 1, serialized_len),
+ isc::BadValue);
+ // A boundary safe case: buffer is placed after the sequence data.
+ // should cause no disruption.
+ LabelSequence(bp + serialized_len).
+ serialize(bp + 2 * serialized_len, serialized_len);
+ // A boundary safe case: buffer is placed before the sequence data
+ // should cause no disruption. (but the original serialized data will
+ // be overridden, so it can't be used any more)
+ LabelSequence(bp + serialized_len).
+ serialize(bp + 1, serialized_len);
+ }
+
+ EXPECT_THROW(ls1.serialize(labels_buf, ls1.getSerializedLength() - 1),
+ isc::BadValue);
+}
+
+#ifdef ENABLE_DEBUG
+
+// These checks are enabled only in debug mode in the LabelSequence
+// class.
+TEST_F(LabelSequenceTest, badDeserialize) {
+ EXPECT_THROW(LabelSequence(0), isc::BadValue);
+ const uint8_t zero_offsets[] = { 0 };
+ EXPECT_THROW(LabelSequence ls(zero_offsets), isc::BadValue);
+ const uint8_t toomany_offsets[] = { Name::MAX_LABELS + 1 };
+ EXPECT_THROW(LabelSequence ls(toomany_offsets), isc::BadValue);
+
+ // (second) offset does not match actual label length
+ const uint8_t offsets_wrongoffset[] = { 2, 0, 64, 1 };
+ EXPECT_THROW(LabelSequence ls(offsets_wrongoffset), isc::BadValue);
+
+ // offset matches, but exceeds MAX_LABEL_LEN
+ const uint8_t offsets_toolonglabel[] = { 2, 0, 64, 64 };
+ EXPECT_THROW(LabelSequence ls(offsets_toolonglabel), isc::BadValue);
+
+ // Inconsistent data: an offset is lower than the previous offset
+ const uint8_t offsets_lower[] = { 3, // # of offsets
+ 0, 2, 1, // offsets
+ 1, 'a', 1, 'b', 0};
+ EXPECT_THROW(LabelSequence ls(offsets_lower), isc::BadValue);
+
+ // Inconsistent data: an offset is equal to the previous offset
+ const uint8_t offsets_noincrease[] = { 2, 0, 0, 0, 0 };
+ EXPECT_THROW(LabelSequence ls(offsets_noincrease), isc::BadValue);
+}
+
+#endif
+
+namespace {
+
+// Helper function; repeatedly calls
+// - Initially, all three labelsequences should be the same
+// - repeatedly performs:
+// - checks all three are equal
+// - stripLeft on ls1
+// - checks ls1 and ls2 are different, and ls2 and ls3 are equal
+// - stripLeft on ls2
+// - checks ls1 and ls2 are equal, and ls2 and ls3 are different
+// - stripLeft on ls3
+//
+// (this test makes sure the stripLeft of one has no effect on the other
+// two, and that the strip properties hold regardless of how they were
+// constructed)
+//
+void stripLeftCheck(LabelSequence ls1, LabelSequence ls2, LabelSequence ls3) {
+ ASSERT_LT(1, ls1.getLabelCount());
+ while (ls1.getLabelCount() > 1) {
+ check_equal(ls1, ls2);
+ check_equal(ls2, ls3);
+
+ ls1.stripLeft(1);
+ check_compare(ls1, ls2, isc::dns::NameComparisonResult::SUPERDOMAIN,
+ ls1.getLabelCount(), true, -1);
+ check_equal(ls2, ls3);
+
+ ls2.stripLeft(1);
+ check_equal(ls1, ls2);
+ check_compare(ls2, ls3, isc::dns::NameComparisonResult::SUPERDOMAIN,
+ ls1.getLabelCount(), true, -1);
+
+ ls3.stripLeft(1);
+ }
+}
+
+// Similar to stripLeftCheck, but using stripRight()
+void stripRightCheck(LabelSequence ls1, LabelSequence ls2, LabelSequence ls3) {
+ ASSERT_LT(1, ls1.getLabelCount());
+ while (ls1.getLabelCount() > 1) {
+ check_equal(ls1, ls2);
+ check_equal(ls2, ls3);
+
+ ls1.stripRight(1);
+ check_compare(ls1, ls2, isc::dns::NameComparisonResult::NONE, 0,
+ false);
+ check_equal(ls2, ls3);
+
+ ls2.stripRight(1);
+ check_equal(ls1, ls2);
+ check_compare(ls2, ls3, isc::dns::NameComparisonResult::NONE, 0,
+ false);
+
+ ls3.stripRight(1);
+ }
+}
+
+} // end anonymous namespace
+
+class ExtendableLabelSequenceTest : public ::testing::Test {
+public:
+ ExtendableLabelSequenceTest() : bar("bar."),
+ example_org("example.org"),
+ foo("foo."),
+ foo_bar("foo.bar."),
+ foo_bar_example_org("foo.bar.example.org."),
+ foo_bar_foo_bar("foo.bar.foo.bar."),
+ foo_example("foo.example."),
+ org("org")
+ {
+ // explicitly set to non-zero data, to make sure
+ // we don't try to use data we don't set
+ memset(buf, 0xff, LabelSequence::MAX_SERIALIZED_LENGTH);
+ }
+
+ Name bar;
+ Name example_org;
+ Name foo;
+ Name foo_bar;
+ Name foo_bar_example_org;
+ Name foo_bar_foo_bar;
+ Name foo_example;
+ Name org;
+
+ uint8_t buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+};
+
+// Test that 'extendable' labelsequences behave correctly when using
+// stripLeft() and stripRight()
+TEST_F(ExtendableLabelSequenceTest, extendableLabelSequence) {
+ LabelSequence ls1(example_org);
+ LabelSequence ls2(example_org);
+
+ LabelSequence els(ls1, buf);
+ // ls1 is absolute, so els should be too
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls1, els);
+
+ ASSERT_EQ(ls1.getDataLength(), els.getDataLength());
+ stripLeftCheck(ls1, els, ls2);
+ stripRightCheck(ls1, els, ls2);
+
+ // Creating an extendable labelsequence from a non-absolute
+ // label sequence should result in a non-absolute label sequence
+ ls1.stripRight(1);
+ els = LabelSequence(ls1, buf);
+ EXPECT_FALSE(els.isAbsolute());
+ check_equal(ls1, els);
+
+ // and extending with the root label should make it absolute again
+ els.extend(LabelSequence(Name(".")), buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls2, els);
+}
+
+// Test that 'extendable' LabelSequences behave correctly when initialized
+// with a stripped source LabelSequence
+TEST_F(ExtendableLabelSequenceTest, extendableLabelSequenceLeftStrippedSource) {
+ LabelSequence ls1(foo_bar_example_org);
+ LabelSequence ls2(foo_bar_example_org);
+
+ while (ls1.getLabelCount() > 2) {
+ ls1.stripLeft(1);
+ ls2.stripLeft(1);
+
+ LabelSequence els(ls1, buf);
+
+ ASSERT_EQ(ls1.getDataLength(), els.getDataLength());
+ stripLeftCheck(ls1, els, ls2);
+ stripRightCheck(ls1, els, ls2);
+ }
+}
+
+TEST_F(ExtendableLabelSequenceTest, extendableLabelSequenceRightStrippedSource) {
+ LabelSequence ls1(foo_bar_example_org);
+ LabelSequence ls2(foo_bar_example_org);
+
+ while (ls1.getLabelCount() > 2) {
+ ls1.stripRight(1);
+ ls2.stripRight(1);
+
+ LabelSequence els(ls1, buf);
+
+ ASSERT_EQ(ls1.getDataLength(), els.getDataLength());
+ stripLeftCheck(ls1, els, ls2);
+ stripRightCheck(ls1, els, ls2);
+ }
+}
+
+// Check some basic 'extend' functionality
+TEST_F(ExtendableLabelSequenceTest, extend) {
+ LabelSequence ls1(foo_bar);
+ LabelSequence ls2(foo);
+ LabelSequence ls3(bar);
+ LabelSequence ls4(foo_bar);
+
+ LabelSequence els(ls2, buf);
+
+ check_compare(ls1, els, isc::dns::NameComparisonResult::COMMONANCESTOR, 1,
+ true, -4);
+ els.extend(ls3, buf);
+ EXPECT_TRUE(els.isAbsolute());
+
+ check_equal(ls1, els);
+ stripLeftCheck(ls1, els, ls4);
+ stripRightCheck(ls1, els, ls4);
+
+ // strip, then extend again
+ els.stripRight(2); // (2, 1 for root label, 1 for last label)
+ els.extend(ls3, buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls1, els);
+
+ // Extending again should make it different
+ els.extend(ls3, buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_compare(ls1, els, isc::dns::NameComparisonResult::COMMONANCESTOR, 2,
+ true, 4);
+
+ // Extending with a non-absolute name should make it non-absolute as well
+ ls3.stripRight(1);
+ els.extend(ls3, buf);
+ EXPECT_FALSE(els.isAbsolute());
+
+ Name check_name("foo.bar.bar.bar");
+ LabelSequence check_ls(check_name);
+ check_ls.stripRight(1);
+ check_equal(check_ls, els);
+
+ // And try extending when both are not absolute
+ els.stripRight(3);
+ ls1.stripRight(1);
+ EXPECT_FALSE(els.isAbsolute());
+ els.extend(ls3, buf);
+ EXPECT_FALSE(els.isAbsolute());
+ check_equal(ls1, els);
+
+ // Extending non-absolute with absolute should make it absolute again
+ EXPECT_FALSE(els.isAbsolute());
+ els.extend(LabelSequence(Name("absolute.")), buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(LabelSequence(Name("foo.bar.absolute")), els);
+}
+
+TEST_F(ExtendableLabelSequenceTest, extendLeftStripped) {
+ LabelSequence ls1(foo_example);
+ LabelSequence ls2(example_org);
+ LabelSequence ls3(org);
+
+ LabelSequence els(ls1, buf);
+
+ els.stripLeft(1);
+ els.extend(ls3, buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls2, els);
+}
+
+// Check that when extending with itself, it does not cause horrible failures
+TEST_F(ExtendableLabelSequenceTest, extendWithItself) {
+ LabelSequence ls1(foo_bar);
+ LabelSequence ls2(foo_bar_foo_bar);
+
+ LabelSequence els(ls1, buf);
+
+ els.extend(els, buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls2, els);
+
+ // Also try for non-absolute names
+ ls2.stripRight(1);
+ els = LabelSequence(ls1, buf);
+ els.stripRight(1);
+ els.extend(els, buf);
+ EXPECT_FALSE(els.isAbsolute());
+ check_equal(ls2, els);
+
+ // Once more, now start out with non-absolute labelsequence
+ ls1.stripRight(1);
+ els = LabelSequence(ls1, buf);
+ els.extend(els, buf);
+ EXPECT_FALSE(els.isAbsolute());
+ check_equal(ls2, els);
+}
+
+// Test that 'extending' with just a root label is a no-op, iff the original
+// was already absolute
+TEST_F(ExtendableLabelSequenceTest, extendWithRoot) {
+ LabelSequence ls1(example_org);
+
+ LabelSequence els(LabelSequence(ls1, buf));
+ check_equal(ls1, els);
+ els.extend(LabelSequence(Name(".")), buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls1, els);
+
+ // but not if the original was not absolute (it will be equal to
+ // the original labelsequence used above, but not the one it was based
+ // on).
+ LabelSequence ls2(example_org);
+ ls2.stripRight(1);
+ els = LabelSequence(ls2, buf);
+ EXPECT_FALSE(els.isAbsolute());
+ els.extend(LabelSequence(Name(".")), buf);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(ls1, els);
+ check_compare(ls2, els, isc::dns::NameComparisonResult::NONE, 0, true, 3);
+}
+
+// Check possible failure modes of extend()
+TEST_F(ExtendableLabelSequenceTest, extendBadData) {
+ LabelSequence ls1(example_org);
+
+ LabelSequence els(ls1, buf);
+
+ // try use with unrelated labelsequence
+ EXPECT_THROW(ls1.extend(ls1, buf), isc::BadValue);
+
+ // Create a long name, but so that we can still extend once
+ Name longlabel("1234567890123456789012345678901234567890"
+ "12345678901234567890");
+ LabelSequence long_ls(longlabel);
+ els = LabelSequence(long_ls, buf);
+ els.extend(els, buf);
+ els.extend(long_ls, buf);
+ els.extend(long_ls, buf);
+ ASSERT_EQ(245, els.getDataLength());
+ // Extending once more with 10 bytes should still work
+ els.extend(LabelSequence(Name("123456789")), buf);
+ EXPECT_TRUE(els.isAbsolute());
+
+ // Extended label sequence should now look like
+ const Name full_name(
+ "123456789012345678901234567890123456789012345678901234567890."
+ "123456789012345678901234567890123456789012345678901234567890."
+ "123456789012345678901234567890123456789012345678901234567890."
+ "123456789012345678901234567890123456789012345678901234567890."
+ "123456789.");
+ const LabelSequence full_ls(full_name);
+ check_equal(full_ls, els);
+
+ // But now, even the shortest extension should fail
+ EXPECT_THROW(els.extend(LabelSequence(Name("1")), buf), isc::BadValue);
+
+ // Check it hasn't been changed
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(full_ls, els);
+
+ // Also check that extending past MAX_LABELS is not possible
+ Name shortname("1.");
+ LabelSequence short_ls(shortname);
+ els = LabelSequence(short_ls, buf);
+ for (size_t i=0; i < 126; ++i) {
+ els.extend(short_ls, buf);
+ }
+
+ // Should now look like this
+ const Name full_name2(
+ "1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1."
+ "1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1."
+ "1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1."
+ "1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1."
+ "1.1.1.1.1.1.1.");
+ const LabelSequence full_ls2(full_name2);
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(full_ls2, els);
+
+ EXPECT_THROW(els.extend(short_ls, buf), isc::BadValue);
+
+ EXPECT_TRUE(els.isAbsolute());
+ check_equal(full_ls2, els);
+}
+
+// Check the static fixed 'wildcard' LabelSequence
+TEST(WildCardLabelSequence, wildcard) {
+ ASSERT_FALSE(LabelSequence::WILDCARD().isAbsolute());
+ ASSERT_EQ("*", LabelSequence::WILDCARD().toText());
+}
+
+}
diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
new file mode 100644
index 0000000..5167f54
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -0,0 +1,368 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <string.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::master_lexer_internal;
+
+namespace {
+
+const char* const test_input =
+ "Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n";
+
+class InputSourceTest : public ::testing::Test {
+protected:
+ InputSourceTest() :
+ str_(test_input),
+ str_length_(strlen(str_)),
+ iss_(str_),
+ source_(iss_)
+ {}
+
+ const char* str_;
+ const size_t str_length_;
+ stringstream iss_;
+ InputSource source_;
+};
+
+// Test the default return values set during InputSource construction.
+TEST_F(InputSourceTest, defaults) {
+ EXPECT_EQ(1, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+}
+
+// getName() on file and stream sources
+TEST_F(InputSourceTest, getName) {
+ EXPECT_EQ(0, source_.getName().find("stream-"));
+
+ // Use some file; doesn't really matter what.
+ InputSource source2(TEST_DATA_SRCDIR "/masterload.txt");
+ EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", source2.getName());
+}
+
+TEST_F(InputSourceTest, nonExistentFile) {
+ EXPECT_THROW({
+ InputSource source(TEST_DATA_SRCDIR "/does-not-exist");
+ }, InputSource::OpenError);
+}
+
+// getChar() should return characters from the input stream in
+// sequence. ungetChar() should skip backwards.
+void
+checkGetAndUngetChar(InputSource& source,
+ const char* str, const size_t str_length)
+{
+ for (size_t i = 0; i < str_length; ++i) {
+ EXPECT_EQ(str[i], source.getChar());
+ EXPECT_EQ(i + 1, source.getPosition());
+ EXPECT_FALSE(source.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source.atEOF());
+
+ // It doesn't increase the position count.
+ EXPECT_EQ(str_length, source.getPosition());
+ EXPECT_EQ(str_length, source.getSize()); // this should be == getSize().
+
+ // Now, let's go backwards. This should cause the EOF to be set to
+ // false.
+ source.ungetChar();
+
+ // Now, EOF should be false.
+ EXPECT_FALSE(source.atEOF());
+
+ // But the position shouldn't change.
+ EXPECT_EQ(str_length, source.getPosition());
+
+ // This should cause EOF to be set again.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source.atEOF());
+
+ // Now, let's go backwards in a loop. Start by skipping the EOF.
+ source.ungetChar();
+
+ for (size_t i = 0; i < str_length; ++i) {
+ const size_t index = str_length - 1 - i;
+ // Skip one character.
+ source.ungetChar();
+ EXPECT_EQ(str[index], source.getChar());
+ EXPECT_EQ(index + 1, source.getPosition());
+ // Skip the character we received again.
+ source.ungetChar();
+ }
+
+ // Skipping past the start of buffer should throw.
+ EXPECT_THROW(source.ungetChar(), InputSource::UngetBeforeBeginning);
+}
+
+TEST_F(InputSourceTest, stream) {
+ checkGetAndUngetChar(source_, str_, str_length_);
+}
+
+TEST_F(InputSourceTest, file) {
+ std::ifstream fs(TEST_DATA_SRCDIR "/masterload.txt");
+ const std::string str((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+
+ InputSource source(TEST_DATA_SRCDIR "/masterload.txt");
+ checkGetAndUngetChar(source, str.c_str(), str.size());
+}
+
+// ungetAll() should skip back to the place where the InputSource
+// started at construction, or the last saved start of line.
+TEST_F(InputSourceTest, ungetAll) {
+ while (!source_.atEOF()) {
+ source_.getChar();
+ }
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ source_.ungetAll();
+
+ // Now we are back to where we started.
+ EXPECT_EQ(1, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(0, source_.getPosition());
+}
+
+TEST_F(InputSourceTest, compact) {
+ // Compact at the start
+ source_.compact();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ for (size_t i = 0; i < str_length_; ++i) {
+ EXPECT_EQ(str_[i], source_.getChar());
+ EXPECT_FALSE(source_.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source_.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Compact again
+ source_.compact();
+
+ // We are still at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // compact shouldn't change the position count.
+ EXPECT_EQ(source_.getSize(), source_.getPosition());
+
+ // Skip the EOF.
+ source_.ungetChar();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+ EXPECT_TRUE(source_.atEOF());
+}
+
+TEST_F(InputSourceTest, markDuring) {
+ // First, skip to line 2.
+ while (!source_.atEOF() &&
+ (source_.getCurrentLine() != 2)) {
+ source_.getChar();
+ }
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(2, source_.getCurrentLine());
+
+ // Now, unget a couple of characters. This should cause the
+ // buffer_pos_ to be not equal to the size of the buffer.
+ source_.ungetChar();
+ source_.ungetChar();
+
+ // Now "mark" the source, meaning that we save line number and also
+ // compact the internal buffer at this stage.
+ source_.mark();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ for (size_t i = 13; i < str_length_; ++i) {
+ EXPECT_EQ(str_[i], source_.getChar());
+ EXPECT_FALSE(source_.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source_.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source_.atEOF());
+
+ // Now, ungetAll() and check where it goes back.
+ source_.ungetAll();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ for (size_t i = 13; i < str_length_; ++i) {
+ EXPECT_EQ(str_[i], source_.getChar());
+ EXPECT_FALSE(source_.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source_.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source_.atEOF());
+}
+
+// Test line counters.
+TEST_F(InputSourceTest, lines) {
+ size_t line = 1;
+ while (!source_.atEOF()) {
+ if (source_.getChar() == '\n') {
+ ++line;
+ }
+ EXPECT_EQ(line, source_.getCurrentLine());
+ }
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Go backwards 2 characters, skipping the last EOF and '\n'.
+ source_.ungetChar();
+ source_.ungetChar();
+
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(3, source_.getCurrentLine());
+
+ source_.ungetAll();
+
+ // Now we are back to where we started.
+ EXPECT_EQ(1, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+
+ // Now check that line numbers are decremented properly (as much as
+ // possible using the available API).
+ while (!source_.atEOF()) {
+ source_.getChar();
+ }
+ line = source_.getCurrentLine();
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, line);
+
+ EXPECT_THROW({
+ while (true) {
+ source_.ungetChar();
+ EXPECT_TRUE(((line == source_.getCurrentLine()) ||
+ ((line - 1) == source_.getCurrentLine())));
+ line = source_.getCurrentLine();
+ }
+ }, InputSource::UngetBeforeBeginning);
+
+ // Now we are back to where we started.
+ EXPECT_EQ(1, source_.getCurrentLine());
+}
+
+// ungetAll() after saveLine() should skip back to the last-saved place.
+TEST_F(InputSourceTest, saveLine) {
+ // First, skip to line 2.
+ while (!source_.atEOF() &&
+ (source_.getCurrentLine() != 2)) {
+ source_.getChar();
+ }
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(2, source_.getCurrentLine());
+
+ // Now, save the line.
+ source_.saveLine();
+
+ // Now, go to EOF
+ while (!source_.atEOF()) {
+ source_.getChar();
+ }
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Now, ungetAll() and check where it goes back.
+ source_.ungetAll();
+
+ // Now we are back to where we last-saved.
+ EXPECT_EQ(2, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+}
+
+TEST_F(InputSourceTest, getSize) {
+ // A simple case using string stream
+ EXPECT_EQ(strlen(test_input), source_.getSize());
+
+ // Check it works with an empty input
+ istringstream iss("");
+ EXPECT_EQ(0, InputSource(iss).getSize());
+
+ // Pretend there's an error in seeking in the stream. It will be
+ // considered a seek specific error, and getSize() returns "unknown".
+ iss.setstate(std::ios_base::failbit);
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, InputSource(iss).getSize());
+ // The fail bit should have been cleared.
+ EXPECT_FALSE(iss.fail());
+
+ // Pretend there's a *critical* error in the stream. The constructor will
+ // throw in the attempt of getting the input size.
+ iss.setstate(std::ios_base::badbit);
+ EXPECT_THROW(InputSource isrc(iss), InputSource::OpenError);
+
+ // Check with input source from file name. We hardcode the file size
+ // for simplicity. It won't change too often.
+ EXPECT_EQ(143, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getSize());
+}
+
+TEST_F(InputSourceTest, getPosition) {
+ // Initially the position is set to 0. Other cases are tested in tests
+ // for get and unget.
+ EXPECT_EQ(0, source_.getPosition());
+ EXPECT_EQ(0, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getPosition());
+}
+
+} // end namespace
diff --git a/src/lib/dns/tests/master_lexer_state_unittest.cc b/src/lib/dns/tests/master_lexer_state_unittest.cc
new file mode 100644
index 0000000..4413ba5
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_state_unittest.cc
@@ -0,0 +1,607 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/master_lexer.h>
+#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer_state.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace isc::dns;
+using namespace master_lexer_internal;
+
+namespace {
+typedef MasterToken Token; // shortcut
+
+class MasterLexerStateTest : public ::testing::Test {
+protected:
+ MasterLexerStateTest() : common_options(MasterLexer::INITIAL_WS),
+ s_null(0),
+ s_crlf(State::getInstance(State::CRLF)),
+ s_string(State::getInstance(State::String)),
+ s_qstring(State::getInstance(State::QString)),
+ s_number(State::getInstance(State::Number)),
+ options(MasterLexer::NONE),
+ orig_options(options)
+ {}
+
+ // Specify INITIAL_WS as common initial options.
+ const MasterLexer::Options common_options;
+ MasterLexer lexer;
+ const State* const s_null;
+ const State& s_crlf;
+ const State& s_string;
+ const State& s_qstring;
+ const State& s_number;
+ std::stringstream ss;
+ MasterLexer::Options options, orig_options;
+};
+
+// Common check for the end-of-file condition.
+// Token is set to END_OF_FILE, and the lexer was NOT last eol state.
+// Passed state can be any valid one; they are stateless, just providing the
+// interface for inspection.
+void
+eofCheck(const State& state, MasterLexer& lexer) {
+ EXPECT_EQ(Token::END_OF_FILE, state.getToken(lexer).getType());
+ EXPECT_FALSE(state.wasLastEOL(lexer));
+}
+
+TEST_F(MasterLexerStateTest, startAndEnd) {
+ // A simple case: the input is empty, so we begin with start and
+ // are immediately done.
+ lexer.pushSource(ss);
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ eofCheck(s_crlf, lexer);
+}
+
+TEST_F(MasterLexerStateTest, startToEOL) {
+ ss << "\n";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+ // The next lexer session will reach EOF. Same eof check should pass.
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ eofCheck(s_crlf, lexer);
+}
+
+TEST_F(MasterLexerStateTest, space) {
+ // repeat '\t\n' twice (see below), then space after EOL
+ ss << " \t\n\t\n ";
+ lexer.pushSource(ss);
+
+ // by default space characters and tabs will be ignored. We check this
+ // twice; at the second iteration, it's a white space at the beginning
+ // of line, but since we don't specify INITIAL_WS option, it's treated as
+ // normal space and ignored.
+ for (size_t i = 0; i < 2; ++i) {
+ EXPECT_EQ(s_null, State::start(lexer, MasterLexer::NONE));
+ EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+ }
+
+ // Now we specify the INITIAL_WS option. It will be recognized and the
+ // corresponding token will be returned.
+ EXPECT_EQ(s_null, State::start(lexer, MasterLexer::INITIAL_WS));
+ EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
+ EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, parentheses) {
+ ss << "\n(\na\n )\n "; // 1st \n is to check if 'was EOL' is set to false
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // handle \n
+
+ // Now handle '('. It skips \n and recognize 'a' as string
+ EXPECT_EQ(0, s_crlf.getParenCount(lexer)); // check pre condition
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ EXPECT_EQ(1, s_crlf.getParenCount(lexer)); // check post condition
+ EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
+
+ // skip 'a'
+ s_string.handle(lexer);
+
+ // Then handle ')'. '\n' before ')' isn't recognized because
+ // it's canceled due to the '('. Likewise, the space after the '\n'
+ // shouldn't be recognized but should be just ignored.
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(0, s_crlf.getParenCount(lexer));
+
+ // Now, temporarily disabled options are restored: Both EOL and the
+ // initial WS are recognized
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, nestedParentheses) {
+ // This is an unusual, but allowed (in this implementation) case.
+ ss << "(a(b)\n c)\n ";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '('
+ s_string.handle(lexer); // consume 'a'
+ EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '('
+ s_string.handle(lexer); // consume 'b'
+ EXPECT_EQ(2, s_crlf.getParenCount(lexer)); // now the count is 2
+
+ // Close the inner most parentheses. count will be decreased, but option
+ // shouldn't be restored yet, so the intermediate EOL or initial WS won't
+ // be recognized.
+ EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume ')'
+ s_string.handle(lexer); // consume 'c'
+ EXPECT_EQ(1, s_crlf.getParenCount(lexer));
+
+ // Close the outermost parentheses. count will be reset to 0, and original
+ // options are restored.
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+
+ // Now, temporarily disabled options are restored: Both EOL and the
+ // initial WS are recognized
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, unbalancedParentheses) {
+ // Only closing paren is provided. We prepend a \n to check if it's
+ // correctly canceled after detecting the error.
+ ss << "\n)";
+ ss << "(a";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // consume '\n'
+ EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); // this \n was remembered
+
+ // Now checking ')'. The result should be error, count shouldn't be
+ // changed. "last EOL" should be canceled.
+ EXPECT_EQ(0, s_crlf.getParenCount(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(0, s_crlf.getParenCount(lexer));
+ ASSERT_EQ(Token::ERROR, s_crlf.getToken(lexer).getType());
+ EXPECT_EQ(Token::UNBALANCED_PAREN, s_crlf.getToken(lexer).getErrorCode());
+ EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
+
+ // Reach EOF with a dangling open parenthesis.
+ EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '('
+ s_string.handle(lexer); // consume 'a'
+ EXPECT_EQ(1, s_crlf.getParenCount(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // reach EOF
+ ASSERT_EQ(Token::ERROR, s_crlf.getToken(lexer).getType());
+ EXPECT_EQ(Token::UNBALANCED_PAREN, s_crlf.getToken(lexer).getErrorCode());
+ EXPECT_EQ(0, s_crlf.getParenCount(lexer)); // should be reset to 0
+}
+
+TEST_F(MasterLexerStateTest, startToComment) {
+ // Begin with 'start', detect space, then encounter a comment. Skip
+ // the rest of the line, and recognize the new line. Note that the
+ // second ';' is simply ignored.
+ ss << " ;a;\n";
+ ss << ";a;"; // Likewise, but the comment ends with EOF.
+ lexer.pushSource(ss);
+
+ // Initial whitespace (asked for in common_options)
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
+ // Comment ending with EOL
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+ // Comment ending with EOF
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, commentAfterParen) {
+ // comment after an opening parenthesis. The code that is tested by
+ // other tests should also ensure that it works correctly, but we
+ // check it explicitly.
+ ss << "( ;this is a comment\na)\n";
+ lexer.pushSource(ss);
+
+ // consume '(', skip comments, consume 'a', then consume ')'
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer);
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, crlf) {
+ ss << "\r\n"; // case 1
+ ss << "\r "; // case 2
+ ss << "\r;comment\na"; // case 3
+ ss << "\r"; // case 4
+ lexer.pushSource(ss);
+
+ // 1. A sequence of \r, \n is recognized as a single 'end-of-line'
+ EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
+ s_crlf.handle(lexer); // recognize '\n'
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+ EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
+
+ // 2. Single '\r' (not followed by \n) is recognized as a single
+ // 'end-of-line'. then there will be "initial WS"
+ EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
+ // see ' ', "unget" it
+ s_crlf.handle(lexer);
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize ' '
+ EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
+
+ // 3. comment between \r and \n
+ EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
+ // skip comments, recognize '\n'
+ s_crlf.handle(lexer);
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // skip 'a'
+
+ // 4. \r then EOF
+ EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
+ // see EOF, then "unget" it
+ s_crlf.handle(lexer);
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize EOF
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+// Commonly used check for string related test cases, checking if the given
+// token has expected values.
+void
+stringTokenCheck(const std::string& expected, const MasterToken& token,
+ bool quoted = false)
+{
+ EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType());
+ EXPECT_EQ(expected, token.getString());
+ const std::string actual(token.getStringRegion().beg,
+ 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*>(0), token.getStringRegion().beg);
+ EXPECT_EQ(0, *(token.getStringRegion().beg + token.getStringRegion().len));
+}
+
+TEST_F(MasterLexerStateTest, string) {
+ // Check with simple strings followed by separate characters
+ ss << "followed-by-EOL\n";
+ ss << "followed-by-CR\r";
+ ss << "followed-by-space ";
+ ss << "followed-by-tab\t";
+ ss << "followed-by-comment;this is comment and ignored\n";
+ ss << "followed-by-paren(closing)";
+ ss << "followed-by-EOF";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \n
+ EXPECT_FALSE(s_string.wasLastEOL(lexer));
+ stringTokenCheck("followed-by-EOL", s_string.getToken(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \r
+ stringTokenCheck("followed-by-CR", s_string.getToken(lexer));
+ EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // handle \r...
+ s_crlf.handle(lexer); // ...and skip it
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' '
+ stringTokenCheck("followed-by-space", s_string.getToken(lexer));
+
+ // skip ' ', then recognize the next string
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \t
+ stringTokenCheck("followed-by-tab", s_string.getToken(lexer));
+
+ // skip \t, then recognize the next string
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see comment
+ stringTokenCheck("followed-by-comment", s_string.getToken(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see '('
+ stringTokenCheck("followed-by-paren", s_string.getToken(lexer));
+ EXPECT_EQ(&s_string, State::start(lexer, common_options)); // str in ()
+ s_string.handle(lexer); // recognize the str, see ')'
+ stringTokenCheck("closing", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see EOF
+ stringTokenCheck("followed-by-EOF", s_string.getToken(lexer));
+}
+
+TEST_F(MasterLexerStateTest, stringEscape) {
+ // some of the separate characters should be considered part of the
+ // string if escaped.
+ ss << "escaped\\ space ";
+ ss << "escaped\\\ttab ";
+ ss << "escaped\\(paren ";
+ ss << "escaped\\)close ";
+ ss << "escaped\\;comment ";
+ ss << "escaped\\\\ backslash "; // second '\' shouldn't escape ' '
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\ space", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\\ttab", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\(paren", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\)close", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\;comment", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' in mid
+ stringTokenCheck("escaped\\\\", s_string.getToken(lexer));
+
+ // Confirm the word that follows the escaped '\' is correctly recognized.
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("backslash", s_string.getToken(lexer));
+}
+
+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
+ // in practice anyway)
+ ss << "\"quoted()\t\rstring\" ";
+ ss << "\"escape\\ in quote\" ";
+ ss << "\"escaped\\\"\" ";
+ ss << "\"escaped backslash\\\\\" ";
+ ss << "\"no;comment\"";
+ lexer.pushSource(ss);
+
+ // by default, '"' is unexpected (when QSTRING is not specified),
+ // and it returns MasterToken::UNEXPECTED_QUOTES.
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::UNEXPECTED_QUOTES, s_string.getToken(lexer).getErrorCode());
+ // Read it as a QSTRING.
+ s_qstring.handle(lexer); // recognize quoted str, see \n
+ stringTokenCheck("ignore-quotes", s_qstring.getToken(lexer), true);
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
+ EXPECT_TRUE(s_string.wasLastEOL(lexer));
+
+ // If QSTRING is specified in option, '"' is regarded as a beginning of
+ // a quoted string.
+ const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ EXPECT_FALSE(s_string.wasLastEOL(lexer)); // EOL is canceled due to '"'
+ 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);
+ stringTokenCheck("quoted()\t\rstring", s_string.getToken(lexer), true);
+
+ // escape character mostly doesn't have any effect in the qstring
+ // processing
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("escape\\ in quote", s_string.getToken(lexer), true);
+
+ // The only exception is the quotation mark itself. Note that the escape
+ // only works on the quotation mark immediately after it.
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("escaped\"", s_string.getToken(lexer), true);
+
+ // quoted '\' then '"'. Unlike the previous case '"' shouldn't be
+ // escaped.
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("escaped backslash\\\\", s_string.getToken(lexer), true);
+
+ // ';' has no meaning in a quoted string (not indicating a comment)
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("no;comment", s_string.getToken(lexer), true);
+}
+
+TEST_F(MasterLexerStateTest, brokenQuotedString) {
+ ss << "\"unbalanced-quote\n";
+ ss << "\"quoted\\\n\" ";
+ ss << "\"unclosed quote and EOF";
+ lexer.pushSource(ss);
+
+ // EOL is encountered without closing the quote
+ const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
+ EXPECT_EQ(Token::UNBALANCED_QUOTES,
+ s_qstring.getToken(lexer).getErrorCode());
+ // We can resume after the error from the '\n'
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+ // \n is okay in a quoted string if escaped
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("quoted\\\n", s_string.getToken(lexer), true);
+
+ // EOF is encountered without closing the quote
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
+ EXPECT_EQ(Token::UNEXPECTED_END, s_qstring.getToken(lexer).getErrorCode());
+ // If we continue we'll simply see the EOF
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, basicNumbers) {
+ ss << "0 ";
+ ss << "1 ";
+ ss << "12345 ";
+ ss << "4294967295 "; // 2^32-1
+ 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
+ // sure none of the above messed up
+ // the tokenizer
+ lexer.pushSource(ss);
+
+ // 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, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(1, s_number.getToken(lexer).getNumber());
+
+ 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, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(4294967295u, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+ s_number.getToken(lexer).getErrorCode());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+ s_number.getToken(lexer).getErrorCode());
+
+ 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, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(42, s_number.getToken(lexer).getNumber());
+
+ 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, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(37, s_number.getToken(lexer).getNumber());
+
+ // If we continue we'll simply see the EOF
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+// 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
+ // be interpreted as strings
+ ss << "123\\456 "; // Numbers containing escaped digits are
+ // interpreted as strings
+ ss << "3scaped\\ space ";
+ ss << "3scaped\\\ttab ";
+ ss << "3scaped\\(paren ";
+ ss << "3scaped\\)close ";
+ ss << "3scaped\\;comment ";
+ ss << "3scaped\\\\ 8ackslash "; // second '\' shouldn't escape ' '
+
+ 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, options));
+ s_number.handle(lexer);
+ stringTokenCheck("123abc456", s_number.getToken(lexer), false);
+
+ 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, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\ space", s_number.getToken(lexer));
+
+ 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, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\(paren", s_number.getToken(lexer));
+
+ 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, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\;comment", s_number.getToken(lexer));
+
+ 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, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("8ackslash", s_number.getToken(lexer));
+
+ // If we continue we'll simply see the EOF
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+} // end anonymous namespace
+
diff --git a/src/lib/dns/tests/master_lexer_token_unittest.cc b/src/lib/dns/tests/master_lexer_token_unittest.cc
new file mode 100644
index 0000000..4a3787c
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_token_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/master_lexer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::dns;
+
+namespace {
+
+const char TEST_STRING[] = "string token";
+// This excludes the ending \0 character
+const size_t TEST_STRING_LEN = sizeof(TEST_STRING) - 1;
+
+class MasterLexerTokenTest : public ::testing::Test {
+protected:
+ MasterLexerTokenTest() :
+ token_eof(MasterToken::END_OF_FILE),
+ token_str(TEST_STRING, TEST_STRING_LEN),
+ token_num(42),
+ token_err(MasterToken::UNEXPECTED_END)
+ {}
+
+ 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(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 MasterToken::StringRegion str_region =
+ token_str.getStringRegion();
+ EXPECT_EQ(TEST_STRING, str_region.beg);
+ EXPECT_EQ(TEST_STRING_LEN, str_region.len);
+
+ // Even if the stored string contains a nul character (in this case,
+ // it happens to be at the end of the string, but could be in the middle),
+ // getString() should return a string object containing the nul.
+ std::string expected_str("string token");
+ expected_str.push_back('\0');
+ EXPECT_EQ(expected_str,
+ 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(MasterToken::QSTRING,
+ MasterToken(TEST_STRING, sizeof(TEST_STRING), true).
+ getType());
+ // if we explicitly set 'quoted' to false, it should be normal string
+ EXPECT_EQ(MasterToken::STRING,
+ MasterToken(TEST_STRING, sizeof(TEST_STRING), false).
+ getType());
+
+ // getString/StringRegion() aren't allowed for non string(-variant) types
+ EXPECT_THROW(token_eof.getString(), isc::InvalidOperation);
+ EXPECT_THROW(token_eof.getString(strval), isc::InvalidOperation);
+ EXPECT_THROW(token_num.getString(), isc::InvalidOperation);
+ EXPECT_THROW(token_num.getString(strval), isc::InvalidOperation);
+ EXPECT_THROW(token_eof.getStringRegion(), isc::InvalidOperation);
+ EXPECT_THROW(token_num.getStringRegion(), isc::InvalidOperation);
+}
+
+TEST_F(MasterLexerTokenTest, numbers) {
+ EXPECT_EQ(42, token_num.getNumber());
+ EXPECT_EQ(MasterToken::NUMBER, token_num.getType());
+
+ // It's copyable and assignable.
+ MasterToken token(token_num);
+ EXPECT_EQ(42, token.getNumber());
+ EXPECT_EQ(MasterToken::NUMBER, token.getType());
+
+ token = token_num;
+ EXPECT_EQ(42, token.getNumber());
+ EXPECT_EQ(MasterToken::NUMBER, token.getType());
+
+ // it's okay to replace it with a different type of token
+ token = token_eof;
+ EXPECT_EQ(MasterToken::END_OF_FILE, token.getType());
+
+ // Possible max value
+ token = MasterToken(0xffffffff);
+ EXPECT_EQ(4294967295u, token.getNumber());
+
+ // getNumber() isn't allowed for non number types
+ EXPECT_THROW(token_eof.getNumber(), isc::InvalidOperation);
+ EXPECT_THROW(token_str.getNumber(), isc::InvalidOperation);
+}
+
+TEST_F(MasterLexerTokenTest, novalues) {
+ // Just checking we can construct them and getType() returns correct value.
+ 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(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(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", MasterToken(MasterToken::NOT_STARTED).
+ getErrorText());
+ EXPECT_EQ("unbalanced parentheses",
+ MasterToken(MasterToken::UNBALANCED_PAREN).
+ getErrorText());
+ EXPECT_EQ("unbalanced quotes", MasterToken(MasterToken::UNBALANCED_QUOTES).
+ getErrorText());
+ EXPECT_EQ("no token produced", MasterToken(MasterToken::NO_TOKEN_PRODUCED).
+ getErrorText());
+ EXPECT_EQ("number out of range",
+ MasterToken(MasterToken::NUMBER_OUT_OF_RANGE).
+ getErrorText());
+ EXPECT_EQ("not a valid number",
+ MasterToken(MasterToken::BAD_NUMBER).getErrorText());
+ EXPECT_EQ("unexpected quotes",
+ MasterToken(MasterToken::UNEXPECTED_QUOTES).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 '8' (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).
+ //
+ // Note: if you fix this testcase, you probably want to update the
+ // getErrorText() tests above too.
+ EXPECT_THROW(MasterToken(MasterToken::ErrorCode(8)),
+ isc::InvalidParameter);
+
+ // Check the coexistence of "from number" and "from error-code"
+ // constructors won't cause confusion.
+ 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
new file mode 100644
index 0000000..a97e18d
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -0,0 +1,521 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/master_lexer.h>
+#include <dns/master_lexer_state.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <string>
+#include <sstream>
+
+using namespace isc::dns;
+using std::string;
+using std::stringstream;
+using boost::lexical_cast;
+using boost::scoped_ptr;
+using master_lexer_internal::State;
+
+namespace {
+
+class MasterLexerTest : public ::testing::Test {
+protected:
+ MasterLexerTest() :
+ expected_stream_name("stream-" + lexical_cast<string>(&ss))
+ {}
+
+ MasterLexer lexer;
+ stringstream ss;
+ const string expected_stream_name;
+};
+
+// Commonly used check case where the input sources stack is empty.
+void
+checkEmptySource(const MasterLexer& lexer) {
+ EXPECT_TRUE(lexer.getSourceName().empty());
+ EXPECT_EQ(0, lexer.getSourceLine());
+ EXPECT_EQ(0, lexer.getPosition());
+}
+
+TEST_F(MasterLexerTest, preOpen) {
+ // Initially sources stack is empty.
+ checkEmptySource(lexer);
+}
+
+TEST_F(MasterLexerTest, pushStream) {
+ EXPECT_EQ(0, lexer.getSourceCount());
+ ss << "test";
+ lexer.pushSource(ss);
+ EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ EXPECT_EQ(1, lexer.getSourceCount());
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // 4 = len("test")
+
+ // From the point of view of this test, we only have to check (though
+ // indirectly) getSourceLine calls InputSource::getCurrentLine. It should
+ // return 1 initially.
+ EXPECT_EQ(1, lexer.getSourceLine());
+
+ // By popping it the stack will be empty again.
+ lexer.popSource();
+ EXPECT_EQ(0, lexer.getSourceCount());
+ checkEmptySource(lexer);
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // this shouldn't change
+}
+
+TEST_F(MasterLexerTest, pushStreamFail) {
+ // Pretend a "bad" thing happened in the stream. This will make the
+ // initialization throw an exception.
+ ss << "test";
+ ss.setstate(std::ios_base::badbit);
+
+ EXPECT_THROW(lexer.pushSource(ss), isc::Unexpected);
+}
+
+TEST_F(MasterLexerTest, pushFile) {
+ // We use zone file (-like) data, but in this test that actually doesn't
+ // matter.
+ EXPECT_EQ(0, lexer.getSourceCount());
+ EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt"));
+ EXPECT_EQ(1, lexer.getSourceCount());
+ EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+ EXPECT_EQ(1, lexer.getSourceLine());
+
+ // 143 = size of the test zone file. hardcode it assuming it won't change
+ // too often.
+ EXPECT_EQ(143, lexer.getTotalSourceSize());
+
+ lexer.popSource();
+ checkEmptySource(lexer);
+ EXPECT_EQ(0, lexer.getSourceCount());
+ EXPECT_EQ(143, lexer.getTotalSourceSize()); // this shouldn't change
+
+ // If we give a non null string pointer, its content will be intact
+ // if pushSource succeeds.
+ std::string error_txt = "dummy";
+ EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt",
+ &error_txt));
+ EXPECT_EQ("dummy", error_txt);
+}
+
+TEST_F(MasterLexerTest, pushBadFileName) {
+ EXPECT_THROW(lexer.pushSource(0), isc::InvalidParameter);
+}
+
+TEST_F(MasterLexerTest, pushFileFail) {
+ // The file to be pushed doesn't exist. pushSource() fails and
+ // some non empty error string should be set.
+ std::string error_txt;
+ EXPECT_TRUE(error_txt.empty());
+ EXPECT_FALSE(lexer.pushSource("no-such-file", &error_txt));
+ EXPECT_FALSE(error_txt.empty());
+
+ // It's safe to pass null error_txt (either explicitly or implicitly as
+ // the default)
+ EXPECT_FALSE(lexer.pushSource("no-such-file", 0));
+ EXPECT_FALSE(lexer.pushSource("no-such-file"));
+}
+
+TEST_F(MasterLexerTest, nestedPush) {
+ const string test_txt = "test";
+ ss << test_txt;
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(0, lexer.getPosition());
+
+ EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+
+ // Read the string; getPosition() should reflect that.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size(), lexer.getPosition());
+
+ // We can push another source without popping the previous one.
+ lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
+ EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+ EXPECT_EQ(143 + test_txt.size(),
+ lexer.getTotalSourceSize()); // see above for magic nums
+
+ // the next token should be the EOL (skipping a comment line), its
+ // position in the file is 35 (hardcoded).
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+
+ // popSource() works on the "topmost" (last-pushed) source
+ lexer.popSource();
+ EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+
+ // pop shouldn't change the total size and the current position
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+
+ lexer.popSource();
+ EXPECT_TRUE(lexer.getSourceName().empty());
+
+ // size and position still shouldn't change
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+}
+
+TEST_F(MasterLexerTest, unknownSourceSize) {
+ // Similar to the previous case, but the size of the second source
+ // will be considered "unknown" (by emulating an error).
+ ss << "test";
+ lexer.pushSource(ss);
+ EXPECT_EQ(4, lexer.getTotalSourceSize());
+
+ stringstream ss2;
+ ss2.setstate(std::ios_base::failbit); // this will make the size unknown
+ lexer.pushSource(ss2);
+ // Then the total size is also unknown.
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
+
+ // Even if we pop that source, the size is still unknown.
+ lexer.popSource();
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
+}
+
+TEST_F(MasterLexerTest, invalidPop) {
+ // popSource() cannot be called if the sources stack is empty.
+ EXPECT_THROW(lexer.popSource(), isc::InvalidOperation);
+}
+
+// Test it is not possible to get token when no source is available.
+TEST_F(MasterLexerTest, noSource) {
+ EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
+}
+
+// Test getting some tokens. It also check basic behavior of getPosition().
+TEST_F(MasterLexerTest, getNextToken) {
+ ss << "\n \n\"STRING\"\n";
+ lexer.pushSource(ss);
+
+ // First, the newline should get out.
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ // Then the whitespace, if we specify the option.
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition());
+ // The newline
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5, lexer.getPosition()); // 1st \n + 3 spaces, then 2nd \n
+ // The (quoted) string
+ EXPECT_EQ(MasterToken::QSTRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(5 + 8, lexer.getPosition()); // 8 = len("STRING') + quotes
+
+ // And the end of line and file
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // previous + 3rd \n
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // position doesn't change
+}
+
+// Test we correctly find end of file.
+TEST_F(MasterLexerTest, eof) {
+ // Let the ss empty.
+ lexer.pushSource(ss);
+
+ // The first one is found to be EOF
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ // And it stays on EOF for any following attempts
+ 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(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Check we properly return error when there's an opened parentheses and no
+// closing one
+TEST_F(MasterLexerTest, getUnbalancedParen) {
+ ss << "(string";
+ lexer.pushSource(ss);
+
+ // The string gets out first
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+ // Then an unbalanced parenthesis
+ EXPECT_EQ(MasterToken::UNBALANCED_PAREN,
+ lexer.getNextToken().getErrorCode());
+ // And then EOF
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Check we properly return error when there's an opened quoted string and no
+// closing one
+TEST_F(MasterLexerTest, getUnbalancedString) {
+ ss << "\"string";
+ lexer.pushSource(ss);
+
+ // Then an unbalanced qstring (reported as an unexpected end)
+ EXPECT_EQ(MasterToken::UNEXPECTED_END,
+ lexer.getNextToken(MasterLexer::QSTRING).getErrorCode());
+ // And then EOF
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Test ungetting tokens works. Also check getPosition() is adjusted
+TEST_F(MasterLexerTest, ungetToken) {
+ ss << "\n (\"string\"\n) more";
+ lexer.pushSource(ss);
+
+ // Try getting the newline
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ // Return it and get again
+ lexer.ungetToken();
+ EXPECT_EQ(0, lexer.getPosition());
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ // Get the string and return it back
+ EXPECT_EQ(MasterToken::QSTRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(string("\n (\"string\"").size(), lexer.getPosition());
+ lexer.ungetToken();
+ EXPECT_EQ(1, lexer.getPosition()); // back to just after 1st \n
+ // But if we change the options, it honors them
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::QSTRING |
+ MasterLexer::INITIAL_WS).getType());
+ // Get to the "more" string
+ EXPECT_EQ(MasterToken::QSTRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ 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(MasterToken::STRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+}
+
+// Check ungetting token without overriding the start method. We also
+// check it works well with changing options between the calls.
+TEST_F(MasterLexerTest, ungetRealOptions) {
+ ss << " \n";
+ lexer.pushSource(ss);
+
+ // If we call it the usual way, it skips up to the newline and returns
+ // it
+ 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(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+}
+
+// Check the initial whitespace is found even in the first line of included
+// file. It also confirms getPosition() works for multiple sources, each
+// of which is partially parsed.
+TEST_F(MasterLexerTest, includeAndInitialWS) {
+ ss << " \n";
+ lexer.pushSource(ss);
+
+ stringstream ss2;
+ ss2 << " \n";
+
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ lexer.pushSource(ss2);
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition()); // should be sum of pushed positions.
+}
+
+// Test only one token can be ungotten
+TEST_F(MasterLexerTest, ungetTwice) {
+ ss << "\n";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ // Unget the token. It can be done once
+ lexer.ungetToken();
+ // But not twice
+ EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Test we can't unget a token before we get one
+TEST_F(MasterLexerTest, ungetBeforeGet) {
+ lexer.pushSource(ss); // Just to eliminate the missing source problem
+ EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Test we can't unget a token after a source switch, even when we got
+// something before.
+TEST_F(MasterLexerTest, ungetAfterSwitch) {
+ ss << "\n\n";
+ lexer.pushSource(ss);
+ 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(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
+//
+// The 'lexer' should be at a position before two consecutive '\n's.
+// The first one will be recognized, and the second one will be considered an
+// unexpected token. Then this helper consumes the second '\n', so the caller
+// can continue the test after these '\n's.
+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);
+
+ // Then skip the 2nd '\n'
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+}
+
+// Common checks regarding expected/unexpected end-of-file
+//
+// The 'lexer' should be at a position just before an 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);
+
+ // 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);
+
+ // 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 << "123abc "; // starting with digits, but resulting in a string
+ ss << "86400";
+ lexer.pushSource(ss);
+
+ // Expecting a number string and get one.
+ EXPECT_EQ(3600,
+ lexer.getNextToken(MasterToken::NUMBER).getNumber());
+ eolCheck(lexer, MasterToken::NUMBER);
+
+ // 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());
+
+ // 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_callbacks_test.cc b/src/lib/dns/tests/master_loader_callbacks_test.cc
new file mode 100644
index 0000000..e89fa92
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_callbacks_test.cc
@@ -0,0 +1,79 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrset.h>
+#include <dns/name.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+#include <functional>
+
+namespace {
+
+using std::string;
+using namespace isc::dns;
+namespace ph = std::placeholders;
+
+class MasterLoaderCallbacksTest : public ::testing::Test {
+protected:
+ MasterLoaderCallbacksTest() :
+ last_was_error_(false), // Not needed, but then cppcheck complains
+ issue_called_(false),
+ rrset_(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
+ RRTTL(3600))),
+ error_(std::bind(&MasterLoaderCallbacksTest::checkCallback, this,
+ true, ph::_1, ph::_2, ph::_3)),
+ warning_(std::bind(&MasterLoaderCallbacksTest::checkCallback, this,
+ false, ph::_1, ph::_2, ph::_3)),
+ callbacks_(error_, warning_)
+ {}
+
+ void checkCallback(bool error, const string& source, size_t line,
+ const string& reason)
+ {
+ issue_called_ = true;
+ last_was_error_ = error;
+ EXPECT_EQ("source", source);
+ EXPECT_EQ(1, line);
+ EXPECT_EQ("reason", reason);
+ }
+ bool last_was_error_;
+ bool issue_called_;
+ const RRsetPtr rrset_;
+ const MasterLoaderCallbacks::IssueCallback error_, warning_;
+ MasterLoaderCallbacks callbacks_;
+};
+
+// Check the constructor rejects empty callbacks, but accepts non-empty ones
+TEST_F(MasterLoaderCallbacksTest, constructor) {
+ EXPECT_THROW(MasterLoaderCallbacks(MasterLoaderCallbacks::IssueCallback(),
+ warning_), isc::InvalidParameter);
+ EXPECT_THROW(MasterLoaderCallbacks(error_,
+ MasterLoaderCallbacks::IssueCallback()),
+ isc::InvalidParameter);
+ EXPECT_NO_THROW(MasterLoaderCallbacks(error_, warning_));
+}
+
+// Call the issue callbacks
+TEST_F(MasterLoaderCallbacksTest, issueCall) {
+ callbacks_.error("source", 1, "reason");
+ EXPECT_TRUE(last_was_error_);
+ EXPECT_TRUE(issue_called_);
+
+ issue_called_ = false;
+
+ callbacks_.warning("source", 1, "reason");
+ EXPECT_FALSE(last_was_error_);
+ EXPECT_TRUE(issue_called_);
+}
+
+}
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
new file mode 100644
index 0000000..ce7b850
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -0,0 +1,967 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#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/rrttl.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <functional>
+#include <string>
+#include <vector>
+#include <list>
+#include <sstream>
+
+using namespace isc::dns;
+using std::vector;
+using std::string;
+using std::list;
+using std::stringstream;
+using std::endl;
+using boost::lexical_cast;
+namespace ph = std::placeholders;
+
+namespace {
+class MasterLoaderTest : public ::testing::Test {
+public:
+ MasterLoaderTest() :
+ callbacks_(std::bind(&MasterLoaderTest::callback, this,
+ &errors_, ph::_1, ph::_2, ph::_3),
+ std::bind(&MasterLoaderTest::callback, this,
+ &warnings_, ph::_1, ph::_2, ph::_3)) {
+ }
+
+ void TearDown() {
+ // Check there are no more RRs we didn't expect
+ EXPECT_TRUE(rrsets_.empty());
+ }
+
+ /// Concatenate file, line, and reason, and add it to either errors
+ /// or warnings
+ void callback(vector<string>* target, const std::string& file, size_t line,
+ const std::string& reason) {
+ std::stringstream ss;
+ ss << reason << " [" << file << ":" << line << "]";
+ target->push_back(ss.str());
+ }
+
+ 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) {
+ loader_.reset(new MasterLoader(file, origin, rrclass, callbacks_,
+ std::bind(&MasterLoaderTest::addRRset,
+ this, ph::_1, ph::_2, ph::_3,
+ ph::_4, ph::_5),
+ options));
+ }
+
+ void setLoader(std::istream& stream, const Name& origin,
+ const RRClass& rrclass, const MasterLoader::Options options) {
+ loader_.reset(new MasterLoader(stream, origin, rrclass, callbacks_,
+ std::bind(&MasterLoaderTest::addRRset,
+ this, ph::_1, ph::_2, ph::_3,
+ ph::_4, ph::_5),
+ options));
+ }
+
+ static string prepareZone(const string& line, bool include_last) {
+ string result;
+ result += "example.org. 3600 IN SOA ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200\n";
+ result += line;
+ if (include_last) {
+ result += "\n";
+ result += "correct 3600 IN A 192.0.2.2\n";
+ }
+ return (result);
+ }
+
+ 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,
+ const RRTTL& rrttl = RRTTL(3600)) {
+ ASSERT_FALSE(rrsets_.empty());
+ RRsetPtr current = rrsets_.front();
+ rrsets_.pop_front();
+
+ EXPECT_EQ(Name(name), current->getName());
+ EXPECT_EQ(type, current->getType());
+ EXPECT_EQ(RRClass::IN(), current->getClass());
+ EXPECT_EQ(rrttl, current->getTTL());
+ ASSERT_EQ(1, current->getRdataCount());
+ EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
+ compare(current->getRdataIterator()->getCurrent()))
+ << data << " vs. "
+ << current->getRdataIterator()->getCurrent().toText();
+ }
+
+ void checkBasicRRs() {
+ 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");
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+ }
+
+ void checkARR(const string& name) {
+ checkRR(name, RRType::A(), "192.0.2.1");
+ }
+
+ MasterLoaderCallbacks callbacks_;
+ 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(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+ RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+
+ // The following three should be set to 0 initially in case the loader
+ // is constructed from a file name.
+ EXPECT_EQ(0, loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ // Hardcode expected values taken from the test data file, assuming it
+ // won't change too often.
+ EXPECT_EQ(550, loader_->getSize());
+ EXPECT_EQ(550, loader_->getPosition());
+
+ checkBasicRRs();
+}
+
+// Test the $INCLUDE directive
+TEST_F(MasterLoaderTest, include) {
+ // Test various cases of include
+ const char* includes[] = {
+ "$include",
+ "$INCLUDE",
+ "$Include",
+ "$InCluDe",
+ "\"$INCLUDE\"",
+ 0
+ };
+ for (const char** include = includes; *include != 0; ++include) {
+ SCOPED_TRACE(*include);
+
+ clear();
+ // Prepare input source that has the include and some more data
+ // below (to see it returns back to the original source).
+ const string include_str = string(*include) + " " +
+ TEST_DATA_SRCDIR + "/example.org\nwww 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(include_str);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ checkBasicRRs();
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+ }
+}
+
+TEST_F(MasterLoaderTest, includeAndIncremental) {
+ // Check getSize() and getPosition() are adjusted before and after
+ // $INCLUDE.
+ const string first_rr = "before.example.org. 0 A 192.0.2.1\n";
+ const string include_str = "$INCLUDE " TEST_DATA_SRCDIR "/example.org";
+ const string zone_data = first_rr + include_str + "\n" +
+ "www 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(zone_data);
+ setLoader(ss, Name("example.org."), RRClass::IN(), MasterLoader::DEFAULT);
+
+ // On construction, getSize() returns the size of the data (exclude the
+ // the file to be included); position is set to 0.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ // Read the first RR. getSize() doesn't change; position should be
+ // at the end of the first line.
+ loader_->loadIncremental(1);
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(first_rr.size(), loader_->getPosition());
+
+ // Read next 4. It includes $INCLUDE processing. Magic number of 550
+ // is the size of the test zone file (see above); 507 is the position in
+ // the file at the end of 4th RR (due to extra comments it's smaller than
+ // the file size).
+ loader_->loadIncremental(4);
+ EXPECT_EQ(zone_data.size() + 550, loader_->getSize());
+ EXPECT_EQ(first_rr.size() + include_str.size() + 507,
+ loader_->getPosition());
+
+ // Read the last one. At this point getSize and getPosition return
+ // the same value, indicating progress of 100%.
+ loader_->loadIncremental(1);
+ EXPECT_EQ(zone_data.size() + 550, loader_->getSize());
+ EXPECT_EQ(zone_data.size() + 550, loader_->getPosition());
+
+ // we were not interested in checking RRs in this test. clear them to
+ // not confuse TearDown().
+ rrsets_.clear();
+}
+
+// A commonly used helper to check callback message.
+void
+checkCallbackMessage(const string& actual_msg, const string& expected_msg,
+ size_t expected_line) {
+ // The actual message should begin with the expected message.
+ EXPECT_EQ(0, actual_msg.find(expected_msg)) << "actual message: " <<
+ actual_msg << " expected: " <<
+ expected_msg;
+
+ // and it should end with "...:<line_num>]"
+ const string line_desc = ":" + lexical_cast<string>(expected_line) + "]";
+ EXPECT_EQ(actual_msg.size() - line_desc.size(),
+ actual_msg.find(line_desc)) << "Expected on line " <<
+ expected_line;
+}
+
+TEST_F(MasterLoaderTest, origin) {
+ // Various forms of the directive
+ const char* origins[] = {
+ "$origin",
+ "$ORIGIN",
+ "$Origin",
+ "$OrigiN",
+ "\"$ORIGIN\"",
+ 0
+ };
+ for (const char** origin = origins; *origin != 0; ++origin) {
+ SCOPED_TRACE(*origin);
+
+ clear();
+ const string directive = *origin;
+ const string input =
+ "@ 1H IN A 192.0.2.1\n" +
+ directive + " sub.example.org.\n"
+ "\"www\" 1H IN A 192.0.2.1\n" +
+ // Relative name in the origin
+ directive + " relative\n"
+ "@ 1H IN A 192.0.2.1\n"
+ // Origin is _not_ used here (absolute name)
+ "noorigin.example.org. 60M IN A 192.0.2.1\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ // There's a relative origin in it, we warn about that.
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0),
+ "The new origin is relative, did you really mean "
+ "relative.sub.example.org.?", 4);
+
+ checkARR("example.org");
+ checkARR("www.sub.example.org");
+ checkARR("relative.sub.example.org");
+ checkARR("noorigin.example.org");
+ }
+}
+
+// Test the source is correctly popped even after error
+TEST_F(MasterLoaderTest, popAfterError) {
+ const string include_str = "$include " TEST_DATA_SRCDIR
+ "/broken.zone\nwww 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(include_str);
+ // We perform the test with MANY_ERRORS, we want to see what happens
+ // after the error.
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_EQ(1, errors_.size()); // For the broken RR
+ EXPECT_EQ(1, warnings_.size()); // For missing EOLN
+
+ // The included file doesn't contain anything usable, but the
+ // line after the include should be there.
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+}
+
+// Check it works the same when created based on a stream, not filename
+TEST_F(MasterLoaderTest, streamConstructor) {
+ const string zone_data(prepareZone("", true));
+ stringstream zone_stream(zone_data);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+
+ // Unlike the basicLoad test, if we construct the loader from a stream
+ // getSize() returns the data size in the stream immediately after the
+ // construction.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+
+ 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("correct.example.org", RRType::A(), "192.0.2.2");
+
+ // On completion of the load, both getSize() and getPosition() return the
+ // size of the data.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(zone_data.size(), loader_->getPosition());
+}
+
+// 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_->loadedSuccessfully());
+ EXPECT_FALSE(loader_->loadIncremental(2));
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+
+ 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(loader_->loadedSuccessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ checkRR("www.example.org", RRType::A(), "192.0.2.1");
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::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("example.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* const line; // The broken line in master file
+ const char* const reason; // If non null, the reason string
+ const char* const problem; // Description of the problem for SCOPED_TRACE
+} const error_cases[] = {
+ { "www... 3600 IN A 192.0.2.1", 0, "Invalid name" },
+ { "www FORTNIGHT IN A 192.0.2.1", 0, "Invalid TTL" },
+ { "www 3600 XX A 192.0.2.1", 0, "Invalid class" },
+ { "www 3600 IN A bad_ip", 0, "Invalid Rdata" },
+
+ // Parameter ordering errors
+ { "www IN A 3600 192.168.2.7",
+ "createRdata from text failed: Bad IN/A RDATA text: '3600'",
+ "Incorrect order of class, TTL and type" },
+ { "www A IN 3600 192.168.2.8",
+ "createRdata from text failed: Bad IN/A RDATA text: 'IN'",
+ "Incorrect order of class, TTL and type" },
+ { "www 3600 A IN 192.168.2.7",
+ "createRdata from text failed: Bad IN/A RDATA text: 'IN'",
+ "Incorrect order of class, TTL and type" },
+ { "www A 3600 IN 192.168.2.8",
+ "createRdata from text failed: Bad IN/A RDATA text: '3600'",
+ "Incorrect order of class, TTL and type" },
+
+ // Missing type and Rdata
+ { "www", "unexpected end of input", "Missing type and Rdata" },
+ { "www 3600", "unexpected end of input", "Missing type and Rdata" },
+ { "www IN", "unexpected end of input", "Missing type and Rdata" },
+ { "www 3600 IN", "unexpected end of input", "Missing type and Rdata" },
+ { "www IN 3600", "unexpected end of input", "Missing type and Rdata" },
+
+ // Missing Rdata
+ { "www A",
+ "createRdata from text failed: unexpected end of input",
+ "Missing Rdata" },
+ { "www 3600 A",
+ "createRdata from text failed: unexpected end of input",
+ "Missing Rdata" },
+ { "www IN A",
+ "createRdata from text failed: unexpected end of input",
+ "Missing Rdata" },
+ { "www 3600 IN A",
+ "createRdata from text failed: unexpected end of input",
+ "Missing Rdata" },
+ { "www IN 3600 A",
+ "createRdata from text failed: unexpected end of input",
+ "Missing Rdata" },
+
+ { "www 3600 IN", 0, "Unexpected EOLN" },
+ { "www 3600 CH TXT nothing", "Class mismatch: CH vs. IN",
+ "Class mismatch" },
+ { "www \"3600\" IN A 192.0.2.1", 0, "Quoted TTL" },
+ { "www 3600 \"IN\" A 192.0.2.1", 0, "Quoted class" },
+ { "www 3600 IN \"A\" 192.0.2.1", 0, "Quoted type" },
+ { "unbalanced)paren 3600 IN A 192.0.2.1", 0, "Token error 1" },
+ { "www 3600 unbalanced)paren A 192.0.2.1", 0,
+ "Token error 2" },
+ // Check the unknown directive. The rest looks like ordinary RR,
+ // so we see the $ is actually special.
+ { "$UNKNOWN 3600 IN A 192.0.2.1", 0, "Unknown $ directive" },
+ { "$INCLUD " TEST_DATA_SRCDIR "/example.org", "Unknown directive 'INCLUD'",
+ "Include too short" },
+ { "$INCLUDES " TEST_DATA_SRCDIR "/example.org",
+ "Unknown directive 'INCLUDES'", "Include too long" },
+ { "$INCLUDE", "unexpected end of input", "Missing include path" },
+ // The following two error messages are system dependent, omitting
+ { "$INCLUDE /file/not/found", 0, "Include file not found" },
+ { "$INCLUDE /file/not/found example.org. and here goes bunch of garbage",
+ 0, "Include file not found and garbage at the end of line" },
+ { "$ORIGIN", "unexpected end of input", "Missing origin name" },
+ { "$ORIGIN invalid...name", "duplicate period in invalid...name",
+ "Invalid name for origin" },
+ { "$ORIGIN )brokentoken", "unbalanced parentheses",
+ "Broken token in origin" },
+ { "$ORIGIN example.org. garbage", "Extra tokens at the end of line",
+ "Garbage after origin" },
+ { "$ORIGI name.", "Unknown directive 'ORIGI'", "$ORIGIN too short" },
+ { "$ORIGINAL name.", "Unknown directive 'ORIGINAL'", "$ORIGIN too long" },
+ { "$TTL 100 extra-garbage", "Extra tokens at the end of line",
+ "$TTL with extra token" },
+ { "$TTL", "unexpected end of input", "missing TTL" },
+ { "$TTL No-ttl", "Unknown unit used: N in: No-ttl", "bad TTL" },
+ { "$TTL \"100\"", "unexpected quotes", "bad TTL, quoted" },
+ { "$TT 100", "Unknown directive 'TT'", "bad directive, too short" },
+ { "$TTLLIKE 100", "Unknown directive 'TTLLIKE'", "bad directive, extra" },
+ { 0, 0, 0 }
+};
+
+// Test a broken zone is handled properly. We test several problems,
+// both in strict and lenient mode.
+TEST_F(MasterLoaderTest, brokenZone) {
+ for (const ErrorCase* ec = error_cases; ec->line; ++ec) {
+ SCOPED_TRACE(ec->problem);
+ const string zone(prepareZone(ec->line, true));
+
+ {
+ SCOPED_TRACE("Strict mode");
+ clear();
+ stringstream zone_stream(zone);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_THROW(loader_->load(), MasterLoaderError);
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_EQ(1, errors_.size());
+ if (ec->reason) {
+ checkCallbackMessage(errors_.at(0), ec->reason, 2);
+ }
+ 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();
+ stringstream zone_stream(zone);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ 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());
+ }
+
+ {
+ SCOPED_TRACE("Error at EOF");
+ // This case is interesting only in the lenient mode.
+ clear();
+ const string zoneEOF(prepareZone(ec->line, false));
+ stringstream zone_stream(zoneEOF);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_EQ(1, errors_.size()) << errors_[0] << "\n" << errors_[1];
+ // The unexpected EOF warning
+ EXPECT_EQ(1, warnings_.size());
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ EXPECT_TRUE(rrsets_.empty());
+ }
+ }
+}
+
+// Check that a garbage after the include generates an error, but not fatal
+// one (in lenient mode) and we can recover.
+TEST_F(MasterLoaderTest, includeWithGarbage) {
+ // Include an origin (example.org) because we expect it to be handled
+ // soon and we don't want it to break here.
+ const string include_str("$INCLUDE " TEST_DATA_SRCDIR
+ "/example.org example.org. bunch of other stuff\n"
+ "www 3600 IN AAAA 2001:db8::1\n");
+ stringstream zone_stream(include_str);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ ASSERT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
+ // It says something about extra tokens at the end
+ EXPECT_NE(string::npos, errors_[0].find("Extra"));
+ EXPECT_TRUE(warnings_.empty());
+ checkBasicRRs();
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+}
+
+// Check we error about garbage at the end of $ORIGIN line (but the line
+// works).
+TEST_F(MasterLoaderTest, originWithGarbage) {
+ const string origin_str = "$ORIGIN www.example.org. More garbage here\n"
+ "@ 1H IN A 192.0.2.1\n";
+ stringstream ss(origin_str);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ ASSERT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
+ EXPECT_TRUE(warnings_.empty());
+ checkARR("www.example.org");
+}
+
+// Test we can pass both file to include and the origin to switch
+TEST_F(MasterLoaderTest, includeAndOrigin) {
+ // First, switch origin to something else, so we can check it is
+ // switched back.
+ const string include_string = "$ORIGIN www.example.org.\n"
+ "@ 1H IN A 192.0.2.1\n"
+ // Then include the file with data and switch origin back
+ "$INCLUDE " TEST_DATA_SRCDIR "/example.org example.org.\n"
+ // Another RR to see we fall back to the previous origin.
+ "www 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ // Successfully load the data
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+ // And check it's the correct data
+ checkARR("www.example.org");
+ checkBasicRRs();
+ checkARR("www.www.example.org");
+}
+
+// Like above, but the origin after include is bogus. The whole line should
+// be rejected.
+TEST_F(MasterLoaderTest, includeAndBadOrigin) {
+ const string include_string =
+ "$INCLUDE " TEST_DATA_SRCDIR "/example.org example..org.\n"
+ // Another RR to see the switch survives after we exit include
+ "www 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "duplicate period in example..org.",
+ 1);
+ EXPECT_TRUE(warnings_.empty());
+ // And check it's the correct data
+ checkARR("www.example.org");
+}
+
+// Check the origin doesn't get outside of the included file.
+TEST_F(MasterLoaderTest, includeOriginRestore) {
+ const string include_string =
+ "$INCLUDE " TEST_DATA_SRCDIR "/origincheck.txt\n"
+ "@ 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ // Successfully load the data
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+ // And check it's the correct data
+ checkARR("www.example.org");
+ checkARR("example.org");
+}
+
+// Check we restore the last name for initial whitespace when returning from
+// include. But we do produce a warning if there's one just ofter the include.
+TEST_F(MasterLoaderTest, includeAndInitialWS) {
+ const string include_string = "xyz 1H IN A 192.0.2.1\n"
+ "$INCLUDE " TEST_DATA_SRCDIR "/example.org\n"
+ " 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ // Successfully load the data
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0),
+ "Owner name omitted around $INCLUDE, the result might "
+ "not be as expected", 3);
+ checkARR("xyz.example.org");
+ checkBasicRRs();
+ checkARR("xyz.example.org");
+}
+
+// Test for "$TTL"
+TEST_F(MasterLoaderTest, ttlDirective) {
+ stringstream zone_stream;
+
+ // Set the default TTL with $TTL followed by an RR omitting the TTL
+ zone_stream << "$TTL 1800\nexample.org. IN A 192.0.2.1\n";
+ // $TTL can be quoted. Also testing the case of $TTL being changed.
+ zone_stream << "\"$TTL\" 100\na.example.org. IN A 192.0.2.2\n";
+ // Extended TTL form is accepted.
+ zone_stream << "$TTL 1H\nb.example.org. IN A 192.0.2.3\n";
+ // Matching is case insensitive.
+ zone_stream << "$tTl 360\nc.example.org. IN A 192.0.2.4\n";
+ // Maximum allowable TTL
+ zone_stream << "$TTL 2147483647\nd.example.org. IN A 192.0.2.5\n";
+
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ checkRR("example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+ checkRR("a.example.org", RRType::A(), "192.0.2.2", RRTTL(100));
+ checkRR("b.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
+ checkRR("c.example.org", RRType::A(), "192.0.2.4", RRTTL(360));
+ checkRR("d.example.org", RRType::A(), "192.0.2.5", RRTTL(2147483647));
+}
+
+TEST_F(MasterLoaderTest, ttlFromSOA) {
+ // No $TTL, and the SOA doesn't have an explicit TTL field. Its minimum
+ // TTL field will be used as the RR's TTL, and it'll be used as the
+ // default TTL for others.
+ stringstream zone_stream("example.org. IN SOA . . 0 0 0 0 1800\n"
+ "a.example.org. IN A 192.0.2.1\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(1800));
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+
+ // The use of SOA minimum TTL should have caused a warning.
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0),
+ "no TTL specified; using SOA MINTTL instead", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlFromPrevious) {
+ // No available default TTL. 2nd and 3rd RR will use the TTL of the
+ // 1st RR. This will result in a warning, but only for the first time.
+ stringstream zone_stream("a.example.org. 1800 IN A 192.0.2.1\n"
+ "b.example.org. IN A 192.0.2.2\n"
+ "c.example.org. IN A 192.0.2.3\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+ checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(1800));
+
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, RRParamsOrdering) {
+ // We test the order and existence of TTL, class and type. See
+ // MasterLoader::MasterLoaderImpl::parseRRParams() for ordering.
+
+ stringstream zone_stream;
+ // <TTL> <class> <type> <RDATA>
+ zone_stream << "a.example.org. 1800 IN A 192.0.2.1\n";
+ // <type> <RDATA>
+ zone_stream << "b.example.org. A 192.0.2.2\n";
+ // <class> <TTL> <type> <RDATA>
+ zone_stream << "c.example.org. IN 3600 A 192.0.2.3\n";
+ // <TTL> <type> <RDATA>
+ zone_stream << "d.example.org. 7200 A 192.0.2.4\n";
+ // <class> <type> <RDATA>
+ zone_stream << "e.example.org. IN A 192.0.2.5\n";
+
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+ checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
+ checkRR("d.example.org", RRType::A(), "192.0.2.4", RRTTL(7200));
+ checkRR("e.example.org", RRType::A(), "192.0.2.5", RRTTL(7200));
+
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, ttlFromPreviousSOA) {
+ // Mixture of the previous two cases: SOA has explicit TTL, followed by
+ // an RR without an explicit TTL. In this case the minimum TTL won't be
+ // recognized as the "default TTL".
+ stringstream zone_stream("example.org. 100 IN SOA . . 0 0 0 0 1800\n"
+ "a.example.org. IN A 192.0.2.1\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+
+ checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(100));
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(100));
+
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknown) {
+ // No available TTL is known for the first RR.
+ stringstream zone_stream("a.example.org. IN A 192.0.2.1\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ EXPECT_THROW(loader_->load(), MasterLoaderError);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknownAndContinue) {
+ stringstream zone_stream("a.example.org. IN A 192.0.2.1\n"
+ "b.example.org. 1800 IN A 192.0.2.2\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+
+ EXPECT_TRUE(warnings_.empty());
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknownAndEOF) {
+ // Similar to the previous case, but the input will be abruptly terminated
+ // after the offending RR. This will cause an additional warning.
+ stringstream zone_stream("a.example.org. IN A 192.0.2.1");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(rrsets_.empty());
+
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
+
+ // RDATA implementation can complain about it, too. To be independent of
+ // its details, we focus on the very last warning.
+ EXPECT_FALSE(warnings_.empty());
+ checkCallbackMessage(*warnings_.rbegin(), "File does not end with newline",
+ 1);
+}
+
+TEST_F(MasterLoaderTest, ttlOverflow) {
+ stringstream zone_stream;
+ zone_stream << "example.org. IN SOA . . 0 0 0 0 2147483648\n";
+ zone_stream << "$TTL 3600\n"; // reset to an in-range value
+ zone_stream << "$TTL 2147483649\n";
+ zone_stream << "a.example.org. IN A 192.0.2.1\n";
+ zone_stream << "$TTL 3600\n"; // reset to an in-range value
+ zone_stream << "b.example.org. 2147483650 IN A 192.0.2.2\n";
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_EQ(3, rrsets_.size());
+
+ checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 2147483648", RRTTL(0));
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(0));
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(0));
+
+ EXPECT_EQ(4, warnings_.size());
+ checkCallbackMessage(warnings_.at(1),
+ "TTL 2147483648 > MAXTTL, setting to 0 per RFC2181",
+ 1);
+ checkCallbackMessage(warnings_.at(2),
+ "TTL 2147483649 > MAXTTL, setting to 0 per RFC2181",
+ 3);
+ checkCallbackMessage(warnings_.at(3),
+ "TTL 2147483650 > MAXTTL, setting to 0 per RFC2181",
+ 6);
+}
+
+// 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);
+ // And the same with the second constructor
+ stringstream ss("");
+ EXPECT_THROW(MasterLoader(ss, 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);
+ // Don't check them, they are not interesting, so suppress the error
+ // at TearDown
+ rrsets_.clear();
+}
+
+// Load 0 items should be rejected
+TEST_F(MasterLoaderTest, loadZero) {
+ setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+ RRClass::IN(), MasterLoader::MANY_ERRORS);
+ EXPECT_THROW(loader_->loadIncremental(0), isc::InvalidParameter);
+}
+
+// Test there's a warning when the file terminates without end of
+// line.
+TEST_F(MasterLoaderTest, noEOLN) {
+ // No \n at the end
+ const string input("example.org. 3600 IN SOA ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ // There should be one warning about the EOLN
+ EXPECT_EQ(1, warnings_.size());
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+}
+
+// Test it rejects when we don't have the previous name to use in place of
+// initial whitespace
+TEST_F(MasterLoaderTest, noPreviousName) {
+ const string input(" 1H IN A 192.0.2.1\n");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "No previous name to use in place of "
+ "initial whitespace", 1);
+ EXPECT_TRUE(warnings_.empty());
+}
+
+// Check we warn if the first RR in an included file has omitted name
+TEST_F(MasterLoaderTest, previousInInclude) {
+ const string input("www 1H IN A 192.0.2.1\n"
+ "$INCLUDE " TEST_DATA_SRCDIR "/omitcheck.txt\n");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ // There should be one warning about the EOLN
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "Owner name omitted around "
+ "$INCLUDE, the result might not be as expected", 1);
+ checkARR("www.example.org");
+ checkARR("www.example.org");
+}
+
+TEST_F(MasterLoaderTest, numericOwnerName) {
+ const string input("$ORIGIN example.org.\n"
+ "1 3600 IN A 192.0.2.1\n");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ checkRR("1.example.org", RRType::A(), "192.0.2.1");
+}
+
+}
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
new file mode 100644
index 0000000..5754c50
--- /dev/null
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -0,0 +1,1156 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <fstream>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <util/unittests/testdata.h>
+#include <util/unittests/textdata.h>
+
+#include <dns/edns.h>
+#include <dns/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/question.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+//
+// Note: we need more tests, including:
+// parsing malformed headers
+// more complete tests about parsing/rendering header flags, opcode, rcode, etc.
+// tests for adding RRsets
+// tests for RRset/Question iterators
+// But, we'll ship with the current set of tests for now, partly because many
+// of the above are covered as part of other tests, and partly due to time
+// limitation. We also expect to revisit the fundamental design of the Message
+// class, at which point we'll also revise the tests including more cases.
+//
+
+const uint16_t Message::DEFAULT_MAX_UDPSIZE;
+
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*getTimeFunction)();
+}
+}
+}
+
+// XXX: this is defined as class static constants, but some compilers
+// seemingly cannot find the symbol when used in the EXPECT_xxx macros.
+const uint16_t TSIGContext::DEFAULT_FUDGE;
+
+namespace {
+class MessageTest : public ::testing::Test {
+protected:
+ MessageTest() : test_name("test.example.com"), obuffer(0),
+ message_parse(Message::PARSE),
+ message_render(Message::RENDER),
+ bogus_section(static_cast<Message::Section>(
+ Message::SECTION_ADDITIONAL + 1)),
+ tsig_ctx(TSIGKey("www.example.com:"
+ "SFuWd/q99SzF8Yzd1QbB9g==")) {
+ rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::A(), RRTTL(3600)));
+ rrset_a->addRdata(in::A("192.0.2.1"));
+ rrset_a->addRdata(in::A("192.0.2.2"));
+
+ rrset_aaaa = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::AAAA(), RRTTL(3600)));
+ rrset_aaaa->addRdata(in::AAAA("2001:db8::1234"));
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
+ "20100220084538 1 example.com. "
+ "FAKEFAKEFAKEFAKE"));
+ rrset_aaaa->addRRsig(rrset_rrsig);
+ }
+
+ static Question factoryFromFile(const char* datafile);
+ const Name test_name;
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+ Message message_parse;
+ Message message_render;
+ const Message::Section bogus_section;
+ RRsetPtr rrset_a; // A RRset with two RDATAs
+ RRsetPtr rrset_aaaa; // AAAA RRset with one RDATA with RRSIG
+ RRsetPtr rrset_rrsig; // RRSIG for the AAAA RRset
+ TSIGContext tsig_ctx;
+ vector<unsigned char> received_data;
+ vector<unsigned char> expected_data;
+
+ void factoryFromFile(Message& message, const char* datafile,
+ Message::ParseOptions options =
+ Message::PARSE_DEFAULT);
+};
+
+void
+MessageTest::factoryFromFile(Message& message, const char* datafile,
+ Message::ParseOptions options)
+{
+ received_data.clear();
+ UnitTestUtil::readWireData(datafile, received_data);
+
+ InputBuffer buffer(&received_data[0], received_data.size());
+ message.fromWire(buffer, options);
+}
+
+TEST_F(MessageTest, headerFlag) {
+ // by default no flag is set
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_TC));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_RD));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_RA));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AD));
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_CD));
+
+ // set operation: by default it will be on
+ message_render.setHeaderFlag(Message::HEADERFLAG_QR);
+ EXPECT_TRUE(message_render.getHeaderFlag(Message::HEADERFLAG_QR));
+
+ // it can be set to on explicitly, too
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, true);
+ EXPECT_TRUE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ // the bit can also be cleared
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, false);
+ EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ // Invalid flag values
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0)), InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x7000)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x0800)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x0040)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x10000)),
+ InvalidParameter);
+ EXPECT_THROW(message_render.setHeaderFlag(
+ static_cast<Message::HeaderFlag>(0x80000000)),
+ InvalidParameter);
+
+ // set operation isn't allowed in the parse mode.
+ EXPECT_THROW(message_parse.setHeaderFlag(Message::HEADERFLAG_QR),
+ InvalidMessageOperation);
+}
+TEST_F(MessageTest, getEDNS) {
+ EXPECT_FALSE(message_parse.getEDNS()); // by default EDNS isn't set
+
+ factoryFromFile(message_parse, "message_fromWire10.wire");
+ EXPECT_TRUE(message_parse.getEDNS());
+ EXPECT_EQ(0, message_parse.getEDNS()->getVersion());
+ EXPECT_EQ(4096, message_parse.getEDNS()->getUDPSize());
+ EXPECT_TRUE(message_parse.getEDNS()->getDNSSECAwareness());
+}
+
+TEST_F(MessageTest, setEDNS) {
+ // setEDNS() isn't allowed in the parse mode
+ EXPECT_THROW(message_parse.setEDNS(EDNSPtr(new EDNS())),
+ InvalidMessageOperation);
+
+ EDNSPtr edns = EDNSPtr(new EDNS());
+ message_render.setEDNS(edns);
+ EXPECT_EQ(edns, message_render.getEDNS());
+}
+
+TEST_F(MessageTest, fromWireWithTSIG) {
+ // Initially there should be no TSIG
+ EXPECT_FALSE(message_parse.getTSIGRecord());
+
+ // getTSIGRecord() is only valid in the parse mode.
+ EXPECT_THROW(message_render.getTSIGRecord(), InvalidMessageOperation);
+
+ factoryFromFile(message_parse, "message_toWire2.wire");
+ const uint8_t expected_mac[] = {
+ 0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7,
+ 0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
+ };
+ const TSIGRecord* tsig_rr = message_parse.getTSIGRecord();
+ ASSERT_TRUE(tsig_rr);
+ EXPECT_EQ(Name("www.example.com"), tsig_rr->getName());
+ EXPECT_EQ(85, tsig_rr->getLength()); // see TSIGRecordTest.getLength
+ EXPECT_EQ(TSIGKey::HMACMD5_NAME(), tsig_rr->getRdata().getAlgorithm());
+ EXPECT_EQ(0x4da8877a, tsig_rr->getRdata().getTimeSigned());
+ EXPECT_EQ(TSIGContext::DEFAULT_FUDGE, tsig_rr->getRdata().getFudge());
+ matchWireData(expected_mac, sizeof(expected_mac),
+ tsig_rr->getRdata().getMAC(),
+ tsig_rr->getRdata().getMACSize());
+ EXPECT_EQ(0, tsig_rr->getRdata().getError());
+ EXPECT_EQ(0, tsig_rr->getRdata().getOtherLen());
+ EXPECT_FALSE(tsig_rr->getRdata().getOtherData());
+
+ // If we clear the message for reuse, the recorded TSIG will be cleared.
+ message_parse.clear(Message::PARSE);
+ EXPECT_FALSE(message_parse.getTSIGRecord());
+}
+
+TEST_F(MessageTest, fromWireWithTSIGCompressed) {
+ // Mostly same as fromWireWithTSIG, but the TSIG owner name is compressed.
+ factoryFromFile(message_parse, "message_fromWire12.wire");
+ const TSIGRecord* tsig_rr = message_parse.getTSIGRecord();
+ ASSERT_TRUE(tsig_rr);
+ EXPECT_EQ(Name("www.example.com"), tsig_rr->getName());
+ // len(www.example.com) = 17, but when fully compressed, the length is
+ // 2 bytes. So the length of the record should be 15 bytes shorter.
+ EXPECT_EQ(70, tsig_rr->getLength());
+}
+
+TEST_F(MessageTest, fromWireWithBadTSIG) {
+ // Multiple TSIG RRs
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire13.wire"),
+ DNSMessageFORMERR);
+ message_parse.clear(Message::PARSE);
+
+ // TSIG in the answer section (must be in additional)
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire14.wire"),
+ DNSMessageFORMERR);
+ message_parse.clear(Message::PARSE);
+
+ // TSIG is not the last record.
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire15.wire"),
+ DNSMessageFORMERR);
+ message_parse.clear(Message::PARSE);
+
+ // Unexpected RR Class (this will fail in constructing TSIGRecord)
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire16.wire"),
+ DNSMessageFORMERR);
+}
+
+TEST_F(MessageTest, getRRCount) {
+ // by default all counters should be 0
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.addQuestion(Question(Name("test.example.com"),
+ RRClass::IN(), RRType::A()));
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+
+ // rrset_a contains two RRs
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // parse a message containing a Question and EDNS OPT RR.
+ // OPT shouldn't be counted as normal RR, so result of getRRCount
+ // shouldn't change.
+ factoryFromFile(message_parse, "message_fromWire11.wire");
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // out-of-band section ID
+ EXPECT_THROW(message_parse.getRRCount(bogus_section), isc::OutOfRange);
+}
+
+TEST_F(MessageTest, addRRset) {
+ // initially, we have 0
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // add two A RRs (unsigned)
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ EXPECT_EQ(rrset_a,
+ *message_render.beginSection(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ message_render.clear(Message::RENDER);
+
+ // add one AAAA RR (signed)
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ EXPECT_EQ(rrset_aaaa,
+ *message_render.beginSection(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, badAddRRset) {
+ // addRRset() isn't allowed in the parse mode.
+ EXPECT_THROW(message_parse.addRRset(Message::SECTION_ANSWER,
+ rrset_a), InvalidMessageOperation);
+ // out-of-band section ID
+ EXPECT_THROW(message_render.addRRset(bogus_section, rrset_a), isc::OutOfRange);
+
+ // NULL RRset
+ EXPECT_THROW(message_render.addRRset(Message::SECTION_ANSWER, RRsetPtr()),
+ InvalidParameter);
+}
+
+TEST_F(MessageTest, hasRRset) {
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ // section doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ // name doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER,
+ Name("nomatch.example"),
+ RRClass::IN(), RRType::A()));
+ // RR class doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::CH(), RRType::A()));
+ // RR type doesn't match
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+ // out-of-band section ID
+ EXPECT_THROW(message_render.hasRRset(bogus_section, test_name,
+ RRClass::IN(), RRType::A()),
+ isc::OutOfRange);
+
+ // Repeat the checks having created an RRset of the appropriate type.
+
+ RRsetPtr rrs1(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(60)));
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, rrs1));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, rrs1));
+
+ RRsetPtr rrs2(new RRset(Name("nomatch.example"), RRClass::IN(), RRType::A(),
+ RRTTL(5)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs2));
+
+ RRsetPtr rrs3(new RRset(test_name, RRClass::CH(), RRType::A(), RRTTL(60)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs3));
+
+ RRsetPtr rrs4(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4));
+
+ RRsetPtr rrs5(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5)));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4));
+
+ EXPECT_THROW(message_render.hasRRset(bogus_section, rrs1), isc::OutOfRange);
+}
+
+TEST_F(MessageTest, removeRRset) {
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(4, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ // Locate the AAAA RRset and remove it and any associated RRSIGs
+ RRsetIterator i = message_render.beginSection(Message::SECTION_ANSWER);
+ if ((*i)->getType() == RRType::A()) {
+ ++i;
+ }
+ EXPECT_EQ(RRType::AAAA(), (*i)->getType());
+ message_render.removeRRset(Message::SECTION_ANSWER, i);
+
+ EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, clearQuestionSection) {
+ QuestionPtr q(new Question(Name("www.example.com"), RRClass::IN(),
+ RRType::A()));
+ message_render.addQuestion(q);
+ ASSERT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+
+ message_render.clearSection(Message::SECTION_QUESTION);
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_TRUE(message_render.beginQuestion() ==
+ message_render.endQuestion());
+}
+
+
+TEST_F(MessageTest, clearAnswerSection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(4, message_render.getRRCount(Message::SECTION_ANSWER));
+
+ message_render.clearSection(Message::SECTION_ANSWER);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER));
+}
+
+TEST_F(MessageTest, clearAuthoritySection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(4, message_render.getRRCount(Message::SECTION_AUTHORITY));
+
+ message_render.clearSection(Message::SECTION_AUTHORITY);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+}
+
+TEST_F(MessageTest, clearAdditionalSection) {
+ // Add two RRsets, check they are present, clear the section,
+ // check if they are gone.
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ ASSERT_EQ(4, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.clearSection(Message::SECTION_ADDITIONAL);
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+}
+
+TEST_F(MessageTest, badClearSection) {
+ // attempt of clearing a message in the parse mode.
+ EXPECT_THROW(message_parse.clearSection(Message::SECTION_QUESTION),
+ InvalidMessageOperation);
+ // attempt of clearing out-of-range section
+ EXPECT_THROW(message_render.clearSection(bogus_section), isc::OutOfRange);
+}
+
+TEST_F(MessageTest, badBeginSection) {
+ // valid cases are tested via other tests
+ EXPECT_THROW(message_render.beginSection(Message::SECTION_QUESTION),
+ InvalidMessageSection);
+ EXPECT_THROW(message_render.beginSection(bogus_section), isc::OutOfRange);
+}
+
+TEST_F(MessageTest, badEndSection) {
+ // valid cases are tested via other tests
+ EXPECT_THROW(message_render.endSection(Message::SECTION_QUESTION),
+ InvalidMessageSection);
+ EXPECT_THROW(message_render.endSection(bogus_section), isc::OutOfRange);
+}
+
+TEST_F(MessageTest, appendSection) {
+ Message target(Message::RENDER);
+
+ // Section check
+ EXPECT_THROW(target.appendSection(bogus_section, message_render),
+ isc::OutOfRange);
+
+ // Make sure nothing is copied if there is nothing to copy
+ target.appendSection(Message::SECTION_QUESTION, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_QUESTION));
+ target.appendSection(Message::SECTION_ANSWER, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_ANSWER));
+ target.appendSection(Message::SECTION_AUTHORITY, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_AUTHORITY));
+ target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+ EXPECT_EQ(0, target.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Now add some data, copy again, and see if it got added
+ message_render.addQuestion(Question(Name("test.example.com"),
+ RRClass::IN(), RRType::A()));
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+ message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a);
+ message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa);
+
+ target.appendSection(Message::SECTION_QUESTION, message_render);
+ EXPECT_EQ(1, target.getRRCount(Message::SECTION_QUESTION));
+
+ target.appendSection(Message::SECTION_ANSWER, message_render);
+ EXPECT_EQ(2, target.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+
+ target.appendSection(Message::SECTION_AUTHORITY, message_render);
+ EXPECT_EQ(2, target.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_AUTHORITY, test_name,
+ RRClass::IN(), RRType::A()));
+
+ target.appendSection(Message::SECTION_ADDITIONAL, message_render);
+ EXPECT_EQ(4, target.getRRCount(Message::SECTION_ADDITIONAL));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name,
+ RRClass::IN(), RRType::AAAA()));
+
+ // One more test, test to see if the section gets added, not replaced
+ Message source2(Message::RENDER);
+ source2.addRRset(Message::SECTION_ANSWER, rrset_aaaa);
+ target.appendSection(Message::SECTION_ANSWER, source2);
+ EXPECT_EQ(4, target.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::A()));
+ EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name,
+ RRClass::IN(), RRType::AAAA()));
+}
+
+TEST_F(MessageTest, parseHeader) {
+ received_data.clear();
+ UnitTestUtil::readWireData("message_fromWire1", received_data);
+
+ // parseHeader() isn't allowed in the render mode.
+ InputBuffer buffer(&received_data[0], received_data.size());
+ EXPECT_THROW(message_render.parseHeader(buffer), InvalidMessageOperation);
+
+ message_parse.parseHeader(buffer);
+ EXPECT_EQ(0x1035, message_parse.getQid());
+ EXPECT_EQ(Opcode::QUERY(), message_parse.getOpcode());
+ EXPECT_EQ(Rcode::NOERROR(), message_parse.getRcode());
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_TC));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_RD));
+ EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_RA));
+ EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_AD));
+ EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_CD));
+ EXPECT_EQ(1, message_parse.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(2, message_parse.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Only the header part should have been examined.
+ EXPECT_EQ(12, buffer.getPosition()); // 12 = size of the header section
+ EXPECT_TRUE(message_parse.beginQuestion() == message_parse.endQuestion());
+ EXPECT_TRUE(message_parse.beginSection(Message::SECTION_ANSWER) ==
+ message_parse.endSection(Message::SECTION_ANSWER));
+ EXPECT_TRUE(message_parse.beginSection(Message::SECTION_AUTHORITY) ==
+ message_parse.endSection(Message::SECTION_AUTHORITY));
+ EXPECT_TRUE(message_parse.beginSection(Message::SECTION_ADDITIONAL) ==
+ message_parse.endSection(Message::SECTION_ADDITIONAL));
+}
+
+void
+checkMessageFromWire(const Message& message_parse,
+ const Name& test_name)
+{
+ EXPECT_EQ(0x1035, message_parse.getQid());
+ EXPECT_EQ(Opcode::QUERY(), message_parse.getOpcode());
+ EXPECT_EQ(Rcode::NOERROR(), message_parse.getRcode());
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_QR));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_RD));
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_AA));
+
+ QuestionPtr q = *message_parse.beginQuestion();
+ EXPECT_EQ(test_name, q->getName());
+ EXPECT_EQ(RRType::A(), q->getType());
+ EXPECT_EQ(RRClass::IN(), q->getClass());
+ EXPECT_EQ(1, message_parse.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(2, message_parse.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_ADDITIONAL));
+
+ RRsetPtr rrset = *message_parse.beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(test_name, rrset->getName());
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRClass::IN(), rrset->getClass());
+ // TTL should be 3600, even though that of the 2nd RR is 7200
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ RdataIteratorPtr it = rrset->getRdataIterator();
+ EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
+ it->next();
+ EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
+ it->next();
+ EXPECT_TRUE(it->isLast());
+}
+
+
+TEST_F(MessageTest, fromWire) {
+ // fromWire() isn't allowed in the render mode.
+ EXPECT_THROW(factoryFromFile(message_render, "message_fromWire1"),
+ InvalidMessageOperation);
+
+ factoryFromFile(message_parse, "message_fromWire1");
+ checkMessageFromWire(message_parse, test_name);
+}
+
+TEST_F(MessageTest, fromWireMultiple) {
+ // Parse from wire multiple times.
+ factoryFromFile(message_parse, "message_fromWire1");
+ factoryFromFile(message_parse, "message_fromWire1");
+ factoryFromFile(message_parse, "message_fromWire1");
+ factoryFromFile(message_parse, "message_fromWire1");
+ checkMessageFromWire(message_parse, test_name);
+
+ // Calling parseHeader() directly before fromWire() should not cause
+ // any problems.
+ received_data.clear();
+ UnitTestUtil::readWireData("message_fromWire1", received_data);
+
+ InputBuffer buffer(&received_data[0], received_data.size());
+ message_parse.parseHeader(buffer);
+ message_parse.fromWire(buffer);
+ message_parse.parseHeader(buffer);
+ message_parse.fromWire(buffer);
+ checkMessageFromWire(message_parse, test_name);
+}
+
+TEST_F(MessageTest, fromWireShortBuffer) {
+ // We trim a valid message (ending with an SOA RR) for one byte.
+ // fromWire() should throw an exception while parsing the trimmed RR.
+ UnitTestUtil::readWireData("message_fromWire22.wire", received_data);
+ InputBuffer buffer(&received_data[0], received_data.size() - 1);
+ EXPECT_THROW(message_parse.fromWire(buffer), isc::OutOfRange);
+}
+
+TEST_F(MessageTest, fromWireCombineRRs) {
+ // This message contains 3 RRs in the answer section in the order of
+ // A, AAAA, A types. fromWire() should combine the two A RRs into a
+ // single RRset by default.
+ factoryFromFile(message_parse, "message_fromWire19.wire");
+
+ RRsetIterator it = message_parse.beginSection(Message::SECTION_ANSWER);
+ RRsetIterator it_end = message_parse.endSection(Message::SECTION_ANSWER);
+ ASSERT_TRUE(it != it_end);
+ EXPECT_EQ(RRType::A(), (*it)->getType());
+ EXPECT_EQ(2, (*it)->getRdataCount());
+
+ ++it;
+ ASSERT_TRUE(it != it_end);
+ EXPECT_EQ(RRType::AAAA(), (*it)->getType());
+ EXPECT_EQ(1, (*it)->getRdataCount());
+}
+
+// A helper function for a test pattern commonly used in several tests below.
+void
+preserveRRCheck(const Message& message, Message::Section section) {
+ RRsetIterator it = message.beginSection(section);
+ RRsetIterator it_end = message.endSection(section);
+ ASSERT_TRUE(it != it_end);
+ EXPECT_EQ(RRType::A(), (*it)->getType());
+ EXPECT_EQ(1, (*it)->getRdataCount());
+ EXPECT_EQ("192.0.2.1", (*it)->getRdataIterator()->getCurrent().toText());
+
+ ++it;
+ ASSERT_TRUE(it != it_end);
+ EXPECT_EQ(RRType::AAAA(), (*it)->getType());
+ EXPECT_EQ(1, (*it)->getRdataCount());
+ EXPECT_EQ("2001:db8::1", (*it)->getRdataIterator()->getCurrent().toText());
+
+ ++it;
+ ASSERT_TRUE(it != it_end);
+ EXPECT_EQ(RRType::A(), (*it)->getType());
+ EXPECT_EQ(1, (*it)->getRdataCount());
+ EXPECT_EQ("192.0.2.2", (*it)->getRdataIterator()->getCurrent().toText());
+}
+
+TEST_F(MessageTest, fromWirePreserveAnswer) {
+ // Using the same data as the previous test, but specify the PRESERVE_ORDER
+ // option. The received order of RRs should be preserved, and each RR
+ // should be stored in a single RRset.
+ factoryFromFile(message_parse, "message_fromWire19.wire",
+ Message::PRESERVE_ORDER);
+ {
+ SCOPED_TRACE("preserve answer RRs");
+ preserveRRCheck(message_parse, Message::SECTION_ANSWER);
+ }
+}
+
+TEST_F(MessageTest, fromWirePreserveAuthority) {
+ // Same for the previous test, but for the authority section.
+ factoryFromFile(message_parse, "message_fromWire20.wire",
+ Message::PRESERVE_ORDER);
+ {
+ SCOPED_TRACE("preserve authority RRs");
+ preserveRRCheck(message_parse, Message::SECTION_AUTHORITY);
+ }
+}
+
+TEST_F(MessageTest, fromWirePreserveAdditional) {
+ // Same for the previous test, but for the additional section.
+ factoryFromFile(message_parse, "message_fromWire21.wire",
+ Message::PRESERVE_ORDER);
+ {
+ SCOPED_TRACE("preserve additional RRs");
+ preserveRRCheck(message_parse, Message::SECTION_ADDITIONAL);
+ }
+}
+
+TEST_F(MessageTest, EDNS0ExtRcode) {
+ // Extended Rcode = BADVERS
+ factoryFromFile(message_parse, "message_fromWire10.wire");
+ EXPECT_EQ(Rcode::BADVERS(), message_parse.getRcode());
+
+ // Maximum extended Rcode
+ message_parse.clear(Message::PARSE);
+ factoryFromFile(message_parse, "message_fromWire11.wire");
+ EXPECT_EQ(0xfff, message_parse.getRcode().getCode());
+}
+
+TEST_F(MessageTest, BadEDNS0) {
+ // OPT RR in the answer section
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire4"),
+ DNSMessageFORMERR);
+ // multiple OPT RRs (in the additional section)
+ message_parse.clear(Message::PARSE);
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire5"),
+ DNSMessageFORMERR);
+}
+
+TEST_F(MessageTest, toWire) {
+ message_render.setQid(0x1035);
+ message_render.setOpcode(Opcode::QUERY());
+ message_render.setRcode(Rcode::NOERROR());
+ message_render.setHeaderFlag(Message::HEADERFLAG_QR, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_RD, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, true);
+ message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(),
+ RRType::A()));
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.toWire(renderer);
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("message_toWire1", data);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageTest, toWireSigned) {
+ message_render.setQid(0x75c1);
+ message_render.setOpcode(Opcode::QUERY());
+ message_render.setRcode(Rcode::NOERROR());
+ message_render.setHeaderFlag(Message::HEADERFLAG_QR, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_RD, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, true);
+ message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(),
+ RRType::A()));
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ // one signature algorithm (5 = RSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("A 5 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ // another signature algorithm (3 = DSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("A 3 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ rrset_a->addRRsig(rrset_rrsig);
+ EXPECT_EQ(2, rrset_a->getRRsigDataCount());
+
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_a);
+
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(4, message_render.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.toWire(renderer);
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("message_toWire6", data);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageTest, toWireSignedAndTruncated) {
+ message_render.setQid(0x75c1);
+ message_render.setOpcode(Opcode::QUERY());
+ message_render.setRcode(Rcode::NOERROR());
+ message_render.setHeaderFlag(Message::HEADERFLAG_QR, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_RD, true);
+ message_render.setHeaderFlag(Message::HEADERFLAG_AA, true);
+ message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(),
+ RRType::TXT()));
+
+ RRsetPtr rrset_txt = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::TXT(), RRTTL(3600)));
+ rrset_txt->addRdata(generic::TXT(string(255, 'a')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'b')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'c')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'd')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'e')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'f')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'g')));
+ rrset_txt->addRdata(generic::TXT(string(255, 'h')));
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ // one signature algorithm (5 = RSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("TXT 5 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ rrset_txt->addRRsig(rrset_rrsig);
+ EXPECT_EQ(1, rrset_txt->getRRsigDataCount());
+
+ message_render.addRRset(Message::SECTION_ANSWER, rrset_txt);
+
+ EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(9, message_render.getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
+
+ message_render.toWire(renderer);
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("message_toWire7", data);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageTest, toWireInParseMode) {
+ // toWire() isn't allowed in the parse mode.
+ EXPECT_THROW(message_parse.toWire(renderer), InvalidMessageOperation);
+}
+
+// See dnssectime_unittest.cc
+template <int64_t NOW>
+int64_t
+testGetTime() {
+ return (NOW);
+}
+
+// bit-wise constant flags to configure DNS header flags for test
+// messages.
+const unsigned int QR_FLAG = 0x1;
+const unsigned int AA_FLAG = 0x2;
+const unsigned int RD_FLAG = 0x4;
+
+void
+commonTSIGToWireCheck(Message& message, MessageRenderer& renderer,
+ TSIGContext& tsig_ctx, const char* const expected_file,
+ unsigned int message_flags = RD_FLAG,
+ RRType qtype = RRType::A(),
+ const vector<const char*>* answer_data = 0) {
+ message.setOpcode(Opcode::QUERY());
+ message.setRcode(Rcode::NOERROR());
+ if ((message_flags & QR_FLAG) != 0) {
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ }
+ if ((message_flags & AA_FLAG) != 0) {
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ }
+ if ((message_flags & RD_FLAG) != 0) {
+ message.setHeaderFlag(Message::HEADERFLAG_RD);
+ }
+ message.addQuestion(Question(Name("www.example.com"), RRClass::IN(),
+ qtype));
+
+ if (answer_data) {
+ RRsetPtr ans_rrset(new RRset(Name("www.example.com"), RRClass::IN(),
+ qtype, RRTTL(86400)));
+ for (auto const& it : *answer_data) {
+ ans_rrset->addRdata(createRdata(qtype, RRClass::IN(), it));
+ }
+ message.addRRset(Message::SECTION_ANSWER, ans_rrset);
+ }
+
+ message.toWire(renderer, &tsig_ctx);
+ vector<unsigned char> expected_data;
+ UnitTestUtil::readWireData(expected_file, expected_data);
+ matchWireData(&expected_data[0], expected_data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageTest, toWireWithTSIG) {
+ // Rendering a message with TSIG. Various special cases specific to
+ // TSIG are tested in the tsig tests. We only check the message contains
+ // a TSIG at the end and the ARCOUNT of the header is updated.
+
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ message_render.setQid(0x2d65);
+
+ {
+ SCOPED_TRACE("Message sign with TSIG");
+ commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire2.wire");
+ }
+}
+
+TEST_F(MessageTest, toWireWithEDNSAndTSIG) {
+ // Similar to the previous test, but with an EDNS before TSIG.
+ // The wire data check will confirm the ordering.
+ isc::util::detail::getTimeFunction = testGetTime<0x4db60d1f>;
+
+ message_render.setQid(0x6cd);
+
+ EDNSPtr edns(new EDNS());
+ edns->setUDPSize(4096);
+ message_render.setEDNS(edns);
+
+ {
+ SCOPED_TRACE("Message sign with TSIG and EDNS");
+ commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire3.wire");
+ }
+}
+
+// Some of the following tests involve truncation. We use the query name
+// "www.example.com" and some TXT question/answers. The length of the
+// header and question will be 33 bytes. If we also try to include a
+// TSIG of the same key name (not compressed) with HMAC-MD5, the TSIG RR
+// will be 85 bytes.
+
+// A long TXT RDATA. With a fully compressed owner name, the corresponding
+// RR will be 268 bytes.
+const char* const long_txt1 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde";
+
+// With a fully compressed owner name, the corresponding RR will be 212 bytes.
+// It should result in truncation even without TSIG (33 + 268 + 212 = 513)
+const char* const long_txt2 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456";
+
+// With a fully compressed owner name, the corresponding RR will be 127 bytes.
+// So, it can fit in the standard 512 bytes with txt1 and without TSIG, but
+// adding a TSIG would result in truncation (33 + 268 + 127 + 85 = 513)
+const char* const long_txt3 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01";
+
+// This is 1 byte shorter than txt3, which will result in a possible longest
+// message containing answer RRs and TSIG.
+const char* const long_txt4 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0";
+
+// Example output generated by
+// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com txt
+// QID: 0x22c2
+// Time Signed: 0x00004e179212
+TEST_F(MessageTest, toWireTSIGTruncation) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4e179212>;
+
+ // Verify a validly signed query so that we can use the TSIG context
+
+ factoryFromFile(message_parse, "message_fromWire17.wire");
+ EXPECT_EQ(TSIGError::NOERROR(),
+ tsig_ctx.verify(message_parse.getTSIGRecord(),
+ &received_data[0], received_data.size()));
+
+ message_render.setQid(0x22c2);
+ vector<const char*> answer_data;
+ answer_data.push_back(long_txt1);
+ answer_data.push_back(long_txt2);
+ {
+ SCOPED_TRACE("Message sign with TSIG and TC bit on");
+ commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire4.wire",
+ QR_FLAG|AA_FLAG|RD_FLAG,
+ RRType::TXT(), &answer_data);
+ }
+}
+
+TEST_F(MessageTest, toWireTSIGTruncation2) {
+ // Similar to the previous test, but without TSIG it wouldn't cause
+ // truncation.
+ isc::util::detail::getTimeFunction = testGetTime<0x4e179212>;
+ factoryFromFile(message_parse, "message_fromWire17.wire");
+ EXPECT_EQ(TSIGError::NOERROR(),
+ tsig_ctx.verify(message_parse.getTSIGRecord(),
+ &received_data[0], received_data.size()));
+
+ message_render.setQid(0x22c2);
+ vector<const char*> answer_data;
+ answer_data.push_back(long_txt1);
+ answer_data.push_back(long_txt3);
+ {
+ SCOPED_TRACE("Message sign with TSIG and TC bit on (2)");
+ commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire4.wire",
+ QR_FLAG|AA_FLAG|RD_FLAG,
+ RRType::TXT(), &answer_data);
+ }
+}
+
+TEST_F(MessageTest, toWireTSIGTruncation3) {
+ // Similar to previous ones, but truncation occurs due to too many
+ // Questions (very unusual, but not necessarily illegal).
+
+ // We are going to create a message starting with a standard
+ // header (12 bytes) and multiple questions in the Question
+ // section of the same owner name (changing the RRType, just so
+ // that it would be the form that would be accepted by the BIND 9
+ // parser). The first Question is 21 bytes in length, and the subsequent
+ // ones are 6 bytes. We'll also use a TSIG whose size is 85 bytes.
+ // Up to 66 questions can fit in the standard 512-byte buffer
+ // (12 + 21 + 6 * 65 + 85 = 508). If we try to add one more it would
+ // result in truncation.
+ message_render.setOpcode(Opcode::QUERY());
+ message_render.setRcode(Rcode::NOERROR());
+ for (int i = 1; i <= 67; ++i) {
+ message_render.addQuestion(Question(Name("www.example.com"),
+ RRClass::IN(), RRType(i)));
+ }
+ message_render.toWire(renderer, &tsig_ctx);
+
+ // Check the rendered data by parsing it. We only check it has the
+ // TC bit on, has the correct number of questions, and has a TSIG RR.
+ // Checking the signature wouldn't be necessary for this rare case
+ // scenario.
+ InputBuffer buffer(renderer.getData(), renderer.getLength());
+ message_parse.fromWire(buffer);
+ EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_TC));
+ // Note that the number of questions are 66, not 67 as we tried to add.
+ EXPECT_EQ(66, message_parse.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_TRUE(message_parse.getTSIGRecord());
+}
+
+TEST_F(MessageTest, toWireTSIGNoTruncation) {
+ // A boundary case that shouldn't cause truncation: the resulting
+ // response message with a TSIG will be 512 bytes long.
+ isc::util::detail::getTimeFunction = testGetTime<0x4e17b38d>;
+ factoryFromFile(message_parse, "message_fromWire18.wire");
+ EXPECT_EQ(TSIGError::NOERROR(),
+ tsig_ctx.verify(message_parse.getTSIGRecord(),
+ &received_data[0], received_data.size()));
+
+ message_render.setQid(0xd6e2);
+ vector<const char*> answer_data;
+ answer_data.push_back(long_txt1);
+ answer_data.push_back(long_txt4);
+ {
+ SCOPED_TRACE("Message sign with TSIG, no truncation");
+ commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire5.wire",
+ QR_FLAG|AA_FLAG|RD_FLAG,
+ RRType::TXT(), &answer_data);
+ }
+}
+
+// This is a buggy renderer for testing. It behaves like the straightforward
+// MessageRenderer, but once it has some data, its setLengthLimit() ignores
+// the given parameter and resets the limit to the current length, making
+// subsequent insertion result in truncation, which would make TSIG RR
+// rendering fail unexpectedly in the test that follows.
+class BadRenderer : public MessageRenderer {
+public:
+ virtual void setLengthLimit(size_t len) {
+ if (getLength() > 0) {
+ MessageRenderer::setLengthLimit(getLength());
+ } else {
+ MessageRenderer::setLengthLimit(len);
+ }
+ }
+};
+
+TEST_F(MessageTest, toWireTSIGLengthErrors) {
+ // specify an unusual short limit that wouldn't be able to hold
+ // the TSIG.
+ renderer.setLengthLimit(tsig_ctx.getTSIGLength() - 1);
+ // Use commonTSIGToWireCheck() only to call toWire() with otherwise valid
+ // conditions. The checks inside it don't matter because we expect an
+ // exception before any of the checks.
+ EXPECT_THROW(commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire2.wire"),
+ InvalidParameter);
+
+ // This one is large enough for TSIG, but the remaining limit isn't
+ // even enough for the Header section.
+ renderer.clear();
+ message_render.clear(Message::RENDER);
+ renderer.setLengthLimit(tsig_ctx.getTSIGLength() + 1);
+ EXPECT_THROW(commonTSIGToWireCheck(message_render, renderer, tsig_ctx,
+ "message_toWire2.wire"),
+ InvalidParameter);
+
+ // Trying to render a message with TSIG using a buggy renderer.
+ BadRenderer bad_renderer;
+ bad_renderer.setLengthLimit(512);
+ message_render.clear(Message::RENDER);
+ EXPECT_THROW(commonTSIGToWireCheck(message_render, bad_renderer, tsig_ctx,
+ "message_toWire2.wire"),
+ Unexpected);
+}
+
+TEST_F(MessageTest, toWireWithoutOpcode) {
+ message_render.setRcode(Rcode::NOERROR());
+ EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
+}
+
+TEST_F(MessageTest, toWireWithoutRcode) {
+ message_render.setOpcode(Opcode::QUERY());
+ EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
+}
+
+TEST_F(MessageTest, toText) {
+ // Check toText() output for a typical DNS response with records in
+ // all sections
+
+ factoryFromFile(message_parse, "message_toText1.wire");
+ {
+ SCOPED_TRACE("Message toText test (basic case)");
+ ifstream ifs;
+ unittests::openTestData("message_toText1.txt", ifs);
+ unittests::matchTextData(ifs, message_parse.toText());
+ }
+
+ // Another example with EDNS. The expected data was slightly modified
+ // from the dig output (other than replacing tabs with a space): adding
+ // a newline after the "OPT PSEUDOSECTION". This is an intentional change
+ // in our version for better readability.
+ message_parse.clear(Message::PARSE);
+ factoryFromFile(message_parse, "message_toText2.wire");
+ {
+ SCOPED_TRACE("Message toText test with EDNS");
+ ifstream ifs;
+ unittests::openTestData("message_toText2.txt", ifs);
+ unittests::matchTextData(ifs, message_parse.toText());
+ }
+
+ // Another example with TSIG. The expected data was slightly modified
+ // from the dig output (other than replacing tabs with a space): removing
+ // a redundant white space at the end of TSIG RDATA. We'd rather consider
+ // it a dig's defect than a feature.
+ message_parse.clear(Message::PARSE);
+ factoryFromFile(message_parse, "message_toText3.wire");
+ {
+ SCOPED_TRACE("Message toText test with TSIG");
+ ifstream ifs;
+ unittests::openTestData("message_toText3.txt", ifs);
+ unittests::matchTextData(ifs, message_parse.toText());
+ }
+}
+
+TEST_F(MessageTest, toTextWithoutOpcode) {
+ message_render.setRcode(Rcode::NOERROR());
+ EXPECT_THROW(message_render.toText(), InvalidMessageOperation);
+}
+
+TEST_F(MessageTest, toTextWithoutRcode) {
+ message_render.setOpcode(Opcode::QUERY());
+ EXPECT_THROW(message_render.toText(), InvalidMessageOperation);
+}
+}
diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc
new file mode 100644
index 0000000..784aa55
--- /dev/null
+++ b/src/lib/dns/tests/messagerenderer_unittest.cc
@@ -0,0 +1,292 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/labelsequence.h>
+#include <dns/messagerenderer.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <vector>
+
+using isc::UnitTestUtil;
+using isc::dns::Name;
+using isc::dns::LabelSequence;
+using isc::dns::MessageRenderer;
+using isc::util::OutputBuffer;
+using boost::lexical_cast;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class MessageRendererTest : public ::testing::Test {
+protected:
+ MessageRendererTest() : expected_size(0) {
+ data16 = (2 << 8) | 3;
+ data32 = (4 << 24) | (5 << 16) | (6 << 8) | 7;
+ }
+ size_t expected_size;
+ uint16_t data16;
+ uint32_t data32;
+ MessageRenderer renderer;
+ std::vector<unsigned char> data;
+ static const uint8_t testdata[5];
+};
+
+const uint8_t MessageRendererTest::testdata[5] = {1, 2, 3, 4, 5};
+
+// The test cases are borrowed from those for the OutputBuffer class.
+TEST_F(MessageRendererTest, writeInteger) {
+ renderer.writeUint16(data16);
+ expected_size += sizeof(data16);
+
+ matchWireData(&testdata[1], sizeof(data16),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeName) {
+ UnitTestUtil::readWireData("name_toWire1", data);
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("b.example.com."));
+ renderer.writeName(Name("a.example.org."));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNameInLargeBuffer) {
+ size_t offset = 0x3fff;
+ renderer.skip(offset);
+
+ UnitTestUtil::readWireData("name_toWire2", data);
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("b.example.com."));
+ matchWireData(&data[0], data.size(),
+ static_cast<const uint8_t*>(renderer.getData()) + offset,
+ renderer.getLength() - offset);
+}
+
+TEST_F(MessageRendererTest, writeNameWithUncompressed) {
+ UnitTestUtil::readWireData("name_toWire3", data);
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("b.example.com."), false);
+ renderer.writeName(Name("b.example.com."));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNamePointerChain) {
+ UnitTestUtil::readWireData("name_toWire4", data);
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("b.example.com."));
+ renderer.writeName(Name("b.example.com."));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, compressMode) {
+ // By default the render performs case insensitive compression.
+ EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode());
+
+ // The mode can be explicitly changed.
+ renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ EXPECT_EQ(MessageRenderer::CASE_SENSITIVE, renderer.getCompressMode());
+ renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE);
+ EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode());
+
+ // The clear() method resets the mode to the default.
+ renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ renderer.clear();
+ EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode());
+}
+
+TEST_F(MessageRendererTest, writeNameCaseCompress) {
+ // By default MessageRenderer performs case insensitive compression.
+
+ UnitTestUtil::readWireData("name_toWire1", data);
+ renderer.writeName(Name("a.example.com."));
+ // this should match the first name in terms of compression:
+ renderer.writeName(Name("b.exAmple.CoM."));
+ renderer.writeName(Name("a.example.org."));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) {
+ // name compression in case sensitive manner. See the data file
+ // description for details.
+ renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ UnitTestUtil::readWireData("name_toWire5.wire", data);
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("b.eXample.com."));
+ renderer.writeName(Name("c.eXample.com."));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNameMixedCaseCompress) {
+ renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE);
+ UnitTestUtil::readWireData("name_toWire6.wire", data);
+ renderer.writeName(Name("a.example.com."));
+ renderer.writeName(Name("b.eXample.com."));
+
+ // Change the compression mode in the middle of rendering. This is not
+ // allowed in this implementation.
+ EXPECT_THROW(renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE),
+ isc::InvalidParameter);
+
+ // Once the renderer is cleared, it's okay again.
+ renderer.clear();
+ EXPECT_NO_THROW(renderer.setCompressMode(
+ MessageRenderer::CASE_INSENSITIVE));
+}
+
+TEST_F(MessageRendererTest, writeRootName) {
+ // root name is special: it never causes compression or can (reasonably)
+ // be a compression pointer. So it makes sense to check this case
+ // explicitly.
+ Name example_name = Name("www.example.com");
+
+ OutputBuffer expected(0);
+ expected.writeUint8(0); // root name
+ example_name.toWire(expected);
+
+ renderer.writeName(Name("."));
+ renderer.writeName(example_name);
+ matchWireData(static_cast<const uint8_t*>(expected.getData()),
+ expected.getLength(),
+ static_cast<const uint8_t*>(renderer.getData()),
+ renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNameLabelSequence1) {
+ UnitTestUtil::readWireData("name_toWire7", data);
+
+ Name n1("a.example.com");
+ LabelSequence ls1(n1);
+
+ // a.example.com.
+ renderer.writeName(ls1);
+
+ ls1.stripLeft(1);
+
+ // example.com.
+ renderer.writeName(ls1);
+
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNameLabelSequence2) {
+ UnitTestUtil::readWireData("name_toWire8", data);
+
+ Name n1("a.example.com");
+ LabelSequence ls1(n1);
+
+ ls1.stripRight(1);
+
+ // a.example.com (without root .)
+ renderer.writeName(ls1);
+
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, writeNameLabelSequence3) {
+ UnitTestUtil::readWireData("name_toWire9", data);
+
+ Name n1("a.example.com");
+ LabelSequence ls1(n1);
+
+ // a.example.com.
+ renderer.writeName(ls1);
+
+ ls1.stripRight(1);
+
+ // a.example.com (without root .)
+ renderer.writeName(ls1);
+
+ ls1.stripRight(1);
+
+ // a.example
+ renderer.writeName(ls1);
+
+ ls1.stripLeft(1);
+
+ // example
+ renderer.writeName(ls1);
+
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(MessageRendererTest, setBuffer) {
+ OutputBuffer new_buffer(0);
+ renderer.setBuffer(&new_buffer);
+ EXPECT_EQ(0, new_buffer.getLength()); // the buffer should be still empty
+ renderer.writeUint32(42);
+ EXPECT_EQ(sizeof(uint32_t), new_buffer.getLength());
+ EXPECT_EQ(sizeof(uint32_t), renderer.getLength());
+
+ // Change some other internal state for the reset test below.
+ EXPECT_EQ(512, renderer.getLengthLimit());
+ renderer.setLengthLimit(4096);
+ EXPECT_EQ(4096, renderer.getLengthLimit());
+
+ // Reset the buffer to the default again. Other internal states and
+ // resources should be cleared. The used buffer should be intact.
+ renderer.setBuffer(0);
+ EXPECT_EQ(sizeof(uint32_t), new_buffer.getLength());
+ EXPECT_EQ(0, renderer.getLength());
+ EXPECT_EQ(512, renderer.getLengthLimit());
+}
+
+TEST_F(MessageRendererTest, setBufferErrors) {
+ OutputBuffer new_buffer(0);
+
+ // Buffer cannot be reset when the renderer is in use.
+ renderer.writeUint32(10);
+ EXPECT_THROW(renderer.setBuffer(&new_buffer), isc::InvalidParameter);
+
+ renderer.clear();
+ renderer.setBuffer(&new_buffer);
+ renderer.writeUint32(10);
+ EXPECT_THROW(renderer.setBuffer(&new_buffer), isc::InvalidParameter);
+
+ // Resetting the buffer isn't allowed for the default buffer.
+ renderer.setBuffer(0);
+ EXPECT_THROW(renderer.setBuffer(0), isc::InvalidParameter);
+
+ // It's okay to reset a temporary buffer without using it.
+ renderer.setBuffer(&new_buffer);
+ EXPECT_NO_THROW(renderer.setBuffer(0));
+}
+
+TEST_F(MessageRendererTest, manyRRs) {
+ // Render a large number of names, and the confirm the resulting wire
+ // data store the expected names in the correct order (1000 is an
+ // arbitrary choice).
+ for (size_t i = 0; i < 1000; ++i) {
+ renderer.writeName(Name(lexical_cast<std::string>(i) + ".example"));
+ }
+ isc::util::InputBuffer b(renderer.getData(), renderer.getLength());
+ for (size_t i = 0; i < 1000; ++i) {
+ EXPECT_EQ(Name(lexical_cast<std::string>(i) + ".example"), Name(b));
+ }
+ // This will trigger trimming excessive hash items. It shouldn't cause
+ // any disruption.
+ EXPECT_NO_THROW(renderer.clear());
+}
+}
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
new file mode 100644
index 0000000..d241701
--- /dev/null
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -0,0 +1,794 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <vector>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <stdexcept>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::util::unittests::matchWireData;
+
+//
+// XXX: these are defined as class static constants, but some compilers
+// seemingly cannot find the symbols when used in the EXPECT_xxx macros.
+//
+const size_t Name::MAX_WIRE;
+const size_t Name::MAX_LABELS;
+
+// This is a name of maximum allowed number of labels
+const char* max_labels_str = "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 40
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 80
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 120
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 160
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 200
+ "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 240
+ "0.1.2.3.4.5.6";
+// This is a name of maximum allowed length
+const char* max_len_str = "123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789."
+ "123";
+
+namespace {
+class NameTest : public ::testing::Test {
+protected:
+ NameTest() : example_name("www.example.com"),
+ example_name_upper("WWW.EXAMPLE.COM"),
+ small_name("aaa.example.com"),
+ large_name("zzz.example.com"),
+ origin_name("example.com."),
+ origin_name_upper("EXAMPLE.COM"),
+ buffer_actual(0), buffer_expected(0)
+ {}
+
+ const Name example_name;
+ Name example_name_upper; // this will be modified and cannot be const
+ const Name small_name;
+ const Name large_name;
+ const Name origin_name;
+ const Name origin_name_upper;
+ OutputBuffer buffer_actual, buffer_expected;
+
+ //
+ // helper methods
+ //
+ static Name nameFactoryFromWire(const char* datafile, size_t position,
+ bool downcase = false);
+ // construct a name including all non-upper-case-alphabet characters.
+ static Name nameFactoryLowerCase();
+ void compareInWireFormat(const Name& name_actual,
+ const Name& name_expected);
+};
+
+const Name downcased_global("\\255.EXAMPLE.COM", true);
+
+Name
+NameTest::nameFactoryFromWire(const char* datafile, size_t position,
+ bool downcase)
+{
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+ buffer.setPosition(position);
+
+ return (Name(buffer, downcase));
+}
+
+Name
+NameTest::nameFactoryLowerCase() {
+ string lowercase_namestr;
+ lowercase_namestr.reserve(Name::MAX_WIRE);
+
+ unsigned int ch = 0;
+ unsigned int labelcount = 0;
+ do {
+ if (ch < 'A' || ch > 'Z') {
+ ostringstream ss;
+ ss.setf(ios_base::right, ios_base::adjustfield);
+ ss.width(3);
+ ss << setfill('0') << ch;
+ lowercase_namestr += '\\' + ss.str();
+
+ if (++labelcount == Name::MAX_LABELLEN) {
+ lowercase_namestr.push_back('.');
+ labelcount = 0;
+ }
+ }
+ } while (++ch <= Name::MAX_WIRE);
+
+ return (Name(lowercase_namestr));
+}
+
+void
+NameTest::compareInWireFormat(const Name& name_actual,
+ const Name& name_expected)
+{
+ buffer_actual.clear();
+ buffer_expected.clear();
+
+ name_actual.toWire(buffer_actual);
+ name_expected.toWire(buffer_expected);
+
+ matchWireData(buffer_expected.getData(), buffer_expected.getLength(),
+ buffer_actual.getData(), buffer_actual.getLength());
+}
+
+TEST_F(NameTest, nonlocalObject) {
+ // A previous version of code relied on a non local static object for
+ // name construction, so a non local static Name object defined outside
+ // the name module might not be initialized correctly. This test detects
+ // that kind of bug.
+ EXPECT_EQ("\\255.example.com.", downcased_global.toText());
+}
+
+template <typename ExceptionType>
+void
+checkBadTextName(const string& txt) {
+ // Check it results in the specified type of exception as well as
+ // NameParserException.
+ EXPECT_THROW(Name(txt, false), ExceptionType);
+ EXPECT_THROW(Name(txt, false), NameParserException);
+ // The same is thrown when constructing by the master-file constructor
+ EXPECT_THROW(Name(txt.c_str(), txt.length(), &Name::ROOT_NAME()),
+ ExceptionType);
+ EXPECT_THROW(Name(txt.c_str(), txt.length(), &Name::ROOT_NAME()),
+ NameParserException);
+}
+
+TEST_F(NameTest, checkExceptionsHierarchy) {
+ EXPECT_NO_THROW({
+ const isc::dns::EmptyLabel exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::TooLongName exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::TooLongLabel exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::BadLabelType exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::BadEscape exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::IncompleteName exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+
+ EXPECT_NO_THROW({
+ const isc::dns::MissingNameOrigin exception("", 0, "");
+ const isc::dns::NameParserException& exception_cast =
+ dynamic_cast<const isc::dns::NameParserException&>(exception);
+ // to avoid compiler warning
+ exception_cast.what();
+ });
+}
+
+TEST_F(NameTest, fromText) {
+ vector<string> strnames;
+ strnames.push_back("www.example.com");
+ strnames.push_back("www.example.com."); // with a trailing dot
+ strnames.push_back("wWw.exAmpLe.com"); // mixed cases
+ strnames.push_back("\\wWw.exAmpLe.com"); // escape with a backslash
+ // decimal representation for "WWW"
+ strnames.push_back("\\087\\087\\087.example.com");
+
+ for (auto const& it : strnames) {
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name, Name(it));
+ }
+
+ // root names
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, Name("@"), Name("."));
+
+ // downcase
+ EXPECT_EQ(Name("Www.eXample.coM", true).toText(), example_name.toText());
+
+ //
+ // Tests for bogus names. These should trigger exceptions.
+ //
+ // empty label cannot be followed by another label
+ checkBadTextName<EmptyLabel>(".a");
+ // duplicate period
+ checkBadTextName<EmptyLabel>("a..");
+ // label length must be < 64
+ checkBadTextName<TooLongLabel>("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "0123");
+ // now-unsupported bitstring labels
+ checkBadTextName<BadLabelType>("\\[b11010000011101]");
+ // label length must be < 64
+ checkBadTextName<TooLongLabel>("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "012\\x");
+ // but okay as long as resulting len < 64 even if the original string is
+ // "too long"
+ EXPECT_NO_THROW(Name("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "01\\x"));
+ // incomplete \DDD pattern (exactly 3 D's must appear)
+ checkBadTextName<BadEscape>("\\12abc");
+ // \DDD must not exceed 255
+ checkBadTextName<BadEscape>("\\256");
+ // Same tests for \111 as for \\x above
+ checkBadTextName<TooLongLabel>("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "012\\111");
+ EXPECT_NO_THROW(Name("012345678901234567890123456789"
+ "012345678901234567890123456789"
+ "01\\111"));
+ // A domain name must be 255 octets or less
+ checkBadTextName<TooLongName>("123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789."
+ "123456789.1234");
+ // This is a possible longest name and should be accepted
+ EXPECT_NO_THROW(Name(string(max_len_str)));
+ // \DDD must consist of 3 digits.
+ checkBadTextName<IncompleteName>("\\12");
+
+ // a name with the max number of labels. should be constructed without
+ // an error, and its length should be the max value.
+ Name maxlabels = Name(string(max_labels_str));
+ EXPECT_EQ(Name::MAX_LABELS, maxlabels.getLabelCount());
+}
+
+// The following test uses a name data that was produced by
+// fuzz testing and causes an unexpected condition in stringParser.
+// Formerly this condition was trapped by an assert, but for
+// robustness it has been replaced by a throw.
+TEST_F(NameTest, unexpectedParseError) {
+ std::vector<uint8_t> badname {
+ 0xff,0xff,0x7f,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x04,0x63,0x82,0x53,0x63,0x35,0x01,0x01,0x3d,0x07,0x01,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x19,0x0c,0x4e,0x01,0x00,0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x04,0x00,0x00,0x07,0x08,0x3b,0x04,0x00,
+ 0x00,0x2e,0x3b,0x04,0x00,0x19,0x2e,0x00,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x04,0x00,0x00,0x07,0x08,0x3b,0x04,
+ 0x00,0x00,0x2e,0x3b,0x04,0x00,0x19,0x2e,0x56,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x19,0x0c,
+ 0x4e,0x01,0x05,0x3a,0x04,0xde,0x00,0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x07,0x08,0x3b,0x04,0x00,0x00,0x2e,0x3b,0x04,
+ 0x00,0x19,0x2e,0x56,0x40,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,0x00,
+ 0x00,0x00,0x19,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x35,0x01,0x05,0xff,0xff,0x05,0x00,0x07,0x08,0x3b,0x04,
+ 0x00,0x00,0x2e,0x3b
+ };
+
+ std::string badnamestr(badname.begin(), badname.end());
+ EXPECT_THROW(Name(badnamestr, false), Unexpected);
+}
+
+// on the rest while we prepare it.
+// Check the @ syntax is accepted and it just copies the origin.
+TEST_F(NameTest, copyOrigin) {
+ EXPECT_EQ(origin_name, Name("@", 1, &origin_name));
+ // The downcase works on the origin too. But only when we provide it.
+ EXPECT_EQ(origin_name, Name("@", 1, &origin_name_upper, true));
+ EXPECT_EQ(origin_name_upper, Name("@", 1, &origin_name_upper, true));
+ // If we don't provide the origin, it throws
+ EXPECT_THROW(Name("@", 1, 0), MissingNameOrigin);
+}
+
+// Test the master-file constructor does not append the origin when the
+// provided name is absolute
+TEST_F(NameTest, dontAppendOrigin) {
+ EXPECT_EQ(example_name, Name("www.example.com.", 16, &origin_name));
+ // The downcase works (only if provided, though)
+ EXPECT_EQ(example_name, Name("WWW.EXAMPLE.COM.", 16, &origin_name, true));
+ EXPECT_EQ(example_name_upper, Name("WWW.EXAMPLE.COM.", 16, &origin_name));
+ // And it does not require the origin to be provided
+ EXPECT_NO_THROW(Name("www.example.com.", 16, 0));
+}
+
+// Test the master-file constructor properly appends the origin when
+// the provided name is relative.
+TEST_F(NameTest, appendOrigin) {
+ EXPECT_EQ(example_name, Name("www", 3, &origin_name));
+ // Check the downcase works (if provided)
+ EXPECT_EQ(example_name, Name("WWW", 3, &origin_name, true));
+ EXPECT_EQ(example_name, Name("WWW", 3, &origin_name_upper, true));
+ EXPECT_EQ(example_name_upper, Name("WWW", 3, &origin_name_upper));
+ // Check we can prepend more than one label
+ EXPECT_EQ(Name("a.b.c.d.example.com."), Name("a.b.c.d", 7, &origin_name));
+ // When the name is relative, we throw.
+ EXPECT_THROW(Name("www", 3, 0), MissingNameOrigin);
+}
+
+// When we don't provide the data, it throws
+TEST_F(NameTest, noDataProvided) {
+ EXPECT_THROW(Name(0, 10, 0), isc::InvalidParameter);
+ EXPECT_THROW(Name(0, 10, &origin_name), isc::InvalidParameter);
+ EXPECT_THROW(Name("www", 0, 0), isc::InvalidParameter);
+ EXPECT_THROW(Name("www", 0, &origin_name), isc::InvalidParameter);
+}
+
+// When we combine the first part and the origin together, the resulting name
+// is too long. It should throw. Other test checks this is valid when alone
+// (without the origin appended).
+TEST_F(NameTest, combinedTooLong) {
+ EXPECT_THROW(Name(max_len_str, strlen(max_len_str), &origin_name),
+ TooLongName);
+ EXPECT_THROW(Name(max_labels_str, strlen(max_labels_str), &origin_name),
+ TooLongName);
+ // Appending the root should be OK
+ EXPECT_NO_THROW(Name(max_len_str, strlen(max_len_str),
+ &Name::ROOT_NAME()));
+ EXPECT_NO_THROW(Name(max_labels_str, strlen(max_labels_str),
+ &Name::ROOT_NAME()));
+}
+
+// Test the handling of @ in the name. If it is alone, it is the origin (when
+// it exists) or the root. If it is somewhere else, it has no special meaning.
+TEST_F(NameTest, atSign) {
+ // If it is alone, it is the origin
+ EXPECT_EQ(origin_name, Name("@", 1, &origin_name));
+ EXPECT_THROW(Name("@", 1, 0), MissingNameOrigin);
+ EXPECT_EQ(Name::ROOT_NAME(), Name("@"));
+
+ // It is not alone. It is taken verbatim. We check the name converted
+ // back to the textual form, since checking it against other name object
+ // may be wrong -- if we create it wrong the same way as the tested
+ // object.
+ EXPECT_EQ("\\@.", Name("@.").toText());
+ EXPECT_EQ("\\@.", Name("@.", 2, 0).toText());
+ EXPECT_EQ("\\@something.", Name("@something").toText());
+ EXPECT_EQ("something\\@.", Name("something@").toText());
+ EXPECT_EQ("\\@x.example.com.", Name("@x", 2, &origin_name).toText());
+ EXPECT_EQ("x\\@.example.com.", Name("x@", 2, &origin_name).toText());
+
+ // An escaped at-sign isn't active
+ EXPECT_EQ("\\@.", Name("\\@").toText());
+ EXPECT_EQ("\\@.example.com.", Name("\\@", 2, &origin_name).toText());
+}
+
+TEST_F(NameTest, fromWire) {
+ //
+ // test cases derived from BIND9 tests.
+ //
+ // normal case with a compression pointer
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName,
+ nameFactoryFromWire("name_fromWire1", 25),
+ Name("vix.com"));
+ // bogus label character (looks like a local compression pointer)
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire2", 25), DNSMessageFORMERR);
+ // a bad compression pointer (too big)
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire3_1", 25),
+ DNSMessageFORMERR);
+ // forward reference
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire3_2", 25),
+ DNSMessageFORMERR);
+ // invalid name length
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire4", 550), DNSMessageFORMERR);
+
+ // skip test for from Wire5. It's for disabling decompression, but our
+ // implementation always allows it.
+
+ // bad pointer (too big)
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire6", 25), DNSMessageFORMERR);
+ // input ends unexpectedly
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire7", 25), DNSMessageFORMERR);
+ // many hops of compression but valid. should succeed.
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName,
+ nameFactoryFromWire("name_fromWire8", 383),
+ Name("vix.com"));
+
+ //
+ // Additional test cases
+ //
+
+ // large names, a long but valid one, and invalid (too long) one.
+ EXPECT_EQ(Name::MAX_WIRE,
+ nameFactoryFromWire("name_fromWire9", 0).getLength());
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire10", 0).getLength(),
+ DNSMessageFORMERR);
+
+ // A name with possible maximum number of labels; awkward but valid
+ EXPECT_EQ(nameFactoryFromWire("name_fromWire11", 0).getLabelCount(),
+ Name::MAX_LABELS);
+
+ // Wire format including an invalid label length
+ EXPECT_THROW(nameFactoryFromWire("name_fromWire12", 0), DNSMessageFORMERR);
+
+ // converting upper-case letters to down-case
+ EXPECT_EQ("vix.com.",
+ nameFactoryFromWire("name_fromWire1", 25, true).toText());
+ EXPECT_EQ(3, nameFactoryFromWire("name_fromWire1", 25).getLabelCount());
+}
+
+TEST_F(NameTest, copyConstruct) {
+ Name copy(example_name);
+ EXPECT_EQ(copy, example_name);
+
+ // Check the copied data is valid even after the original is deleted
+ Name* copy2 = new Name(example_name);
+ Name copy3(*copy2);
+ delete copy2;
+ EXPECT_EQ(copy3, example_name);
+}
+
+TEST_F(NameTest, assignment) {
+ Name copy(".");
+ copy = example_name;
+ EXPECT_EQ(copy, example_name);
+
+ // Check if the copied data is valid even after the original is deleted
+ Name* copy2 = new Name(example_name);
+ Name copy3(".");
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(copy3, example_name);
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(example_name, copy);
+}
+
+TEST_F(NameTest, toText) {
+ // tests derived from BIND9
+ EXPECT_EQ("a.b.c.d", Name("a.b.c.d").toText(true));
+ EXPECT_EQ("a.\\\\[[.c.d", Name("a.\\\\[\\[.c.d").toText(true));
+ EXPECT_EQ("a.b.C.d.", Name("a.b.C.d").toText(false));
+ EXPECT_EQ("a.b.", Name("a.b.").toText(false));
+
+ // test omit_final_dot. It's false by default.
+ EXPECT_EQ("a.b.c.d", Name("a.b.c.d.").toText(true));
+ EXPECT_EQ(Name("a.b.").toText(false), Name("a.b.").toText());
+
+ // the root name is a special case: omit_final_dot will be ignored.
+ EXPECT_EQ(".", Name(".").toText(true));
+
+ // test all printable characters to see whether special characters are
+ // escaped while the others are intact. note that the conversion is
+ // implementation specific; for example, it's not invalid to escape a
+ // "normal" character such as 'a' with regard to the standard.
+ string all_printable("!\\\"#\\$%&'\\(\\)*+,-\\./0123456789:\\;<=>?\\@"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "[\\\\]^_.`abcdefghijklmnopqrstuvwxyz{|}~.");
+ EXPECT_EQ(all_printable,
+ nameFactoryFromWire("name_fromWire13", 0).toText());
+
+ string all_nonprintable(
+ "\\000\\001\\002\\003\\004\\005\\006\\007\\008\\009"
+ "\\010\\011\\012\\013\\014\\015\\016\\017\\018\\019"
+ "\\020\\021\\022\\023\\024\\025\\026\\027\\028\\029"
+ "\\030\\031\\032\\127\\128\\129"
+ "\\130\\131\\132\\133\\134\\135\\136\\137\\138\\139"
+ "\\140\\141\\142\\143\\144\\145\\146\\147\\148\\149"
+ "\\150\\151\\152\\153\\154\\155\\156."
+ "\\157\\158\\159"
+ "\\160\\161\\162\\163\\164\\165\\166\\167\\168\\169"
+ "\\170\\171\\172\\173\\174\\175\\176\\177\\178\\179"
+ "\\180\\181\\182\\183\\184\\185\\186\\187\\188\\189"
+ "\\190\\191\\192\\193\\194\\195\\196\\197\\198\\199"
+ "\\200\\201\\202\\203\\204\\205\\206\\207\\208\\209"
+ "\\210\\211\\212\\213\\214\\215\\216\\217\\218\\219."
+ "\\220\\221\\222\\223\\224\\225\\226\\227\\228\\229"
+ "\\230\\231\\232\\233\\234\\235\\236\\237\\238\\239"
+ "\\240\\241\\242\\243\\244\\245\\246\\247\\248\\249"
+ "\\250\\251\\252\\253\\254\\255.");
+ EXPECT_EQ(all_nonprintable,
+ nameFactoryFromWire("name_fromWire14", 0).toText());
+}
+
+TEST_F(NameTest, toWireBuffer) {
+ vector<unsigned char> data;
+ OutputBuffer buffer(0);
+
+ UnitTestUtil::readWireData(string("01610376697803636f6d00"), data);
+ Name("a.vix.com.").toWire(buffer);
+ matchWireData(&data[0], data.size(),
+ buffer.getData(), buffer.getLength());
+}
+
+//
+// We test various corner cases in Renderer tests, but add this test case
+// to fill the code coverage gap.
+//
+TEST_F(NameTest, toWireRenderer) {
+ vector<unsigned char> data;
+ MessageRenderer renderer;
+
+ UnitTestUtil::readWireData(string("01610376697803636f6d00"), data);
+ Name("a.vix.com.").toWire(renderer);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+//
+// Helper class to hold comparison test parameters.
+//
+struct CompareParameters {
+ CompareParameters(const Name& n1, const Name& n2,
+ NameComparisonResult::NameRelation r, int o,
+ unsigned int l) :
+ name1(n1), name2(n2), reln(r), order(o), labels(l) {}
+ static int normalizeOrder(int o)
+ {
+ if (o > 0) {
+ return (1);
+ } else if (o < 0) {
+ return (-1);
+ }
+ return (0);
+ }
+ Name name1;
+ Name name2;
+ NameComparisonResult::NameRelation reln;
+ int order;
+ unsigned int labels;
+};
+
+TEST_F(NameTest, compare) {
+ vector<CompareParameters> params;
+ params.push_back(CompareParameters(Name("c.d"), Name("a.b.c.d"),
+ NameComparisonResult::SUPERDOMAIN,
+ -1, 3));
+ params.push_back(CompareParameters(Name("a.b.c.d"), Name("c.d"),
+ NameComparisonResult::SUBDOMAIN, 1, 3));
+ params.push_back(CompareParameters(Name("a.b.c.d"), Name("c.d.e.f"),
+ NameComparisonResult::COMMONANCESTOR,
+ -1, 1));
+ params.push_back(CompareParameters(Name("a.b.c.d"), Name("f.g.c.d"),
+ NameComparisonResult::COMMONANCESTOR,
+ -1, 3));
+ params.push_back(CompareParameters(Name("a.b.c.d"), Name("A.b.C.d."),
+ NameComparisonResult::EQUAL,
+ 0, 5));
+
+ for (auto const& it : params) {
+ NameComparisonResult result = it.name1.compare(it.name2);
+ EXPECT_EQ(it.reln, result.getRelation());
+ EXPECT_EQ(it.order, CompareParameters::normalizeOrder(result.getOrder()));
+ EXPECT_EQ(it.labels, result.getCommonLabels());
+ }
+}
+
+TEST_F(NameTest, equal) {
+ EXPECT_TRUE(example_name == Name("WWW.EXAMPLE.COM."));
+ EXPECT_TRUE(example_name.equals(Name("WWW.EXAMPLE.COM.")));
+ EXPECT_TRUE(example_name != Name("www.example.org."));
+ EXPECT_TRUE(example_name.nequals(Name("www.example.org.")));
+ // lengths don't match
+ EXPECT_TRUE(example_name != Name("www2.example.com."));
+ EXPECT_TRUE(example_name.nequals(Name("www2.example.com.")));
+ // lengths are equal, but # of labels don't match (first test checks the
+ // prerequisite).
+ EXPECT_EQ(example_name.getLength(), Name("www\\.example.com.").getLength());
+ EXPECT_TRUE(example_name != Name("www\\.example.com."));
+ EXPECT_TRUE(example_name.nequals(Name("www\\.example.com.")));
+}
+
+TEST_F(NameTest, isWildcard) {
+ EXPECT_FALSE(example_name.isWildcard());
+ EXPECT_TRUE(Name("*.a.example.com").isWildcard());
+ EXPECT_FALSE(Name("a.*.example.com").isWildcard());
+}
+
+TEST_F(NameTest, concatenate) {
+ NameComparisonResult result =
+ Name("aaa.www.example.com.").compare(Name("aaa").concatenate(example_name));
+ EXPECT_EQ(NameComparisonResult::EQUAL, result.getRelation());
+
+ result = example_name.compare(Name(".").concatenate(example_name));
+ EXPECT_EQ(NameComparisonResult::EQUAL, result.getRelation());
+
+ result = example_name.compare(example_name.concatenate(Name(".")));
+ EXPECT_EQ(NameComparisonResult::EQUAL, result.getRelation());
+
+ // concatenating two valid names would result in too long a name.
+ Name n1("123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789.");
+ Name n2("123456789.123456789.123456789.123456789.123456789."
+ "123456789.123456789.123456789.123456789.123456789."
+ "1234.");
+ EXPECT_THROW(n1.concatenate(n2), TooLongName);
+}
+
+TEST_F(NameTest, reverse) {
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.reverse(),
+ Name("com.example.www."));
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, Name(".").reverse(),
+ Name("."));
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName,
+ Name("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s").reverse(),
+ Name("s.r.q.p.o.n.m.l.k.j.i.h.g.f.e.d.c.b.a"));
+}
+
+TEST_F(NameTest, split) {
+ // normal cases with or without explicitly specifying the trailing dot.
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(1, 2),
+ Name("example.com."));
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(1, 3),
+ Name("example.com."));
+ // edge cases: only the first or last label.
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(0, 1),
+ Name("www."));
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(3, 1),
+ Name("."));
+ // invalid range: an exception should be thrown.
+ EXPECT_THROW(example_name.split(1, 0), OutOfRange);
+ EXPECT_THROW(example_name.split(2, 3), OutOfRange);
+
+ // invalid range: the following parameters would cause overflow,
+ // bypassing naive validation.
+ EXPECT_THROW(example_name.split(1, numeric_limits<unsigned int>::max()),
+ OutOfRange);
+}
+
+TEST_F(NameTest, split_for_suffix) {
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(1),
+ Name("example.com"));
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(0),
+ example_name);
+ EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(3),
+ Name("."));
+
+ // Invalid case: the level must be less than the original label count.
+ EXPECT_THROW(example_name.split(4), OutOfRange);
+}
+
+TEST_F(NameTest, downcase) {
+ // usual case: all-upper case name to all-lower case
+ compareInWireFormat(example_name_upper.downcase(), example_name);
+ // confirm that non upper-case characters are intact
+ compareInWireFormat(nameFactoryLowerCase().downcase(),
+ nameFactoryLowerCase());
+ // confirm the calling object is actually modified
+ example_name_upper.downcase();
+ compareInWireFormat(example_name_upper, example_name);
+}
+
+TEST_F(NameTest, at) {
+ // Confirm at() produces the exact sequence of wire-format name data
+ vector<uint8_t> data;
+
+ for (size_t i = 0; i < example_name.getLength(); i++) {
+ data.push_back(example_name.at(i));
+ }
+
+ example_name.toWire(buffer_expected);
+ matchWireData(&data[0], data.size(),
+ buffer_expected.getData(), buffer_expected.getLength());
+
+ // Out-of-range access: should trigger an exception.
+ EXPECT_THROW(example_name.at(example_name.getLength()), OutOfRange);
+}
+
+//
+// The following set of tests confirm the result of <=, <, >=, >
+// The test logic is simple, and all tests are just straightforward variations
+// of the first one.
+//
+TEST_F(NameTest, leq) {
+ // small <= large is true
+ EXPECT_TRUE(small_name.leq(large_name));
+ EXPECT_TRUE(small_name <= large_name);
+
+ // small <= small is true
+ EXPECT_TRUE(small_name.leq(small_name));
+ EXPECT_LE(small_name, small_name);
+
+ // large <= small is false
+ EXPECT_FALSE(large_name.leq(small_name));
+ EXPECT_FALSE(large_name <= small_name);
+}
+
+TEST_F(NameTest, geq) {
+ EXPECT_TRUE(large_name.geq(small_name));
+ EXPECT_TRUE(large_name >= small_name);
+
+ EXPECT_TRUE(large_name.geq(large_name));
+ EXPECT_GE(large_name, large_name);
+
+ EXPECT_FALSE(small_name.geq(large_name));
+ EXPECT_FALSE(small_name >= large_name);
+}
+
+TEST_F(NameTest, lthan) {
+ EXPECT_TRUE(small_name.lthan(large_name));
+ EXPECT_TRUE(small_name < large_name);
+
+ EXPECT_FALSE(small_name.lthan(small_name));
+ // cppcheck-suppress duplicateExpression
+ EXPECT_FALSE(small_name < small_name);
+
+ EXPECT_FALSE(large_name.lthan(small_name));
+ EXPECT_FALSE(large_name < small_name);
+}
+
+TEST_F(NameTest, gthan) {
+ EXPECT_TRUE(large_name.gthan(small_name));
+ EXPECT_TRUE(large_name > small_name);
+
+ EXPECT_FALSE(large_name.gthan(large_name));
+ // cppcheck-suppress duplicateExpression
+ EXPECT_FALSE(large_name > large_name);
+
+ EXPECT_FALSE(small_name.gthan(large_name));
+ EXPECT_FALSE(small_name > large_name);
+}
+
+TEST_F(NameTest, constants) {
+ EXPECT_EQ(Name("."), Name::ROOT_NAME());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(NameTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << example_name;
+ EXPECT_EQ(example_name.toText(), oss.str());
+}
+
+// The following verifies that toRawText() returns a string
+// actual characters in place of escape sequences. We do not
+// bother with an exhaustive set of tests here as this is
+// not a primary use case.
+TEST_F(NameTest, toRawText) {
+ Name n("a bc.$exa(m)ple.@org");
+ EXPECT_EQ("a bc.$exa(m)ple.@org", n.toRawText(true));
+ EXPECT_EQ("a bc.$exa(m)ple.@org.", n.toRawText(false));
+ // Verify default value of omit parameter is false.
+ EXPECT_EQ("a bc.$exa(m)ple.@org.", n.toRawText());
+}
+
+}
diff --git a/src/lib/dns/tests/opcode_unittest.cc b/src/lib/dns/tests/opcode_unittest.cc
new file mode 100644
index 0000000..f45b89e
--- /dev/null
+++ b/src/lib/dns/tests/opcode_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <vector>
+#include <sstream>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/opcode.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+TEST(OpcodeTest, construct) {
+ // This test also tests getCode()
+ EXPECT_EQ(0, Opcode(0).getCode());
+ EXPECT_EQ(15, Opcode(Opcode::RESERVED15_CODE).getCode());
+
+ EXPECT_THROW(Opcode(16), isc::OutOfRange);
+}
+
+TEST(OpcodeTest, constants) {
+ // We'll only test arbitrarily chosen subsets of the codes.
+ // This class is quite simple, so it should be suffice.
+
+ EXPECT_EQ(Opcode::QUERY_CODE, Opcode(0).getCode());
+ EXPECT_EQ(Opcode::IQUERY_CODE, Opcode(1).getCode());
+ EXPECT_EQ(Opcode::NOTIFY_CODE, Opcode(4).getCode());
+ EXPECT_EQ(Opcode::UPDATE_CODE, Opcode(5).getCode());
+ EXPECT_EQ(Opcode::RESERVED15_CODE, Opcode(15).getCode());
+
+ EXPECT_EQ(Opcode::QUERY_CODE, Opcode::QUERY().getCode());
+ EXPECT_EQ(Opcode::IQUERY_CODE, Opcode::IQUERY().getCode());
+ EXPECT_EQ(Opcode::NOTIFY_CODE, Opcode::NOTIFY().getCode());
+ EXPECT_EQ(Opcode::UPDATE_CODE, Opcode::UPDATE().getCode());
+ EXPECT_EQ(Opcode::RESERVED15_CODE, Opcode::RESERVED15().getCode());
+}
+
+TEST(OpcodeTest, equal) {
+ EXPECT_TRUE(Opcode::QUERY() == Opcode(Opcode::QUERY_CODE));
+ EXPECT_TRUE(Opcode::QUERY().equals(Opcode(Opcode::QUERY_CODE)));
+ EXPECT_TRUE(Opcode::IQUERY() == Opcode(Opcode::IQUERY_CODE));
+ EXPECT_TRUE(Opcode::IQUERY().equals(Opcode(Opcode::IQUERY_CODE)));
+ EXPECT_TRUE(Opcode::NOTIFY() == Opcode(Opcode::NOTIFY_CODE));
+ EXPECT_TRUE(Opcode::NOTIFY().equals(Opcode(Opcode::NOTIFY_CODE)));
+ EXPECT_TRUE(Opcode::UPDATE() == Opcode(Opcode::UPDATE_CODE));
+ EXPECT_TRUE(Opcode::UPDATE().equals(Opcode(Opcode::UPDATE_CODE)));
+ EXPECT_TRUE(Opcode::RESERVED15() == Opcode(Opcode::RESERVED15()));
+ EXPECT_TRUE(Opcode::RESERVED15().equals(Opcode(Opcode::RESERVED15())));
+}
+
+TEST(OpcodeTest, nequal) {
+ EXPECT_TRUE(Opcode::QUERY() != Opcode::IQUERY());
+ EXPECT_TRUE(Opcode::QUERY().nequals(Opcode::IQUERY()));
+ EXPECT_TRUE(Opcode::NOTIFY() != Opcode(1));
+ EXPECT_TRUE(Opcode::NOTIFY().nequals(Opcode(1)));
+ EXPECT_TRUE(Opcode(10) != Opcode(11));
+ EXPECT_TRUE(Opcode(10).nequals(Opcode(11)));
+}
+
+TEST(OpcodeTest, toText) {
+ vector<const char*> expects;
+ expects.resize(Opcode::RESERVED15_CODE + 1);
+ expects[Opcode::QUERY_CODE] = "QUERY";
+ expects[Opcode::IQUERY_CODE] = "IQUERY";
+ expects[Opcode::STATUS_CODE] = "STATUS";
+ expects[Opcode::RESERVED3_CODE] = "RESERVED3";
+ expects[Opcode::NOTIFY_CODE] = "NOTIFY";
+ expects[Opcode::UPDATE_CODE] = "UPDATE";
+ expects[Opcode::RESERVED6_CODE] = "RESERVED6";
+ expects[Opcode::RESERVED7_CODE] = "RESERVED7";
+ expects[Opcode::RESERVED8_CODE] = "RESERVED8";
+ expects[Opcode::RESERVED9_CODE] = "RESERVED9";
+ expects[Opcode::RESERVED10_CODE] = "RESERVED10";
+ expects[Opcode::RESERVED11_CODE] = "RESERVED11";
+ expects[Opcode::RESERVED12_CODE] = "RESERVED12";
+ expects[Opcode::RESERVED13_CODE] = "RESERVED13";
+ expects[Opcode::RESERVED14_CODE] = "RESERVED14";
+ expects[Opcode::RESERVED15_CODE] = "RESERVED15";
+
+ for (unsigned int i = 0; i <= Opcode::RESERVED15_CODE; ++i) {
+ EXPECT_EQ(expects.at(i), Opcode(i).toText());
+ }
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(OpcodeTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << Opcode::NOTIFY();
+ EXPECT_EQ(Opcode::NOTIFY().toText(), oss.str());
+}
+}
diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc
new file mode 100644
index 0000000..443d900
--- /dev/null
+++ b/src/lib/dns/tests/question_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <vector>
+#include <sstream>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class QuestionTest : public ::testing::Test {
+protected:
+ QuestionTest() : obuffer(0),
+ example_name1(Name("foo.example.com")),
+ example_name2(Name("bar.example.com")),
+ test_question1(example_name1, RRClass::IN(),
+ RRType::NS()),
+ test_question2(example_name2, RRClass::CH(),
+ RRType::A()) {
+ }
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+ Name example_name1;
+ Name example_name2;
+ Question test_question1;
+ Question test_question2;
+ vector<unsigned char> wiredata;
+};
+
+Question
+questionFromWire(const char* datafile, size_t position = 0) {
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+ buffer.setPosition(position);
+
+ return (Question(buffer));
+}
+
+TEST_F(QuestionTest, fromWire) {
+ Question q = questionFromWire("question_fromWire");
+
+ EXPECT_EQ(example_name1, q.getName());
+ EXPECT_EQ(RRClass::IN(), q.getClass());
+ EXPECT_EQ(RRType::NS(), q.getType());
+
+ // owner name of the second Question is compressed. It's uncommon
+ // (to have multiple questions), but isn't prohibited by the protocol.
+ q = questionFromWire("question_fromWire", 21);
+ EXPECT_EQ(example_name2, q.getName());
+ EXPECT_EQ(RRClass::CH(), q.getClass());
+ EXPECT_EQ(RRType::A(), q.getType());
+
+ // Pathological cases: Corresponding exceptions will be thrown from
+ // the underlying parser.
+ EXPECT_THROW(questionFromWire("question_fromWire", 31), DNSMessageFORMERR);
+ EXPECT_THROW(questionFromWire("question_fromWire", 36), IncompleteRRClass);
+}
+
+TEST_F(QuestionTest, toText) {
+ EXPECT_EQ("foo.example.com. IN NS", test_question1.toText());
+ EXPECT_EQ("bar.example.com. CH A", test_question2.toText());
+
+ EXPECT_EQ("foo.example.com. IN NS", test_question1.toText(false));
+ EXPECT_EQ("bar.example.com. CH A", test_question2.toText(false));
+
+ EXPECT_EQ("foo.example.com. IN NS\n", test_question1.toText(true));
+ EXPECT_EQ("bar.example.com. CH A\n", test_question2.toText(true));
+}
+
+TEST_F(QuestionTest, toWireBuffer) {
+ test_question1.toWire(obuffer);
+ test_question2.toWire(obuffer);
+ UnitTestUtil::readWireData("question_toWire1", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(QuestionTest, toWireRenderer) {
+ test_question1.toWire(renderer);
+ test_question2.toWire(renderer);
+ UnitTestUtil::readWireData("question_toWire2", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(QuestionTest, toWireTruncated) {
+ // If the available length in the renderer is too small, it would require
+ // truncation. This won't happen in normal cases, but protocol wise it
+ // could still happen if and when we support some (possibly future) opcode
+ // that allows multiple questions.
+
+ // Set the length limit to the qname length so that the whole question
+ // would request truncated
+ renderer.setLengthLimit(example_name1.getLength());
+
+ EXPECT_FALSE(renderer.isTruncated()); // check pre-render condition
+ EXPECT_EQ(0, test_question1.toWire(renderer));
+ EXPECT_TRUE(renderer.isTruncated());
+ EXPECT_EQ(0, renderer.getLength()); // renderer shouldn't have any data
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(QuestionTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << test_question1;
+ EXPECT_EQ(test_question1.toText(), oss.str());
+}
+
+TEST_F(QuestionTest, comparison) {
+ const Name a("a");
+ const Name b("b");
+ const RRClass in(RRClass::IN());
+ const RRClass ch(RRClass::CH());
+ const RRType ns(RRType::NS());
+ const RRType aaaa(RRType::AAAA());
+
+ EXPECT_TRUE(Question(a, in, ns) < Question(a, in, aaaa));
+ EXPECT_FALSE(Question(a, in, aaaa) < Question(a, in, ns));
+
+ EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, ns));
+ EXPECT_FALSE(Question(a, ch, ns) < Question(a, in, ns));
+
+ EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, aaaa));
+ EXPECT_FALSE(Question(a, ch, aaaa) < Question(a, in, ns));
+
+ EXPECT_TRUE(Question(a, in, ns) < Question(b, in, ns));
+ EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns));
+
+ EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, ns));
+ EXPECT_FALSE(Question(b, ch, ns) < Question(a, in, ns));
+
+ EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, aaaa));
+ EXPECT_FALSE(Question(b, ch, aaaa) < Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns));
+ EXPECT_FALSE(Question(a, ch, ns) < Question(a, ch, ns));
+ EXPECT_FALSE(Question(b, in, ns) < Question(b, in, ns));
+ EXPECT_FALSE(Question(b, in, aaaa) < Question(b, in, aaaa));
+
+ // Identical questions are equal
+
+ EXPECT_TRUE(Question(a, in, ns) == Question(a, in, ns));
+ EXPECT_FALSE(Question(a, in, ns) != Question(a, in, ns));
+
+ // Components differing by one component are unequal...
+
+ EXPECT_FALSE(Question(b, in, ns) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, in, ns) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, ch, ns) == Question(a, in, ns));
+ EXPECT_TRUE(Question(a, ch, ns) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, in, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(a, in, aaaa) != Question(a, in, ns));
+
+ // ... as are those differing by two components
+
+ EXPECT_FALSE(Question(b, ch, ns) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, ch, ns) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(b, in, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, in, aaaa) != Question(a, in, ns));
+
+ EXPECT_FALSE(Question(a, ch, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(a, ch, aaaa) != Question(a, in, ns));
+
+ // ... and question differing by all three
+
+ EXPECT_FALSE(Question(b, ch, aaaa) == Question(a, in, ns));
+ EXPECT_TRUE(Question(b, ch, aaaa) != Question(a, in, ns));
+}
+
+}
diff --git a/src/lib/dns/tests/rcode_unittest.cc b/src/lib/dns/tests/rcode_unittest.cc
new file mode 100644
index 0000000..7f7a3c8
--- /dev/null
+++ b/src/lib/dns/tests/rcode_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <vector>
+#include <sstream>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+TEST(RcodeTest, constructFromCode) {
+ // Normal cases. This test also tests getCode()
+ EXPECT_EQ(0, Rcode(0).getCode());
+ EXPECT_EQ(0xfff, Rcode(0xfff).getCode()); // possible max code
+
+ // should fail on attempt of construction with an out of range code
+ EXPECT_THROW(Rcode(0x1000), isc::OutOfRange);
+ EXPECT_THROW(Rcode(0xffff), isc::OutOfRange);
+}
+
+TEST(RcodeTest, constructFromCodePair) {
+ EXPECT_EQ(3, Rcode(Rcode::NXDOMAIN_CODE, 0).getCode());
+ EXPECT_EQ(Rcode::BADVERS_CODE, Rcode(0, 1).getCode());
+ EXPECT_EQ(0xfff, Rcode(0xf, 0xff).getCode());
+ EXPECT_THROW(Rcode(0x10, 0xff), isc::OutOfRange);
+}
+
+TEST(RcodeTest, getExtendedCode) {
+ EXPECT_EQ(0, Rcode::NOERROR().getExtendedCode());
+ EXPECT_EQ(0, Rcode::YXRRSET().getExtendedCode());
+ EXPECT_EQ(1, Rcode::BADVERS().getExtendedCode());
+ EXPECT_EQ(0xab, Rcode(0xabf).getExtendedCode());
+ EXPECT_EQ(0xff, Rcode(0xfff).getExtendedCode());
+}
+
+TEST(RcodeTest, constants) {
+ // We'll only test arbitrarily chosen subsets of the codes.
+ // This class is quite simple, so it should be suffice.
+
+ EXPECT_EQ(Rcode::NOERROR_CODE, Rcode(0).getCode());
+ EXPECT_EQ(Rcode::FORMERR_CODE, Rcode(1).getCode());
+ EXPECT_EQ(Rcode::NOTIMP_CODE, Rcode(4).getCode());
+ EXPECT_EQ(Rcode::REFUSED_CODE, Rcode(5).getCode());
+ EXPECT_EQ(Rcode::RESERVED15_CODE, Rcode(15).getCode());
+ EXPECT_EQ(Rcode::BADVERS_CODE, Rcode(16).getCode());
+
+ EXPECT_EQ(Rcode::NOERROR_CODE, Rcode::NOERROR().getCode());
+ EXPECT_EQ(Rcode::FORMERR_CODE, Rcode::FORMERR().getCode());
+ EXPECT_EQ(Rcode::NOTIMP_CODE, Rcode::NOTIMP().getCode());
+ EXPECT_EQ(Rcode::REFUSED_CODE, Rcode::REFUSED().getCode());
+ EXPECT_EQ(Rcode::RESERVED15_CODE, Rcode::RESERVED15().getCode());
+ EXPECT_EQ(Rcode::BADVERS_CODE, Rcode::BADVERS().getCode());
+}
+
+TEST(RcodeTest, equal) {
+ EXPECT_TRUE(Rcode::NOERROR() == Rcode(Rcode::NOERROR_CODE));
+ EXPECT_TRUE(Rcode::NOERROR().equals(Rcode(Rcode::NOERROR_CODE)));
+ EXPECT_TRUE(Rcode::FORMERR() == Rcode(Rcode::FORMERR_CODE));
+ EXPECT_TRUE(Rcode::FORMERR().equals(Rcode(Rcode::FORMERR_CODE)));
+ EXPECT_TRUE(Rcode::NOTIMP() == Rcode(Rcode::NOTIMP_CODE));
+ EXPECT_TRUE(Rcode::NOTIMP().equals(Rcode(Rcode::NOTIMP_CODE)));
+ EXPECT_TRUE(Rcode::REFUSED() == Rcode(Rcode::REFUSED_CODE));
+ EXPECT_TRUE(Rcode::REFUSED().equals(Rcode(Rcode::REFUSED_CODE)));
+ EXPECT_TRUE(Rcode::RESERVED15() == Rcode(Rcode::RESERVED15()));
+ EXPECT_TRUE(Rcode::RESERVED15().equals(Rcode(Rcode::RESERVED15())));
+ EXPECT_TRUE(Rcode::BADVERS() == Rcode(Rcode::BADVERS_CODE));
+ EXPECT_TRUE(Rcode::BADVERS().equals(Rcode(Rcode::BADVERS_CODE)));
+}
+
+TEST(RcodeTest, nequal) {
+ EXPECT_TRUE(Rcode::NOERROR() != Rcode::FORMERR());
+ EXPECT_TRUE(Rcode::NOERROR().nequals(Rcode::FORMERR()));
+ EXPECT_TRUE(Rcode::NOTIMP() != Rcode(1));
+ EXPECT_TRUE(Rcode::NOTIMP().nequals(Rcode(1)));
+ EXPECT_TRUE(Rcode(10) != Rcode(11));
+ EXPECT_TRUE(Rcode(10).nequals(Rcode(11)));
+}
+
+TEST(RcodeTest, toText) {
+ vector<const char*> expects;
+ expects.resize(Rcode::BADVERS_CODE + 1);
+ expects[Rcode::NOERROR_CODE] = "NOERROR";
+ expects[Rcode::FORMERR_CODE] = "FORMERR";
+ expects[Rcode::SERVFAIL_CODE] = "SERVFAIL";
+ expects[Rcode::NXDOMAIN_CODE] = "NXDOMAIN";
+ expects[Rcode::NOTIMP_CODE] = "NOTIMP";
+ expects[Rcode::REFUSED_CODE] = "REFUSED";
+ expects[Rcode::YXDOMAIN_CODE] = "YXDOMAIN";
+ expects[Rcode::YXRRSET_CODE] = "YXRRSET";
+ expects[Rcode::NXRRSET_CODE] = "NXRRSET";
+ expects[Rcode::NOTAUTH_CODE] = "NOTAUTH";
+ expects[Rcode::NOTZONE_CODE] = "NOTZONE";
+ expects[Rcode::RESERVED11_CODE] = "RESERVED11";
+ expects[Rcode::RESERVED12_CODE] = "RESERVED12";
+ expects[Rcode::RESERVED13_CODE] = "RESERVED13";
+ expects[Rcode::RESERVED14_CODE] = "RESERVED14";
+ expects[Rcode::RESERVED15_CODE] = "RESERVED15";
+ expects[Rcode::BADVERS_CODE] = "BADVERS";
+
+ for (unsigned int i = 0; i <= Rcode::BADVERS_CODE; ++i) {
+ EXPECT_EQ(expects.at(i), Rcode(i).toText());
+ }
+
+ // Non well-known Rcodes
+ EXPECT_EQ("17", Rcode(Rcode::BADVERS().getCode() + 1).toText());
+ EXPECT_EQ("4095", Rcode(Rcode(0xfff)).toText());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(RcodeTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << Rcode::SERVFAIL();
+ EXPECT_EQ(Rcode::SERVFAIL().toText(), oss.str());
+}
+}
diff --git a/src/lib/dns/tests/rdata_char_string_data_unittest.cc b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
new file mode 100644
index 0000000..22cf1a4
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/char_string.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharStringData;
+using isc::dns::rdata::generic::detail::stringToCharStringData;
+using isc::dns::rdata::generic::detail::charStringDataToString;
+using isc::dns::rdata::generic::detail::compareCharStringDatas;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringDataTest : public ::testing::Test {
+protected:
+ CharStringDataTest() :
+ // char-string representation for test data using two types of escape
+ // ('r' = 114)
+ test_str("Test\\ St\\114ing")
+ {
+ str_region.beg = &test_str[0];
+ str_region.len = test_str.size();
+ }
+ CharStringData chstr; // place holder
+ const std::string test_str;
+ MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+ MasterToken::StringRegion region;
+ region.beg = &str[0]; // note std ensures this works even if str is empty
+ region.len = str.size();
+ return (region);
+}
+
+TEST_F(CharStringDataTest, normalConversion) {
+ uint8_t tmp[3]; // placeholder for expected sequence
+
+ stringToCharStringData(str_region, chstr);
+ matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+ // Empty string
+ chstr.clear();
+ stringToCharStringData(createStringRegion(""), chstr);
+ EXPECT_TRUE(chstr.empty());
+
+ // Possible largest char string
+ chstr.clear();
+ std::string long_str(255, 'x');
+ stringToCharStringData(createStringRegion(long_str), chstr);
+ std::vector<uint8_t> expected;
+ expected.insert(expected.end(), long_str.begin(), long_str.end());
+ matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+ // Escaped '\'
+ chstr.clear();
+ tmp[0] = '\\';
+ stringToCharStringData(createStringRegion("\\\\"), chstr);
+ matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+ // Boundary values for \DDD
+ chstr.clear();
+ tmp[0] = 0;
+ stringToCharStringData(createStringRegion("\\000"), chstr);
+ matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+ chstr.clear();
+ stringToCharStringData(createStringRegion("\\255"), chstr);
+ tmp[0] = 255;
+ matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+ // Another digit follows DDD; it shouldn't cause confusion
+ chstr.clear();
+ stringToCharStringData(createStringRegion("\\2550"), chstr);
+ tmp[1] = '0';
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringDataTest, badConversion) {
+ // input string ending with (non escaped) '\'
+ chstr.clear();
+ EXPECT_THROW(stringToCharStringData(createStringRegion("foo\\"), chstr),
+ InvalidRdataText);
+}
+
+TEST_F(CharStringDataTest, badDDD) {
+ // Check various type of bad form of \DDD
+
+ // Not a number
+ EXPECT_THROW(stringToCharStringData(createStringRegion("\\1a2"), chstr),
+ InvalidRdataText);
+ EXPECT_THROW(stringToCharStringData(createStringRegion("\\12a"), chstr),
+ InvalidRdataText);
+
+ // Not in the range of uint8_t
+ EXPECT_THROW(stringToCharStringData(createStringRegion("\\256"), chstr),
+ InvalidRdataText);
+
+ // Short buffer
+ EXPECT_THROW(stringToCharStringData(createStringRegion("\\42"), chstr),
+ InvalidRdataText);
+}
+
+const struct TestData {
+ const char *data;
+ const char *expected;
+} conversion_data[] = {
+ {"Test\"Test", "Test\\\"Test"},
+ {"Test;Test", "Test\\;Test"},
+ {"Test\\Test", "Test\\\\Test"},
+ {"Test\x1fTest", "Test\\031Test"},
+ {"Test ~ Test", "Test ~ Test"},
+ {"Test\x7fTest", "Test\\127Test"},
+ {0, 0}
+};
+
+TEST_F(CharStringDataTest, charStringDataToString) {
+ for (const TestData* cur = conversion_data; cur->data; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ ASSERT_LT(length, sizeof(idata));
+ std::memcpy(idata, cur->data, length);
+ const CharStringData test_data(idata, idata + length);
+ EXPECT_EQ(cur->expected, charStringDataToString(test_data));
+ }
+}
+
+TEST_F(CharStringDataTest, compareCharStringData) {
+ CharStringData charstr;
+ CharStringData charstr2;
+ CharStringData charstr_small1;
+ CharStringData charstr_small2;
+ CharStringData charstr_large1;
+ CharStringData charstr_large2;
+ CharStringData charstr_empty;
+
+ stringToCharStringData(createStringRegion("test string"), charstr);
+ stringToCharStringData(createStringRegion("test string"), charstr2);
+ stringToCharStringData(createStringRegion("test strin"), charstr_small1);
+ stringToCharStringData(createStringRegion("test strina"), charstr_small2);
+ stringToCharStringData(createStringRegion("test stringa"), charstr_large1);
+ stringToCharStringData(createStringRegion("test strinz"), charstr_large2);
+
+ EXPECT_EQ(0, compareCharStringDatas(charstr, charstr2));
+ EXPECT_EQ(0, compareCharStringDatas(charstr2, charstr));
+ EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small1));
+ EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small2));
+ EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large1));
+ EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large2));
+ EXPECT_EQ(-1, compareCharStringDatas(charstr_small1, charstr));
+ EXPECT_EQ(-1, compareCharStringDatas(charstr_small2, charstr));
+ EXPECT_EQ(1, compareCharStringDatas(charstr_large1, charstr));
+ EXPECT_EQ(1, compareCharStringDatas(charstr_large2, charstr));
+
+ EXPECT_EQ(-1, compareCharStringDatas(charstr_empty, charstr));
+ EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_empty));
+ EXPECT_EQ(0, compareCharStringDatas(charstr_empty, charstr_empty));
+}
+
+} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
new file mode 100644
index 0000000..b0500d8
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/char_string.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharString;
+using isc::dns::rdata::generic::detail::bufferToCharString;
+using isc::dns::rdata::generic::detail::stringToCharString;
+using isc::dns::rdata::generic::detail::charStringToString;
+using isc::dns::rdata::generic::detail::compareCharStrings;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+ sizeof("Test String") - 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringTest : public ::testing::Test {
+protected:
+ CharStringTest() :
+ // char-string representation for test data using two types of escape
+ // ('r' = 114)
+ test_str("Test\\ St\\114ing")
+ {
+ str_region.beg = &test_str[0];
+ str_region.len = test_str.size();
+ }
+ CharString chstr; // place holder
+ const std::string test_str;
+ MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+ MasterToken::StringRegion region;
+ region.beg = &str[0]; // note std ensures this works even if str is empty
+ region.len = str.size();
+ return (region);
+}
+
+TEST_F(CharStringTest, normalConversion) {
+ uint8_t tmp[3]; // placeholder for expected sequence
+
+ stringToCharString(str_region, chstr);
+ matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+ // Empty string
+ chstr.clear();
+ stringToCharString(createStringRegion(""), chstr);
+ tmp[0] = 0;
+ matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+ // Possible largest char string
+ chstr.clear();
+ std::string long_str(255, 'x');
+ stringToCharString(createStringRegion(long_str), chstr);
+ std::vector<uint8_t> expected;
+ expected.push_back(255); // len of char string
+ expected.insert(expected.end(), long_str.begin(), long_str.end());
+ matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+ // Same data as the previous case, but the original string is longer than
+ // the max; this shouldn't be rejected
+ chstr.clear();
+ long_str.at(254) = '\\'; // replace the last 'x' with '\'
+ long_str.append("120"); // 'x' = 120
+ stringToCharString(createStringRegion(long_str), chstr);
+ matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+ // Escaped '\'
+ chstr.clear();
+ tmp[0] = 1;
+ tmp[1] = '\\';
+ stringToCharString(createStringRegion("\\\\"), chstr);
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+ // Boundary values for \DDD
+ chstr.clear();
+ tmp[0] = 1;
+ tmp[1] = 0;
+ stringToCharString(createStringRegion("\\000"), chstr);
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+ chstr.clear();
+ stringToCharString(createStringRegion("\\255"), chstr);
+ tmp[0] = 1;
+ tmp[1] = 255;
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+ // Another digit follows DDD; it shouldn't cause confusion
+ chstr.clear();
+ stringToCharString(createStringRegion("\\2550"), chstr);
+ tmp[0] = 2; // string len is now 2
+ tmp[2] = '0';
+ matchWireData(tmp, 3, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringTest, badConversion) {
+ // string cannot exceed 255 bytes
+ EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')),
+ chstr),
+ CharStringTooLong);
+
+ // input string ending with (non escaped) '\'
+ chstr.clear();
+ EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr),
+ InvalidRdataText);
+}
+
+TEST_F(CharStringTest, badDDD) {
+ // Check various type of bad form of \DDD
+
+ // Not a number
+ EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr),
+ InvalidRdataText);
+ EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr),
+ InvalidRdataText);
+
+ // Not in the range of uint8_t
+ EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr),
+ InvalidRdataText);
+
+ // Short buffer
+ EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr),
+ InvalidRdataText);
+}
+
+const struct TestData {
+ const char *data;
+ const char *expected;
+} conversion_data[] = {
+ {"Test\"Test", "Test\\\"Test"},
+ {"Test;Test", "Test\\;Test"},
+ {"Test\\Test", "Test\\\\Test"},
+ {"Test\x1fTest", "Test\\031Test"},
+ {"Test ~ Test", "Test ~ Test"},
+ {"Test\x7fTest", "Test\\127Test"},
+ {0, 0}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+ for (const TestData* cur = conversion_data; cur->data; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ // length (1 byte) + string (length bytes)
+ ASSERT_TRUE(sizeof(idata) > length);
+ idata[0] = static_cast<uint8_t>(length);
+ std::memcpy(idata + 1, cur->data, length);
+ const CharString test_data(idata, idata + length + 1);
+ EXPECT_EQ(cur->expected, charStringToString(test_data));
+ }
+}
+
+TEST_F(CharStringTest, bufferToCharString) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ size_t read = bufferToCharString(buf, chstr_size, chstr);
+
+ EXPECT_EQ(chstr_size, read);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+}
+
+TEST_F(CharStringTest, bufferToCharString_bad) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ // Set valid data in both so we can make sure the charstr is not
+ // modified
+ bufferToCharString(buf, chstr_size, chstr);
+ ASSERT_EQ("Test String", charStringToString(chstr));
+
+ // Should be at end of buffer now, so it should fail
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // reset and try to read with too low rdata_len
+ buf.setPosition(0);
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // set internal charstring len too high
+ const uint8_t test_charstr_err[] = {
+ sizeof("Test String") + 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+ };
+ buf = isc::util::InputBuffer(test_charstr_err, sizeof(test_charstr_err));
+ EXPECT_THROW(bufferToCharString(buf, chstr_size, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+}
+
+
+
+TEST_F(CharStringTest, compareCharString) {
+ CharString charstr;
+ CharString charstr2;
+ CharString charstr_small1;
+ CharString charstr_small2;
+ CharString charstr_large1;
+ CharString charstr_large2;
+ CharString charstr_empty;
+
+ stringToCharString(createStringRegion("test string"), charstr);
+ stringToCharString(createStringRegion("test string"), charstr2);
+ stringToCharString(createStringRegion("test strin"), charstr_small1);
+ stringToCharString(createStringRegion("test strina"), charstr_small2);
+ stringToCharString(createStringRegion("test stringa"), charstr_large1);
+ stringToCharString(createStringRegion("test strinz"), charstr_large2);
+
+ EXPECT_EQ(0, compareCharStrings(charstr, charstr2));
+ EXPECT_EQ(0, compareCharStrings(charstr2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small1));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small2));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large1));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large2));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small1, charstr));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large1, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large2, charstr));
+
+ EXPECT_EQ(-1, compareCharStrings(charstr_empty, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_empty));
+ EXPECT_EQ(0, compareCharStrings(charstr_empty, charstr_empty));
+}
+
+} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
new file mode 100644
index 0000000..5c12829
--- /dev/null
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -0,0 +1,165 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+#include <util/encode/encode.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+
+class Rdata_DHCID_Test : public RdataTest {
+protected:
+ Rdata_DHCID_Test() :
+ dhcid_txt("0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="),
+ rdata_dhcid(dhcid_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<in::DHCID, isc::Exception, isc::Exception>(
+ rdata_str, rdata_dhcid, false, false);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<in::DHCID, BadValue, BadValue>(
+ rdata_str, rdata_dhcid, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <in::DHCID, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_dhcid, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <in::DHCID, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_dhcid, true, false);
+ }
+
+ const string dhcid_txt;
+ const in::DHCID rdata_dhcid;
+};
+
+TEST_F(Rdata_DHCID_Test, fromText) {
+ EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
+
+ // Space in digest data is OK
+ checkFromText_None(
+ "0LIg0LvQtdGB0YMg 0YDQvtC00LjQu9Cw 0YHRjCDRkdC70L7R h9C60LA=");
+
+ // Multi-line digest data is OK, if enclosed in parentheses
+ checkFromText_None(
+ "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA= )");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="
+ " ; comment\n"
+ "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=");
+}
+
+TEST_F(Rdata_DHCID_Test, badText) {
+ // missing digest data
+ checkFromText_LexerError("");
+
+ // invalid base64
+ checkFromText_BadValue("EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
+ // unterminated multi-line base64
+ checkFromText_LexerError(
+ "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA=");
+}
+
+TEST_F(Rdata_DHCID_Test, copy) {
+ const in::DHCID rdata_dhcid2(rdata_dhcid);
+ EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid2));
+}
+
+TEST_F(Rdata_DHCID_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_dhcid.compare(
+ *rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"),
+ "rdata_dhcid_fromWire")));
+
+ InputBuffer buffer(0, 0);
+ EXPECT_THROW(in::DHCID(buffer, 0), InvalidRdataLength);
+
+ // TBD: more tests
+}
+
+TEST_F(Rdata_DHCID_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_dhcid.compare(
+ *test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
+ dhcid_txt)));
+}
+
+TEST_F(Rdata_DHCID_Test, toWireRenderer) {
+ rdata_dhcid.toWire(renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_DHCID_Test, toWireBuffer) {
+ rdata_dhcid.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
+ matchWireData(&data[0], data.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_DHCID_Test, toText) {
+ EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
+}
+
+TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
+ const string dhcid_txt1(encodeBase64(rdata_dhcid.getDigest()));
+
+ EXPECT_EQ(dhcid_txt, dhcid_txt1);
+}
+
+TEST_F(Rdata_DHCID_Test, compare) {
+ // trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
+ EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid));
+
+ in::DHCID rdata_dhcid1("0YLQvtC/0L7Qu9GPINC00LLQsCDRgNGD0LHQu9GP");
+ in::DHCID rdata_dhcid2("0YLQvtC/0L7Qu9GPINGC0YDQuCDRgNGD0LHQu9GP");
+ in::DHCID rdata_dhcid3("0YLQvtC/0L7Qu9GPINGH0LXRgtGL0YDQtSDRgNGD0LHQu9GP");
+
+ EXPECT_LT(rdata_dhcid1.compare(rdata_dhcid2), 0);
+ EXPECT_GT(rdata_dhcid2.compare(rdata_dhcid1), 0);
+
+ EXPECT_LT(rdata_dhcid2.compare(rdata_dhcid3), 0);
+ EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0);
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc
new file mode 100644
index 0000000..bc92c1c
--- /dev/null
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -0,0 +1,157 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/rdataclass.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/buffer.h>
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_IN_A_Test : public RdataTest {
+protected:
+ Rdata_IN_A_Test() : rdata_in_a("192.0.2.1") {}
+
+ void checkFromTextIN_A(const std::string& rdata_txt,
+ bool throw_str_version = true,
+ bool throw_lexer_version = true) {
+ checkFromText<in::A, InvalidRdataText, InvalidRdataText>(
+ rdata_txt, rdata_in_a, throw_str_version, throw_lexer_version);
+ }
+
+ const in::A rdata_in_a;
+};
+
+const uint8_t wiredata_in_a[] = { 192, 0, 2, 1 };
+
+TEST_F(Rdata_IN_A_Test, createFromText) {
+ // Normal case: no exception for either case, so the exception type
+ // doesn't matter.
+ checkFromText<in::A, isc::Exception, isc::Exception>("192.0.2.1",
+ rdata_in_a, false,
+ false);
+
+ // should reject an abbreviated form of IPv4 address
+ checkFromTextIN_A("10.1");
+ // or an IPv6 address
+ checkFromTextIN_A("2001:db8::1234");
+ // or any meaningless text as an IP address
+ checkFromTextIN_A("xxx");
+
+ // NetBSD's inet_pton accepts trailing space after an IPv4 address, which
+ // would confuse some of the tests below. We check the case differently
+ // in these cases depending on the strictness of inet_pton (most
+ // implementations seem to be stricter).
+ uint8_t v4addr_buf[4];
+ const bool reject_extra_space =
+ inet_pton(AF_INET, "192.0.2.1 ", v4addr_buf) == 0;
+
+ // trailing white space: only string version throws
+ checkFromTextIN_A("192.0.2.1 ", reject_extra_space, false);
+ // same for beginning white space.
+ checkFromTextIN_A(" 192.0.2.1", true, false);
+ // same for trailing non-space garbage (note that lexer version still
+ // ignore it; it's expected to be detected at a higher layer).
+ checkFromTextIN_A("192.0.2.1 xxx", reject_extra_space, false);
+
+ // nul character after a valid textual representation.
+ string nul_after_addr = "192.0.2.1";
+ nul_after_addr.push_back(0);
+ checkFromTextIN_A(nul_after_addr, true, true);
+
+ // a valid address surrounded by parentheses; only okay with lexer
+ checkFromTextIN_A("(192.0.2.1)", true, false);
+
+ // input that would cause lexer-specific error; it's bad text as an
+ // address so should result in the string version, too.
+ checkFromText<in::A, InvalidRdataText, MasterLexer::LexerError>(
+ ")192.0.2.1", rdata_in_a);
+}
+
+TEST_F(Rdata_IN_A_Test, createFromWire) {
+ // Valid data
+ EXPECT_EQ(0, rdata_in_a.compare(
+ *rdataFactoryFromFile(RRType::A(), RRClass::IN(),
+ "rdata_in_a_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::A(), RRClass::IN(),
+ "rdata_in_a_fromWire", 6),
+ DNSMessageFORMERR);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType::A(), RRClass::IN(),
+ "rdata_in_a_fromWire", 12),
+ DNSMessageFORMERR);
+ // buffer too short.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::A(), RRClass::IN(),
+ "rdata_in_a_fromWire", 19),
+ DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_IN_A_Test, toWireBuffer) {
+ rdata_in_a.toWire(obuffer);
+ matchWireData(wiredata_in_a, sizeof (wiredata_in_a),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_IN_A_Test, toWireRenderer) {
+ rdata_in_a.toWire(renderer);
+ matchWireData(wiredata_in_a, sizeof (wiredata_in_a),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_IN_A_Test, toText) {
+ EXPECT_EQ("192.0.2.1", rdata_in_a.toText());
+
+ // this shouldn't make the code crash
+ const string longaddr("255.255.255.255");
+ EXPECT_EQ(longaddr, in::A(longaddr).toText());
+}
+
+TEST_F(Rdata_IN_A_Test, compare) {
+ const in::A small1("1.1.1.1");
+ const in::A small2("1.2.3.4");
+ const in::A large1("255.255.255.255");
+ const in::A large2("4.3.2.1");
+
+ // trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
+ EXPECT_EQ(0, small1.compare(small1));
+
+ // confirm these are compared as unsigned values
+ EXPECT_GT(0, small1.compare(large1));
+ EXPECT_LT(0, large1.compare(small1));
+
+ // confirm these are compared in network byte order
+ EXPECT_GT(0, small2.compare(large2));
+ EXPECT_LT(0, large2.compare(small2));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_in_a.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
new file mode 100644
index 0000000..c75e829
--- /dev/null
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/buffer.h>
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_IN_AAAA_Test : public RdataTest {
+protected:
+ Rdata_IN_AAAA_Test() : rdata_in_aaaa("2001:db8::1234") {}
+
+ // Common check to see the result of in::A Rdata construction either from
+ // std::string or with MasterLexer object. If it's expected to succeed
+ // the result should be identical to the commonly used test data
+ // (rdata_in_a); otherwise it should result in the exception specified as
+ // the template parameter.
+ void checkFromTextIN_AAAA(const string& in_aaaa_txt,
+ bool throw_str_version = true,
+ bool throw_lexer_version = true)
+ {
+ checkFromText<in::AAAA, InvalidRdataText, InvalidRdataText>(
+ in_aaaa_txt, rdata_in_aaaa, throw_str_version,
+ throw_lexer_version);
+ }
+
+ const in::AAAA rdata_in_aaaa;
+};
+
+const uint8_t wiredata_in_aaaa[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x34 };
+
+TEST_F(Rdata_IN_AAAA_Test, createFromText) {
+ // Normal case: no exception for either case, so the exception type
+ // doesn't matter.
+ checkFromText<in::AAAA, isc::Exception, isc::Exception>(
+ "2001:db8::1234", rdata_in_aaaa, false, false);
+
+ // should reject an IP4 address.
+ checkFromTextIN_AAAA("192.0.2.1");
+ // or any meaningless text as an IPv6 address
+ checkFromTextIN_AAAA("xxx");
+
+ // trailing white space: only string version throws
+ checkFromTextIN_AAAA("2001:db8::1234 ", true, false);
+ // same for beginning white space.
+ checkFromTextIN_AAAA(" 2001:db8::1234", true, false);
+ // same for trailing non-space garbage (note that lexer version still
+ // ignore it; it's expected to be detected at a higher layer).
+ checkFromTextIN_AAAA("2001:db8::1234 xxx", true, false);
+
+ // nul character after a valid textual representation.
+ string nul_after_addr = "2001:db8::1234";
+ nul_after_addr.push_back(0);
+ checkFromTextIN_AAAA(nul_after_addr, true, true);
+
+ // a valid address surrounded by parentheses; only okay with lexer
+ checkFromTextIN_AAAA("(2001:db8::1234)", true, false);
+
+ // input that would cause lexer-specific error; it's bad text as an
+ // address so should result in the string version, too.
+ checkFromText<in::AAAA, InvalidRdataText, MasterLexer::LexerError>(
+ ")2001:db8::1234", rdata_in_aaaa);
+}
+
+TEST_F(Rdata_IN_AAAA_Test, createFromWire) {
+ // Valid data
+ EXPECT_EQ(0, rdata_in_aaaa.compare(
+ *rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(),
+ "rdata_in_aaaa_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(),
+ "rdata_in_aaaa_fromWire", 18),
+ DNSMessageFORMERR);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(),
+ "rdata_in_aaaa_fromWire", 36),
+ DNSMessageFORMERR);
+ // buffer too short.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(),
+ "rdata_in_aaaa_fromWire", 55),
+ 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);
+ matchWireData(wiredata_in_aaaa, sizeof (wiredata_in_aaaa),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_IN_AAAA_Test, toWireRenderer) {
+ rdata_in_aaaa.toWire(renderer);
+ matchWireData(wiredata_in_aaaa, sizeof (wiredata_in_aaaa),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_IN_AAAA_Test, toText) {
+ EXPECT_EQ("2001:db8::1234", rdata_in_aaaa.toText());
+}
+
+TEST_F(Rdata_IN_AAAA_Test, compare) {
+ in::AAAA small1("::1");
+ in::AAAA small2("1:2:3:4:5:6:7:8");
+ in::AAAA large1("ffff::");
+ in::AAAA large2("8:7:6:5:4:3:2:1");
+
+ // trivial case: self equivalence
+ // cppcheck-suppress uselessCallsCompare
+ EXPECT_EQ(0, small1.compare(small1));
+
+ // confirm these are compared as unsigned values
+ EXPECT_GT(0, small1.compare(large1));
+ EXPECT_LT(0, large1.compare(small1));
+
+ // confirm these are compared in network byte order
+ EXPECT_GT(0, small2.compare(large2));
+ EXPECT_LT(0, large2.compare(small2));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_in_aaaa.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc
new file mode 100644
index 0000000..cb7b160
--- /dev/null
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+using namespace std;
+
+namespace {
+class Rdata_NS_Test : public RdataTest {
+public:
+ Rdata_NS_Test() :
+ rdata_ns("ns.example.com."),
+ rdata_ns2("ns2.example.com.") {
+ }
+
+ const generic::NS rdata_ns;
+ const generic::NS rdata_ns2;
+};
+
+const uint8_t wiredata_ns[] = {
+ 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00 };
+const uint8_t wiredata_ns2[] = {
+ // first name: ns.example.com.
+ 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // second name: ns2.example.com. all labels except the first should be
+ // compressed.
+ 0x03, 0x6e, 0x73, 0x32, 0xc0, 0x03 };
+
+TEST_F(Rdata_NS_Test, createFromText) {
+ EXPECT_EQ(0, rdata_ns.compare(generic::NS("ns.example.com.")));
+ // explicitly add a trailing dot. should be the same RDATA.
+ EXPECT_EQ(0, rdata_ns.compare(generic::NS("ns.example.com.")));
+ // should be case sensitive.
+ EXPECT_EQ(0, rdata_ns.compare(generic::NS("NS.EXAMPLE.COM.")));
+ // RDATA of a class-independent type should be recognized for any
+ // "unknown" class.
+ EXPECT_EQ(0, rdata_ns.compare(*createRdata(RRType("NS"), RRClass(65000),
+ "ns.example.com.")));
+}
+
+TEST_F(Rdata_NS_Test, badText) {
+ // Extra input at end of line
+ EXPECT_THROW(generic::NS("ns.example.com. extra."), InvalidRdataText);
+}
+
+TEST_F(Rdata_NS_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_ns.compare(
+ *rdataFactoryFromFile(RRType("NS"), RRClass("IN"),
+ "rdata_ns_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType("NS"), RRClass("IN"),
+ "rdata_ns_fromWire", 18),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType("NS"), RRClass("IN"),
+ "rdata_ns_fromWire", 36),
+ InvalidRdataLength);
+ // incomplete name. the error should be detected in the name constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType("NS"), RRClass("IN"),
+ "rdata_ns_fromWire", 71),
+ DNSMessageFORMERR);
+
+ EXPECT_EQ(0, generic::NS("ns2.example.com.").compare(
+ *rdataFactoryFromFile(RRType("NS"), RRClass("IN"),
+ "rdata_ns_fromWire", 55)));
+ EXPECT_THROW(*rdataFactoryFromFile(RRType("NS"), RRClass("IN"),
+ "rdata_ns_fromWire", 63),
+ InvalidRdataLength);
+}
+
+TEST_F(Rdata_NS_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_ns.compare(
+ *test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+ "ns.example.com.")));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, generic::NS("ns8.example.org.").compare(
+ *test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+ "ns8")));
+
+ // Exceptions cause null to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+ ""));
+
+ // Extra input at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+ "ns.example.com. extra."));
+}
+
+TEST_F(Rdata_NS_Test, toWireBuffer) {
+ rdata_ns.toWire(obuffer);
+ matchWireData(wiredata_ns, sizeof(wiredata_ns),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_NS_Test, toWireRenderer) {
+ rdata_ns.toWire(renderer);
+ matchWireData(wiredata_ns, sizeof(wiredata_ns),
+ renderer.getData(), renderer.getLength());
+
+ rdata_ns2.toWire(renderer);
+ matchWireData(wiredata_ns2, sizeof(wiredata_ns2),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_NS_Test, toText) {
+ EXPECT_EQ("ns.example.com.", rdata_ns.toText());
+}
+
+TEST_F(Rdata_NS_Test, compare) {
+ generic::NS small("a.example.");
+ generic::NS large("example.");
+ EXPECT_TRUE(Name("a.example") > Name("example"));
+ EXPECT_GT(0, small.compare(large));
+}
+
+TEST_F(Rdata_NS_Test, getNSName) {
+ EXPECT_EQ(Name("ns.example.com."), rdata_ns.getNSName());
+}
+}
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
new file mode 100644
index 0000000..d24a565
--- /dev/null
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_OPT_Test : public RdataTest {
+ // there's nothing to specialize
+};
+
+const uint8_t rdata_opt_wiredata[] = {
+ // Option code
+ 0x00, 0x2a,
+ // Option length
+ 0x00, 0x03,
+ // Option data
+ 0x00, 0x01, 0x02
+};
+
+TEST_F(Rdata_OPT_Test, createFromText) {
+ // OPT RR cannot be created from text.
+ EXPECT_THROW(generic::OPT("this does not matter"), InvalidRdataText);
+}
+
+TEST_F(Rdata_OPT_Test, createFromWire) {
+ // Valid cases: in the simple implementation with no supported options,
+ // we can only check these don't throw.
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass("CLASS4096"),
+ "rdata_opt_fromWire1"));
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::CH(),
+ "rdata_opt_fromWire1", 2));
+
+ // Short RDLEN. This throws InvalidRdataLength even if subsequent
+ // pseudo RRs cause RDLEN size to be exhausted.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+ "rdata_opt_fromWire2"),
+ InvalidRdataLength);
+ EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+ "rdata_opt_fromWire3"),
+ InvalidRdataLength);
+ // Option lengths can add up and overflow RDLEN. Unlikely when
+ // parsed from wire data, but we'll check for it anyway.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+ "rdata_opt_fromWire4"),
+ InvalidRdataText);
+
+ // short buffer case.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(),
+ "rdata_opt_fromWire1", 11),
+ isc::OutOfRange);
+}
+
+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) {
+ const generic::OPT rdata_opt =
+ dynamic_cast<const generic::OPT&>
+ (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+ "rdata_opt_fromWire1", 2));
+
+ obuffer.clear();
+ rdata_opt.toWire(obuffer);
+
+ matchWireData(rdata_opt_wiredata, sizeof(rdata_opt_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_OPT_Test, toWireRenderer) {
+ const generic::OPT rdata_opt =
+ dynamic_cast<const generic::OPT&>
+ (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+ "rdata_opt_fromWire1", 2));
+
+ renderer.clear();
+ rdata_opt.toWire(renderer);
+
+ matchWireData(rdata_opt_wiredata, sizeof(rdata_opt_wiredata),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_OPT_Test, toText) {
+ // empty OPT
+ const generic::OPT rdata_opt;
+
+ EXPECT_THROW(rdata_opt.toText(),
+ isc::InvalidOperation);
+}
+
+TEST_F(Rdata_OPT_Test, compare) {
+ // empty OPT
+ const generic::OPT rdata_opt;
+
+ EXPECT_THROW(rdata_opt.compare(
+ *rdataFactoryFromFile(RRType::OPT(), RRClass::CH(),
+ "rdata_opt_fromWire1", 2)),
+ isc::InvalidOperation);
+
+ // comparison attempt between incompatible RR types also results in
+ // isc::InvalidOperation.
+ EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch),
+ isc::InvalidOperation);
+}
+
+TEST_F(Rdata_OPT_Test, appendPseudoRR) {
+ generic::OPT rdata_opt;
+
+ // Append empty option data
+ rdata_opt.appendPseudoRR(0x0042, 0, 0);
+
+ // Append simple option data
+ const uint8_t option_data[] = {'H', 'e', 'l', 'l', 'o'};
+ rdata_opt.appendPseudoRR(0x0043, option_data, sizeof(option_data));
+
+ // Duplicate option codes are okay.
+ rdata_opt.appendPseudoRR(0x0042, option_data, sizeof(option_data));
+
+ // When option length may overflow RDLEN, append should throw.
+ const std::vector<uint8_t> buffer((1 << 16) - 1);
+ EXPECT_THROW(rdata_opt.appendPseudoRR(0x0044, &buffer[0], buffer.size()),
+ isc::InvalidParameter);
+
+ const uint8_t rdata_opt_wiredata2[] = {
+ // OPTION #1
+ // ` Option code
+ 0x00, 0x42,
+ // ` Option length
+ 0x00, 0x00,
+
+ // OPTION #2
+ // ` Option code
+ 0x00, 0x43,
+ // ` Option length
+ 0x00, 0x05,
+ // ` Option data
+ 'H', 'e', 'l', 'l', 'o',
+
+ // OPTION #3
+ // ` Option code
+ 0x00, 0x42,
+ // ` Option length
+ 0x00, 0x05,
+ // ` Option data
+ 'H', 'e', 'l', 'l', 'o'
+ };
+
+ obuffer.clear();
+ rdata_opt.toWire(obuffer);
+
+ matchWireData(rdata_opt_wiredata2, sizeof(rdata_opt_wiredata2),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_OPT_Test, getPseudoRRs) {
+ const generic::OPT rdf =
+ dynamic_cast<const generic::OPT&>
+ (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"),
+ "rdata_opt_fromWire1", 2));
+
+ const std::vector<generic::OPT::PseudoRR>& rrs = rdf.getPseudoRRs();
+ ASSERT_FALSE(rrs.empty());
+ EXPECT_EQ(1, rrs.size());
+ EXPECT_EQ(0x2a, rrs.at(0).getCode());
+ EXPECT_EQ(3, rrs.at(0).getLength());
+
+ const uint8_t expected_data[] = {0x00, 0x01, 0x02};
+ const uint8_t* actual_data = rrs.at(0).getData();
+ EXPECT_EQ(0, std::memcmp(expected_data, actual_data,
+ sizeof(expected_data)));
+}
+}
diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc
new file mode 100644
index 0000000..b1db20b
--- /dev/null
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+using namespace std;
+//
+// This test currently simply copies the NS RDATA tests.
+//
+
+namespace {
+class Rdata_PTR_Test : public RdataTest {
+public:
+ Rdata_PTR_Test() :
+ rdata_ptr("ns.example.com."),
+ rdata_ptr2("ns2.example.com.") {
+ }
+
+ const generic::PTR rdata_ptr;
+ const generic::PTR rdata_ptr2;
+};
+
+const uint8_t wiredata_ptr[] = {
+ 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00 };
+const uint8_t wiredata_ptr2[] = {
+ // first name: ns.example.com.
+ 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // second name: ns2.example.com. all labels except the first should be
+ // compressed.
+ 0x03, 0x6e, 0x73, 0x32, 0xc0, 0x03 };
+
+TEST_F(Rdata_PTR_Test, createFromText) {
+ EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("ns.example.com.")));
+ // explicitly add a trailing dot. should be the same RDATA.
+ EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("ns.example.com.")));
+ // should be case sensitive.
+ EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("NS.EXAMPLE.COM.")));
+ // RDATA of a class-independent type should be recognized for any
+ // "unknown" class.
+ EXPECT_EQ(0, rdata_ptr.compare(*createRdata(RRType("PTR"), RRClass(65000),
+ "ns.example.com.")));
+}
+
+TEST_F(Rdata_PTR_Test, badText) {
+ // Extra text at end of line
+ EXPECT_THROW(generic::PTR("foo.example.com. extra."), InvalidRdataText);
+}
+
+TEST_F(Rdata_PTR_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_ptr.compare(
+ *rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+ "rdata_ns_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+ "rdata_ns_fromWire", 18),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+ "rdata_ns_fromWire", 36),
+ InvalidRdataLength);
+ // incomplete name. the error should be detected in the name constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+ "rdata_ns_fromWire", 71),
+ DNSMessageFORMERR);
+
+ EXPECT_EQ(0, generic::PTR("ns2.example.com.").compare(
+ *rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+ "rdata_ns_fromWire", 55)));
+ EXPECT_THROW(*rdataFactoryFromFile(RRType("PTR"), RRClass("IN"),
+ "rdata_ns_fromWire", 63),
+ InvalidRdataLength);
+}
+
+TEST_F(Rdata_PTR_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_ptr.compare(
+ *test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(),
+ "ns.example.com.")));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, generic::PTR("foo0.example.org.").compare(
+ *test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(),
+ "foo0")));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(),
+ "foo.example.com. extra."));
+}
+
+TEST_F(Rdata_PTR_Test, toWireBuffer) {
+ rdata_ptr.toWire(obuffer);
+ matchWireData(wiredata_ptr, sizeof(wiredata_ptr),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_PTR_Test, toWireRenderer) {
+ rdata_ptr.toWire(renderer);
+ matchWireData(wiredata_ptr, sizeof(wiredata_ptr),
+ renderer.getData(), renderer.getLength());
+
+ rdata_ptr2.toWire(renderer);
+ matchWireData(wiredata_ptr2, sizeof(wiredata_ptr2),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_PTR_Test, toText) {
+ EXPECT_EQ("ns.example.com.", rdata_ptr.toText());
+}
+
+TEST_F(Rdata_PTR_Test, compare) {
+ generic::PTR small("a.example.");
+ generic::PTR large("example.");
+ EXPECT_TRUE(Name("a.example") > Name("example"));
+ EXPECT_GT(0, small.compare(large));
+}
+
+TEST_F(Rdata_PTR_Test, getPTRName) {
+ EXPECT_EQ(Name("ns.example.com"), rdata_ptr.getPTRName());
+}
+}
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
new file mode 100644
index 0000000..e0873e1
--- /dev/null
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -0,0 +1,369 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/time_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+#include <dns/tests/rdata_unittest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+
+const uint8_t wiredata_rrsig[] = {
+ // type covered = A
+ 0x00, 0x01,
+ // algorithm = 5
+ 0x05,
+ // labels = 4
+ 0x04,
+ // original TTL = 43200 (0x0000a8c0)
+ 0x00, 0x00, 0xa8, 0xc0,
+ // signature expiration = 1266961577 (0x4b844ca9)
+ 0x4b, 0x84, 0x4c, 0xa9,
+ // signature inception = 1266875177 (0x4b82fb29)
+ 0x4b, 0x82, 0xfb, 0x29,
+ // key tag = 8496 (0x2130)
+ 0x21, 0x30,
+ // signer's name (isc.org.)
+ // 3 i s c 3 o r g 0
+ 0x03, 0x69, 0x73, 0x63, 0x03, 0x6f, 0x72, 0x67, 0x00,
+ // signature data follows
+ 0x7a, 0xfc, 0x61, 0x94, 0x6c,
+ 0x75, 0xde, 0x6a, 0x4a, 0x2d, 0x59, 0x0a, 0xb2,
+ 0x3a, 0x46, 0xcf, 0x27, 0x12, 0xe6, 0xdc, 0x2d,
+ 0x22, 0x8c, 0x4e, 0x9a, 0x53, 0x75, 0xe3, 0x0f,
+ 0x6d, 0xe4, 0x08, 0x33, 0x18, 0x19, 0xb3, 0x76,
+ 0x21, 0x9d, 0x2c, 0x8a, 0xc5, 0x69, 0xba, 0xab,
+ 0xef, 0x66, 0x9f, 0xda, 0xb5, 0x2a, 0xf9, 0x40,
+ 0xc1, 0x28, 0xc5, 0x97, 0xba, 0x3c, 0x19, 0x4d,
+ 0x95, 0x13, 0xc2, 0xcd, 0xf6, 0xb1, 0x59, 0x5d,
+ 0x0c, 0xf9, 0x3f, 0x35, 0xbb, 0x9a, 0x70, 0x93,
+ 0x36, 0xe5, 0xf4, 0x17, 0x7e, 0xfe, 0x66, 0x3b,
+ 0x70, 0x1f, 0xed, 0x33, 0xa8, 0xa3, 0x0d, 0xc0,
+ 0x8c, 0xc6, 0x95, 0x1b, 0xd8, 0x9c, 0x8c, 0x25,
+ 0xb4, 0x57, 0x9e, 0x56, 0x71, 0x64, 0x14, 0x7f,
+ 0x8f, 0x6d, 0xfa, 0xc5, 0xca, 0x3f, 0x36, 0xe2,
+ 0xa4, 0xdf, 0x60, 0xfa, 0xcd, 0x59, 0x3e, 0x22,
+ 0x32, 0xa1, 0xf7
+};
+
+class Rdata_RRSIG_Test : public RdataTest {
+protected:
+ 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)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::RRSIG, isc::Exception, isc::Exception>(
+ rdata_str, rdata_rrsig, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_InvalidType(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidRRType, InvalidRRType>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_InvalidTime(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidTime, InvalidTime>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::RRSIG, BadValue, BadValue>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_rrsig, true, false);
+ }
+
+ const string rrsig_txt;
+ const generic::RRSIG rdata_rrsig;
+};
+
+TEST_F(Rdata_RRSIG_Test, fromText) {
+ EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
+ EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
+
+ // Missing signature is OK
+ EXPECT_NO_THROW(const generic::RRSIG sig(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org."));
+
+ // Space in signature data is OK
+ checkFromText_None(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+ // Multi-line signature data is OK, if enclosed in parentheses
+ checkFromText_None(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc= )");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc= ; comment\n"
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_missingFields) {
+ checkFromText_LexerError("A");
+ checkFromText_LexerError("A 5");
+ checkFromText_LexerError("A 5 4");
+ checkFromText_LexerError("A 5 4 43200");
+ checkFromText_LexerError("A 5 4 43200 20100223214617");
+ checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617");
+ checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617 "
+ "8496");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_coveredType) {
+ checkFromText_InvalidType("SPORK");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_algorithm) {
+ checkFromText_InvalidText(
+ "A 555 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A FIVE 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_labels) {
+ checkFromText_InvalidText(
+ "A 5 4444 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 FOUR 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_ttl) {
+ checkFromText_LexerError(
+ "A 5 4 999999999999 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 4 TTL "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+ // alternate form of TTL is not okay
+ checkFromText_LexerError(
+ "A 5 4 12H 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_expiration) {
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "201002232 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "EXPIRATION 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_inception) {
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "20100223214617 20100227 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "20100223214617 INCEPTION 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_keytag) {
+ checkFromText_InvalidText(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 999999 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 TAG isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_signer) {
+ checkFromText_MissingOrigin(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_signature) {
+ checkFromText_BadValue(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
+ // no space between the tag and signer
+ checkFromText_LexerError(
+ "A 5 4 43200 20100223214617 20100222214617 "
+ "8496isc.org. ofc=");
+
+ // unterminated multi-line base64
+ checkFromText_LexerError(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+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) {
+ rdata_rrsig.toWire(renderer);
+
+ matchWireData(wiredata_rrsig, sizeof(wiredata_rrsig),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_RRSIG_Test, toWireBuffer) {
+ rdata_rrsig.toWire(obuffer);
+
+ matchWireData(wiredata_rrsig, sizeof(wiredata_rrsig),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_RRSIG_Test, createFromWire) {
+ 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")));
+
+ // RDLEN is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RRSIG(), RRClass::IN(),
+ "rdata_rrsig_fromWire2.wire"),
+ InvalidRdataLength);
+}
+}
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
new file mode 100644
index 0000000..33b1b4b
--- /dev/null
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_SOA_Test : public RdataTest {
+protected:
+ Rdata_SOA_Test() :
+ rdata_soa(Name("ns.example.com"),
+ Name("root.example.com"),
+ 2010012601, 3600, 300, 3600000, 1200)
+ {}
+
+ template <typename ExForString, typename ExForLexer>
+ void checkFromTextSOA(const string& soa_txt, const Name* origin = 0,
+ bool throw_str_version = true,
+ bool throw_lexer_version = true)
+ {
+ checkFromText<generic::SOA, ExForString, ExForLexer>(
+ soa_txt, rdata_soa, throw_str_version, throw_lexer_version,
+ origin);
+ }
+
+ const generic::SOA rdata_soa;
+};
+
+TEST_F(Rdata_SOA_Test, createFromText) {
+ // Below we specify isc::Exception as a dummy value for the exception type
+ // in case it's not expected to throw an exception; the type isn't used
+ // in the check code.
+
+ // A simple case.
+ checkFromTextSOA<isc::Exception, isc::Exception>(
+ "ns.example.com. root.example.com. 2010012601 3600 300 3600000 1200",
+ 0, false, false);
+
+ // Beginning and trailing space are ignored.
+ checkFromTextSOA<isc::Exception, isc::Exception>(
+ " ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200 ", 0, false, false);
+
+ // using extended TTL-like form for some parameters.
+ checkFromTextSOA<isc::Exception, isc::Exception>(
+ "ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M",
+ 0, false, false);
+
+ // multi-line.
+ checkFromTextSOA<isc::Exception, isc::Exception>(
+ "ns.example.com. (root.example.com.\n"
+ "2010012601 1H 5M 1000H) 20M", 0, false, false);
+
+ // relative names for MNAME and RNAME with a separate origin (lexer
+ // version only)
+ const Name origin("example.com");
+ checkFromTextSOA<MissingNameOrigin, isc::Exception>(
+ "ns root 2010012601 1H 5M 1000H 20M", &origin, true, false);
+
+ // with the '@' notation with a separate origin (lexer version only;
+ // string version would throw)
+ const Name full_mname("ns.example.com");
+ checkFromTextSOA<MissingNameOrigin, isc::Exception>(
+ "@ root.example.com. 2010012601 1H 5M 1000H 20M", &full_mname, true,
+ false);
+
+ // bad MNAME/RNAMEs
+ checkFromTextSOA<EmptyLabel, EmptyLabel>(
+ "bad..example. . 2010012601 1H 5M 1000H 20M");
+ checkFromTextSOA<EmptyLabel, EmptyLabel>(
+ ". bad..example. 2010012601 1H 5M 1000H 20M");
+
+ // Names shouldn't be quoted.
+ checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
+ "\".\" . 0 0 0 0 0");
+ checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
+ ". \".\" 0 0 0 0 0");
+
+ // Missing MAME or RNAME: for the string version, the serial would be
+ // tried as RNAME and result in "not absolute". For the lexer version,
+ // it reaches the end-of-line, missing min TTL.
+ checkFromTextSOA<MissingNameOrigin, MasterLexer::LexerError>(
+ ". 2010012601 0 0 0 0", &Name::ROOT_NAME());
+
+ // bad serial. the string version converts lexer error to
+ // InvalidRdataText.
+ checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
+ ". . bad 0 0 0 0");
+
+ // bad serial; exceeding the uint32_t range (4294967296 = 2^32)
+ checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
+ ". . 4294967296 0 0 0 0");
+
+ // Bad format for other numeric parameters. These will be tried as a TTL,
+ // and result in an exception there.
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 bad 0 0 0");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 4294967296 0 0 0");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 bad 0 0");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 4294967296 0 0");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 bad 0");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 4294967296 0");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 0 bad");
+ checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 0 4294967296");
+
+ // No space between RNAME and serial. This case is the same as missing
+ // M/RNAME.
+ checkFromTextSOA<MissingNameOrigin, MasterLexer::LexerError>(
+ ". example.0 0 0 0 0", &Name::ROOT_NAME());
+
+ // Extra parameter. string version immediately detects the error.
+ // lexer version defers the check to the upper layer (we pass origin
+ // to skip the check with the string version).
+ checkFromTextSOA<InvalidRdataText, isc::Exception>(
+ "ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M "
+ "extra", &origin, true, false);
+
+ // Likewise. Redundant newline is also considered an error. The lexer
+ // version accepts trailing newline, but not the beginning one (where
+ // the lexer expects a string excluding newline and EOF).
+ checkFromTextSOA<InvalidRdataText, isc::Exception>(
+ "ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M\n",
+ 0, true, false);
+ checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
+ "\nns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M",
+ 0, true, true);
+}
+
+TEST_F(Rdata_SOA_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_soa.compare(
+ *rdataFactoryFromFile(RRType("SOA"), RRClass("IN"),
+ "rdata_soa_fromWire")));
+ // 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);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_soa_fromWire", data);
+ matchWireData(&data[2], data.size() - 2,
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2);
+}
+
+TEST_F(Rdata_SOA_Test, toWireBuffer) {
+ obuffer.skip(2);
+ rdata_soa.toWire(obuffer);
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_soa_toWireUncompressed.wire", data);
+ matchWireData(&data[2], data.size() - 2,
+ static_cast<const uint8_t *>(obuffer.getData()) + 2,
+ obuffer.getLength() - 2);
+}
+
+TEST_F(Rdata_SOA_Test, toText) {
+ EXPECT_EQ("ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200", rdata_soa.toText());
+}
+
+TEST_F(Rdata_SOA_Test, getSerial) {
+ EXPECT_EQ(2010012601, rdata_soa.getSerial().getValue());
+}
+
+TEST_F(Rdata_SOA_Test, getMinimum) {
+ EXPECT_EQ(1200, rdata_soa.getMinimum());
+
+ // Also check with a very large number (with the MSB being 1).
+ EXPECT_EQ(2154848336u, generic::SOA(Name("ns.example.com"),
+ Name("root.example.com"),
+ 0, 0, 0, 0, 0x80706050).getMinimum());
+}
+
+void
+compareCheck(const generic::SOA& small, const generic::SOA& large) {
+ EXPECT_GT(0, small.compare(large));
+ EXPECT_LT(0, large.compare(small));
+}
+
+TEST_F(Rdata_SOA_Test, compare) {
+ // Check simple equivalence
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200")));
+ // Check name comparison is case insensitive
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "NS.example.com. root.EXAMPLE.com. "
+ "2010012601 3600 300 3600000 1200")));
+
+ // Check names are compared in the RDATA comparison semantics (different
+ // from DNSSEC ordering for owner names)
+ compareCheck(generic::SOA("a.example. . 0 0 0 0 0"),
+ generic::SOA("example. . 0 0 0 0 0"));
+ compareCheck(generic::SOA(". a.example. 0 0 0 0 0"),
+ generic::SOA(". example. 0 0 0 0 0"));
+
+ // Compare other numeric fields: 1076895760 = 0x40302010,
+ // 270544960 = 0x10203040. These are chosen to make sure that machine
+ // endianness doesn't confuse the comparison results.
+ compareCheck(generic::SOA(". . 270544960 0 0 0 0"),
+ generic::SOA(". . 1076895760 0 0 0 0"));
+ compareCheck(generic::SOA(". . 0 270544960 0 0 0"),
+ generic::SOA(". . 0 1076895760 0 0 0"));
+ compareCheck(generic::SOA(". . 0 0 270544960 0 0"),
+ generic::SOA(". . 0 0 1076895760 0 0"));
+ compareCheck(generic::SOA(". . 0 0 0 270544960 0"),
+ generic::SOA(". . 0 0 0 1076895760 0"));
+ compareCheck(generic::SOA(". . 0 0 0 0 270544960"),
+ generic::SOA(". . 0 0 0 0 1076895760"));
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_tkey_unittest.cc b/src/lib/dns/tests/rdata_tkey_unittest.cc
new file mode 100644
index 0000000..31c64bc
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tkey_unittest.cc
@@ -0,0 +1,450 @@
+// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+
+#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/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/time_utils.h>
+#include <dns/tsigerror.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+
+class Rdata_TKEY_Test : public RdataTest {
+protected:
+ Rdata_TKEY_Test() :
+ // no Key or Other Data
+ valid_text1("gss-tsig. 20210501120000 20210501130000 GSS-API NOERROR 0 0"),
+ // Key but no Other Data
+ valid_text2("GSS-TSIG. 20210501120000 20210501130000 GSS-API BADSIG "
+ "12 FAKEFAKEFAKEFAKE 0"),
+ // Key and Other Data
+ valid_text3("gss.tsig. 20210501120000 20210501130000 GSS-API BADSIG "
+ "12 FAKEFAKEFAKEFAKE 6 FAKEFAKE"),
+ // Key and Other Data (with Error that doesn't expect Other Data)
+ valid_text4("gss.tsig. 20210501120000 20210501130000 3 BADSIG 12 "
+ "FAKEFAKEFAKEFAKE 6 FAKEFAKE"),
+ // numeric error code
+ valid_text5("GSS-TSIG. 20210501120000 20210501130000 GSS-API 2845 12 "
+ "FAKEFAKEFAKEFAKE 0"),
+ // GSS-API mode
+ valid_text6("gss-tsig. 20210501120000 20210501130000 GSS-API 0 12 "
+ "FAKEFAKEFAKEFAKE 0"),
+ rdata_tkey(valid_text1)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::TKEY, isc::Exception, isc::Exception>(
+ rdata_str, rdata_tkey, false, false);
+ }
+
+ void checkFromText_InvalidTime(const string& rdata_str) {
+ checkFromText<generic::TKEY, InvalidTime, InvalidTime>(
+ rdata_str, rdata_tkey, true, true);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::TKEY, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_tkey, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::TKEY, BadValue, BadValue>(
+ rdata_str, rdata_tkey, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::TKEY, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_tkey, true, true);
+ }
+
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<generic::TKEY, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_tkey, true, true);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::TKEY, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_tkey, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::TKEY, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_tkey, true, false);
+ }
+
+ template <typename Output>
+ void toWireCommonChecks(Output& output) const;
+
+ const string valid_text1;
+ const string valid_text2;
+ const string valid_text3;
+ const string valid_text4;
+ const string valid_text5;
+ const string valid_text6;
+ vector<uint8_t> expect_data;
+ const generic::TKEY rdata_tkey; // commonly used test RDATA
+};
+
+TEST_F(Rdata_TKEY_Test, fromText) {
+ // normal case. it also tests getter methods.
+ EXPECT_EQ(Name("gss-tsig"), rdata_tkey.getAlgorithm());
+ EXPECT_EQ(1619870400, rdata_tkey.getInception());
+ EXPECT_EQ("20210501120000", rdata_tkey.getInceptionDate());
+ EXPECT_EQ(1619874000, rdata_tkey.getExpire());
+ EXPECT_EQ("20210501130000", rdata_tkey.getExpireDate());
+ EXPECT_EQ(3, rdata_tkey.getMode());
+ EXPECT_EQ(0, rdata_tkey.getError());
+ EXPECT_EQ(0, rdata_tkey.getKeyLen());
+ EXPECT_FALSE(rdata_tkey.getKey());
+ EXPECT_EQ(0, rdata_tkey.getOtherLen());
+ EXPECT_FALSE(rdata_tkey.getOtherData());
+
+ generic::TKEY tkey2(valid_text2);
+ EXPECT_EQ(12, tkey2.getKeyLen());
+ EXPECT_EQ(TSIGError::BAD_SIG_CODE, tkey2.getError());
+
+ generic::TKEY tkey3(valid_text3);
+ EXPECT_EQ(6, tkey3.getOtherLen());
+
+ // The other data is unusual, but we don't reject it.
+ EXPECT_NO_THROW(generic::TKEY tkey4(valid_text4));
+
+ // numeric representation of TKEY error
+ generic::TKEY tkey5(valid_text5);
+ EXPECT_EQ(2845, tkey5.getError());
+
+ // symbolic representation of TKEY mode
+ generic::TKEY tkey6(valid_text6);
+ EXPECT_EQ(generic::TKEY::GSS_API_MODE, tkey6.getMode());
+
+ // not fully qualified algorithm name
+ generic::TKEY tkey1("gss-tsig 20210501120000 20210501130000 3 0 0 0");
+ EXPECT_EQ(0, tkey1.compare(rdata_tkey));
+
+ // multi-line rdata
+ checkFromText_None("gss-tsig. ( 20210501120000 20210501130000 GSS-API \n"
+ "NOERROR 0 0 )");
+};
+
+TEST_F(Rdata_TKEY_Test, badText) {
+ // too many fields
+ checkFromText_BadString(valid_text1 + " 0 0");
+ // not enough fields
+ checkFromText_LexerError("foo 20210501120000 20210501130000 0 BADKEY");
+ // bad domain name
+ checkFromText_TooLongLabel(
+ "0123456789012345678901234567890123456789012345678901234567890123"
+ " 20210501120000 20210501130000 0 0 0 0");
+ checkFromText_EmptyLabel("foo..bar 20210501120000 20210501130000 0 0 0 0");
+ // invalid inception (no digit)
+ checkFromText_InvalidTime("foo TIME 20210501130000 0 0 0 0");
+ // invalid inception (bad format)
+ checkFromText_InvalidTime("foo 0 20210501130000 0 0 0 0");
+ // invalid expire (no digit)
+ checkFromText_InvalidTime("foo 20210501120000 TIME 0 0 0 0");
+ // invalid expire (bad format)
+ checkFromText_InvalidTime("foo 20210501120000 0 0 0 0 0");
+ // Unknown mode
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 TEST 0 0 0");
+ // Numeric mode is is too large
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 65536 0 0 0");
+ // Numeric mode is negative
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 -1 0 0 0 0");
+ // Unknown error code
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 TEST 0 0");
+ // Numeric error code is too large
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 65536 0 0");
+ // Numeric error code is negative
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 -1 0 0");
+ // Key len is too large
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 65536 0");
+ // invalid Key len (negative)
+ checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 -1 0");
+ // invalid Key len (not a number)
+ checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 MACSIZE 0");
+ // Key len and Key mismatch
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 9 FAKE 0");
+ // Key is bad base64
+ checkFromText_BadValue("foo 20210501120000 20210501130000 0 0 3 FAK= 0");
+ // Other len is too large
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 0 65536 FAKE");
+ // Other len is negative
+ checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 0 -1 FAKE");
+ // invalid Other len
+ checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 0 LEN FAKE");
+ // Other len and data mismatch
+ checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 0 9 FAKE");
+}
+
+void
+fromWireCommonChecks(const generic::TKEY& tkey) {
+ EXPECT_EQ(Name("gss-tsig"), tkey.getAlgorithm());
+ EXPECT_EQ(1619870400, tkey.getInception());
+ EXPECT_EQ("20210501120000", tkey.getInceptionDate());
+ EXPECT_EQ(1619874000, tkey.getExpire());
+ EXPECT_EQ("20210501130000", tkey.getExpireDate());
+ EXPECT_EQ(3, tkey.getMode());
+ EXPECT_EQ(0, tkey.getError());
+
+ vector<uint8_t> expect_key(32, 'x');
+ matchWireData(&expect_key[0], expect_key.size(),
+ tkey.getKey(), tkey.getKeyLen());
+
+ EXPECT_EQ(0, tkey.getOtherLen());
+ EXPECT_FALSE(tkey.getOtherData());
+}
+
+TEST_F(Rdata_TKEY_Test, createFromWire) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire1.wire"));
+ fromWireCommonChecks(dynamic_cast<generic::TKEY&>(*rdata));
+}
+
+TEST_F(Rdata_TKEY_Test, createFromWireWithOtherData) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire2.wire"));
+ const generic::TKEY& tkey(dynamic_cast<generic::TKEY&>(*rdata));
+
+ vector<uint8_t> expect_key(32, 'x');
+ matchWireData(&expect_key[0], expect_key.size(),
+ tkey.getKey(), tkey.getKeyLen());
+
+ vector<uint8_t> expect_data = { 'a', 'b', 'c', 'd', '0', '1', '2', '3' };
+ matchWireData(&expect_data[0], expect_data.size(),
+ tkey.getOtherData(), tkey.getOtherLen());
+}
+
+TEST_F(Rdata_TKEY_Test, createFromWireWithoutKey) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire3.wire"));
+ const generic::TKEY& tkey(dynamic_cast<generic::TKEY&>(*rdata));
+ EXPECT_EQ(0, tkey.getKeyLen());
+ EXPECT_FALSE(tkey.getKey());
+
+ vector<uint8_t> expect_data = { 'a', 'b', 'c', 'd', '0', '1', '2', '3' };
+ matchWireData(&expect_data[0], expect_data.size(),
+ tkey.getOtherData(), tkey.getOtherLen());
+}
+
+TEST_F(Rdata_TKEY_Test, createFromWireWithCompression) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire4.wire",
+ // we need to skip the dummy name:
+ Name("gss-tsig").getLength()));
+ fromWireCommonChecks(dynamic_cast<generic::TKEY&>(*rdata));
+}
+
+TEST_F(Rdata_TKEY_Test, badFromWire) {
+ // RDLENGTH is too short:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire5.wire"),
+ InvalidRdataLength);
+ // RDLENGTH is too long:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire6.wire"),
+ InvalidRdataLength);
+ // Algorithm name is broken:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire7.wire"),
+ DNSMessageFORMERR);
+ // Key length is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire8.wire"),
+ isc::OutOfRange);
+ // Other-data length is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire9.wire"),
+ isc::OutOfRange);
+}
+
+TEST_F(Rdata_TKEY_Test, copyConstruct) {
+ const generic::TKEY copy(rdata_tkey);
+ EXPECT_EQ(0, copy.compare(rdata_tkey));
+
+ // Check the copied data is valid even after the original is deleted
+ generic::TKEY* copy2 = new generic::TKEY(rdata_tkey);
+ generic::TKEY copy3(*copy2);
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_tkey));
+}
+
+TEST_F(Rdata_TKEY_Test, createFromParams) {
+ EXPECT_EQ(0, rdata_tkey.compare(generic::TKEY(Name("gss-tsig"),
+ 1619870400,
+ 1619874000,
+ 3, 0, 0, 0, 0, 0)));
+
+ const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
+ 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ EXPECT_EQ(0, generic::TKEY(valid_text2).compare(
+ generic::TKEY(Name("GSS-TSIG"), 1619870400, 1619874000,
+ 3, 16, 12, fake_data, 0, 0)));
+
+ const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ EXPECT_EQ(0, generic::TKEY(valid_text3).compare(
+ generic::TKEY(Name("gss.tsig"), 1619870400, 1619874000,
+ 3, 16, 12, fake_data, 6, fake_data2)));
+
+ EXPECT_THROW(generic::TKEY(Name("gss-tsig"), 0, 0, 0, 0, 0, fake_data, 0, 0),
+ isc::InvalidParameter);
+ EXPECT_THROW(generic::TKEY(Name("gss-tsig"), 0, 0, 0, 0, 12, 0, 0, 0),
+ isc::InvalidParameter);
+ EXPECT_THROW(generic::TKEY(Name("gss-tsig"), 0, 0, 0, 0, 0, 0, 0, fake_data),
+ isc::InvalidParameter);
+ EXPECT_THROW(generic::TKEY(Name("fake_data"), 0, 0, 0, 0, 0, 0, 6, 0),
+ isc::InvalidParameter);
+}
+
+TEST_F(Rdata_TKEY_Test, assignment) {
+ generic::TKEY copy(valid_text2);
+ copy = rdata_tkey;
+ EXPECT_EQ(0, copy.compare(rdata_tkey));
+
+ // Check if the copied data is valid even after the original is deleted
+ generic::TKEY* copy2 = new generic::TKEY(rdata_tkey);
+ generic::TKEY copy3(valid_text2);
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_tkey));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(rdata_tkey));
+}
+
+template <typename Output>
+void
+Rdata_TKEY_Test::toWireCommonChecks(Output& output) const {
+ vector<uint8_t> expect_data;
+
+ output.clear();
+ expect_data.clear();
+ rdata_tkey.toWire(output);
+ // read the expected wire format data and trim the RDLEN part.
+ UnitTestUtil::readWireData("rdata_tkey_toWire1.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ matchWireData(&expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+
+ expect_data.clear();
+ output.clear();
+ generic::TKEY(valid_text2).toWire(output);
+ UnitTestUtil::readWireData("rdata_tkey_toWire2.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ matchWireData(&expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+
+ expect_data.clear();
+ output.clear();
+ generic::TKEY(valid_text3).toWire(output);
+ UnitTestUtil::readWireData("rdata_tkey_toWire3.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ matchWireData(&expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+}
+
+TEST_F(Rdata_TKEY_Test, toWireBuffer) {
+ toWireCommonChecks<OutputBuffer>(obuffer);
+}
+
+TEST_F(Rdata_TKEY_Test, toWireRenderer) {
+ toWireCommonChecks<MessageRenderer>(renderer);
+
+ // check algorithm name won't compressed when it would otherwise.
+ expect_data.clear();
+ renderer.clear();
+ renderer.writeName(Name("gss-tsig"));
+ renderer.writeUint16(26); // RDLEN
+ rdata_tkey.toWire(renderer);
+ UnitTestUtil::readWireData("rdata_tkey_toWire4.wire", expect_data);
+ matchWireData(&expect_data[0], expect_data.size(),
+ renderer.getData(), renderer.getLength());
+
+ // check algorithm can be used as a compression target.
+ expect_data.clear();
+ renderer.clear();
+ renderer.writeUint16(26);
+ rdata_tkey.toWire(renderer);
+ renderer.writeName(Name("gss-tsig"));
+ UnitTestUtil::readWireData("rdata_tkey_toWire5.wire", expect_data);
+ matchWireData(&expect_data[0], expect_data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_TKEY_Test, toText) {
+ EXPECT_EQ(valid_text1, rdata_tkey.toText());
+ EXPECT_EQ(valid_text2, generic::TKEY(valid_text2).toText());
+ EXPECT_EQ(valid_text3, generic::TKEY(valid_text3).toText());
+ EXPECT_EQ(valid_text5, generic::TKEY(valid_text5).toText());
+}
+
+TEST_F(Rdata_TKEY_Test, compare) {
+ // test RDATAs, sorted in the ascending order.
+ // "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the
+ // smallest data of the same length.
+ vector<generic::TKEY> compare_set;
+ compare_set.push_back(generic::TKEY("a.example 20210501120000 "
+ "20210501130000 3 0 0 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120000 "
+ "20210501130000 3 0 0 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130000 3 0 0 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 3 0 0 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 4 0 0 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 4 1 0 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 4 1 3 AAAA 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 4 1 3 FAKE 0"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 4 1 3 FAKE 3 AAAA"));
+ compare_set.push_back(generic::TKEY("example 20210501120001 "
+ "20210501130001 4 1 3 FAKE 3 FAKE"));
+
+ EXPECT_EQ(0,
+ compare_set[0].compare(generic::TKEY("A.EXAMPLE 20210501120000 "
+ "20210501130000 3 0 0 0")));
+
+ vector<generic::TKEY>::const_iterator it;
+ vector<generic::TKEY>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_tkey.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
new file mode 100644
index 0000000..a9cab14
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -0,0 +1,423 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+
+#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/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/tsigerror.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using namespace isc::dns::rdata::any;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+
+class Rdata_TSIG_Test : public RdataTest {
+protected:
+ Rdata_TSIG_Test() :
+ // no MAC or Other Data
+ valid_text1("hmac-md5.sig-alg.reg.int. 1286779327 300 "
+ "0 16020 BADKEY 0"),
+ // MAC but no Other Data
+ valid_text2("hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 0"),
+ // MAC and Other Data
+ valid_text3("hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE"),
+ // MAC and Other Data (with Error that doesn't expect Other Data)
+ valid_text4("hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE"),
+ // numeric error code
+ valid_text5("hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 2845 0"),
+ rdata_tsig(valid_text1)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<TSIG, isc::Exception, isc::Exception>(
+ rdata_str, rdata_tsig, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<TSIG, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<TSIG, BadValue, BadValue>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <TSIG, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<TSIG, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<TSIG, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <TSIG, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_tsig, true, false);
+ }
+
+ template <typename Output>
+ void toWireCommonChecks(Output& output) const;
+
+ const string valid_text1;
+ const string valid_text2;
+ const string valid_text3;
+ const string valid_text4;
+ const string valid_text5;
+ vector<uint8_t> expect_data;
+ const TSIG rdata_tsig; // commonly used test RDATA
+};
+
+TEST_F(Rdata_TSIG_Test, fromText) {
+ // normal case. it also tests getter methods.
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), rdata_tsig.getAlgorithm());
+ EXPECT_EQ(1286779327, rdata_tsig.getTimeSigned());
+ EXPECT_EQ(300, rdata_tsig.getFudge());
+ EXPECT_EQ(0, rdata_tsig.getMACSize());
+ EXPECT_FALSE(rdata_tsig.getMAC());
+ EXPECT_EQ(16020, rdata_tsig.getOriginalID());
+ EXPECT_EQ(TSIGError::BAD_KEY_CODE, rdata_tsig.getError());
+ EXPECT_EQ(0, rdata_tsig.getOtherLen());
+ EXPECT_FALSE(rdata_tsig.getOtherData());
+
+ TSIG tsig2(valid_text2);
+ EXPECT_EQ(12, tsig2.getMACSize());
+ EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig2.getError());
+
+ TSIG tsig3(valid_text3);
+ EXPECT_EQ(6, tsig3.getOtherLen());
+
+ // The other data is unusual, but we don't reject it.
+ EXPECT_NO_THROW(TSIG tsig4(valid_text4));
+
+ // numeric representation of TSIG error
+ TSIG tsig5(valid_text5);
+ EXPECT_EQ(2845, tsig5.getError());
+
+ // not fully qualified algorithm name
+ TSIG tsig1("hmac-md5.sig-alg.reg.int 1286779327 300 0 16020 BADKEY 0");
+ EXPECT_EQ(0, tsig1.compare(rdata_tsig));
+
+ // multi-line rdata
+ checkFromText_None("hmac-md5.sig-alg.reg.int. ( 1286779327 300 \n"
+ "0 16020 BADKEY 0 )");
+
+ // short-form HMAC-MD5 name
+ const TSIG tsig6("hmac-md5. 1286779327 300 0 16020 BADKEY 0");
+ EXPECT_EQ(0, tsig6.compare(rdata_tsig));
+};
+
+TEST_F(Rdata_TSIG_Test, badText) {
+ // too many fields
+ checkFromText_BadString(valid_text1 + " 0 0");
+ // not enough fields
+ checkFromText_LexerError("foo 0 0 0 0 BADKEY");
+ // bad domain name
+ checkFromText_TooLongLabel(
+ "0123456789012345678901234567890123456789012345678901234567890123"
+ " 0 0 0 0 BADKEY 0");
+ checkFromText_EmptyLabel("foo..bar 0 0 0 0 BADKEY");
+ // time is too large (2814...6 is 2^48)
+ checkFromText_InvalidText("foo 281474976710656 0 0 0 BADKEY 0");
+ // invalid time (negative)
+ checkFromText_InvalidText("foo -1 0 0 0 BADKEY 0");
+ // invalid time (not a number)
+ checkFromText_InvalidText("foo TIME 0 0 0 BADKEY 0");
+ // fudge is too large
+ checkFromText_InvalidText("foo 0 65536 0 0 BADKEY 0");
+ // invalid fudge (negative)
+ checkFromText_LexerError("foo 0 -1 0 0 BADKEY 0");
+ // invalid fudge (not a number)
+ checkFromText_LexerError("foo 0 FUDGE 0 0 BADKEY 0");
+ // MAC size is too large
+ checkFromText_InvalidText("foo 0 0 65536 0 BADKEY 0");
+ // invalid MAC size (negative)
+ checkFromText_LexerError("foo 0 0 -1 0 BADKEY 0");
+ // invalid MAC size (not a number)
+ checkFromText_LexerError("foo 0 0 MACSIZE 0 BADKEY 0");
+ // MAC size and MAC mismatch
+ checkFromText_InvalidText("foo 0 0 9 FAKE 0 BADKEY 0");
+ // MAC is bad base64
+ checkFromText_BadValue("foo 0 0 3 FAK= 0 BADKEY 0");
+ // Unknown error code
+ checkFromText_InvalidText("foo 0 0 0 0 TEST 0");
+ // Numeric error code is too large
+ checkFromText_InvalidText("foo 0 0 0 0 65536 0");
+ // Numeric error code is negative
+ checkFromText_InvalidText("foo 0 0 0 0 -1 0");
+ // Other len is too large
+ checkFromText_InvalidText("foo 0 0 0 0 NOERROR 65536 FAKE");
+ // Other len is negative
+ checkFromText_LexerError("foo 0 0 0 0 NOERROR -1 FAKE");
+ // invalid Other len
+ checkFromText_LexerError("foo 0 0 0 0 NOERROR LEN FAKE");
+ // Other len and data mismatch
+ checkFromText_InvalidText("foo 0 0 0 0 NOERROR 9 FAKE");
+}
+
+void
+fromWireCommonChecks(const TSIG& tsig) {
+ EXPECT_EQ(Name("hmac-sha256"), tsig.getAlgorithm());
+ EXPECT_EQ(1286978795, tsig.getTimeSigned());
+ EXPECT_EQ(300, tsig.getFudge());
+
+ vector<uint8_t> expect_mac(32, 'x');
+ matchWireData(&expect_mac[0], expect_mac.size(),
+ tsig.getMAC(), tsig.getMACSize());
+
+ EXPECT_EQ(2845, tsig.getOriginalID());
+
+ EXPECT_EQ(0, tsig.getOtherLen());
+ EXPECT_FALSE(tsig.getOtherData());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWire) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire1.wire"));
+ fromWireCommonChecks(dynamic_cast<TSIG&>(*rdata));
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithOtherData) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire2.wire"));
+ const TSIG& tsig(dynamic_cast<TSIG&>(*rdata));
+
+ EXPECT_EQ(18, tsig.getError());
+ const uint64_t otherdata = 1286978795 + 300 + 1; // time-signed + fudge + 1
+ expect_data.resize(6);
+ expect_data[0] = (otherdata >> 40);
+ expect_data[1] = ((otherdata >> 32) & 0xff);
+ expect_data[2] = ((otherdata >> 24) & 0xff);
+ expect_data[3] = ((otherdata >> 16) & 0xff);
+ expect_data[4] = ((otherdata >> 8) & 0xff);
+ expect_data[5] = (otherdata & 0xff);
+ matchWireData(&expect_data[0], expect_data.size(),
+ tsig.getOtherData(), tsig.getOtherLen());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithoutMAC) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire3.wire"));
+ const TSIG& tsig(dynamic_cast<TSIG&>(*rdata));
+ EXPECT_EQ(16, tsig.getError());
+ EXPECT_EQ(0, tsig.getMACSize());
+ EXPECT_FALSE(tsig.getMAC());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithCompression) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire4.wire",
+ // we need to skip the dummy name:
+ Name("hmac-sha256").getLength()));
+ fromWireCommonChecks(dynamic_cast<TSIG&>(*rdata));
+}
+
+TEST_F(Rdata_TSIG_Test, badFromWire) {
+ // RDLENGTH is too short:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire5.wire"),
+ InvalidRdataLength);
+ // RDLENGTH is too long:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire6.wire"),
+ InvalidRdataLength);
+ // Algorithm name is broken:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire7.wire"),
+ DNSMessageFORMERR);
+ // MAC size is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire8.wire"),
+ isc::OutOfRange);
+ // Other-data length is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire9.wire"),
+ isc::OutOfRange);
+}
+
+TEST_F(Rdata_TSIG_Test, copyConstruct) {
+ const TSIG copy(rdata_tsig);
+ EXPECT_EQ(0, copy.compare(rdata_tsig));
+
+ // Check the copied data is valid even after the original is deleted
+ TSIG* copy2 = new TSIG(rdata_tsig);
+ TSIG copy3(*copy2);
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_tsig));
+}
+
+TEST_F(Rdata_TSIG_Test, createFromParams) {
+ EXPECT_EQ(0, rdata_tsig.compare(TSIG(Name("hmac-md5.sig-alg.reg.int"),
+ 1286779327, 300, 0, 0, 16020, 17, 0, 0)));
+
+ const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
+ 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ EXPECT_EQ(0, TSIG(valid_text2).compare(TSIG(Name("hmac-sha256"), 1286779327, 300, 12,
+ fake_data, 16020, 16, 0, 0)));
+
+ const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ EXPECT_EQ(0, TSIG(valid_text3).compare(TSIG(Name("hmac-sha1"), 1286779327, 300, 12,
+ fake_data, 16020, 18, 6, fake_data2)));
+
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 1ULL << 48, 300, 12, fake_data, 16020, 18, 6, fake_data2),
+ isc::OutOfRange);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, fake_data, 16020, 18, 0, 0),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 12, 0, 16020, 18, 0, 0),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, 0, 16020, 18, 0, fake_data),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, 0, 16020, 18, 6, 0),
+ isc::InvalidParameter);
+}
+
+TEST_F(Rdata_TSIG_Test, assignment) {
+ TSIG copy(valid_text2);
+ copy = rdata_tsig;
+ EXPECT_EQ(0, copy.compare(rdata_tsig));
+
+ // Check if the copied data is valid even after the original is deleted
+ TSIG* copy2 = new TSIG(rdata_tsig);
+ TSIG copy3(valid_text2);
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_tsig));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(rdata_tsig));
+}
+
+template <typename Output>
+void
+Rdata_TSIG_Test::toWireCommonChecks(Output& output) const {
+ vector<uint8_t> expect_data;
+
+ output.clear();
+ expect_data.clear();
+ rdata_tsig.toWire(output);
+ // read the expected wire format data and trim the RDLEN part.
+ UnitTestUtil::readWireData("rdata_tsig_toWire1.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ matchWireData(&expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+
+ expect_data.clear();
+ output.clear();
+ TSIG(valid_text2).toWire(output);
+ UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ matchWireData(&expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+
+ expect_data.clear();
+ output.clear();
+ TSIG(valid_text3).toWire(output);
+ UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data);
+ expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+ matchWireData(&expect_data[0], expect_data.size(),
+ output.getData(), output.getLength());
+}
+
+TEST_F(Rdata_TSIG_Test, toWireBuffer) {
+ toWireCommonChecks<OutputBuffer>(obuffer);
+}
+
+TEST_F(Rdata_TSIG_Test, toWireRenderer) {
+ toWireCommonChecks<MessageRenderer>(renderer);
+
+ // check algorithm name won't compressed when it would otherwise.
+ expect_data.clear();
+ renderer.clear();
+ renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
+ renderer.writeUint16(42); // RDLEN
+ rdata_tsig.toWire(renderer);
+ UnitTestUtil::readWireData("rdata_tsig_toWire4.wire", expect_data);
+ matchWireData(&expect_data[0], expect_data.size(),
+ renderer.getData(), renderer.getLength());
+
+ // check algorithm can be used as a compression target.
+ expect_data.clear();
+ renderer.clear();
+ renderer.writeUint16(42);
+ rdata_tsig.toWire(renderer);
+ renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
+ UnitTestUtil::readWireData("rdata_tsig_toWire5.wire", expect_data);
+ matchWireData(&expect_data[0], expect_data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_TSIG_Test, toText) {
+ EXPECT_EQ(valid_text1, rdata_tsig.toText());
+ EXPECT_EQ(valid_text2, TSIG(valid_text2).toText());
+ EXPECT_EQ(valid_text3, TSIG(valid_text3).toText());
+ EXPECT_EQ(valid_text5, TSIG(valid_text5).toText());
+}
+
+TEST_F(Rdata_TSIG_Test, compare) {
+ // test RDATAs, sorted in the ascending order.
+ // "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the
+ // smallest data of the same length.
+ vector<TSIG> compare_set;
+ compare_set.push_back(TSIG("a.example 0 300 0 16020 0 0"));
+ compare_set.push_back(TSIG("example 0 300 0 16020 0 0"));
+ compare_set.push_back(TSIG("example 1 300 0 16020 0 0"));
+ compare_set.push_back(TSIG("example 1 600 0 16020 0 0"));
+ compare_set.push_back(TSIG("example 1 600 3 AAAA 16020 0 0"));
+ compare_set.push_back(TSIG("example 1 600 3 FAKE 16020 0 0"));
+ compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 0 0"));
+ compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 1 0"));
+ compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 1 3 AAAA"));
+ compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 1 3 FAKE"));
+
+ EXPECT_EQ(0, compare_set[0].compare(TSIG("A.EXAMPLE 0 300 0 16020 0 0")));
+
+ vector<TSIG>::const_iterator it;
+ vector<TSIG>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_tsig.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
new file mode 100644
index 0000000..91f2735
--- /dev/null
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -0,0 +1,394 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// This is the common code for TXT tests.
+
+#include <config.h>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/rdataclass.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+
+template<class T>
+class RRTYPE : public RRType {
+public:
+ RRTYPE();
+};
+
+template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
+
+const uint8_t wiredata_txt_like[] = {
+ sizeof("Test-String") - 1,
+ 'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+const uint8_t wiredata_nulltxt[] = { 0 };
+
+template<class TXT_LIKE>
+class Rdata_TXT_LIKE_Test : public RdataTest {
+protected:
+ Rdata_TXT_LIKE_Test() :
+ wiredata_longesttxt(256, 'a'),
+ rdata_txt_like("Test-String"),
+ rdata_txt_like_empty("\"\""),
+ rdata_txt_like_quoted("\"Test-String\"")
+ {
+ wiredata_longesttxt[0] = 255; // adjust length
+ }
+
+protected:
+ vector<uint8_t> wiredata_longesttxt;
+ const TXT_LIKE rdata_txt_like;
+ const TXT_LIKE rdata_txt_like_empty;
+ const TXT_LIKE rdata_txt_like_quoted;
+};
+
+// The list of types we want to test.
+typedef testing::Types<generic::TXT> Implementations;
+
+#ifdef TYPED_TEST_SUITE
+TYPED_TEST_SUITE(Rdata_TXT_LIKE_Test, Implementations);
+#else
+TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
+#endif
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
+ // Below we check the behavior for the "from text" constructors, both
+ // from std::string and with MasterLexer. The underlying implementation
+ // is the same, so both should work exactly same, but we confirm both
+ // cases.
+
+ const std::string multi_line = "(\n \"Test-String\" )";
+ const std::string escaped_txt = "Test\\045Strin\\g";
+
+ // test input for the lexer version
+ std::stringstream ss;
+ ss << "Test-String\n";
+ ss << "\"Test-String\"\n"; // explicitly surrounded by '"'s
+ ss << multi_line << "\n"; // multi-line text with ()
+ ss << escaped_txt << "\n"; // using the two types of escape with '\'
+ ss << "\"\"\n"; // empty string (note: still valid char-str)
+ ss << string(255, 'a') << "\n"; // Longest possible character-string.
+ ss << string(256, 'a') << "\n"; // char-string too long
+ ss << "\"Test-String\\\"\n"; // unbalanced quote
+ ss << "\"Test-String\\\"\"\n";
+ this->lexer.pushSource(ss);
+
+ // commonly used Rdata to compare below, created from wire
+ ConstRdataPtr const rdata =
+ this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"), "rdata_txt_fromWire1");
+
+ // normal case is covered in toWireBuffer. First check the std::string
+ // case, then with MasterLexer. For the latter, we need to read and skip
+ // '\n'. These apply to most of the other cases below.
+ EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // surrounding double-quotes shouldn't change the result.
+ EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // multi-line input with ()
+ EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // for the same data using escape
+ EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // Null character-string.
+ this->obuffer.clear();
+ TypeParam(string("\"\"")).toWire(this->obuffer);
+ matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt),
+ this->obuffer.getData(), this->obuffer.getLength());
+
+ this->obuffer.clear();
+ TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, this->loader_cb).
+ toWire(this->obuffer);
+ matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt),
+ this->obuffer.getData(), this->obuffer.getLength());
+
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // Longest possible character-string.
+ this->obuffer.clear();
+ TypeParam(string(255, 'a')).toWire(this->obuffer);
+ matchWireData(&this->wiredata_longesttxt[0],
+ this->wiredata_longesttxt.size(),
+ this->obuffer.getData(), this->obuffer.getLength());
+
+ this->obuffer.clear();
+ TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, this->loader_cb).
+ toWire(this->obuffer);
+ matchWireData(&this->wiredata_longesttxt[0],
+ this->wiredata_longesttxt.size(),
+ this->obuffer.getData(), this->obuffer.getLength());
+
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // Too long text for a valid character-string.
+ EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
+ EXPECT_THROW(TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb), CharStringTooLong);
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // The escape character makes the double quote a part of character-string,
+ // so this is invalid input and should be rejected.
+ EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText);
+ EXPECT_THROW(TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb), MasterLexer::LexerError);
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) {
+ // Tests for "from text" variants construction with various forms of
+ // multi character-strings.
+
+ std::vector<std::string > texts;
+ texts.push_back("\"Test-String\" \"Test-String\""); // most common form
+ texts.push_back("\"Test-String\"\"Test-String\""); // no space between'em
+ texts.push_back("\"Test-String\" Test-String"); // no '"' for one
+ texts.push_back("\"Test-String\"Test-String"); // and no space either
+ texts.push_back("Test-String \"Test-String\""); // no '"' for the other
+ texts.push_back("Test-String\"Test-String\""); // and no space either
+
+ std::stringstream ss;
+ for (auto const& it : texts) {
+ ss << it << "\n";
+ }
+ this->lexer.pushSource(ss);
+
+ // The corresponding Rdata built from wire to compare in the checks below.
+ ConstRdataPtr const rdata =
+ this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"), "rdata_txt_fromWire3.wire");
+
+ // Confirm we can construct the Rdata from the test text, both from
+ // std::string and with lexer, and that matches the from-wire data.
+ for (auto const& it : texts) {
+ SCOPED_TRACE(it);
+ EXPECT_EQ(0, TypeParam(it).compare(*rdata));
+
+ EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE,
+ this->lexer.getNextToken().getType());
+ }
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) {
+ // This is for the std::string version only: the input must end with EOF;
+ // an extra new-line will result in an exception.
+ EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText);
+ // Same if there's a space before '\n'
+ EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) {
+ // If the input text doesn't contain any character-string, it should be
+ // rejected
+ EXPECT_THROW(TypeParam(""), InvalidRdataText);
+ EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space
+ EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with ()
+}
+
+void
+makeLargest(vector<uint8_t>& data) {
+ uint8_t ch = 0;
+
+ // create 255 sets of character-strings, each of which has the longest
+ // length (255bytes string + 1-byte length field)
+ for (int i = 0; i < 255; ++i, ++ch) {
+ data.push_back(255);
+ data.insert(data.end(), 255, ch);
+ }
+ // the last character-string should be 255 bytes (including the one-byte
+ // length field) in length so that the total length should be in the range
+ // of 16-bit integers.
+ data.push_back(254);
+ data.insert(data.end(), 254, ch);
+
+ ASSERT_TRUE(data.size() == 65535);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
+ EXPECT_EQ(0, this->rdata_txt_like.compare(
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"),
+ "rdata_txt_fromWire1")));
+
+ // Empty character string
+ EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"),
+ "rdata_txt_fromWire2.wire")));
+
+ // Multiple character strings
+ this->obuffer.clear();
+ this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire3.wire")->toWire(this->obuffer);
+ // the result should be 'wiredata_txt' repeated twice
+ vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like +
+ sizeof(wiredata_txt_like));
+ expected_data.insert(expected_data.end(), wiredata_txt_like,
+ wiredata_txt_like + sizeof(wiredata_txt_like));
+ matchWireData(&expected_data[0], expected_data.size(),
+ this->obuffer.getData(), this->obuffer.getLength());
+
+ // Largest length of data. There's nothing special, but should be
+ // constructed safely, and the content should be identical to the original
+ // data.
+ vector<uint8_t> largest_txt_like_data;
+ makeLargest(largest_txt_like_data);
+ InputBuffer ibuffer(&largest_txt_like_data[0],
+ largest_txt_like_data.size());
+ TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size());
+ this->obuffer.clear();
+ largest_txt_like.toWire(this->obuffer);
+ matchWireData(&largest_txt_like_data[0], largest_txt_like_data.size(),
+ this->obuffer.getData(), this->obuffer.getLength());
+
+ // rdlen parameter is out of range. This is a rare event because we'd
+ // normally call the constructor via a polymorphic wrapper, where the
+ // length is validated. But this should be checked explicitly.
+ InputBuffer ibuffer2(&largest_txt_like_data[0],
+ largest_txt_like_data.size());
+ EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength);
+
+ // RDATA is empty, which is invalid for TXT_LIKE.
+ EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire4.wire"),
+ DNSMessageFORMERR);
+
+ // character-string length is too large, which could cause overrun.
+ EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+ "rdata_txt_fromWire5.wire"),
+ DNSMessageFORMERR);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
+ EXPECT_EQ(0, this->rdata_txt_like.compare(
+ *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "Test-String")));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
+ this->rdata_txt_like.toWire(this->obuffer);
+ matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like),
+ this->obuffer.getData(), this->obuffer.getLength());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
+ this->rdata_txt_like.toWire(this->renderer);
+ matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like),
+ this->renderer.getData(), this->renderer.getLength());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
+ EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
+
+ // Check escape behavior
+ const TypeParam double_quotes("Test-String\"Test-String\"");
+ EXPECT_EQ("\"Test-String\" \"Test-String\"", double_quotes.toText());
+ const TypeParam semicolon("Test-String\\;Test-String");
+ EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
+ const TypeParam backslash("Test-String\\\\Test-String");
+ EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
+ const TypeParam before_x20("Test-String\\031Test-String");
+ EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
+ const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
+ const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
+ const TypeParam after_x7e("Test-String\\127Test-String");
+ EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
+ TypeParam rdata1("assignment1");
+ TypeParam rdata2("assignment2");
+ rdata1 = rdata2;
+ EXPECT_EQ(0, rdata2.compare(rdata1));
+
+ // Check if the copied data is valid even after the original is deleted
+ TypeParam* rdata3 = new TypeParam(rdata1);
+ TypeParam rdata4("assignment3");
+ rdata4 = *rdata3;
+ delete rdata3;
+ EXPECT_EQ(0, rdata4.compare(rdata1));
+
+ // Self assignment
+ rdata2 = *&rdata2;
+ EXPECT_EQ(0, rdata2.compare(rdata1));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
+ string const txt1("aaaaaaaa");
+ string const txt2("aaaaaaaaaa");
+ string const txt3("bbbbbbbb");
+ string const txt4(129, 'a');
+ string const txt5(128, 'b');
+
+ EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
+
+ EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0);
+ EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0);
+
+ EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
+ EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);
+
+ EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0);
+ EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0);
+
+ // we're comparing the data raw, starting at the length octet, so a shorter
+ // string sorts before a longer one no matter the lexicopraphical order
+ EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0);
+ EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0);
+
+ // to make sure the length octet compares unsigned
+ EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0);
+ EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0);
+
+ EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0);
+ EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0);
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch),
+ bad_cast);
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
new file mode 100644
index 0000000..b52d7cb
--- /dev/null
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -0,0 +1,471 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <functional>
+#include <iomanip>
+#include <vector>
+#include <string>
+#include <sstream>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+#include <util/unittests/wiredata.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+
+namespace {
+void
+nullCallback(const std::string&, size_t, const std::string&) {
+}
+}
+
+RdataTest::RdataTest() :
+ obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")),
+ loader_cb(MasterLoaderCallbacks(nullCallback, nullCallback)) {
+}
+
+RdataPtr
+RdataTest::rdataFactoryFromFile(const RRType& rrtype, const RRClass& rrclass,
+ const char* datafile, size_t position) {
+ std::vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+ buffer.setPosition(position);
+
+ uint16_t rdlen = buffer.readUint16();
+ return (createRdata(rrtype, rrclass, buffer, rdlen));
+}
+
+namespace test {
+
+RdataPtr
+createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& str) {
+ std::stringstream ss(str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ MasterLoaderCallbacks callbacks(nullCallback, nullCallback);
+ 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() << "; comment, should be ignored\n";
+ 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(
+ std::bind(&CreateRdataCallback::callback, &callback,
+ CreateRdataCallback::ERROR, ph::_1, ph::_2, ph::_3),
+ std::bind(&CreateRdataCallback::callback, &callback,
+ CreateRdataCallback::WARN, ph::_1, ph::_2, ph::_3));
+
+ size_t line = 0;
+
+ // Valid case.
+ ++line;
+ ConstRdataPtr rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer,
+ 0, MasterLoader::MANY_ERRORS,
+ callbacks);
+ EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+ EXPECT_FALSE(callback.isCalled());
+
+ // Similar to the previous case, but RDATA is followed by a comment.
+ // It should cause any confusion.
+ ++line;
+ callback.clear();
+ rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0,
+ 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.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, 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.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed near 'extra': "
+ "extra input text");
+
+ // Lexer error will happen, corresponding error callback will be triggered.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed: unbalanced parentheses");
+
+ // Semantics level error will happen, corresponding error callback will be
+ // triggered.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed: Bad IN/AAAA RDATA text: "
+ "'192.0.2.1'");
+
+ // Input is valid and parse will succeed, but with a warning that the
+ // file is not ended with a newline.
+ ++line;
+ callback.clear();
+ rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0,
+ MasterLoader::MANY_ERRORS, callbacks);
+ EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+ callback.check(src_name, line, CreateRdataCallback::WARN,
+ "file does not end with newline");
+}
+
+TEST_F(RdataTest, getLength) {
+ const in::AAAA aaaa_rdata("2001:db8::1");
+ EXPECT_EQ(16, aaaa_rdata.getLength());
+
+ const generic::TXT txt_rdata("Hello World");
+ EXPECT_EQ(12, txt_rdata.getLength());
+}
+
+}
+}
+}
+
+namespace {
+
+// Wire-format data correspond to rdata_unknown. Note that it doesn't
+// include RDLENGTH.
+const uint8_t wiredata_unknown[] = { 0xa1, 0xb2, 0xc3, 0x0d };
+
+class Rdata_Unknown_Test : public RdataTest {
+public:
+ Rdata_Unknown_Test() :
+ // "Unknown" RR Type used for the test cases below. If/when we
+ // use this type number as a "well-known" (probably
+ // experimental) type, we'll need to renumber it.
+ unknown_rrtype(RRType(65000)),
+ rdata_unknowntxt("\\# 4 a1b2c30d"),
+ rdata_unknown(rdata_unknowntxt)
+ {}
+protected:
+ static string getLongestRdataTxt();
+ static void getLongestRdataWire(vector<uint8_t>& v);
+
+ const RRType unknown_rrtype;
+ const std::string rdata_unknowntxt;
+ const generic::Generic rdata_unknown;
+};
+
+string
+Rdata_Unknown_Test::getLongestRdataTxt() {
+ ostringstream oss;
+
+ oss << "\\# " << MAX_RDLENGTH << " ";
+ oss.fill('0');
+ oss << right << hex;
+ for (int i = 0; i < MAX_RDLENGTH; i++) {
+ oss << setw(2) << (i & 0xff);
+ }
+
+ return (oss.str());
+}
+
+void
+Rdata_Unknown_Test::getLongestRdataWire(vector<uint8_t>& v) {
+ unsigned char ch = 0;
+ for (int i = 0; i < MAX_RDLENGTH; ++i, ++ch) {
+ v.push_back(ch);
+ }
+}
+
+TEST_F(Rdata_Unknown_Test, createFromText) {
+ // valid construction. This also tests a normal case of "FromWire".
+ EXPECT_EQ(0, generic::Generic("\\# 4 a1b2c30d").compare(
+ *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(),
+ "rdata_unknown_fromWire")));
+ // upper case hexadecimal digits should also be okay.
+ EXPECT_EQ(0, generic::Generic("\\# 4 A1B2C30D").compare(
+ *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(),
+ "rdata_unknown_fromWire")));
+ // 0-length RDATA should be accepted
+ EXPECT_EQ(0, generic::Generic("\\# 0").compare(
+ *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(),
+ "rdata_unknown_fromWire", 6)));
+ // hex encoding can be space-separated
+ EXPECT_EQ(0, generic::Generic("\\# 4 a1 b2c30d").compare(rdata_unknown));
+ EXPECT_EQ(0, generic::Generic("\\# 4 a1b2 c30d").compare(rdata_unknown));
+ EXPECT_EQ(0, generic::Generic("\\# 4 a1 b2 c3 0d").compare(rdata_unknown));
+ EXPECT_EQ(0, generic::Generic("\\# 4 a1\tb2c3 0d").compare(rdata_unknown));
+
+ // Max-length RDATA
+ vector<uint8_t> v;
+ getLongestRdataWire(v);
+ InputBuffer ibuffer(&v[0], v.size());
+ EXPECT_EQ(0, generic::Generic(getLongestRdataTxt()).compare(
+ generic::Generic(ibuffer, v.size())));
+
+ // the length field must match the encoding data length.
+ EXPECT_THROW(generic::Generic("\\# 4 1080c0ff00"), InvalidRdataLength);
+ EXPECT_THROW(generic::Generic("\\# 5 1080c0ff"), InvalidRdataLength);
+ // RDATA encoding part must consist of an even number of hex digits.
+ EXPECT_THROW(generic::Generic("\\# 1 1"), InvalidRdataText);
+ EXPECT_THROW(generic::Generic("\\# 1 ax"), InvalidRdataText);
+ // the length should be 16-bit unsigned integer
+ EXPECT_THROW(generic::Generic("\\# 65536 a1b2c30d"), InvalidRdataLength);
+ EXPECT_THROW(generic::Generic("\\# -1 a1b2c30d"), InvalidRdataLength);
+ EXPECT_THROW(generic::Generic("\\# 1.1 a1"), InvalidRdataLength);
+ EXPECT_THROW(generic::Generic("\\# 0a 00010203040506070809"),
+ InvalidRdataLength);
+ // should reject if the special token is missing.
+ EXPECT_THROW(generic::Generic("4 a1b2c30d"), InvalidRdataText);
+ // the special token, the RDLENGTH and the data must be space separated.
+ EXPECT_THROW(generic::Generic("\\#0"), InvalidRdataText);
+ EXPECT_THROW(generic::Generic("\\# 1ff"), InvalidRdataLength);
+}
+
+TEST_F(Rdata_Unknown_Test, createFromWire) {
+ // normal case (including 0-length data) is covered in createFromText.
+
+ // buffer too short. the error should be detected in buffer read
+ EXPECT_THROW(rdataFactoryFromFile(unknown_rrtype, RRClass::IN(),
+ "rdata_unknown_fromWire", 8),
+ isc::OutOfRange);
+
+ // too large data
+ vector<uint8_t> v;
+ getLongestRdataWire(v);
+ v.push_back(0); // making it too long
+ InputBuffer ibuffer(&v[0], v.size());
+ EXPECT_THROW(generic::Generic(ibuffer, v.size()), InvalidRdataLength);
+}
+
+// The following 3 sets of tests check the behavior of createRdata() variants
+// with the "unknown" RRtype. The result should be RRclass independent.
+TEST_F(Rdata_Unknown_Test, createRdataFromString) {
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass::IN(),
+ rdata_unknowntxt)));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass::CH(),
+ rdata_unknowntxt)));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass("CLASS65000"),
+ rdata_unknowntxt)));
+}
+
+TEST_F(Rdata_Unknown_Test, createRdataFromWire) {
+ InputBuffer ibuffer(wiredata_unknown, sizeof(wiredata_unknown));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass::IN(),
+ ibuffer, sizeof(wiredata_unknown))));
+
+ InputBuffer ibuffer2(wiredata_unknown, sizeof(wiredata_unknown));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass::CH(),
+ ibuffer2, sizeof(wiredata_unknown))));
+
+ InputBuffer ibuffer3(wiredata_unknown, sizeof(wiredata_unknown));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass(65000),
+ ibuffer3, sizeof(wiredata_unknown))));
+}
+
+TEST_F(Rdata_Unknown_Test, createRdataByCopy) {
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass::IN(), rdata_unknown)));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass::CH(), rdata_unknown)));
+ EXPECT_EQ(0, rdata_unknown.compare(
+ *createRdata(unknown_rrtype, RRClass(65000),
+ rdata_unknown)));
+}
+
+TEST_F(Rdata_Unknown_Test, copyConstruct) {
+ generic::Generic copy(rdata_unknown);
+ EXPECT_EQ(0, copy.compare(rdata_unknown));
+
+ // Check the copied data is valid even after the original is deleted
+ generic::Generic* copy2 = new generic::Generic(rdata_unknown);
+ generic::Generic copy3(*copy2);
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_unknown));
+}
+
+TEST_F(Rdata_Unknown_Test, assignment) {
+ generic::Generic copy("\\# 1 10");
+ copy = rdata_unknown;
+ EXPECT_EQ(0, copy.compare(rdata_unknown));
+
+ // Check if the copied data is valid even after the original is deleted
+ generic::Generic* copy2 = new generic::Generic(rdata_unknown);
+ generic::Generic copy3("\\# 1 10");
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_unknown));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(rdata_unknown));
+}
+
+TEST_F(Rdata_Unknown_Test, toText) {
+ EXPECT_EQ(rdata_unknowntxt, rdata_unknown.toText());
+ EXPECT_EQ(getLongestRdataTxt(),
+ generic::Generic(getLongestRdataTxt()).toText());
+}
+
+TEST_F(Rdata_Unknown_Test, toWireBuffer) {
+ rdata_unknown.toWire(obuffer);
+ matchWireData(wiredata_unknown, sizeof(wiredata_unknown),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_Unknown_Test, toWireRenderer) {
+ rdata_unknown.toWire(renderer);
+ matchWireData(wiredata_unknown, sizeof(wiredata_unknown),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_Unknown_Test, compare) {
+ // comparison as left-justified unsigned octet sequences:
+ // cppcheck-suppress uselessCallsCompare
+ EXPECT_EQ(0, rdata_unknown.compare(rdata_unknown));
+
+ generic::Generic rdata_unknown_small("\\# 4 00b2c3ff");
+ EXPECT_GT(0, rdata_unknown_small.compare(rdata_unknown));
+ EXPECT_LT(0, rdata_unknown.compare(rdata_unknown_small));
+
+ generic::Generic rdata_unknown_large("\\# 4 ffb2c300");
+ EXPECT_LT(0, rdata_unknown_large.compare(rdata_unknown));
+ EXPECT_GT(0, rdata_unknown.compare(rdata_unknown_large));
+
+ // the absence of an octet sorts before a zero octet.
+ generic::Generic rdata_unknown_short("\\# 3 a1b2c3");
+ EXPECT_GT(0, rdata_unknown_short.compare(rdata_unknown));
+ EXPECT_LT(0, rdata_unknown.compare(rdata_unknown_short));
+}
+
+TEST_F(Rdata_Unknown_Test, LeftShiftOperator) {
+ ostringstream oss;
+ oss << rdata_unknown;
+ EXPECT_EQ(rdata_unknown.toText(), oss.str());
+}
+
+//
+// Tests for global utility functions
+//
+TEST_F(RdataTest, compareNames) {
+ Name small("a.example");
+ Name large("example");
+
+ // Check the case where the order is different from the owner name
+ // comparison:
+ EXPECT_TRUE(small > large);
+ EXPECT_EQ(-1, compareNames(small, large));
+ EXPECT_EQ(1, compareNames(large, small));
+
+ // Check case insensitive comparison:
+ Name small_upper("A.EXAMPLE");
+ EXPECT_EQ(0, compareNames(small, small_upper));
+
+ // the absence of an octet sorts before a zero octet.
+ Name large2("a.example2");
+ EXPECT_EQ(-1, compareNames(small, large2));
+ EXPECT_EQ(1, compareNames(large2, small));
+}
+}
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
new file mode 100644
index 0000000..b2ba483
--- /dev/null
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RDATA_UNITTEST_H
+#define RDATA_UNITTEST_H
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <sstream>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+class RdataTest : public ::testing::Test {
+protected:
+ RdataTest();
+ static RdataPtr rdataFactoryFromFile(const RRType& rrtype,
+ const RRClass& rrclass,
+ const char* datafile,
+ size_t position = 0);
+
+ // Common check to see the result of Rdata construction of given type
+ // (template parameter RdataType) either from std::string or with
+ // MasterLexer object. If it's expected to succeed the result should be
+ // identical to the commonly used test data (rdata_expected); otherwise it
+ // should result in the exception specified as the template parameter:
+ // ExForString for the string version, and ExForLexer for the lexer
+ // version. throw_str_version and throw_lexer_version are set to true
+ // iff the string/lexer version is expected to throw, respectively.
+ // Parameter origin can be set to non null for the origin parameter of
+ // the lexer version of Rdata constructor.
+ template <typename RdataType, typename ExForString, typename ExForLexer>
+ void checkFromText(const std::string& rdata_txt,
+ const RdataType& rdata_expected,
+ bool throw_str_version = true,
+ bool throw_lexer_version = true,
+ const Name* origin = 0) {
+ SCOPED_TRACE(rdata_txt);
+
+ if (throw_str_version) {
+ EXPECT_THROW(RdataType rdata(rdata_txt), ExForString);
+ } else {
+ EXPECT_EQ(0, RdataType(rdata_txt).compare(rdata_expected));
+ }
+
+ std::stringstream ss(rdata_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+ if (throw_lexer_version) {
+ EXPECT_THROW(RdataType rdata(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb), ExForLexer);
+ } else {
+ EXPECT_EQ(0, RdataType(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb).compare(rdata_expected));
+ }
+ }
+
+ isc::util::OutputBuffer obuffer;
+ MessageRenderer renderer;
+ /// 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;
+ MasterLoaderCallbacks loader_cb;
+};
+
+namespace test {
+RdataPtr
+createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& str);
+}
+
+}
+}
+}
+#endif // RDATA_UNITTEST_H
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
new file mode 100644
index 0000000..bf71e92
--- /dev/null
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -0,0 +1,204 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrparamregistry.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+#include <boost/scoped_ptr.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using boost::scoped_ptr;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class RRClassTest : public ::testing::Test {
+protected:
+ RRClassTest() : obuffer(0) {}
+
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+
+ static RRClass rrclassFactoryFromWire(const char* datafile);
+ static const RRClass rrclass_1, rrclass_0x80, rrclass_0x800,
+ rrclass_0x8000, rrclass_max;
+ static const uint8_t wiredata[];
+};
+
+const RRClass RRClassTest::rrclass_1(1);
+const RRClass RRClassTest::rrclass_0x80(0x80);
+const RRClass RRClassTest::rrclass_0x800(0x800);
+const RRClass RRClassTest::rrclass_0x8000(0x8000);
+const RRClass RRClassTest::rrclass_max(0xffff);
+// This is wire-format data for the above sample RRClass rendered in the
+// appearing order.
+const uint8_t RRClassTest::wiredata[] = { 0x00, 0x01, 0x00, 0x80, 0x08,
+ 0x00, 0x80, 0x00, 0xff, 0xff };
+
+RRClass
+RRClassTest::rrclassFactoryFromWire(const char* datafile) {
+ std::vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+
+ return (RRClass(buffer));
+}
+
+TEST_F(RRClassTest, fromTextConstructor) {
+ EXPECT_EQ("IN", RRClass("IN").toText());
+ EXPECT_EQ("CH", RRClass("CH").toText());
+
+ EXPECT_EQ("CLASS65535", RRClass("CLASS65535").toText());
+
+ // some uncommon cases: see the corresponding RRType tests.
+ EXPECT_EQ(53, RRClass("CLASS00053").getCode());
+ EXPECT_THROW(RRClass("CLASS000053"), InvalidRRClass);
+
+ // bogus CLASSnnn representations: should trigger an exception
+ EXPECT_THROW(RRClass("CLASS"), InvalidRRClass);
+ EXPECT_THROW(RRClass("CLASS-1"), InvalidRRClass);
+ EXPECT_THROW(RRClass("CLASSxxx"), InvalidRRClass);
+ EXPECT_THROW(RRClass("CLASS65536"), InvalidRRClass);
+ EXPECT_THROW(RRClass("CLASS6500x"), InvalidRRClass);
+ EXPECT_THROW(RRClass("CLASS65000 "), InvalidRRClass);
+}
+
+TEST_F(RRClassTest, fromWire) {
+ EXPECT_EQ(0x1234,
+ rrclassFactoryFromWire("rrcode16_fromWire1").getCode());
+ EXPECT_THROW(rrclassFactoryFromWire("rrcode16_fromWire2"),
+ IncompleteRRClass);
+}
+
+TEST_F(RRClassTest, caseConstruct) {
+ EXPECT_EQ("IN", RRClass("in").toText());
+ EXPECT_EQ("CH", RRClass("ch").toText());
+ EXPECT_EQ("CLASS65535", RRClass("class65535").toText());
+}
+
+TEST_F(RRClassTest, toText) {
+ EXPECT_EQ("IN", RRClass(1).toText());
+ EXPECT_EQ("CLASS65000", RRClass(65000).toText());
+}
+
+TEST_F(RRClassTest, createFromText) {
+ scoped_ptr<RRClass> chclass(RRClass::createFromText("CH"));
+ EXPECT_TRUE(chclass);
+ EXPECT_EQ("CH", chclass->toText());
+
+ scoped_ptr<RRClass> zzclass(RRClass::createFromText("ZZ"));
+ EXPECT_FALSE(zzclass);
+}
+
+TEST_F(RRClassTest, toWireBuffer) {
+ rrclass_1.toWire(obuffer);
+ rrclass_0x80.toWire(obuffer);
+ rrclass_0x800.toWire(obuffer);
+ rrclass_0x8000.toWire(obuffer);
+ rrclass_max.toWire(obuffer);
+
+ matchWireData(wiredata, sizeof (wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(RRClassTest, toWireRenderer) {
+ rrclass_1.toWire(renderer);
+ rrclass_0x80.toWire(renderer);
+ rrclass_0x800.toWire(renderer);
+ rrclass_0x8000.toWire(renderer);
+ rrclass_max.toWire(renderer);
+
+ matchWireData(wiredata, sizeof (wiredata),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RRClassTest, wellKnownClass) {
+ EXPECT_EQ(1, RRClass::IN().getCode());
+ EXPECT_EQ("IN", RRClass::IN().toText());
+}
+
+TEST_F(RRClassTest, compare) {
+ EXPECT_TRUE(RRClass(1) == RRClass("IN"));
+ EXPECT_TRUE(RRClass(1).equals(RRClass("IN")));
+ EXPECT_TRUE(RRClass(0).nequals(RRClass("IN")));
+
+ EXPECT_TRUE(RRClass("IN") < RRClass("CH"));
+ EXPECT_TRUE(RRClass(100) < RRClass(65535));
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(RRClassTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << RRClass::IN();
+ EXPECT_EQ(RRClass::IN().toText(), oss.str());
+}
+
+// Below, we'll check definitions for all well-known RR classes; whether they
+// are defined and have the correct parameter values. Test data are generated
+// from the list available at:
+// http://www.iana.org/assignments/dns-parameters/dns-parameters.xml
+struct WellKnownClassParam {
+ const char* const txt; // "IN", "CH", etc
+ const uint16_t code; // 1, 3, etc
+ const RRClass& (*obj)(); // RRClass::IN(), RRClass::CH(), etc
+} well_known_classes[] = {
+ {"IN", 1, RRClass::IN},
+ {"CH", 3, RRClass::CH},
+ {"NONE", 254, RRClass::NONE},
+ {"ANY", 255, RRClass::ANY},
+ {0, 0, 0}
+};
+
+TEST(RRClassConstTest, wellKnowns) {
+ for (size_t i = 0; well_known_classes[i].txt; ++i) {
+ SCOPED_TRACE("Checking well known RRClass: " +
+ string(well_known_classes[i].txt));
+ EXPECT_EQ(well_known_classes[i].code,
+ RRClass(well_known_classes[i].txt).getCode());
+ EXPECT_EQ(well_known_classes[i].code,
+ (*well_known_classes[i].obj)().getCode());
+ }
+}
+
+// Below, we'll check definitions for all registered RR classes.
+struct RegisteredClassParam {
+ const char* const txt; // "IN", "CH", etc
+ const uint16_t code; // 1, 3, etc
+} registered_classes[] = {
+ {"IN", 1},
+ {"CH", 3},
+ {"HS", 4},
+ {"NONE", 254},
+ {"ANY", 255},
+ {0, 0}
+};
+
+TEST(RRClassConstTest, registered) {
+ for (size_t i = 0; registered_classes[i].txt; ++i) {
+ SCOPED_TRACE("Checking registered RRClass: " +
+ string(registered_classes[i].txt));
+ uint16_t code = 0;
+ EXPECT_NO_THROW(RRParamRegistry::getRegistry().textToClassCode(registered_classes[i].txt, code));
+ EXPECT_EQ(code, registered_classes[i].code);
+ string txt;
+ EXPECT_NO_THROW(txt = RRParamRegistry::getRegistry().codeToClassText(registered_classes[i].code));
+ EXPECT_EQ(txt, registered_classes[i].txt);
+ }
+}
+
+}
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
new file mode 100644
index 0000000..b60106d
--- /dev/null
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+#include <sstream>
+
+#include <stdint.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/rrclass.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrtype.h>
+#include <dns/master_loader.h>
+
+#include <boost/scoped_ptr.hpp>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+namespace {
+void
+nullCallback(const std::string&, size_t, const std::string&) {
+}
+
+class RRParamRegistryTest : public ::testing::Test {
+protected:
+ RRParamRegistryTest()
+ {
+ ostringstream oss1;
+ oss1 << test_class_code;
+ // cppcheck-suppress useInitializationList
+ test_class_unknown_str = "CLASS" + oss1.str();
+
+ ostringstream oss2;
+ oss2 << test_type_code;
+ test_type_unknown_str = "TYPE" + oss2.str();
+ }
+ ~RRParamRegistryTest()
+ {
+ // cleanup any non well-known parameters that possibly remain
+ // as a side effect.
+ RRParamRegistry::getRegistry().removeType(test_type_code);
+ RRParamRegistry::getRegistry().removeClass(test_class_code);
+ RRParamRegistry::getRegistry().removeRdataFactory(
+ RRType(test_type_code), RRClass(test_class_code));
+ RRParamRegistry::getRegistry().removeRdataFactory(
+ RRType(test_type_code));
+ }
+
+ string test_class_unknown_str;
+ string test_type_unknown_str;
+
+ // we assume class/type numbers are officially unassigned. If not we'll
+ // need to update the test cases.
+ static const uint16_t test_class_code = 65533;
+ static const uint16_t test_type_code = 65534;
+ static const string test_class_str;
+ static const string test_type_str;
+};
+
+const string RRParamRegistryTest::test_class_str("TESTCLASS");
+const string RRParamRegistryTest::test_type_str("TESTTYPE");
+
+TEST_F(RRParamRegistryTest, addRemove) {
+ RRParamRegistry::getRegistry().addType(test_type_str, test_type_code);
+ RRParamRegistry::getRegistry().addClass(test_class_str, test_class_code);
+ EXPECT_EQ(65533, RRClass("TESTCLASS").getCode());
+ EXPECT_EQ(65534, RRType("TESTTYPE").getCode());
+
+ // the first removal attempt should succeed
+ EXPECT_TRUE(RRParamRegistry::getRegistry().removeType(test_type_code));
+ // then toText() should treat it as an "unknown"
+ EXPECT_EQ(test_type_unknown_str, RRType(test_type_code).toText());
+ // attempt of removing non-existent mapping should result in 'false'
+ EXPECT_FALSE(RRParamRegistry::getRegistry().removeType(test_type_code));
+
+ // same set of tests for RR class.
+ EXPECT_TRUE(RRParamRegistry::getRegistry().removeClass(test_class_code));
+ EXPECT_EQ(test_class_unknown_str, RRClass(test_class_code).toText());
+ EXPECT_FALSE(RRParamRegistry::getRegistry().removeClass(test_class_code));
+}
+
+TEST_F(RRParamRegistryTest, addError) {
+ // An attempt to override a pre-registered class should fail with an
+ // exception, and the pre-registered one should remain in the registry.
+ EXPECT_THROW(RRParamRegistry::getRegistry().addClass(test_class_str, 1),
+ RRClassExists);
+ EXPECT_EQ("IN", RRClass(1).toText());
+
+ // Same for RRType
+ EXPECT_THROW(RRParamRegistry::getRegistry().addType(test_type_str, 1),
+ RRTypeExists);
+ EXPECT_EQ("A", RRType(1).toText());
+}
+
+class TestRdataFactory : public AbstractRdataFactory {
+public:
+ 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
+ { return (RdataPtr(new in::A(buffer, rdata_len))); }
+ virtual RdataPtr create(const Rdata& source) const
+ { return (RdataPtr(new in::A(dynamic_cast<const in::A&>(source)))); }
+ virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) const
+ { return (RdataPtr(new in::A(lexer, origin, options, callbacks))); }
+};
+
+TEST_F(RRParamRegistryTest, addRemoveFactory) {
+ // By default, the test type/code pair should be considered "unknown",
+ // so the following should trigger an exception.
+ EXPECT_THROW(createRdata(RRType(test_type_code), RRClass(test_class_code),
+ "192.0.2.1"),
+ InvalidRdataText);
+ // Add factories so that we can treat this pair just like in::A.
+ RRParamRegistry::getRegistry().add(test_type_str, test_type_code,
+ test_class_str, test_class_code,
+ RdataFactoryPtr(new TestRdataFactory()));
+ // Now it should be accepted, and should be identical to the same data of
+ // in::A.
+ EXPECT_EQ(0, in::A("192.0.2.1").compare(
+ *createRdata(RRType(test_type_code), RRClass(test_class_code),
+ "192.0.2.1")));
+ // It should still fail with other classes as we specified the factories
+ // as class-specific.
+ EXPECT_THROW(createRdata(RRType(test_type_code), RRClass("IN"),
+ "192.0.2.1"),
+ InvalidRdataText);
+ // Add the factories also as a class independent RRtype
+ RRParamRegistry::getRegistry().add(test_type_str, test_type_code,
+ RdataFactoryPtr(new TestRdataFactory()));
+ // Now it should be okay for other classes than the test class.
+ EXPECT_EQ(0, in::A("192.0.2.1").compare(
+ *createRdata(RRType(test_type_code), RRClass("IN"),
+ "192.0.2.1")));
+
+ // Remove the added factories: first attempt should succeed; the second
+ // should return false as there's no match
+ EXPECT_TRUE(RRParamRegistry::getRegistry().removeRdataFactory(
+ RRType(test_type_code), RRClass(test_class_code)));
+ EXPECT_FALSE(RRParamRegistry::getRegistry().removeRdataFactory(
+ RRType(test_type_code), RRClass(test_class_code)));
+ EXPECT_TRUE(RRParamRegistry::getRegistry().removeRdataFactory(
+ RRType(test_type_code)));
+ EXPECT_FALSE(RRParamRegistry::getRegistry().removeRdataFactory(
+ RRType(test_type_code)));
+}
+
+RdataPtr
+createRdataHelper(const std::string& str) {
+ boost::scoped_ptr<AbstractRdataFactory> rdf(new TestRdataFactory());
+
+ std::stringstream ss(str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ MasterLoaderCallbacks callbacks(nullCallback, nullCallback);
+ 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/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
new file mode 100644
index 0000000..53cadec
--- /dev/null
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -0,0 +1,441 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+#include <stdexcept>
+#include <sstream>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class RRsetTest : public ::testing::Test {
+protected:
+ RRsetTest() : buffer(0),
+ test_name("test.example.com"),
+ test_domain("example.com"),
+ test_nsname("ns.example.com"),
+ rrset_a(test_name, RRClass::IN(), RRType::A(), RRTTL(3600)),
+ rrset_a_empty(test_name, RRClass::IN(), RRType::A(),
+ RRTTL(3600)),
+ rrset_any_a_empty(test_name, RRClass::ANY(), RRType::A(),
+ RRTTL(3600)),
+ rrset_none_a_empty(test_name, RRClass::NONE(), RRType::A(),
+ RRTTL(3600)),
+ rrset_ns(test_domain, RRClass::IN(), RRType::NS(),
+ RRTTL(86400)),
+ rrset_ch_txt(test_domain, RRClass::CH(), RRType::TXT(),
+ RRTTL(0)) {
+ rrset_a.addRdata(in::A("192.0.2.1"));
+ rrset_a.addRdata(in::A("192.0.2.2"));
+ }
+
+ OutputBuffer buffer;
+ MessageRenderer renderer;
+ Name test_name;
+ Name test_domain;
+ Name test_nsname;
+ RRset rrset_a;
+ RRset rrset_a_empty;
+ RRset rrset_any_a_empty;
+ RRset rrset_none_a_empty;
+ RRset rrset_ns;
+ RRset rrset_ch_txt;
+ std::vector<unsigned char> wiredata;
+
+ // max number of Rdata objects added to a test RRset object.
+ // this is an arbitrary chosen limit, but should be sufficiently large
+ // in practice and reasonable even as an extreme test case.
+ static const int MAX_RDATA_COUNT = 100;
+};
+
+TEST_F(RRsetTest, getRdataCount) {
+ for (int i = 0; i < MAX_RDATA_COUNT; ++i) {
+ EXPECT_EQ(i, rrset_a_empty.getRdataCount());
+ rrset_a_empty.addRdata(in::A("192.0.2.1"));
+ }
+}
+
+TEST_F(RRsetTest, getName) {
+ EXPECT_EQ(test_name, rrset_a.getName());
+ EXPECT_EQ(test_domain, rrset_ns.getName());
+}
+
+TEST_F(RRsetTest, getClass) {
+ EXPECT_EQ(RRClass("IN"), rrset_a.getClass());
+ EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass());
+}
+
+TEST_F(RRsetTest, getType) {
+ EXPECT_EQ(RRType("A"), rrset_a.getType());
+ EXPECT_EQ(RRType("NS"), rrset_ns.getType());
+ EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType());
+}
+
+TEST_F(RRsetTest, getTTL) {
+ EXPECT_EQ(RRTTL(3600), rrset_a.getTTL());
+ EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL());
+ EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL());
+}
+
+TEST_F(RRsetTest, setTTL) {
+ rrset_a.setTTL(RRTTL(86400));
+ EXPECT_EQ(RRTTL(86400), rrset_a.getTTL());
+ rrset_a.setTTL(RRTTL(0));
+ EXPECT_EQ(RRTTL(0), rrset_a.getTTL());
+}
+
+TEST_F(RRsetTest, isSameKind) {
+ RRset rrset_w(test_name, RRClass::IN(), RRType::A(), RRTTL(3600));
+ RRset rrset_x(test_name, RRClass::IN(), RRType::A(), RRTTL(3600));
+ RRset rrset_y(test_name, RRClass::IN(), RRType::NS(), RRTTL(3600));
+ RRset rrset_z(test_name, RRClass::CH(), RRType::A(), RRTTL(3600));
+ RRset rrset_p(test_nsname, RRClass::IN(), RRType::A(), RRTTL(3600));
+
+ EXPECT_TRUE(rrset_w.isSameKind(rrset_w));
+ EXPECT_TRUE(rrset_w.isSameKind(rrset_x));
+ EXPECT_FALSE(rrset_w.isSameKind(rrset_y));
+ EXPECT_FALSE(rrset_w.isSameKind(rrset_z));
+ EXPECT_FALSE(rrset_w.isSameKind(rrset_p));
+}
+
+void
+addRdataTestCommon(const RRset& rrset) {
+ ASSERT_EQ(2, rrset.getRdataCount());
+
+ RdataIteratorPtr it = rrset.getRdataIterator(); // cursor is set to the 1st
+ EXPECT_FALSE(it->isLast());
+ EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
+ it->next();
+ EXPECT_FALSE(it->isLast());
+ EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2")));
+ it->next();
+ EXPECT_TRUE(it->isLast());
+}
+
+TEST_F(RRsetTest, addRdata) {
+ addRdataTestCommon(rrset_a);
+
+ // Reference version of addRdata() doesn't allow to add a different
+ // type of Rdata.
+ EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), std::bad_cast);
+}
+
+TEST_F(RRsetTest, addRdataPtr) {
+ rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
+ rrset_a_empty.getClass(),
+ "192.0.2.1"));
+ rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
+ rrset_a_empty.getClass(),
+ "192.0.2.2"));
+ addRdataTestCommon(rrset_a_empty);
+}
+
+TEST_F(RRsetTest, addRdataPtrMismatched) {
+ // Pointer version of addRdata() doesn't type check and does allow to
+ //add a different type of Rdata as a result.
+
+ // Type mismatch
+ rrset_a_empty.addRdata(createRdata(RRType::NS(), RRClass::IN(),
+ "ns.example.com."));
+ EXPECT_EQ(1, rrset_a_empty.getRdataCount());
+
+ // Class mismatch
+ rrset_ch_txt.addRdata(createRdata(RRType::TXT(), RRClass::IN(),
+ "Test String"));
+ EXPECT_EQ(1, rrset_ch_txt.getRdataCount());
+}
+
+TEST_F(RRsetTest, addRdataString) {
+ rrset_a_empty.addRdata("192.0.2.1");
+ rrset_a_empty.addRdata("192.0.2.2");
+
+ addRdataTestCommon(rrset_a_empty);
+
+ // String version of addRdata() will throw for bad RDATA for
+ // RRType::A().
+ EXPECT_THROW(rrset_a_empty.addRdata("ns.example.com."), InvalidRdataText);
+ addRdataTestCommon(rrset_a_empty);
+}
+
+TEST_F(RRsetTest, iterator) {
+ // Iterator for an empty RRset.
+ RdataIteratorPtr it = rrset_a_empty.getRdataIterator();
+ EXPECT_TRUE(it->isLast());
+
+ // Normal case (already tested, but do it again just in case)
+ rrset_a_empty.addRdata(in::A("192.0.2.1"));
+ rrset_a_empty.addRdata(in::A("192.0.2.2"));
+ addRdataTestCommon(rrset_a_empty);
+
+ // Rewind test: should be repeat the iteration by calling first().
+ for (int i = 0; i < 2; ++i) {
+ it = rrset_a_empty.getRdataIterator();
+ it->first();
+ EXPECT_FALSE(it->isLast());
+ it->next();
+ EXPECT_FALSE(it->isLast());
+ it->next();
+ EXPECT_TRUE(it->isLast());
+ }
+}
+
+TEST_F(RRsetTest, toText) {
+ EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n"
+ "test.example.com. 3600 IN A 192.0.2.2\n",
+ rrset_a.toText());
+
+ // toText() cannot be performed for an empty RRset
+ EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset);
+
+ // Unless it is type ANY or NONE
+ EXPECT_EQ("test.example.com. 3600 ANY A\n",
+ rrset_any_a_empty.toText());
+ EXPECT_EQ("test.example.com. 3600 NONE A\n",
+ rrset_none_a_empty.toText());
+}
+
+TEST_F(RRsetTest, getLength) {
+ // Empty RRset should throw
+ EXPECT_THROW(rrset_a_empty.getLength(), EmptyRRset);
+
+ // Unless it is type ANY or NONE:
+ // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // Total = 18 + 2 + 2 + 4 + 2 = 28 octets
+ EXPECT_EQ(28, rrset_any_a_empty.getLength());
+ EXPECT_EQ(28, rrset_none_a_empty.getLength());
+
+ // RRset with single RDATA
+ // 28 (above) + 4 octets (A RDATA) = 32 octets
+ rrset_a_empty.addRdata(in::A("192.0.2.1"));
+ EXPECT_EQ(32, rrset_a_empty.getLength());
+
+ // 2 A RRs
+ rrset_a_empty.addRdata(in::A("192.0.2.2"));
+ EXPECT_EQ(32 + 32, rrset_a_empty.getLength());
+}
+
+TEST_F(RRsetTest, toWireBuffer) {
+ rrset_a.toWire(buffer);
+
+ UnitTestUtil::readWireData("rrset_toWire1", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ buffer.getData(), buffer.getLength());
+
+ // toWire() cannot be performed for an empty RRset except when
+ // class=ANY or class=NONE.
+ buffer.clear();
+ EXPECT_THROW(rrset_a_empty.toWire(buffer), EmptyRRset);
+
+ // When class=ANY or class=NONE, toWire() can also be performed for
+ // an empty RRset.
+ buffer.clear();
+ rrset_any_a_empty.toWire(buffer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire3", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ buffer.getData(), buffer.getLength());
+
+ buffer.clear();
+ rrset_none_a_empty.toWire(buffer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire4", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ buffer.getData(), buffer.getLength());
+}
+
+TEST_F(RRsetTest, toWireRenderer) {
+ rrset_ns.addRdata(generic::NS(test_nsname));
+
+ rrset_a.toWire(renderer);
+ rrset_ns.toWire(renderer);
+
+ UnitTestUtil::readWireData("rrset_toWire2", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ // toWire() cannot be performed for an empty RRset except when
+ // class=ANY or class=NONE.
+ renderer.clear();
+ EXPECT_THROW(rrset_a_empty.toWire(renderer), EmptyRRset);
+
+ // When class=ANY or class=NONE, toWire() can also be performed for
+ // an empty RRset.
+ renderer.clear();
+ rrset_any_a_empty.toWire(renderer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire3", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+
+ renderer.clear();
+ rrset_none_a_empty.toWire(renderer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire4", wiredata);
+ matchWireData(&wiredata[0], wiredata.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(RRsetTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << rrset_a;
+ EXPECT_EQ(rrset_a.toText(), oss.str());
+}
+
+class RRsetRRSIGTest : public ::testing::Test {
+protected:
+ RRsetRRSIGTest() : test_name("test.example.com")
+ {
+ rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::A(), RRTTL(3600)));
+ rrset_a->addRdata(in::A("192.0.2.1"));
+ rrset_a->addRdata(in::A("192.0.2.2"));
+
+ rrset_aaaa = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::AAAA(), RRTTL(3600)));
+ rrset_aaaa->addRdata(in::AAAA("2001:db8::1234"));
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
+ "20100220084538 1 example.com. "
+ "FAKEFAKEFAKEFAKE"));
+ rrset_aaaa->addRRsig(rrset_rrsig);
+ }
+
+ const Name test_name;
+ RRsetPtr rrset_a; // A RRset with two RDATAs
+ RRsetPtr rrset_aaaa; // AAAA RRset with one RDATA with RRSIG
+ RRsetPtr rrset_rrsig; // RRSIG for the AAAA RRset
+};
+
+TEST_F(RRsetRRSIGTest, getRRsig) {
+ RRsetPtr sp = rrset_a->getRRsig();
+ EXPECT_FALSE(sp);
+
+ sp = rrset_aaaa->getRRsig();
+ EXPECT_TRUE(sp);
+}
+
+TEST_F(RRsetRRSIGTest, addRRsig) {
+ RRsetPtr sp = rrset_a->getRRsig();
+ EXPECT_FALSE(sp);
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ // one signature algorithm (5 = RSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("A 5 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ // another signature algorithm (3 = DSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("A 3 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ rrset_a->addRRsig(rrset_rrsig);
+
+ sp = rrset_a->getRRsig();
+ EXPECT_TRUE(sp);
+ EXPECT_EQ(2, sp->getRdataCount());
+
+ // add to existing RRSIG
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ // another signature algorithm (4 = ECC)
+ rrset_rrsig->addRdata(generic::RRSIG("A 4 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ rrset_a->addRRsig(rrset_rrsig);
+ EXPECT_EQ(3, sp->getRdataCount());
+}
+
+TEST_F(RRsetRRSIGTest, getRRsigDataCount) {
+ EXPECT_EQ(1, rrset_aaaa->getRRsigDataCount());
+ EXPECT_EQ(0, rrset_a->getRRsigDataCount());
+
+ rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ // one signature algorithm (5 = RSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("A 5 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ // another signature algorithm (3 = DSA/SHA-1)
+ rrset_rrsig->addRdata(generic::RRSIG("A 3 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ rrset_a->addRRsig(rrset_rrsig);
+ EXPECT_EQ(2, rrset_a->getRRsigDataCount());
+
+ rrset_a->removeRRsig();
+ EXPECT_EQ(0, rrset_a->getRRsigDataCount());
+}
+
+TEST_F(RRsetRRSIGTest, toText) {
+ // toText() should also return the associated RRSIG.
+ EXPECT_EQ("test.example.com. 3600 IN AAAA 2001:db8::1234\n"
+ "test.example.com. 3600 IN RRSIG AAAA 5 3 7200 "
+ "20100322084538 20100220084538 1 example.com. FAKEFAKEFAKEFAKE\n",
+ rrset_aaaa->toText());
+}
+
+TEST_F(RRsetRRSIGTest, getLength) {
+ // A RR
+ // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // A RDATA = 4 octets
+ // Total = 18 + 2 + 2 + 4 + 2 + 4 = 32 octets
+
+ // 2 A RRs
+ EXPECT_EQ(32 + 32, rrset_a->getLength());
+
+ // RRSIG
+ // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // RRSIG RDATA = 40 octets
+ // Total = 18 + 2 + 2 + 4 + 2 + 40 = 68 octets
+ RRsetPtr my_rrsig(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ my_rrsig->addRdata(generic::RRSIG("A 4 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ EXPECT_EQ(68, my_rrsig->getLength());
+
+ // RRset with attached RRSIG
+ rrset_a->addRRsig(my_rrsig);
+
+ EXPECT_EQ(32 + 32 + 68, rrset_a->getLength());
+}
+}
diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc
new file mode 100644
index 0000000..49e48fc
--- /dev/null
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -0,0 +1,279 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrttl.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+#include <boost/scoped_ptr.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using boost::scoped_ptr;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class RRTTLTest : public ::testing::Test {
+protected:
+ RRTTLTest() : obuffer(0) {}
+
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+
+ static RRTTL rrttlFactoryFromWire(const char* datafile);
+ static const RRTTL ttl_0, ttl_1h, ttl_1d, ttl_32bit, ttl_max;
+ static const RRTTL ttl_small, ttl_large;
+ static const uint8_t wiredata[20];
+};
+
+const RRTTL RRTTLTest::ttl_0(0);
+const RRTTL RRTTLTest::ttl_1h(3600);
+const RRTTL RRTTLTest::ttl_1d(86400);
+const RRTTL RRTTLTest::ttl_32bit(0x12345678);
+const RRTTL RRTTLTest::ttl_max(0xffffffff);
+
+const RRTTL RRTTLTest::ttl_small(1);
+const RRTTL RRTTLTest::ttl_large(0x80000001);
+// This is wire-format data for the above sample RRTTLs rendered in the
+// appearing order.
+const uint8_t RRTTLTest::wiredata[20] = { 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0e, 0x10,
+ 0x00, 0x01, 0x51, 0x80,
+ 0x12, 0x34, 0x56, 0x78,
+ 0xff, 0xff, 0xff, 0xff };
+
+RRTTL
+RRTTLTest::rrttlFactoryFromWire(const char* datafile) {
+ std::vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+
+ return (RRTTL(buffer));
+}
+
+TEST_F(RRTTLTest, getValue) {
+ EXPECT_EQ(0, ttl_0.getValue());
+ EXPECT_EQ(3600, ttl_1h.getValue());
+ EXPECT_EQ(86400, ttl_1d.getValue());
+ EXPECT_EQ(0x12345678, ttl_32bit.getValue());
+ EXPECT_EQ(0xffffffff, ttl_max.getValue());
+}
+
+TEST_F(RRTTLTest, copyConstruct) {
+ const RRTTL ttl1(3600);
+ const RRTTL ttl2(ttl1);
+ EXPECT_EQ(ttl1.getValue(), ttl2.getValue());
+}
+
+TEST_F(RRTTLTest, fromText) {
+ // Border cases
+ EXPECT_EQ(0, RRTTL("0").getValue());
+ EXPECT_EQ(4294967295U, RRTTL("4294967295").getValue());
+
+ // Invalid cases
+ EXPECT_THROW(RRTTL("0xdeadbeef"), InvalidRRTTL); // must be decimal
+ EXPECT_THROW(RRTTL("-1"), InvalidRRTTL); // must be positive
+ EXPECT_THROW(RRTTL("1.1"), InvalidRRTTL); // must be integer
+ EXPECT_THROW(RRTTL("4294967296"), InvalidRRTTL); // must be 32-bit
+}
+
+TEST_F(RRTTLTest, createFromText) {
+ // It returns an actual RRTTL iff the given text is recognized as a
+ // valid RR TTL.
+ scoped_ptr<RRTTL> good_ttl(RRTTL::createFromText("3600"));
+ EXPECT_TRUE(good_ttl);
+ EXPECT_EQ(RRTTL(3600), *good_ttl);
+
+ scoped_ptr<RRTTL> bad_ttl(RRTTL::createFromText("bad"));
+ EXPECT_FALSE(bad_ttl);
+}
+
+void
+checkUnit(unsigned multiply, char suffix) {
+ SCOPED_TRACE(string("Unit check with suffix ") + suffix);
+ const uint32_t value = 10 * multiply;
+ const string num = "10";
+ // Check both lower and upper version of the suffix
+ EXPECT_EQ(value,
+ RRTTL(num + static_cast<char>(tolower(suffix))).getValue());
+ EXPECT_EQ(value,
+ RRTTL(num + static_cast<char>(toupper(suffix))).getValue());
+}
+
+// Check parsing the unit form (1D, etc)
+TEST_F(RRTTLTest, fromTextUnit) {
+ // Check each of the units separately
+ checkUnit(1, 'S');
+ checkUnit(60, 'M');
+ checkUnit(60 * 60, 'H');
+ checkUnit(24 * 60 * 60, 'D');
+ checkUnit(7 * 24 * 60 * 60, 'W');
+
+ // Some border cases (with units)
+ EXPECT_EQ(4294967295U, RRTTL("4294967295S").getValue());
+ EXPECT_EQ(0, RRTTL("0W0D0H0M0S").getValue());
+ EXPECT_EQ(4294967295U, RRTTL("1193046H1695S").getValue());
+ // Leading zeroes are accepted
+ EXPECT_EQ(4294967295U, RRTTL("0000000000000004294967295S").getValue());
+
+ // Now some compound ones. We allow any order (it would be much work to
+ // check the order anyway).
+ EXPECT_EQ(60 * 60 + 3, RRTTL("1H3S").getValue());
+
+ // Awkward, but allowed case - the same unit used twice.
+ EXPECT_EQ(20 * 3600, RRTTL("12H8H").getValue());
+
+ // Negative number in part of the expression, but the total is positive.
+ // Rejected.
+ EXPECT_THROW(RRTTL("-1S1H"), InvalidRRTTL);
+
+ // Some things out of range in the ttl, but it wraps to number in range
+ // in int64_t. Should still not get fooled and reject it.
+
+ // First part out of range
+ EXPECT_THROW(RRTTL("9223372036854775807S9223372036854775807S2S"),
+ InvalidRRTTL);
+ // Second part out of range, but it immediately wraps (2S+2^64-2S)
+ EXPECT_THROW(RRTTL("2S18446744073709551614S"), InvalidRRTTL);
+ // The whole thing wraps right away (2^64S)
+ EXPECT_THROW(RRTTL("18446744073709551616S"), InvalidRRTTL);
+ // Second part out of range, and will become negative with the unit,
+ EXPECT_THROW(RRTTL("256S307445734561825856M"), InvalidRRTTL);
+
+ // Missing before unit.
+ EXPECT_THROW(RRTTL("W5H"), InvalidRRTTL);
+ EXPECT_THROW(RRTTL("5hW"), InvalidRRTTL);
+
+ // Empty string is not allowed
+ EXPECT_THROW(RRTTL(""), InvalidRRTTL);
+ // Missing the last unit is not allowed
+ EXPECT_THROW(RRTTL("3D5"), InvalidRRTTL);
+
+ // There are some wrong units
+ EXPECT_THROW(RRTTL("13X"), InvalidRRTTL);
+ EXPECT_THROW(RRTTL("3D5F"), InvalidRRTTL);
+}
+
+TEST_F(RRTTLTest, fromWire) {
+ EXPECT_EQ(0x12345678,
+ rrttlFactoryFromWire("rrcode32_fromWire1").getValue());
+ EXPECT_THROW(rrttlFactoryFromWire("rrcode32_fromWire2"),
+ IncompleteRRTTL);
+}
+
+TEST_F(RRTTLTest, toText) {
+ EXPECT_EQ("0", ttl_0.toText());
+ EXPECT_EQ("3600", ttl_1h.toText());
+ EXPECT_EQ("86400", ttl_1d.toText());
+ EXPECT_EQ("305419896", ttl_32bit.toText());
+ EXPECT_EQ("4294967295", ttl_max.toText());
+}
+
+TEST_F(RRTTLTest, toWireBuffer) {
+ ttl_0.toWire(obuffer);
+ ttl_1h.toWire(obuffer);
+ ttl_1d.toWire(obuffer);
+ ttl_32bit.toWire(obuffer);
+ ttl_max.toWire(obuffer);
+
+ matchWireData(wiredata, sizeof(wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(RRTTLTest, toWireRenderer) {
+ ttl_0.toWire(renderer);
+ ttl_1h.toWire(renderer);
+ ttl_1d.toWire(renderer);
+ ttl_32bit.toWire(renderer);
+ ttl_max.toWire(renderer);
+
+ matchWireData(wiredata, sizeof(wiredata),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RRTTLTest, equal) {
+ EXPECT_TRUE(RRTTL("3600") == ttl_1h);
+ EXPECT_TRUE(RRTTL("86400").equals(ttl_1d));
+
+ EXPECT_TRUE(ttl_1d != ttl_1h);
+ EXPECT_TRUE(ttl_1d.nequals(ttl_max));
+}
+
+//
+// The following set of tests confirm the result of <=, <, >=, >
+// The test logic is simple, and all tests are just straightforward variations
+// of the first one.
+//
+TEST_F(RRTTLTest, leq) {
+ // small <= large is true
+ EXPECT_TRUE(ttl_small.leq(ttl_large));
+ EXPECT_TRUE(ttl_small <= ttl_large);
+
+ // small <= small is true
+ EXPECT_TRUE(ttl_small.leq(ttl_small));
+ EXPECT_LE(ttl_small, ttl_small);
+
+ // large <= small is false
+ EXPECT_FALSE(ttl_large.leq(ttl_small));
+ EXPECT_FALSE(ttl_large <= ttl_small);
+}
+
+TEST_F(RRTTLTest, geq) {
+ EXPECT_TRUE(ttl_large.geq(ttl_small));
+ EXPECT_TRUE(ttl_large >= ttl_small);
+
+ EXPECT_TRUE(ttl_large.geq(ttl_large));
+ EXPECT_GE(ttl_large, ttl_large);
+
+ EXPECT_FALSE(ttl_small.geq(ttl_large));
+ EXPECT_FALSE(ttl_small >= ttl_large);
+}
+
+TEST_F(RRTTLTest, lthan) {
+ EXPECT_TRUE(ttl_small.lthan(ttl_large));
+ EXPECT_TRUE(ttl_small < ttl_large);
+
+ EXPECT_FALSE(ttl_small.lthan(ttl_small));
+ // cppcheck-suppress duplicateExpression
+ EXPECT_FALSE(ttl_small < ttl_small);
+
+ EXPECT_FALSE(ttl_large.lthan(ttl_small));
+ EXPECT_FALSE(ttl_large < ttl_small);
+}
+
+TEST_F(RRTTLTest, gthan) {
+ EXPECT_TRUE(ttl_large.gthan(ttl_small));
+ EXPECT_TRUE(ttl_large > ttl_small);
+
+ EXPECT_FALSE(ttl_large.gthan(ttl_large));
+ // cppcheck-suppress duplicateExpression
+ EXPECT_FALSE(ttl_large > ttl_large);
+
+ EXPECT_FALSE(ttl_small.gthan(ttl_large));
+ EXPECT_FALSE(ttl_small > ttl_large);
+}
+
+TEST_F(RRTTLTest, maxTTL) {
+ EXPECT_EQ((1u << 31) - 1, RRTTL::MAX_TTL().getValue());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(RRTTLTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << ttl_1h;
+ EXPECT_EQ(ttl_1h.toText(), oss.str());
+}
+}
diff --git a/src/lib/dns/tests/rrtype_unittest.cc b/src/lib/dns/tests/rrtype_unittest.cc
new file mode 100644
index 0000000..d874ebc
--- /dev/null
+++ b/src/lib/dns/tests/rrtype_unittest.cc
@@ -0,0 +1,290 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrtype.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class RRTypeTest : public ::testing::Test {
+protected:
+ RRTypeTest() : obuffer(0) {}
+
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+
+ static RRType rrtypeFactoryFromWire(const char* datafile);
+ static const RRType rrtype_1, rrtype_0x80, rrtype_0x800, rrtype_0x8000,
+ rrtype_max;
+ static const uint8_t wiredata[];
+};
+
+const RRType RRTypeTest::rrtype_1(1);
+const RRType RRTypeTest::rrtype_0x80(0x80);
+const RRType RRTypeTest::rrtype_0x800(0x800);
+const RRType RRTypeTest::rrtype_0x8000(0x8000);
+const RRType RRTypeTest::rrtype_max(0xffff);
+// This is wire-format data for the above sample RRTypes rendered in the
+// appearing order.
+const uint8_t RRTypeTest::wiredata[] = { 0x00, 0x01, 0x00, 0x80, 0x08,
+ 0x00, 0x80, 0x00, 0xff, 0xff };
+
+RRType
+RRTypeTest::rrtypeFactoryFromWire(const char* datafile) {
+ std::vector<unsigned char> data;
+ UnitTestUtil::readWireData(datafile, data);
+
+ InputBuffer buffer(&data[0], data.size());
+
+ return (RRType(buffer));
+}
+
+TEST_F(RRTypeTest, fromText) {
+ EXPECT_EQ("A", RRType("A").toText());
+ EXPECT_EQ("NS", RRType("NS").toText());
+
+ EXPECT_EQ("TYPE65535", RRType("TYPE65535").toText());
+
+ // something unusual, but existing implementations accept this form,
+ // so do we.
+ EXPECT_EQ(53, RRType("TYPE00053").getCode());
+ // again, unusual, and the majority of other implementations reject it.
+ // In any case, there should be no reasonable reason to accept such a
+ // ridiculously long input.
+ EXPECT_THROW(RRType("TYPE000053"), InvalidRRType);
+
+ // bogus TYPEnnn representations: should trigger an exception
+ EXPECT_THROW(RRType("TYPE"), InvalidRRType);
+ EXPECT_THROW(RRType("TYPE-1"), InvalidRRType);
+ EXPECT_THROW(RRType("TYPExxx"), InvalidRRType);
+ EXPECT_THROW(RRType("TYPE65536"), InvalidRRType);
+ EXPECT_THROW(RRType("TYPE6500x"), InvalidRRType);
+ EXPECT_THROW(RRType("TYPE65000 "), InvalidRRType);
+}
+
+TEST_F(RRTypeTest, fromWire) {
+ EXPECT_EQ(0x1234,
+ rrtypeFactoryFromWire("rrcode16_fromWire1").getCode());
+ EXPECT_THROW(rrtypeFactoryFromWire("rrcode16_fromWire2"), IncompleteRRType);
+}
+
+// from string, lower case
+TEST_F(RRTypeTest, caseConstruct) {
+ EXPECT_EQ("A", RRType("a").toText());
+ EXPECT_EQ("NS", RRType("ns").toText());
+ EXPECT_EQ("TYPE65535", RRType("type65535").toText());
+}
+
+TEST_F(RRTypeTest, toText) {
+ EXPECT_EQ("A", RRType(1).toText());
+ EXPECT_EQ("TYPE65000", RRType(65000).toText());
+}
+
+TEST_F(RRTypeTest, toWireBuffer) {
+ rrtype_1.toWire(obuffer);
+ rrtype_0x80.toWire(obuffer);
+ rrtype_0x800.toWire(obuffer);
+ rrtype_0x8000.toWire(obuffer);
+ rrtype_max.toWire(obuffer);
+
+ matchWireData(wiredata, sizeof(wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(RRTypeTest, toWireRenderer) {
+ rrtype_1.toWire(renderer);
+ rrtype_0x80.toWire(renderer);
+ rrtype_0x800.toWire(renderer);
+ rrtype_0x8000.toWire(renderer);
+ rrtype_max.toWire(renderer);
+
+ matchWireData(wiredata, sizeof(wiredata),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RRTypeTest, wellKnownTypes) {
+ EXPECT_EQ(1, RRType::A().getCode());
+ EXPECT_EQ("A", RRType::A().toText());
+}
+
+TEST_F(RRTypeTest, compare) {
+ EXPECT_TRUE(RRType(1) == RRType("A"));
+ EXPECT_TRUE(RRType(1).equals(RRType("A")));
+ EXPECT_TRUE(RRType(0) != RRType("A"));
+ EXPECT_TRUE(RRType(0).nequals(RRType("A")));
+
+ EXPECT_TRUE(RRType("A") < RRType("NS"));
+ EXPECT_TRUE(RRType(100) < RRType(65535));
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(RRTypeTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << RRType::A();
+ EXPECT_EQ(RRType::A().toText(), oss.str());
+}
+
+// Below, we'll check definitions for all well-known RR types; whether they
+// are defined and have the correct parameter values. Test data are generated
+// from the list available at:
+// http://www.iana.org/assignments/dns-parameters/dns-parameters.xml
+struct WellKnownTypeParam {
+ const char* const txt; // "A", "AAAA", "NS", etc
+ const uint16_t code; // 1, 28, 2, etc
+ const RRType& (*obj)(); // RRType::A(), etc
+} well_known_types[] = {
+ {"A", 1, RRType::A},
+ {"NS", 2, RRType::NS},
+ {"SOA", 6, RRType::SOA},
+ {"PTR", 12, RRType::PTR},
+ {"TXT", 16, RRType::TXT},
+ {"AAAA", 28, RRType::AAAA},
+ {"OPT", 41, RRType::OPT},
+ {"RRSIG", 46, RRType::RRSIG},
+ {"DHCID", 49, RRType::DHCID},
+ {"TKEY", 249, RRType::TKEY},
+ {"TSIG", 250, RRType::TSIG},
+ {"ANY", 255, RRType::ANY},
+ {0, 0, 0}
+};
+
+TEST(RRTypeConstTest, wellKnowns) {
+ for (size_t i = 0; well_known_types[i].txt; ++i) {
+ SCOPED_TRACE("Checking well known RRType: " +
+ string(well_known_types[i].txt));
+ EXPECT_EQ(well_known_types[i].code,
+ RRType(well_known_types[i].txt).getCode());
+ EXPECT_EQ(well_known_types[i].code,
+ (*well_known_types[i].obj)().getCode());
+ }
+}
+
+// Below, we'll check definitions for all registered RR types.
+struct RegisteredTypeParam {
+ const char* const txt; // "A", "AAAA", "NS", etc
+ const uint16_t code; // 1, 28, 2, etc
+} registered_types[] = {
+ {"A", 1},
+ {"NS", 2},
+ {"MD", 3},
+ {"MF", 4},
+ {"CNAME", 5},
+ {"SOA", 6},
+ {"MB", 7},
+ {"MG", 8},
+ {"MR", 9},
+ {"NULL", 10},
+ {"WKS", 11},
+ {"PTR", 12},
+ {"HINFO", 13},
+ {"MINFO", 14},
+ {"MX", 15},
+ {"TXT", 16},
+ {"RP", 17},
+ {"AFSDB", 18},
+ {"X25", 19},
+ {"ISDN", 20},
+ {"RT", 21},
+ {"NSAP", 22},
+ {"NSAP-PTR", 23},
+ {"SIG", 24},
+ {"KEY", 25},
+ {"PX", 26},
+ {"GPOS", 27},
+ {"AAAA", 28},
+ {"LOC", 29},
+ {"NXT", 30},
+ {"EID", 31},
+ {"NIMLOC", 32},
+ {"SRV", 33},
+ {"ATMA", 34},
+ {"NAPTR", 35},
+ {"KX", 36},
+ {"CERT", 37},
+ {"A6", 38},
+ {"DNAME", 39},
+ {"SINK", 40},
+ {"OPT", 41},
+ {"APL", 42},
+ {"DS", 43},
+ {"SSHFP", 44},
+ {"IPSECKEY", 45},
+ {"RRSIG", 46},
+ {"NSEC", 47},
+ {"DNSKEY", 48},
+ {"DHCID", 49},
+ {"NSEC3", 50},
+ {"NSEC3PARAM", 51},
+ {"TLSA", 52},
+ {"SMIMEA", 53},
+ {"HIP", 55},
+ {"NINFO", 56},
+ {"RKEY", 57},
+ {"TALINK", 58},
+ {"CDS", 59},
+ {"CDNSKEY", 60},
+ {"OPENPGPKEY", 61},
+ {"CSYNC", 62},
+ {"ZONEMD", 63},
+ {"SVCB", 64},
+ {"HTTPS", 65},
+ {"SPF", 99},
+ {"UINFO", 100},
+ {"UID", 101},
+ {"GID", 102},
+ {"UNSPEC", 103},
+ {"NID", 104},
+ {"L32", 105},
+ {"L64", 106},
+ {"LP", 107},
+ {"EUI48", 108},
+ {"EUI64", 109},
+ {"TKEY", 249},
+ {"TSIG", 250},
+ {"IXFR", 251},
+ {"AXFR", 252},
+ {"MAILB", 253},
+ {"MAILA", 254},
+ {"ANY", 255},
+ {"URI", 256},
+ {"CAA", 257},
+ {"AVC", 258},
+ {"DOA", 259},
+ {"AMTRELAY", 260},
+ {"RESINFO", 261},
+ {"TA", 32768},
+ {"DLV", 32769},
+ {0, 0}
+};
+
+TEST(RRTypeConstTest, registered) {
+ for (size_t i = 0; registered_types[i].txt; ++i) {
+ SCOPED_TRACE("Checking registered RRType: " +
+ string(registered_types[i].txt));
+ uint16_t code = 0;
+ EXPECT_NO_THROW(RRParamRegistry::getRegistry().textToTypeCode(registered_types[i].txt, code));
+ EXPECT_EQ(code, registered_types[i].code);
+ string txt;
+ EXPECT_NO_THROW(txt = RRParamRegistry::getRegistry().codeToTypeText(registered_types[i].code));
+ EXPECT_EQ(txt, registered_types[i].txt);
+ }
+}
+
+}
diff --git a/src/lib/dns/tests/run_unittests.cc b/src/lib/dns/tests/run_unittests.cc
new file mode 100644
index 0000000..de396fa
--- /dev/null
+++ b/src/lib/dns/tests/run_unittests.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+#include <util/unittests/testdata.h>
+#include <dns/tests/unittest_util.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+ isc::util::unittests::addTestDataPath(TEST_DATA_SRCDIR);
+ isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+ isc::util::unittests::addTestDataPath(TEST_DATA_BUILDDIR);
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/dns/tests/serial_unittest.cc b/src/lib/dns/tests/serial_unittest.cc
new file mode 100644
index 0000000..07cd142
--- /dev/null
+++ b/src/lib/dns/tests/serial_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/serial.h>
+
+using namespace isc::dns;
+
+class SerialTest : public ::testing::Test {
+public:
+ SerialTest() : one(1), one_2(1), two(2),
+ date_zero(1980120100), date_one(1980120101),
+ min(0), max(4294967295u),
+ number_low(12345),
+ number_medium(2000000000),
+ number_high(4000000000u)
+ {}
+ Serial one, one_2, two, date_zero, date_one, min, max, number_low, number_medium, number_high;
+};
+
+//
+// Basic tests
+//
+
+TEST_F(SerialTest, get_value) {
+ EXPECT_EQ(1, one.getValue());
+ EXPECT_NE(2, one.getValue());
+ EXPECT_EQ(2, two.getValue());
+ EXPECT_EQ(1980120100, date_zero.getValue());
+ EXPECT_EQ(1980120101, date_one.getValue());
+ EXPECT_EQ(0, min.getValue());
+ EXPECT_EQ(4294967295u, max.getValue());
+ EXPECT_EQ(12345, number_low.getValue());
+ EXPECT_EQ(2000000000, number_medium.getValue());
+ EXPECT_EQ(4000000000u, number_high.getValue());
+}
+
+TEST_F(SerialTest, equals) {
+ EXPECT_EQ(one, one);
+ EXPECT_EQ(one, one_2);
+ EXPECT_NE(one, two);
+ EXPECT_NE(two, one);
+ EXPECT_EQ(Serial(12345), number_low);
+ EXPECT_NE(Serial(12346), number_low);
+}
+
+TEST_F(SerialTest, comparison) {
+ // These should be true/false even without serial arithmetic
+ EXPECT_LE(one, one);
+ EXPECT_LE(one, one_2);
+ EXPECT_LT(one, two);
+ EXPECT_LE(one, two);
+ EXPECT_GE(two, two);
+ EXPECT_GT(two, one);
+ EXPECT_GE(two, one);
+ EXPECT_LT(one, number_low);
+ EXPECT_LT(number_low, number_medium);
+ EXPECT_LT(number_medium, number_high);
+
+ // now let's try some that 'wrap', as it were
+ EXPECT_GT(min, max);
+ EXPECT_LT(max, min);
+ EXPECT_LT(number_high, number_low);
+}
+
+//
+// RFC 1982 Section 3.1
+//
+TEST_F(SerialTest, addition) {
+ EXPECT_EQ(two, one + one);
+ EXPECT_EQ(two, one + one_2);
+ EXPECT_EQ(max, max + min);
+ EXPECT_EQ(min, max + one);
+ EXPECT_EQ(one, max + two);
+ EXPECT_EQ(one, max + one + one);
+
+ EXPECT_EQ(one + 100, max + 102);
+ EXPECT_EQ(min + 2147483645, max + 2147483646);
+ EXPECT_EQ(min + 2147483646, max + MAX_SERIAL_INCREMENT);
+}
+
+//
+// RFC 1982 Section 3.2 has been checked by the basic tests above
+//
+
+//
+// RFC 1982 Section 4.1
+//
+
+// Helper function for addition_always_larger test, add some numbers
+// and check that the result is always larger than the original
+void do_addition_larger_test(const Serial& number) {
+ EXPECT_GE(number + 0, number);
+ EXPECT_EQ(number + 0, number);
+ EXPECT_GT(number + 1, number);
+ EXPECT_GT(number + 2, number);
+ EXPECT_GT(number + 100, number);
+ EXPECT_GT(number + 1111111, number);
+ EXPECT_GT(number + 2147483646, number);
+ EXPECT_GT(number + MAX_SERIAL_INCREMENT, number);
+ // Try MAX_SERIAL_INCREMENT as a hardcoded number as well
+ EXPECT_GT(number + 2147483647, number);
+}
+
+TEST_F(SerialTest, addition_always_larger) {
+ do_addition_larger_test(one);
+ do_addition_larger_test(two);
+ do_addition_larger_test(date_zero);
+ do_addition_larger_test(date_one);
+ do_addition_larger_test(min);
+ do_addition_larger_test(max);
+ do_addition_larger_test(number_low);
+ do_addition_larger_test(number_medium);
+ do_addition_larger_test(number_high);
+}
+
+//
+// RFC 1982 Section 4.2
+//
+
+// Helper function to do the second addition
+void
+do_two_additions_test_second(const Serial &original,
+ const Serial &number)
+{
+ EXPECT_NE(original, number);
+ EXPECT_NE(original, number + 0);
+ EXPECT_NE(original, number + 1);
+ EXPECT_NE(original, number + 2);
+ EXPECT_NE(original, number + 100);
+ EXPECT_NE(original, number + 1111111);
+ EXPECT_NE(original, number + 2147483646);
+ EXPECT_NE(original, number + MAX_SERIAL_INCREMENT);
+ EXPECT_NE(original, number + 2147483647);
+}
+
+void do_two_additions_test_first(const Serial &number) {
+ do_two_additions_test_second(number, number + 1);
+ do_two_additions_test_second(number, number + 2);
+ do_two_additions_test_second(number, number + 100);
+ do_two_additions_test_second(number, number + 1111111);
+ do_two_additions_test_second(number, number + 2147483646);
+ do_two_additions_test_second(number, number + MAX_SERIAL_INCREMENT);
+ do_two_additions_test_second(number, number + 2147483647);
+}
+
+TEST_F(SerialTest, two_additions_never_equal) {
+ do_two_additions_test_first(one);
+ do_two_additions_test_first(two);
+ do_two_additions_test_first(date_zero);
+ do_two_additions_test_first(date_one);
+ do_two_additions_test_first(min);
+ do_two_additions_test_first(max);
+ do_two_additions_test_first(number_low);
+ do_two_additions_test_first(number_medium);
+ do_two_additions_test_first(number_high);
+}
+
+//
+// RFC 1982 Section 4.3 and 4.4 have nothing to test
+//
+
+//
+// Tests from RFC 1982 examples
+//
+TEST(SerialTextRFCExamples, rfc_example_tests) {
+}
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
new file mode 100644
index 0000000..bd56c11
--- /dev/null
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -0,0 +1,122 @@
+CLEANFILES =
+
+# NOTE: keep this in sync with real file listing
+# so is included in tarball
+EXTRA_DIST = edns_toWire1.spec edns_toWire2.spec
+EXTRA_DIST += edns_toWire3.spec edns_toWire4.spec
+EXTRA_DIST += masterload.txt
+EXTRA_DIST += message_fromWire1 message_fromWire2
+EXTRA_DIST += message_fromWire3 message_fromWire4
+EXTRA_DIST += message_fromWire5 message_fromWire6
+EXTRA_DIST += message_fromWire7 message_fromWire8
+EXTRA_DIST += message_fromWire9 message_fromWire10.spec
+EXTRA_DIST += message_fromWire11.spec message_fromWire12.spec
+EXTRA_DIST += message_fromWire13.spec message_fromWire14.spec
+EXTRA_DIST += message_fromWire15.spec message_fromWire16.spec
+EXTRA_DIST += message_fromWire17.spec message_fromWire18.spec
+EXTRA_DIST += message_fromWire19.spec message_fromWire20.spec
+EXTRA_DIST += message_fromWire21.spec message_fromWire22.spec
+EXTRA_DIST += message_toWire1 message_toWire2.spec message_toWire3.spec
+EXTRA_DIST += message_toWire4.spec message_toWire5.spec
+EXTRA_DIST += message_toWire6 message_toWire7
+EXTRA_DIST += message_toText1.txt message_toText1.spec
+EXTRA_DIST += message_toText2.txt message_toText2.spec
+EXTRA_DIST += message_toText3.txt message_toText3.spec
+EXTRA_DIST += name_fromWire1 name_fromWire2 name_fromWire3_1 name_fromWire3_2
+EXTRA_DIST += name_fromWire4 name_fromWire6 name_fromWire7 name_fromWire8
+EXTRA_DIST += name_fromWire9 name_fromWire10 name_fromWire11 name_fromWire12
+EXTRA_DIST += name_fromWire13 name_fromWire14
+EXTRA_DIST += name_toWire1 name_toWire2 name_toWire3 name_toWire4
+EXTRA_DIST += name_toWire5.spec name_toWire6.spec
+EXTRA_DIST += name_toWire7 name_toWire8 name_toWire9
+EXTRA_DIST += question_fromWire question_toWire1 question_toWire2
+EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire
+EXTRA_DIST += rdata_ns_fromWire
+EXTRA_DIST += rdata_in_a_fromWire rdata_in_aaaa_fromWire
+EXTRA_DIST += rdata_opt_fromWire1 rdata_opt_fromWire2
+EXTRA_DIST += rdata_opt_fromWire3 rdata_opt_fromWire4
+EXTRA_DIST += rdata_rrsig_fromWire1
+EXTRA_DIST += rdata_rrsig_fromWire2.spec
+EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec
+EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.spec
+EXTRA_DIST += rdata_txt_fromWire3.spec rdata_txt_fromWire4.spec
+EXTRA_DIST += rdata_txt_fromWire5.spec rdata_unknown_fromWire
+EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
+EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
+EXTRA_DIST += rrset_toWire1 rrset_toWire2
+EXTRA_DIST += rrset_toWire3 rrset_toWire4
+EXTRA_DIST += rdata_tkey_fromWire1.spec rdata_tkey_fromWire2.spec
+EXTRA_DIST += rdata_tkey_fromWire3.spec rdata_tkey_fromWire4.spec
+EXTRA_DIST += rdata_tkey_fromWire5.spec rdata_tkey_fromWire6.spec
+EXTRA_DIST += rdata_tkey_fromWire7.spec rdata_tkey_fromWire8.spec
+EXTRA_DIST += rdata_tkey_fromWire9.spec
+EXTRA_DIST += rdata_tkey_toWire1.spec rdata_tkey_toWire2.spec
+EXTRA_DIST += rdata_tkey_toWire3.spec rdata_tkey_toWire4.spec
+EXTRA_DIST += rdata_tkey_toWire5.spec
+EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec
+EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec
+EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec
+EXTRA_DIST += rdata_tsig_fromWire7.spec rdata_tsig_fromWire8.spec
+EXTRA_DIST += rdata_tsig_fromWire9.spec
+EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
+EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
+EXTRA_DIST += rdata_tsig_toWire5.spec
+EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
+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 tsig_verify11.spec
+EXTRA_DIST += example.org
+EXTRA_DIST += broken.zone
+EXTRA_DIST += origincheck.txt
+EXTRA_DIST += omitcheck.txt
+
+# Generated .wire files
+EXTRA_DIST += edns_toWire1.wire edns_toWire2.wire
+EXTRA_DIST += edns_toWire3.wire edns_toWire4.wire
+EXTRA_DIST += message_fromWire10.wire
+EXTRA_DIST += message_fromWire11.wire message_fromWire12.wire
+EXTRA_DIST += message_fromWire13.wire message_fromWire14.wire
+EXTRA_DIST += message_fromWire15.wire message_fromWire16.wire
+EXTRA_DIST += message_fromWire17.wire message_fromWire18.wire
+EXTRA_DIST += message_fromWire19.wire message_fromWire20.wire
+EXTRA_DIST += message_fromWire21.wire message_fromWire22.wire
+EXTRA_DIST += message_toWire1 message_toWire2.wire message_toWire3.wire
+EXTRA_DIST += message_toWire4.wire message_toWire5.wire
+EXTRA_DIST += message_toText1.txt message_toText1.wire
+EXTRA_DIST += message_toText2.txt message_toText2.wire
+EXTRA_DIST += message_toText3.txt message_toText3.wire
+EXTRA_DIST += name_toWire5.wire name_toWire6.wire
+EXTRA_DIST += rdata_rrsig_fromWire2.wire
+EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.wire
+EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.wire
+EXTRA_DIST += rdata_txt_fromWire3.wire rdata_txt_fromWire4.wire
+EXTRA_DIST += rdata_txt_fromWire5.wire rdata_unknown_fromWire
+EXTRA_DIST += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
+EXTRA_DIST += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
+EXTRA_DIST += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire
+EXTRA_DIST += rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire
+EXTRA_DIST += rdata_tsig_fromWire9.wire
+EXTRA_DIST += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
+EXTRA_DIST += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
+EXTRA_DIST += rdata_tsig_toWire5.wire
+EXTRA_DIST += rdata_tkey_fromWire1.wire rdata_tkey_fromWire2.wire
+EXTRA_DIST += rdata_tkey_fromWire3.wire rdata_tkey_fromWire4.wire
+EXTRA_DIST += rdata_tkey_fromWire5.wire rdata_tkey_fromWire6.wire
+EXTRA_DIST += rdata_tkey_fromWire7.wire rdata_tkey_fromWire8.wire
+EXTRA_DIST += rdata_tkey_fromWire9.wire
+EXTRA_DIST += rdata_tkey_toWire1.wire rdata_tkey_toWire2.wire
+EXTRA_DIST += rdata_tkey_toWire3.wire rdata_tkey_toWire4.wire
+EXTRA_DIST += rdata_tkey_toWire5.wire
+EXTRA_DIST += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
+EXTRA_DIST += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire
+EXTRA_DIST += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire
+EXTRA_DIST += tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire
+EXTRA_DIST += tsig_verify10.wire tsig_verify11.wire
+
+# We no longer use gen_wiredata.py during build process, so the
+# dependency is no longer needed. However, we'll keep this dependency
+# commented till the gen_wiredata.py script is removed.
+
+#.spec.wire:
+# $(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/Makefile.in b/src/lib/dns/tests/testdata/Makefile.in
new file mode 100644
index 0000000..955f1bd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/Makefile.in
@@ -0,0 +1,654 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/dns/tests/testdata
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp14.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DPKG = @DPKG@
+DPKGQUERY = @DPKGQUERY@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+XMLLINT = @XMLLINT@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+CLEANFILES =
+
+# NOTE: keep this in sync with real file listing
+# so is included in tarball
+
+# Generated .wire files
+EXTRA_DIST = edns_toWire1.spec edns_toWire2.spec edns_toWire3.spec \
+ edns_toWire4.spec masterload.txt message_fromWire1 \
+ message_fromWire2 message_fromWire3 message_fromWire4 \
+ message_fromWire5 message_fromWire6 message_fromWire7 \
+ message_fromWire8 message_fromWire9 message_fromWire10.spec \
+ message_fromWire11.spec message_fromWire12.spec \
+ message_fromWire13.spec message_fromWire14.spec \
+ message_fromWire15.spec message_fromWire16.spec \
+ message_fromWire17.spec message_fromWire18.spec \
+ message_fromWire19.spec message_fromWire20.spec \
+ message_fromWire21.spec message_fromWire22.spec \
+ message_toWire1 message_toWire2.spec message_toWire3.spec \
+ message_toWire4.spec message_toWire5.spec message_toWire6 \
+ message_toWire7 message_toText1.txt message_toText1.spec \
+ message_toText2.txt message_toText2.spec message_toText3.txt \
+ message_toText3.spec name_fromWire1 name_fromWire2 \
+ name_fromWire3_1 name_fromWire3_2 name_fromWire4 \
+ name_fromWire6 name_fromWire7 name_fromWire8 name_fromWire9 \
+ name_fromWire10 name_fromWire11 name_fromWire12 \
+ name_fromWire13 name_fromWire14 name_toWire1 name_toWire2 \
+ name_toWire3 name_toWire4 name_toWire5.spec name_toWire6.spec \
+ name_toWire7 name_toWire8 name_toWire9 question_fromWire \
+ question_toWire1 question_toWire2 rdata_dhcid_fromWire \
+ rdata_dhcid_toWire rdata_ns_fromWire rdata_in_a_fromWire \
+ rdata_in_aaaa_fromWire rdata_opt_fromWire1 rdata_opt_fromWire2 \
+ rdata_opt_fromWire3 rdata_opt_fromWire4 rdata_rrsig_fromWire1 \
+ rdata_rrsig_fromWire2.spec rdata_soa_fromWire \
+ rdata_soa_toWireUncompressed.spec rdata_txt_fromWire1 \
+ rdata_txt_fromWire2.spec rdata_txt_fromWire3.spec \
+ rdata_txt_fromWire4.spec rdata_txt_fromWire5.spec \
+ rdata_unknown_fromWire rrcode16_fromWire1 rrcode16_fromWire2 \
+ rrcode32_fromWire1 rrcode32_fromWire2 rrset_toWire1 \
+ rrset_toWire2 rrset_toWire3 rrset_toWire4 \
+ rdata_tkey_fromWire1.spec rdata_tkey_fromWire2.spec \
+ rdata_tkey_fromWire3.spec rdata_tkey_fromWire4.spec \
+ rdata_tkey_fromWire5.spec rdata_tkey_fromWire6.spec \
+ rdata_tkey_fromWire7.spec rdata_tkey_fromWire8.spec \
+ rdata_tkey_fromWire9.spec rdata_tkey_toWire1.spec \
+ rdata_tkey_toWire2.spec rdata_tkey_toWire3.spec \
+ rdata_tkey_toWire4.spec rdata_tkey_toWire5.spec \
+ rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec \
+ rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec \
+ rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec \
+ rdata_tsig_fromWire7.spec rdata_tsig_fromWire8.spec \
+ rdata_tsig_fromWire9.spec rdata_tsig_toWire1.spec \
+ rdata_tsig_toWire2.spec rdata_tsig_toWire3.spec \
+ rdata_tsig_toWire4.spec rdata_tsig_toWire5.spec \
+ tsigrecord_toWire1.spec tsigrecord_toWire2.spec \
+ tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec \
+ tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec \
+ tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec \
+ tsig_verify10.spec tsig_verify11.spec example.org broken.zone \
+ origincheck.txt omitcheck.txt edns_toWire1.wire \
+ edns_toWire2.wire edns_toWire3.wire edns_toWire4.wire \
+ message_fromWire10.wire message_fromWire11.wire \
+ message_fromWire12.wire message_fromWire13.wire \
+ message_fromWire14.wire message_fromWire15.wire \
+ message_fromWire16.wire message_fromWire17.wire \
+ message_fromWire18.wire message_fromWire19.wire \
+ message_fromWire20.wire message_fromWire21.wire \
+ message_fromWire22.wire message_toWire1 message_toWire2.wire \
+ message_toWire3.wire message_toWire4.wire message_toWire5.wire \
+ message_toText1.txt message_toText1.wire message_toText2.txt \
+ message_toText2.wire message_toText3.txt message_toText3.wire \
+ name_toWire5.wire name_toWire6.wire rdata_rrsig_fromWire2.wire \
+ rdata_soa_fromWire rdata_soa_toWireUncompressed.wire \
+ rdata_txt_fromWire1 rdata_txt_fromWire2.wire \
+ rdata_txt_fromWire3.wire rdata_txt_fromWire4.wire \
+ rdata_txt_fromWire5.wire rdata_unknown_fromWire \
+ rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire \
+ rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire \
+ rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire \
+ rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire \
+ rdata_tsig_fromWire9.wire rdata_tsig_toWire1.wire \
+ rdata_tsig_toWire2.wire rdata_tsig_toWire3.wire \
+ rdata_tsig_toWire4.wire rdata_tsig_toWire5.wire \
+ rdata_tkey_fromWire1.wire rdata_tkey_fromWire2.wire \
+ rdata_tkey_fromWire3.wire rdata_tkey_fromWire4.wire \
+ rdata_tkey_fromWire5.wire rdata_tkey_fromWire6.wire \
+ rdata_tkey_fromWire7.wire rdata_tkey_fromWire8.wire \
+ rdata_tkey_fromWire9.wire rdata_tkey_toWire1.wire \
+ rdata_tkey_toWire2.wire rdata_tkey_toWire3.wire \
+ rdata_tkey_toWire4.wire rdata_tkey_toWire5.wire \
+ tsigrecord_toWire1.wire tsigrecord_toWire2.wire \
+ tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire \
+ tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire \
+ tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire \
+ tsig_verify10.wire tsig_verify11.wire
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/dns/tests/testdata/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dns/tests/testdata/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# We no longer use gen_wiredata.py during build process, so the
+# dependency is no longer needed. However, we'll keep this dependency
+# commented till the gen_wiredata.py script is removed.
+
+#.spec.wire:
+# $(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/dns/tests/testdata/broken.zone b/src/lib/dns/tests/testdata/broken.zone
new file mode 100644
index 0000000..70f4540
--- /dev/null
+++ b/src/lib/dns/tests/testdata/broken.zone
@@ -0,0 +1,3 @@
+; This should fail due to broken TTL
+; The file should _NOT_ end with EOLN
+broken. 3600X IN A 192.0.2.2 More data \ No newline at end of file
diff --git a/src/lib/dns/tests/testdata/edns_toWire1.spec b/src/lib/dns/tests/testdata/edns_toWire1.spec
new file mode 100644
index 0000000..483aefa
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire1.spec
@@ -0,0 +1,5 @@
+#
+# A simplest form of EDNS: all default parameters
+#
+[edns]
+
diff --git a/src/lib/dns/tests/testdata/edns_toWire1.wire b/src/lib/dns/tests/testdata/edns_toWire1.wire
new file mode 100644
index 0000000..2884e29
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire1.wire
@@ -0,0 +1,9 @@
+###
+### This data file was auto-generated from edns_toWire1.spec
+###
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=0
+00 0029 1000 0000 0000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/edns_toWire2.spec b/src/lib/dns/tests/testdata/edns_toWire2.spec
new file mode 100644
index 0000000..7fe1ffd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire2.spec
@@ -0,0 +1,5 @@
+#
+# Same as edns_toWire1 but setting the DO bit
+#
+[edns]
+do: 1
diff --git a/src/lib/dns/tests/testdata/edns_toWire2.wire b/src/lib/dns/tests/testdata/edns_toWire2.wire
new file mode 100644
index 0000000..cb09000
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire2.wire
@@ -0,0 +1,9 @@
+###
+### This data file was auto-generated from edns_toWire2.spec
+###
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=1
+00 0029 1000 0000 8000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/edns_toWire3.spec b/src/lib/dns/tests/testdata/edns_toWire3.spec
new file mode 100644
index 0000000..0332097
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire3.spec
@@ -0,0 +1,7 @@
+#
+# Same as edns_toWire1 but setting the DO bit, and extended Rcode being non 0
+# (for BADVER)
+#
+[edns]
+do: 1
+extrcode: 0x1
diff --git a/src/lib/dns/tests/testdata/edns_toWire3.wire b/src/lib/dns/tests/testdata/edns_toWire3.wire
new file mode 100644
index 0000000..b8d0775
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire3.wire
@@ -0,0 +1,9 @@
+###
+### This data file was auto-generated from edns_toWire3.spec
+###
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=1 Version=0 DO=1
+00 0029 1000 0100 8000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/edns_toWire4.spec b/src/lib/dns/tests/testdata/edns_toWire4.spec
new file mode 100644
index 0000000..ea1f5e3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire4.spec
@@ -0,0 +1,7 @@
+#
+# Same as edns_toWire1 but setting the DO bit, and using an unusual
+# UDP payload size
+#
+[edns]
+do: 1
+udpsize = 511
diff --git a/src/lib/dns/tests/testdata/edns_toWire4.wire b/src/lib/dns/tests/testdata/edns_toWire4.wire
new file mode 100644
index 0000000..73bf757
--- /dev/null
+++ b/src/lib/dns/tests/testdata/edns_toWire4.wire
@@ -0,0 +1,9 @@
+###
+### This data file was auto-generated from edns_toWire4.spec
+###
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=511 ExtRcode=0 Version=0 DO=1
+00 0029 01ff 0000 8000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/example.org b/src/lib/dns/tests/testdata/example.org
new file mode 100644
index 0000000..2708ef4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/example.org
@@ -0,0 +1,17 @@
+example.org. 3600 IN SOA ( ; The SOA, split across lines for testing
+ ns1.example.org.
+ admin.example.org.
+ 1234
+ 3600
+ 1800
+ 2419200
+ 7200
+ )
+; Check it accepts quoted name too
+"\101xample.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.
+ 3600 IN AAAA 2001:db8::1 ; And initial whitespace handling
+ ; Here be just some space, no RRs
diff --git a/src/lib/dns/tests/testdata/masterload.txt b/src/lib/dns/tests/testdata/masterload.txt
new file mode 100644
index 0000000..0d2f942
--- /dev/null
+++ b/src/lib/dns/tests/testdata/masterload.txt
@@ -0,0 +1,5 @@
+;; a simple (incomplete) zone file
+
+example.com. 3600 IN TXT "test data"
+www.example.com. 60 IN A 192.0.2.1
+www.example.com. 60 IN A 192.0.2.2
diff --git a/src/lib/dns/tests/testdata/message_fromWire1 b/src/lib/dns/tests/testdata/message_fromWire1
new file mode 100644
index 0000000..5b76e3f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire1
@@ -0,0 +1,22 @@
+#
+# A simple DNS response message
+# ID = 0x1035
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: test.example.com. IN A
+# Answer:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 7200 IN A 192.0.2.2
+#
+1035 8500
+0001 0002 0000 0000
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA and TTL
+c0 0c
+0001 0001 00001c20 0004 c0 00 02 02
diff --git a/src/lib/dns/tests/testdata/message_fromWire10.spec b/src/lib/dns/tests/testdata/message_fromWire10.spec
new file mode 100644
index 0000000..d3fb014
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire10.spec
@@ -0,0 +1,13 @@
+#
+# A simple DNS response message with an EDNS0 indicating a BADVERS error
+#
+
+[header]
+qr: response
+rd: 1
+arcount: 1
+[question]
+# use default
+[edns]
+do: 1
+extrcode: 1
diff --git a/src/lib/dns/tests/testdata/message_fromWire10.wire b/src/lib/dns/tests/testdata/message_fromWire10.wire
new file mode 100644
index 0000000..fa76b92
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire10.wire
@@ -0,0 +1,19 @@
+###
+### This data file was auto-generated from message_fromWire10.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) RD
+1035 8100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=1 Version=0 DO=1
+00 0029 1000 0100 8000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire11.spec b/src/lib/dns/tests/testdata/message_fromWire11.spec
new file mode 100644
index 0000000..5f31746
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire11.spec
@@ -0,0 +1,15 @@
+#
+# A simple DNS response message with an EDNS0 indicating the maximum error code
+# (0xfff)
+#
+
+[header]
+qr: response
+rd: 1
+rcode: 0xf
+arcount: 1
+[question]
+# use default
+[edns]
+do: 1
+extrcode: 0xff
diff --git a/src/lib/dns/tests/testdata/message_fromWire11.wire b/src/lib/dns/tests/testdata/message_fromWire11.wire
new file mode 100644
index 0000000..f20132c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire11.wire
@@ -0,0 +1,19 @@
+###
+### This data file was auto-generated from message_fromWire11.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=15 RD
+1035 810f
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=255 Version=0 DO=1
+00 0029 1000 ff00 8000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire12.spec b/src/lib/dns/tests/testdata/message_fromWire12.spec
new file mode 100644
index 0000000..4eadeed
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire12.spec
@@ -0,0 +1,21 @@
+#
+# A simple DNS response message with TSIG signed, but the owner name of TSIG
+# is compressed
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: ptr=12
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire12.wire b/src/lib/dns/tests/testdata/message_fromWire12.wire
new file mode 100644
index 0000000..9ceb356
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire12.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_fromWire12.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=ptr=12 Class=ANY(255) TTL=0, RDLEN=58)
+c00c 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire13.spec b/src/lib/dns/tests/testdata/message_fromWire13.spec
new file mode 100644
index 0000000..e81ec4c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire13.spec
@@ -0,0 +1,20 @@
+#
+# Invalid TSIG: containing 2 TSIG RRs.
+#
+
+[custom]
+sections: header:question:tsig:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire13.wire b/src/lib/dns/tests/testdata/message_fromWire13.wire
new file mode 100644
index 0000000..05b064a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire13.wire
@@ -0,0 +1,35 @@
+###
+### This data file was auto-generated from message_fromWire13.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=2
+0001 0000 0000 0002
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire14.spec b/src/lib/dns/tests/testdata/message_fromWire14.spec
new file mode 100644
index 0000000..bf68a93
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire14.spec
@@ -0,0 +1,21 @@
+#
+# Invalid TSIG: not in the additional section.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+# TSIG goes to the answer section
+ancount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire14.wire b/src/lib/dns/tests/testdata/message_fromWire14.wire
new file mode 100644
index 0000000..17d0e21
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire14.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_fromWire14.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=0
+0001 0001 0000 0000
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire15.spec b/src/lib/dns/tests/testdata/message_fromWire15.spec
new file mode 100644
index 0000000..25d810f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire15.spec
@@ -0,0 +1,22 @@
+#
+# Invalid TSIG: not at the end of the message
+#
+
+[custom]
+sections: header:question:tsig:edns
+[header]
+id: 0x2d65
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
+[edns]
+# (all default)
diff --git a/src/lib/dns/tests/testdata/message_fromWire15.wire b/src/lib/dns/tests/testdata/message_fromWire15.wire
new file mode 100644
index 0000000..e3f36d0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire15.wire
@@ -0,0 +1,30 @@
+###
+### This data file was auto-generated from message_fromWire15.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=2
+0001 0000 0000 0002
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=0
+00 0029 1000 0000 0000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire16.spec b/src/lib/dns/tests/testdata/message_fromWire16.spec
new file mode 100644
index 0000000..be0abc3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire16.spec
@@ -0,0 +1,21 @@
+#
+# Invalid TSIG: not in the additional section.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_class: IN
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire16.wire b/src/lib/dns/tests/testdata/message_fromWire16.wire
new file mode 100644
index 0000000..04a791a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire16.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_fromWire16.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=IN(1) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 0001 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire17.spec b/src/lib/dns/tests/testdata/message_fromWire17.spec
new file mode 100644
index 0000000..366cf05
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire17.spec
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with TSIG signed
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x22c2
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+rrtype: TXT
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4e179212
+mac_size: 16
+mac: 0x8214b04634e32323d651ac60b08e6388
+original_id: 0x22c2
diff --git a/src/lib/dns/tests/testdata/message_fromWire17.wire b/src/lib/dns/tests/testdata/message_fromWire17.wire
new file mode 100644
index 0000000..e607c52
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire17.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_fromWire17.spec
+###
+
+# Header Section
+# ID=8898 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+22c2 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0010 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1310167570 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004e179212 012c
+# MAC Size=16 MAC=(see hex)
+0010 8214b04634e32323d651ac60b08e6388
+# Original-ID=8898 Error=0
+22c2 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire18.spec b/src/lib/dns/tests/testdata/message_fromWire18.spec
new file mode 100644
index 0000000..0b2592a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire18.spec
@@ -0,0 +1,23 @@
+#
+# Another simple DNS query message with TSIG signed. Only ID and time signed
+# (and MAC as a result) are different.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0xd6e2
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+rrtype: TXT
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4e17b38d
+mac_size: 16
+mac: 0x903b5b194a799b03a37718820c2404f2
+original_id: 0xd6e2
diff --git a/src/lib/dns/tests/testdata/message_fromWire18.wire b/src/lib/dns/tests/testdata/message_fromWire18.wire
new file mode 100644
index 0000000..82bdf6b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire18.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_fromWire18.spec
+###
+
+# Header Section
+# ID=55010 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+d6e2 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0010 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1310176141 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004e17b38d 012c
+# MAC Size=16 MAC=(see hex)
+0010 903b5b194a799b03a37718820c2404f2
+# Original-ID=55010 Error=0
+d6e2 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire19.spec b/src/lib/dns/tests/testdata/message_fromWire19.spec
new file mode 100644
index 0000000..8212dbf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire19.spec
@@ -0,0 +1,20 @@
+#
+# A non realistic DNS response message containing mixed types of RRs in the
+# answer section in a mixed order.
+#
+
+[custom]
+sections: header:question:a/1:aaaa:a/2
+[header]
+qr: 1
+ancount: 3
+[question]
+name: www.example.com
+rrtype: A
+[a/1]
+as_rr: True
+[aaaa]
+as_rr: True
+[a/2]
+as_rr: True
+address: 192.0.2.2
diff --git a/src/lib/dns/tests/testdata/message_fromWire19.wire b/src/lib/dns/tests/testdata/message_fromWire19.wire
new file mode 100644
index 0000000..d154244
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire19.wire
@@ -0,0 +1,28 @@
+###
+### This data file was auto-generated from message_fromWire19.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=3, NSCNT=0, ARCNT=0
+0001 0003 0000 0000
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4)
+076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
+
+# AAAA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16)
+076578616d706c6503636f6d00 001c 0001 00015180 0010
+# Address=2001:db8::1
+20010db8000000000000000000000001
+
+# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4)
+076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.2
+c0000202
diff --git a/src/lib/dns/tests/testdata/message_fromWire2 b/src/lib/dns/tests/testdata/message_fromWire2
new file mode 100644
index 0000000..194cbf2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire2
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with a valid EDNS0 OPT RR
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1
+# Question: test.example.com. IN A
+1035 0100
+0001 0000 0000 0001
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire20.spec b/src/lib/dns/tests/testdata/message_fromWire20.spec
new file mode 100644
index 0000000..91986e4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire20.spec
@@ -0,0 +1,20 @@
+#
+# A non realistic DNS response message containing mixed types of RRs in the
+# authority section in a mixed order.
+#
+
+[custom]
+sections: header:question:a/1:aaaa:a/2
+[header]
+qr: 1
+nscount: 3
+[question]
+name: www.example.com
+rrtype: A
+[a/1]
+as_rr: True
+[aaaa]
+as_rr: True
+[a/2]
+as_rr: True
+address: 192.0.2.2
diff --git a/src/lib/dns/tests/testdata/message_fromWire20.wire b/src/lib/dns/tests/testdata/message_fromWire20.wire
new file mode 100644
index 0000000..887dd1e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire20.wire
@@ -0,0 +1,28 @@
+###
+### This data file was auto-generated from message_fromWire20.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=0, NSCNT=3, ARCNT=0
+0001 0000 0003 0000
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4)
+076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
+
+# AAAA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16)
+076578616d706c6503636f6d00 001c 0001 00015180 0010
+# Address=2001:db8::1
+20010db8000000000000000000000001
+
+# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4)
+076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.2
+c0000202
diff --git a/src/lib/dns/tests/testdata/message_fromWire21.spec b/src/lib/dns/tests/testdata/message_fromWire21.spec
new file mode 100644
index 0000000..cd6aac9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire21.spec
@@ -0,0 +1,20 @@
+#
+# A non realistic DNS response message containing mixed types of RRs in the
+# additional section in a mixed order.
+#
+
+[custom]
+sections: header:question:a/1:aaaa:a/2
+[header]
+qr: 1
+arcount: 3
+[question]
+name: www.example.com
+rrtype: A
+[a/1]
+as_rr: True
+[aaaa]
+as_rr: True
+[a/2]
+as_rr: True
+address: 192.0.2.2
diff --git a/src/lib/dns/tests/testdata/message_fromWire21.wire b/src/lib/dns/tests/testdata/message_fromWire21.wire
new file mode 100644
index 0000000..14cfcc0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire21.wire
@@ -0,0 +1,28 @@
+###
+### This data file was auto-generated from message_fromWire21.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=3
+0001 0000 0000 0003
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4)
+076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
+
+# AAAA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16)
+076578616d706c6503636f6d00 001c 0001 00015180 0010
+# Address=2001:db8::1
+20010db8000000000000000000000001
+
+# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4)
+076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.2
+c0000202
diff --git a/src/lib/dns/tests/testdata/message_fromWire22.spec b/src/lib/dns/tests/testdata/message_fromWire22.spec
new file mode 100644
index 0000000..a52523b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire22.spec
@@ -0,0 +1,14 @@
+#
+# A simple DNS message containing one SOA RR in the answer section. This is
+# intended to be trimmed to emulate a bogus message.
+#
+
+[custom]
+sections: header:question:soa
+[header]
+qr: 1
+ancount: 1
+[question]
+rrtype: SOA
+[soa]
+as_rr: True
diff --git a/src/lib/dns/tests/testdata/message_fromWire22.wire b/src/lib/dns/tests/testdata/message_fromWire22.wire
new file mode 100644
index 0000000..69c3254
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire22.wire
@@ -0,0 +1,20 @@
+###
+### This data file was auto-generated from message_fromWire22.spec
+###
+
+# Header Section
+# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0)
+1035 8000
+# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=0
+0001 0001 0000 0000
+
+# Question Section
+# QNAME=example.com. QTYPE=SOA(6) QCLASS=IN(1)
+076578616d706c6503636f6d00 0006 0001
+
+# SOA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=54)
+076578616d706c6503636f6d00 0006 0001 00015180 0036
+# NNAME=ns.example.com RNAME=root.example.com
+026e73076578616d706c6503636f6d00 04726f6f74076578616d706c6503636f6d00
+# SERIAL(2010012601) REFRESH(3600) RETRY(300) EXPIRE(3600000) MINIMUM(1200)
+77ce5bb9 00000e10 0000012c 0036ee80 000004b0
diff --git a/src/lib/dns/tests/testdata/message_fromWire3 b/src/lib/dns/tests/testdata/message_fromWire3
new file mode 100644
index 0000000..9bd536a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire3
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with a valid EDNS0 OPT RR, DO bit off
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1
+# Question: test.example.com. IN A
+1035 0100
+0001 0000 0000 0001
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=0
+0000 0000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire4 b/src/lib/dns/tests/testdata/message_fromWire4
new file mode 100644
index 0000000..23eb7cf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire4
@@ -0,0 +1,23 @@
+#
+# A simple DNS query message with a bogus EDNS0 OPT RR (included in the
+# answer section)
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=1, NSCOUNT=0, ARCOUNT=0
+# Question: test.example.com. IN A
+1035 0100
+0001 0001 0000 0000
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire5 b/src/lib/dns/tests/testdata/message_fromWire5
new file mode 100644
index 0000000..6f08d22
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire5
@@ -0,0 +1,33 @@
+#
+# A simple DNS query message with multiple EDNS0 OPT RRs (bogus)
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=2
+# Question: test.example.com. IN A
+1035 0100
+0001 0000 0000 0002
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR (1st)
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
+# EDNS0 OPT RR (2nd)
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire6 b/src/lib/dns/tests/testdata/message_fromWire6
new file mode 100644
index 0000000..2783fd0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire6
@@ -0,0 +1,23 @@
+#
+# A simple DNS query message with EDNS0 OPT RRs of non root name (bogus)
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1
+1035 0100
+0001 0000 0000 0001
+# Question: test.example.com. IN A
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "example.com"
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire7 b/src/lib/dns/tests/testdata/message_fromWire7
new file mode 100644
index 0000000..4d85314
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire7
@@ -0,0 +1,27 @@
+#
+# A simple DNS query message with EDNS0 OPT RRs of compressed owner name
+# pointing to root (is this bogus?)
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1
+#0 1 2 3
+1035 0100
+#4 5 6 7 8 9 10 1
+0001 0000 0000 0001
+# Question: test.example.com. IN A
+# 2 3 4 5 6 7 8 9 20 1 2 3 4 5 6 7 8 9
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "example.com"
+# pointer = 29 (end of question section)
+ c0 1d
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire8 b/src/lib/dns/tests/testdata/message_fromWire8
new file mode 100644
index 0000000..c950b5e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire8
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with a valid EDNS0 OPT RR (but unusual UDP size)
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1
+# Question: test.example.com. IN A
+1035 0100
+0001 0000 0000 0001
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 500
+01f4
+# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO
+0000 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_fromWire9 b/src/lib/dns/tests/testdata/message_fromWire9
new file mode 100644
index 0000000..f9ff950
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire9
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with an unsupported version of EDNS0
+# ID = 0x1035
+# QR=0 (query), Opcode=0, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1
+# Question: test.example.com. IN A
+1035 0100
+0001 0000 0000 0001
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# EDNS0 OPT RR
+# owner name: "."
+00
+# TYPE: OPT (41 = 0x29)
+00 29
+# CLASS (= UDP size): 4096
+1000
+# TTL (extended RCODE and flags): RCODE=0, version=1, flags=DO
+0001 8000
+# RDLEN = 0
+0000
diff --git a/src/lib/dns/tests/testdata/message_toText1.spec b/src/lib/dns/tests/testdata/message_toText1.spec
new file mode 100644
index 0000000..b31310e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText1.spec
@@ -0,0 +1,24 @@
+#
+# A standard DNS message (taken from an invocation of dig)
+#
+
+[custom]
+sections: header:question:a/1:ns:a/2
+[header]
+id: 29174
+qr: 1
+aa: 1
+ancount: 1
+nscount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a/1]
+as_rr: True
+rr_name: www.example.com
+address: 192.0.2.80
+[ns]
+as_rr: True
+[a/2]
+as_rr: True
+rr_name: ns.example.com
diff --git a/src/lib/dns/tests/testdata/message_toText1.txt b/src/lib/dns/tests/testdata/message_toText1.txt
new file mode 100644
index 0000000..58c7239
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText1.txt
@@ -0,0 +1,14 @@
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29174
+;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
+
+;; QUESTION SECTION:
+;www.example.com. IN A
+
+;; ANSWER SECTION:
+www.example.com. 86400 IN A 192.0.2.80
+
+;; AUTHORITY SECTION:
+example.com. 86400 IN NS ns.example.com.
+
+;; ADDITIONAL SECTION:
+ns.example.com. 86400 IN A 192.0.2.1
diff --git a/src/lib/dns/tests/testdata/message_toText1.wire b/src/lib/dns/tests/testdata/message_toText1.wire
new file mode 100644
index 0000000..2a959bd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText1.wire
@@ -0,0 +1,28 @@
+###
+### This data file was auto-generated from message_toText1.spec
+###
+
+# Header Section
+# ID=29174 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA
+71f6 8400
+# QDCNT=1, ANCNT=1, NSCNT=1, ARCNT=1
+0001 0001 0001 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=www.example.com Class=IN(1) TTL=86400, RDLEN=4)
+03777777076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.80
+c0000250
+
+# NS RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16)
+076578616d706c6503636f6d00 0002 0001 00015180 0010
+# NS name=ns.example.com
+026e73076578616d706c6503636f6d00
+
+# A RR (QNAME=ns.example.com Class=IN(1) TTL=86400, RDLEN=4)
+026e73076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
diff --git a/src/lib/dns/tests/testdata/message_toText2.spec b/src/lib/dns/tests/testdata/message_toText2.spec
new file mode 100644
index 0000000..978aab3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText2.spec
@@ -0,0 +1,14 @@
+#
+# A standard DNS message with EDNS (taken from an invocation of dig)
+#
+
+[custom]
+sections: header:question:edns
+[header]
+id: 45981
+qr: 1
+rcode: refused
+arcount: 1
+[question]
+[edns]
+do: 1
diff --git a/src/lib/dns/tests/testdata/message_toText2.txt b/src/lib/dns/tests/testdata/message_toText2.txt
new file mode 100644
index 0000000..42cc2c1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText2.txt
@@ -0,0 +1,8 @@
+;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 45981
+;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
+
+;; OPT PSEUDOSECTION:
+; EDNS: version: 0, flags: do; udp: 4096
+
+;; QUESTION SECTION:
+;example.com. IN A
diff --git a/src/lib/dns/tests/testdata/message_toText2.wire b/src/lib/dns/tests/testdata/message_toText2.wire
new file mode 100644
index 0000000..1047b63
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText2.wire
@@ -0,0 +1,19 @@
+###
+### This data file was auto-generated from message_toText2.spec
+###
+
+# Header Section
+# ID=45981 QR=Response Opcode=QUERY(0) Rcode=REFUSED(5)
+b39d 8005
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1)
+076578616d706c6503636f6d00 0001 0001
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=1
+00 0029 1000 0000 8000
+# RDLEN=0
+0000
diff --git a/src/lib/dns/tests/testdata/message_toText3.spec b/src/lib/dns/tests/testdata/message_toText3.spec
new file mode 100644
index 0000000..a74ea1b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText3.spec
@@ -0,0 +1,31 @@
+#
+# A standard DNS message with TSIG (taken from an invocation of dig)
+#
+
+[custom]
+sections: header:question:a/1:ns:a/2:tsig
+[header]
+id: 10140
+qr: 1
+aa: 1
+ancount: 1
+nscount: 1
+arcount: 2
+[question]
+name: www.example.com
+[a/1]
+as_rr: True
+rr_name: www.example.com
+address: 192.0.2.80
+[ns]
+as_rr: True
+[a/2]
+as_rr: True
+rr_name: ns.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 1304384318
+original_id: 10140
+mac: 0x5257c80396f2fa95b20c77ae9a652fb2
diff --git a/src/lib/dns/tests/testdata/message_toText3.txt b/src/lib/dns/tests/testdata/message_toText3.txt
new file mode 100644
index 0000000..359b9c5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText3.txt
@@ -0,0 +1,17 @@
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10140
+;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
+
+;; QUESTION SECTION:
+;www.example.com. IN A
+
+;; ANSWER SECTION:
+www.example.com. 86400 IN A 192.0.2.80
+
+;; AUTHORITY SECTION:
+example.com. 86400 IN NS ns.example.com.
+
+;; ADDITIONAL SECTION:
+ns.example.com. 86400 IN A 192.0.2.1
+
+;; TSIG PSEUDOSECTION:
+www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1304384318 300 16 UlfIA5by+pWyDHeummUvsg== 10140 NOERROR 0
diff --git a/src/lib/dns/tests/testdata/message_toText3.wire b/src/lib/dns/tests/testdata/message_toText3.wire
new file mode 100644
index 0000000..eb3632b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText3.wire
@@ -0,0 +1,39 @@
+###
+### This data file was auto-generated from message_toText3.spec
+###
+
+# Header Section
+# ID=10140 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA
+279c 8400
+# QDCNT=1, ANCNT=1, NSCNT=1, ARCNT=2
+0001 0001 0001 0002
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=www.example.com Class=IN(1) TTL=86400, RDLEN=4)
+03777777076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.80
+c0000250
+
+# NS RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16)
+076578616d706c6503636f6d00 0002 0001 00015180 0010
+# NS name=ns.example.com
+026e73076578616d706c6503636f6d00
+
+# A RR (QNAME=ns.example.com Class=IN(1) TTL=86400, RDLEN=4)
+026e73076578616d706c6503636f6d00 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1304384318 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004dbf533e 012c
+# MAC Size=16 MAC=(see hex)
+0010 5257c80396f2fa95b20c77ae9a652fb2
+# Original-ID=10140 Error=0
+279c 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_toWire1 b/src/lib/dns/tests/testdata/message_toWire1
new file mode 100644
index 0000000..daeb85a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire1
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message
+# ID = 0x1035
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: test.example.com. IN A
+# Answer:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 7200 IN A 192.0.2.2
+#
+1035 8500
+0001 0002 0000 0000
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA
+c0 0c
+0001 0001 00000e10 0004 c0 00 02 02
diff --git a/src/lib/dns/tests/testdata/message_toWire2.spec b/src/lib/dns/tests/testdata/message_toWire2.spec
new file mode 100644
index 0000000..d256052
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire2.spec
@@ -0,0 +1,21 @@
+#
+# A simple DNS query message with TSIG signed
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_toWire2.wire b/src/lib/dns/tests/testdata/message_toWire2.wire
new file mode 100644
index 0000000..a495253
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire2.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_toWire2.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_toWire3.spec b/src/lib/dns/tests/testdata/message_toWire3.spec
new file mode 100644
index 0000000..c8e9453
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire3.spec
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with EDNS and TSIG
+#
+
+[custom]
+sections: header:question:edns:tsig
+[header]
+id: 0x06cd
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[edns]
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4db60d1f
+mac_size: 16
+mac: 0x93444053881c83d7eb120e86f25b369e
+original_id: 0x06cd
diff --git a/src/lib/dns/tests/testdata/message_toWire3.wire b/src/lib/dns/tests/testdata/message_toWire3.wire
new file mode 100644
index 0000000..46808b9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire3.wire
@@ -0,0 +1,30 @@
+###
+### This data file was auto-generated from message_toWire3.spec
+###
+
+# Header Section
+# ID=1741 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+06cd 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=2
+0001 0000 0000 0002
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# EDNS OPT RR
+# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=0
+00 0029 1000 0000 0000
+# RDLEN=0
+0000
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1303776543 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004db60d1f 012c
+# MAC Size=16 MAC=(see hex)
+0010 93444053881c83d7eb120e86f25b369e
+# Original-ID=1741 Error=0
+06cd 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_toWire4.spec b/src/lib/dns/tests/testdata/message_toWire4.spec
new file mode 100644
index 0000000..aab7e10
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire4.spec
@@ -0,0 +1,27 @@
+#
+# Truncated DNS response with TSIG signed
+# This is expected to be a response to "fromWire17"
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x22c2
+rd: 1
+qr: 1
+aa: 1
+# It's "truncated":
+tc: 1
+arcount: 1
+[question]
+name: www.example.com
+rrtype: TXT
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4e179212
+mac_size: 16
+mac: 0x88adc3811d1d6bec7c684438906fc694
+original_id: 0x22c2
diff --git a/src/lib/dns/tests/testdata/message_toWire4.wire b/src/lib/dns/tests/testdata/message_toWire4.wire
new file mode 100644
index 0000000..d2cda30
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire4.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from message_toWire4.spec
+###
+
+# Header Section
+# ID=8898 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA TC RD
+22c2 8700
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0010 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1310167570 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004e179212 012c
+# MAC Size=16 MAC=(see hex)
+0010 88adc3811d1d6bec7c684438906fc694
+# Original-ID=8898 Error=0
+22c2 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_toWire5.spec b/src/lib/dns/tests/testdata/message_toWire5.spec
new file mode 100644
index 0000000..e316833
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire5.spec
@@ -0,0 +1,36 @@
+#
+# A longest possible (without EDNS) DNS response with TSIG, i.e. total
+# length should be 512 bytes.
+#
+
+[custom]
+sections: header:question:txt/1:txt/2:tsig
+[header]
+id: 0xd6e2
+rd: 1
+qr: 1
+aa: 1
+ancount: 2
+arcount: 1
+[question]
+name: www.example.com
+rrtype: TXT
+[txt/1]
+as_rr: True
+# QNAME is fully compressed
+rr_name: ptr=12
+string: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde
+[txt/2]
+as_rr: True
+# QNAME is fully compressed
+rr_name: ptr=12
+string: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4e17b38d
+mac_size: 16
+mac: 0xbe2ba477373d2496891e2fda240ee4ec
+original_id: 0xd6e2
diff --git a/src/lib/dns/tests/testdata/message_toWire5.wire b/src/lib/dns/tests/testdata/message_toWire5.wire
new file mode 100644
index 0000000..ef7ee6e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire5.wire
@@ -0,0 +1,34 @@
+###
+### This data file was auto-generated from message_toWire5.spec
+###
+
+# Header Section
+# ID=55010 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA RD
+d6e2 8500
+# QDCNT=1, ANCNT=2, NSCNT=0, ARCNT=1
+0001 0002 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0010 0001
+
+# TXT RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=256)
+c00c 0010 0001 00015180 0100
+# String Len=255, String="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde"
+ff 303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465
+
+# TXT RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=114)
+c00c 0010 0001 00015180 0072
+# String Len=113, String="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"
+71 3031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1310176141 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004e17b38d 012c
+# MAC Size=16 MAC=(see hex)
+0010 be2ba477373d2496891e2fda240ee4ec
+# Original-ID=55010 Error=0
+d6e2 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/message_toWire6 b/src/lib/dns/tests/testdata/message_toWire6
new file mode 100644
index 0000000..996c99c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire6
@@ -0,0 +1,48 @@
+#
+# A simple DNS query message (with a signed response)
+# ID = 0x75c1
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=4, other COUNTS=0
+# Question: test.example.com. IN A
+# Answer:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 7200 IN A 192.0.2.2
+#
+75c1 8500
+0001 0004 0000 0000
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000e10 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA
+c0 0c
+0001 0001 00000e10 0004 c0 00 02 02
+
+# signature 1
+
+# same name
+c0 0c
+# RRSIG, IN, TTL=3600, RDLENGTH=0x28 TYPE_COV=A ALGO=5 (RSA/SHA-1) LABELS=3 ORIG_TTL=3600
+002e 0001 00000e10 0028 0001 05 03 00000e10
+# SIG_EXPIRY=20000101000000 SIG_INCEP=20000201000000 KEY_ID=12345
+386d4380 38962200 3039
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# FAKEFAKEFAKE
+14 02 84 14 02 84 14 02 84
+
+# signature 2
+
+# same name
+c0 0c
+# RRSIG, IN, TTL=3600, RDLENGTH=0x28 TYPE_COV=A ALGO=3 (DSA/SHA-1) LABELS=3 ORIG_TTL=3600
+002e 0001 00000e10 0028 0001 03 03 00000e10
+# SIG_EXPIRY=20000101000000 SIG_INCEP=20000201000000 KEY_ID=12345
+386d4380 38962200 3039
+#(7) e x a m p l e (3) c o m .
+ 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# FAKEFAKEFAKE
+14 02 84 14 02 84 14 02 84
diff --git a/src/lib/dns/tests/testdata/message_toWire7 b/src/lib/dns/tests/testdata/message_toWire7
new file mode 100644
index 0000000..ba22634
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toWire7
@@ -0,0 +1,35 @@
+#
+# A simple DNS query message (with a signed response)
+# ID = 0x75c1
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=1, ADCOUNT=0
+# Question: test.example.com. IN TXT
+# Answer:
+# test.example.com. 3600 IN TXT aaaaa...
+#
+75c1 8700
+0001 0001 0000 0000
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+0010 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, TXT, IN, RDLENGTH=256, RDATA
+0010 0001 00000e10 0100 ff
+# 'a' repeated 255 times
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
+61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
diff --git a/src/lib/dns/tests/testdata/name_fromWire1 b/src/lib/dns/tests/testdata/name_fromWire1
new file mode 100644
index 0000000..42fc61d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire1
@@ -0,0 +1,14 @@
+#
+# a global14 compression pointer
+#
+000a85800001000300000003
+# V i x c o m
+0356697803636f6d0000020001c00c00
+02000100000e10000b05697372763102
+7061c00cc00c0002000100000e100009
+066e732d657874c00cc00c0002000100
+000e10000e036e733104676e61630363
+6f6d00c0250001000100000e100004cc
+98b886c03c0001000100000e100004cc
+98b840c051000100010002a14a0004c6
+97f8f6
diff --git a/src/lib/dns/tests/testdata/name_fromWire10 b/src/lib/dns/tests/testdata/name_fromWire10
new file mode 100644
index 0000000..65be775
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire10
@@ -0,0 +1,12 @@
+#
+# Too large name; should trigger an exception.
+#
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 040102030400
diff --git a/src/lib/dns/tests/testdata/name_fromWire11 b/src/lib/dns/tests/testdata/name_fromWire11
new file mode 100644
index 0000000..32184f6
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire11
@@ -0,0 +1,12 @@
+#
+# A name with possible maximum number of labels; should be accepted safely.
+#
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 01000100010001000100 01000100010001000100
+01000100010001000100 0100010000
diff --git a/src/lib/dns/tests/testdata/name_fromWire12 b/src/lib/dns/tests/testdata/name_fromWire12
new file mode 100644
index 0000000..073adda
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire12
@@ -0,0 +1,13 @@
+#
+# Wire format including an invalid label length
+#
+#(1) a (7) e x a m p l e
+ 01 61 07 65 78 61 6d 70 6c 65
+# invalid label length: 64
+40
+# a "label" of 64 characters: shouldn't be parsed
+00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
+10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
+20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
+30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
+00
diff --git a/src/lib/dns/tests/testdata/name_fromWire13 b/src/lib/dns/tests/testdata/name_fromWire13
new file mode 100644
index 0000000..447f54b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire13
@@ -0,0 +1,5 @@
+#
+# A name including all "printable" characters
+#
+
+3f2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f1f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e00
diff --git a/src/lib/dns/tests/testdata/name_fromWire14 b/src/lib/dns/tests/testdata/name_fromWire14
new file mode 100644
index 0000000..3123aec
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire14
@@ -0,0 +1,7 @@
+#
+# A name including all "non-printable" characters
+#
+
+3f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f207f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c
+3f9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadb
+24dcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff00
diff --git a/src/lib/dns/tests/testdata/name_fromWire2 b/src/lib/dns/tests/testdata/name_fromWire2
new file mode 100644
index 0000000..0758a68
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire2
@@ -0,0 +1,15 @@
+#
+# bogus label character (looks like a local compression pointer)
+#
+000a85800001000300000003
+#this is the bogus label character:
+83
+76697803636f6d0000020001c00c00
+02000100000e10000b05697372763102
+7061c00cc00c0002000100000e100009
+066e732d657874c00cc00c0002000100
+000e10000e036e733104676e61630363
+6f6d00c0250001000100000e100004cc
+98b886c03c0001000100000e100004cc
+98b840c051000100010002a14a0004c6
+97f8f6
diff --git a/src/lib/dns/tests/testdata/name_fromWire3_1 b/src/lib/dns/tests/testdata/name_fromWire3_1
new file mode 100644
index 0000000..e38efcc
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire3_1
@@ -0,0 +1,11 @@
+#
+# a bad compression pointer starting with the bits 1111 (too big pointer)
+#
+000a85800001000300000003
+03766978 03636f6d 00 0002 0001
+f00c 0002 0001 0000 0e10 000b 056973727631 027061 c00c
+c00c 0002 0001 0000 0e10 0009 066e732d657874 c00c
+c00c 0002 0001 0000 0e10 000e 036e7331 04676e6163 03636f6d 00
+c025 0001 0001 0000 0e10 0004 cc98b886
+c03c 0001 0001 0000 0e10 0004 cc98b840
+c051 0001 0001 0002 a14a 0004 c697f8f6
diff --git a/src/lib/dns/tests/testdata/name_fromWire3_2 b/src/lib/dns/tests/testdata/name_fromWire3_2
new file mode 100644
index 0000000..c377bb1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire3_2
@@ -0,0 +1,13 @@
+#
+# a bad compression pointer due to forward reference of 0x30 to
+# another compression pointer with a valid backreference
+#
+000a85800001000300000003
+03766978 03636f6d 00 0002 0001
+#'30' is the forward reference, 'c00c' at the end is the valid pointer:
+c030 0002 0001 0000 0e10 000b 056973727631 027061 c00c
+c00c 0002 0001 0000 0e10 0009 066e732d657874 c00c
+c00c 0002 0001 0000 0e10 000e 036e7331 04676e6163 03636f6d 00
+c025 0001 0001 0000 0e10 0004 cc98b886
+c03c 0001 0001 0000 0e10 0004 cc98b840
+c051 0001 0001 0002 a14a 0004 c697f8f6
diff --git a/src/lib/dns/tests/testdata/name_fromWire4 b/src/lib/dns/tests/testdata/name_fromWire4
new file mode 100644
index 0000000..dba6035
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire4
@@ -0,0 +1,45 @@
+#
+# invalid name length, pointer at offset 0x0226 points to
+# long name at offset 0x25
+#
+000a 8580 0001 0003 0000 0001
+03 766978 03 636f6d 00 0002 0001
+c00c 0002 0001 00000e10
+0101
+# long name starts here
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a
+03616263 0358595a 03616263 0358595a
+03414243 0378797a 03414243 0378797a 00
+# compression pointer start here and refers back to long name
+c023 0002 0001 00000e10 0009 066e732d657874 c00c
+c00c 0002 0001 00000e10 000e 036e733104676e616303636f6d00
+c025 0001 0001 00000e10 0004 cc98b886
diff --git a/src/lib/dns/tests/testdata/name_fromWire6 b/src/lib/dns/tests/testdata/name_fromWire6
new file mode 100644
index 0000000..fa1abe6
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire6
@@ -0,0 +1,14 @@
+#
+# a bad pointer
+#
+000a85800001000300000003
+# the bad pointer is f00c (offset = 300c) which is too large
+0376697803636f6d0000020001 f00c 00
+02000100000e10000b05697372763102
+7061c00cc00c0002000100000e100009
+066e732d657874c00cc00c0002000100
+000e10000e036e733104676e61630363
+6f6d00c0250001000100000e100004cc
+98b886c03c0001000100000e100004cc
+98b840c051000100010002a14a0004c6
+97f8f6
diff --git a/src/lib/dns/tests/testdata/name_fromWire7 b/src/lib/dns/tests/testdata/name_fromWire7
new file mode 100644
index 0000000..2dedd4a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire7
@@ -0,0 +1,6 @@
+#
+# input ends unexpectedly
+#
+000a85800001000300000003
+# parser will start at the ending 'c0', which is an incomplete sequence.
+0376697803636f6d0000020001 c0
diff --git a/src/lib/dns/tests/testdata/name_fromWire8 b/src/lib/dns/tests/testdata/name_fromWire8
new file mode 100644
index 0000000..575563d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire8
@@ -0,0 +1,27 @@
+#
+# many hops of compression. absolutely many, but should be decompressed.
+#
+000a85800001000300000013
+03 766978 03 636f6d 00 0002 0001
+c00c 0002 0001 00000e10 000b 056973727631027061 c00c
+c019 0002 0001 00000e10 0009 066e732d657874 c00c
+c030 0002 0001 00000e10 000e 036e7331 04676e6163 03636f6d 00
+c045 0001 0001 00000e10 0004 cc98b886
+c05f 0001 0001 00000e10 0004 cc98b840
+c06f 0001 0001 0002a14a 0004 c697f8f6
+c07f 0001 0001 0002a14a 0004 c697f8f6
+c08f 0001 0001 0002a14a 0004 c697f8f6
+c09f 0001 0001 0002a14a 0004 c697f8f6
+c0af 0001 0001 0002a14a 0004 c697f8f6
+c0bf 0001 0001 0002a14a 0004 c697f8f6
+c0cf 0001 0001 0002a14a 0004 c697f8f6
+c0df 0001 0001 0002a14a 0004 c697f8f6
+c0ef 0001 0001 0002a14a 0004 c697f8f6
+c0ff 0001 0001 0002a14a 0004 c697f8f6
+c10f 0001 0001 0002a14a 0004 c697f8f6
+c11f 0001 0001 0002a14a 0004 c697f8f6
+c12f 0001 0001 0002a14a 0004 c697f8f6
+c13f 0001 0001 0002a14a 0004 c697f8f6
+c14f 0001 0001 0002a14a 0004 c697f8f6
+c15f 0001 0001 0002a14a 0004 c697f8f6
+c16f 0001 0001 0002a14a 0004 c697f8f6
diff --git a/src/lib/dns/tests/testdata/name_fromWire9 b/src/lib/dns/tests/testdata/name_fromWire9
new file mode 100644
index 0000000..79b2978
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_fromWire9
@@ -0,0 +1,12 @@
+#
+# A possible longest name; should be accepted safely.
+#
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 09010203040506070809 09010203040506070809
+09010203040506070809 0301020300
diff --git a/src/lib/dns/tests/testdata/name_toWire1 b/src/lib/dns/tests/testdata/name_toWire1
new file mode 100644
index 0000000..c06ec4b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire1
@@ -0,0 +1,12 @@
+#
+# Rendering 3 names with compression. [x] means a compression pointer pointing
+# to offset 'x'.
+#
+#bytes:
+# 0 1 2 3 4 5 6 7 8 9 a b c d e
+#(1)a(7)e x a m p l e(3)c o m .
+ 0161076578616d706c6503636f6d00
+#(1)b [2]
+ 0162c002
+# a . e x a m p l e . o r g .
+ 0161076578616d706c65036f726700
diff --git a/src/lib/dns/tests/testdata/name_toWire2 b/src/lib/dns/tests/testdata/name_toWire2
new file mode 100644
index 0000000..2377121
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire2
@@ -0,0 +1,14 @@
+#
+# Rendering names in a large buffer. [x] means a compression pointer pointing
+# to offset 'x'.
+#
+#bytes:
+#3f 40
+#ff 00
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#[3fff] = a.example.com: can be compressed
+ ffff
+#(1) b(7) e x a m p l e (3) c o m .; cannot compress as the pointer
+# (0x4001) would exceed 0x4000
+ 01 62 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
diff --git a/src/lib/dns/tests/testdata/name_toWire3 b/src/lib/dns/tests/testdata/name_toWire3
new file mode 100644
index 0000000..08c1474
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire3
@@ -0,0 +1,14 @@
+#
+# Rendering names including one explicitly uncompressed.
+# [x] means a compression pointer pointing to offset 'x'.
+#
+# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (bytes)
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+
+#15 29 (bytes)
+#(1) b (7) e x a m p l e (3) c o m .; specified to be not compressed,
+# but can be pointed to from others
+ 01 62 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#[0f] referring to the second (uncompressed name)
+ c0 0f
diff --git a/src/lib/dns/tests/testdata/name_toWire4 b/src/lib/dns/tests/testdata/name_toWire4
new file mode 100644
index 0000000..740d718
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire4
@@ -0,0 +1,16 @@
+#
+# Rendering 3 names with compression, including one resulting in a chain of
+# pointers (the last one).
+# legend: [x] means a compression pointer pointing to offset 'x'.
+#bytes:
+#00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+
+#0f 10 11 12
+#(1) b [2] (b.example.com.)
+ 01 62 c0 02
+
+#13 14
+# [0f]: (b.example.com.)
+ c0 0f
diff --git a/src/lib/dns/tests/testdata/name_toWire5.spec b/src/lib/dns/tests/testdata/name_toWire5.spec
new file mode 100644
index 0000000..87e140d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire5.spec
@@ -0,0 +1,19 @@
+#
+# A sequence of names that would be compressed case-sensitive manner.
+# First name: "a.example.com"
+# Second name: "b.eXample.com". Due to case-sensitive comparison only "com"
+# can be compressed.
+# Third name: "c.eXample.com". "eXample.com" part matches that of the second
+# name and can be compressed.
+#
+
+[custom]
+sections: name/1:name/2:name/3
+[name/1]
+name: a.example.com
+[name/2]
+name: b.eXample
+pointer: 10
+[name/3]
+name: c
+pointer: 17
diff --git a/src/lib/dns/tests/testdata/name_toWire5.wire b/src/lib/dns/tests/testdata/name_toWire5.wire
new file mode 100644
index 0000000..c6e62ed
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire5.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from name_toWire5.spec
+###
+
+# DNS Name: a.example.com
+0161076578616d706c6503636f6d00
+
+# DNS Name: b.eXample + compression pointer: 10
+0162076558616d706c65c00a
+
+# DNS Name: c + compression pointer: 17
+0163c011
diff --git a/src/lib/dns/tests/testdata/name_toWire6.spec b/src/lib/dns/tests/testdata/name_toWire6.spec
new file mode 100644
index 0000000..a536f5d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire6.spec
@@ -0,0 +1,19 @@
+#
+# A sequence of names that would be compressed both case-sensitive and
+# case-insensitive manner (unusual, but allowed).
+# First and second name: see name_toWire5.spec.
+# Third name: "c.b.EXAMPLE.com". This is rendered with case-insensitive
+# compression, so "b.EXAMPLE.com" part of the name matches that of the
+# second name.
+#
+
+[custom]
+sections: name/1:name/2:name/3
+[name/1]
+name: a.example.com
+[name/2]
+name: b.eXample
+pointer: 10
+[name/3]
+name: c
+pointer: 15
diff --git a/src/lib/dns/tests/testdata/name_toWire6.wire b/src/lib/dns/tests/testdata/name_toWire6.wire
new file mode 100644
index 0000000..dcaa39f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire6.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from name_toWire6.spec
+###
+
+# DNS Name: a.example.com
+0161076578616d706c6503636f6d00
+
+# DNS Name: b.eXample + compression pointer: 10
+0162076558616d706c65c00a
+
+# DNS Name: c + compression pointer: 15
+0163c00f
diff --git a/src/lib/dns/tests/testdata/name_toWire7 b/src/lib/dns/tests/testdata/name_toWire7
new file mode 100644
index 0000000..bff599f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire7
@@ -0,0 +1,10 @@
+#
+# Rendering names including one explicitly uncompressed.
+# [x] means a compression pointer pointing to offset 'x'.
+#
+# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (bytes)
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+
+#[02] pointing to -> "example.com."
+ c0 02
diff --git a/src/lib/dns/tests/testdata/name_toWire8 b/src/lib/dns/tests/testdata/name_toWire8
new file mode 100644
index 0000000..d01093b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire8
@@ -0,0 +1,7 @@
+#
+# Rendering names.
+# [x] means a compression pointer pointing to offset 'x'.
+#
+# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 (bytes)
+#(1) a (7) e x a m p l e (3) c o m
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d
diff --git a/src/lib/dns/tests/testdata/name_toWire9 b/src/lib/dns/tests/testdata/name_toWire9
new file mode 100644
index 0000000..51a1987
--- /dev/null
+++ b/src/lib/dns/tests/testdata/name_toWire9
@@ -0,0 +1,13 @@
+#
+# Rendering names including one explicitly uncompressed.
+# [x] means a compression pointer pointing to offset 'x'.
+#
+# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (bytes)
+#(1) a (7) e x a m p l e (3) c o m .
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#(1) a (7) e x a m p l e (3) c o m
+ 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d
+#(1) a (7) e x a m p l e
+ 01 61 07 65 78 61 6d 70 6c 65
+#[1f] pointing to ^^ "example"
+ c0 1f
diff --git a/src/lib/dns/tests/testdata/omitcheck.txt b/src/lib/dns/tests/testdata/omitcheck.txt
new file mode 100644
index 0000000..580cab4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/omitcheck.txt
@@ -0,0 +1 @@
+ 1H IN A 192.0.2.1
diff --git a/src/lib/dns/tests/testdata/origincheck.txt b/src/lib/dns/tests/testdata/origincheck.txt
new file mode 100644
index 0000000..c370ed2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/origincheck.txt
@@ -0,0 +1,5 @@
+; We change the origin here. We want to check it is not propagated
+; outside of the included file.
+$ORIGIN www.example.org.
+
+@ 1H IN A 192.0.2.1
diff --git a/src/lib/dns/tests/testdata/question_fromWire b/src/lib/dns/tests/testdata/question_fromWire
new file mode 100644
index 0000000..cbc28c0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/question_fromWire
@@ -0,0 +1,33 @@
+#
+# Wire-format data of a sequence of DNS questions.
+# foo.example.com. IN NS
+# bar.example.com. CH A (owner name is compressed)
+# and some pathological cases
+#
+# 0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 (-th byte)
+#(3) f o o (7) e x a m p l e (3) c o m .
+ 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#7 8 9 20
+# type/class: NS = 2, IN = 1
+00 02 00 01
+
+# 1 2 3 4 5 6
+#(3) b a r [ptr=0x04]
+ 03 62 61 72 c0 04
+#7 8 9 30
+# type/class: A = 1, CH = 3
+00 01 00 03
+
+# owner name is broken
+#1
+# invalid label type
+ff
+#2 3 4 5
+#type/class IN/A
+00 01 00 01
+
+# short buffer
+# (root name)
+00
+#class is missing
+00 01
diff --git a/src/lib/dns/tests/testdata/question_toWire1 b/src/lib/dns/tests/testdata/question_toWire1
new file mode 100644
index 0000000..77886db
--- /dev/null
+++ b/src/lib/dns/tests/testdata/question_toWire1
@@ -0,0 +1,14 @@
+#
+# Rendering two DNS Questions without name compression
+# foo.example.com. IN NS
+# bar.example.com. CH A
+#
+#(3) f o o (7) e x a m p l e (3) c o m .
+ 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: NS = 2, IN = 1
+00 02 00 01
+#(3) b a r (7) e x a m p l e (3) c o m .
+ 03 62 61 72 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#7 8 9 30
+# type/class: A = 1, CH = 3
+00 01 00 03
diff --git a/src/lib/dns/tests/testdata/question_toWire2 b/src/lib/dns/tests/testdata/question_toWire2
new file mode 100644
index 0000000..9117ab2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/question_toWire2
@@ -0,0 +1,14 @@
+#
+# Rendering two DNS Questions with name compression
+# foo.example.com. IN NS
+# bar.example.com. CH A
+#
+# 0 1 2 3 4 ... (-th byte)
+#(3) f o o (7) e x a m p l e (3) c o m .
+ 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: NS = 2, IN = 1
+00 02 00 01
+#(3) b a r [ptr=0x04]
+ 03 62 61 72 c0 04
+# type/class: A = 1, CH = 3
+00 01 00 03
diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_fromWire b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
new file mode 100644
index 0000000..b28b5b3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
@@ -0,0 +1,12 @@
+#
+# DHCID RDATA stored in an input buffer
+#
+# Valid RDATA for 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+#
+# RDLENGTH=41 bytes
+# 0 1
+ 00 29
+# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be
+d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0
+bb d0 be d1 87 d0 ba d0 b0
diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_toWire b/src/lib/dns/tests/testdata/rdata_dhcid_toWire
new file mode 100644
index 0000000..99ec229
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dhcid_toWire
@@ -0,0 +1,7 @@
+#
+# DHCID RDATA stored in an output buffer
+#
+# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be
+d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0
+bb d0 be d1 87 d0 ba d0 b0
diff --git a/src/lib/dns/tests/testdata/rdata_in_a_fromWire b/src/lib/dns/tests/testdata/rdata_in_a_fromWire
new file mode 100644
index 0000000..12f508b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_in_a_fromWire
@@ -0,0 +1,19 @@
+#
+# various kinds of IN/A RDATA stored in an input buffer
+#
+# valid RDATA for 192.0.2.1
+#
+# 0 1 2 3 4 5 (bytes)
+ 00 04 c0 00 02 01
+#
+# short length
+# 6 7 8 9 10 11 (bytes)
+ 00 03 c0 00 02 01
+#
+# length too long
+#12 13 14 15 16 17 18
+ 00 05 c0 00 02 01 00
+#
+# short buffer (this can be tested only at the end of the buffer)
+#19 20 21 22 23
+ 00 04 c0 00 02
diff --git a/src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire b/src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire
new file mode 100644
index 0000000..22fdd1f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire
@@ -0,0 +1,18 @@
+#
+# various kinds of IN/AAAA RDATA stored in an input buffer
+#
+# valid RDATA for 2001:db8::1234
+#
+#RDLENGTH=16
+0010
+#IPv6 address (18 bytes)
+2001 0db8 0000 0000 0000 0000 0000 1234
+#
+# short length (36 bytes)
+0008 2001 0db8 0000 0000 0000 0000 0000 1234
+#
+# length too long (55 bytes)
+0011 2001 0db8 0000 0000 0000 0000 0000 1234 ff
+#
+# short buffer (this can be tested only at the end of the buffer)
+0010 2001 0db8
diff --git a/src/lib/dns/tests/testdata/rdata_ns_fromWire b/src/lib/dns/tests/testdata/rdata_ns_fromWire
new file mode 100644
index 0000000..973365f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_ns_fromWire
@@ -0,0 +1,44 @@
+#
+# various kinds of NS RDATA stored in an input buffer
+#
+# Valid non-compressed RDATA for ns.example.com.
+# RDLENGTH=16 bytes
+# 0 1
+ 00 10
+# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes)
+#(2) n s (7) e x a m p l e (3) c o m .
+ 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+# short length
+# 8 9
+ 00 0f
+#20 1 2 3 4 5 6 7 8 9 30 1 2 3 4 5
+ 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+# length too long
+# 6 7
+ 00 11
+#
+# 8 9 40 1 2 3 4 5 6 7 8 9 50 1 2 3 4
+ 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00
+#
+# Valid compressed NS name: 'ns2' + pointer
+# 5 6
+ 00 06
+# 7 8 9 60 1 2
+#(3) n s 2 ptr=5
+ 03 6e 73 32 c0 05
+#
+# Valid compressed NS name but RDLENGTH is incorrect: it must be the length
+# of the sequence from the head to the pointer, not the decompressed name
+# length.
+# 3 4
+ 00 11
+# 5 6 7 8 9 70
+ 03 6e 73 32 c0 05
+# incomplete name (no trailing dot). this can be tested only at the end of
+# the buffer.
+# 1 2
+ 00 0f
+# 3 4 5 6 7 8 9 80 1 2 3 4 5 6 7
+ 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire1 b/src/lib/dns/tests/testdata/rdata_opt_fromWire1
new file mode 100644
index 0000000..f2eb680
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire1
@@ -0,0 +1,15 @@
+# Various kinds of OPT RDATA stored in an input buffer
+#
+# Empty RDATA (which is okay)
+#
+# 0 1 (bytes)
+ 00 00
+#
+# An OPT RR containing an NSID Option
+# code=3 len=3 ID value (opaque)
+# 2 3 4 5 6 7 8 9 10
+ 00 07 00 2a 00 03 00 01 02
+#
+# Short buffer (this can be tested only at the end of the buffer)
+# 1 2 3 4 5
+ 00 04 c0 00 02
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire2 b/src/lib/dns/tests/testdata/rdata_opt_fromWire2
new file mode 100644
index 0000000..2c5a11f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire2
@@ -0,0 +1,4 @@
+# Short RDATA length
+#
+# OPT RDATA, RDLEN=1
+0001
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire3 b/src/lib/dns/tests/testdata/rdata_opt_fromWire3
new file mode 100644
index 0000000..52db1d8
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire3
@@ -0,0 +1,8 @@
+# Short RDATA length (in second pseudo RR)
+#
+# OPT RDATA, RDLEN=8
+0008
+# Pseudo RR 1 of size 7 (code=3, len=3)
+00 03 00 03 00 01 02
+# Pseudo RR 2 of size 7 exhausts RDLEN (code=4, len=3)
+00 04 00 03 00 01 02
diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire4 b/src/lib/dns/tests/testdata/rdata_opt_fromWire4
new file mode 100644
index 0000000..a302127
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire4
@@ -0,0 +1,9 @@
+# Sum of option lengths would overflow RDLEN
+#
+# OPT RDATA, RDLEN=14 (0x000e)
+000e
+# Pseudo RR 1 (code=3, len=3)
+00 03 00 03 00 01 02
+# Pseudo RR 2 (code=4, len=65535 overflows RDLEN)
+00 04 ff ff 00 01 02
+# Rest of option data is omitted...
diff --git a/src/lib/dns/tests/testdata/rdata_rrsig_fromWire1 b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire1
new file mode 100644
index 0000000..1b799c2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire1
@@ -0,0 +1,13 @@
+# RDLENGTH 155 bytes
+00 9b
+# RRSIG record
+00 01 05 02 00 00 a8 c0 4b ad ad 5d 4b 86 20 5d
+0a 62 03 69 73 63 03 6f 72 67 00 1e 42 64 ff 16
+53 bf 37 8f 53 c3 44 36 5f e5 7b 2f 1b 6d 4b a6
+86 4d 61 5d c8 b2 aa 12 e7 cf 55 50 17 39 03 a2
+87 a3 ea 77 97 66 05 99 cd 02 9e 4c a3 ce 61 6a
+e7 62 d7 59 5b 83 e7 3d 01 5e 52 5d e8 ae 02 de
+bf b1 7c 27 a1 59 94 39 f4 cb f2 7f 4e 14 79 9b
+7e 8c a3 6f c6 77 18 e3 f2 52 7f 22 33 d5 91 da
+4a c8 1a 5c 9d 83 43 f0 a1 08 99 ae 4c c8 d0 8d
+7b 23 b5 52 47 cf 41 91 87 35 24
diff --git a/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec
new file mode 100644
index 0000000..582975a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec
@@ -0,0 +1,8 @@
+#
+# RRSIG RDATA with a bogus RDLEN (too short)
+#
+
+[custom]
+sections: rrsig
+[rrsig]
+rdlen: 19
diff --git a/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire
new file mode 100644
index 0000000..b3601a1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from rdata_rrsig_fromWire2.spec
+###
+
+# RRSIG RDATA, RDLEN=19
+0013
+# Covered=A(1) Algorithm=RSASHA1(5) Labels=2 OrigTTL=3600
+0001 05 02 00000e10
+# Expiration=1264935600, Inception=1262343600
+4b6562b0 4b3dd5b0
+# Tag=4149 Signer=example.com and Signature
+1035 076578616d706c6503636f6d00 123456789abcdef123456789abcdef
diff --git a/src/lib/dns/tests/testdata/rdata_soa_fromWire b/src/lib/dns/tests/testdata/rdata_soa_fromWire
new file mode 100644
index 0000000..5cd67f0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_soa_fromWire
@@ -0,0 +1,20 @@
+#
+# A common style of SOA RDATA stored in an input buffer
+#
+# Valid compressed RDATA for "(ns.example.com. root.example.com.
+# 2010012601 3600 300 3600000 1200)"
+# RDLENGTH=43 bytes
+# 0 1
+ 00 2b
+# MNAME: non compressed
+# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes)
+#(2) n s (7) e x a m p l e (3) c o m .
+ 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# RNAME: compressed
+# 8 9 20 1 2 3 4
+#(4) r o o t ptr=5
+ 04 72 6f 6f 74 c0 05
+# other numeric parameters
+# 28 32 36 40 44
+# serial, refresh, retry, expire, minimum
+ 77ce5bb9 00000e10 0000012c 0036ee80 000004b0
diff --git a/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec
new file mode 100644
index 0000000..389cec9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec
@@ -0,0 +1,7 @@
+#
+# A simple SOA RDATA without name compression
+#
+
+[custom]
+sections: soa
+[soa]
diff --git a/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire
new file mode 100644
index 0000000..4b6442f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_soa_toWireUncompressed.spec
+###
+
+# SOA RDATA, RDLEN=54
+0036
+# NNAME=ns.example.com RNAME=root.example.com
+026e73076578616d706c6503636f6d00 04726f6f74076578616d706c6503636f6d00
+# SERIAL(2010012601) REFRESH(3600) RETRY(300) EXPIRE(3600000) MINIMUM(1200)
+77ce5bb9 00000e10 0000012c 0036ee80 000004b0
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec
new file mode 100644
index 0000000..e46d9b3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of TKEY: all default parameters
+#
+[custom]
+sections: tkey
+[tkey]
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire
new file mode 100644
index 0000000..e8ee944
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire1.spec
+###
+
+# TKEY RDATA, RDLEN=58
+003a
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec
new file mode 100644
index 0000000..e4a1920
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec
@@ -0,0 +1,8 @@
+#
+# TKEY with other data
+#
+[custom]
+sections: tkey
+[tkey]
+other_len: 8
+other_data: abcd0123
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire
new file mode 100644
index 0000000..614844f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire2.spec
+###
+
+# TKEY RDATA, RDLEN=66
+0042
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=8 Other-Data=(see hex)
+0008 6162636430313233
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec
new file mode 100644
index 0000000..2566b58
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec
@@ -0,0 +1,9 @@
+#
+# TKEY without Key
+#
+[custom]
+sections: tkey
+[tkey]
+key_len: 0
+other_len: 8
+other_data: abcd0123
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire
new file mode 100644
index 0000000..df27910
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire3.spec
+###
+
+# TKEY RDATA, RDLEN=34
+0022
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=0 Key=(see hex)
+0000
+# Other-Len=8 Other-Data=(see hex)
+0008 6162636430313233
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec
new file mode 100644
index 0000000..33459eb
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec
@@ -0,0 +1,11 @@
+#
+# A simplest form of TKEY, but the algorithm name is compressed (quite
+# pathological, but we accept it)
+#
+[custom]
+sections: name:tkey
+[name]
+name: gss-tsig
+[tkey]
+algorithm: ptr=0
+key_len: 32
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire
new file mode 100644
index 0000000..550052e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire
@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire4.spec
+###
+
+# DNS Name: gss-tsig
+086773732d7473696700
+
+# TKEY RDATA, RDLEN=50
+0032
+# Algorithm=ptr=0
+c000
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec
new file mode 100644
index 0000000..6cfa4b4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# TKEY-like RDATA but RDLEN is too short.
+#
+[custom]
+sections: tkey
+[tkey]
+rdlen: 57
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire
new file mode 100644
index 0000000..fa32566
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire5.spec
+###
+
+# TKEY RDATA, RDLEN=57
+0039
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec
new file mode 100644
index 0000000..87460a2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# TKEY-like RDATA but RDLEN is too long.
+#
+[custom]
+sections: tkey
+[tkey]
+rdlen: 60
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire
new file mode 100644
index 0000000..7f5f112
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire6.spec
+###
+
+# TKEY RDATA, RDLEN=60
+003c
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec
new file mode 100644
index 0000000..3fc0929
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec
@@ -0,0 +1,8 @@
+#
+# TKEY-like RDATA but algorithm name is broken.
+#
+[custom]
+sections: tkey
+[tkey]
+algorithm: "01234567890123456789012345678901234567890123456789012345678901234"
+key_len: 32
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire
new file mode 100644
index 0000000..73b277c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire7.spec
+###
+
+# TKEY RDATA, RDLEN=117
+0075
+# Algorithm="01234567890123456789012345678901234567890123456789012345678901234"
+432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec
new file mode 100644
index 0000000..8338279
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec
@@ -0,0 +1,8 @@
+#
+# TKEY-like RDATA but Key len is bogus
+#
+[custom]
+sections: tkey
+[tkey]
+key_len: 65535
+key: "dummy data"
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire
new file mode 100644
index 0000000..abeb95b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire8.spec
+###
+
+# TKEY RDATA, RDLEN=38
+0026
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=65535 Key=(see hex)
+ffff 2264756d6d79206461746122
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec
new file mode 100644
index 0000000..9fb63e0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec
@@ -0,0 +1,8 @@
+#
+# TKEY-like RDATA but Other-Data length is bogus
+#
+[custom]
+sections: tkey
+[tkey]
+other_len: 65535
+otherdata: "dummy data"
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire
new file mode 100644
index 0000000..8e5f943
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_fromWire9.spec
+###
+
+# TKEY RDATA, RDLEN=58
+003a
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=32 Key=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Other-Len=65535 Other-Data=(see hex)
+ffff
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec
new file mode 100644
index 0000000..42521a3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec
@@ -0,0 +1,8 @@
+#
+# An artificial TKEY RDATA for toWire test.
+#
+[custom]
+sections: tkey
+[tkey]
+algorithm: gss-tsig
+key_len: 0
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire
new file mode 100644
index 0000000..3f49a69
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_toWire1.spec
+###
+
+# TKEY RDATA, RDLEN=26
+001a
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=0 Key=(see hex)
+0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec
new file mode 100644
index 0000000..25d47e5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec
@@ -0,0 +1,11 @@
+#
+# An artificial TKEY RDATA for toWire test.
+#
+[custom]
+sections: tkey
+[tkey]
+algorithm: GSS-TSIG
+error: 16
+key_len: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+key: 0x140284140284140284140284
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire
new file mode 100644
index 0000000..a1fdb9a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_toWire2.spec
+###
+
+# TKEY RDATA, RDLEN=38
+0026
+# Algorithm=GSS-TSIG
+084753532d5453494700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=16
+608d42c0 608d50d0 0003 0010
+# Key Len=12 Key=(see hex)
+000c 140284140284140284140284
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec
new file mode 100644
index 0000000..b3ea3db
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TKEY RDATA for toWire test.
+#
+[custom]
+sections: tkey
+[tkey]
+algorithm: gss.tsig
+error: 16
+key_len: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+key: 0x140284140284140284140284
+other_len: 6
+other_data: 0x140284140284
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire
new file mode 100644
index 0000000..f2f8a6f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tkey_toWire3.spec
+###
+
+# TKEY RDATA, RDLEN=44
+002c
+# Algorithm=gss.tsig
+03677373047473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=16
+608d42c0 608d50d0 0003 0010
+# Key Len=12 Key=(see hex)
+000c 140284140284140284140284
+# Other-Len=6 Other-Data=(see hex)
+0006 140284140284
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec
new file mode 100644
index 0000000..e403c00
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec
@@ -0,0 +1,10 @@
+#
+# An artificial TKEY RDATA for toWire test.
+#
+[custom]
+sections: name:tkey
+[name]
+name: gss-tsig
+[tkey]
+algorithm: gss-tsig
+key_len: 0
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire
new file mode 100644
index 0000000..81a1443
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire
@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from rdata_tkey_toWire4.spec
+###
+
+# DNS Name: gss-tsig
+086773732d7473696700
+
+# TKEY RDATA, RDLEN=26
+001a
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=0 Key=(see hex)
+0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec
new file mode 100644
index 0000000..b4a1bca
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec
@@ -0,0 +1,10 @@
+#
+# An artificial TKEY RDATA for toWire test.
+#
+[custom]
+sections: tkey:name
+[tkey]
+algorithm: gss-tsig
+key_len: 0
+[name]
+name: ptr=2
diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire
new file mode 100644
index 0000000..e59a1f0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire
@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from rdata_tkey_toWire5.spec
+###
+
+# TKEY RDATA, RDLEN=26
+001a
+# Algorithm=gss-tsig
+086773732d7473696700
+# Inception=1619870400 Expire=1619874000 Mode=3 Error=0
+608d42c0 608d50d0 0003 0000
+# Key Len=0 Key=(see hex)
+0000
+# Other-Len=0 Other-Data=(see hex)
+0000
+
+# DNS Name: ptr=2
+c002
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec
new file mode 100644
index 0000000..a30c371
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of TSIG: all default parameters
+#
+[custom]
+sections: tsig
+[tsig]
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire
new file mode 100644
index 0000000..cec3a0f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire1.spec
+###
+
+# TSIG RDATA, RDLEN=61
+003d
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=32 MAC=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec
new file mode 100644
index 0000000..d1e49a5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec
@@ -0,0 +1,8 @@
+#
+# TSIG with other data (error = BADTIME(18))
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 0
+error: 18
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire
new file mode 100644
index 0000000..ca85df4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire2.spec
+###
+
+# TSIG RDATA, RDLEN=35
+0023
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=2845 Error=18
+0b1d 0012
+# Other-Len=6 Other-Data=(see hex)
+0006 00004cb5be18
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec
new file mode 100644
index 0000000..57f8e83
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec
@@ -0,0 +1,8 @@
+#
+# TSIG without MAC (error = BADSIG(16))
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 0
+error: 16
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire
new file mode 100644
index 0000000..16c3e39
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire3.spec
+###
+
+# TSIG RDATA, RDLEN=29
+001d
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=2845 Error=16
+0b1d 0010
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec
new file mode 100644
index 0000000..8c38e9e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec
@@ -0,0 +1,11 @@
+#
+# A simplest form of TSIG, but the algorithm name is compressed (quite
+# pathological, but we accept it)
+#
+[custom]
+sections: name:tsig
+[name]
+name: hmac-sha256
+[tsig]
+algorithm: ptr=0
+mac_size: 32
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire
new file mode 100644
index 0000000..6b8e0f3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire
@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire4.spec
+###
+
+# DNS Name: hmac-sha256
+0b686d61632d73686132353600
+
+# TSIG RDATA, RDLEN=50
+0032
+# Algorithm=ptr=0 Time-Signed=1286978795 Fudge=300
+c000 00004cb5bceb 012c
+# MAC Size=32 MAC=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec
new file mode 100644
index 0000000..da90b18
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# TSIG-like RDATA but RDLEN is too short.
+#
+[custom]
+sections: tsig
+[tsig]
+rdlen: 60
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire
new file mode 100644
index 0000000..9656ce2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire5.spec
+###
+
+# TSIG RDATA, RDLEN=60
+003c
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=32 MAC=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec
new file mode 100644
index 0000000..9d2f627
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# TSIG-like RDATA but RDLEN is too long.
+#
+[custom]
+sections: tsig
+[tsig]
+rdlen: 63
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire
new file mode 100644
index 0000000..bb1ecfb
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire6.spec
+###
+
+# TSIG RDATA, RDLEN=63
+003f
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=32 MAC=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec
new file mode 100644
index 0000000..ed7a81c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec
@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but algorithm name is broken.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: "01234567890123456789012345678901234567890123456789012345678901234"
+mac_size: 32
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire
new file mode 100644
index 0000000..327211f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire7.spec
+###
+
+# TSIG RDATA, RDLEN=117
+0075
+# Algorithm="01234567890123456789012345678901234567890123456789012345678901234" Time-Signed=1286978795 Fudge=300
+432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200 00004cb5bceb 012c
+# MAC Size=32 MAC=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec
new file mode 100644
index 0000000..0b44f87
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec
@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but MAC size is bogus
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 65535
+mac: "dummy data"
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire
new file mode 100644
index 0000000..a4209f9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire8.spec
+###
+
+# TSIG RDATA, RDLEN=41
+0029
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=65535 MAC=(see hex)
+ffff 2264756d6d79206461746122
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec
new file mode 100644
index 0000000..f512fb4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec
@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but Other-Data length is bogus
+#
+[custom]
+sections: tsig
+[tsig]
+other_len: 65535
+otherdata: "dummy data"
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire
new file mode 100644
index 0000000..b356825
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_fromWire9.spec
+###
+
+# TSIG RDATA, RDLEN=61
+003d
+# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300
+0b686d61632d73686132353600 00004cb5bceb 012c
+# MAC Size=32 MAC=(see hex)
+0020 7878787878787878787878787878787878787878787878787878787878787878
+# Original-ID=2845 Error=0
+0b1d 0000
+# Other-Len=65535 Other-Data=(see hex)
+ffff
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec
new file mode 100644
index 0000000..eb74000
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec
@@ -0,0 +1,11 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire
new file mode 100644
index 0000000..c368853
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_toWire1.spec
+###
+
+# TSIG RDATA, RDLEN=42
+002a
+# Algorithm=hmac-md5 Time-Signed=1286779327 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004cb2b1bf 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=16020 Error=17
+3e94 0011
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec
new file mode 100644
index 0000000..b2c38e9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-sha256
+time_signed: 1286779327
+mac_size: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+mac: 0x140284140284140284140284
+original_id: 16020
+error: 16
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire
new file mode 100644
index 0000000..7ea336d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_toWire2.spec
+###
+
+# TSIG RDATA, RDLEN=41
+0029
+# Algorithm=hmac-sha256 Time-Signed=1286779327 Fudge=300
+0b686d61632d73686132353600 00004cb2b1bf 012c
+# MAC Size=12 MAC=(see hex)
+000c 140284140284140284140284
+# Original-ID=16020 Error=16
+3e94 0010
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec
new file mode 100644
index 0000000..6520a08
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec
@@ -0,0 +1,15 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-sha1
+time_signed: 1286779327
+mac_size: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+mac: 0x140284140284140284140284
+original_id: 16020
+error: 18
+other_len: 6
+other_data: 0x140284140284
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire
new file mode 100644
index 0000000..535a83b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_tsig_toWire3.spec
+###
+
+# TSIG RDATA, RDLEN=45
+002d
+# Algorithm=hmac-sha1 Time-Signed=1286779327 Fudge=300
+09686d61632d7368613100 00004cb2b1bf 012c
+# MAC Size=12 MAC=(see hex)
+000c 140284140284140284140284
+# Original-ID=16020 Error=18
+3e94 0012
+# Other-Len=6 Other-Data=(see hex)
+0006 140284140284
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec
new file mode 100644
index 0000000..d95cd23
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: name:tsig
+[name]
+name: hmac-md5.sig-alg.reg.int.
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire
new file mode 100644
index 0000000..5e8f68b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire
@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from rdata_tsig_toWire4.spec
+###
+
+# DNS Name: hmac-md5.sig-alg.reg.int.
+08686d61632d6d6435077369672d616c670372656703696e7400
+
+# TSIG RDATA, RDLEN=42
+002a
+# Algorithm=hmac-md5 Time-Signed=1286779327 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004cb2b1bf 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=16020 Error=17
+3e94 0011
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec
new file mode 100644
index 0000000..81e3a78
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec
@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig:name
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
+[name]
+name: ptr=2
diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire
new file mode 100644
index 0000000..bc83de6
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire
@@ -0,0 +1,17 @@
+###
+### This data file was auto-generated from rdata_tsig_toWire5.spec
+###
+
+# TSIG RDATA, RDLEN=42
+002a
+# Algorithm=hmac-md5 Time-Signed=1286779327 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004cb2b1bf 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=16020 Error=17
+3e94 0011
+# Other-Len=0 Other-Data=(see hex)
+0000
+
+# DNS Name: ptr=2
+c002
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire1 b/src/lib/dns/tests/testdata/rdata_txt_fromWire1
new file mode 100644
index 0000000..547d76f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire1
@@ -0,0 +1,9 @@
+#
+# various kinds of TXT RDATA stored in an input buffer
+#
+# Valid RDATA for "Test-String"
+#
+# RDLENGTH=12 bytes
+ 00 0c
+# T e s t - S t r i n g
+ 0b 54 65 73 74 2d 53 74 72 69 6e 67
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec
new file mode 100644
index 0000000..c5829d9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec
@@ -0,0 +1,8 @@
+#
+# TXT RDATA with empty character-string. unusual, but valid.
+#
+
+[custom]
+sections: txt
+[txt]
+string: ''
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire
new file mode 100644
index 0000000..83ebea3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_txt_fromWire2.spec
+###
+
+# TXT RDATA, RDLEN=1
+0001
+# String Len=0, String=""
+00
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec
new file mode 100644
index 0000000..fe5c129
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec
@@ -0,0 +1,8 @@
+#
+# TXT RDATA with multiple character-strings.
+#
+
+[custom]
+sections: txt
+[txt]
+nstring: 2
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire
new file mode 100644
index 0000000..42fdf76
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_txt_fromWire3.spec
+###
+
+# TXT RDATA, RDLEN=24
+0018
+# String Len=11, String="Test-String"
+0b 546573742d537472696e67
+# String Len=11, String="Test-String"
+0b 546573742d537472696e67
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec
new file mode 100644
index 0000000..0e015d4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec
@@ -0,0 +1,9 @@
+#
+# Malformed TXT RDATA: RDLEN is 0
+#
+
+[custom]
+sections: txt
+[txt]
+rdlen: 0
+# following data is provided, but that doesn't matter.
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire
new file mode 100644
index 0000000..6ac73b8
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_txt_fromWire4.spec
+###
+
+# TXT RDATA, RDLEN=0
+0000
+# String Len=11, String="Test-String"
+0b 546573742d537472696e67
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec
new file mode 100644
index 0000000..7710cdf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec
@@ -0,0 +1,9 @@
+#
+# Malformed TXT RDATA: character-string length is too large
+#
+
+[custom]
+sections: txt
+[txt]
+stringlen: 255
+string: 'too short'
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire
new file mode 100644
index 0000000..ea7a9cf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_txt_fromWire5.spec
+###
+
+# TXT RDATA, RDLEN=10
+000a
+# String Len=255, String="too short"
+ff 746f6f2073686f7274
diff --git a/src/lib/dns/tests/testdata/rdata_unknown_fromWire b/src/lib/dns/tests/testdata/rdata_unknown_fromWire
new file mode 100644
index 0000000..69ea8ff
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_unknown_fromWire
@@ -0,0 +1,13 @@
+#
+# various kinds of "unknown" RDATA stored in an input buffer
+#
+# 0 1 2 3 4 5 (bytes)
+ 00 04 a1 b2 c3 0d
+#
+# 0-length data
+# 6 7
+ 00 00
+#
+# short buffer (this can be tested only at the end of the buffer)
+# 8 9 10 1 2
+ 00 04 a1 b2 c3
diff --git a/src/lib/dns/tests/testdata/rrcode16_fromWire1 b/src/lib/dns/tests/testdata/rrcode16_fromWire1
new file mode 100644
index 0000000..df2a177
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrcode16_fromWire1
@@ -0,0 +1,4 @@
+#
+# a 16 bit wire-format data (network byte order)
+#
+1234
diff --git a/src/lib/dns/tests/testdata/rrcode16_fromWire2 b/src/lib/dns/tests/testdata/rrcode16_fromWire2
new file mode 100644
index 0000000..fec2dd0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrcode16_fromWire2
@@ -0,0 +1,4 @@
+#
+# an incomplete segment for a 16 bit wire-format data
+#
+12
diff --git a/src/lib/dns/tests/testdata/rrcode32_fromWire1 b/src/lib/dns/tests/testdata/rrcode32_fromWire1
new file mode 100644
index 0000000..fb2818f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrcode32_fromWire1
@@ -0,0 +1,4 @@
+#
+# a 32 bit wire-format data (network byte order)
+#
+12345678
diff --git a/src/lib/dns/tests/testdata/rrcode32_fromWire2 b/src/lib/dns/tests/testdata/rrcode32_fromWire2
new file mode 100644
index 0000000..504d9d2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrcode32_fromWire2
@@ -0,0 +1,4 @@
+#
+# an incomplete segment for a 32 bit wire-format data
+#
+123456
diff --git a/src/lib/dns/tests/testdata/rrset_toWire1 b/src/lib/dns/tests/testdata/rrset_toWire1
new file mode 100644
index 0000000..8f81a0e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrset_toWire1
@@ -0,0 +1,23 @@
+#
+# Rendering an IN/A RRset containing 2 RRs:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 3600 IN A 192.0.2.2
+#
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, IN = 1
+00 01 00 01
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 4
+00 04
+# RDATA: 192.0.2.1
+c0 00 02 01
+#
+# 2nd RR: mostly the same except the RDATA
+04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+00 01 00 01
+00 00 0e 10
+00 04
+c0 00 02 02
diff --git a/src/lib/dns/tests/testdata/rrset_toWire2 b/src/lib/dns/tests/testdata/rrset_toWire2
new file mode 100644
index 0000000..ca6483f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrset_toWire2
@@ -0,0 +1,38 @@
+#
+# Rendering an IN/A RRset and NS RRset as follows:
+# test.example.com. 3600 IN A 192.0.2.1
+# test.example.com. 3600 IN A 192.0.2.2
+# example.com. 1D IN NS ns.example.com.
+# Names will be compressed when possible.
+#
+# 0 1 2 3 4 5
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, IN = 1
+00 01 00 01
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 4
+00 04
+# RDATA: 192.0.2.1
+c0 00 02 01
+#
+# 2nd RR: the owner name is compressed
+c0 00
+00 01 00 01
+00 00 0e 10
+00 04
+c0 00 02 02
+# 3rd RR: the owner name and NS name are compressed
+# pointing to the 5th octet of the owner name of the 1st RR
+c0 05
+# type/class: NS = 2, IN = 1
+00 02 00 01
+# TTL: 1D = 86400sec = 0x15180
+00 01 51 80
+# RDLENGTH: 5 octets
+00 05
+# NSDNAME: "ns." + compression pointer
+#(2) n s
+ 02 6e 73 c0 05
diff --git a/src/lib/dns/tests/testdata/rrset_toWire3 b/src/lib/dns/tests/testdata/rrset_toWire3
new file mode 100644
index 0000000..47f8e6b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrset_toWire3
@@ -0,0 +1,12 @@
+#
+# Rendering an empty IN/A RRset
+#
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, ANY = 255
+00 01 00 ff
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 0
+00 00
diff --git a/src/lib/dns/tests/testdata/rrset_toWire4 b/src/lib/dns/tests/testdata/rrset_toWire4
new file mode 100644
index 0000000..6fb409c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrset_toWire4
@@ -0,0 +1,12 @@
+#
+# Rendering an empty IN/A RRset
+#
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, ANY = 255
+00 01 00 fe
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 0
+00 00
diff --git a/src/lib/dns/tests/testdata/tsig_verify1.spec b/src/lib/dns/tests/testdata/tsig_verify1.spec
new file mode 100644
index 0000000..687013a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify1.spec
@@ -0,0 +1,19 @@
+#
+# An example of signed AXFR request
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x3410
+arcount: 1
+[question]
+rrtype: AXFR
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0x35b2fd08268781634400c7c8a5533b13
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify1.wire b/src/lib/dns/tests/testdata/tsig_verify1.wire
new file mode 100644
index 0000000..b2d12f1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify1.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify1.spec
+###
+
+# Header Section
+# ID=13328 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0)
+3410 0000
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=AXFR(252) QCLASS=IN(1)
+076578616d706c6503636f6d00 00fc 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302915409 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8e951 012c
+# MAC Size=16 MAC=(see hex)
+0010 35b2fd08268781634400c7c8a5533b13
+# Original-ID=13328 Error=0
+3410 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify10.spec b/src/lib/dns/tests/testdata/tsig_verify10.spec
new file mode 100644
index 0000000..33ce83e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify10.spec
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with TSIG signed whose MAC is too short
+# (only 1 byte)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 1
+mac: 0x22
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify10.wire b/src/lib/dns/tests/testdata/tsig_verify10.wire
new file mode 100644
index 0000000..9ffe4ea
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify10.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify10.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=43)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 002b
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=1 MAC=(see hex)
+0001 22
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify11.spec b/src/lib/dns/tests/testdata/tsig_verify11.spec
new file mode 100644
index 0000000..9927b48
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify11.spec
@@ -0,0 +1,24 @@
+#
+# A simple DNS query message with TSIG signed with truncated MAC
+# using common HMAC-SHA512-256
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-sha512
+time_signed: 0x4da8877a
+#mac_size: 64
+mac_size: 32
+#mac: 0xc4bc4053572d62dd1b26998111565c18056be773dedc6ecea60dff31db2f25966e5d9bafbaaed56efbd5ee2d7e2a12ede4caad630ddf69846c980409724da34e
+mac: 0xc4bc4053572d62dd1b26998111565c18056be773dedc6ecea60dff31db2f2596
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify11.wire b/src/lib/dns/tests/testdata/tsig_verify11.wire
new file mode 100644
index 0000000..f0a8f4f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify11.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify11.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=61)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003d
+# Algorithm=hmac-sha512 Time-Signed=1302890362 Fudge=300
+0b686d61632d73686135313200 00004da8877a 012c
+# MAC Size=32 MAC=(see hex)
+0020 c4bc4053572d62dd1b26998111565c18056be773dedc6ecea60dff31db2f2596
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify2.spec b/src/lib/dns/tests/testdata/tsig_verify2.spec
new file mode 100644
index 0000000..ff98ca3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify2.spec
@@ -0,0 +1,32 @@
+#
+# An example of signed AXFR response
+#
+
+[custom]
+sections: header:question:soa:tsig
+[header]
+id: 0x3410
+aa: 1
+qr: 1
+ancount: 1
+arcount: 1
+[question]
+rrtype: AXFR
+[soa]
+# note that names are compressed in this RR
+as_rr: True
+rr_name: ptr=12
+mname: ns.ptr=12
+rname: root.ptr=12
+serial: 2011041503
+refresh: 7200
+retry: 3600
+expire: 2592000
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0xbdd612cd2c7f9e0648bd6dc23713e83c
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify2.wire b/src/lib/dns/tests/testdata/tsig_verify2.wire
new file mode 100644
index 0000000..65f5f89
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify2.wire
@@ -0,0 +1,31 @@
+###
+### This data file was auto-generated from tsig_verify2.spec
+###
+
+# Header Section
+# ID=13328 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA
+3410 8400
+# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=1
+0001 0001 0000 0001
+
+# Question Section
+# QNAME=example.com. QTYPE=AXFR(252) QCLASS=IN(1)
+076578616d706c6503636f6d00 00fc 0001
+
+# SOA RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=32)
+c00c 0006 0001 00015180 0020
+# NNAME=ns.ptr=12 RNAME=root.ptr=12
+026e73c00c 04726f6f74c00c
+# SERIAL(2011041503) REFRESH(7200) RETRY(3600) EXPIRE(2592000) MINIMUM(1200)
+77de0edf 00001c20 00000e10 00278d00 000004b0
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302915409 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8e951 012c
+# MAC Size=16 MAC=(see hex)
+0010 bdd612cd2c7f9e0648bd6dc23713e83c
+# Original-ID=13328 Error=0
+3410 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify3.spec b/src/lib/dns/tests/testdata/tsig_verify3.spec
new file mode 100644
index 0000000..7e2f797
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify3.spec
@@ -0,0 +1,26 @@
+#
+# An example of signed AXFR response (continued)
+#
+
+[custom]
+sections: header:ns:tsig
+[header]
+id: 0x3410
+aa: 1
+qr: 1
+qdcount: 0
+ancount: 1
+arcount: 1
+[ns]
+# note that names are compressed in this RR
+as_rr: True
+rr_name: example.com.
+nsname: ns.ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0x102458f7f62ddd7d638d746034130968
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify3.wire b/src/lib/dns/tests/testdata/tsig_verify3.wire
new file mode 100644
index 0000000..c479ac3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify3.wire
@@ -0,0 +1,25 @@
+###
+### This data file was auto-generated from tsig_verify3.spec
+###
+
+# Header Section
+# ID=13328 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA
+3410 8400
+# QDCNT=0, ANCNT=1, NSCNT=0, ARCNT=1
+0000 0001 0000 0001
+
+# NS RR (QNAME=example.com. Class=IN(1) TTL=86400, RDLEN=5)
+076578616d706c6503636f6d00 0002 0001 00015180 0005
+# NS name=ns.ptr=12
+026e73c00c
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302915409 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8e951 012c
+# MAC Size=16 MAC=(see hex)
+0010 102458f7f62ddd7d638d746034130968
+# Original-ID=13328 Error=0
+3410 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify4.spec b/src/lib/dns/tests/testdata/tsig_verify4.spec
new file mode 100644
index 0000000..4ffbbcf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify4.spec
@@ -0,0 +1,27 @@
+#
+# An example of signed DNS response with bogus MAC
+#
+
+[custom]
+sections: header:question:a:tsig
+[header]
+id: 0x2d65
+aa: 1
+qr: 1
+rd: 1
+ancount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a]
+as_rr: True
+rr_name: ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+# bogus MAC
+mac: 0xdeadbeefdeadbeefdeadbeefdeadbeef
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify4.wire b/src/lib/dns/tests/testdata/tsig_verify4.wire
new file mode 100644
index 0000000..de5e050
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify4.wire
@@ -0,0 +1,29 @@
+###
+### This data file was auto-generated from tsig_verify4.spec
+###
+
+# Header Section
+# ID=11621 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA RD
+2d65 8500
+# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=1
+0001 0001 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=4)
+c00c 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 deadbeefdeadbeefdeadbeefdeadbeef
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify5.spec b/src/lib/dns/tests/testdata/tsig_verify5.spec
new file mode 100644
index 0000000..a6cc643
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify5.spec
@@ -0,0 +1,26 @@
+#
+# An example of signed DNS response
+#
+
+[custom]
+sections: header:question:a:tsig
+[header]
+id: 0x2d65
+aa: 1
+qr: 1
+rd: 1
+ancount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a]
+as_rr: True
+rr_name: ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x8fcda66a7cd1a3b9948eb1869d384a9f
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify5.wire b/src/lib/dns/tests/testdata/tsig_verify5.wire
new file mode 100644
index 0000000..3cfb89b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify5.wire
@@ -0,0 +1,29 @@
+###
+### This data file was auto-generated from tsig_verify5.spec
+###
+
+# Header Section
+# ID=11621 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA RD
+2d65 8500
+# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=1
+0001 0001 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# A RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=4)
+c00c 0001 0001 00015180 0004
+# Address=192.0.2.1
+c0000201
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 8fcda66a7cd1a3b9948eb1869d384a9f
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify6.spec b/src/lib/dns/tests/testdata/tsig_verify6.spec
new file mode 100644
index 0000000..32e0818
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify6.spec
@@ -0,0 +1,21 @@
+#
+# Forwarded DNS query message with TSIG signed (header ID != orig ID)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x1035
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify6.wire b/src/lib/dns/tests/testdata/tsig_verify6.wire
new file mode 100644
index 0000000..9e3e5b4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify6.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify6.spec
+###
+
+# Header Section
+# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+1035 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify7.spec b/src/lib/dns/tests/testdata/tsig_verify7.spec
new file mode 100644
index 0000000..377578e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify7.spec
@@ -0,0 +1,21 @@
+#
+# DNS query message with TSIG that has empty MAC (invalidly)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 0
+mac: ''
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify7.wire b/src/lib/dns/tests/testdata/tsig_verify7.wire
new file mode 100644
index 0000000..4de7d24
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify7.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify7.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=42)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 002a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify8.spec b/src/lib/dns/tests/testdata/tsig_verify8.spec
new file mode 100644
index 0000000..5432d4a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify8.spec
@@ -0,0 +1,23 @@
+#
+# DNS query message with TSIG that has empty MAC + BADKEY error
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 0
+mac: ''
+# 17: BADKEY
+error: 17
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify8.wire b/src/lib/dns/tests/testdata/tsig_verify8.wire
new file mode 100644
index 0000000..50845eb
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify8.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify8.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=42)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 002a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=0 MAC=(see hex)
+0000
+# Original-ID=11621 Error=17
+2d65 0011
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsig_verify9.spec b/src/lib/dns/tests/testdata/tsig_verify9.spec
new file mode 100644
index 0000000..5888455
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify9.spec
@@ -0,0 +1,21 @@
+#
+# A simple DNS query message with TSIG signed, but TSIG key and algorithm
+# names have upper case characters (unusual)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: WWW.EXAMPLE.COM
+algorithm: HMAC-MD5.SIG-ALG.REG.INT
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify9.wire b/src/lib/dns/tests/testdata/tsig_verify9.wire
new file mode 100644
index 0000000..163fcee
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify9.wire
@@ -0,0 +1,24 @@
+###
+### This data file was auto-generated from tsig_verify9.spec
+###
+
+# Header Section
+# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD
+2d65 0100
+# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1
+0001 0000 0000 0001
+
+# Question Section
+# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1)
+03777777076578616d706c6503636f6d00 0001 0001
+
+# TSIG RR (QNAME=WWW.EXAMPLE.COM Class=ANY(255) TTL=0, RDLEN=58)
+03575757074558414d504c4503434f4d00 00fa 00ff 00000000 003a
+# Algorithm=HMAC-MD5.SIG-ALG.REG.INT Time-Signed=1302890362 Fudge=300
+08484d41432d4d4435075349472d414c470352454703494e5400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 227026ad297beee721ce6c6fff1e9ef3
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire1.spec b/src/lib/dns/tests/testdata/tsigrecord_toWire1.spec
new file mode 100644
index 0000000..a25dc46
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsigrecord_toWire1.spec
@@ -0,0 +1,16 @@
+#
+# A simple TSIG RR (some of the parameters are taken from a live example
+# and don't have a specific meaning)
+#
+
+[custom]
+sections: tsig
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0xdadadadadadadadadadadadadadadada
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire1.wire b/src/lib/dns/tests/testdata/tsigrecord_toWire1.wire
new file mode 100644
index 0000000..a125e3d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsigrecord_toWire1.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from tsigrecord_toWire1.spec
+###
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 dadadadadadadadadadadadadadadada
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire2.spec b/src/lib/dns/tests/testdata/tsigrecord_toWire2.spec
new file mode 100644
index 0000000..f667e4c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsigrecord_toWire2.spec
@@ -0,0 +1,19 @@
+#
+# TSIG RR after some names that could (unexpectedly) cause name compression
+#
+
+[custom]
+sections: name/1:name/2:tsig
+[name/1]
+name: hmac-md5.sig-alg.reg.int
+[name/2]
+name: foo.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0xdadadadadadadadadadadadadadadada
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire2.wire b/src/lib/dns/tests/testdata/tsigrecord_toWire2.wire
new file mode 100644
index 0000000..980e1f1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsigrecord_toWire2.wire
@@ -0,0 +1,20 @@
+###
+### This data file was auto-generated from tsigrecord_toWire2.spec
+###
+
+# DNS Name: hmac-md5.sig-alg.reg.int
+08686d61632d6d6435077369672d616c670372656703696e7400
+
+# DNS Name: foo.example.com
+03666f6f076578616d706c6503636f6d00
+
+# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58)
+03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a
+# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300
+08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c
+# MAC Size=16 MAC=(see hex)
+0010 dadadadadadadadadadadadadadadada
+# Original-ID=11621 Error=0
+2d65 0000
+# Other-Len=0 Other-Data=(see hex)
+0000
diff --git a/src/lib/dns/tests/time_utils_unittest.cc b/src/lib/dns/tests/time_utils_unittest.cc
new file mode 100644
index 0000000..320b6ce
--- /dev/null
+++ b/src/lib/dns/tests/time_utils_unittest.cc
@@ -0,0 +1,147 @@
+#include <config.h>
+
+#include <dns/time_utils.h>
+
+#include <ctime>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace std;
+using namespace isc::util;
+
+// See time_utilities.cc
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*getTimeFunction)();
+}
+} // namespace util
+} // namespace isc
+
+namespace {
+
+class DNSSECTimeTest : public ::testing::Test {
+protected:
+ ~DNSSECTimeTest() {
+ detail::getTimeFunction = 0;
+ }
+};
+
+TEST_F(DNSSECTimeTest, fromText) {
+ // In most cases (in practice) the 32-bit and 64-bit versions should
+ // behave identically, so we'll mainly test the 32-bit version, which
+ // will be more commonly used in actual code (because many of the wire
+ // format time field are 32-bit). The subtle cases where these two
+ // return different values will be tested at the end of this test case.
+
+ // These are bogus and should be rejected
+ EXPECT_THROW(timeFromText32("2011 101120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("201101011200-0"), InvalidTime);
+
+ // Short length (or "decimal integer" version of representation;
+ // it's valid per RFC4034, but is not supported in this implementation)
+ EXPECT_THROW(timeFromText32("20100223"), InvalidTime);
+
+ // Leap year checks
+ EXPECT_THROW(timeFromText32("20110229120000"), InvalidTime);
+ EXPECT_THROW(timeFromText32("21000229120000"), InvalidTime);
+ EXPECT_NO_THROW(timeFromText32("20000229120000"));
+ EXPECT_NO_THROW(timeFromText32("20120229120000"));
+
+ // unusual case: this implementation allows SS=60 for "leap seconds"
+ EXPECT_NO_THROW(timeFromText32("20110101120060"));
+
+ // Out of range parameters
+ EXPECT_THROW(timeFromText32("19100223214617"), InvalidTime); // YY<1970
+ EXPECT_THROW(timeFromText32("20110001120000"), InvalidTime); // MM=00
+ EXPECT_THROW(timeFromText32("20111301120000"), InvalidTime); // MM=13
+ EXPECT_THROW(timeFromText32("20110100120000"), InvalidTime); // DD=00
+ EXPECT_THROW(timeFromText32("20110132120000"), InvalidTime); // DD=32
+ EXPECT_THROW(timeFromText32("20110431120000"), InvalidTime); // 'Apr31'
+ EXPECT_THROW(timeFromText32("20110101250000"), InvalidTime); // HH=25
+ EXPECT_THROW(timeFromText32("20110101126000"), InvalidTime); // mm=60
+ EXPECT_THROW(timeFromText32("20110101120061"), InvalidTime); // SS=61
+
+ // Feb 7, 06:28:15 UTC 2106 is the possible maximum time that can be
+ // represented as an unsigned 32bit integer without overflow.
+ EXPECT_EQ(4294967295LU, timeFromText32("21060207062815"));
+
+ // After that, timeFromText32() should start returning the second count
+ // modulo 2^32.
+ EXPECT_EQ(0, timeFromText32("21060207062816"));
+ EXPECT_EQ(10, timeFromText32("21060207062826"));
+
+ // On the other hand, the 64-bit version should return monotonically
+ // increasing counters.
+ EXPECT_EQ(4294967296LL, timeFromText64("21060207062816"));
+ EXPECT_EQ(4294967306LL, timeFromText64("21060207062826"));
+}
+
+// This helper templated function tells timeToText32 a faked current time.
+// The template parameter is that faked time in the form of int64_t seconds
+// since epoch.
+template <int64_t NOW>
+int64_t
+testGetTime() {
+ return (NOW);
+}
+
+// Seconds since epoch for the year 10K eve. Commonly used in some tests
+// below.
+constexpr uint64_t YEAR10K_EVE = 253402300799LL;
+
+TEST_F(DNSSECTimeTest, toText) {
+ // Check a basic case with the default (normal) getTimeFunction
+ // based on the "real current time".
+ // Note: this will fail after year 2078, but at that point we won't use
+ // this program anyway:-)
+ EXPECT_EQ("20100311233000", timeToText32(1268350200));
+
+ // Set the current time to: Feb 18 09:04:14 UTC 2012 (an arbitrary choice
+ // in the range of the first half of uint32 since epoch).
+ detail::getTimeFunction = testGetTime<1329555854LL>;
+
+ // Test the "year 2038" problem.
+ // Check the result of toText() for "INT_MIN" in int32_t. It's in the
+ // 68-year range from the faked current time, so the result should be
+ // in year 2038, instead of 1901.
+ EXPECT_EQ("20380119031408", timeToText64(0x80000000L));
+ EXPECT_EQ("20380119031408", timeToText32(0x80000000L));
+
+ // A controversial case: what should we do with "-1"? It's out of range
+ // in future, but according to RFC time before epoch doesn't seem to be
+ // considered "in-range" either. Our toText() implementation handles
+ // this range as a special case and always treats them as future time
+ // until year 2038. This won't be a real issue in practice, though,
+ // since such too large values won't be used in actual deployment by then.
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // After the singular point of year 2038, the first half of uint32 can
+ // point to a future time.
+ // Set the current time to: Apr 1 00:00:00 UTC 2038:
+ detail::getTimeFunction = testGetTime<2153692800LL>;
+ // then time "10" is Feb 7 06:28:26 UTC 2106
+ EXPECT_EQ("21060207062826", timeToText32(10));
+ // in 64-bit, it's 2^32 + 10
+ EXPECT_EQ("21060207062826", timeToText64(0x10000000aLL));
+
+ // After year 2106, the upper half of uint32 can point to past time
+ // (as it should).
+ detail::getTimeFunction = testGetTime<0x10000000aLL>;
+ EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+ // Try very large time value. Actually it's the possible farthest time
+ // that can be represented in the form of YYYYMMDDHHmmSS.
+ EXPECT_EQ("99991231235959", timeToText64(YEAR10K_EVE));
+ detail::getTimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_EQ("99991231235959", timeToText32(4294197631LU));
+}
+
+TEST_F(DNSSECTimeTest, overflow) {
+ // Jan 1, Year 10,000.
+ EXPECT_THROW(timeToText64(253402300800LL), InvalidTime);
+ detail::getTimeFunction = testGetTime<YEAR10K_EVE - 10>;
+ EXPECT_THROW(timeToText32(4294197632LU), InvalidTime);
+}
+
+} // namespace \ No newline at end of file
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
new file mode 100644
index 0000000..2eb418b
--- /dev/null
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -0,0 +1,1177 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/question.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
+#include <dns/tests/unittest_util.h>
+#include <util/buffer.h>
+#include <util/encode/encode.h>
+#include <util/unittests/newhook.h>
+#include <util/unittests/wiredata.h>
+
+#include <time.h>
+#include <string>
+#include <stdexcept>
+#include <vector>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata;
+using namespace isc::dns::rdata::any;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+// @note: blocks and SCOPED_TRACE can make buggy cppchecks raise
+// a spurious syntax error...
+
+// See dnssectime.cc
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*getTimeFunction)();
+}
+}
+}
+
+namespace {
+// See dnssectime_unittest.cc
+template <int64_t NOW>
+int64_t
+testGetTime() {
+ return (NOW);
+}
+
+// Thin wrapper around TSIGContext to allow access to the
+// update method.
+class TestTSIGContext : public TSIGContext {
+public:
+ TestTSIGContext(const TSIGKey& key) :
+ TSIGContext(key) {
+ }
+ TestTSIGContext(const Name& key_name, const Name& algorithm_name,
+ const TSIGKeyRing& keyring) :
+ TSIGContext(key_name, algorithm_name, keyring) {
+ }
+ void update(const void* const data, size_t len) {
+ TSIGContext::update(data, len);
+ }
+};
+
+class TSIGTest : public ::testing::Test {
+protected:
+ TSIGTest() :
+ tsig_ctx(0), qid(0x2d65), test_name("www.example.com"),
+ badkey_name("badkey.example.com"), test_class(RRClass::IN()),
+ test_ttl(86400), message(Message::RENDER),
+ dummy_data(1024, 0xdd), // should be sufficiently large for all tests
+ dummy_record(badkey_name, TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a,
+ TSIGContext::DEFAULT_FUDGE, 0, 0, qid, 0, 0, 0)) {
+ // Make sure we use the system time by default so that we won't be
+ // confused due to other tests that tweak the time.
+ isc::util::detail::getTimeFunction = 0;
+
+ decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret);
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACMD5_NAME(),
+ &secret[0],
+ secret.size())));
+ tsig_verify_ctx.reset(new TSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACMD5_NAME(),
+ &secret[0],
+ secret.size())));
+ }
+ ~TSIGTest() {
+ isc::util::detail::getTimeFunction = 0;
+ }
+
+ // Many of the tests below create some DNS message and sign it under
+ // some specific TSIG context. This helper method unifies the common
+ // logic with slightly different parameters.
+ ConstTSIGRecordPtr createMessageAndSign(uint16_t qid, const Name& qname,
+ TSIGContext* ctx,
+ unsigned int message_flags =
+ RD_FLAG,
+ RRType qtype = RRType::A(),
+ const char* answer_data = 0,
+ const RRType* answer_type = 0,
+ bool add_question = true,
+ Rcode rcode = Rcode::NOERROR());
+
+ void createMessageFromFile(const char* datafile);
+
+ // bit-wise constant flags to configure DNS header flags for test
+ // messages.
+ static const unsigned int QR_FLAG = 0x1;
+ static const unsigned int AA_FLAG = 0x2;
+ static const unsigned int RD_FLAG = 0x4;
+
+ boost::scoped_ptr<TestTSIGContext> tsig_ctx;
+ boost::scoped_ptr<TSIGContext> tsig_verify_ctx;
+ TSIGKeyRing keyring;
+ const uint16_t qid;
+ const Name test_name;
+ const Name badkey_name;
+ const RRClass test_class;
+ const RRTTL test_ttl;
+ Message message;
+ MessageRenderer renderer;
+ vector<uint8_t> secret;
+ vector<uint8_t> dummy_data;
+ const TSIGRecord dummy_record;
+ vector<uint8_t> received_data;
+};
+
+ConstTSIGRecordPtr
+TSIGTest::createMessageAndSign(uint16_t id, const Name& qname,
+ TSIGContext* ctx, unsigned int message_flags,
+ RRType qtype, const char* answer_data,
+ const RRType* answer_type, bool add_question,
+ Rcode rcode) {
+ message.clear(Message::RENDER);
+ message.setQid(id);
+ message.setOpcode(Opcode::QUERY());
+ message.setRcode(rcode);
+ if ((message_flags & QR_FLAG) != 0) {
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ }
+ if ((message_flags & AA_FLAG) != 0) {
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ }
+ if ((message_flags & RD_FLAG) != 0) {
+ message.setHeaderFlag(Message::HEADERFLAG_RD);
+ }
+ if (add_question) {
+ message.addQuestion(Question(qname, test_class, qtype));
+ }
+ if (answer_data) {
+ if (!answer_type) {
+ answer_type = &qtype;
+ }
+ RRsetPtr answer_rrset(new RRset(qname, test_class, *answer_type,
+ test_ttl));
+ answer_rrset->addRdata(createRdata(*answer_type, test_class,
+ answer_data));
+ message.addRRset(Message::SECTION_ANSWER, answer_rrset);
+ }
+ renderer.clear();
+
+ TSIGContext::State expected_new_state =
+ (ctx->getState() == TSIGContext::INIT) ?
+ TSIGContext::SENT_REQUEST : TSIGContext::SENT_RESPONSE;
+
+ message.toWire(renderer, ctx);
+
+ message.clear(Message::PARSE);
+ InputBuffer buffer(renderer.getData(), renderer.getLength());
+ message.fromWire(buffer);
+
+ EXPECT_EQ(expected_new_state, ctx->getState());
+
+ return (ConstTSIGRecordPtr(new TSIGRecord(*message.getTSIGRecord())));
+}
+
+void
+TSIGTest::createMessageFromFile(const char* datafile) {
+ message.clear(Message::PARSE);
+ received_data.clear();
+ UnitTestUtil::readWireData(datafile, received_data);
+ InputBuffer buffer(&received_data[0], received_data.size());
+ message.fromWire(buffer);
+}
+
+void
+commonSignChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
+ uint64_t expected_timesigned,
+ const uint8_t* expected_mac, size_t expected_maclen,
+ uint16_t expected_error = 0,
+ uint16_t expected_otherlen = 0,
+ const uint8_t* expected_otherdata = 0,
+ const Name& expected_algorithm = TSIGKey::HMACMD5_NAME()) {
+ ASSERT_TRUE(tsig);
+ const TSIG& tsig_rdata = tsig->getRdata();
+
+ EXPECT_EQ(expected_algorithm, tsig_rdata.getAlgorithm());
+ EXPECT_EQ(expected_timesigned, tsig_rdata.getTimeSigned());
+ EXPECT_EQ(300, tsig_rdata.getFudge());
+ EXPECT_EQ(expected_maclen, tsig_rdata.getMACSize());
+ matchWireData(expected_mac, expected_maclen,
+ tsig_rdata.getMAC(), tsig_rdata.getMACSize());
+
+ EXPECT_EQ(expected_qid, tsig_rdata.getOriginalID());
+ EXPECT_EQ(expected_error, tsig_rdata.getError());
+ EXPECT_EQ(expected_otherlen, tsig_rdata.getOtherLen());
+ matchWireData(expected_otherdata, expected_otherlen,
+ tsig_rdata.getOtherData(), tsig_rdata.getOtherLen());
+}
+
+void
+commonVerifyChecks(TSIGContext& ctx, const TSIGRecord* record,
+ const void* data, size_t data_len, TSIGError expected_error,
+ TSIGContext::State expected_new_state =
+ TSIGContext::VERIFIED_RESPONSE,
+ bool last_should_throw = false) {
+ EXPECT_EQ(expected_error, ctx.verify(record, data, data_len));
+ EXPECT_EQ(expected_error, ctx.getError());
+ EXPECT_EQ(expected_new_state, ctx.getState());
+ if (last_should_throw) {
+ EXPECT_THROW(ctx.lastHadSignature(), TSIGContextError);
+ } else {
+ EXPECT_EQ(record != 0, ctx.lastHadSignature());
+ }
+}
+
+TEST_F(TSIGTest, initialState) {
+ // Until signing or verifying, the state should be INIT
+ EXPECT_EQ(TSIGContext::INIT, tsig_ctx->getState());
+
+ // And there should be no error code.
+ EXPECT_EQ(TSIGError(Rcode::NOERROR()), tsig_ctx->getError());
+
+ // Nothing verified yet
+ EXPECT_THROW(tsig_ctx->lastHadSignature(), TSIGContextError);
+}
+
+TEST_F(TSIGTest, constructFromKeyRing) {
+ // Construct a TSIG context with an empty key ring. Key shouldn't be
+ // found, and the BAD_KEY error should be recorded.
+ TSIGContext ctx1(test_name, TSIGKey::HMACMD5_NAME(), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx1.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx1.getError());
+
+ // Add a matching key (we don't use the secret so leave it empty), and
+ // construct it again. This time it should be constructed with a valid
+ // key.
+ keyring.add(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(), 0, 0));
+ TSIGContext ctx2(test_name, TSIGKey::HMACMD5_NAME(), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx2.getState());
+ EXPECT_EQ(TSIGError::NOERROR(), ctx2.getError());
+
+ // Similar to the first case except that the key ring isn't empty but
+ // it doesn't contain a matching key.
+ TSIGContext ctx3(test_name, TSIGKey::HMACSHA1_NAME(), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx3.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx3.getError());
+
+ TSIGContext ctx4(Name("different-key.example"), TSIGKey::HMACMD5_NAME(),
+ keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx4.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx4.getError());
+
+ // "Unknown" algorithm name will result in BADKEY, too.
+ TSIGContext ctx5(test_name, Name("unknown.algorithm"), keyring);
+ EXPECT_EQ(TSIGContext::INIT, ctx5.getState());
+ EXPECT_EQ(TSIGError::BAD_KEY(), ctx5.getError());
+}
+
+// Example output generated by
+// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com
+// QID: 0x2d65
+// Time Signed: 0x00004da8877a
+// MAC: 227026ad297beee721ce6c6fff1e9ef3
+const uint8_t common_expected_mac[] = {
+ 0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7,
+ 0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
+};
+TEST_F(TSIGTest, sign) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ {
+ SCOPED_TRACE("Sign test for query");
+ commonSignChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+ qid, 0x4da8877a, common_expected_mac,
+ sizeof(common_expected_mac));
+ }
+}
+
+// Same test as sign, but specifying the key name with upper-case (i.e.
+// non canonical) characters. The digest must be the same. It should actually
+// be ensured at the level of TSIGKey, but we confirm that at this level, too.
+TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"),
+ TSIGKey::HMACMD5_NAME(),
+ &secret[0], secret.size()));
+
+ {
+ SCOPED_TRACE("Sign test for query using non canonical key name");
+ commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+ 0x4da8877a, common_expected_mac,
+ sizeof(common_expected_mac));
+ }
+}
+
+// Same as the previous test, but for the algorithm name.
+TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ TSIGContext cap_ctx(TSIGKey(test_name,
+ Name("HMAC-md5.SIG-alg.REG.int"),
+ &secret[0], secret.size()));
+
+ {
+ SCOPED_TRACE("Sign test for query using non canonical algorithm name");
+ commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+ 0x4da8877a, common_expected_mac,
+ sizeof(common_expected_mac));
+ }
+}
+
+TEST_F(TSIGTest, signAtActualTime) {
+ // Sign the message using the actual time, and check the accuracy of it.
+ // We cannot reasonably predict the expected MAC, so don't bother to
+ // check it.
+ const uint64_t now = static_cast<uint64_t>(time(0));
+
+ {
+ SCOPED_TRACE("Sign test for query at actual time");
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get());
+ const TSIG& tsig_rdata = tsig->getRdata();
+
+ // Check the resulted time signed is in the range of [now, now + 5]
+ // (5 is an arbitrary choice). Note that due to the order of the call
+ // to time() and sign(), time signed must not be smaller than the
+ // current time.
+ EXPECT_LE(now, tsig_rdata.getTimeSigned());
+ EXPECT_GE(now + 5, tsig_rdata.getTimeSigned());
+ }
+}
+
+TEST_F(TSIGTest, signBadData) {
+ // some specific bad data should be rejected proactively.
+ const unsigned char dummy_data = 0;
+ EXPECT_THROW(tsig_ctx->sign(0, 0, 10), InvalidParameter);
+ EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter);
+}
+
+TEST_F(TSIGTest, verifyBadData) {
+ // the data must at least hold the DNS message header and the specified
+ // TSIG.
+ EXPECT_THROW(tsig_ctx->verify(&dummy_record, &dummy_data[0],
+ 12 + dummy_record.getLength() - 1),
+ InvalidParameter);
+
+ // Still nothing verified
+ EXPECT_THROW(tsig_ctx->lastHadSignature(), TSIGContextError);
+
+ // And the data must not be null.
+ EXPECT_THROW(tsig_ctx->verify(&dummy_record, 0,
+ 12 + dummy_record.getLength()),
+ InvalidParameter);
+
+ // Still nothing verified
+ EXPECT_THROW(tsig_ctx->lastHadSignature(), TSIGContextError);
+
+}
+
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+// We enable this test only when we enable custom new/delete at build time
+// We could enable/disable the test runtime using the gtest filter, but
+// we'd basically like to minimize the number of disabled tests (they
+// should generally be considered tests that temporarily fail and should
+// be fixed).
+TEST_F(TSIGTest, signExceptionSafety) {
+ // Check sign() provides the strong exception guarantee for the simpler
+ // case (with a key error and empty MAC). The general case is more
+ // complicated and involves more memory allocation, so the test result
+ // won't be reliable.
+
+ commonVerifyChecks(*tsig_verify_ctx, &dummy_record, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::RECEIVED_REQUEST);
+
+ try {
+ int dummydata;
+ isc::util::unittests::force_throw_on_new = true;
+ isc::util::unittests::throw_size_on_new = sizeof(TSIGRecord);
+ tsig_verify_ctx->sign(0, &dummydata, sizeof(dummydata));
+ isc::util::unittests::force_throw_on_new = false;
+ ASSERT_FALSE(true) << "Expected throw on new, but it didn't happen";
+ } catch (const std::bad_alloc&) {
+ isc::util::unittests::force_throw_on_new = false;
+
+ // sign() threw, so the state should still be RECEIVED_REQUEST
+ EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState());
+ }
+ isc::util::unittests::force_throw_on_new = false;
+}
+#endif // ENABLE_CUSTOM_OPERATOR_NEW
+
+// Same test as "sign" but use a different algorithm just to confirm we don't
+// naively hardcode constants specific to a particular algorithm.
+// Test data generated by
+// "dig -y hmac-sha1:www.example.com:MA+QDhXbyqUak+qnMFyTyEirzng= www.example.com"
+// QID: 0x0967, RDflag
+// Current Time: 00004da8be86
+// Time Signed: 00004dae7d5f
+// HMAC Size: 20
+// HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3
+TEST_F(TSIGTest, signUsingHMACSHA1) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4dae7d5f>;
+
+ secret.clear();
+ decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret);
+ TSIGContext sha1_ctx(TSIGKey(test_name, TSIGKey::HMACSHA1_NAME(),
+ &secret[0], secret.size()));
+
+ const uint16_t sha1_qid = 0x0967;
+ const uint8_t expected_mac[] = {
+ 0x41, 0x53, 0x40, 0xc7, 0xda, 0xf8, 0x24, 0xed, 0x68, 0x4e,
+ 0xe5, 0x86, 0xf7, 0xb5, 0xa6, 0x7a, 0x2f, 0xeb, 0xc0, 0xd3
+ };
+ {
+ SCOPED_TRACE("Sign test using HMAC-SHA1");
+ commonSignChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
+ sha1_qid, 0x4dae7d5f, expected_mac,
+ sizeof(expected_mac), 0, 0, 0,
+ TSIGKey::HMACSHA1_NAME());
+ }
+}
+
+TEST_F(TSIGTest, signUsingHMACSHA224) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4dae7d5f>;
+
+ secret.clear();
+ decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret);
+ TSIGContext sha1_ctx(TSIGKey(test_name, TSIGKey::HMACSHA224_NAME(),
+ &secret[0], secret.size()));
+
+ const uint16_t sha1_qid = 0x0967;
+ const uint8_t expected_mac[] = {
+ 0x3b, 0x93, 0xd3, 0xc5, 0xf9, 0x64, 0xb9, 0xc5, 0x00, 0x35,
+ 0x02, 0x69, 0x9f, 0xfc, 0x44, 0xd6, 0xe2, 0x66, 0xf4, 0x08,
+ 0xef, 0x33, 0xa2, 0xda, 0xa1, 0x48, 0x71, 0xd3
+ };
+ {
+ SCOPED_TRACE("Sign test using HMAC-SHA224");
+ commonSignChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
+ sha1_qid, 0x4dae7d5f, expected_mac,
+ sizeof(expected_mac), 0, 0, 0,
+ TSIGKey::HMACSHA224_NAME());
+ }
+}
+
+// The first part of this test checks verifying the signed query used for
+// the "sign" test.
+// The second part of this test generates a signed response to the signed
+// query as follows:
+// Answer: www.example.com. 86400 IN A 192.0.2.1
+// MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
+TEST_F(TSIGTest, verifyThenSignResponse) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ // This test data for the message test has the same wire format data
+ // as the message used in the "sign" test.
+ createMessageFromFile("message_toWire2.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // Transform the original message to a response, then sign the response
+ // with the context of "verified state".
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_verify_ctx.get(),
+ QR_FLAG|AA_FLAG|RD_FLAG,
+ RRType::A(), "192.0.2.1");
+ const uint8_t expected_mac[] = {
+ 0x8f, 0xcd, 0xa6, 0x6a, 0x7c, 0xd1, 0xa3, 0xb9,
+ 0x94, 0x8e, 0xb1, 0x86, 0x9d, 0x38, 0x4a, 0x9f
+ };
+ {
+ SCOPED_TRACE("Sign test for response");
+ commonSignChecks(tsig, qid, 0x4da8877a, expected_mac,
+ sizeof(expected_mac));
+ }
+}
+
+TEST_F(TSIGTest, verifyUpperCaseNames) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ // This test data for the message test has the same wire format data
+ // as the message used in the "sign" test.
+ createMessageFromFile("tsig_verify9.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, verifyForwardedMessage) {
+ // Similar to the first part of the previous test, but this test emulates
+ // the "forward" case, where the ID of the Header and the original ID in
+ // TSIG is different.
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ createMessageFromFile("tsig_verify6.wire");
+ {
+ SCOPED_TRACE("Verify test for forwarded request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+// Example of signing multiple messages in a single TCP stream,
+// taken from data using BIND 9's "one-answer" transfer-format.
+// Request:
+// QID: 0x3410, flags (none)
+// Question: example.com/IN/AXFR
+// Time Signed: 0x4da8e951
+// MAC: 35b2fd08268781634400c7c8a5533b13
+// First message:
+// QID: 0x3410, flags QR, AA
+// Question: example.com/IN/AXFR
+// Answer: example.com. 86400 IN SOA ns.example.com. root.example.com. (
+// 2011041503 7200 3600 2592000 1200)
+// MAC: bdd612cd2c7f9e0648bd6dc23713e83c
+// Second message:
+// Answer: example.com. 86400 IN NS ns.example.com.
+// MAC: 102458f7f62ddd7d638d746034130968
+TEST_F(TSIGTest, signContinuation) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8e951>;
+
+ const uint16_t axfr_qid = 0x3410;
+ const Name zone_name("example.com");
+
+ // Create and sign the AXFR request
+ ConstTSIGRecordPtr tsig = createMessageAndSign(axfr_qid, zone_name,
+ tsig_ctx.get(), 0,
+ RRType("AXFR"));
+ // Then verify it (the wire format test data should contain the same
+ // message data, and verification should succeed).
+ received_data.clear();
+ UnitTestUtil::readWireData("tsig_verify1.wire", received_data);
+ {
+ SCOPED_TRACE("Verify AXFR query");
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &received_data[0],
+ received_data.size(), TSIGError::NOERROR(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // Create and sign the first response message
+ tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+ AA_FLAG|QR_FLAG, RRType("AXFR"),
+ "ns.example.com. root.example.com. "
+ "2011041503 7200 3600 2592000 1200",
+ &RRType::SOA());
+
+ // Then verify it at the requester side.
+ received_data.clear();
+ UnitTestUtil::readWireData("tsig_verify2.wire", received_data);
+ {
+ SCOPED_TRACE("Verify first AXFR response");
+ commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0],
+ received_data.size(), TSIGError::NOERROR());
+ }
+
+ // Create and sign the second response message
+ const uint8_t expected_mac[] = {
+ 0x10, 0x24, 0x58, 0xf7, 0xf6, 0x2d, 0xdd, 0x7d,
+ 0x63, 0x8d, 0x74, 0x60, 0x34, 0x13, 0x09, 0x68
+ };
+ {
+ SCOPED_TRACE("Sign test for continued response in TCP stream");
+ tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+ AA_FLAG|QR_FLAG, RRType("AXFR"),
+ "ns.example.com.", &RRType::NS(), false);
+ commonSignChecks(tsig, axfr_qid, 0x4da8e951, expected_mac,
+ sizeof(expected_mac));
+ }
+
+ // Then verify it at the requester side.
+ received_data.clear();
+ UnitTestUtil::readWireData("tsig_verify3.wire", received_data);
+ {
+ SCOPED_TRACE("Verify second AXFR response");
+ commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0],
+ received_data.size(), TSIGError::NOERROR());
+ }
+}
+
+// BADTIME example, taken from data using specially hacked BIND 9's nsupdate
+// Query:
+// QID: 0x1830, RD flag
+// Current Time: 00004da8be86
+// Time Signed: 00004da8b9d6
+// Question: www.example.com/IN/SOA
+//(mac) 8406 7d50 b8e7 d054 3d50 5bd9 de2a bb68
+// Response:
+// QRbit, RCODE=9(NOTAUTH)
+// Time Signed: 00004da8b9d6 (the one in the query)
+// MAC: d4b043f6f44495ec8a01260e39159d76
+// Error: 0x12 (BADTIME), Other Len: 6
+// Other data: 00004da8be86
+TEST_F(TSIGTest, badtimeResponse) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6>;
+
+ const uint16_t test_qid = 0x7fc4;
+ ConstTSIGRecordPtr tsig = createMessageAndSign(test_qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+
+ // "advance the clock" and try validating, which should fail due to BADTIME
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8be86>;
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG");
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_TIME(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // make and sign a response in the context of TSIG error.
+ tsig = createMessageAndSign(test_qid, test_name, tsig_verify_ctx.get(),
+ QR_FLAG, RRType::SOA(), 0, 0,
+ true, Rcode::NOTAUTH());
+ const uint8_t expected_otherdata[] = { 0, 0, 0x4d, 0xa8, 0xbe, 0x86 };
+ const uint8_t expected_mac[] = {
+ 0xd4, 0xb0, 0x43, 0xf6, 0xf4, 0x44, 0x95, 0xec,
+ 0x8a, 0x01, 0x26, 0x0e, 0x39, 0x15, 0x9d, 0x76
+ };
+ {
+ SCOPED_TRACE("Sign test for response with BADTIME");
+ commonSignChecks(tsig, message.getQid(), 0x4da8b9d6,
+ expected_mac, sizeof(expected_mac),
+ 18, // error: BADTIME
+ sizeof(expected_otherdata),
+ expected_otherdata);
+ }
+}
+
+TEST_F(TSIGTest, badtimeResponse2) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6>;
+
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+
+ // "rewind the clock" and try validating, which should fail due to BADTIME
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 - 600>;
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME due to too future SIG");
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_TIME(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, badtimeBoundaries) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6>;
+
+ // Test various boundary conditions. We intentionally use the magic
+ // number of 300 instead of the constant variable for testing.
+ // In the okay cases, signature is not correct, but it's sufficient to
+ // check the error code isn't BADTIME for the purpose of this test.
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 + 301>;
+ EXPECT_EQ(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 + 300>;
+ EXPECT_NE(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 - 301>;
+ EXPECT_EQ(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 - 300>;
+ EXPECT_NE(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+}
+
+TEST_F(TSIGTest, badtimeOverflow) {
+ isc::util::detail::getTimeFunction = testGetTime<200>;
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get(), 0,
+ RRType::SOA());
+
+ // This should be in the okay range, but since "200 - fudge" overflows
+ // and we compare them as 64-bit unsigned integers, it results in a false
+ // positive (we intentionally accept that).
+ isc::util::detail::getTimeFunction = testGetTime<100>;
+ EXPECT_EQ(TSIGError::BAD_TIME(),
+ tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+ dummy_data.size()));
+}
+
+TEST_F(TSIGTest, badsigResponse) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ // Try to sign a simple message with bogus secret. It should fail
+ // with BADSIG.
+ createMessageFromFile("message_toWire2.wire");
+ TSIGContext bad_ctx(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(),
+ &dummy_data[0], dummy_data.size()));
+ {
+ SCOPED_TRACE("Verify resulting in BADSIG");
+ commonVerifyChecks(bad_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // Sign the same message (which doesn't matter for this test) with the
+ // context of "checked state".
+ {
+ SCOPED_TRACE("Sign test for response with BADSIG error");
+ commonSignChecks(createMessageAndSign(qid, test_name, &bad_ctx),
+ message.getQid(), 0x4da8877a, 0, 0,
+ 16); // 16: BADSIG
+ }
+}
+
+TEST_F(TSIGTest, badkeyResponse) {
+ // A similar test as badsigResponse but for BADKEY
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+ tsig_ctx.reset(new TestTSIGContext(badkey_name, TSIGKey::HMACMD5_NAME(),
+ keyring));
+ {
+ SCOPED_TRACE("Verify resulting in BADKEY");
+ commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+
+ {
+ SCOPED_TRACE("Sign test for response with BADKEY error");
+ ConstTSIGRecordPtr sig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get());
+ EXPECT_EQ(badkey_name, sig->getName());
+ commonSignChecks(sig, qid, 0x4da8877a, 0, 0, 17); // 17: BADKEY
+ }
+}
+
+TEST_F(TSIGTest, badkeyForResponse) {
+ // "BADKEY" case for a response to a signed message
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+ {
+ SCOPED_TRACE("Verify a response resulting in BADKEY");
+ commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::SENT_REQUEST);
+ }
+
+ // A similar case with a different algorithm
+ const TSIGRecord dummy_record2(test_name, TSIG(TSIGKey::HMACSHA1_NAME(),
+ 0x4da8877a,
+ TSIGContext::DEFAULT_FUDGE,
+ 0, 0, qid, 0, 0, 0));
+ {
+ SCOPED_TRACE("Verify a response resulting in BADKEY due to bad alg");
+ commonVerifyChecks(*tsig_ctx, &dummy_record2, &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_KEY(),
+ TSIGContext::SENT_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, badsigThenValidate) {
+ // According to RFC2845 4.6, if TSIG verification fails the client
+ // should discard that message and wait for another signed response.
+ // This test emulates that situation.
+
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+
+ createMessageFromFile("tsig_verify4.wire");
+ {
+ SCOPED_TRACE("Verify a response that should fail due to BADSIG");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::SENT_REQUEST);
+ }
+
+ createMessageFromFile("tsig_verify5.wire");
+ {
+ SCOPED_TRACE("Verify a response after a BADSIG failure");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(),
+ TSIGContext::VERIFIED_RESPONSE);
+ }
+}
+
+TEST_F(TSIGTest, nosigThenValidate) {
+ // Similar to the previous test, but the first response doesn't contain
+ // TSIG.
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+
+ {
+ SCOPED_TRACE("Verify a response without TSIG that should exist");
+ commonVerifyChecks(*tsig_ctx, 0, &dummy_data[0],
+ dummy_data.size(), TSIGError::FORMERR(),
+ TSIGContext::SENT_REQUEST, true);
+ }
+
+ createMessageFromFile("tsig_verify5.wire");
+ {
+ SCOPED_TRACE("Verify a response after a FORMERR failure");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(),
+ TSIGContext::VERIFIED_RESPONSE);
+ }
+}
+
+TEST_F(TSIGTest, badtimeThenValidate) {
+ // Similar to the previous test, but the first response results in BADTIME.
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+ tsig_ctx.get());
+
+ // "advance the clock" and try validating, which should fail due to BADTIME
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a + 600>;
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG");
+ commonVerifyChecks(*tsig_ctx, tsig.get(), &dummy_data[0],
+ dummy_data.size(), TSIGError::BAD_TIME(),
+ TSIGContext::SENT_REQUEST);
+ }
+
+ // revert the clock again.
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("tsig_verify5.wire");
+ {
+ SCOPED_TRACE("Verify a response after a BADTIME failure");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(),
+ TSIGContext::VERIFIED_RESPONSE);
+ }
+}
+
+TEST_F(TSIGTest, emptyMAC) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ // We don't allow empty MAC unless the TSIG error is BADSIG or BADKEY.
+ createMessageFromFile("tsig_verify7.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // If the empty MAC comes with a BADKEY error, the error is passed
+ // transparently.
+ createMessageFromFile("tsig_verify8.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_KEY(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, verifyAfterSendResponse) {
+ // Once the context is used for sending a signed response, it shouldn't
+ // be used for further verification.
+
+ // The following are essentially the same as what verifyThenSignResponse
+ // does with simplification.
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("message_toWire2.wire");
+ tsig_verify_ctx->verify(message.getTSIGRecord(), &received_data[0],
+ received_data.size());
+ EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState());
+ createMessageAndSign(qid, test_name, tsig_verify_ctx.get(),
+ QR_FLAG|AA_FLAG|RD_FLAG, RRType::A(), "192.0.2.1");
+ EXPECT_EQ(TSIGContext::SENT_RESPONSE, tsig_verify_ctx->getState());
+
+ // Now trying further verification.
+ createMessageFromFile("message_toWire2.wire");
+ EXPECT_THROW(tsig_verify_ctx->verify(message.getTSIGRecord(),
+ &received_data[0],
+ received_data.size()),
+ TSIGContextError);
+}
+
+TEST_F(TSIGTest, signAfterVerified) {
+ // Likewise, once the context verifies a response, it shouldn't for
+ // signing any more.
+
+ // The following are borrowed from badsigThenValidate (without the
+ // intermediate failure)
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+ createMessageAndSign(qid, test_name, tsig_ctx.get());
+ createMessageFromFile("tsig_verify5.wire");
+ tsig_ctx->verify(message.getTSIGRecord(), &received_data[0],
+ received_data.size());
+ EXPECT_EQ(TSIGContext::VERIFIED_RESPONSE, tsig_ctx->getState());
+
+ // Now trying further signing.
+ EXPECT_THROW(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+ TSIGContextError);
+}
+
+TEST_F(TSIGTest, tooShortMAC) {
+ // Too short MAC should be rejected.
+
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("tsig_verify10.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::FORMERR(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, truncatedMAC) {
+ // Check truncated MAC support with HMAC-SHA512-256
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ secret.clear();
+ decodeBase64("jI/Pa4qRu96t76Pns5Z/Ndxbn3QCkwcxLOgt9vgvnJw5wqTRvNyk3FtD6yIMd1dWVlqZ+Y4fe6Uasc0ckctEmg==", secret);
+ TSIGContext sha_ctx(TSIGKey(test_name, TSIGKey::HMACSHA512_NAME(),
+ &secret[0], secret.size(), 256));
+
+ createMessageFromFile("tsig_verify11.wire");
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(sha_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+ }
+
+ // Try with HMAC-SHA512-264 (should fail)
+ TSIGContext bad_sha_ctx(TSIGKey(test_name, TSIGKey::HMACSHA512_NAME(),
+ &secret[0], secret.size(), 264));
+ {
+ SCOPED_TRACE("Verify test for request");
+ commonVerifyChecks(bad_sha_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_TRUNC(), TSIGContext::RECEIVED_REQUEST);
+ }
+}
+
+TEST_F(TSIGTest, getTSIGLength) {
+ // Check for the most common case with various algorithms
+ // See the comment in TSIGContext::getTSIGLength() for calculation and
+ // parameter notation.
+ // The key name (www.example.com) is the same for most cases, where n1=17
+
+ // hmac-md5.sig-alg.reg.int.: n2=26, x=16
+ EXPECT_EQ(85, tsig_ctx->getTSIGLength());
+
+ // hmac-md5-80: n2=26, x=10
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACMD5_NAME(),
+ &dummy_data[0], 10, 80)));
+ EXPECT_EQ(79, tsig_ctx->getTSIGLength());
+
+ // hmac-sha1: n2=11, x=20
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACSHA1_NAME(),
+ &dummy_data[0], 20)));
+ EXPECT_EQ(74, tsig_ctx->getTSIGLength());
+
+ // hmac-sha256: n2=13, x=32
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACSHA256_NAME(),
+ &dummy_data[0], 32)));
+ EXPECT_EQ(88, tsig_ctx->getTSIGLength());
+
+ // hmac-sha224: n2=13, x=28
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACSHA224_NAME(),
+ &dummy_data[0], 28)));
+ EXPECT_EQ(84, tsig_ctx->getTSIGLength());
+
+ // hmac-sha384: n2=13, x=48
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACSHA384_NAME(),
+ &dummy_data[0], 48)));
+ EXPECT_EQ(104, tsig_ctx->getTSIGLength());
+
+ // hmac-sha512: n2=13, x=64
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACSHA512_NAME(),
+ &dummy_data[0], 64)));
+ EXPECT_EQ(120, tsig_ctx->getTSIGLength());
+
+ // hmac-sha512-256: n2=13, x=32
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACSHA512_NAME(),
+ &dummy_data[0], 32, 256)));
+ EXPECT_EQ(88, tsig_ctx->getTSIGLength());
+
+ // bad key case: n1=len(badkey.example.com)=20, n2=26, x=0
+ tsig_ctx.reset(new TestTSIGContext(badkey_name, TSIGKey::HMACMD5_NAME(),
+ keyring));
+ EXPECT_EQ(72, tsig_ctx->getTSIGLength());
+
+ // bad sig case: n1=17, n2=26, x=0
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+ createMessageFromFile("message_toWire2.wire");
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACMD5_NAME(),
+ &dummy_data[0],
+ dummy_data.size())));
+ {
+ SCOPED_TRACE("Verify resulting in BADSIG");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+ }
+ EXPECT_EQ(69, tsig_ctx->getTSIGLength());
+
+ // bad time case: n1=17, n2=26, x=16, y=6
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a - 1000>;
+ tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name,
+ TSIGKey::HMACMD5_NAME(),
+ &dummy_data[0],
+ dummy_data.size())));
+ {
+ SCOPED_TRACE("Verify resulting in BADTIME");
+ commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+ &received_data[0], received_data.size(),
+ TSIGError::BAD_TIME(),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+ EXPECT_EQ(91, tsig_ctx->getTSIGLength());
+}
+
+// Verify a stream of multiple messages. Some of them have a signature omitted.
+//
+// We have two contexts, one that signs, another that verifies.
+TEST_F(TSIGTest, verifyMulti) {
+ isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>;
+
+ // First, send query from the verify one to the normal one, so
+ // we initialize something like AXFR
+ {
+ SCOPED_TRACE("Query");
+ ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name,
+ tsig_verify_ctx.get());
+ commonVerifyChecks(*tsig_ctx, tsig.get(),
+ renderer.getData(), renderer.getLength(),
+ TSIGError(Rcode::NOERROR()),
+ TSIGContext::RECEIVED_REQUEST);
+ }
+
+ {
+ SCOPED_TRACE("First message");
+ ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name,
+ tsig_ctx.get());
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(),
+ renderer.getData(), renderer.getLength(),
+ TSIGError(Rcode::NOERROR()),
+ TSIGContext::VERIFIED_RESPONSE);
+ EXPECT_TRUE(tsig_verify_ctx->lastHadSignature());
+ }
+
+ {
+ SCOPED_TRACE("Second message");
+ ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name,
+ tsig_ctx.get());
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(),
+ renderer.getData(), renderer.getLength(),
+ TSIGError(Rcode::NOERROR()),
+ TSIGContext::VERIFIED_RESPONSE);
+ EXPECT_TRUE(tsig_verify_ctx->lastHadSignature());
+ }
+
+ {
+ SCOPED_TRACE("Third message. Unsigned.");
+ // Another message does not carry the TSIG on it. But it should
+ // be OK, it's in the middle of stream.
+ message.clear(Message::RENDER);
+ message.setQid(1234);
+ message.setOpcode(Opcode::QUERY());
+ message.setRcode(Rcode::NOERROR());
+ RRsetPtr answer_rrset(new RRset(test_name, test_class, RRType::A(),
+ test_ttl));
+ answer_rrset->addRdata(createRdata(RRType::A(), test_class,
+ "192.0.2.1"));
+ message.addRRset(Message::SECTION_ANSWER, answer_rrset);
+ message.toWire(renderer);
+ // Update the internal state. We abuse the knowledge of
+ // internals here a little bit to generate correct test data
+ tsig_ctx->update(renderer.getData(), renderer.getLength());
+
+ commonVerifyChecks(*tsig_verify_ctx, 0,
+ renderer.getData(), renderer.getLength(),
+ TSIGError(Rcode::NOERROR()),
+ TSIGContext::VERIFIED_RESPONSE);
+
+ EXPECT_FALSE(tsig_verify_ctx->lastHadSignature());
+ }
+
+ {
+ SCOPED_TRACE("Fourth message. Signed again.");
+ ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name,
+ tsig_ctx.get());
+ commonVerifyChecks(*tsig_verify_ctx, tsig.get(),
+ renderer.getData(), renderer.getLength(),
+ TSIGError(Rcode::NOERROR()),
+ TSIGContext::VERIFIED_RESPONSE);
+ EXPECT_TRUE(tsig_verify_ctx->lastHadSignature());
+ }
+
+ {
+ SCOPED_TRACE("Filling in bunch of unsigned messages");
+ for (size_t i = 0; i < 100; ++i) {
+ SCOPED_TRACE(i);
+ // Another message does not carry the TSIG on it. But it should
+ // be OK, it's in the middle of stream.
+ message.clear(Message::RENDER);
+ message.setQid(1234);
+ message.setOpcode(Opcode::QUERY());
+ message.setRcode(Rcode::NOERROR());
+ RRsetPtr answer_rrset(new RRset(test_name, test_class, RRType::A(),
+ test_ttl));
+ answer_rrset->addRdata(createRdata(RRType::A(), test_class,
+ "192.0.2.1"));
+ message.addRRset(Message::SECTION_ANSWER, answer_rrset);
+ message.toWire(renderer);
+ // Update the internal state. We abuse the knowledge of
+ // internals here a little bit to generate correct test data
+ tsig_ctx->update(renderer.getData(), renderer.getLength());
+
+ // 99 unsigned messages is OK. But the 100th must be signed, according
+ // to the RFC2845, section 4.4
+ commonVerifyChecks(*tsig_verify_ctx, 0,
+ renderer.getData(), renderer.getLength(),
+ i == 99 ? TSIGError::FORMERR() :
+ TSIGError(Rcode::NOERROR()),
+ TSIGContext::VERIFIED_RESPONSE);
+
+ EXPECT_FALSE(tsig_verify_ctx->lastHadSignature());
+ }
+ }
+}
+
+} // end namespace
diff --git a/src/lib/dns/tests/tsigerror_unittest.cc b/src/lib/dns/tests/tsigerror_unittest.cc
new file mode 100644
index 0000000..7355eb2
--- /dev/null
+++ b/src/lib/dns/tests/tsigerror_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+#include <ostream>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
+#include <dns/tsigerror.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+TEST(TSIGErrorTest, constructFromErrorCode) {
+ // These are pretty trivial, and also test getCode();
+ EXPECT_EQ(0, TSIGError(0).getCode());
+ EXPECT_EQ(18, TSIGError(18).getCode());
+ EXPECT_EQ(65535, TSIGError(65535).getCode());
+}
+
+TEST(TSIGErrorTest, constructFromRcode) {
+ // We use RCODE for code values from 0-15.
+ EXPECT_EQ(0, TSIGError(Rcode::NOERROR()).getCode());
+ EXPECT_EQ(15, TSIGError(Rcode(15)).getCode());
+
+ // From error code 16 TSIG errors define a separate space, so passing
+ // corresponding RCODE for such code values should be prohibited.
+ EXPECT_THROW(TSIGError(Rcode(16)).getCode(), OutOfRange);
+}
+
+TEST(TSIGErrorTest, constants) {
+ // We'll only test arbitrarily chosen subsets of the codes.
+ // This class is quite simple, so it should be suffice.
+
+ EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError(16).getCode());
+ EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError(17).getCode());
+ EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError(18).getCode());
+ EXPECT_EQ(TSIGError::BAD_MODE_CODE, TSIGError(19).getCode());
+ EXPECT_EQ(TSIGError::BAD_NAME_CODE, TSIGError(20).getCode());
+ EXPECT_EQ(TSIGError::BAD_ALG_CODE, TSIGError(21).getCode());
+ EXPECT_EQ(TSIGError::BAD_TRUNC_CODE, TSIGError(22).getCode());
+
+ EXPECT_EQ(0, TSIGError::NOERROR().getCode());
+ EXPECT_EQ(9, TSIGError::NOTAUTH().getCode());
+ EXPECT_EQ(14, TSIGError::RESERVED14().getCode());
+ EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError::BAD_SIG().getCode());
+ EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError::BAD_KEY().getCode());
+ EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError::BAD_TIME().getCode());
+ EXPECT_EQ(TSIGError::BAD_MODE_CODE, TSIGError::BAD_MODE().getCode());
+ EXPECT_EQ(TSIGError::BAD_NAME_CODE, TSIGError::BAD_NAME().getCode());
+ EXPECT_EQ(TSIGError::BAD_ALG_CODE, TSIGError::BAD_ALG().getCode());
+ EXPECT_EQ(TSIGError::BAD_TRUNC_CODE, TSIGError::BAD_TRUNC().getCode());
+}
+
+TEST(TSIGErrorTest, equal) {
+ EXPECT_TRUE(TSIGError::NOERROR() == TSIGError(Rcode::NOERROR()));
+ EXPECT_TRUE(TSIGError(Rcode::NOERROR()) == TSIGError::NOERROR());
+ EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR())));
+ EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR())));
+
+ EXPECT_TRUE(TSIGError::BAD_SIG() == TSIGError(16));
+ EXPECT_TRUE(TSIGError(16) == TSIGError::BAD_SIG());
+ EXPECT_TRUE(TSIGError::BAD_SIG().equals(TSIGError(16)));
+ EXPECT_TRUE(TSIGError(16).equals(TSIGError::BAD_SIG()));
+}
+
+TEST(TSIGErrorTest, nequal) {
+ EXPECT_TRUE(TSIGError::BAD_KEY() != TSIGError(Rcode::NOERROR()));
+ EXPECT_TRUE(TSIGError(Rcode::NOERROR()) != TSIGError::BAD_KEY());
+ EXPECT_TRUE(TSIGError::BAD_KEY().nequals(TSIGError(Rcode::NOERROR())));
+ EXPECT_TRUE(TSIGError(Rcode::NOERROR()).nequals(TSIGError::BAD_KEY()));
+}
+
+TEST(TSIGErrorTest, toText) {
+ // TSIGError derived from the standard Rcode
+ EXPECT_EQ("NOERROR", TSIGError(Rcode::NOERROR()).toText());
+
+ // Well known TSIG errors
+ EXPECT_EQ("BADSIG", TSIGError::BAD_SIG().toText());
+ EXPECT_EQ("BADKEY", TSIGError::BAD_KEY().toText());
+ EXPECT_EQ("BADTIME", TSIGError::BAD_TIME().toText());
+ EXPECT_EQ("BADMODE", TSIGError::BAD_MODE().toText());
+ EXPECT_EQ("BADNAME", TSIGError::BAD_NAME().toText());
+ EXPECT_EQ("BADALG", TSIGError::BAD_ALG().toText());
+ EXPECT_EQ("BADTRUNC", TSIGError::BAD_TRUNC().toText());
+
+ // Unknown (or not yet supported) codes. Simply converted as numeric.
+ EXPECT_EQ("23", TSIGError(23).toText());
+ EXPECT_EQ("65535", TSIGError(65535).toText());
+}
+
+TEST(TSIGErrorTest, toRcode) {
+ // TSIGError derived from the standard Rcode
+ EXPECT_EQ(Rcode::NOERROR(), TSIGError(Rcode::NOERROR()).toRcode());
+
+ // Well known TSIG errors
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_SIG().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_KEY().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_TIME().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_MODE().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_NAME().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_ALG().toRcode());
+ EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_TRUNC().toRcode());
+
+ // Unknown (or not yet supported) codes are treated as SERVFAIL.
+ EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(23).toRcode());
+ EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(65535).toRcode());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(TSIGErrorTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << TSIGError::BAD_KEY();
+ EXPECT_EQ(TSIGError::BAD_KEY().toText(), oss.str());
+}
+} // end namespace
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
new file mode 100644
index 0000000..80b4604
--- /dev/null
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -0,0 +1,347 @@
+// Copyright (C) 2019-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <cryptolink/cryptolink.h>
+
+#include <dns/tsigkey.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc::dns;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class TSIGKeyTest : public ::testing::Test {
+protected:
+ TSIGKeyTest() : secret("someRandomData"), key_name("example.com") {}
+ string secret;
+ const Name key_name;
+};
+
+TEST_F(TSIGKeyTest, algorithmNames) {
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME());
+ EXPECT_EQ(Name("hmac-md5"), TSIGKey::HMACMD5_SHORT_NAME());
+ EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME());
+ EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME());
+ EXPECT_EQ(Name("hmac-sha224"), TSIGKey::HMACSHA224_NAME());
+ EXPECT_EQ(Name("hmac-sha384"), TSIGKey::HMACSHA384_NAME());
+ EXPECT_EQ(Name("hmac-sha512"), TSIGKey::HMACSHA512_NAME());
+ EXPECT_EQ(Name("gss-tsig"), TSIGKey::GSSTSIG_NAME());
+
+ // Also check conversion to cryptolink definitions
+ EXPECT_EQ(isc::cryptolink::MD5, TSIGKey(key_name, TSIGKey::HMACMD5_NAME(),
+ 0, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::MD5,
+ TSIGKey(key_name, TSIGKey::HMACMD5_SHORT_NAME(),
+ 0, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA1, TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(),
+ 0, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name,
+ TSIGKey::HMACSHA256_NAME(),
+ 0, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA224, TSIGKey(key_name,
+ TSIGKey::HMACSHA224_NAME(),
+ 0, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA384, TSIGKey(key_name,
+ TSIGKey::HMACSHA384_NAME(),
+ 0, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA512, TSIGKey(key_name,
+ TSIGKey::HMACSHA512_NAME(),
+ 0, 0).getAlgorithm());
+}
+
+TEST_F(TSIGKeyTest, construct) {
+ TSIGKey key(key_name, TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size());
+ EXPECT_EQ(key_name, key.getKeyName());
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), key.getAlgorithmName());
+ matchWireData(secret.c_str(), secret.size(),
+ key.getSecret(), key.getSecretLength());
+
+ TSIGKey key_short_md5(key_name, TSIGKey::HMACMD5_SHORT_NAME(),
+ secret.c_str(), secret.size());
+ EXPECT_EQ(key_name, key_short_md5.getKeyName());
+ EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"),
+ key_short_md5.getAlgorithmName());
+ matchWireData(secret.c_str(), secret.size(),
+ key_short_md5.getSecret(), key_short_md5.getSecretLength());
+
+ // "unknown" algorithm is only accepted with empty secret.
+ EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"),
+ secret.c_str(), secret.size()),
+ isc::InvalidParameter);
+ TSIGKey key2(key_name, Name("unknown-alg"), 0, 0);
+ EXPECT_EQ(key_name, key2.getKeyName());
+ EXPECT_EQ(Name("unknown-alg"), key2.getAlgorithmName());
+
+ // The algorithm name should be converted to the canonical form.
+ EXPECT_EQ("hmac-sha1.",
+ TSIGKey(key_name, Name("HMAC-sha1"),
+ secret.c_str(),
+ secret.size()).getAlgorithmName().toText());
+
+ // Same for key name
+ EXPECT_EQ("example.com.",
+ TSIGKey(Name("EXAMPLE.CoM."), TSIGKey::HMACSHA256_NAME(),
+ secret.c_str(),
+ secret.size()).getKeyName().toText());
+
+ // Check digestbits
+ EXPECT_EQ(key.getDigestbits(), 0);
+ TSIGKey key_trunc(key_name, TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size(), 120);
+ EXPECT_EQ(key_trunc.getDigestbits(), 120);
+
+ // Invalid combinations of secret and secret_len:
+ EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), secret.c_str(), 0),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), 0, 16),
+ isc::InvalidParameter);
+
+ // Empty secret
+ TSIGKey keye = TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), 0, 0);
+ EXPECT_EQ(keye.getSecretLength(), 0);
+ EXPECT_EQ(keye.getSecret(), (const void*)0);
+}
+
+void
+compareTSIGKeys(const TSIGKey& expect, const TSIGKey& actual) {
+ EXPECT_EQ(expect.getKeyName(), actual.getKeyName());
+ EXPECT_EQ(expect.getAlgorithmName(), actual.getAlgorithmName());
+ EXPECT_EQ(expect.getDigestbits(), actual.getDigestbits());
+ matchWireData(expect.getSecret(), expect.getSecretLength(),
+ actual.getSecret(), actual.getSecretLength());
+}
+
+TEST_F(TSIGKeyTest, copyConstruct) {
+ const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret.c_str(), secret.size(), 128);
+ const TSIGKey copy(original);
+ compareTSIGKeys(original, copy);
+
+ // Check the copied data is valid even after the original is deleted
+ TSIGKey* copy2 = new TSIGKey(original);
+ TSIGKey copy3(*copy2);
+ delete copy2;
+ compareTSIGKeys(original, copy3);
+}
+
+TEST_F(TSIGKeyTest, assignment) {
+ const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret.c_str(), secret.size(), 200);
+ TSIGKey copy = original;
+ compareTSIGKeys(original, copy);
+
+ // Check if the copied data is valid even after the original is deleted
+ TSIGKey* copy2 = new TSIGKey(original);
+ TSIGKey copy3(original);
+ copy3 = *copy2;
+ delete copy2;
+ compareTSIGKeys(original, copy3);
+
+ // Self assignment
+ copy = *&copy;
+ compareTSIGKeys(original, copy);
+}
+
+class TSIGKeyRingTest : public ::testing::Test {
+protected:
+ TSIGKeyRingTest() :
+ key_name("example.com"),
+ md5_name("hmac-md5.sig-alg.reg.int"),
+ sha1_name("hmac-sha1"),
+ sha256_name("hmac-sha256"),
+ secretstring("anotherRandomData"),
+ secret(secretstring.c_str()),
+ secret_len(secretstring.size())
+ {}
+ TSIGKeyRing keyring;
+ const Name key_name;
+ const Name md5_name;
+ const Name sha1_name;
+ const Name sha256_name;
+private:
+ const string secretstring;
+protected:
+ const char* secret;
+ size_t secret_len;
+};
+
+TEST_F(TSIGKeyRingTest, init) {
+ EXPECT_EQ(0, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, add) {
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(1, keyring.size());
+ EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ // keys are identified by their names, the same name of key with a
+ // different algorithm would be considered a duplicate.
+ EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+ TSIGKey(Name("example.com"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+ // names are compared in a case insensitive manner.
+ EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+ TSIGKey(Name("EXAMPLE.COM"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(1, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, addMore) {
+ // essentially the same test, but try adding more than 1
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(3, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, remove) {
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(key_name));
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(key_name));
+}
+
+TEST_F(TSIGKeyRingTest, removeFromSome) {
+ // essentially the same test, but try removing from a larger set
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+ TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+ secret, secret_len)));
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(Name("another.example")));
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(Name("noexist.example")));
+ EXPECT_EQ(2, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, find) {
+ // If the keyring is empty the search should fail.
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name, md5_name).code);
+ EXPECT_FALSE(keyring.find(key_name, md5_name).key);
+
+ // Add a key and try to find it. Should succeed.
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name,
+ secret, secret_len)));
+ const TSIGKeyRing::FindResult result1(keyring.find(key_name, sha256_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result1.code);
+ EXPECT_EQ(key_name, result1.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result1.key->getAlgorithmName());
+ matchWireData(secret, secret_len,
+ result1.key->getSecret(), result1.key->getSecretLength());
+
+ // If either key name or algorithm doesn't match, search should fail.
+ const TSIGKeyRing::FindResult result2 =
+ keyring.find(Name("different-key.example"), sha256_name);
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, result2.code);
+ EXPECT_FALSE(result2.key);
+
+ const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
+ EXPECT_FALSE(result3.key);
+
+ // But with just the name it should work
+ const TSIGKeyRing::FindResult result4(keyring.find(key_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result4.code);
+ EXPECT_EQ(key_name, result4.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result4.key->getAlgorithmName());
+ matchWireData(secret, secret_len,
+ result4.key->getSecret(), result4.key->getSecretLength());
+}
+
+TEST_F(TSIGKeyRingTest, findFromSome) {
+ // essentially the same test, but search a larger set
+
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name,
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("another.example"),
+ md5_name,
+ secret, secret_len)));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("more.example"),
+ sha1_name,
+ secret, secret_len)));
+
+ const TSIGKeyRing::FindResult result(
+ keyring.find(Name("another.example"), md5_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+ EXPECT_EQ(Name("another.example"), result.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACMD5_NAME(), result.key->getAlgorithmName());
+
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+ keyring.find(Name("noexist.example"), sha1_name).code);
+ EXPECT_FALSE(keyring.find(Name("noexist.example"), sha256_name).key);
+
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+ keyring.find(Name("another.example"), sha1_name).code);
+ EXPECT_FALSE(keyring.find(Name("another.example"), sha256_name).key);
+}
+
+TEST(TSIGStringTest, TSIGKeyFromToString) {
+ TSIGKey k1 = TSIGKey("test.example:MSG6Ng==:hmac-md5.sig-alg.reg.int");
+ TSIGKey k2 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.");
+ TSIGKey k3 = TSIGKey("test.example:MSG6Ng==");
+ TSIGKey k4 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:120");
+ TSIGKey k5 = TSIGKey(Name("test.example."), Name("hmac-sha1."), 0, 0);
+ // "Unknown" key with empty secret is okay
+ TSIGKey k6 = TSIGKey("test.example.::unknown");
+
+ EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
+ k1.toText());
+ EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
+ k2.toText());
+ EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
+ k3.toText());
+ EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:120",
+ k4.toText());
+ EXPECT_EQ(120, k4.getDigestbits());
+ EXPECT_EQ("test.example.::hmac-sha1.", k5.toText());
+ EXPECT_EQ(Name("test.example."), k6.getKeyName());
+ EXPECT_EQ(Name("unknown"), k6.getAlgorithmName());
+
+ EXPECT_THROW(TSIGKey(""), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey(":"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("::"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("..:aa:"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example:xxxx:"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example.::"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example.:"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:unknown"), isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:"),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:xxx"),
+ isc::InvalidParameter);
+}
+
+
+} // end namespace
diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc
new file mode 100644
index 0000000..e491036
--- /dev/null
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <vector>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
+
+#include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::dns::rdata::any;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class TSIGRecordTest : public ::testing::Test {
+protected:
+ TSIGRecordTest() :
+ test_name("www.example.com"), test_mac(16, 0xda),
+ test_rdata(TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a, TSIGContext::DEFAULT_FUDGE,
+ test_mac.size(), &test_mac[0], 0x2d65, 0, 0, 0)),
+ test_record(test_name, test_rdata),
+ buffer(0) {
+ }
+
+ const Name test_name;
+
+ vector<unsigned char> test_mac;
+
+ const TSIG test_rdata;
+
+ const TSIGRecord test_record;
+
+ OutputBuffer buffer;
+
+ MessageRenderer renderer;
+
+ vector<unsigned char> data;
+};
+
+TEST_F(TSIGRecordTest, getName) {
+ EXPECT_EQ(test_name, test_record.getName());
+}
+
+TEST_F(TSIGRecordTest, getLength) {
+ // 85 = 17 + 26 + 16 + 26
+ // len(www.example.com) = 17
+ // len(hmac-md5.sig-alg.reg.int) = 26
+ // len(MAC) = 16
+ // the rest are fixed length fields (26 in total)
+ EXPECT_EQ(85, test_record.getLength());
+}
+
+TEST_F(TSIGRecordTest, fromParams) {
+ // Construct the same TSIG RR as test_record from parameters.
+ // See the getLength test for the magic number of 85 (although it
+ // actually doesn't matter)
+ const TSIGRecord record(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 85);
+ // Perform straight sanity checks
+ EXPECT_EQ(test_name, record.getName());
+ EXPECT_EQ(85, record.getLength());
+ EXPECT_EQ(0, test_rdata.compare(record.getRdata()));
+
+ // The constructor doesn't check the length...
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 82));
+ // ...even for impossibly small values...
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 1));
+ // ...or too large values.
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 65536));
+
+ // RDATA must indeed be TSIG
+ EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), in::A("192.0.2.1"), 85),
+ DNSMessageFORMERR);
+
+ // Unexpected class
+ EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(),
+ test_rdata, 85), DNSMessageFORMERR);
+
+ // Unexpected TTL
+ EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ RRTTL(3600), test_rdata, 85), DNSMessageFORMERR);
+}
+
+TEST_F(TSIGRecordTest, recordToWire) {
+ UnitTestUtil::readWireData("tsigrecord_toWire1.wire", data);
+ EXPECT_EQ(1, test_record.toWire(renderer));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+
+ // Same test for a dumb buffer
+ buffer.clear();
+ EXPECT_EQ(1, test_record.toWire(buffer));
+ matchWireData(&data[0], data.size(),
+ buffer.getData(), buffer.getLength());
+}
+
+TEST_F(TSIGRecordTest, recordToOLongToWire) {
+ // By setting the limit to "record length - 1", it will fail, and the
+ // renderer will be marked as "truncated".
+ renderer.setLengthLimit(test_record.getLength() - 1);
+ EXPECT_FALSE(renderer.isTruncated()); // not marked before render attempt
+ EXPECT_EQ(0, test_record.toWire(renderer));
+ EXPECT_TRUE(renderer.isTruncated());
+}
+
+TEST_F(TSIGRecordTest, recordToWireAfterNames) {
+ // A similar test but the TSIG RR follows some domain names that could
+ // cause name compression inside TSIG. Our implementation shouldn't
+ // compress either owner (key) name or the algorithm name. This test
+ // confirms that.
+
+ UnitTestUtil::readWireData("tsigrecord_toWire2.wire", data);
+ renderer.writeName(TSIGKey::HMACMD5_NAME());
+ renderer.writeName(Name("foo.example.com"));
+ EXPECT_EQ(1, test_record.toWire(renderer));
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(TSIGRecordTest, toText) {
+ EXPECT_EQ("www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. "
+ "1302890362 300 16 2tra2tra2tra2tra2tra2g== 11621 NOERROR 0\n",
+ test_record.toText());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST_F(TSIGRecordTest, LeftShiftOperator) {
+ ostringstream oss;
+ oss << test_record;
+ EXPECT_EQ(test_record.toText(), oss.str());
+}
+} // end namespace
diff --git a/src/lib/dns/tests/unittest_util.cc b/src/lib/dns/tests/unittest_util.cc
new file mode 100644
index 0000000..caedeac
--- /dev/null
+++ b/src/lib/dns/tests/unittest_util.cc
@@ -0,0 +1,140 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <dns/rcode.h>
+#include <dns/name.h>
+#include <dns/message.h>
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc::dns;
+
+using isc::UnitTestUtil;
+
+namespace {
+class UnitTestUtilConfig {
+private:
+ // This is a singleton object and cannot be constructed explicitly.
+ UnitTestUtilConfig() {}
+ UnitTestUtilConfig(const UnitTestUtilConfig& source);
+ ~UnitTestUtilConfig() {}
+public:
+ /// Return a singleton unit test configuration object. On first invocation
+ /// one will be constructed.
+ static UnitTestUtilConfig& getConfig();
+
+ /// A list of paths to wire data files.
+ /// \c UnitTestUtil::readWireData() (first version)
+ /// will search the directories in this list for the specified data file.
+ std::vector<string> data_paths_;
+};
+
+UnitTestUtilConfig&
+UnitTestUtilConfig::getConfig() {
+ static UnitTestUtilConfig config;
+ return (config);
+}
+}
+
+void
+UnitTestUtil::readWireData(const char* datafile, vector<unsigned char>& data) {
+ ifstream ifs;
+
+ const UnitTestUtilConfig& config = UnitTestUtilConfig::getConfig();
+ vector<string>::const_iterator it = config.data_paths_.begin();
+ for (; it != config.data_paths_.end(); ++it) {
+ string data_path = *it;
+ if (data_path.empty() || *data_path.rbegin() != '/') {
+ data_path.push_back('/');
+ }
+ ifs.open((data_path + datafile).c_str(), ios_base::in);
+ if ((ifs.rdstate() & istream::failbit) == 0) {
+ break;
+ }
+ }
+
+ if (it == config.data_paths_.end()) {
+ throw runtime_error("failed to open data file in data paths: " +
+ string(datafile));
+ }
+
+ data.clear();
+
+ string s;
+ while (getline(ifs, s), !ifs.eof()) {
+ if (ifs.bad() || ifs.fail()) {
+ throw runtime_error("unexpected data line");
+ }
+ if (s.empty() || s[0] == '#') {
+ continue;
+ }
+
+ readWireData(s, data);
+ }
+}
+
+void
+UnitTestUtil::addDataPath(const string& directory) {
+ UnitTestUtilConfig::getConfig().data_paths_.push_back(directory);
+}
+
+void
+UnitTestUtil::readWireData(const string& datastr,
+ vector<unsigned char>& data) {
+ istringstream iss(datastr);
+
+ do {
+ string bytes;
+ iss >> bytes;
+ if (iss.bad() || iss.fail() || (bytes.size() % 2) != 0) {
+ ostringstream err_oss;
+ err_oss << "unexpected input or I/O error in reading " <<
+ datastr;
+ throw runtime_error(err_oss.str());
+ }
+
+ for (string::size_type pos = 0; pos < bytes.size(); pos += 2) {
+ istringstream iss_byte(bytes.substr(pos, 2));
+ unsigned int ch;
+
+ iss_byte >> hex >> ch;
+ if (iss_byte.rdstate() != istream::eofbit) {
+ ostringstream err_oss;
+ err_oss << "invalid byte representation: " << iss_byte.str();
+ throw runtime_error(err_oss.str());
+ }
+ data.push_back(static_cast<unsigned char>(ch));
+ }
+ } while (!iss.eof());
+}
+
+::testing::AssertionResult
+UnitTestUtil::matchName(const char*, const char*,
+ const isc::dns::Name& name1,
+ const isc::dns::Name& name2) {
+ ::testing::Message msg;
+
+ NameComparisonResult cmpresult = name1.compare(name2);
+ if (cmpresult.getOrder() != 0 ||
+ cmpresult.getRelation() != NameComparisonResult::EQUAL) {
+ msg << "Two names are expected to be equal but not:\n"
+ << " One: " << name1 << "\n"
+ << "Other: " << name2 << "\n";
+ return (::testing::AssertionFailure(msg));
+ }
+ return (::testing::AssertionSuccess());
+}
diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h
new file mode 100644
index 0000000..34045a4
--- /dev/null
+++ b/src/lib/dns/tests/unittest_util.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNITTEST_UTIL_H
+#define UNITTEST_UTIL_H
+
+#include <vector>
+#include <string>
+
+#include <dns/name.h>
+#include <dns/message.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+
+class UnitTestUtil {
+public:
+ ///
+ /// read text format wire data from a file and put it to the given vector.
+ ///
+ static void readWireData(const char* datafile,
+ std::vector<unsigned char>& data);
+
+ ///
+ /// add a path that \c readWireData() will search for test data files.
+ ///
+ static void addDataPath(const std::string& directory);
+
+ ///
+ /// convert a sequence of hex strings into the corresponding list of
+ /// 8-bit integers, and append them to the vector.
+ ///
+ static void readWireData(const std::string& datastr,
+ std::vector<unsigned char>& data);
+
+ ///
+ /// Compare two names.
+ ///
+ /// This check method uses \c Name::compare() for comparison, which performs
+ /// deeper checks including the equality of offsets, and should be better
+ /// than EXPECT_EQ, which uses operator==. Like the \c matchWireData()
+ /// method, the usage is a bit awkward; the caller should use
+ /// \c EXPECT_PRED_FORMAT2.
+ ///
+ static ::testing::AssertionResult
+ matchName(const char* nameexp1, const char* nameexp2,
+ const isc::dns::Name& name1, const isc::dns::Name& name2);
+};
+
+}
+#endif // UNITTEST_UTIL_H
diff --git a/src/lib/dns/time_utils.cc b/src/lib/dns/time_utils.cc
new file mode 100644
index 0000000..2a34afe
--- /dev/null
+++ b/src/lib/dns/time_utils.cc
@@ -0,0 +1,195 @@
+// Copyright (C) 2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/time_utils.h>
+#include <exceptions/exceptions.h>
+
+#include <cstdint>
+#include <cstdio>
+#include <ctime>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <sys/time.h>
+
+using namespace std;
+
+namespace {
+int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+bool
+isLeap(const int y) {
+ return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
+}
+
+unsigned int
+yearSecs(const int year) {
+ return ((isLeap(year) ? 366 : 365) * 86400);
+}
+
+unsigned int
+monthSecs(const int month, const int year) {
+ return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0)) * 86400);
+}
+} // anonymous namespace
+
+namespace isc {
+namespace util {
+namespace {
+constexpr size_t DATE_LEN = 14; // YYYYMMDDHHmmSS
+
+uint64_t
+ull(const int c) {
+ return (static_cast<uint64_t>(c));
+}
+
+void
+checkRange(const unsigned min, const unsigned max, const unsigned value, const string& valname) {
+ if ((value >= min) && (value <= max)) {
+ return;
+ }
+
+ isc_throw(InvalidTime, "Invalid " << valname << " value: " << value);
+}
+} // anonymous namespace
+
+namespace detail {
+// timeToText32() below uses the current system time. To test it with
+// unusual current time values we introduce the following function pointer;
+// when it's non NULL, we call it to get the (normally faked) current time.
+// Otherwise we use the standard gettimeofday(2). This hook is specifically
+// intended for testing purposes, so, even if it's visible outside of this
+// library, it's not even declared in a header file.
+int64_t (*getTimeFunction)() = 0;
+
+int64_t
+getTimeWrapper() {
+ if (getTimeFunction != 0) {
+ return (getTimeFunction());
+ }
+
+ struct timeval now{};
+ gettimeofday(&now, 0);
+
+ return (static_cast<int64_t>(now.tv_sec));
+}
+} // namespace detail
+
+string
+timeToText64(uint64_t value) {
+ struct tm tm{};
+ unsigned int secs;
+
+ // We cannot rely on gmtime() because time_t may not be of 64 bit
+ // integer. The following conversion logic is borrowed from BIND 9.
+ tm.tm_year = 70;
+ while ((secs = yearSecs(tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ ++tm.tm_year;
+ if (tm.tm_year + 1900 > 9999) {
+ isc_throw(InvalidTime, "Time value out of range (year > 9999): " << tm.tm_year + 1900);
+ }
+ }
+
+ tm.tm_mon = 0;
+ while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) {
+ value -= secs;
+ tm.tm_mon++;
+ }
+
+ tm.tm_mday = 1;
+ while (86400 <= value) {
+ value -= 86400;
+ ++tm.tm_mday;
+ }
+
+ tm.tm_hour = 0;
+ while (3600 <= value) {
+ value -= 3600;
+ ++tm.tm_hour;
+ }
+
+ tm.tm_min = 0;
+ while (60 <= value) {
+ value -= 60;
+ ++tm.tm_min;
+ }
+
+ tm.tm_sec = value; // now t < 60, so this substitution is safe.
+
+ ostringstream oss;
+ oss << setfill('0') << setw(4) << tm.tm_year + 1900 << setw(2) << tm.tm_mon + 1 << setw(2)
+ << tm.tm_mday << setw(2) << tm.tm_hour << setw(2) << tm.tm_min << setw(2) << tm.tm_sec;
+ return (oss.str());
+}
+
+string
+timeToText32(const uint32_t value) {
+ // We first adjust the time to the closest epoch based on the current time.
+ // Note that the following variables must be signed in order to handle
+ // time until year 2038 correctly.
+ const int64_t start = detail::getTimeWrapper() - 0x7fffffff;
+ int64_t base = 0;
+ int64_t t;
+ while ((t = (base + value)) < start) {
+ base += 0x100000000LL;
+ }
+
+ // Then convert it to text.
+ return (timeToText64(t));
+}
+
+uint64_t
+timeFromText64(const string& time_txt) {
+ // Confirm the source only consists digits. sscanf() allows some
+ // minor exceptions.
+ for (string::size_type i = 0; i < time_txt.length(); ++i) {
+ if (!isdigit(time_txt.at(i))) {
+ isc_throw(InvalidTime, "Couldn't convert non-numeric time value: " << time_txt);
+ }
+ }
+
+ unsigned year, month, day, hour, minute, second;
+ if (time_txt.length() != DATE_LEN || sscanf(time_txt.c_str(), "%4u%2u%2u%2u%2u%2u", &year,
+ &month, &day, &hour, &minute, &second) != 6) {
+ isc_throw(InvalidTime, "Couldn't convert time value: " << time_txt);
+ }
+
+ checkRange(1970, 9999, year, "year");
+ checkRange(1, 12, month, "month");
+ checkRange(1, days[month - 1] + ((month == 2 && isLeap(year)) ? 1 : 0), day, "day");
+ checkRange(0, 23, hour, "hour");
+ checkRange(0, 59, minute, "minute");
+ checkRange(0, 60, second, "second"); // 60 == leap second.
+
+ uint64_t timeval = second + (ull(60) * minute) + (ull(3600) * hour) + ((day - 1) * ull(86400));
+ for (unsigned m = 0; m < (month - 1); ++m) {
+ timeval += days[m] * ull(86400);
+ }
+
+ if (isLeap(year) && month > 2) {
+ timeval += ull(86400);
+ }
+
+ for (unsigned y = 1970; y < year; ++y) {
+ timeval += ((isLeap(y) ? 366 : 365) * ull(86400));
+ }
+
+ return (timeval);
+}
+
+uint32_t
+timeFromText32(const string& time_txt) {
+ // The implicit conversion from uint64_t to uint32_t should just work here,
+ // because we only need to drop higher 32 bits.
+ return (timeFromText64(time_txt));
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/dns/time_utils.h b/src/lib/dns/time_utils.h
new file mode 100644
index 0000000..7459446
--- /dev/null
+++ b/src/lib/dns/time_utils.h
@@ -0,0 +1,168 @@
+// Copyright (C) 2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TIME_UTILS_H
+#define TIME_UTILS_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <cstdint>
+#include <string>
+#include <sys/types.h>
+
+namespace isc {
+namespace util {
+
+/// @brief A standard DNS (or ISC) module exception that is thrown if
+/// a time conversion function encounters bad input.
+class InvalidTime : public Exception {
+public:
+ InvalidTime(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {
+ }
+};
+
+namespace detail {
+
+/// @brief Return the current time in seconds.
+///
+/// This function returns the "current" time in seconds from epoch
+/// (00:00:00 January 1, 1970) as a 64-bit signed integer. The return
+/// value can represent a point of time before epoch as a negative number.
+///
+/// This function is provided to help test time conscious implementations
+/// such as DNSSEC and TSIG signatures. It is difficult to test them with
+/// an unusual or a specifically chosen "current" via system-provided
+/// library functions to get time. This function acts as a straightforward
+/// wrapper of such a library function, but provides test code with a hook
+/// to return an arbitrary time value: if @c isc::util::detail::getTimeFunction
+/// is set to a pointer of function that returns 64-bit signed integer,
+/// @c getTimeWrapper() calls that function instead of the system library.
+///
+/// This hook variable is specifically intended for testing purposes, so,
+/// even if it's visible outside of this library, it's not even declared in a
+/// header file.
+///
+/// If the implementation doesn't need to be tested with faked current time,
+/// it should simply use the system supplied library function instead of
+/// this one.
+///
+/// @return current time in seconds
+int64_t
+getTimeWrapper();
+
+} // namespace detail
+
+/// @name DNSSEC time conversion functions.
+///
+/// These functions convert between times represented in seconds (in integer)
+/// since epoch and those in the textual form used in the RRSIG records.
+/// For integers we provide both 32-bit and 64-bit versions.
+/// The RRSIG expiration and inception fields are both 32-bit unsigned
+/// integers, so 32-bit versions would be more useful for protocol operations.
+/// However, with 32-bit integers we need to take into account wrap-around
+/// points and compare values using the serial number arithmetic as specified
+/// in RFC4034, which would be more error prone. We therefore provide 64-bit
+/// versions, too.
+///
+/// The timezone is always UTC for these functions.
+
+/// @{
+/// @brief Convert textual DNSSEC time to integer, 64-bit version.
+///
+/// The textual form must only consist of digits and be in the form of
+/// YYYYMMDDHHmmSS, where:
+/// - YYYY must be between 1970 and 9999
+/// - MM must be between 01 and 12
+/// - DD must be between 01 and 31 and must be a valid day for the month
+/// represented in 'MM'. For example, if MM is 04, DD cannot be 31.
+/// DD can be 29 when MM is 02 only when YYYY is a leap year.
+/// - HH must be between 00 and 23
+/// - mm must be between 00 and 59
+/// - SS must be between 00 and 60
+///
+/// For all fields the range includes the begin and end values. Note that
+/// 60 is allowed for 'SS', intending a leap second, although in real operation
+/// it's unlikely to be specified.
+///
+/// If the given text is valid, this function converts it to an unsigned
+/// 64-bit number of seconds since epoch (1 January 1970 00:00:00) and returns
+/// the converted value. 64 bits are sufficient to represent all possible
+/// values for the valid format uniquely, so there is no overflow.
+///
+/// @note RFC4034 also defines the textual form of an unsigned decimal integer
+/// for the corresponding time in seconds. This function doesn't support
+/// this form, and if given it throws an exception of class @c InvalidTime.
+///
+/// @param time_txt Textual time in the form of YYYYMMDDHHmmSS
+///
+/// @return Seconds since epoch corresponding to @c time_txt
+///
+/// @throw InvalidTime The given textual representation is invalid.
+uint64_t
+timeFromText64(const std::string& time_txt);
+
+/// @brief Convert textual DNSSEC time to integer, 32-bit version.
+///
+/// This version is the same as @c timeFromText64() except that the return
+/// value is wrapped around to an unsigned 32-bit integer, simply dropping
+/// the upper 32 bits.
+///
+/// @param time_txt Textual time in the form of YYYYMMDDHHmmSS
+///
+/// @return Seconds since epoch corresponding to @c time_txt as uint_32
+uint32_t
+timeFromText32(const std::string& time_txt);
+
+/// @brief Convert integral DNSSEC time to textual form, 64-bit version.
+///
+/// This function takes an integer that would be seconds since epoch and
+/// converts it in the form of YYYYMMDDHHmmSS. For example, if @c value is
+/// 0, it returns "19700101000000". If the value corresponds to a point
+/// of time on and after year 10,000, which cannot be represented in the
+/// YYYY... form, an exception of class @c InvalidTime will be thrown.
+///
+/// @param value Seconds since epoch to be converted.
+///
+/// @return Textual representation of @c value in the form of YYYYMMDDHHmmSS.
+///
+/// @throw InvalidTime The given time specifies on or after year 10,000.
+/// @throw Other A standard exception, if resource allocation for the
+/// returned text fails.
+std::string
+timeToText64(uint64_t value);
+
+/// @brief Convert integral DNSSEC time to textual form, 32-bit version.
+///
+/// This version is the same as @c timeToText64(), but the time value
+/// is expected to be the lower 32 bits of the full 64-bit value.
+/// These two will be different on and after a certain point of time
+/// in year 2106, so this function internally resolves the ambiguity
+/// using the current system time at the time of function call;
+/// it first identifies the range of [N*2^32 - 2^31, N*2^32 + 2^31)
+/// that contains the current time, and interprets @c value in the context
+/// of that range. It then applies the same process as @c timeToText64().
+///
+/// There is one important exception in this processing, however.
+/// Until 19 Jan 2038 03:14:08 (2^31 seconds since epoch), this range
+/// would contain time before epoch. In order to ensure the returned
+/// value is also a valid input to @c timeFromText, this function uses
+/// a special range [0, 2^32) until that time. As a result, all upper
+/// half of the 32-bit values are treated as a future time. For example,
+/// 2^32-1 (the highest value in 32-bit unsigned integers) will be converted
+/// to "21060207062815", instead of "19691231235959".
+///
+/// @param value Seconds since epoch to be converted.
+///
+/// @return Textual representation of @c value in the form of YYYYMMDDHHmmSS.
+std::string
+timeToText32(uint32_t value);
+///@}
+
+} // namespace util
+} // namespace isc
+
+#endif // TIME_UTILS_H
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
new file mode 100644
index 0000000..c096ae5
--- /dev/null
+++ b/src/lib/dns/tsig.cc
@@ -0,0 +1,577 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/time_utils.h>
+#include <dns/tsig.h>
+#include <dns/tsigerror.h>
+#include <dns/tsigkey.h>
+#include <util/buffer.h>
+
+#include <cassert>
+#include <sys/time.h>
+#include <stdint.h>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+using namespace isc::util;
+using namespace isc::cryptolink;
+using namespace isc::dns::rdata;
+
+using namespace std;
+
+
+namespace isc {
+namespace dns {
+namespace {
+typedef boost::shared_ptr<HMAC> HMACPtr;
+
+// TSIG uses 48-bit unsigned integer to represent time signed.
+// Since getTimeWrapper() returns a 64-bit *signed* integer, we
+// make sure it's stored in an unsigned 64-bit integer variable and
+// represents a value in the expected range. (In reality, however,
+// getTimeWrapper() will return a positive integer that will fit
+// in 48 bits)
+uint64_t
+getTSIGTime() {
+ return (detail::getTimeWrapper() & 0x0000ffffffffffffULL);
+}
+}
+
+struct TSIGContext::TSIGContextImpl {
+ TSIGContextImpl(const TSIGKey& key,
+ TSIGError error = TSIGError::NOERROR()) :
+ state_(INIT), key_(key), error_(error),
+ previous_timesigned_(0), digest_len_(0),
+ last_sig_dist_(-1) {
+ if (error == TSIGError::NOERROR()) {
+ // In normal (NOERROR) case, the key should be valid, and we
+ // should be able to pre-create a corresponding HMAC object,
+ // which will be likely to be used for sign or verify later.
+ // We do this in the constructor so that we can know the expected
+ // digest length in advance. The creation should normally succeed,
+ // but the key information could be still broken, which could
+ // trigger an exception inside the cryptolink module. We ignore
+ // it at this moment; a subsequent sign/verify operation will try
+ // to create the HMAC, which would also fail.
+ try {
+ hmac_.reset(CryptoLink::getCryptoLink().createHMAC(
+ key_.getSecret(), key_.getSecretLength(),
+ key_.getAlgorithm()),
+ deleteHMAC);
+ } catch (const isc::Exception&) {
+ return;
+ }
+ size_t digestbits = key_.getDigestbits();
+ size_t default_digest_len = hmac_->getOutputLength();
+ if (digestbits > 0) {
+ digest_len_ = (digestbits + 7) / 8;
+ // sanity (cf. RFC 4635)
+ if ((digest_len_ < 10) ||
+ (digest_len_ < (default_digest_len / 2)) ||
+ (digest_len_ > default_digest_len)) {
+ // should emit a warning?
+ digest_len_ = default_digest_len;
+ }
+ } else {
+ digest_len_ = default_digest_len;
+ }
+ }
+ }
+
+ // This helper method is used from verify(). It's expected to be called
+ // just before verify() returns. It updates internal state based on
+ // the verification result and return the TSIGError to be returned to
+ // the caller of verify(), so that verify() can call this method within
+ // its 'return' statement.
+ TSIGError postVerifyUpdate(TSIGError error, const void* digest,
+ uint16_t digest_len) {
+ if (state_ == INIT) {
+ state_ = RECEIVED_REQUEST;
+ } else if (state_ == SENT_REQUEST && error == TSIGError::NOERROR()) {
+ state_ = VERIFIED_RESPONSE;
+ }
+ if (digest) {
+ previous_digest_.assign(static_cast<const uint8_t*>(digest),
+ static_cast<const uint8_t*>(digest) +
+ digest_len);
+ }
+ error_ = error;
+ return (error);
+ }
+
+ // A shortcut method to create an HMAC object for sign/verify. If one
+ // has been successfully created in the constructor, return it; otherwise
+ // create a new one and return it. In the former case, the ownership is
+ // transferred to the caller; the stored HMAC will be reset after the
+ // call.
+ HMACPtr createHMAC() {
+ if (hmac_) {
+ HMACPtr ret = HMACPtr();
+ ret.swap(hmac_);
+ return (ret);
+ }
+ return (HMACPtr(CryptoLink::getCryptoLink().createHMAC(
+ key_.getSecret(), key_.getSecretLength(),
+ key_.getAlgorithm()),
+ deleteHMAC));
+ }
+
+ // The following three are helper methods to compute the digest for
+ // TSIG sign/verify in order to unify the common code logic for sign()
+ // and verify() and to keep these callers concise.
+ // These methods take an HMAC object, which will be updated with the
+ // calculated digest.
+ // Note: All methods construct a local OutputBuffer as a work space with a
+ // fixed initial buffer size to avoid intermediate buffer extension.
+ // This should be efficient enough, especially for fundamentally expensive
+ // operation like cryptographic sign/verify, but if the creation of the
+ // buffer in each helper method is still identified to be a severe
+ // performance bottleneck, we could have this class a buffer as a member
+ // variable and reuse it throughout the object's lifetime. Right now,
+ // we prefer keeping the scope for local things as small as possible.
+ void digestPreviousMAC(HMACPtr hmac);
+ void digestTSIGVariables(HMACPtr hmac, uint16_t rrclass, uint32_t rrttl,
+ uint64_t time_signed, uint16_t fudge,
+ uint16_t error, uint16_t otherlen,
+ const void* otherdata,
+ bool time_variables_only) const;
+ void digestDNSMessage(HMACPtr hmac, uint16_t qid, const void* data,
+ size_t data_len) const;
+ State state_;
+ const TSIGKey key_;
+ vector<uint8_t> previous_digest_;
+ TSIGError error_;
+ uint64_t previous_timesigned_; // only meaningful for response with BADTIME
+ size_t digest_len_;
+ HMACPtr hmac_;
+ // This is the distance from the last verified signed message. Value of 0
+ // means the last message was signed. Special value -1 means there was no
+ // signed message yet.
+ int last_sig_dist_;
+};
+
+void
+TSIGContext::TSIGContextImpl::digestPreviousMAC(HMACPtr hmac) {
+ // We should have ensured the digest size fits 16 bits within this class
+ // implementation.
+ isc_throw_assert(previous_digest_.size() <= 0xffff);
+
+ if (previous_digest_.empty()) {
+ // The previous digest was already used. We're in the middle of
+ // TCP stream somewhere and we already pushed some unsigned message
+ // into the HMAC state.
+ return;
+ }
+
+ OutputBuffer buffer(sizeof(uint16_t) + previous_digest_.size());
+ const uint16_t previous_digest_len(previous_digest_.size());
+ buffer.writeUint16(previous_digest_len);
+ if (previous_digest_len != 0) {
+ buffer.writeData(&previous_digest_[0], previous_digest_len);
+ }
+ hmac->update(buffer.getData(), buffer.getLength());
+}
+
+void
+TSIGContext::TSIGContextImpl::digestTSIGVariables(HMACPtr hmac, uint16_t rrclass,
+ uint32_t rrttl, uint64_t time_signed,
+ uint16_t fudge, uint16_t error,
+ uint16_t otherlen, const void* otherdata,
+ bool time_variables_only) const {
+ // It's bit complicated, but we can still predict the necessary size of
+ // the data to be digested. So we precompute it to avoid possible
+ // reallocation inside OutputBuffer (not absolutely necessary, but this
+ // is a bit more efficient)
+ size_t data_size = 8;
+ if (!time_variables_only) {
+ data_size += 10 + key_.getKeyName().getLength() +
+ key_.getAlgorithmName().getLength();
+ }
+ OutputBuffer buffer(data_size);
+
+ if (!time_variables_only) {
+ key_.getKeyName().toWire(buffer);
+ buffer.writeUint16(rrclass);
+ buffer.writeUint32(rrttl);
+ key_.getAlgorithmName().toWire(buffer);
+ }
+ buffer.writeUint16(time_signed >> 32);
+ buffer.writeUint32(time_signed & 0xffffffff);
+ buffer.writeUint16(fudge);
+
+ if (!time_variables_only) {
+ buffer.writeUint16(error);
+ buffer.writeUint16(otherlen);
+ }
+
+ hmac->update(buffer.getData(), buffer.getLength());
+ if (!time_variables_only && otherlen > 0) {
+ hmac->update(otherdata, otherlen);
+ }
+}
+
+// In digestDNSMessage, we exploit some minimum knowledge of DNS message
+// format:
+// - the header section has a fixed length of 12 octets (MESSAGE_HEADER_LEN)
+// - the offset in the header section to the ID field is 0
+// - the offset in the header section to the ARCOUNT field is 10 (and the field
+// length is 2 octets)
+// We could construct a separate Message object from the given data, adjust
+// fields via the Message interfaces and then render it back to a separate
+// buffer, but that would be overkilling. The DNS message header has a
+// fixed length and necessary modifications are quite straightforward, so
+// we do the job using lower level interfaces.
+namespace {
+const size_t MESSAGE_HEADER_LEN = 12;
+}
+
+void
+TSIGContext::TSIGContextImpl::digestDNSMessage(HMACPtr hmac,
+ uint16_t qid, const void* data,
+ size_t data_len) const {
+ OutputBuffer buffer(MESSAGE_HEADER_LEN);
+ const uint8_t* msgptr = static_cast<const uint8_t*>(data);
+
+ // Install the original ID
+ buffer.writeUint16(qid);
+ msgptr += sizeof(uint16_t);
+
+ // Copy the rest of the header except the ARCOUNT field.
+ buffer.writeData(msgptr, 8);
+ msgptr += 8;
+
+ // Install the adjusted ARCOUNT (we don't care even if the value is bogus
+ // and it underflows; it would simply result in verification failure)
+ buffer.writeUint16(InputBuffer(msgptr, sizeof(uint16_t)).readUint16() - 1);
+ msgptr += 2;
+
+ // Digest the header and the rest of the DNS message
+ hmac->update(buffer.getData(), buffer.getLength());
+ hmac->update(msgptr, data_len - MESSAGE_HEADER_LEN);
+}
+
+TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key)) {
+}
+
+TSIGContext::TSIGContext(const Name& key_name, const Name& algorithm_name,
+ const TSIGKeyRing& keyring) : impl_(0) {
+ const TSIGKeyRing::FindResult result(keyring.find(key_name,
+ algorithm_name));
+ if (result.code == TSIGKeyRing::NOTFOUND) {
+ // If not key is found, create a dummy key with the specified key
+ // parameters and empty secret. In the common scenario this will
+ // be used in subsequent response with a TSIG indicating a BADKEY
+ // error.
+ impl_.reset(new TSIGContextImpl(TSIGKey(key_name, algorithm_name, 0, 0),
+ TSIGError::BAD_KEY()));
+ } else {
+ impl_.reset(new TSIGContextImpl(*result.key));
+ }
+}
+
+TSIGContext::~TSIGContext() {
+}
+
+size_t
+TSIGContext::getTSIGLength() const {
+ //
+ // The space required for an TSIG record is:
+ //
+ // n1 bytes for the (key) name
+ // 2 bytes for the type
+ // 2 bytes for the class
+ // 4 bytes for the ttl
+ // 2 bytes for the rdlength
+ // n2 bytes for the algorithm name
+ // 6 bytes for the time signed
+ // 2 bytes for the fudge
+ // 2 bytes for the MAC size
+ // x bytes for the MAC
+ // 2 bytes for the original id
+ // 2 bytes for the error
+ // 2 bytes for the other data length
+ // y bytes for the other data (at most)
+ // ---------------------------------
+ // 26 + n1 + n2 + x + y bytes
+ //
+
+ // Normally the digest length ("x") is the length of the underlying
+ // hash output. If a key related error occurred, however, the
+ // corresponding TSIG will be "unsigned", and the digest length will be 0.
+ const size_t digest_len =
+ (impl_->error_ == TSIGError::BAD_KEY() ||
+ impl_->error_ == TSIGError::BAD_SIG()) ? 0 : impl_->digest_len_;
+
+ // Other Len ("y") is normally 0; if BAD_TIME error occurred, the
+ // subsequent TSIG will contain 48 bits of the server current time.
+ const size_t other_len = (impl_->error_ == TSIGError::BAD_TIME()) ? 6 : 0;
+
+ return (26 + impl_->key_.getKeyName().getLength() +
+ impl_->key_.getAlgorithmName().getLength() +
+ digest_len + other_len);
+}
+
+TSIGContext::State
+TSIGContext::getState() const {
+ return (impl_->state_);
+}
+
+TSIGError
+TSIGContext::getError() const {
+ return (impl_->error_);
+}
+
+ConstTSIGRecordPtr
+TSIGContext::sign(const uint16_t qid, const void* const data,
+ const size_t data_len) {
+ if (impl_->state_ == VERIFIED_RESPONSE) {
+ isc_throw(TSIGContextError,
+ "TSIG sign attempt after verifying a response");
+ }
+
+ if (!data || data_len == 0) {
+ isc_throw(InvalidParameter, "TSIG sign error: empty data is given");
+ }
+
+ TSIGError error(TSIGError::NOERROR());
+ const uint64_t now = getTSIGTime();
+
+ // For responses adjust the error code.
+ if (impl_->state_ == RECEIVED_REQUEST) {
+ error = impl_->error_;
+ }
+
+ // For errors related to key or MAC, return an unsigned response as
+ // specified in Section 4.3 of RFC2845.
+ if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
+ ConstTSIGRecordPtr tsig(new TSIGRecord(
+ impl_->key_.getKeyName(),
+ any::TSIG(impl_->key_.getAlgorithmName(),
+ now, DEFAULT_FUDGE, 0, 0,
+ qid, error.getCode(), 0, 0)));
+ impl_->previous_digest_.clear();
+ impl_->state_ = SENT_RESPONSE;
+ return (tsig);
+ }
+
+ HMACPtr hmac(impl_->createHMAC());
+
+ // If the context has previous MAC (either the Request MAC or its own
+ // previous MAC), digest it.
+ if (impl_->state_ != INIT) {
+ impl_->digestPreviousMAC(hmac);
+ }
+
+ // Digest the message (without TSIG)
+ hmac->update(data, data_len);
+
+ // Digest TSIG variables.
+ // First, prepare some non constant variables.
+ const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ?
+ impl_->previous_timesigned_ : now;
+ // For BADTIME error, we include 6 bytes of other data.
+ // (6 bytes = size of time signed value)
+ const uint16_t otherlen = (error == TSIGError::BAD_TIME()) ? 6 : 0;
+ OutputBuffer otherdatabuf(otherlen);
+ if (error == TSIGError::BAD_TIME()) {
+ otherdatabuf.writeUint16(now >> 32);
+ otherdatabuf.writeUint32(now & 0xffffffff);
+ }
+ const void* const otherdata =
+ (otherlen == 0) ? 0 : otherdatabuf.getData();
+ // Then calculate the digest. If state_ is SENT_RESPONSE we are sending
+ // a continued message in the same TCP stream so skip digesting
+ // variables except for time related variables (RFC2845 4.4).
+ impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+ TSIGRecord::TSIG_TTL, time_signed,
+ DEFAULT_FUDGE, error.getCode(),
+ otherlen, otherdata,
+ impl_->state_ == SENT_RESPONSE);
+
+ // Get the final digest, update internal state, then finish.
+ vector<uint8_t> digest = hmac->sign(impl_->digest_len_);
+ isc_throw_assert(digest.size() <= 0xffff); // cryptolink API should have ensured it.
+ ConstTSIGRecordPtr tsig(new TSIGRecord(
+ impl_->key_.getKeyName(),
+ any::TSIG(impl_->key_.getAlgorithmName(),
+ time_signed, DEFAULT_FUDGE,
+ digest.size(), &digest[0],
+ qid, error.getCode(), otherlen,
+ otherdata)));
+ // Exception free from now on.
+ impl_->previous_digest_.swap(digest);
+ impl_->state_ = (impl_->state_ == INIT) ? SENT_REQUEST : SENT_RESPONSE;
+ return (tsig);
+}
+
+TSIGError
+TSIGContext::verify(const TSIGRecord* const record, const void* const data,
+ const size_t data_len) {
+ if (impl_->state_ == SENT_RESPONSE) {
+ isc_throw(TSIGContextError,
+ "TSIG verify attempt after sending a response");
+ }
+
+ if (!record) {
+ if (impl_->last_sig_dist_ >= 0 && impl_->last_sig_dist_ < 99) {
+ // It is not signed, but in the middle of TCP stream. We just
+ // update the HMAC state and consider this message OK.
+ update(data, data_len);
+ // This one is not signed, the last signed is one message further
+ // now.
+ impl_->last_sig_dist_++;
+ // No digest to return now. Just say it's OK.
+ return (impl_->postVerifyUpdate(TSIGError::NOERROR(), 0, 0));
+ }
+ // This case happens when we sent a signed request and have received an
+ // unsigned response. According to RFC2845 Section 4.6 this case should be
+ // considered a "format error" (although the specific error code
+ // wouldn't matter much for the caller).
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), 0, 0));
+ }
+
+ const any::TSIG& tsig_rdata = record->getRdata();
+
+ // Reject some obviously invalid data
+ if (data_len < MESSAGE_HEADER_LEN + record->getLength()) {
+ isc_throw(InvalidParameter,
+ "TSIG verify: data length is invalid: " << data_len);
+ }
+ if (!data) {
+ isc_throw(InvalidParameter, "TSIG verify: empty data is invalid");
+ }
+
+ // This message is signed and we won't throw any more.
+ impl_->last_sig_dist_ = 0;
+
+ // Check key: whether we first verify it with a known key or we verify
+ // it using the consistent key in the context. If the check fails we are
+ // done with BADKEY.
+ if (impl_->state_ == INIT && impl_->error_ == TSIGError::BAD_KEY()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), 0, 0));
+ }
+ if (impl_->key_.getKeyName() != record->getName() ||
+ impl_->key_.getAlgorithmName() != tsig_rdata.getAlgorithm()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), 0, 0));
+ }
+
+ // Check time: the current time must be in the range of
+ // [time signed - fudge, time signed + fudge]. Otherwise verification
+ // fails with BADTIME. (RFC2845 Section 4.6.2)
+ // Note: for simplicity we don't explicitly catch the case of too small
+ // current time causing underflow. With the fact that fudge is quite
+ // small and (for now) non configurable, it shouldn't be a real concern
+ // in practice.
+ const uint64_t now = getTSIGTime();
+ if (tsig_rdata.getTimeSigned() + DEFAULT_FUDGE < now ||
+ tsig_rdata.getTimeSigned() - DEFAULT_FUDGE > now) {
+ const void* digest = 0;
+ size_t digest_len = 0;
+ if (impl_->state_ == INIT) {
+ digest = tsig_rdata.getMAC();
+ digest_len = tsig_rdata.getMACSize();
+ impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+ }
+ return (impl_->postVerifyUpdate(TSIGError::BAD_TIME(), digest,
+ digest_len));
+ }
+
+ // Handling empty MAC. While RFC2845 doesn't explicitly prohibit other
+ // cases, it can only reasonably happen in a response with BADSIG or
+ // BADKEY. We reject other cases as if it were BADSIG to avoid unexpected
+ // acceptance of a bogus signature. This behavior follows the BIND 9
+ // implementation.
+ if (tsig_rdata.getMACSize() == 0) {
+ TSIGError error = TSIGError(tsig_rdata.getError());
+ if (error != TSIGError::BAD_SIG() && error != TSIGError::BAD_KEY()) {
+ error = TSIGError::BAD_SIG();
+ }
+ return (impl_->postVerifyUpdate(error, 0, 0));
+ }
+
+ HMACPtr hmac(impl_->createHMAC());
+
+ // If the context has previous MAC (either the Request MAC or its own
+ // previous MAC), digest it.
+ if (impl_->state_ != INIT) {
+ impl_->digestPreviousMAC(hmac);
+ }
+
+ // Signature length check based on RFC 4635 3.1
+ if (tsig_rdata.getMACSize() > hmac->getOutputLength()) {
+ // signature length too big
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), 0, 0));
+ }
+ if ((tsig_rdata.getMACSize() < 10) ||
+ (tsig_rdata.getMACSize() < (hmac->getOutputLength() / 2))) {
+ // signature length below minimum
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), 0, 0));
+ }
+ if (tsig_rdata.getMACSize() < impl_->digest_len_) {
+ // (truncated) signature length too small
+ return (impl_->postVerifyUpdate(TSIGError::BAD_TRUNC(), 0, 0));
+ }
+
+ //
+ // Digest DNS message (excluding the trailing TSIG RR and adjusting the
+ // QID and ARCOUNT header fields)
+ //
+ impl_->digestDNSMessage(hmac, tsig_rdata.getOriginalID(),
+ data, data_len - record->getLength());
+
+ // Digest TSIG variables. If state_ is VERIFIED_RESPONSE, it's a
+ // continuation of the same TCP stream and skip digesting them except
+ // for time related variables (RFC2845 4.4).
+ // Note: we use the constant values for RR class and TTL specified
+ // in RFC2845, not received values (we reject other values in constructing
+ // the TSIGRecord).
+ impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+ TSIGRecord::TSIG_TTL,
+ tsig_rdata.getTimeSigned(),
+ tsig_rdata.getFudge(), tsig_rdata.getError(),
+ tsig_rdata.getOtherLen(),
+ tsig_rdata.getOtherData(),
+ impl_->state_ == VERIFIED_RESPONSE);
+
+ // Verify the digest with the received signature.
+ if (hmac->verify(tsig_rdata.getMAC(), tsig_rdata.getMACSize())) {
+ return (impl_->postVerifyUpdate(TSIGError::NOERROR(),
+ tsig_rdata.getMAC(),
+ tsig_rdata.getMACSize()));
+ }
+
+ return (impl_->postVerifyUpdate(TSIGError::BAD_SIG(), 0, 0));
+}
+
+bool
+TSIGContext::lastHadSignature() const {
+ if (impl_->last_sig_dist_ == -1) {
+ isc_throw(TSIGContextError, "No message was verified yet");
+ }
+ return (impl_->last_sig_dist_ == 0);
+}
+
+void
+TSIGContext::update(const void* const data, size_t len) {
+ HMACPtr hmac(impl_->createHMAC());
+ // Use the previous digest and never use it again
+ impl_->digestPreviousMAC(hmac);
+ impl_->previous_digest_.clear();
+ // Push the message there
+ hmac->update(data, len);
+ impl_->hmac_ = hmac;
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h
new file mode 100644
index 0000000..10e59b3
--- /dev/null
+++ b/src/lib/dns/tsig.h
@@ -0,0 +1,441 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// IMPORTANT: the server side of this code MUST NOT be used until
+// it was fixed, cf RFC 8945. Note that Kea uses only the client side.
+
+#ifndef TSIG_H
+#define TSIG_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tsigerror.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigrecord.h>
+
+namespace isc {
+namespace dns {
+
+/// An exception that is thrown for logic errors identified in TSIG
+/// sign/verify operations.
+///
+/// Note that this exception is not thrown for TSIG protocol errors such as
+/// verification failures. In general, this exception indicates an internal
+/// program bug.
+class TSIGContextError : public isc::Exception {
+public:
+ TSIGContextError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// TSIG session context.
+///
+/// The \c TSIGContext class maintains a context of a signed session of
+/// DNS transactions by TSIG. In many cases a TSIG signed session consists
+/// of a single set of request (e.g. normal query) and reply (e.g. normal
+/// response), where the request is initially signed by the client, and the
+/// reply is signed by the server using the initial signature. As mentioned
+/// in RFC2845, a session can consist of multiple exchanges in a TCP
+/// connection. As also mentioned in the RFC, an AXFR response often contains
+/// multiple DNS messages, which can belong to the same TSIG session.
+/// This class supports all these cases.
+///
+/// A \c TSIGContext object is generally constructed with a TSIG key to be
+/// used for the session, and keeps track of various kinds of session specific
+/// information, such as the original digest while waiting for a response or
+/// verification error information that is to be used for a subsequent
+/// response.
+///
+/// This class has two main methods, \c sign() and \c verify().
+/// The \c sign() method signs given data (which is supposed to be a complete
+/// DNS message without the TSIG itself) using the TSIG key and other
+/// related information associated with the \c TSIGContext object.
+/// The \c verify() method verifies a given DNS message that contains a TSIG
+/// RR using the key and other internal information.
+///
+/// In general, a DNS client that wants to send a signed query will construct
+/// a \c TSIGContext object with the TSIG key that the client is intending to
+/// use, and sign the query with the context. The client will keeps the
+/// context, and verify the response with it.
+///
+/// On the other hand, a DNS server will construct a \c TSIGContext object
+/// with the information of the TSIG RR included in a query with a set of
+/// possible keys (in the form of a \c TSIGKeyRing object). The constructor
+/// in this mode will identify the appropriate TSIG key (or internally record
+/// an error if it doesn't find a key). The server will then verify the
+/// query with the context, and generate a signed response using the same
+/// same context.
+///
+/// When multiple messages belong to the same TSIG session, either side
+/// (signer or verifier) will keep using the same context. It records
+/// the latest session state (such as the previous digest) so that repeated
+/// calls to \c sign() or \c verify() work correctly in terms of the TSIG
+/// protocol.
+///
+/// \b Examples
+///
+/// This is a typical client application that sends a TSIG signed query
+/// and verifies the response.
+///
+/// \code
+/// // "renderer" is of MessageRenderer to render the message.
+/// // (TSIGKey would be configured from config or command line in real app)
+/// TSIGContext ctx(TSIGKey("key.example:MSG6Ng=="));
+/// Message message(Message::RENDER);
+/// message.addQuestion(Question(Name("www.example.com"), RRClass::IN(),
+/// RRType::A()));
+/// message.toWire(renderer, ctx);
+///
+/// // sendto, then recvfrom. received result in (data, data_len)
+///
+/// message.clear(Message::PARSE);
+/// InputBuffer buffer(data, data_len);
+/// message.fromWire(buffer);
+/// TSIGError tsig_error = ctx.verify(message.getTSIGRecord(),
+/// data, data_len);
+/// if (tsig_error == TSIGError::NOERROR()) {
+/// // okay. ctx can be continuously used if it's receiving subsequent
+/// // signed responses from a TCP stream.
+/// } else if (message.getRcode() == Rcode::NOTAUTH()) {
+/// // hard error. give up this transaction per RFC2845 4.6.
+/// } else {
+/// // Other error: discard response keep waiting with the same ctx
+/// // for another (again, RFC2845 4.6).
+/// } \endcode
+///
+/// And this is a typical server application that authenticates a signed
+/// query and returns a response according to the result.
+///
+/// \code
+/// // Assume "message" is of type Message for query handling and
+/// // "renderer" is of MessageRenderer to render responses.
+/// Message message(Message::RENDER);
+///
+/// TSIGKeyRing keyring; // this must be configured with keys somewhere
+///
+/// // Receive a query and store it in (data, data_len)
+/// InputBuffer buffer(data, data_len);
+/// message.clear(Message::PARSE);
+/// message.fromWire(buffer);
+///
+/// const TSIGRecord* tsig = message.getTSIGRecord();
+/// if (tsig) {
+/// TSIGContext ctx(tsig->getName(), tsig->getRdata().getAlgorithm(),
+/// keyring);
+/// ctx.verify(tsig, data, data_len);
+///
+/// // prepare response
+/// message.makeResponse();
+/// //...
+/// message.toWire(renderer, ctx);
+///
+/// // send the response data back to the client.
+/// // If this is a beginning of a signed session over a TCP and
+/// // server has more data to send to the client, this ctx
+/// // will be used to sign subsequent messages.
+/// } \endcode
+///
+/// <b>TCP Consideration</b>
+///
+/// RFC2845 describes the case where a single TSIG session is used for
+/// multiple DNS messages (Section 4.4). This class supports signing and
+/// verifying the messages in this scenario, but does not care if the messages
+/// were delivered over a TCP connection or not. If, for example, the
+/// same \c TSIGContext object is used to sign two independent DNS queries
+/// sent over UDP, they will be considered to belong to the same TSIG
+/// session, and, as a result, verification will be likely to fail.
+///
+/// \b Copyability
+///
+/// This class is currently non copyable based on the observation of the
+/// typical usage as described above. But there is no strong technical
+/// reason why this class cannot be copyable. If we see the need for it
+/// in future we may change the implementation on this point.
+///
+/// <b>Note to developers:</b>
+/// One basic design choice is to make the \c TSIGContext class is as
+/// independent from the \c Message class. This is because the latter is
+/// much more complicated, depending on many other classes, while TSIG is
+/// a very specific part of the entire DNS protocol set. If the \c TSIGContext
+/// class depends on \c \c Message, it will be more vulnerable to changes
+/// to other classes, and will be more difficult to test due to the
+/// direct or indirect dependencies. The interface of \c sign() that takes
+/// opaque data (instead of, e.g., a \c Message or \c MessageRenderer object)
+/// is therefore a deliberate design decision.
+class TSIGContext : boost::noncopyable {
+public:
+ /// Internal state of context
+ ///
+ /// The constants of this enum type define a specific state of
+ /// \c TSIGContext to adjust the behavior. The definition is public
+ /// and the state can be seen via the \c getState() method, but this is
+ /// mostly private information. It's publicly visible mainly for testing
+ /// purposes; there is no API for the application to change the state
+ /// directly.
+ enum State {
+ INIT, ///< Initial state
+ SENT_REQUEST, ///< Client sent a signed request, waiting response
+ RECEIVED_REQUEST, ///< Server received a signed request
+ SENT_RESPONSE, ///< Server sent a signed response
+ VERIFIED_RESPONSE ///< Client successfully verified a response
+ };
+
+ /// \name Constructors and destructor
+ ///
+ //@{
+ /// Constructor from a TSIG key.
+ ///
+ /// \exception std::bad_alloc Resource allocation for internal data fails
+ ///
+ /// \param key The TSIG key to be used for TSIG sessions with this context.
+ explicit TSIGContext(const TSIGKey& key);
+
+ /// Constructor from key parameters and key ring.
+ TSIGContext(const Name& key_name, const Name& algorithm_name,
+ const TSIGKeyRing& keyring);
+
+ /// The destructor.
+ virtual ~TSIGContext();
+ //@}
+
+ /// Sign a DNS message.
+ ///
+ /// This method computes the TSIG MAC for the given data, which is
+ /// generally expected to be a complete, wire-format DNS message
+ /// that doesn't contain a TSIG RR, based on the TSIG key and
+ /// other context information of \c TSIGContext, and returns a
+ /// result in the form of a (pointer object pointing to)
+ /// \c TSIGRecord object.
+ ///
+ /// The caller of this method will use the returned value to render a
+ /// complete TSIG RR into the message that has been signed so that it
+ /// will become a complete TSIG-signed message.
+ ///
+ /// In general, this method is called once by a client to send a
+ /// signed request or one more times by a server to sign
+ /// response(s) to a signed request. To avoid allowing accidental
+ /// misuse, if this method is called after a "client" validates a
+ /// response, an exception of class \c TSIGContextError will be
+ /// thrown.
+ ///
+ /// \note Normal applications are not expected to call this method
+ /// directly; they will usually use the \c Message::toWire() method
+ /// with a \c TSIGContext object being a parameter and have the
+ /// \c Message class create a complete signed message.
+ ///
+ /// This method treats the given data as opaque, even though it's generally
+ /// expected to represent a wire-format DNS message (see also the class
+ /// description), and doesn't inspect it in any way. For example, it
+ /// doesn't check whether the data length is sane for a valid DNS message.
+ /// This is also the reason why this method takes the \c qid parameter,
+ /// which will be used as the original ID of the resulting
+ /// \c TSIGRecordx object, even though this value should be stored in the
+ /// first two octets (in wire format) of the given data.
+ ///
+ /// \note This method still checks and rejects empty data (null pointer
+ /// data or the specified data length is 0) in order to avoid catastrophic
+ /// effect such as program crash. Empty data is not necessarily invalid
+ /// for HMAC computation, but obviously it doesn't make sense for a DNS
+ /// message.
+ ///
+ /// This method provides the strong exception guarantee; unless the method
+ /// returns (without an exception being thrown), the internal state of
+ /// the \c TSIGContext won't be modified.
+ ///
+ /// \exception TSIGContextError Context already verified a response.
+ /// \exception InvalidParameter \c data is 0 or \c data_len is 0
+ /// \exception cryptolink::LibraryError Some unexpected error in the
+ /// underlying crypto operation
+ /// \exception std::bad_alloc Temporary resource allocation failure
+ ///
+ /// \param qid The QID to be as the value of the original ID field of
+ /// the resulting TSIG record
+ /// \param data Points to the wire-format data to be signed
+ /// \param data_len The length of \c data in bytes
+ ///
+ /// \return A TSIG record for the given data along with the context.
+ virtual ConstTSIGRecordPtr
+ sign(const uint16_t qid, const void* const data, const size_t data_len);
+
+ /// Verify a DNS message.
+ ///
+ /// This method verifies given data along with the context and a given
+ /// TSIG in the form of a \c TSIGRecord object. The data to be verified
+ /// is generally expected to be a complete, wire-format DNS message,
+ /// exactly as received by the host, and ending with a TSIG RR.
+ /// After verification process this method updates its internal state,
+ /// and returns the result in the form of a \c TSIGError object.
+ /// Possible return values are (see the \c TSIGError class description
+ /// for the mnemonics):
+ ///
+ /// - \c NOERROR: The data has been verified correctly.
+ /// - \c FORMERR: \c TSIGRecord is not given (see below).
+ /// - \c BAD_KEY: Appropriate key is not found or specified key doesn't
+ /// match for the data.
+ /// - \c BAD_TIME: The current time doesn't fall in the range specified
+ /// in the TSIG.
+ /// - \c BAD_SIG: The signature given in the TSIG doesn't match against
+ /// the locally computed digest or is the signature is
+ /// invalid in other way.
+ /// - \c BAD_MODE: Not yet implemented TKEY error
+ /// - \c BAD_NAME: Not yet implemented TKEY error
+ /// - \c BAD_ALG: Not yet implemented TKEY error
+ /// - \c BAD_TRUNC: The signature or truncated signature length is too
+ /// small.
+ ///
+ /// If this method is called by a DNS client waiting for a signed
+ /// response and the result is not \c NOERROR, the context can be used
+ /// to try validating another signed message as described in RFC2845
+ /// Section 4.6.
+ ///
+ /// If this method is called by a DNS server that tries to authenticate
+ /// a signed request, and if the result is not \c NOERROR, the
+ /// corresponding error condition is recorded in the context so that
+ /// the server can return a response indicating what was wrong by calling
+ /// \c sign() with the updated context.
+ ///
+ /// In general, this method is called once by a server for
+ /// authenticating a signed request or one more times by a client to
+ /// validate signed response(s) to a signed request. To avoid allowing
+ /// accidental misuse, if this method is called after a "server" signs
+ /// a response, an exception of class \c TSIGContextError will be thrown.
+ ///
+ /// The \c record parameter can be 0; in that case this method simply
+ /// returns \c FORMERR as the case described in Section 4.6 of RFC2845,
+ /// i.e., receiving an unsigned response to a signed request. This way
+ /// a client can transparently pass the result of
+ /// \c Message::getTSIGRecord() without checking whether it isn't 0
+ /// and take an appropriate action based on the result of this method.
+ ///
+ /// This method handles the given data mostly as opaque. It digests
+ /// the data assuming it begins with a DNS header and ends with a TSIG
+ /// RR whose length is given by calling \c TSIGRecord::getLength() on
+ /// \c record, but otherwise it doesn't parse the data to confirm the
+ /// assumption. It's caller's responsibility to ensure the data is
+ /// valid and consistent with \c record. To avoid disruption, this
+ /// method performs minimal validation on the given \c data and \c record:
+ /// \c data must not be 0; \c data_len must not be smaller than the
+ /// sum of the DNS header length (fixed, 12 octets) and the length of
+ /// the TSIG RR. If this check fails it throws an \c InvalidParameter
+ /// exception.
+ ///
+ /// One unexpected case that is not covered by this method is that a
+ /// client receives a signed response to an unsigned request. RFC2845 is
+ /// silent about such cases; BIND 9 explicitly identifies the case and
+ /// rejects it. With this implementation, the client can know that the
+ /// response contains a TSIG via the result of
+ /// \c Message::getTSIGRecord() and that it is an unexpected TSIG due to
+ /// the fact that it doesn't have a corresponding \c TSIGContext.
+ /// It's up to the client implementation whether to react to such a case
+ /// explicitly (for example, it could either ignore the TSIG and accept
+ /// the response or drop it).
+ ///
+ /// This method provides the strong exception guarantee; unless the method
+ /// returns (without an exception being thrown), the internal state of
+ /// the \c TSIGContext won't be modified.
+ ///
+ /// \todo Signature truncation support based on RFC4635
+ ///
+ /// \exception TSIGContextError Context already signed a response.
+ /// \exception InvalidParameter \c data is 0 or \c data_len is too small.
+ ///
+ /// \param record The \c TSIGRecord to be verified with \c data
+ /// \param data Points to the wire-format data (exactly as received) to
+ /// be verified
+ /// \param data_len The length of \c data in bytes
+ /// \return The \c TSIGError that indicates verification result
+ virtual TSIGError
+ verify(const TSIGRecord* const record, const void* const data, const size_t data_len);
+
+ /// \brief Check whether the last verified message was signed.
+ ///
+ /// RFC2845 allows for some of the messages not to be signed. However,
+ /// the last message must be signed and the class has no knowledge if a
+ /// given message is the last one, therefore it can't check directly.
+ ///
+ /// It is up to the caller to check if the last verified message was signed
+ /// after all are verified by calling this function.
+ ///
+ /// \return If the last message was signed or not.
+ /// \exception TSIGContextError if no message was verified yet.
+ virtual bool lastHadSignature() const;
+
+ /// Return the expected length of TSIG RR after \c sign()
+ ///
+ /// This method returns the length of the TSIG RR that would be
+ /// produced as a result of \c sign() with the state of the context
+ /// at the time of the call. The expected length can be decided
+ /// from the key and the algorithm (which determines the MAC size if
+ /// included) and the recorded TSIG error. Specifically, if a key
+ /// related error has been identified, the MAC will be excluded; if
+ /// a time error has occurred, the TSIG will include "other data".
+ ///
+ /// This method is provided mainly for the convenience of the Message
+ /// class, which needs to know the expected TSIG length in rendering a
+ /// signed DNS message so that it can handle truncated messages with TSIG
+ /// correctly. Normal applications wouldn't need this method. The Python
+ /// binding for this method won't be provided for the same reason.
+ ///
+ /// \exception None
+ ///
+ /// \return The expected TSIG RR length in bytes
+ virtual size_t getTSIGLength() const;
+
+ /// Return the current state of the context
+ ///
+ /// \note
+ /// The states are visible in public mainly for testing purposes.
+ /// Normal applications won't have to deal with them.
+ ///
+ /// \exception None
+ virtual State getState() const;
+
+ /// Return the TSIG error as a result of the latest verification
+ ///
+ /// This method can be called even before verifying anything, but the
+ /// returned value is meaningless in that case.
+ ///
+ /// \exception None
+ virtual TSIGError getError() const;
+
+ /// \name Protocol constants and defaults
+ ///
+ //@{
+ /// The recommended fudge value (in seconds) by RFC2845.
+ ///
+ /// Right now fudge is not tunable, and all TSIGs generated by this API
+ /// will have this value of fudge.
+ static const uint16_t DEFAULT_FUDGE = 300;
+ //@}
+
+protected:
+ /// \brief Update internal HMAC state by more data.
+ ///
+ /// This is used mostly internally, when we need to verify a message without
+ /// TSIG signature in the middle of signed TCP stream. However, it is also
+ /// used in tests, so it's protected instead of private, to allow tests
+ /// in.
+ ///
+ /// It doesn't contain sanity checks, and it is not tested directly. But
+ /// we may want to add these one day to allow generating the skipped TSIG
+ /// messages too. Until then, do not use this method.
+ void update(const void* const data, size_t len);
+
+private:
+ struct TSIGContextImpl;
+ boost::shared_ptr<TSIGContextImpl> impl_;
+};
+
+typedef boost::shared_ptr<TSIGContext> TSIGContextPtr;
+typedef boost::shared_ptr<TSIGKey> TSIGKeyPtr;
+
+}
+}
+
+#endif // TSIG_H
diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc
new file mode 100644
index 0000000..d51094a
--- /dev/null
+++ b/src/lib/dns/tsigerror.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <ostream>
+#include <string>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
+#include <dns/tsigerror.h>
+
+namespace isc {
+namespace dns {
+namespace {
+const char* const tsigerror_text[] = {
+ "BADSIG",
+ "BADKEY",
+ "BADTIME",
+ "BADMODE",
+ "BADNAME",
+ "BADALG",
+ "BADTRUNC"
+};
+}
+
+TSIGError::TSIGError(Rcode rcode) : code_(rcode.getCode()) {
+ if (code_ > MAX_RCODE_FOR_TSIGERROR) {
+ isc_throw(OutOfRange, "Invalid RCODE for TSIG Error: " << rcode);
+ }
+}
+
+std::string
+TSIGError::toText() const {
+ if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
+ return (Rcode(code_).toText());
+ } else if (code_ <= BAD_TRUNC_CODE) {
+ return (tsigerror_text[code_ - (MAX_RCODE_FOR_TSIGERROR + 1)]);
+ } else {
+ return (boost::lexical_cast<std::string>(code_));
+ }
+}
+
+Rcode
+TSIGError::toRcode() const {
+ if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
+ return (Rcode(code_));
+ }
+ if (code_ > BAD_TRUNC_CODE) {
+ return (Rcode::SERVFAIL());
+ }
+ return (Rcode::NOTAUTH());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const TSIGError& error) {
+ return (os << error.toText());
+}
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/tsigerror.h b/src/lib/dns/tsigerror.h
new file mode 100644
index 0000000..da9bc54
--- /dev/null
+++ b/src/lib/dns/tsigerror.h
@@ -0,0 +1,378 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TSIGERROR_H
+#define TSIGERROR_H
+
+#include <ostream>
+#include <string>
+
+#include <dns/rcode.h>
+
+namespace isc {
+namespace dns {
+/// TSIG errors
+///
+/// The \c TSIGError class objects represent standard errors related to
+/// TSIG protocol operations as defined in related specifications, mainly
+/// in RFC2845, RFC2930 and RFC4635.
+class TSIGError {
+public:
+ /// Constants for pre-defined TSIG error values.
+ ///
+ /// Code values from 0 through 15 (inclusive) are derived from those of
+ /// RCODE and are not defined here. See the \c Rcode class.
+ ///
+ /// \note Unfortunately some systems define "BADSIG" as a macro in a public
+ /// header file. To avoid conflict with it we add an underscore to our
+ /// definitions.
+ enum CodeValue {
+ BAD_SIG_CODE = 16, ///< 16: TSIG verification failure
+ BAD_KEY_CODE = 17, ///< 17: TSIG key is not recognized
+ BAD_TIME_CODE = 18, ///< 18: Current time and time signed are too different
+ BAD_MODE_CODE = 19, ///< 19: Bad TKEY mode
+ BAD_NAME_CODE = 20, ///< 20: Duplicate TKEY name
+ BAD_ALG_CODE = 21, ///< 21: TKEY algorithm not supported
+ BAD_TRUNC_CODE = 22 ///< 22: Bad truncation
+ };
+
+ /// \name Constructors
+ ///
+ /// We use the default versions of destructor, copy constructor,
+ /// and assignment operator.
+ //@{
+ /// Constructor from the code value.
+ ///
+ /// \exception None
+ ///
+ /// \param error_code The underlying 16-bit error code value of the \c TSIGError.
+ explicit TSIGError(uint16_t error_code) : code_(error_code) {}
+
+ /// Constructor from \c Rcode.
+ ///
+ /// As defined in RFC2845, error code values from 0 to 15 (inclusive) are
+ /// derived from the DNS RCODEs, which are represented via the \c Rcode
+ /// class in this library. This constructor works as a converter from
+ /// these RCODEs to corresponding TSIGError objects.
+ ///
+ /// \exception isc::OutOfRange Given rcode is not convertible to
+ /// TSIGErrors.
+ ///
+ /// \param rcode the \c Rcode from which the TSIGError should be derived.
+ explicit TSIGError(Rcode rcode);
+ //@}
+
+ /// \brief Returns the \c TSIGCode error code value.
+ ///
+ /// \exception None
+ ///
+ /// \return The underlying code value corresponding to the \c TSIGError.
+ uint16_t getCode() const {
+ return (code_);
+ }
+
+ /// \brief Return true iff two \c TSIGError objects are equal.
+ ///
+ /// Two TSIGError objects are equal iff their error codes are equal.
+ ///
+ /// \exception None
+ ///
+ /// \param other the \c TSIGError object to compare against.
+ /// \return true if the two TSIGError are equal; otherwise false.
+ bool equals(const TSIGError& other) const {
+ return (code_ == other.code_);
+ }
+
+ /// \brief Same as \c equals().
+ bool operator==(const TSIGError& other) const {
+ return (equals(other));
+ }
+
+ /// \brief Return true iff two \c TSIGError objects are not equal.
+ ///
+ /// \exception None
+ ///
+ /// \param other the \c TSIGError object to compare against.
+ /// \return true if the two TSIGError objects are not equal;
+ /// otherwise false.
+ bool nequals(const TSIGError& other) const {
+ return (code_ != other.code_);
+ }
+
+ /// \brief Same as \c nequals().
+ bool operator!=(const TSIGError& other) const {
+ return (nequals(other));
+ }
+
+ /// \brief Convert the \c TSIGError to a string.
+ ///
+ /// For codes derived from RCODEs up to 15, this method returns the
+ /// same string as \c Rcode::toText() for the corresponding code.
+ /// For other pre-defined code values (see TSIGError::CodeValue),
+ /// this method returns a string representation of the "mnemonic' used
+ /// for the enum and constant objects as defined in RFC2845.
+ /// For example, the string for code value 16 is "BADSIG", etc.
+ /// For other code values it returns a string representation of the decimal
+ /// number of the value, e.g. "32", "100", etc.
+ ///
+ /// \exception std::bad_alloc Resource allocation for the string fails
+ ///
+ /// \return A string representation of the \c TSIGError.
+ std::string toText() const;
+
+ /// \brief Convert the \c TSIGError to a \c Rcode
+ ///
+ /// This method returns an \c Rcode object that is corresponding to
+ /// the TSIG error. The returned \c Rcode is expected to be used
+ /// by a verifying server to specify the RCODE of a response when
+ /// TSIG verification fails.
+ ///
+ /// Specifically, this method returns \c Rcode::NOTAUTH() for the
+ /// TSIG specific errors, BADSIG, BADKEY, BADTIME, as described in
+ /// RFC2845. For errors derived from the standard Rcode (code 0-15),
+ /// it returns the corresponding \c Rcode. For others, this method
+ /// returns \c Rcode::SERVFAIL() as a last resort.
+ ///
+ /// \exception None
+ Rcode toRcode() const;
+
+ /// A constant TSIG error object derived from \c Rcode::NOERROR()
+ static const TSIGError& NOERROR();
+
+ /// A constant TSIG error object derived from \c Rcode::FORMERR()
+ static const TSIGError& FORMERR();
+
+ /// A constant TSIG error object derived from \c Rcode::SERVFAIL()
+ static const TSIGError& SERVFAIL();
+
+ /// A constant TSIG error object derived from \c Rcode::NXDOMAIN()
+ static const TSIGError& NXDOMAIN();
+
+ /// A constant TSIG error object derived from \c Rcode::NOTIMP()
+ static const TSIGError& NOTIMP();
+
+ /// A constant TSIG error object derived from \c Rcode::REFUSED()
+ static const TSIGError& REFUSED();
+
+ /// A constant TSIG error object derived from \c Rcode::YXDOMAIN()
+ static const TSIGError& YXDOMAIN();
+
+ /// A constant TSIG error object derived from \c Rcode::YXRRSET()
+ static const TSIGError& YXRRSET();
+
+ /// A constant TSIG error object derived from \c Rcode::NXRRSET()
+ static const TSIGError& NXRRSET();
+
+ /// A constant TSIG error object derived from \c Rcode::NOTAUTH()
+ static const TSIGError& NOTAUTH();
+
+ /// A constant TSIG error object derived from \c Rcode::NOTZONE()
+ static const TSIGError& NOTZONE();
+
+ /// A constant TSIG error object derived from \c Rcode::RESERVED11()
+ static const TSIGError& RESERVED11();
+
+ /// A constant TSIG error object derived from \c Rcode::RESERVED12()
+ static const TSIGError& RESERVED12();
+
+ /// A constant TSIG error object derived from \c Rcode::RESERVED13()
+ static const TSIGError& RESERVED13();
+
+ /// A constant TSIG error object derived from \c Rcode::RESERVED14()
+ static const TSIGError& RESERVED14();
+
+ /// A constant TSIG error object derived from \c Rcode::RESERVED15()
+ static const TSIGError& RESERVED15();
+
+ /// A constant TSIG error object for the BADSIG code
+ /// (see \c TSIGError::BAD_SIG_CODE).
+ static const TSIGError& BAD_SIG();
+
+ /// A constant TSIG error object for the BADKEY code
+ /// (see \c TSIGError::BAD_KEY_CODE).
+ static const TSIGError& BAD_KEY();
+
+ /// A constant TSIG error object for the BADTIME code
+ /// (see \c TSIGError::BAD_TIME_CODE).
+ static const TSIGError& BAD_TIME();
+
+ /// A constant TSIG error object for the BADMODE code
+ /// (see \c TSIGError::BAD_MODE_CODE).
+ static const TSIGError& BAD_MODE();
+
+ /// A constant TSIG error object for the BADNAME code
+ /// (see \c TSIGError::BAD_NAME_CODE).
+ static const TSIGError& BAD_NAME();
+
+ /// A constant TSIG error object for the BADALG code
+ /// (see \c TSIGError::BAD_ALG_CODE).
+ static const TSIGError& BAD_ALG();
+
+ /// A constant TSIG error object for the BADTRUNC code
+ /// (see \c TSIGError::BAD_TRUNC_CODE).
+ static const TSIGError& BAD_TRUNC();
+
+private:
+ // This is internally used to specify the maximum possible RCODE value
+ // that can be convertible to TSIGErrors.
+ static const int MAX_RCODE_FOR_TSIGERROR = 15;
+
+ uint16_t code_;
+};
+
+inline const TSIGError&
+TSIGError::NOERROR() {
+ static TSIGError e(Rcode::NOERROR());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::FORMERR() {
+ static TSIGError e(Rcode::FORMERR());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::SERVFAIL() {
+ static TSIGError e(Rcode::SERVFAIL());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::NXDOMAIN() {
+ static TSIGError e(Rcode::NXDOMAIN());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::NOTIMP() {
+ static TSIGError e(Rcode::NOTIMP());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::REFUSED() {
+ static TSIGError e(Rcode::REFUSED());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::YXDOMAIN() {
+ static TSIGError e(Rcode::YXDOMAIN());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::YXRRSET() {
+ static TSIGError e(Rcode::YXRRSET());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::NXRRSET() {
+ static TSIGError e(Rcode::NXRRSET());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::NOTAUTH() {
+ static TSIGError e(Rcode::NOTAUTH());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::NOTZONE() {
+ static TSIGError e(Rcode::NOTZONE());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED11() {
+ static TSIGError e(Rcode::RESERVED11());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED12() {
+ static TSIGError e(Rcode::RESERVED12());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED13() {
+ static TSIGError e(Rcode::RESERVED13());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED14() {
+ static TSIGError e(Rcode::RESERVED14());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED15() {
+ static TSIGError e(Rcode::RESERVED15());
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_SIG() {
+ static TSIGError e(BAD_SIG_CODE);
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_KEY() {
+ static TSIGError e(BAD_KEY_CODE);
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_TIME() {
+ static TSIGError e(BAD_TIME_CODE);
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_MODE() {
+ static TSIGError e(BAD_MODE_CODE);
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_NAME() {
+ static TSIGError e(BAD_NAME_CODE);
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_ALG() {
+ static TSIGError e(BAD_ALG_CODE);
+ return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_TRUNC() {
+ static TSIGError e(BAD_TRUNC_CODE);
+ return (e);
+}
+
+/// Insert the \c TSIGError as a string into stream.
+///
+/// This method convert \c tsig_error into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param tsig_error An \c TSIGError object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const TSIGError& tsig_error);
+}
+}
+
+#endif // TSIGERROR_H
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
new file mode 100644
index 0000000..92a12bd
--- /dev/null
+++ b/src/lib/dns/tsigkey.cc
@@ -0,0 +1,354 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <map>
+#include <utility>
+#include <vector>
+#include <sstream>
+
+#include <exceptions/exceptions.h>
+
+#include <cryptolink/cryptolink.h>
+
+#include <dns/name.h>
+#include <util/encode/encode.h>
+#include <dns/tsigkey.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using namespace isc::cryptolink;
+
+namespace isc {
+namespace dns {
+namespace {
+ HashAlgorithm
+ convertAlgorithmName(const isc::dns::Name& name) {
+ if (name == TSIGKey::HMACMD5_NAME()) {
+ return (isc::cryptolink::MD5);
+ }
+ if (name == TSIGKey::HMACMD5_SHORT_NAME()) {
+ return (isc::cryptolink::MD5);
+ }
+ if (name == TSIGKey::HMACSHA1_NAME()) {
+ return (isc::cryptolink::SHA1);
+ }
+ if (name == TSIGKey::HMACSHA256_NAME()) {
+ return (isc::cryptolink::SHA256);
+ }
+ if (name == TSIGKey::HMACSHA224_NAME()) {
+ return (isc::cryptolink::SHA224);
+ }
+ if (name == TSIGKey::HMACSHA384_NAME()) {
+ return (isc::cryptolink::SHA384);
+ }
+ if (name == TSIGKey::HMACSHA512_NAME()) {
+ return (isc::cryptolink::SHA512);
+ }
+
+ return (isc::cryptolink::UNKNOWN_HASH);
+ }
+}
+
+struct
+TSIGKey::TSIGKeyImpl {
+ TSIGKeyImpl(const Name& key_name, const Name& algorithm_name,
+ isc::cryptolink::HashAlgorithm algorithm,
+ size_t digestbits) :
+ key_name_(key_name), algorithm_name_(algorithm_name),
+ algorithm_(algorithm), digestbits_(digestbits),
+ secret_() {
+ // Convert the key and algorithm names to the canonical form.
+ key_name_.downcase();
+ if (algorithm == isc::cryptolink::MD5) {
+ algorithm_name_ = TSIGKey::HMACMD5_NAME();
+ }
+ algorithm_name_.downcase();
+ }
+ TSIGKeyImpl(const Name& key_name, const Name& algorithm_name,
+ isc::cryptolink::HashAlgorithm algorithm,
+ size_t digestbits,
+ const void* secret, size_t secret_len) :
+ key_name_(key_name), algorithm_name_(algorithm_name),
+ algorithm_(algorithm), digestbits_(digestbits),
+ secret_(static_cast<const uint8_t*>(secret),
+ static_cast<const uint8_t*>(secret) + secret_len) {
+ // Convert the key and algorithm names to the canonical form.
+ key_name_.downcase();
+ if (algorithm == isc::cryptolink::MD5) {
+ algorithm_name_ = TSIGKey::HMACMD5_NAME();
+ }
+ algorithm_name_.downcase();
+ }
+ Name key_name_;
+ Name algorithm_name_;
+ const isc::cryptolink::HashAlgorithm algorithm_;
+ size_t digestbits_;
+ const vector<uint8_t> secret_;
+};
+
+TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len,
+ size_t digestbits /*= 0*/) : impl_(0) {
+ const HashAlgorithm algorithm = convertAlgorithmName(algorithm_name);
+ if ((secret && secret_len == 0) ||
+ (!secret && secret_len != 0)) {
+ isc_throw(InvalidParameter,
+ "TSIGKey secret and its length are inconsistent: " <<
+ key_name << ":" << algorithm_name);
+ }
+ if (algorithm == isc::cryptolink::UNKNOWN_HASH && secret_len != 0) {
+ isc_throw(InvalidParameter,
+ "TSIGKey with unknown algorithm has non empty secret: " <<
+ key_name << ":" << algorithm_name);
+ }
+ if (!secret) {
+ impl_.reset(new TSIGKey::TSIGKeyImpl(key_name, algorithm_name, algorithm,
+ digestbits));
+ } else {
+ impl_.reset(new TSIGKey::TSIGKeyImpl(key_name, algorithm_name, algorithm,
+ digestbits, secret, secret_len));
+ }
+}
+
+TSIGKey::TSIGKey(const std::string& str) : impl_(0) {
+ try {
+ istringstream iss(str);
+
+ string keyname_str;
+ getline(iss, keyname_str, ':');
+ if (iss.fail() || iss.bad() || iss.eof()) {
+ isc_throw(InvalidParameter, "Invalid TSIG key string: " << str);
+ }
+
+ string secret_str;
+ getline(iss, secret_str, ':');
+ if (iss.fail() || iss.bad()) {
+ isc_throw(InvalidParameter, "Invalid TSIG key string: " << str);
+ }
+
+ string algo_str;
+ if (!iss.eof()) {
+ getline(iss, algo_str, ':');
+ }
+ if (iss.fail() || iss.bad()) {
+ isc_throw(InvalidParameter, "Invalid TSIG key string: " << str);
+ }
+
+ string dgstbt_str;
+ if (!iss.eof()) {
+ getline(iss, dgstbt_str);
+ }
+ if (iss.fail() || iss.bad()) {
+ isc_throw(InvalidParameter, "Invalid TSIG key string: " << str);
+ }
+
+ const Name algo_name(algo_str.empty() ? "hmac-md5.sig-alg.reg.int" :
+ algo_str);
+ const HashAlgorithm algorithm = convertAlgorithmName(algo_name);
+ size_t digestbits = 0;
+ try {
+ if (!dgstbt_str.empty()) {
+ digestbits = boost::lexical_cast<size_t>(dgstbt_str);
+ }
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter,
+ "TSIG key with non-numeric digestbits: " << dgstbt_str);
+ }
+
+ vector<uint8_t> secret;
+ isc::util::encode::decodeBase64(secret_str, secret);
+
+ if (algorithm == isc::cryptolink::UNKNOWN_HASH && !secret.empty()) {
+ isc_throw(InvalidParameter,
+ "TSIG key with unknown algorithm has non empty secret: "
+ << str);
+ }
+
+ if (secret.empty()) {
+ impl_.reset(new TSIGKey::TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
+ digestbits));
+ } else {
+ impl_.reset(new TSIGKey::TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
+ digestbits, &secret[0], secret.size()));
+ }
+ } catch (const isc::Exception& e) {
+ // 'reduce' the several types of exceptions name parsing and
+ // Base64 decoding can throw to just the InvalidParameter
+ isc_throw(InvalidParameter, e.what());
+ }
+}
+
+TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_)) {
+}
+
+TSIGKey&
+TSIGKey::operator=(const TSIGKey& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ impl_.reset(new TSIGKey::TSIGKeyImpl(*source.impl_));
+ return (*this);
+}
+
+TSIGKey::~TSIGKey() {
+}
+
+const Name&
+TSIGKey::getKeyName() const {
+ return (impl_->key_name_);
+}
+
+const Name&
+TSIGKey::getAlgorithmName() const {
+ return (impl_->algorithm_name_);
+}
+
+isc::cryptolink::HashAlgorithm
+TSIGKey::getAlgorithm() const {
+ return (impl_->algorithm_);
+}
+
+size_t
+TSIGKey::getDigestbits() const {
+ return (impl_->digestbits_);
+}
+
+const void*
+TSIGKey::getSecret() const {
+ return ((impl_->secret_.size() > 0) ? &impl_->secret_[0] : 0);
+}
+
+size_t
+TSIGKey::getSecretLength() const {
+ return (impl_->secret_.size());
+}
+
+std::string
+TSIGKey::toText() const {
+ size_t digestbits = getDigestbits();
+ const vector<uint8_t> secret_v(static_cast<const uint8_t*>(getSecret()),
+ static_cast<const uint8_t*>(getSecret()) +
+ getSecretLength());
+ std::string secret_str = isc::util::encode::encodeBase64(secret_v);
+
+ if (digestbits) {
+ std::string dgstbt_str = boost::lexical_cast<std::string>(static_cast<int>(digestbits));
+ return (getKeyName().toText() + ":" + secret_str + ":" +
+ getAlgorithmName().toText() + ":" + dgstbt_str);
+ } else {
+ return (getKeyName().toText() + ":" + secret_str + ":" +
+ getAlgorithmName().toText());
+ }
+}
+
+const
+Name& TSIGKey::HMACMD5_NAME() {
+ static Name alg_name("hmac-md5.sig-alg.reg.int");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACMD5_SHORT_NAME() {
+ static Name alg_name("hmac-md5");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA1_NAME() {
+ static Name alg_name("hmac-sha1");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA256_NAME() {
+ static Name alg_name("hmac-sha256");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA224_NAME() {
+ static Name alg_name("hmac-sha224");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA384_NAME() {
+ static Name alg_name("hmac-sha384");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA512_NAME() {
+ static Name alg_name("hmac-sha512");
+ return (alg_name);
+}
+
+const
+Name& TSIGKey::GSSTSIG_NAME() {
+ static Name alg_name("gss-tsig");
+ return (alg_name);
+}
+
+struct TSIGKeyRing::TSIGKeyRingImpl {
+ typedef map<Name, TSIGKey> TSIGKeyMap;
+ typedef pair<Name, TSIGKey> NameAndKey;
+ TSIGKeyMap keys;
+};
+
+TSIGKeyRing::TSIGKeyRing() : impl_(new TSIGKeyRingImpl()) {
+}
+
+TSIGKeyRing::~TSIGKeyRing() {
+}
+
+unsigned int
+TSIGKeyRing::size() const {
+ return (impl_->keys.size());
+}
+
+TSIGKeyRing::Result
+TSIGKeyRing::add(const TSIGKey& key) {
+ if (impl_->keys.insert(
+ TSIGKeyRingImpl::NameAndKey(key.getKeyName(), key)).second
+ == true) {
+ return (SUCCESS);
+ } else {
+ return (EXIST);
+ }
+}
+
+TSIGKeyRing::Result
+TSIGKeyRing::remove(const Name& key_name) {
+ return (impl_->keys.erase(key_name) == 1 ? SUCCESS : NOTFOUND);
+}
+
+TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name) const {
+ TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+ impl_->keys.find(key_name);
+ if (found == impl_->keys.end()) {
+ return (FindResult(NOTFOUND, 0));
+ }
+ return (FindResult(SUCCESS, &((*found).second)));
+}
+
+TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const {
+ TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+ impl_->keys.find(key_name);
+ if (found == impl_->keys.end() ||
+ (*found).second.getAlgorithmName() != algorithm_name) {
+ return (FindResult(NOTFOUND, 0));
+ }
+ return (FindResult(SUCCESS, &((*found).second)));
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h
new file mode 100644
index 0000000..9381074
--- /dev/null
+++ b/src/lib/dns/tsigkey.h
@@ -0,0 +1,387 @@
+// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TSIGKEY_H
+#define TSIGKEY_H
+
+#include <cryptolink/cryptolink.h>
+
+namespace isc {
+namespace dns {
+
+class Name;
+
+/// \brief TSIG key.
+///
+/// This class holds a TSIG key along with some related attributes as
+/// defined in RFC2845.
+///
+/// A TSIG key consists of the following attributes:
+/// - Key name
+/// - Hash algorithm
+/// - Digest bits
+/// - Shared secret
+///
+/// <b>Implementation Notes</b>
+///
+/// We may add more attributes in future versions. For example, if and when
+/// we support the TKEY protocol (RFC2930), we may need to introduce the
+/// notion of inception and expiration times.
+/// At that point we may also have to introduce a class hierarchy to handle
+/// different types of keys in a polymorphic way.
+/// At the moment we use the straightforward value-type class with minimal
+/// attributes.
+///
+/// In the TSIG protocol, hash algorithms are represented in the form of
+/// domain name.
+/// Our interfaces provide direct translation of this concept; for example,
+/// the constructor from parameters take a \c Name object to specify the
+/// algorithm.
+/// On one hand, this may be counter intuitive.
+/// An API user would rather specify "hmac-md5" instead of
+/// <code>Name("hmac-md5.sig-alg.reg.int")</code>.
+/// On the other hand, it may be more convenient for some kind of applications
+/// if we maintain the algorithm as the expected representation for
+/// protocol operations (such as sign and very a message).
+/// Considering these points, we adopt the interface closer to the protocol
+/// specification for now.
+/// To minimize the burden for API users, we also define a set of constants
+/// for commonly used algorithm names so that the users don't have to
+/// remember the actual domain names defined in the protocol specification.
+/// We may also have to add conversion routines between domain names
+/// and more intuitive representations (e.g. strings) for algorithms.
+class TSIGKey {
+public:
+ ///
+ /// \name Constructors, Assignment Operator and Destructor.
+ ///
+ //@{
+ /// \brief Constructor from key parameters
+ ///
+ /// \c algorithm_name should generally be a known algorithm to this
+ /// implementation, which are defined via the
+ /// <code>static const</code> member functions.
+ ///
+ /// Other names are still accepted as long as the secret is empty
+ /// (\c secret is null and \c secret_len is 0), however; in some cases
+ /// we might want to treat just the pair of key name and algorithm name
+ /// opaquely, e.g., when generating a response TSIG with a BADKEY error
+ /// because the algorithm is unknown as specified in Section 3.2 of
+ /// RFC2845 (in which case the algorithm name would be copied from the
+ /// request to the response, and for that purpose it would be convenient
+ /// if a \c TSIGKey object can hold a name for an "unknown" algorithm).
+ ///
+ /// \note RFC2845 does not specify which algorithm name should be used
+ /// in such a BADKEY response. The behavior of using the same algorithm
+ /// is derived from the BIND 9 implementation.
+ ///
+ /// It is unlikely that a TSIG key with an unknown algorithm is of any
+ /// use with actual crypto operation, so care must be taken when dealing
+ /// with such keys. (The restriction for the secret will prevent
+ /// accidental creation of such a dangerous key, e.g., due to misspelling
+ /// in a configuration file).
+ /// If the given algorithm name is unknown and non empty secret is
+ /// specified, an exception of type \c InvalidParameter will be thrown.
+ ///
+ /// \c secret and \c secret_len must be consistent in that the latter
+ /// is 0 if and only if the former is null;
+ /// otherwise an exception of type \c InvalidParameter will be thrown.
+ ///
+ /// \c digestbits is the truncated length in bits or 0 which means no
+ /// truncation and is the default. Constraints for non-zero value
+ /// are in RFC 4635 section 3.1: minimum 80 or the half of the
+ /// full (i.e., not truncated) length, integral number of octets
+ /// (i.e., multiple of 8), and maximum the full length.
+ ///
+ /// This constructor internally involves resource allocation, and if
+ /// it fails, a corresponding standard exception will be thrown.
+ ///
+ /// \param key_name The name of the key as a domain name.
+ /// \param algorithm_name The hash algorithm used for this key in the
+ /// form of domain name. For example, it can be
+ /// \c TSIGKey::HMACSHA256_NAME() for HMAC-SHA256.
+ /// \param secret Point to a binary sequence of the shared secret to be
+ /// used for this key, or null if the secret is empty.
+ /// \param secret_len The size of the binary %data (\c secret) in bytes.
+ /// \param digestbits The number of bits to include in the digest
+ /// (0 means to include all)
+ TSIGKey(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len, size_t digestbits = 0);
+
+ /// \brief Constructor from an input string
+ ///
+ /// The string must be of the form:
+ /// name:secret[:algorithm][:digestbits]
+ /// Where "name" is a domain name for the key, "secret" is a
+ /// base64 representation of the key secret, and the optional
+ /// "algorithm" is an algorithm identifier as specified in RFC 4635.
+ /// The default algorithm is hmac-md5.sig-alg.reg.int.
+ /// "digestbits" is the minimum truncated length in bits.
+ /// The default digestbits value is 0 and means truncation is forbidden.
+ ///
+ /// The same restriction about the algorithm name (and secret) as that
+ /// for the other constructor applies.
+ ///
+ /// Since ':' is used as a separator here, it is not possible to
+ /// use this constructor to create keys with a ':' character in
+ /// their name.
+ ///
+ /// \exception InvalidParameter exception if the input string is
+ /// invalid.
+ ///
+ /// \param str The string to make a TSIGKey from
+ explicit TSIGKey(const std::string& str);
+
+ /// \brief The copy constructor.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This constructor never throws an exception otherwise.
+ TSIGKey(const TSIGKey& source);
+
+ /// \brief Assignment operator.
+ ///
+ /// It internally allocates a resource, and if it fails a corresponding
+ /// standard exception will be thrown.
+ /// This operator never throws an exception otherwise.
+ ///
+ /// This operator provides the strong exception guarantee: When an
+ /// exception is thrown the content of the assignment target will be
+ /// intact.
+ TSIGKey& operator=(const TSIGKey& source);
+
+ /// The destructor.
+ virtual ~TSIGKey();
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ /// These methods never throw an exception.
+ //@{
+ /// Return the key name.
+ const Name& getKeyName() const;
+
+ /// Return the algorithm name.
+ const Name& getAlgorithmName() const;
+
+ /// Return the hash algorithm name in the form of cryptolink::HashAlgorithm
+ isc::cryptolink::HashAlgorithm getAlgorithm() const;
+
+ /// Return the minimum truncated length.
+ size_t getDigestbits() const;
+
+ /// Return the length of the TSIG secret in bytes.
+ size_t getSecretLength() const;
+
+ /// Return the value of the TSIG secret.
+ ///
+ /// If it returns a non null pointer, the memory region beginning at the
+ /// address returned by this method is valid up to the bytes specified
+ /// by the return value of \c getSecretLength().
+ ///
+ /// The memory region is only valid while the corresponding \c TSIGKey
+ /// object is valid. The caller must hold the \c TSIGKey object while
+ /// it needs to refer to the region or it must make a local copy of the
+ /// region.
+ const void* getSecret() const;
+ //@}
+
+ /// \brief Converts the TSIGKey to a string value
+ ///
+ /// The resulting string will be of the form
+ /// name:secret:algorithm[:digestbits]
+ /// Where "name" is a domain name for the key, "secret" is a
+ /// base64 representation of the key secret, and "algorithm" is
+ /// an algorithm identifier as specified in RFC 4635.
+ /// When not zero, digestbits is appended.
+ ///
+ /// \return The string representation of the given TSIGKey.
+ std::string toText() const;
+
+ ///
+ /// \name Well known algorithm names as defined in RFC2845 and RFC4635.
+ ///
+ /// Note: we begin with the "mandatory" algorithms defined in RFC4635
+ /// as a minimal initial set.
+ /// We'll add others as we see the need for them.
+ //@{
+ static const Name& HMACMD5_NAME(); ///< HMAC-MD5 (RFC2845)
+ static const Name& HMACMD5_SHORT_NAME();
+ static const Name& HMACSHA1_NAME(); ///< HMAC-SHA1 (RFC4635)
+ static const Name& HMACSHA256_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& HMACSHA224_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& HMACSHA384_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& HMACSHA512_NAME(); ///< HMAC-SHA256 (RFC4635)
+ static const Name& GSSTSIG_NAME(); ///< GSS-TSIG (RFC3645)
+ //@}
+
+private:
+ struct TSIGKeyImpl;
+ boost::shared_ptr<TSIGKeyImpl> impl_;
+};
+
+/// \brief A simple repository of a set of \c TSIGKey objects.
+///
+/// This is a "key ring" to maintain TSIG keys (\c TSIGKey objects) and
+/// provides trivial operations such as add, remove, and find.
+///
+/// The keys are identified by their key names.
+/// So, for example, two or more keys of the same key name but of different
+/// algorithms are considered to be the same, and cannot be stored in the
+/// key ring at the same time.
+///
+/// <b>Implementation Note:</b>
+/// For simplicity the initial implementation requests the application make
+/// a copy of keys stored in the key ring if it needs to use the keys for
+/// a long period (during which some of the keys may be removed).
+/// This is based on the observations that a single server will not hold
+/// a huge number of keys nor use keys in many different contexts (such as
+/// in different DNS transactions).
+/// If this assumption does not hold and memory consumption becomes an issue
+/// we may have to revisit the design.
+class TSIGKeyRing {
+public:
+ /// Result codes of various public methods of \c TSIGKeyRing
+ enum Result {
+ SUCCESS = 0, ///< The operation is successful.
+ EXIST = 1, ///< A key is already stored in \c TSIGKeyRing.
+ NOTFOUND = 2 ///< The specified key is not found in \c TSIGKeyRing.
+ };
+
+ /// \brief A helper structure to represent the search result of
+ /// <code>TSIGKeyRing::find()</code>.
+ ///
+ /// This is a straightforward pair of the result code and a pointer
+ /// to the found key to represent the result of \c find().
+ /// We use this in order to avoid overloading the return value for both
+ /// the result code ("success" or "not found") and the found object,
+ /// i.e., avoid using null to mean "not found", etc.
+ ///
+ /// This is a simple value class with no internal state, so for
+ /// convenience we allow the applications to refer to the members
+ /// directly.
+ ///
+ /// See the description of \c find() for the semantics of the member
+ /// variables.
+ struct FindResult {
+ FindResult(Result param_code, const TSIGKey* param_key) :
+ code(param_code), key(param_key) {
+ }
+ const Result code;
+ const TSIGKey* const key;
+ };
+
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ /// \b Note:
+ /// The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non copyable.
+ /// There is no technical reason why this class cannot be copied,
+ /// but since the key ring can potentially have a large number of keys,
+ /// a naive copy operation may cause unexpected overhead.
+ /// It's generally expected for an application to share the same
+ /// instance of key ring and share it throughout the program via
+ /// references, so we prevent the copy operation explicitly to avoid
+ /// unexpected copy operations.
+ //@{
+private:
+ TSIGKeyRing(const TSIGKeyRing& source);
+ TSIGKeyRing& operator=(const TSIGKeyRing& source);
+public:
+ /// \brief The default constructor.
+ ///
+ /// This constructor never throws an exception.
+ TSIGKeyRing();
+
+ /// The destructor.
+ ~TSIGKeyRing();
+ //@}
+
+ /// Return the number of keys stored in the \c TSIGKeyRing.
+ ///
+ /// This method never throws an exception.
+ unsigned int size() const;
+
+ /// Add a \c TSIGKey to the \c TSIGKeyRing.
+ ///
+ /// This method will create a local copy of the given key, so the caller
+ /// does not have to keep owning it.
+ ///
+ /// If internal resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// \param key A \c TSIGKey to be added.
+ /// \return \c SUCCESS If the key is successfully added to the key ring.
+ /// \return \c EXIST The key ring already stores a key whose name is
+ /// identical to that of \c key.
+ Result add(const TSIGKey& key);
+
+ /// Remove a \c TSIGKey for the given name from the \c TSIGKeyRing.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be removed.
+ /// \return \c SUCCESS If the key is successfully removed from the key
+ /// ring.
+ /// \return \c NOTFOUND The key ring does not store the key that matches
+ /// \c key_name.
+ Result remove(const Name& key_name);
+
+ /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+ ///
+ /// It searches the internal storage for a \c TSIGKey whose name is
+ /// \c key_name.
+ /// It returns the result in the form of a \c FindResult
+ /// object as follows:
+ /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+ /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+ /// otherwise null.
+ ///
+ /// The pointer returned in the \c FindResult object is only valid until
+ /// the corresponding key is removed from the key ring.
+ /// The caller must ensure that the key is held in the key ring while
+ /// it needs to refer to it, or it must make a local copy of the key.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be found.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult find(const Name& key_name) const;
+
+ /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+ ///
+ /// It searches the internal storage for a \c TSIGKey whose name is
+ /// \c key_name and that uses the hash algorithm identified by
+ /// \c algorithm_name.
+ /// It returns the result in the form of a \c FindResult
+ /// object as follows:
+ /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+ /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+ /// otherwise null.
+ ///
+ /// The pointer returned in the \c FindResult object is only valid until
+ /// the corresponding key is removed from the key ring.
+ /// The caller must ensure that the key is held in the key ring while
+ /// it needs to refer to it, or it must make a local copy of the key.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be found.
+ /// \param algorithm_name The name of the algorithm of the found key.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult find(const Name& key_name, const Name& algorithm_name) const;
+
+private:
+ struct TSIGKeyRingImpl;
+ boost::shared_ptr<TSIGKeyRingImpl> impl_;
+};
+}
+}
+
+#endif // TSIGKEY_H
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
new file mode 100644
index 0000000..28ff079
--- /dev/null
+++ b/src/lib/dns/tsigrecord.cc
@@ -0,0 +1,140 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/tsigrecord.h>
+#include <util/buffer.h>
+
+#include <ostream>
+#include <string>
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+// Internally used constants:
+
+// Size in octets for the RR type, class TTL, RDLEN fields.
+const size_t RR_COMMON_LEN = 10;
+
+// Size in octets for the fixed part of TSIG RDATAs.
+// - Time Signed (6)
+// - Fudge (2)
+// - MAC Size (2)
+// - Original ID (2)
+// - Error (2)
+// - Other Len (2)
+const size_t RDATA_COMMON_LEN = 16;
+}
+
+namespace isc {
+namespace dns {
+TSIGRecord::TSIGRecord(const Name& key_name,
+ const rdata::any::TSIG& tsig_rdata) :
+ key_name_(key_name), rdata_(tsig_rdata),
+ length_(RR_COMMON_LEN + RDATA_COMMON_LEN + key_name_.getLength() +
+ rdata_.getAlgorithm().getLength() +
+ rdata_.getMACSize() + rdata_.getOtherLen()) {
+}
+
+namespace {
+// This is a straightforward wrapper of dynamic_cast<const any::TSIG&>.
+// We use this so that we can throw the DNSMessageFORMERR exception when
+// unexpected type of RDATA is detected in the member initialization list
+// of the constructor below.
+const any::TSIG&
+castToTSIGRdata(const rdata::Rdata& rdata) {
+ const any::TSIG* tsig_rdata =
+ dynamic_cast<const any::TSIG*>(&rdata);
+ if (!tsig_rdata) {
+ isc_throw(DNSMessageFORMERR,
+ "TSIG record is being constructed from "
+ "incompatible RDATA: " << rdata.toText());
+ }
+ return (*tsig_rdata);
+}
+}
+
+TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass,
+ const RRTTL& ttl, const rdata::Rdata& rdata,
+ size_t length) :
+ key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length) {
+ if (rrclass != getClass()) {
+ isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass);
+ }
+ if (ttl != RRTTL(TSIG_TTL)) {
+ isc_throw(DNSMessageFORMERR, "Unexpected TSIG TTL: " << ttl);
+ }
+}
+
+const RRClass&
+TSIGRecord::getClass() {
+ return (RRClass::ANY());
+}
+
+const RRTTL&
+TSIGRecord::getTTL() {
+ static RRTTL ttl(TSIG_TTL);
+ return (ttl);
+}
+
+namespace {
+template <typename OUTPUT>
+void
+toWireCommon(OUTPUT& output, const rdata::any::TSIG& rdata) {
+ // RR type, class, TTL are fixed constants.
+ RRType::TSIG().toWire(output);
+ TSIGRecord::getClass().toWire(output);
+ output.writeUint32(TSIGRecord::TSIG_TTL);
+
+ // RDLEN
+ output.writeUint16(RDATA_COMMON_LEN + rdata.getAlgorithm().getLength() +
+ rdata.getMACSize() + rdata.getOtherLen());
+
+ // TSIG RDATA
+ rdata.toWire(output);
+}
+}
+
+uint32_t
+TSIGRecord::toWire(AbstractMessageRenderer& renderer) const {
+ // If adding the TSIG would exceed the size limit, don't do it.
+ if (renderer.getLength() + length_ > renderer.getLengthLimit()) {
+ renderer.setTruncated();
+ return (0);
+ }
+
+ // key name = owner. note that we disable compression.
+ renderer.writeName(key_name_, false);
+ toWireCommon(renderer, rdata_);
+ return (1);
+}
+
+uint32_t
+TSIGRecord::toWire(OutputBuffer& buffer) const {
+ key_name_.toWire(buffer);
+ toWireCommon(buffer, rdata_);
+ return (1);
+}
+
+std::string
+TSIGRecord::toText() const {
+ return (key_name_.toText() + " " + RRTTL(TSIG_TTL).toText() + " " +
+ getClass().toText() + " " + RRType::TSIG().toText() + " " +
+ rdata_.toText() + "\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const TSIGRecord& record) {
+ return (os << record.toText());
+}
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/tsigrecord.h b/src/lib/dns/tsigrecord.h
new file mode 100644
index 0000000..1ab71b4
--- /dev/null
+++ b/src/lib/dns/tsigrecord.h
@@ -0,0 +1,299 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TSIGRECORD_H
+#define TSIGRECORD_H
+
+#include <ostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <util/buffer.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+
+namespace isc {
+namespace dns {
+class AbstractMessageRenderer;
+
+/// TSIG resource record.
+///
+/// A \c TSIGRecord class object represents a TSIG resource record and is
+/// responsible for conversion to and from wire format TSIG record based on
+/// the protocol specification (RFC2845).
+/// This class is provided so that other classes and applications can handle
+/// TSIG without knowing protocol details of TSIG, such as that it uses a
+/// fixed constant of TTL.
+///
+/// \todo So the plan is to eventually provide the "from wire" constructor.
+/// It's not yet provided in the current phase of development.
+///
+/// \note
+/// This class could be a derived class of \c AbstractRRset. That way
+/// it would be able to be used in a polymorphic way; for example,
+/// an application can construct a TSIG RR by itself and insert it to a
+/// \c Message object as a generic RRset. On the other hand, it would mean
+/// this class would have to implement an \c RdataIterator (even though it
+/// can be done via straightforward forwarding) while the iterator is mostly
+/// redundant since there should be one and only one RDATA for a valid TSIG
+/// RR. Likewise, some methods such as \c setTTL() method wouldn't be well
+/// defined due to such special rules for TSIG as using a fixed TTL.
+/// Overall, TSIG is a very special RR type that simply uses the compatible
+/// resource record format, and it will be unlikely that a user wants to
+/// handle it through a generic interface in a polymorphic way.
+/// We therefore chose to define it as a separate class. This is also
+/// similar to why \c EDNS is a separate class.
+class TSIGRecord {
+public:
+ ///
+ /// \name Constructors
+ ///
+ /// We use the default copy constructor, default copy assignment operator,
+ /// (and default destructor) intentionally.
+ //@{
+ /// Constructor from TSIG key name and RDATA
+ ///
+ /// \exception std::bad_alloc Resource allocation for copying the name or
+ /// RDATA fails
+ TSIGRecord(const Name& key_name, const rdata::any::TSIG& tsig_rdata);
+
+ /// Constructor from resource record (RR) parameters.
+ ///
+ /// This constructor is intended to be used in the context of parsing
+ /// an incoming DNS message that contains a TSIG. The parser would
+ /// first extract the owner name, RR type (which is TSIG) class, TTL and
+ /// the TSIG RDATA from the message. This constructor is expected to
+ /// be given these RR parameters (except the RR type, because it must be
+ /// TSIG).
+ ///
+ /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0).
+ /// If the RR class or TTL is different from the expected one, this
+ /// implementation considers it an invalid record and throws an exception
+ /// of class \c DNSMessageFORMERR.
+ ///
+ /// \note This behavior is not specified in the protocol specification,
+ /// but this implementation rejects unexpected values for the following
+ /// reasons (but in any case, this won't matter much in practice as
+ /// RFC2848 clearly states these fields always have the fixed values and
+ /// any sane implementation of TSIG signer will follow that):
+ /// According to the RFC editor (in a private communication), the intended
+ /// use of the TSIG TTL field is to signal protocol extensions (currently
+ /// no such extension is defined), so this field may actually be
+ /// validly non 0 in future. However, until the implementation supports
+ /// that extension it may not always be able to handle the extended
+ /// TSIG as intended; the extension may even affect digest computation.
+ /// There's a related issue on this point. Different implementations
+ /// interpret the RFC in different ways on the received TTL when
+ /// digesting the message: BIND 9 uses the received value (even if
+ /// it's not 0) as part of the TSIG variables; NLnet Labs' LDNS and NSD
+ /// always use a fixed constant of 0 regardless of the received TTL value.
+ /// This means if and when an extension with non 0 TTL is introduced
+ /// there will be interoperability problems in the form of verification
+ /// failure. By explicitly rejecting it (and subsequently returning
+ /// a response with a format error) we can indicate the source of the
+ /// problem more clearly than a "bad signature" TSIG error, which can
+ /// happen for various reasons. On the other hand, rejecting unexpected
+ /// RR classes is mostly for consistency; the RFC lists these two fields
+ /// in the same way, so it makes more sense to handle them equally.
+ /// (BIND 9 rejects unexpected RR classes for TSIG, but that is part of
+ /// general check on RR classes on received RRs; it generally requests
+ /// all classes are the same, and if the protocol specifies the use of
+ /// a particular class for a particular type of RR, it requests that
+ /// class be used).
+ ///
+ /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of
+ /// class DNSMessageFORMERR will be thrown. When the caller is a
+ /// DNS message parser and builds \c rdata from incoming wire format
+ /// data as described above, this case happens when the RR class is
+ /// different from ANY (in the implementation, the type check takes place
+ /// before the explicit check against the RR class explained in the
+ /// previous paragraph).
+ ///
+ /// The \c length parameter is intended to be the length of the TSIG RR
+ /// (from the beginning of the owner name to the end of the RDATA) when
+ /// the caller is a DNS message parser. Note that it is the actual length
+ /// for the RR in the format; if the owner name or the algorithm name
+ /// (in the RDATA) is compressed (although the latter should not be
+ /// compressed according to RFC3597), the length must be the size of the
+ /// compressed data. The length is recorded inside the class and will
+ /// be returned via subsequent calls to \c getLength(). It's intended to
+ /// be used in the context TSIG verification; in the verify process
+ /// the MAC computation must be performed for the original data without
+ /// TSIG, so, to avoid parsing the entire data in the verify process
+ /// again, it's necessary to record information that can identify the
+ /// length to be digested for the MAC. This parameter serves for that
+ /// purpose.
+ ///
+ /// \note Since the constructor doesn't take the wire format data per se,
+ /// it doesn't (and cannot) check the validity of \c length, and simply
+ /// accepts any given value. It even accepts obviously invalid values
+ /// such as 0. It's caller's responsibility to provide a valid value of
+ /// length, and, the verifier's responsibility to use the length safely.
+ ///
+ /// <b>DISCUSSION:</b> this design is fragile in that it introduces
+ /// a tight coupling between message parsing and TSIG verification via
+ /// the \c TSIGRecord class. In terms of responsibility decoupling,
+ /// the ideal way to have \c TSIGRecord remember the entire wire data
+ /// along with the length of the TSIG. Then in the TSIG verification
+ /// we could refer to the necessary potion of data solely from a
+ /// \c TSIGRecord object. However, this approach would require expensive
+ /// heavy copy of the original data or introduce another kind of coupling
+ /// between the data holder and this class (if the original data is freed
+ /// while a \c TSIGRecord object referencing the data still exists, the
+ /// result will be catastrophic). As a "best current compromise", we
+ /// use the current design. We may reconsider it if it turns out to
+ /// cause a big problem or we come up with a better idea.
+ ///
+ /// \exception DNSMessageFORMERR Given RR parameters are invalid for TSIG.
+ /// \exception std::bad_alloc Internal resource allocation fails.
+ ///
+ /// \param name The owner name of the TSIG RR
+ /// \param rrclass The RR class of the RR. Must be \c RRClass::ANY()
+ /// (see above)
+ /// \param ttl The TTL of the RR. Must be 0 (see above)
+ /// \param rdata The RDATA of the RR. Must be of type \c any::TSIG.
+ /// \param length The size of the RR (see above)
+ TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl,
+ const rdata::Rdata& rdata, size_t length);
+ //@}
+
+ /// Return the owner name of the TSIG RR, which is the TSIG key name
+ ///
+ /// \exception None
+ const Name& getName() const {
+ return (key_name_);
+ }
+
+ /// Return the RDATA of the TSIG RR
+ ///
+ /// \exception None
+ const rdata::any::TSIG& getRdata() const {
+ return (rdata_);
+ }
+
+ /// \name Protocol constants and defaults
+ ///
+ //@{
+ /// Return the RR class of TSIG
+ ///
+ /// TSIG always uses the ANY RR class. This static method returns it,
+ /// when, though unlikely, an application wants to know which class TSIG
+ /// is supposed to use.
+ ///
+ /// \exception None
+ static const RRClass& getClass();
+
+ /// Return the TTL value of TSIG
+ ///
+ /// TSIG always uses 0 TTL. This static method returns it,
+ /// when, though unlikely, an application wants to know the TTL TSIG
+ /// is supposed to use.
+ ///
+ /// \exception None
+ static const RRTTL& getTTL();
+ //@}
+
+ /// Return the length of the TSIG record
+ ///
+ /// When constructed from the key name and RDATA, it is the length of
+ /// the record when it is rendered by the \c toWire() method.
+ ///
+ /// \note When constructed "from wire", that will mean the length of
+ /// the wire format data for the TSIG RR. The length will be necessary
+ /// to verify the message once parse is completed.
+ ///
+ /// \exception None
+ size_t getLength() const {
+ return (length_);
+ }
+
+ /// \brief Render the \c TSIG RR in the wire format.
+ ///
+ /// This method renders the TSIG record as a form of a DNS TSIG RR
+ /// via \c renderer, which encapsulates output buffer and other rendering
+ /// contexts.
+ ///
+ /// Normally this version of \c toWire() method tries to compress the
+ /// owner name of the RR whenever possible, but this method intentionally
+ /// skips owner name compression. This is due to a report that some
+ /// Windows clients refuse a TSIG if its owner name is compressed
+ /// (See http://marc.info/?l=bind-workers&m=126637138430819&w=2).
+ /// Reportedly this seemed to be specific to GSS-TSIG, but this
+ /// implementation skip compression regardless of the algorithm.
+ ///
+ /// If by adding the TSIG RR the message size would exceed the limit
+ /// maintained in \c renderer, this method skips rendering the RR
+ /// and returns 0 and mark \c renderer as "truncated" (so that a
+ /// subsequent call to \c isTruncated() on \c renderer will result in
+ /// \c true); otherwise it returns 1, which is the number of RR
+ /// rendered.
+ ///
+ /// \note If the caller follows the specification of adding TSIG
+ /// as described in RFC2845, this should not happen; the caller is
+ /// generally expected to leave a sufficient room in the message for
+ /// the TSIG. But this method checks the unexpected case nevertheless.
+ ///
+ /// \exception std::bad_alloc Internal resource allocation fails (this
+ /// should be rare).
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ /// \return 1 if the TSIG RR fits in the message size limit; otherwise 0.
+ uint32_t toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the \c TSIG RR in the wire format.
+ ///
+ /// This method is same as \c toWire(AbstractMessageRenderer&)const
+ /// except it renders the RR in an \c OutputBuffer and therefore
+ /// does not care about message size limit.
+ /// As a consequence it always returns 1.
+ uint32_t toWire(isc::util::OutputBuffer& buffer) const;
+
+ /// Convert the TSIG record to a string.
+ ///
+ /// The output format is the same as the result of \c toText() for
+ /// other normal types of RRsets (with always using the same RR class
+ /// and TTL). It also ends with a newline.
+ ///
+ /// \exception std::bad_alloc Internal resource allocation fails (this
+ /// should be rare).
+ ///
+ /// \return A string representation of \c TSIG record
+ std::string toText() const;
+
+ /// The TTL value to be used in TSIG RRs.
+ static const uint32_t TSIG_TTL = 0;
+ //@}
+
+private:
+ const Name key_name_;
+ const rdata::any::TSIG rdata_;
+ const size_t length_;
+};
+
+/// A pointer-like type pointing to a \c TSIGRecord object.
+typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr;
+
+/// A pointer-like type pointing to an immutable \c TSIGRecord object.
+typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
+
+/// Insert the \c TSIGRecord as a string into stream.
+///
+/// This method convert \c record into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param record A \c TSIGRecord object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const TSIGRecord& record);
+}
+}
+
+#endif // TSIGRECORD_H
diff --git a/src/lib/dns/txt_like.h b/src/lib/dns/txt_like.h
new file mode 100644
index 0000000..f88b1a6
--- /dev/null
+++ b/src/lib/dns/txt_like.h
@@ -0,0 +1,233 @@
+// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TXT_LIKE_H
+#define TXT_LIKE_H
+
+#include <dns/master_lexer.h>
+#include <dns/char_string.h>
+
+#include <stdint.h>
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT
+/// and SPF types.
+///
+/// This class implements the basic interfaces inherited by the TXT and SPF
+/// classes from the abstract \c rdata::Rdata class, and provides trivial
+/// accessors to TXT-like RDATA.
+template<class Type, uint16_t typeCode>class TXTLikeImpl {
+public:
+ /// \brief Constructor from wire-format data.
+ ///
+ /// \param buffer A buffer storing the wire format data.
+ /// \param rdata_len The length of the RDATA in bytes, normally expected
+ /// to be the value of the RDLENGTH field of the corresponding RR.
+ ///
+ /// <b>Exceptions</b>
+ ///
+ /// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum.
+ /// \c DNSMessageFORMERR is thrown if the RR is malformed.
+ TXTLikeImpl(util::InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len > MAX_RDLENGTH) {
+ isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
+ }
+
+ if (rdata_len == 0) { // note that this couldn't happen in the loop.
+ isc_throw(DNSMessageFORMERR, "Error in parsing " <<
+ RRType(typeCode) << " RDATA: 0-length character string");
+ }
+
+ do {
+ const uint8_t len = buffer.readUint8();
+ if (rdata_len < len + 1) {
+ isc_throw(DNSMessageFORMERR, "Error in parsing " <<
+ RRType(typeCode) <<
+ " RDATA: character string length is too large: " <<
+ static_cast<int>(len));
+ }
+ std::vector<uint8_t> data(len + 1);
+ data[0] = len;
+ buffer.readData(&data[0] + 1, len);
+ string_list_.push_back(data);
+
+ rdata_len -= (len + 1);
+ } while (rdata_len > 0);
+ }
+
+ /// \brief Constructor from string.
+ ///
+ /// \throw CharStringTooLong the parameter string length exceeds maximum.
+ /// \throw InvalidRdataText the method cannot process the parameter data
+ explicit TXTLikeImpl(const std::string& txtstr) {
+ std::istringstream ss(txtstr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ buildFromTextHelper(lexer);
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "Failed to construct " <<
+ RRType(typeCode) << " RDATA from '" << txtstr <<
+ "': extra new line");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct " <<
+ RRType(typeCode) << " RDATA from '" << txtstr << "': "
+ << ex.what());
+ }
+ }
+
+ /// \brief Constructor using the master lexer.
+ ///
+ /// \throw CharStringTooLong the parameter string length exceeds maximum.
+ /// \throw InvalidRdataText the method cannot process the parameter data
+ ///
+ /// \param lexer A \c MasterLexer object parsing a master file for this
+ /// RDATA.
+ TXTLikeImpl(MasterLexer& lexer) {
+ buildFromTextHelper(lexer);
+ }
+
+private:
+ void buildFromTextHelper(MasterLexer& lexer) {
+ while (true) {
+ const MasterToken& token = lexer.getNextToken(
+ MasterToken::QSTRING, true);
+ if (token.getType() != MasterToken::STRING &&
+ token.getType() != MasterToken::QSTRING) {
+ break;
+ }
+ string_list_.push_back(std::vector<uint8_t>());
+ stringToCharString(token.getStringRegion(), string_list_.back());
+ }
+
+ // Let upper layer handle eol/eof.
+ lexer.ungetToken();
+
+ if (string_list_.empty()) {
+ isc_throw(InvalidRdataText, "Failed to construct " <<
+ RRType(typeCode) << " RDATA: empty input");
+ }
+ }
+
+public:
+ /// \brief The copy constructor.
+ ///
+ /// Trivial for now, we could've used the default one.
+ TXTLikeImpl(const TXTLikeImpl& other) :
+ string_list_(other.string_list_)
+ {}
+
+ /// \brief Render the TXT-like data in the wire format to an OutputBuffer
+ /// object.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ void
+ toWire(util::OutputBuffer& buffer) const {
+ for (std::vector<std::vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
+ it != string_list_.end();
+ ++it)
+ {
+ buffer.writeData(&(*it)[0], (*it).size());
+ }
+ }
+
+ /// \brief Render the TXT-like data in the wire format to an
+ /// AbstractMessageRenderer object.
+ ///
+ /// \param renderer An output AbstractMessageRenderer to send the wire data
+ /// to.
+ void
+ toWire(AbstractMessageRenderer& renderer) const {
+ for (std::vector<std::vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
+ it != string_list_.end();
+ ++it)
+ {
+ renderer.writeData(&(*it)[0], (*it).size());
+ }
+ }
+
+ /// \brief Convert the TXT-like data to a string.
+ ///
+ /// \return A \c string object that represents the TXT-like data.
+ std::string
+ toText() const {
+ std::string s;
+
+ for (std::vector<std::vector<uint8_t> >::const_iterator it =
+ string_list_.begin(); it != string_list_.end(); ++it)
+ {
+ if (!s.empty()) {
+ s.push_back(' ');
+ }
+ s.push_back('"');
+ s.append(charStringToString(*it));
+ s.push_back('"');
+ }
+
+ return (s);
+ }
+
+ /// \brief Compare two instances of TXT-like RDATA.
+ ///
+ /// It is up to the caller to make sure that \c other is an object of the
+ /// same \c TXTLikeImpl class.
+ ///
+ /// \param other the right-hand operand to compare against.
+ /// \return < 0 if \c this would be sorted before \c other.
+ /// \return 0 if \c this is identical to \c other in terms of sorting
+ /// order.
+ /// \return > 0 if \c this would be sorted after \c other.
+ int
+ compare(const TXTLikeImpl& other) const {
+ // This implementation is not efficient. Revisit this (TBD).
+ util::OutputBuffer this_buffer(0);
+ toWire(this_buffer);
+ uint8_t const* const this_data = (uint8_t const*)this_buffer.getData();
+ const size_t this_len = this_buffer.getLength();
+
+ util::OutputBuffer other_buffer(0);
+ other.toWire(other_buffer);
+ uint8_t const* const other_data
+ = (uint8_t const*)other_buffer.getData();
+ const size_t other_len = other_buffer.getLength();
+
+ const size_t cmplen = std::min(this_len, other_len);
+ const int cmp = memcmp(this_data, other_data, cmplen);
+
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 :
+ (this_len < other_len) ? -1 : 1);
+ }
+ }
+
+private:
+ /// Note: this is a prototype version; we may reconsider
+ /// this representation later.
+ std::vector<std::vector<uint8_t> > string_list_;
+};
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+
+#endif // TXT_LIKE_H